mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
Compare commits
47 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 |
+1
-1
@@ -29,7 +29,7 @@ environment:
|
|||||||
tasks:
|
tasks:
|
||||||
- install_go: |
|
- install_go: |
|
||||||
mkdir -p /home/build/sdk
|
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: |
|
- prepare_toolchain: |
|
||||||
mkdir -p $APPLE_TOOLCHAIN_ROOT
|
mkdir -p $APPLE_TOOLCHAIN_ROOT
|
||||||
cd $APPLE_TOOLCHAIN_ROOT
|
cd $APPLE_TOOLCHAIN_ROOT
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ environment:
|
|||||||
tasks:
|
tasks:
|
||||||
- install_go: |
|
- install_go: |
|
||||||
mkdir -p /home/build/sdk
|
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: |
|
- test_gio: |
|
||||||
cd gio
|
cd gio
|
||||||
go test ./...
|
go test ./...
|
||||||
|
|||||||
+3
-1
@@ -40,7 +40,7 @@ secrets:
|
|||||||
tasks:
|
tasks:
|
||||||
- install_go: |
|
- install_go: |
|
||||||
mkdir -p /home/build/sdk
|
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: |
|
- check_gofmt: |
|
||||||
cd gio
|
cd gio
|
||||||
test -z "$(gofmt -s -l .)"
|
test -z "$(gofmt -s -l .)"
|
||||||
@@ -80,6 +80,8 @@ tasks:
|
|||||||
unzip -q ndk.zip
|
unzip -q ndk.zip
|
||||||
rm ndk.zip
|
rm ndk.zip
|
||||||
mv android-ndk-* ndk-bundle
|
mv android-ndk-* ndk-bundle
|
||||||
|
# sdkmanager needs lots of file descriptors
|
||||||
|
ulimit -n 10000
|
||||||
yes|sdkmanager --licenses
|
yes|sdkmanager --licenses
|
||||||
sdkmanager "platforms;android-31" "build-tools;32.0.0"
|
sdkmanager "platforms;android-31" "build-tools;32.0.0"
|
||||||
- test_android: |
|
- test_android: |
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ environment:
|
|||||||
tasks:
|
tasks:
|
||||||
- install_go: |
|
- install_go: |
|
||||||
mkdir -p /home/build/sdk
|
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
|
cd /home/build/sdk/go/src
|
||||||
./make.bash
|
./make.bash
|
||||||
- test_gio: |
|
- test_gio: |
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ type FrameEvent struct {
|
|||||||
type ViewEvent interface {
|
type ViewEvent interface {
|
||||||
implementsViewEvent()
|
implementsViewEvent()
|
||||||
ImplementsEvent()
|
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
|
// Insets is the space taken up by
|
||||||
|
|||||||
+4
-6
@@ -17,9 +17,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type androidContext struct {
|
type androidContext struct {
|
||||||
win *window
|
win *window
|
||||||
eglSurf egl.NativeWindowType
|
eglSurf egl.NativeWindowType
|
||||||
width, height int
|
|
||||||
*egl.Context
|
*egl.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,9 +44,8 @@ func (c *androidContext) Refresh() error {
|
|||||||
if err := c.win.setVisual(c.Context.VisualID()); err != nil {
|
if err := c.win.setVisual(c.Context.VisualID()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
win, width, height := c.win.nativeWindow()
|
win, _, _ := c.win.nativeWindow()
|
||||||
c.eglSurf = egl.NativeWindowType(unsafe.Pointer(win))
|
c.eglSurf = egl.NativeWindowType(unsafe.Pointer(win))
|
||||||
c.width, c.height = width, height
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +53,7 @@ func (c *androidContext) Lock() error {
|
|||||||
// The Android emulator creates a broken surface if it is not
|
// The Android emulator creates a broken surface if it is not
|
||||||
// created on the same thread as the context is made current.
|
// created on the same thread as the context is made current.
|
||||||
if c.eglSurf != nil {
|
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
|
return err
|
||||||
}
|
}
|
||||||
c.eglSurf = nil
|
c.eglSurf = nil
|
||||||
|
|||||||
+1
-1
@@ -69,7 +69,7 @@ func (c *wlContext) Refresh() error {
|
|||||||
}
|
}
|
||||||
c.eglWin = eglWin
|
c.eglWin = eglWin
|
||||||
eglSurf := egl.NativeWindowType(uintptr(unsafe.Pointer(eglWin)))
|
eglSurf := egl.NativeWindowType(uintptr(unsafe.Pointer(eglWin)))
|
||||||
if err := c.Context.CreateSurface(eglSurf, width, height); err != nil {
|
if err := c.Context.CreateSurface(eglSurf); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := c.Context.MakeCurrent(); err != nil {
|
if err := c.Context.MakeCurrent(); err != nil {
|
||||||
|
|||||||
+12
-17
@@ -5,8 +5,6 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
|
|
||||||
"gioui.org/internal/egl"
|
"gioui.org/internal/egl"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,6 +22,18 @@ func init() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
return &glContext{win: w, Context: ctx}, nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -37,21 +47,6 @@ func (c *glContext) Release() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *glContext) Refresh() error {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+12
-11
@@ -25,6 +25,18 @@ func init() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
return &x11Context{win: w, Context: ctx}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,17 +49,6 @@ func (c *x11Context) Release() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *x11Context) Refresh() error {
|
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
|
|
||||||
}
|
|
||||||
defer c.Context.ReleaseCurrent()
|
|
||||||
c.Context.EnableVSync(true)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
#include <OpenGL/OpenGL.h>
|
#include <OpenGL/OpenGL.h>
|
||||||
#include "_cgo_export.h"
|
#include "_cgo_export.h"
|
||||||
|
|
||||||
CALayer *gio_layerFactory(void) {
|
CALayer *gio_layerFactory(BOOL presentWithTrans) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
return [CALayer layer];
|
return [CALayer layer];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -266,6 +266,7 @@ const (
|
|||||||
WM_MOUSEWHEEL = 0x020A
|
WM_MOUSEWHEEL = 0x020A
|
||||||
WM_MOUSEHWHEEL = 0x020E
|
WM_MOUSEHWHEEL = 0x020E
|
||||||
WM_NCACTIVATE = 0x0086
|
WM_NCACTIVATE = 0x0086
|
||||||
|
WM_NCLBUTTONDBLCLK = 0x00A3
|
||||||
WM_NCHITTEST = 0x0084
|
WM_NCHITTEST = 0x0084
|
||||||
WM_NCCALCSIZE = 0x0083
|
WM_NCCALCSIZE = 0x0083
|
||||||
WM_PAINT = 0x000F
|
WM_PAINT = 0x000F
|
||||||
|
|||||||
+2
-1
@@ -60,8 +60,9 @@ static void presentDrawable(CFTypeRef queueRef, CFTypeRef drawableRef) {
|
|||||||
id<MTLDrawable> drawable = (__bridge id<MTLDrawable>)drawableRef;
|
id<MTLDrawable> drawable = (__bridge id<MTLDrawable>)drawableRef;
|
||||||
id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)queueRef;
|
id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)queueRef;
|
||||||
id<MTLCommandBuffer> cmdBuffer = [queue commandBuffer];
|
id<MTLCommandBuffer> cmdBuffer = [queue commandBuffer];
|
||||||
[cmdBuffer presentDrawable:drawable];
|
|
||||||
[cmdBuffer commit];
|
[cmdBuffer commit];
|
||||||
|
[cmdBuffer waitUntilScheduled];
|
||||||
|
[drawable present];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-1
@@ -21,7 +21,10 @@ Class gio_layerClass(void) {
|
|||||||
static CFTypeRef getMetalLayer(CFTypeRef viewRef) {
|
static CFTypeRef getMetalLayer(CFTypeRef viewRef) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
UIView *view = (__bridge UIView *)viewRef;
|
UIView *view = (__bridge UIView *)viewRef;
|
||||||
return CFBridgingRetain(view.layer);
|
CAMetalLayer *l = (CAMetalLayer *)view.layer;
|
||||||
|
l.needsDisplayOnBoundsChange = YES;
|
||||||
|
l.presentsWithTransaction = YES;
|
||||||
|
return CFBridgingRetain(l);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-2
@@ -12,9 +12,13 @@ package app
|
|||||||
#import <QuartzCore/CAMetalLayer.h>
|
#import <QuartzCore/CAMetalLayer.h>
|
||||||
#include <CoreFoundation/CoreFoundation.h>
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
|
|
||||||
CALayer *gio_layerFactory(void) {
|
CALayer *gio_layerFactory(BOOL presentWithTrans) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
return [CAMetalLayer layer];
|
CAMetalLayer *l = [CAMetalLayer layer];
|
||||||
|
l.autoresizingMask = kCALayerHeightSizable|kCALayerWidthSizable;
|
||||||
|
l.needsDisplayOnBoundsChange = YES;
|
||||||
|
l.presentsWithTransaction = presentWithTrans;
|
||||||
|
return l;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ type Config struct {
|
|||||||
CustomRenderer bool
|
CustomRenderer bool
|
||||||
// Decorated reports whether window decorations are provided automatically.
|
// Decorated reports whether window decorations are provided automatically.
|
||||||
Decorated bool
|
Decorated bool
|
||||||
|
// Focused reports whether has the keyboard focus.
|
||||||
|
Focused bool
|
||||||
// decoHeight is the height of the fallback decoration for platforms such
|
// decoHeight is the height of the fallback decoration for platforms such
|
||||||
// as Wayland that may need fallback client-side decorations.
|
// as Wayland that may need fallback client-side decorations.
|
||||||
decoHeight unit.Dp
|
decoHeight unit.Dp
|
||||||
@@ -171,19 +173,13 @@ type context interface {
|
|||||||
Unlock()
|
Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// basicDriver is the subset of [driver] that may be called even after
|
|
||||||
// a window is destroyed.
|
|
||||||
type basicDriver interface {
|
|
||||||
// Event blocks until an even is available and returns it.
|
|
||||||
Event() event.Event
|
|
||||||
// Invalidate requests a FrameEvent.
|
|
||||||
Invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
// driver is the interface for the platform implementation
|
// driver is the interface for the platform implementation
|
||||||
// of a window.
|
// of a window.
|
||||||
type driver interface {
|
type driver interface {
|
||||||
basicDriver
|
// 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,
|
// SetAnimating sets the animation flag. When the window is animating,
|
||||||
// FrameEvents are delivered as fast as the display can handle them.
|
// FrameEvents are delivered as fast as the display can handle them.
|
||||||
SetAnimating(anim bool)
|
SetAnimating(anim bool)
|
||||||
|
|||||||
+5
-1
@@ -593,7 +593,8 @@ func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong)
|
|||||||
//export Java_org_gioui_GioView_onFocusChange
|
//export Java_org_gioui_GioView_onFocusChange
|
||||||
func Java_org_gioui_GioView_onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) {
|
func Java_org_gioui_GioView_onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) {
|
||||||
w := cgo.Handle(view).Value().(*window)
|
w := cgo.Handle(view).Value().(*window)
|
||||||
w.processEvent(key.FocusEvent{Focus: focus == C.JNI_TRUE})
|
w.config.Focused = focus == C.JNI_TRUE
|
||||||
|
w.processEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Java_org_gioui_GioView_onWindowInsets
|
//export Java_org_gioui_GioView_onWindowInsets
|
||||||
@@ -1494,3 +1495,6 @@ func Java_org_gioui_Gio_scheduleMainFuncs(env *C.JNIEnv, cls C.jclass) {
|
|||||||
|
|
||||||
func (AndroidViewEvent) implementsViewEvent() {}
|
func (AndroidViewEvent) implementsViewEvent() {}
|
||||||
func (AndroidViewEvent) ImplementsEvent() {}
|
func (AndroidViewEvent) ImplementsEvent() {}
|
||||||
|
func (a AndroidViewEvent) Valid() bool {
|
||||||
|
return a != (AndroidViewEvent{})
|
||||||
|
}
|
||||||
|
|||||||
+5
-1
@@ -75,9 +75,13 @@ var displayLinks sync.Map
|
|||||||
|
|
||||||
var mainFuncs = make(chan func(), 1)
|
var mainFuncs = make(chan func(), 1)
|
||||||
|
|
||||||
|
func isMainThread() bool {
|
||||||
|
return bool(C.isMainThread())
|
||||||
|
}
|
||||||
|
|
||||||
// runOnMain runs the function on the main thread.
|
// runOnMain runs the function on the main thread.
|
||||||
func runOnMain(f func()) {
|
func runOnMain(f func()) {
|
||||||
if C.isMainThread() {
|
if isMainThread() {
|
||||||
f()
|
f()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+49
-2
@@ -12,6 +12,7 @@ package app
|
|||||||
#include <UIKit/UIKit.h>
|
#include <UIKit/UIKit.h>
|
||||||
#include <stdint.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);
|
__attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle);
|
||||||
|
|
||||||
struct drawParams {
|
struct drawParams {
|
||||||
@@ -21,6 +22,7 @@ struct drawParams {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static void writeClipboard(unichar *chars, NSUInteger length) {
|
static void writeClipboard(unichar *chars, NSUInteger length) {
|
||||||
|
#if !TARGET_OS_TV
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
NSString *s = [NSString string];
|
NSString *s = [NSString string];
|
||||||
if (length > 0) {
|
if (length > 0) {
|
||||||
@@ -29,13 +31,18 @@ static void writeClipboard(unichar *chars, NSUInteger length) {
|
|||||||
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
||||||
p.string = s;
|
p.string = s;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static CFTypeRef readClipboard(void) {
|
static CFTypeRef readClipboard(void) {
|
||||||
|
#if !TARGET_OS_TV
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
||||||
return (__bridge_retained CFTypeRef)p.string;
|
return (__bridge_retained CFTypeRef)p.string;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
return nil;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static void showTextInput(CFTypeRef viewRef) {
|
static void showTextInput(CFTypeRef viewRef) {
|
||||||
@@ -75,6 +82,7 @@ import "C"
|
|||||||
import (
|
import (
|
||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/cgo"
|
"runtime/cgo"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
@@ -211,7 +219,8 @@ func onDestroy(h C.uintptr_t) {
|
|||||||
//export onFocus
|
//export onFocus
|
||||||
func onFocus(h C.uintptr_t, focus int) {
|
func onFocus(h C.uintptr_t, focus int) {
|
||||||
w := viewFor(h)
|
w := viewFor(h)
|
||||||
w.ProcessEvent(key.FocusEvent{Focus: focus != 0})
|
w.config.Focused = focus != 0
|
||||||
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onLowMemory
|
//export onLowMemory
|
||||||
@@ -387,13 +396,51 @@ func newWindow(win *callbacks, options []Option) {
|
|||||||
<-mainWindow.windows
|
<-mainWindow.windows
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mainMode = mainModeUndefined
|
||||||
|
|
||||||
|
const (
|
||||||
|
mainModeUndefined = iota
|
||||||
|
mainModeExe
|
||||||
|
mainModeLibrary
|
||||||
|
)
|
||||||
|
|
||||||
func osMain() {
|
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
|
//export gio_runMain
|
||||||
func 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 (UIKitViewEvent) implementsViewEvent() {}
|
func (UIKitViewEvent) implementsViewEvent() {}
|
||||||
func (UIKitViewEvent) ImplementsEvent() {}
|
func (UIKitViewEvent) ImplementsEvent() {}
|
||||||
|
func (u UIKitViewEvent) Valid() bool {
|
||||||
|
return u != (UIKitViewEvent{})
|
||||||
|
}
|
||||||
|
|||||||
+25
-1
@@ -26,12 +26,13 @@ CGFloat _keyboardHeight;
|
|||||||
self.view.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
|
self.view.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
|
||||||
UIView *drawView = [[GioView alloc] initWithFrame:zeroFrame];
|
UIView *drawView = [[GioView alloc] initWithFrame:zeroFrame];
|
||||||
[self.view addSubview: drawView];
|
[self.view addSubview: drawView];
|
||||||
#ifndef TARGET_OS_TV
|
#if !TARGET_OS_TV
|
||||||
drawView.multipleTouchEnabled = YES;
|
drawView.multipleTouchEnabled = YES;
|
||||||
#endif
|
#endif
|
||||||
drawView.preservesSuperviewLayoutMargins = YES;
|
drawView.preservesSuperviewLayoutMargins = YES;
|
||||||
drawView.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
|
drawView.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
|
||||||
onCreate((__bridge CFTypeRef)drawView, (__bridge CFTypeRef)self);
|
onCreate((__bridge CFTypeRef)drawView, (__bridge CFTypeRef)self);
|
||||||
|
#if !TARGET_OS_TV
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
selector:@selector(keyboardWillChange:)
|
selector:@selector(keyboardWillChange:)
|
||||||
name:UIKeyboardWillShowNotification
|
name:UIKeyboardWillShowNotification
|
||||||
@@ -44,6 +45,7 @@ CGFloat _keyboardHeight;
|
|||||||
selector:@selector(keyboardWillHide:)
|
selector:@selector(keyboardWillHide:)
|
||||||
name:UIKeyboardWillHideNotification
|
name:UIKeyboardWillHideNotification
|
||||||
object:nil];
|
object:nil];
|
||||||
|
#endif
|
||||||
[[NSNotificationCenter defaultCenter] addObserver: self
|
[[NSNotificationCenter defaultCenter] addObserver: self
|
||||||
selector: @selector(applicationDidEnterBackground:)
|
selector: @selector(applicationDidEnterBackground:)
|
||||||
name: UIApplicationDidEnterBackgroundNotification
|
name: UIApplicationDidEnterBackgroundNotification
|
||||||
@@ -89,6 +91,7 @@ CGFloat _keyboardHeight;
|
|||||||
[super didReceiveMemoryWarning];
|
[super didReceiveMemoryWarning];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !TARGET_OS_TV
|
||||||
- (void)keyboardWillChange:(NSNotification *)note {
|
- (void)keyboardWillChange:(NSNotification *)note {
|
||||||
NSDictionary *userInfo = note.userInfo;
|
NSDictionary *userInfo = note.userInfo;
|
||||||
CGRect f = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
CGRect f = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
||||||
@@ -100,6 +103,7 @@ CGFloat _keyboardHeight;
|
|||||||
_keyboardHeight = 0.0;
|
_keyboardHeight = 0.0;
|
||||||
[self.view setNeedsLayout];
|
[self.view setNeedsLayout];
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
@end
|
@end
|
||||||
|
|
||||||
static void handleTouches(int last, GioView *view, NSSet<UITouch *> *touches, UIEvent *event) {
|
static void handleTouches(int last, GioView *view, NSSet<UITouch *> *touches, UIEvent *event) {
|
||||||
@@ -276,3 +280,23 @@ void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
|
|||||||
GioView *v = (__bridge GioView *)viewRef;
|
GioView *v = (__bridge GioView *)viewRef;
|
||||||
v.handle = handle;
|
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]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+7
-2
@@ -258,11 +258,13 @@ func (w *window) addEventListeners() {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.tarea, "focus", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.tarea, "focus", func(this js.Value, args []js.Value) interface{} {
|
||||||
w.processEvent(key.FocusEvent{Focus: true})
|
w.config.Focused = true
|
||||||
|
w.processEvent(ConfigEvent{Config: w.config})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.tarea, "blur", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.tarea, "blur", func(this js.Value, args []js.Value) interface{} {
|
||||||
w.processEvent(key.FocusEvent{Focus: false})
|
w.config.Focused = false
|
||||||
|
w.processEvent(ConfigEvent{Config: w.config})
|
||||||
w.blur()
|
w.blur()
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -820,3 +822,6 @@ func translateKey(k string) (key.Name, bool) {
|
|||||||
|
|
||||||
func (JSViewEvent) implementsViewEvent() {}
|
func (JSViewEvent) implementsViewEvent() {}
|
||||||
func (JSViewEvent) ImplementsEvent() {}
|
func (JSViewEvent) ImplementsEvent() {}
|
||||||
|
func (j JSViewEvent) Valid() bool {
|
||||||
|
return !(j.Element.IsNull() || j.Element.IsUndefined())
|
||||||
|
}
|
||||||
|
|||||||
+29
-5
@@ -40,7 +40,7 @@ import (
|
|||||||
#define MOUSE_SCROLL 4
|
#define MOUSE_SCROLL 4
|
||||||
|
|
||||||
__attribute__ ((visibility ("hidden"))) void gio_main(void);
|
__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"))) 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);
|
__attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle);
|
||||||
|
|
||||||
@@ -195,6 +195,14 @@ static void setScreenFrame(CFTypeRef windowRef, CGFloat x, CGFloat y, CGFloat w,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void resetLayerFrame(CFTypeRef viewRef) {
|
||||||
|
@autoreleasepool {
|
||||||
|
NSView* view = (__bridge NSView *)viewRef;
|
||||||
|
NSRect r = view.frame;
|
||||||
|
view.layer.frame = r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void hideWindow(CFTypeRef windowRef) {
|
static void hideWindow(CFTypeRef windowRef) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||||
@@ -456,6 +464,9 @@ func (w *window) Configure(options []Option) {
|
|||||||
C.setWindowStandardButtonHidden(window, C.NSWindowCloseButton, barTrans)
|
C.setWindowStandardButtonHidden(window, C.NSWindowCloseButton, barTrans)
|
||||||
C.setWindowStandardButtonHidden(window, C.NSWindowMiniaturizeButton, barTrans)
|
C.setWindowStandardButtonHidden(window, C.NSWindowMiniaturizeButton, barTrans)
|
||||||
C.setWindowStandardButtonHidden(window, C.NSWindowZoomButton, 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.ProcessEvent(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
@@ -612,8 +623,9 @@ func gio_onDraw(h C.uintptr_t) {
|
|||||||
//export gio_onFocus
|
//export gio_onFocus
|
||||||
func gio_onFocus(h C.uintptr_t, focus C.int) {
|
func gio_onFocus(h C.uintptr_t, focus C.int) {
|
||||||
w := windowFor(h)
|
w := windowFor(h)
|
||||||
w.ProcessEvent(key.FocusEvent{Focus: focus == 1})
|
|
||||||
w.SetCursor(w.cursor)
|
w.SetCursor(w.cursor)
|
||||||
|
w.config.Focused = focus == 1
|
||||||
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onChangeScreen
|
//export gio_onChangeScreen
|
||||||
@@ -913,7 +925,9 @@ func newWindow(win *callbacks, options []Option) {
|
|||||||
w.loop = newEventLoop(w.w, w.wakeup)
|
w.loop = newEventLoop(w.w, w.wakeup)
|
||||||
win.SetDriver(w)
|
win.SetDriver(w)
|
||||||
res <- struct{}{}
|
res <- struct{}{}
|
||||||
if err := w.init(); err != nil {
|
var cnf Config
|
||||||
|
cnf.apply(unit.Metric{}, options)
|
||||||
|
if err := w.init(cnf.CustomRenderer); err != nil {
|
||||||
w.ProcessEvent(DestroyEvent{Err: err})
|
w.ProcessEvent(DestroyEvent{Err: err})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -934,8 +948,12 @@ func newWindow(win *callbacks, options []Option) {
|
|||||||
<-res
|
<-res
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) init() error {
|
func (w *window) init(customRenderer bool) error {
|
||||||
view := C.gio_createView()
|
presentWithTrans := 1
|
||||||
|
if customRenderer {
|
||||||
|
presentWithTrans = 0
|
||||||
|
}
|
||||||
|
view := C.gio_createView(C.int(presentWithTrans))
|
||||||
if view == 0 {
|
if view == 0 {
|
||||||
return errors.New("newOSWindow: failed to create view")
|
return errors.New("newOSWindow: failed to create view")
|
||||||
}
|
}
|
||||||
@@ -964,6 +982,9 @@ func (w *window) init() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func osMain() {
|
func osMain() {
|
||||||
|
if !isMainThread() {
|
||||||
|
panic("app.Main must run on the main goroutine")
|
||||||
|
}
|
||||||
C.gio_main()
|
C.gio_main()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1053,3 +1074,6 @@ func convertMods(mods C.NSUInteger) key.Modifiers {
|
|||||||
|
|
||||||
func (AppKitViewEvent) implementsViewEvent() {}
|
func (AppKitViewEvent) implementsViewEvent() {}
|
||||||
func (AppKitViewEvent) ImplementsEvent() {}
|
func (AppKitViewEvent) ImplementsEvent() {}
|
||||||
|
func (a AppKitViewEvent) Valid() bool {
|
||||||
|
return a != (AppKitViewEvent{})
|
||||||
|
}
|
||||||
|
|||||||
+21
-7
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
#include "_cgo_export.h"
|
#include "_cgo_export.h"
|
||||||
|
|
||||||
__attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(void);
|
__attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(BOOL presentWithTrans);
|
||||||
|
|
||||||
@interface GioAppDelegate : NSObject<NSApplicationDelegate>
|
@interface GioAppDelegate : NSObject<NSApplicationDelegate>
|
||||||
@end
|
@end
|
||||||
@@ -16,6 +16,7 @@ __attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(void);
|
|||||||
|
|
||||||
@interface GioView : NSView <CALayerDelegate,NSTextInputClient>
|
@interface GioView : NSView <CALayerDelegate,NSTextInputClient>
|
||||||
@property uintptr_t handle;
|
@property uintptr_t handle;
|
||||||
|
@property BOOL presentWithTrans;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation GioWindowDelegate
|
@implementation GioWindowDelegate
|
||||||
@@ -47,13 +48,17 @@ __attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(void);
|
|||||||
}
|
}
|
||||||
- (void)windowDidBecomeKey:(NSNotification *)notification {
|
- (void)windowDidBecomeKey:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
GioView *view = (GioView *)window.contentView;
|
GioView *view = (GioView *)window.contentView;
|
||||||
gio_onFocus(view.handle, 1);
|
if ([window firstResponder] == view) {
|
||||||
|
gio_onFocus(view.handle, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
- (void)windowDidResignKey:(NSNotification *)notification {
|
- (void)windowDidResignKey:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
GioView *view = (GioView *)window.contentView;
|
GioView *view = (GioView *)window.contentView;
|
||||||
gio_onFocus(view.handle, 0);
|
if ([window firstResponder] == view) {
|
||||||
|
gio_onFocus(view.handle, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -84,7 +89,7 @@ static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFl
|
|||||||
gio_onDraw(self.handle);
|
gio_onDraw(self.handle);
|
||||||
}
|
}
|
||||||
- (CALayer *)makeBackingLayer {
|
- (CALayer *)makeBackingLayer {
|
||||||
CALayer *layer = gio_layerFactory();
|
CALayer *layer = gio_layerFactory(self.presentWithTrans);
|
||||||
layer.delegate = self;
|
layer.delegate = self;
|
||||||
return layer;
|
return layer;
|
||||||
}
|
}
|
||||||
@@ -205,6 +210,14 @@ static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFl
|
|||||||
- (void)dealloc {
|
- (void)dealloc {
|
||||||
gio_onDestroy(self.handle);
|
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
|
@end
|
||||||
|
|
||||||
// Delegates are weakly referenced from their peers. Nothing
|
// Delegates are weakly referenced from their peers. Nothing
|
||||||
@@ -380,10 +393,11 @@ CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGF
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CFTypeRef gio_createView(void) {
|
CFTypeRef gio_createView(int presentWithTrans) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
NSRect frame = NSMakeRect(0, 0, 0, 0);
|
NSRect frame = NSMakeRect(0, 0, 0, 0);
|
||||||
GioView* view = [[GioView alloc] initWithFrame:frame];
|
GioView* view = [[GioView alloc] initWithFrame:frame];
|
||||||
|
view.presentWithTrans = presentWithTrans ? YES : NO;
|
||||||
view.wantsLayer = YES;
|
view.wantsLayer = YES;
|
||||||
view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
|
view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
|
||||||
|
|
||||||
|
|||||||
+6
-24
@@ -9,7 +9,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"gioui.org/io/event"
|
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -22,6 +21,9 @@ type X11ViewEvent struct {
|
|||||||
|
|
||||||
func (X11ViewEvent) implementsViewEvent() {}
|
func (X11ViewEvent) implementsViewEvent() {}
|
||||||
func (X11ViewEvent) ImplementsEvent() {}
|
func (X11ViewEvent) ImplementsEvent() {}
|
||||||
|
func (x X11ViewEvent) Valid() bool {
|
||||||
|
return x != (X11ViewEvent{})
|
||||||
|
}
|
||||||
|
|
||||||
type WaylandViewEvent struct {
|
type WaylandViewEvent struct {
|
||||||
// Display is the *wl_display returned by wl_display_connect.
|
// Display is the *wl_display returned by wl_display_connect.
|
||||||
@@ -32,6 +34,9 @@ type WaylandViewEvent struct {
|
|||||||
|
|
||||||
func (WaylandViewEvent) implementsViewEvent() {}
|
func (WaylandViewEvent) implementsViewEvent() {}
|
||||||
func (WaylandViewEvent) ImplementsEvent() {}
|
func (WaylandViewEvent) ImplementsEvent() {}
|
||||||
|
func (w WaylandViewEvent) Valid() bool {
|
||||||
|
return w != (WaylandViewEvent{})
|
||||||
|
}
|
||||||
|
|
||||||
func osMain() {
|
func osMain() {
|
||||||
select {}
|
select {}
|
||||||
@@ -57,35 +62,12 @@ func newWindow(window *callbacks, options []Option) {
|
|||||||
errFirst = err
|
errFirst = err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
window.SetDriver(&dummyDriver{
|
|
||||||
win: window,
|
|
||||||
wakeups: make(chan event.Event, 1),
|
|
||||||
})
|
|
||||||
if errFirst == nil {
|
if errFirst == nil {
|
||||||
errFirst = errors.New("app: no window driver available")
|
errFirst = errors.New("app: no window driver available")
|
||||||
}
|
}
|
||||||
window.ProcessEvent(DestroyEvent{Err: errFirst})
|
window.ProcessEvent(DestroyEvent{Err: errFirst})
|
||||||
}
|
}
|
||||||
|
|
||||||
type dummyDriver struct {
|
|
||||||
win *callbacks
|
|
||||||
wakeups chan event.Event
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dummyDriver) Event() event.Event {
|
|
||||||
if e, ok := d.win.nextEvent(); ok {
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
return <-d.wakeups
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dummyDriver) Invalidate() {
|
|
||||||
select {
|
|
||||||
case d.wakeups <- wakeupEvent{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// xCursor contains mapping from pointer.Cursor to XCursor.
|
// xCursor contains mapping from pointer.Cursor to XCursor.
|
||||||
var xCursor = [...]string{
|
var xCursor = [...]string{
|
||||||
pointer.CursorDefault: "left_ptr",
|
pointer.CursorDefault: "left_ptr",
|
||||||
|
|||||||
+24
-26
@@ -216,9 +216,7 @@ type window struct {
|
|||||||
|
|
||||||
wakeups chan struct{}
|
wakeups chan struct{}
|
||||||
|
|
||||||
// invMu avoids the race between the destruction of disp and
|
closing bool
|
||||||
// Invalidate waking it up.
|
|
||||||
invMu sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type poller struct {
|
type poller struct {
|
||||||
@@ -556,7 +554,7 @@ func gio_onXdgSurfaceConfigure(data unsafe.Pointer, wmSurf *C.struct_xdg_surface
|
|||||||
//export gio_onToplevelClose
|
//export gio_onToplevelClose
|
||||||
func gio_onToplevelClose(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel) {
|
func gio_onToplevelClose(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel) {
|
||||||
w := callbackLoad(data).(*window)
|
w := callbackLoad(data).(*window)
|
||||||
w.close(nil)
|
w.closing = true
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onToplevelConfigure
|
//export gio_onToplevelConfigure
|
||||||
@@ -1139,7 +1137,7 @@ func (w *window) Perform(actions system.Action) {
|
|||||||
walkActions(actions, func(action system.Action) {
|
walkActions(actions, func(action system.Action) {
|
||||||
switch action {
|
switch action {
|
||||||
case system.ActionClose:
|
case system.ActionClose:
|
||||||
w.close(nil)
|
w.closing = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1222,7 +1220,8 @@ func gio_onKeyboardEnter(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, se
|
|||||||
w := callbackLoad(unsafe.Pointer(surf)).(*window)
|
w := callbackLoad(unsafe.Pointer(surf)).(*window)
|
||||||
s.keyboardFocus = w
|
s.keyboardFocus = w
|
||||||
s.disp.repeat.Stop(0)
|
s.disp.repeat.Stop(0)
|
||||||
w.ProcessEvent(key.FocusEvent{Focus: true})
|
w.config.Focused = true
|
||||||
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onKeyboardLeave
|
//export gio_onKeyboardLeave
|
||||||
@@ -1231,7 +1230,8 @@ func gio_onKeyboardLeave(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, se
|
|||||||
s.serial = serial
|
s.serial = serial
|
||||||
s.disp.repeat.Stop(0)
|
s.disp.repeat.Stop(0)
|
||||||
w := s.keyboardFocus
|
w := s.keyboardFocus
|
||||||
w.ProcessEvent(key.FocusEvent{Focus: false})
|
w.config.Focused = false
|
||||||
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onKeyboardKey
|
//export gio_onKeyboardKey
|
||||||
@@ -1364,6 +1364,9 @@ func gio_onFrameDone(data unsafe.Pointer, callback *C.struct_wl_callback, t C.ui
|
|||||||
func (w *window) close(err error) {
|
func (w *window) close(err error) {
|
||||||
w.ProcessEvent(WaylandViewEvent{})
|
w.ProcessEvent(WaylandViewEvent{})
|
||||||
w.ProcessEvent(DestroyEvent{Err: err})
|
w.ProcessEvent(DestroyEvent{Err: err})
|
||||||
|
w.destroy()
|
||||||
|
w.disp.destroy()
|
||||||
|
w.disp = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) dispatch() {
|
func (w *window) dispatch() {
|
||||||
@@ -1372,7 +1375,7 @@ func (w *window) dispatch() {
|
|||||||
w.w.Invalidate()
|
w.w.Invalidate()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := w.disp.dispatch(); err != nil {
|
if err := w.disp.dispatch(); err != nil || w.closing {
|
||||||
w.close(err)
|
w.close(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1397,13 +1400,6 @@ func (w *window) Event() event.Event {
|
|||||||
w.dispatch()
|
w.dispatch()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if _, destroy := evt.(DestroyEvent); destroy {
|
|
||||||
w.destroy()
|
|
||||||
w.invMu.Lock()
|
|
||||||
w.disp.destroy()
|
|
||||||
w.disp = nil
|
|
||||||
w.invMu.Unlock()
|
|
||||||
}
|
|
||||||
return evt
|
return evt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1414,11 +1410,7 @@ func (w *window) Invalidate() {
|
|||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.invMu.Lock()
|
w.disp.wakeup()
|
||||||
defer w.invMu.Unlock()
|
|
||||||
if w.disp != nil {
|
|
||||||
w.disp.wakeup()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Run(f func()) {
|
func (w *window) Run(f func()) {
|
||||||
@@ -1513,6 +1505,10 @@ func (d *wlDisplay) wakeup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) destroy() {
|
func (w *window) destroy() {
|
||||||
|
if w.lastFrameCallback != nil {
|
||||||
|
C.wl_callback_destroy(w.lastFrameCallback)
|
||||||
|
w.lastFrameCallback = nil
|
||||||
|
}
|
||||||
if w.cursor.surf != nil {
|
if w.cursor.surf != nil {
|
||||||
C.wl_surface_destroy(w.cursor.surf)
|
C.wl_surface_destroy(w.cursor.surf)
|
||||||
}
|
}
|
||||||
@@ -1637,6 +1633,14 @@ func (w *window) flushScroll() {
|
|||||||
if total == (f32.Point{}) {
|
if total == (f32.Point{}) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
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{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Scroll,
|
Kind: pointer.Scroll,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
@@ -1646,12 +1650,6 @@ func (w *window) flushScroll() {
|
|||||||
Time: w.scroll.time,
|
Time: w.scroll.time,
|
||||||
Modifiers: w.disp.xkb.Modifiers(),
|
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) {
|
func (w *window) onPointerMotion(x, y C.wl_fixed_t, t C.uint32_t) {
|
||||||
|
|||||||
+14
-18
@@ -50,14 +50,10 @@ type window struct {
|
|||||||
placement *windows.WindowPlacement
|
placement *windows.WindowPlacement
|
||||||
|
|
||||||
animating bool
|
animating bool
|
||||||
focused bool
|
|
||||||
|
|
||||||
borderSize image.Point
|
borderSize image.Point
|
||||||
config Config
|
config Config
|
||||||
loop *eventLoop
|
loop *eventLoop
|
||||||
|
|
||||||
// invMu avoids the race between destroying the window and Invalidate.
|
|
||||||
invMu sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const _WM_WAKEUP = windows.WM_USER + iota
|
const _WM_WAKEUP = windows.WM_USER + iota
|
||||||
@@ -269,11 +265,11 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
Kind: pointer.Cancel,
|
Kind: pointer.Cancel,
|
||||||
})
|
})
|
||||||
case windows.WM_SETFOCUS:
|
case windows.WM_SETFOCUS:
|
||||||
w.focused = true
|
w.config.Focused = true
|
||||||
w.ProcessEvent(key.FocusEvent{Focus: true})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
case windows.WM_KILLFOCUS:
|
case windows.WM_KILLFOCUS:
|
||||||
w.focused = false
|
w.config.Focused = false
|
||||||
w.ProcessEvent(key.FocusEvent{Focus: false})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
case windows.WM_NCHITTEST:
|
case windows.WM_NCHITTEST:
|
||||||
if w.config.Decorated {
|
if w.config.Decorated {
|
||||||
// Let the system handle it.
|
// Let the system handle it.
|
||||||
@@ -305,10 +301,8 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
windows.ReleaseDC(w.hdc)
|
windows.ReleaseDC(w.hdc)
|
||||||
w.hdc = 0
|
w.hdc = 0
|
||||||
}
|
}
|
||||||
w.invMu.Lock()
|
|
||||||
// The system destroys the HWND for us.
|
// The system destroys the HWND for us.
|
||||||
w.hwnd = 0
|
w.hwnd = 0
|
||||||
w.invMu.Unlock()
|
|
||||||
windows.PostQuitMessage(0)
|
windows.PostQuitMessage(0)
|
||||||
case windows.WM_NCCALCSIZE:
|
case windows.WM_NCCALCSIZE:
|
||||||
if w.config.Decorated {
|
if w.config.Decorated {
|
||||||
@@ -332,6 +326,12 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
mi := windows.GetMonitorInfo(w.hwnd)
|
mi := windows.GetMonitorInfo(w.hwnd)
|
||||||
szp.Rgrc[0] = mi.WorkArea
|
szp.Rgrc[0] = mi.WorkArea
|
||||||
return 0
|
return 0
|
||||||
|
case windows.WM_NCLBUTTONDBLCLK:
|
||||||
|
if !w.config.Decorated {
|
||||||
|
// Override Windows behaviour when we
|
||||||
|
// draw decorations.
|
||||||
|
return 0
|
||||||
|
}
|
||||||
case windows.WM_PAINT:
|
case windows.WM_PAINT:
|
||||||
w.draw(true)
|
w.draw(true)
|
||||||
case windows.WM_SIZE:
|
case windows.WM_SIZE:
|
||||||
@@ -496,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) {
|
func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) {
|
||||||
if !w.focused {
|
if !w.config.Focused {
|
||||||
windows.SetFocus(w.hwnd)
|
windows.SetFocus(w.hwnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -621,13 +621,6 @@ func (w *window) Frame(frame *op.Ops) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) wakeup() {
|
func (w *window) wakeup() {
|
||||||
w.invMu.Lock()
|
|
||||||
defer w.invMu.Unlock()
|
|
||||||
if w.hwnd == 0 {
|
|
||||||
w.loop.Wakeup()
|
|
||||||
w.loop.FlushEvents()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil {
|
if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -994,3 +987,6 @@ func configForDPI(dpi int) unit.Metric {
|
|||||||
|
|
||||||
func (Win32ViewEvent) implementsViewEvent() {}
|
func (Win32ViewEvent) implementsViewEvent() {}
|
||||||
func (Win32ViewEvent) ImplementsEvent() {}
|
func (Win32ViewEvent) ImplementsEvent() {}
|
||||||
|
func (w Win32ViewEvent) Valid() bool {
|
||||||
|
return w != (Win32ViewEvent{})
|
||||||
|
}
|
||||||
|
|||||||
+14
-16
@@ -113,9 +113,6 @@ type x11Window struct {
|
|||||||
wakeups chan struct{}
|
wakeups chan struct{}
|
||||||
handler x11EventHandler
|
handler x11EventHandler
|
||||||
buf [100]byte
|
buf [100]byte
|
||||||
|
|
||||||
// invMy avoids the race between destroy and Invalidate.
|
|
||||||
invMu sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -389,6 +386,7 @@ func (w *x11Window) ProcessEvent(e event.Event) {
|
|||||||
func (w *x11Window) shutdown(err error) {
|
func (w *x11Window) shutdown(err error) {
|
||||||
w.ProcessEvent(X11ViewEvent{})
|
w.ProcessEvent(X11ViewEvent{})
|
||||||
w.ProcessEvent(DestroyEvent{Err: err})
|
w.ProcessEvent(DestroyEvent{Err: err})
|
||||||
|
w.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *x11Window) Event() event.Event {
|
func (w *x11Window) Event() event.Event {
|
||||||
@@ -398,9 +396,6 @@ func (w *x11Window) Event() event.Event {
|
|||||||
w.dispatch()
|
w.dispatch()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if _, destroy := evt.(DestroyEvent); destroy {
|
|
||||||
w.destroy()
|
|
||||||
}
|
|
||||||
return evt
|
return evt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -418,11 +413,6 @@ func (w *x11Window) Invalidate() {
|
|||||||
case w.wakeups <- struct{}{}:
|
case w.wakeups <- struct{}{}:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
w.invMu.Lock()
|
|
||||||
defer w.invMu.Unlock()
|
|
||||||
if w.x == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, err := syscall.Write(w.notify.write, x11OneByte); err != nil && err != syscall.EAGAIN {
|
if _, err := syscall.Write(w.notify.write, x11OneByte); err != nil && err != syscall.EAGAIN {
|
||||||
panic(fmt.Errorf("failed to write to pipe: %v", err))
|
panic(fmt.Errorf("failed to write to pipe: %v", err))
|
||||||
}
|
}
|
||||||
@@ -464,7 +454,12 @@ func (w *x11Window) dispatch() {
|
|||||||
// Check for pending draw events before checking animation or blocking.
|
// Check for pending draw events before checking animation or blocking.
|
||||||
// This fixes an issue on Xephyr where on startup XPending() > 0 but
|
// This fixes an issue on Xephyr where on startup XPending() > 0 but
|
||||||
// poll will still block. This also prevents no-op calls to poll.
|
// poll will still block. This also prevents no-op calls to poll.
|
||||||
if syn = w.handler.handleEvents(); !syn {
|
syn = w.handler.handleEvents()
|
||||||
|
if w.x == nil {
|
||||||
|
// handleEvents received a close request and destroyed the window.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !syn {
|
||||||
anim = w.animating
|
anim = w.animating
|
||||||
if !anim {
|
if !anim {
|
||||||
// Clear poll events.
|
// Clear poll events.
|
||||||
@@ -476,6 +471,9 @@ func (w *x11Window) dispatch() {
|
|||||||
switch {
|
switch {
|
||||||
case *xEvents&syscall.POLLIN != 0:
|
case *xEvents&syscall.POLLIN != 0:
|
||||||
syn = w.handler.handleEvents()
|
syn = w.handler.handleEvents()
|
||||||
|
if w.x == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
case *xEvents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
case *xEvents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -503,8 +501,6 @@ func (w *x11Window) dispatch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *x11Window) destroy() {
|
func (w *x11Window) destroy() {
|
||||||
w.invMu.Lock()
|
|
||||||
defer w.invMu.Unlock()
|
|
||||||
if w.notify.write != 0 {
|
if w.notify.write != 0 {
|
||||||
syscall.Close(w.notify.write)
|
syscall.Close(w.notify.write)
|
||||||
w.notify.write = 0
|
w.notify.write = 0
|
||||||
@@ -657,9 +653,11 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
// redraw only on the last expose event
|
// redraw only on the last expose event
|
||||||
redraw = (*C.XExposeEvent)(unsafe.Pointer(xev)).count == 0
|
redraw = (*C.XExposeEvent)(unsafe.Pointer(xev)).count == 0
|
||||||
case C.FocusIn:
|
case C.FocusIn:
|
||||||
w.ProcessEvent(key.FocusEvent{Focus: true})
|
w.config.Focused = true
|
||||||
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
case C.FocusOut:
|
case C.FocusOut:
|
||||||
w.ProcessEvent(key.FocusEvent{Focus: false})
|
w.config.Focused = false
|
||||||
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
case C.ConfigureNotify: // window configuration change
|
case C.ConfigureNotify: // window configuration change
|
||||||
cevt := (*C.XConfigureEvent)(unsafe.Pointer(xev))
|
cevt := (*C.XConfigureEvent)(unsafe.Pointer(xev))
|
||||||
if sz := image.Pt(int(cevt.width), int(cevt.height)); sz != w.config.Size {
|
if sz := image.Pt(int(cevt.width), int(cevt.height)); sz != w.config.Size {
|
||||||
|
|||||||
+129
-72
@@ -7,7 +7,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"reflect"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -36,14 +35,15 @@ type Option func(unit.Metric, *Config)
|
|||||||
|
|
||||||
// Window represents an operating system window.
|
// Window represents an operating system window.
|
||||||
//
|
//
|
||||||
// The zero-value Window is useful, and calling any method on
|
// The zero-value Window is useful; the GUI window is created and shown the first
|
||||||
// it creates and shows a new GUI window. On iOS or Android,
|
// time the [Event] method is called. On iOS or Android, the first Window represents
|
||||||
// the first Window represents the the window previously
|
// the window previously created by the platform.
|
||||||
// created by the platform.
|
|
||||||
//
|
//
|
||||||
// More than one Window is not supported on iOS, Android,
|
// More than one Window is not supported on iOS, Android, WebAssembly.
|
||||||
// WebAssembly.
|
|
||||||
type Window struct {
|
type Window struct {
|
||||||
|
initialOpts []Option
|
||||||
|
initialActions []system.Action
|
||||||
|
|
||||||
ctx context
|
ctx context
|
||||||
gpu gpu.GPU
|
gpu gpu.GPU
|
||||||
// timer tracks the delayed invalidate goroutine.
|
// timer tracks the delayed invalidate goroutine.
|
||||||
@@ -89,13 +89,15 @@ type Window struct {
|
|||||||
}
|
}
|
||||||
imeState editorState
|
imeState editorState
|
||||||
driver driver
|
driver driver
|
||||||
// basic is the driver interface that is needed even after the window is gone.
|
|
||||||
basic basicDriver
|
// invMu protects mayInvalidate.
|
||||||
once sync.Once
|
invMu sync.Mutex
|
||||||
|
mayInvalidate bool
|
||||||
|
|
||||||
// coalesced tracks the most recent events waiting to be delivered
|
// coalesced tracks the most recent events waiting to be delivered
|
||||||
// to the client.
|
// to the client.
|
||||||
coalesced eventSummary
|
coalesced eventSummary
|
||||||
// frame tracks the most recently frame event.
|
// frame tracks the most recent frame event.
|
||||||
lastFrame struct {
|
lastFrame struct {
|
||||||
sync bool
|
sync bool
|
||||||
size image.Point
|
size image.Point
|
||||||
@@ -105,11 +107,12 @@ type Window struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type eventSummary struct {
|
type eventSummary struct {
|
||||||
wakeup bool
|
wakeup bool
|
||||||
cfg *ConfigEvent
|
cfg *ConfigEvent
|
||||||
view *ViewEvent
|
view *ViewEvent
|
||||||
frame *frameEvent
|
frame *frameEvent
|
||||||
destroy *DestroyEvent
|
framePending bool
|
||||||
|
destroy *DestroyEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
type callbacks struct {
|
type callbacks struct {
|
||||||
@@ -216,6 +219,7 @@ func (w *Window) frame(frame *op.Ops, viewport image.Point) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) processFrame(frame *op.Ops, ack chan<- struct{}) {
|
func (w *Window) processFrame(frame *op.Ops, ack chan<- struct{}) {
|
||||||
|
w.coalesced.framePending = false
|
||||||
wrapper := &w.decorations.Ops
|
wrapper := &w.decorations.Ops
|
||||||
off := op.Offset(w.lastFrame.off).Push(wrapper)
|
off := op.Offset(w.lastFrame.off).Push(wrapper)
|
||||||
ops.AddCall(&wrapper.Internal, &frame.Internal, ops.PC{}, ops.PCFor(&frame.Internal))
|
ops.AddCall(&wrapper.Internal, &frame.Internal, ops.PC{}, ops.PCFor(&frame.Internal))
|
||||||
@@ -273,8 +277,12 @@ func (w *Window) updateState() {
|
|||||||
//
|
//
|
||||||
// Invalidate is safe for concurrent use.
|
// Invalidate is safe for concurrent use.
|
||||||
func (w *Window) Invalidate() {
|
func (w *Window) Invalidate() {
|
||||||
w.init()
|
w.invMu.Lock()
|
||||||
w.basic.Invalidate()
|
defer w.invMu.Unlock()
|
||||||
|
if w.mayInvalidate {
|
||||||
|
w.mayInvalidate = false
|
||||||
|
w.driver.Invalidate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Option applies the options to the window. The options are hints; the platform is
|
// Option applies the options to the window. The options are hints; the platform is
|
||||||
@@ -283,7 +291,10 @@ func (w *Window) Option(opts ...Option) {
|
|||||||
if len(opts) == 0 {
|
if len(opts) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.init(opts...)
|
if w.driver == nil {
|
||||||
|
w.initialOpts = append(w.initialOpts, opts...)
|
||||||
|
return
|
||||||
|
}
|
||||||
w.Run(func() {
|
w.Run(func() {
|
||||||
cnf := Config{Decorated: w.decorations.enabled}
|
cnf := Config{Decorated: w.decorations.enabled}
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
@@ -302,16 +313,14 @@ func (w *Window) Option(opts ...Option) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run f in the same thread as the native window event loop, and wait for f to
|
// Run f in the same thread as the native window event loop, and wait for f to
|
||||||
// return or the window to close. Run is guaranteed not to deadlock if it is
|
// return or the window to close. If the window has not yet been created,
|
||||||
// invoked during the handling of a [ViewEvent], [FrameEvent],
|
// Run calls f directly.
|
||||||
// [StageEvent]; call Run in a separate goroutine to avoid deadlock in all
|
|
||||||
// other cases.
|
|
||||||
//
|
//
|
||||||
// Note that most programs should not call Run; configuring a Window with
|
// Note that most programs should not call Run; configuring a Window with
|
||||||
// [CustomRenderer] is a notable exception.
|
// [CustomRenderer] is a notable exception.
|
||||||
func (w *Window) Run(f func()) {
|
func (w *Window) Run(f func()) {
|
||||||
w.init()
|
|
||||||
if w.driver == nil {
|
if w.driver == nil {
|
||||||
|
f()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
@@ -377,11 +386,11 @@ func (w *Window) setNextFrame(at time.Time) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *callbacks) SetDriver(d basicDriver) {
|
func (c *callbacks) SetDriver(d driver) {
|
||||||
c.w.basic = d
|
if d == nil {
|
||||||
if d, ok := d.(driver); ok {
|
panic("nil driver")
|
||||||
c.w.driver = d
|
|
||||||
}
|
}
|
||||||
|
c.w.driver = d
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *callbacks) ProcessFrame(frame *op.Ops, ack chan<- struct{}) {
|
func (c *callbacks) ProcessFrame(frame *op.Ops, ack chan<- struct{}) {
|
||||||
@@ -548,10 +557,20 @@ func (c *callbacks) Invalidate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *callbacks) nextEvent() (event.Event, bool) {
|
func (c *callbacks) nextEvent() (event.Event, bool) {
|
||||||
s := &c.w.coalesced
|
return c.w.nextEvent()
|
||||||
// Every event counts as a wakeup.
|
}
|
||||||
defer func() { s.wakeup = false }()
|
|
||||||
|
func (w *Window) nextEvent() (event.Event, bool) {
|
||||||
|
s := &w.coalesced
|
||||||
|
defer func() {
|
||||||
|
// Every event counts as a wakeup.
|
||||||
|
s.wakeup = false
|
||||||
|
}()
|
||||||
switch {
|
switch {
|
||||||
|
case s.framePending:
|
||||||
|
// If the user didn't call FrameEvent.Event, process
|
||||||
|
// an empty frame.
|
||||||
|
w.processFrame(new(op.Ops), nil)
|
||||||
case s.view != nil:
|
case s.view != nil:
|
||||||
e := *s.view
|
e := *s.view
|
||||||
s.view = nil
|
s.view = nil
|
||||||
@@ -568,10 +587,14 @@ func (c *callbacks) nextEvent() (event.Event, bool) {
|
|||||||
case s.frame != nil:
|
case s.frame != nil:
|
||||||
e := *s.frame
|
e := *s.frame
|
||||||
s.frame = nil
|
s.frame = nil
|
||||||
|
s.framePending = true
|
||||||
return e.FrameEvent, true
|
return e.FrameEvent, true
|
||||||
case s.wakeup:
|
case s.wakeup:
|
||||||
return wakeupEvent{}, true
|
return wakeupEvent{}, true
|
||||||
}
|
}
|
||||||
|
w.invMu.Lock()
|
||||||
|
defer w.invMu.Unlock()
|
||||||
|
w.mayInvalidate = w.driver != nil
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -615,6 +638,9 @@ func (w *Window) processEvent(e event.Event) bool {
|
|||||||
w.coalesced.frame = &e2
|
w.coalesced.frame = &e2
|
||||||
case DestroyEvent:
|
case DestroyEvent:
|
||||||
w.destroyGPU()
|
w.destroyGPU()
|
||||||
|
w.invMu.Lock()
|
||||||
|
w.mayInvalidate = false
|
||||||
|
w.invMu.Unlock()
|
||||||
w.driver = nil
|
w.driver = nil
|
||||||
if q := w.timer.quit; q != nil {
|
if q := w.timer.quit; q != nil {
|
||||||
q <- struct{}{}
|
q <- struct{}{}
|
||||||
@@ -622,7 +648,7 @@ func (w *Window) processEvent(e event.Event) bool {
|
|||||||
}
|
}
|
||||||
w.coalesced.destroy = &e2
|
w.coalesced.destroy = &e2
|
||||||
case ViewEvent:
|
case ViewEvent:
|
||||||
if reflect.ValueOf(e2).IsZero() && w.gpu != nil {
|
if !e2.Valid() && w.gpu != nil {
|
||||||
w.ctx.Lock()
|
w.ctx.Lock()
|
||||||
w.gpu.Release()
|
w.gpu.Release()
|
||||||
w.gpu = nil
|
w.gpu = nil
|
||||||
@@ -630,9 +656,19 @@ func (w *Window) processEvent(e event.Event) bool {
|
|||||||
}
|
}
|
||||||
w.coalesced.view = &e2
|
w.coalesced.view = &e2
|
||||||
case ConfigEvent:
|
case ConfigEvent:
|
||||||
|
wasFocused := w.decorations.Config.Focused
|
||||||
w.decorations.Config = e2.Config
|
w.decorations.Config = e2.Config
|
||||||
e2.Config = w.effectiveConfig()
|
e2.Config = w.effectiveConfig()
|
||||||
w.coalesced.cfg = &e2
|
w.coalesced.cfg = &e2
|
||||||
|
if f := w.decorations.Config.Focused; f != wasFocused {
|
||||||
|
w.queue.Queue(key.FocusEvent{Focus: f})
|
||||||
|
}
|
||||||
|
t, handled := w.queue.WakeupTime()
|
||||||
|
if handled {
|
||||||
|
w.setNextFrame(t)
|
||||||
|
w.updateAnimation()
|
||||||
|
}
|
||||||
|
return handled
|
||||||
case event.Event:
|
case event.Event:
|
||||||
focusDir := key.FocusDirection(-1)
|
focusDir := key.FocusDirection(-1)
|
||||||
if e, ok := e2.(key.Event); ok && e.State == key.Press {
|
if e, ok := e2.(key.Event); ok && e.State == key.Press {
|
||||||
@@ -673,48 +709,61 @@ func (w *Window) processEvent(e event.Event) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Event blocks until an event is received from the window, such as
|
// Event blocks until an event is received from the window, such as
|
||||||
// [FrameEvent], or until [Invalidate] is called.
|
// [FrameEvent], or until [Invalidate] is called. The window is created
|
||||||
|
// and shown the first time Event is called.
|
||||||
func (w *Window) Event() event.Event {
|
func (w *Window) Event() event.Event {
|
||||||
w.init()
|
if w.driver == nil {
|
||||||
return w.basic.Event()
|
w.init()
|
||||||
|
}
|
||||||
|
if w.driver == nil {
|
||||||
|
e, ok := w.nextEvent()
|
||||||
|
if !ok {
|
||||||
|
panic("window initializion failed without a DestroyEvent")
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return w.driver.Event()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) init(initial ...Option) {
|
func (w *Window) init() {
|
||||||
w.once.Do(func() {
|
debug.Parse()
|
||||||
debug.Parse()
|
// Measure decoration height.
|
||||||
// Measure decoration height.
|
deco := new(widget.Decorations)
|
||||||
deco := new(widget.Decorations)
|
theme := material.NewTheme()
|
||||||
theme := material.NewTheme()
|
theme.Shaper = text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Regular()))
|
||||||
theme.Shaper = text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Regular()))
|
decoStyle := material.Decorations(theme, deco, 0, "")
|
||||||
decoStyle := material.Decorations(theme, deco, 0, "")
|
gtx := layout.Context{
|
||||||
gtx := layout.Context{
|
Ops: new(op.Ops),
|
||||||
Ops: new(op.Ops),
|
// Measure in Dp.
|
||||||
// Measure in Dp.
|
Metric: unit.Metric{},
|
||||||
Metric: unit.Metric{},
|
}
|
||||||
}
|
// Allow plenty of space.
|
||||||
// Allow plenty of space.
|
gtx.Constraints.Max.Y = 200
|
||||||
gtx.Constraints.Max.Y = 200
|
dims := decoStyle.Layout(gtx)
|
||||||
dims := decoStyle.Layout(gtx)
|
decoHeight := unit.Dp(dims.Size.Y)
|
||||||
decoHeight := unit.Dp(dims.Size.Y)
|
defaultOptions := []Option{
|
||||||
defaultOptions := []Option{
|
Size(800, 600),
|
||||||
Size(800, 600),
|
Title("Gio"),
|
||||||
Title("Gio"),
|
Decorated(true),
|
||||||
Decorated(true),
|
decoHeightOpt(decoHeight),
|
||||||
decoHeightOpt(decoHeight),
|
}
|
||||||
}
|
options := append(defaultOptions, w.initialOpts...)
|
||||||
options := append(defaultOptions, initial...)
|
w.initialOpts = nil
|
||||||
var cnf Config
|
var cnf Config
|
||||||
cnf.apply(unit.Metric{}, options)
|
cnf.apply(unit.Metric{}, options)
|
||||||
|
|
||||||
w.nocontext = cnf.CustomRenderer
|
w.nocontext = cnf.CustomRenderer
|
||||||
w.decorations.Theme = theme
|
w.decorations.Theme = theme
|
||||||
w.decorations.Decorations = deco
|
w.decorations.Decorations = deco
|
||||||
w.decorations.enabled = cnf.Decorated
|
w.decorations.enabled = cnf.Decorated
|
||||||
w.decorations.height = decoHeight
|
w.decorations.height = decoHeight
|
||||||
w.imeState.compose = key.Range{Start: -1, End: -1}
|
w.imeState.compose = key.Range{Start: -1, End: -1}
|
||||||
w.semantic.ids = make(map[input.SemanticID]input.SemanticNode)
|
w.semantic.ids = make(map[input.SemanticID]input.SemanticNode)
|
||||||
newWindow(&callbacks{w}, options)
|
newWindow(&callbacks{w}, options)
|
||||||
})
|
for _, acts := range w.initialActions {
|
||||||
|
w.Perform(acts)
|
||||||
|
}
|
||||||
|
w.initialActions = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) updateCursor() {
|
func (w *Window) updateCursor() {
|
||||||
@@ -762,8 +811,12 @@ func (w *Window) decorate(e FrameEvent, o *op.Ops) image.Point {
|
|||||||
}
|
}
|
||||||
// Update the window based on the actions on the decorations.
|
// Update the window based on the actions on the decorations.
|
||||||
opts, acts := splitActions(deco.Update(gtx))
|
opts, acts := splitActions(deco.Update(gtx))
|
||||||
w.driver.Configure(opts)
|
if len(opts) > 0 {
|
||||||
w.driver.Perform(acts)
|
w.driver.Configure(opts)
|
||||||
|
}
|
||||||
|
if acts != 0 {
|
||||||
|
w.driver.Perform(acts)
|
||||||
|
}
|
||||||
style.Layout(gtx)
|
style.Layout(gtx)
|
||||||
// Offset to place the frame content below the decorations.
|
// Offset to place the frame content below the decorations.
|
||||||
decoHeight := gtx.Dp(w.decorations.Config.decoHeight)
|
decoHeight := gtx.Dp(w.decorations.Config.decoHeight)
|
||||||
@@ -810,6 +863,10 @@ func (w *Window) Perform(actions system.Action) {
|
|||||||
if acts == 0 {
|
if acts == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if w.driver == nil {
|
||||||
|
w.initialActions = append(w.initialActions, acts)
|
||||||
|
return
|
||||||
|
}
|
||||||
w.Run(func() {
|
w.Run(func() {
|
||||||
w.driver.Perform(actions)
|
w.driver.Perform(actions)
|
||||||
})
|
})
|
||||||
|
|||||||
+5
-4
@@ -271,12 +271,13 @@ func (s *Scroll) Stop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update state and report the scroll distance along axis.
|
// 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
|
total := 0
|
||||||
f := pointer.Filter{
|
f := pointer.Filter{
|
||||||
Target: s,
|
Target: s,
|
||||||
Kinds: pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll | pointer.Cancel,
|
Kinds: pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll | pointer.Cancel,
|
||||||
ScrollBounds: bounds,
|
ScrollX: scrollx,
|
||||||
|
ScrollY: scrolly,
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
evt, ok := q.Event(f)
|
evt, ok := q.Event(f)
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
module gioui.org
|
module gioui.org
|
||||||
|
|
||||||
go 1.19
|
go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d
|
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d
|
||||||
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2
|
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2
|
||||||
gioui.org/shader v1.0.8
|
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 v0.0.0-20221012211006-4de253d81b95
|
||||||
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91
|
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91
|
||||||
golang.org/x/image v0.5.0
|
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/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 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA=
|
||||||
gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
|
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.1.1 h1:bGAesCuo85nXnEN5LmFMVGAGpGkCPtHrZLi//qD7EJo=
|
||||||
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k=
|
github.com/go-text/typesetting v0.1.1/go.mod h1:d22AnmeKq/on0HNv73UFriMKc4Ez6EqZAofLhAzpSzI=
|
||||||
github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI=
|
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=
|
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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
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-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-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-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM=
|
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
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.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.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.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.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-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.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
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 {
|
type blitter struct {
|
||||||
ctx driver.Device
|
ctx driver.Device
|
||||||
viewport image.Point
|
viewport image.Point
|
||||||
pipelines [3]*pipeline
|
pipelines [2][3]*pipeline
|
||||||
colUniforms *blitColUniforms
|
colUniforms *blitColUniforms
|
||||||
texUniforms *blitTexUniforms
|
texUniforms *blitTexUniforms
|
||||||
linearGradientUniforms *blitLinearGradientUniforms
|
linearGradientUniforms *blitLinearGradientUniforms
|
||||||
@@ -560,12 +560,24 @@ func newBlitter(ctx driver.Device) *blitter {
|
|||||||
func (b *blitter) release() {
|
func (b *blitter) release() {
|
||||||
b.quadVerts.Release()
|
b.quadVerts.Release()
|
||||||
for _, p := range b.pipelines {
|
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) {
|
func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.Sources, uniforms [3]interface{}) (pipelines [2][3]*pipeline, err error) {
|
||||||
var pipelines [3]*pipeline
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
for _, p := range pipelines {
|
||||||
|
for _, p := range p {
|
||||||
|
if p != nil {
|
||||||
|
p.Release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
blend := driver.BlendDesc{
|
blend := driver.BlendDesc{
|
||||||
Enable: true,
|
Enable: true,
|
||||||
SrcFactor: driver.BlendFactorOne,
|
SrcFactor: driver.BlendFactorOne,
|
||||||
@@ -583,86 +595,76 @@ func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.
|
|||||||
return pipelines, err
|
return pipelines, err
|
||||||
}
|
}
|
||||||
defer vsh.Release()
|
defer vsh.Release()
|
||||||
{
|
for i, format := range []driver.TextureFormat{driver.TextureFormatOutput, driver.TextureFormatSRGBA} {
|
||||||
fsh, err := b.NewFragmentShader(fsSrc[materialTexture])
|
{
|
||||||
if err != nil {
|
fsh, err := b.NewFragmentShader(fsSrc[materialTexture])
|
||||||
return pipelines, err
|
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{
|
var vertBuffer *uniformBuffer
|
||||||
VertexShader: vsh,
|
fsh, err := b.NewFragmentShader(fsSrc[materialColor])
|
||||||
FragmentShader: fsh,
|
if err != nil {
|
||||||
BlendDesc: blend,
|
return pipelines, err
|
||||||
VertexLayout: layout,
|
}
|
||||||
PixelFormat: driver.TextureFormatOutput,
|
defer fsh.Release()
|
||||||
Topology: driver.TopologyTriangleStrip,
|
pipe, err := b.NewPipeline(driver.PipelineDesc{
|
||||||
})
|
VertexShader: vsh,
|
||||||
if err != nil {
|
FragmentShader: fsh,
|
||||||
return pipelines, err
|
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 {
|
var vertBuffer *uniformBuffer
|
||||||
vertBuffer = newUniformBuffer(b, u)
|
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
|
return pipelines, nil
|
||||||
}
|
}
|
||||||
@@ -865,7 +867,7 @@ func (r *renderer) drawLayers(layers []opacityLayer, ops []imageOp) {
|
|||||||
Min: l.place.Pos,
|
Min: l.place.Pos,
|
||||||
Max: l.place.Pos.Add(l.clip.Size()),
|
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]
|
f := r.layerFBOs.fbos[fbo]
|
||||||
r.drawOps(true, l.clip.Min.Mul(-1), l.clip.Size(), ops[l.opStart:l.opEnd])
|
r.drawOps(true, l.clip.Min.Mul(-1), l.clip.Size(), ops[l.opStart:l.opEnd])
|
||||||
sr := f32.FRect(v)
|
sr := f32.FRect(v)
|
||||||
@@ -930,7 +932,7 @@ func (d *drawOps) newPathOp() *pathOp {
|
|||||||
return &d.pathOpCache[len(d.pathOpCache)-1]
|
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 := d.newPathOp()
|
||||||
*npath = pathOp{
|
*npath = pathOp{
|
||||||
parent: state.cpath,
|
parent: state.cpath,
|
||||||
@@ -1055,7 +1057,7 @@ loop:
|
|||||||
quads.aux, bounds, _ = d.boundsForTransformedRect(bounds, trans)
|
quads.aux, bounds, _ = d.boundsForTransformedRect(bounds, trans)
|
||||||
quads.key = opKey{Key: encOp.Key}
|
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{}
|
quads = quadsOp{}
|
||||||
case ops.TypePopClip:
|
case ops.TypePopClip:
|
||||||
state.cpath = state.cpath.parent
|
state.cpath = state.cpath.parent
|
||||||
@@ -1100,7 +1102,7 @@ loop:
|
|||||||
// this transformed rectangle.
|
// this transformed rectangle.
|
||||||
k := opKey{Key: encOp.Key}
|
k := opKey{Key: encOp.Key}
|
||||||
k.SetTransform(t) // TODO: This call has no effect.
|
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()
|
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
|
var coverTex driver.Texture
|
||||||
for i := 0; i < len(ops); i++ {
|
for i := 0; i < len(ops); i++ {
|
||||||
img := 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)
|
scale, off := clipSpaceTransform(drc, viewport)
|
||||||
var fbo FBO
|
var fbo FBO
|
||||||
|
fboIdx := 0
|
||||||
|
if isFBO {
|
||||||
|
fboIdx = 1
|
||||||
|
}
|
||||||
switch img.clipType {
|
switch img.clipType {
|
||||||
case clipTypeNone:
|
case clipTypeNone:
|
||||||
p := r.blitter.pipelines[m.material]
|
p := r.blitter.pipelines[fboIdx][m.material]
|
||||||
r.ctx.BindPipeline(p.pipeline)
|
r.ctx.BindPipeline(p.pipeline)
|
||||||
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
|
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)
|
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()),
|
Max: img.place.Pos.Add(drc.Size()),
|
||||||
}
|
}
|
||||||
coverScale, coverOff := texSpaceTransform(f32.FRect(uv), fbo.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.BindPipeline(p.pipeline)
|
||||||
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
|
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)
|
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) {
|
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)
|
b.ctx.BindPipeline(p.pipeline)
|
||||||
var uniforms *blitUniforms
|
var uniforms *blitUniforms
|
||||||
switch mat {
|
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) {
|
func TestOpacity(t *testing.T) {
|
||||||
run(t, func(ops *op.Ops) {
|
run(t, func(ops *op.Ops) {
|
||||||
opc1 := paint.PushOpacity(ops, .3)
|
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())
|
paint.FillShape(ops, color.NRGBA{R: 255, A: 255}, clip.Rect{Max: image.Pt(1024, 1024)}.Op())
|
||||||
opc2 := paint.PushOpacity(ops, .6)
|
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())
|
paint.FillShape(ops, color.NRGBA{G: 255, A: 255}, clip.Rect{Min: image.Pt(20, 10), Max: image.Pt(64, 128)}.Op())
|
||||||
opc2.Pop()
|
opc2.Pop()
|
||||||
opc1.Pop()
|
opc1.Pop()
|
||||||
opc3 := paint.PushOpacity(ops, .6)
|
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()
|
opc3.Pop()
|
||||||
}, func(r result) {
|
}, func(r result) {
|
||||||
})
|
})
|
||||||
|
|||||||
+9
-3
@@ -30,7 +30,7 @@ type pather struct {
|
|||||||
|
|
||||||
type coverer struct {
|
type coverer struct {
|
||||||
ctx driver.Device
|
ctx driver.Device
|
||||||
pipelines [3]*pipeline
|
pipelines [2][3]*pipeline
|
||||||
texUniforms *coverTexUniforms
|
texUniforms *coverTexUniforms
|
||||||
colUniforms *coverColUniforms
|
colUniforms *coverColUniforms
|
||||||
linearGradientUniforms *coverLinearGradientUniforms
|
linearGradientUniforms *coverLinearGradientUniforms
|
||||||
@@ -309,7 +309,9 @@ func (p *pather) release() {
|
|||||||
|
|
||||||
func (c *coverer) release() {
|
func (c *coverer) release() {
|
||||||
for _, p := range c.pipelines {
|
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.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
|
||||||
uniforms.uvCoverTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.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)
|
c.ctx.DrawArrays(0, 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-7
@@ -15,10 +15,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Context struct {
|
type Context struct {
|
||||||
disp _EGLDisplay
|
disp _EGLDisplay
|
||||||
eglCtx *eglContext
|
eglCtx *eglContext
|
||||||
eglSurf _EGLSurface
|
eglSurf _EGLSurface
|
||||||
width, height int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type eglContext struct {
|
type eglContext struct {
|
||||||
@@ -121,11 +120,9 @@ func (c *Context) VisualID() int {
|
|||||||
return c.eglCtx.visualID
|
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)
|
eglSurf, err := createSurface(c.disp, c.eglCtx, win)
|
||||||
c.eglSurf = eglSurf
|
c.eglSurf = eglSurf
|
||||||
c.width = width
|
|
||||||
c.height = height
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -361,6 +361,9 @@ func (c *Functions) GetProgrami(p Program, pname Enum) int {
|
|||||||
}
|
}
|
||||||
func (c *Functions) GetProgramInfoLog(p Program) string {
|
func (c *Functions) GetProgramInfoLog(p Program) string {
|
||||||
n := c.GetProgrami(p, INFO_LOG_LENGTH)
|
n := c.GetProgrami(p, INFO_LOG_LENGTH)
|
||||||
|
if n == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
buf := make([]byte, n)
|
buf := make([]byte, n)
|
||||||
syscall.Syscall6(_glGetProgramInfoLog.Addr(), 4, uintptr(p.V), uintptr(len(buf)), 0, uintptr(unsafe.Pointer(&buf[0])), 0, 0)
|
syscall.Syscall6(_glGetProgramInfoLog.Addr(), 4, uintptr(p.V), uintptr(len(buf)), 0, uintptr(unsafe.Pointer(&buf[0])), 0, 0)
|
||||||
return string(buf)
|
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 rot90CW(p f32.Point) f32.Point { return f32.Pt(+p.Y, -p.X) }
|
||||||
|
|
||||||
func normPt(p f32.Point, l float32) f32.Point {
|
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))
|
d := math.Hypot(float64(p.X), float64(p.Y))
|
||||||
l64 := float64(l)
|
l64 := float64(l)
|
||||||
if math.Abs(d-l64) < 1e-10 {
|
if math.Abs(d-l64) < 1e-10 {
|
||||||
|
|||||||
@@ -251,9 +251,10 @@ func TestFocusScroll(t *testing.T) {
|
|||||||
filters := []event.Filter{
|
filters := []event.Filter{
|
||||||
key.FocusFilter{Target: h},
|
key.FocusFilter{Target: h},
|
||||||
pointer.Filter{
|
pointer.Filter{
|
||||||
Target: h,
|
Target: h,
|
||||||
Kinds: pointer.Scroll,
|
Kinds: pointer.Scroll,
|
||||||
ScrollBounds: image.Rect(-100, -100, 100, 100),
|
ScrollX: pointer.ScrollRange{Min: -100, Max: +100},
|
||||||
|
ScrollY: pointer.ScrollRange{Min: -100, Max: +100},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
events(r, -1, filters...)
|
events(r, -1, filters...)
|
||||||
|
|||||||
+7
-5
@@ -72,7 +72,7 @@ type pointerHandler struct {
|
|||||||
type pointerFilter struct {
|
type pointerFilter struct {
|
||||||
kinds pointer.Kind
|
kinds pointer.Kind
|
||||||
// min and max horizontal/vertical scroll
|
// min and max horizontal/vertical scroll
|
||||||
scrollRange image.Rectangle
|
scrollX, scrollY pointer.ScrollRange
|
||||||
|
|
||||||
sourceMimes []string
|
sourceMimes []string
|
||||||
targetMimes []string
|
targetMimes []string
|
||||||
@@ -297,7 +297,8 @@ func (p *pointerFilter) Add(f event.Filter) {
|
|||||||
p.targetMimes = append(p.targetMimes, f.Type)
|
p.targetMimes = append(p.targetMimes, f.Type)
|
||||||
case pointer.Filter:
|
case pointer.Filter:
|
||||||
p.kinds = p.kinds | f.Kinds
|
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) {
|
func (p *pointerFilter) Merge(p2 pointerFilter) {
|
||||||
p.kinds = p.kinds | p2.kinds
|
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.sourceMimes = append(p.sourceMimes, p2.sourceMimes...)
|
||||||
p.targetMimes = append(p.targetMimes, p2.targetMimes...)
|
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
|
// clampScroll splits a scroll distance in the remaining scroll and the
|
||||||
// scroll accepted by the filter.
|
// scroll accepted by the filter.
|
||||||
func (p *pointerFilter) clampScroll(scroll f32.Point) (left, scrolled f32.Point) {
|
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.X, scrolled.X = clampSplit(scroll.X, p.scrollX.Min, p.scrollX.Max)
|
||||||
left.Y, scrolled.Y = clampSplit(scroll.Y, p.scrollRange.Min.Y, p.scrollRange.Max.Y)
|
left.Y, scrolled.Y = clampSplit(scroll.Y, p.scrollY.Min, p.scrollY.Max)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -300,9 +300,9 @@ func TestPointerPriority(t *testing.T) {
|
|||||||
r1 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops)
|
r1 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops)
|
||||||
f1 := func(t event.Tag) event.Filter {
|
f1 := func(t event.Tag) event.Filter {
|
||||||
return pointer.Filter{
|
return pointer.Filter{
|
||||||
Target: t,
|
Target: t,
|
||||||
Kinds: pointer.Scroll,
|
Kinds: pointer.Scroll,
|
||||||
ScrollBounds: image.Rectangle{Max: image.Point{X: 100}},
|
ScrollX: pointer.ScrollRange{Max: 100},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
events(&r, -1, f1(handler1))
|
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)
|
r2 := clip.Rect(image.Rect(0, 0, 100, 50)).Push(&ops)
|
||||||
f2 := func(t event.Tag) event.Filter {
|
f2 := func(t event.Tag) event.Filter {
|
||||||
return pointer.Filter{
|
return pointer.Filter{
|
||||||
Target: t,
|
Target: t,
|
||||||
Kinds: pointer.Scroll,
|
Kinds: pointer.Scroll,
|
||||||
ScrollBounds: image.Rectangle{Max: image.Point{X: 20}},
|
ScrollX: pointer.ScrollRange{Max: 20},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
events(&r, -1, f2(handler2))
|
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)
|
r3 := clip.Rect(image.Rect(0, 100, 100, 200)).Push(&ops)
|
||||||
f3 := func(t event.Tag) event.Filter {
|
f3 := func(t event.Tag) event.Filter {
|
||||||
return pointer.Filter{
|
return pointer.Filter{
|
||||||
Target: t,
|
Target: t,
|
||||||
Kinds: pointer.Scroll,
|
Kinds: pointer.Scroll,
|
||||||
ScrollBounds: image.Rectangle{Min: image.Point{X: -20, Y: -40}},
|
ScrollX: pointer.ScrollRange{Min: -20},
|
||||||
|
ScrollY: pointer.ScrollRange{Min: -40},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
events(&r, -1, f3(handler3))
|
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.
|
// offer satisfies io.ReadCloser for use in data transfers.
|
||||||
type offer struct {
|
type offer struct {
|
||||||
data string
|
data string
|
||||||
|
|||||||
+29
-30
@@ -35,10 +35,9 @@ type Router struct {
|
|||||||
queue keyQueue
|
queue keyQueue
|
||||||
// The following fields have the same purpose as the fields in
|
// The following fields have the same purpose as the fields in
|
||||||
// type handler, but for key.Events.
|
// type handler, but for key.Events.
|
||||||
filter keyFilter
|
filter keyFilter
|
||||||
nextFilter keyFilter
|
nextFilter keyFilter
|
||||||
processedFilter keyFilter
|
scratchFilter keyFilter
|
||||||
scratchFilter keyFilter
|
|
||||||
}
|
}
|
||||||
cqueue clipboardQueue
|
cqueue clipboardQueue
|
||||||
// states is the list of pending state changes resulting from
|
// 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 {
|
||||||
for i := range q.changes {
|
if q.deferring && i > 0 {
|
||||||
change := &q.changes[i]
|
break
|
||||||
for j, evt := range change.events {
|
}
|
||||||
match := false
|
change := &q.changes[i]
|
||||||
switch e := evt.event.(type) {
|
for j, evt := range change.events {
|
||||||
case key.Event:
|
match := false
|
||||||
match = q.key.scratchFilter.Matches(change.state.keyState.focus, e, false)
|
switch e := evt.event.(type) {
|
||||||
default:
|
case key.Event:
|
||||||
for _, tf := range q.scratchFilters {
|
match = q.key.scratchFilter.Matches(change.state.keyState.focus, e, false)
|
||||||
if evt.tag == tf.tag && tf.filter.Matches(evt.event) {
|
default:
|
||||||
match = true
|
for _, tf := range q.scratchFilters {
|
||||||
break
|
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:]...)
|
if match {
|
||||||
// Fast forward state to last matched.
|
change.events = append(change.events[:j], change.events[j+1:]...)
|
||||||
q.collapseState(i)
|
// Fast forward state to last matched.
|
||||||
return evt.event, true
|
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 := q.stateFor(tf.tag)
|
||||||
h.processedFilter.Merge(tf.filter)
|
h.processedFilter.Merge(tf.filter)
|
||||||
}
|
}
|
||||||
q.key.processedFilter = append(q.key.processedFilter, q.key.scratchFilter...)
|
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,15 +314,15 @@ func (q *Router) collapseState(idx int) {
|
|||||||
}
|
}
|
||||||
first := &q.changes[0]
|
first := &q.changes[0]
|
||||||
first.state = q.changes[idx].state
|
first.state = q.changes[idx].state
|
||||||
for i := 1; i <= idx; i++ {
|
for _, ch := range q.changes[1 : idx+1] {
|
||||||
first.events = append(first.events, q.changes[i].events...)
|
first.events = append(first.events, ch.events...)
|
||||||
}
|
}
|
||||||
q.changes = append(q.changes[:1], q.changes[idx+1:]...)
|
q.changes = append(q.changes[:1], q.changes[idx+1:]...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Frame replaces the declared handlers from the supplied
|
// Frame completes the current frame and starts a new with the
|
||||||
// operation list. The text input state, wakeup time and whether
|
// handlers from the frame argument. Remaining events are discarded,
|
||||||
// there are active profile handlers is also saved.
|
// unless they were deferred by a command.
|
||||||
func (q *Router) Frame(frame *op.Ops) {
|
func (q *Router) Frame(frame *op.Ops) {
|
||||||
var remaining []event.Event
|
var remaining []event.Event
|
||||||
if n := len(q.changes); n > 0 {
|
if n := len(q.changes); n > 0 {
|
||||||
|
|||||||
+2
-2
@@ -37,11 +37,11 @@ For example:
|
|||||||
var h1, h2 *Handler
|
var h1, h2 *Handler
|
||||||
|
|
||||||
area := clip.Rect(...).Push(ops)
|
area := clip.Rect(...).Push(ops)
|
||||||
event.Op{Tag: h1}.Add(Ops)
|
event.Op(Ops, h1)
|
||||||
area.Pop()
|
area.Pop()
|
||||||
|
|
||||||
area := clip.Rect(...).Push(ops)
|
area := clip.Rect(...).Push(ops)
|
||||||
event.Op{Tag: h2}.Add(ops)
|
event.Op(Ops, h2)
|
||||||
area.Pop()
|
area.Pop()
|
||||||
|
|
||||||
implies a tree of two inner nodes, each with one pointer handler attached.
|
implies a tree of two inner nodes, each with one pointer handler attached.
|
||||||
|
|||||||
+19
-6
@@ -3,7 +3,6 @@
|
|||||||
package pointer
|
package pointer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"image"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -61,12 +60,19 @@ type Filter struct {
|
|||||||
Target event.Tag
|
Target event.Tag
|
||||||
// Kinds is a bitwise-or of event types to match.
|
// Kinds is a bitwise-or of event types to match.
|
||||||
Kinds Kind
|
Kinds Kind
|
||||||
// ScrollBounds describe the maximum scrollable distances in both
|
// ScrollX and ScrollY constrain the range of scrolling events delivered
|
||||||
// axes. Specifically, any Event e delivered to Tag will satisfy
|
// to Target. Specifically, any Event e delivered to Tag will satisfy
|
||||||
//
|
//
|
||||||
// ScrollBounds.Min.X <= e.Scroll.X <= ScrollBounds.Max.X (horizontal axis)
|
// ScrollX.Min <= e.Scroll.X <= ScrollX.Max (horizontal axis)
|
||||||
// ScrollBounds.Min.Y <= e.Scroll.Y <= ScrollBounds.Max.Y (vertical axis)
|
// ScrollY.Min <= e.Scroll.Y <= ScrollY.Max (vertical axis)
|
||||||
ScrollBounds image.Rectangle
|
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.
|
// GrabCmd requests a pointer grab on the pointer identified by ID.
|
||||||
@@ -219,6 +225,13 @@ const (
|
|||||||
ButtonTertiary
|
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.
|
// Push the current pass mode to the pass stack and set the pass mode.
|
||||||
func (p PassOp) Push(o *op.Ops) PassStack {
|
func (p PassOp) Push(o *op.Ops) PassStack {
|
||||||
id, mid := ops.PushOp(&o.Internal, ops.PassStack)
|
id, mid := ops.PushOp(&o.Internal, ops.PassStack)
|
||||||
|
|||||||
+6
-4
@@ -7,6 +7,7 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
|
|
||||||
"gioui.org/gesture"
|
"gioui.org/gesture"
|
||||||
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
"gioui.org/op/clip"
|
"gioui.org/op/clip"
|
||||||
)
|
)
|
||||||
@@ -158,11 +159,12 @@ func (l *List) update(gtx Context) {
|
|||||||
max = 0
|
max = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scrollRange := image.Rectangle{
|
xrange := pointer.ScrollRange{Min: min, Max: max}
|
||||||
Min: l.Axis.Convert(image.Pt(min, 0)),
|
yrange := pointer.ScrollRange{}
|
||||||
Max: l.Axis.Convert(image.Pt(max, 0)),
|
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.scrollDelta = d
|
||||||
l.Position.Offset += d
|
l.Position.Offset += d
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,6 +138,9 @@ type Path struct {
|
|||||||
func (p *Path) Pos() f32.Point { return p.pen }
|
func (p *Path) Pos() f32.Point { return p.pen }
|
||||||
|
|
||||||
// Begin the path, storing the path data and final Op into ops.
|
// 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) {
|
func (p *Path) Begin(o *op.Ops) {
|
||||||
*p = Path{
|
*p = Path{
|
||||||
ops: &o.Internal,
|
ops: &o.Internal,
|
||||||
|
|||||||
+3
-1
@@ -4,6 +4,7 @@ package text
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"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
|
// 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.
|
// in the order in which they are loaded, with the first face being the default.
|
||||||
func (s *shaperImpl) Load(f FontFace) {
|
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)
|
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
|
axis = gesture.Vertical
|
||||||
smin, smax = sbounds.Min.Y, sbounds.Max.Y
|
smin, smax = sbounds.Min.Y, sbounds.Max.Y
|
||||||
}
|
}
|
||||||
var scrollRange image.Rectangle
|
var scrollX, scrollY pointer.ScrollRange
|
||||||
textDims := e.text.FullDimensions()
|
textDims := e.text.FullDimensions()
|
||||||
visibleDims := e.text.Dimensions()
|
visibleDims := e.text.Dimensions()
|
||||||
if e.SingleLine {
|
if e.SingleLine {
|
||||||
scrollOffX := e.text.ScrollOff().X
|
scrollOffX := e.text.ScrollOff().X
|
||||||
scrollRange.Min.X = min(-scrollOffX, 0)
|
scrollX.Min = min(-scrollOffX, 0)
|
||||||
scrollRange.Max.X = max(0, textDims.Size.X-(scrollOffX+visibleDims.Size.X))
|
scrollX.Max = max(0, textDims.Size.X-(scrollOffX+visibleDims.Size.X))
|
||||||
} else {
|
} else {
|
||||||
scrollOffY := e.text.ScrollOff().Y
|
scrollOffY := e.text.ScrollOff().Y
|
||||||
scrollRange.Min.Y = -scrollOffY
|
scrollY.Min = -scrollOffY
|
||||||
scrollRange.Max.Y = max(0, textDims.Size.Y-(scrollOffY+visibleDims.Size.Y))
|
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
|
var soff int
|
||||||
if e.SingleLine {
|
if e.SingleLine {
|
||||||
e.text.ScrollRel(sdist, 0)
|
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))),
|
Y: int(math.Round(float64(evt.Position.Y))),
|
||||||
})
|
})
|
||||||
gtx.Execute(key.FocusCmd{Tag: e})
|
gtx.Execute(key.FocusCmd{Tag: e})
|
||||||
|
if !e.ReadOnly {
|
||||||
|
gtx.Execute(key.SoftKeyboardCmd{Show: true})
|
||||||
|
}
|
||||||
if e.scroller.State() != gesture.StateFlinging {
|
if e.scroller.State() != gesture.StateFlinging {
|
||||||
e.scrollCaret = true
|
e.scrollCaret = true
|
||||||
}
|
}
|
||||||
@@ -312,8 +315,8 @@ func (e *Editor) processPointerEvent(gtx layout.Context, ev event.Event) (Editor
|
|||||||
e.text.MoveWord(1, selectionExtend)
|
e.text.MoveWord(1, selectionExtend)
|
||||||
e.dragging = false
|
e.dragging = false
|
||||||
case evt.NumClicks >= 3:
|
case evt.NumClicks >= 3:
|
||||||
e.text.MoveStart(selectionClear)
|
e.text.MoveLineStart(selectionClear)
|
||||||
e.text.MoveEnd(selectionExtend)
|
e.text.MoveLineEnd(selectionExtend)
|
||||||
e.dragging = false
|
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.NameDeleteBackward, Optional: key.ModShortcutAlt | key.ModShift},
|
||||||
key.Filter{Focus: e, Name: key.NameDeleteForward, 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.NameHome, Optional: key.ModShortcut | key.ModShift},
|
||||||
key.Filter{Focus: e, Name: key.NameEnd, Optional: 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.NamePageDown, Optional: key.ModShift},
|
||||||
key.Filter{Focus: e, Name: key.NamePageUp, 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}),
|
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:
|
case key.FocusEvent:
|
||||||
// Reset IME state.
|
// Reset IME state.
|
||||||
e.ime.imeState = imeState{}
|
e.ime.imeState = imeState{}
|
||||||
if ke.Focus {
|
if ke.Focus && !e.ReadOnly {
|
||||||
gtx.Execute(key.SoftKeyboardCmd{Show: true})
|
gtx.Execute(key.SoftKeyboardCmd{Show: true})
|
||||||
}
|
}
|
||||||
case key.Event:
|
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
|
return nil, false
|
||||||
}
|
}
|
||||||
@@ -582,9 +589,9 @@ func (e *Editor) command(gtx layout.Context, k key.Event) (EditorEvent, bool) {
|
|||||||
case key.NamePageDown:
|
case key.NamePageDown:
|
||||||
e.text.MovePages(+1, selAct)
|
e.text.MovePages(+1, selAct)
|
||||||
case key.NameHome:
|
case key.NameHome:
|
||||||
e.text.MoveStart(selAct)
|
e.text.MoveLineStart(selAct)
|
||||||
case key.NameEnd:
|
case key.NameEnd:
|
||||||
e.text.MoveEnd(selAct)
|
e.text.MoveLineEnd(selAct)
|
||||||
}
|
}
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|||||||
+77
-17
@@ -256,7 +256,7 @@ func TestEditor(t *testing.T) {
|
|||||||
// Regression test for bad in-cluster rune offset math.
|
// Regression test for bad in-cluster rune offset math.
|
||||||
e.SetText("æbc")
|
e.SetText("æbc")
|
||||||
e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{})
|
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"))
|
assertCaret(t, e, 0, 3, len("æbc"))
|
||||||
|
|
||||||
textSample := "æbc\naøå••"
|
textSample := "æbc\naøå••"
|
||||||
@@ -268,7 +268,7 @@ func TestEditor(t *testing.T) {
|
|||||||
}
|
}
|
||||||
e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{})
|
e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{})
|
||||||
assertCaret(t, e, 0, 0, 0)
|
assertCaret(t, e, 0, 0, 0)
|
||||||
e.text.MoveEnd(selectionClear)
|
e.text.MoveLineEnd(selectionClear)
|
||||||
assertCaret(t, e, 0, 3, len("æbc"))
|
assertCaret(t, e, 0, 3, len("æbc"))
|
||||||
e.MoveCaret(+1, +1)
|
e.MoveCaret(+1, +1)
|
||||||
assertCaret(t, e, 1, 0, len("æbc\n"))
|
assertCaret(t, e, 1, 0, len("æbc\n"))
|
||||||
@@ -276,7 +276,7 @@ func TestEditor(t *testing.T) {
|
|||||||
assertCaret(t, e, 0, 3, len("æbc"))
|
assertCaret(t, e, 0, 3, len("æbc"))
|
||||||
e.text.MoveLines(+1, selectionClear)
|
e.text.MoveLines(+1, selectionClear)
|
||||||
assertCaret(t, e, 1, 4, len("æbc\naøå•"))
|
assertCaret(t, e, 1, 4, len("æbc\naøå•"))
|
||||||
e.text.MoveEnd(selectionClear)
|
e.text.MoveLineEnd(selectionClear)
|
||||||
assertCaret(t, e, 1, 5, len("æbc\naøå••"))
|
assertCaret(t, e, 1, 5, len("æbc\naøå••"))
|
||||||
e.MoveCaret(+1, +1)
|
e.MoveCaret(+1, +1)
|
||||||
assertCaret(t, e, 1, 5, len("æbc\naøå••"))
|
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.
|
// Test that moveLine applies x offsets from previous moves.
|
||||||
e.SetText("long line\nshort")
|
e.SetText("long line\nshort")
|
||||||
e.SetCaret(0, 0)
|
e.SetCaret(0, 0)
|
||||||
e.text.MoveEnd(selectionClear)
|
e.text.MoveLineEnd(selectionClear)
|
||||||
e.text.MoveLines(+1, selectionClear)
|
e.text.MoveLines(+1, selectionClear)
|
||||||
e.text.MoveLines(-1, selectionClear)
|
e.text.MoveLines(-1, selectionClear)
|
||||||
assertCaret(t, e, 0, utf8.RuneCountInString("long line"), len("long line"))
|
assertCaret(t, e, 0, utf8.RuneCountInString("long line"), len("long line"))
|
||||||
@@ -342,14 +342,14 @@ func TestEditorRTL(t *testing.T) {
|
|||||||
e.MoveCaret(+1, +1)
|
e.MoveCaret(+1, +1)
|
||||||
assertCaret(t, e, 0, 3, len("الح"))
|
assertCaret(t, e, 0, 3, len("الح"))
|
||||||
// Move to the "end" of the line. This moves to the left edge of the line.
|
// 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("الحب"))
|
assertCaret(t, e, 0, 4, len("الحب"))
|
||||||
|
|
||||||
sentence := "الحب سماء لا\nتمط غير الأحلام"
|
sentence := "الحب سماء لا\nتمط غير الأحلام"
|
||||||
e.SetText(sentence)
|
e.SetText(sentence)
|
||||||
e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{})
|
e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{})
|
||||||
assertCaret(t, e, 0, 0, 0)
|
assertCaret(t, e, 0, 0, 0)
|
||||||
e.text.MoveEnd(selectionClear)
|
e.text.MoveLineEnd(selectionClear)
|
||||||
assertCaret(t, e, 0, 12, len("الحب سماء لا"))
|
assertCaret(t, e, 0, 12, len("الحب سماء لا"))
|
||||||
e.MoveCaret(+1, +1)
|
e.MoveCaret(+1, +1)
|
||||||
assertCaret(t, e, 1, 0, len("الحب سماء لا\n"))
|
assertCaret(t, e, 1, 0, len("الحب سماء لا\n"))
|
||||||
@@ -361,7 +361,7 @@ func TestEditorRTL(t *testing.T) {
|
|||||||
assertCaret(t, e, 0, 12, len("الحب سماء لا"))
|
assertCaret(t, e, 0, 12, len("الحب سماء لا"))
|
||||||
e.text.MoveLines(+1, selectionClear)
|
e.text.MoveLines(+1, selectionClear)
|
||||||
assertCaret(t, e, 1, 14, len("الحب سماء لا\nتمط غير الأحلا"))
|
assertCaret(t, e, 1, 14, len("الحب سماء لا\nتمط غير الأحلا"))
|
||||||
e.text.MoveEnd(selectionClear)
|
e.text.MoveLineEnd(selectionClear)
|
||||||
assertCaret(t, e, 1, 15, len("الحب سماء لا\nتمط غير الأحلام"))
|
assertCaret(t, e, 1, 15, len("الحب سماء لا\nتمط غير الأحلام"))
|
||||||
e.MoveCaret(+1, +1)
|
e.MoveCaret(+1, +1)
|
||||||
assertCaret(t, e, 1, 15, len("الحب سماء لا\nتمط غير الأحلام"))
|
assertCaret(t, e, 1, 15, len("الحب سماء لا\nتمط غير الأحلام"))
|
||||||
@@ -417,7 +417,7 @@ func TestEditorLigature(t *testing.T) {
|
|||||||
assertCaret(t, e, 0, 0, 0)
|
assertCaret(t, e, 0, 0, 0)
|
||||||
e.SetText("fl") // just a ligature
|
e.SetText("fl") // just a ligature
|
||||||
e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{})
|
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"))
|
assertCaret(t, e, 0, 2, len("fl"))
|
||||||
e.MoveCaret(-1, -1)
|
e.MoveCaret(-1, -1)
|
||||||
assertCaret(t, e, 0, 1, len("f"))
|
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.SetText("flaffl•ffi\n•fflfi") // 3 ligatures on line 0, 2 on line 1
|
||||||
e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{})
|
e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{})
|
||||||
assertCaret(t, e, 0, 0, 0)
|
assertCaret(t, e, 0, 0, 0)
|
||||||
e.text.MoveEnd(selectionClear)
|
e.text.MoveLineEnd(selectionClear)
|
||||||
assertCaret(t, e, 0, 10, len("ffaffl•ffi"))
|
assertCaret(t, e, 0, 10, len("ffaffl•ffi"))
|
||||||
e.MoveCaret(+1, +1)
|
e.MoveCaret(+1, +1)
|
||||||
assertCaret(t, e, 1, 0, len("ffaffl•ffi\n"))
|
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{})
|
e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{})
|
||||||
// Ensure that all runes in the final cluster of a line are properly
|
// 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.
|
// 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
|
// 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. However, we should never be able to reach the "other side" of such a trailing
|
||||||
// whitespace glyph.
|
// whitespace glyph.
|
||||||
@@ -548,8 +548,10 @@ const (
|
|||||||
moveRune
|
moveRune
|
||||||
moveLine
|
moveLine
|
||||||
movePage
|
movePage
|
||||||
moveStart
|
moveTextStart
|
||||||
moveEnd
|
moveTextEnd
|
||||||
|
moveLineStart
|
||||||
|
moveLineEnd
|
||||||
moveCoord
|
moveCoord
|
||||||
moveWord
|
moveWord
|
||||||
deleteWord
|
deleteWord
|
||||||
@@ -599,10 +601,14 @@ func TestEditorCaretConsistency(t *testing.T) {
|
|||||||
e.text.MoveLines(int(distance), selectionClear)
|
e.text.MoveLines(int(distance), selectionClear)
|
||||||
case movePage:
|
case movePage:
|
||||||
e.text.MovePages(int(distance), selectionClear)
|
e.text.MovePages(int(distance), selectionClear)
|
||||||
case moveStart:
|
case moveLineStart:
|
||||||
e.text.MoveStart(selectionClear)
|
e.text.MoveLineStart(selectionClear)
|
||||||
case moveEnd:
|
case moveLineEnd:
|
||||||
e.text.MoveEnd(selectionClear)
|
e.text.MoveLineEnd(selectionClear)
|
||||||
|
case moveTextStart:
|
||||||
|
e.text.MoveTextStart(selectionClear)
|
||||||
|
case moveTextEnd:
|
||||||
|
e.text.MoveTextEnd(selectionClear)
|
||||||
case moveCoord:
|
case moveCoord:
|
||||||
e.text.MoveCoord(image.Pt(int(x), int(y)))
|
e.text.MoveCoord(image.Pt(int(x), int(y)))
|
||||||
case moveWord:
|
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
|
// 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
|
// then verifies that the updated (col, line) positions of the selected text
|
||||||
// are where we expect.
|
// are where we expect.
|
||||||
func TestEditorSelect(t *testing.T) {
|
func TestEditorSelectReflow(t *testing.T) {
|
||||||
e := new(Editor)
|
e := new(Editor)
|
||||||
e.SetText(`a 2 4 6 8 a
|
e.SetText(`a 2 4 6 8 a
|
||||||
b 2 4 6 8 b
|
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.
|
// Verify that an existing selection is dismissed when you press arrow keys.
|
||||||
func TestSelectMove(t *testing.T) {
|
func TestSelectMove(t *testing.T) {
|
||||||
e := new(Editor)
|
e := new(Editor)
|
||||||
|
|||||||
@@ -284,9 +284,9 @@ func TestIndexPositionBidi(t *testing.T) {
|
|||||||
name: "bidi rtl",
|
name: "bidi rtl",
|
||||||
glyphs: bidiRTLText,
|
glyphs: bidiRTLText,
|
||||||
expectedXs: []fixed.Int26_6{
|
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.
|
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),
|
xOff: fixed.Int26_6(0),
|
||||||
yOff: 22,
|
yOff: 22,
|
||||||
glyphs: 15,
|
glyphs: 15,
|
||||||
width: fixed.Int26_6(7133),
|
width: fixed.Int26_6(7114),
|
||||||
ascent: fixed.Int26_6(1407),
|
ascent: fixed.Int26_6(1407),
|
||||||
descent: fixed.Int26_6(756),
|
descent: fixed.Int26_6(756),
|
||||||
},
|
},
|
||||||
@@ -410,7 +410,7 @@ func TestIndexPositionLines(t *testing.T) {
|
|||||||
xOff: fixed.Int26_6(0),
|
xOff: fixed.Int26_6(0),
|
||||||
yOff: 41,
|
yOff: 41,
|
||||||
glyphs: 15,
|
glyphs: 15,
|
||||||
width: fixed.Int26_6(7886),
|
width: fixed.Int26_6(7867),
|
||||||
ascent: fixed.Int26_6(1407),
|
ascent: fixed.Int26_6(1407),
|
||||||
descent: fixed.Int26_6(756),
|
descent: fixed.Int26_6(756),
|
||||||
},
|
},
|
||||||
@@ -477,18 +477,18 @@ func TestIndexPositionLines(t *testing.T) {
|
|||||||
glyphs: bidiRTLTextOpp,
|
glyphs: bidiRTLTextOpp,
|
||||||
expectedLines: []lineInfo{
|
expectedLines: []lineInfo{
|
||||||
{
|
{
|
||||||
xOff: fixed.Int26_6(3107),
|
xOff: fixed.Int26_6(3126),
|
||||||
yOff: 22,
|
yOff: 22,
|
||||||
glyphs: 15,
|
glyphs: 15,
|
||||||
width: fixed.Int26_6(7133),
|
width: fixed.Int26_6(7114),
|
||||||
ascent: fixed.Int26_6(1407),
|
ascent: fixed.Int26_6(1407),
|
||||||
descent: fixed.Int26_6(756),
|
descent: fixed.Int26_6(756),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
xOff: fixed.Int26_6(2354),
|
xOff: fixed.Int26_6(2373),
|
||||||
yOff: 41,
|
yOff: 41,
|
||||||
glyphs: 15,
|
glyphs: 15,
|
||||||
width: fixed.Int26_6(7886),
|
width: fixed.Int26_6(7867),
|
||||||
ascent: fixed.Int26_6(1407),
|
ascent: fixed.Int26_6(1407),
|
||||||
descent: fixed.Int26_6(756),
|
descent: fixed.Int26_6(756),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -54,6 +54,9 @@ func (p ProgressBarStyle) Layout(gtx layout.Context) layout.Dimensions {
|
|||||||
if !gtx.Enabled() {
|
if !gtx.Enabled() {
|
||||||
fillColor = f32color.Disabled(fillColor)
|
fillColor = f32color.Disabled(fillColor)
|
||||||
}
|
}
|
||||||
|
if fillWidth < int(p.Radius*2) {
|
||||||
|
fillWidth = int(p.Radius * 2)
|
||||||
|
}
|
||||||
return shader(fillWidth, fillColor)
|
return shader(fillWidth, fillColor)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -247,8 +247,8 @@ func (e *Selectable) processPointer(gtx layout.Context) {
|
|||||||
e.text.MoveWord(1, selectionExtend)
|
e.text.MoveWord(1, selectionExtend)
|
||||||
e.dragging = false
|
e.dragging = false
|
||||||
case evt.NumClicks >= 3:
|
case evt.NumClicks >= 3:
|
||||||
e.text.MoveStart(selectionClear)
|
e.text.MoveLineStart(selectionClear)
|
||||||
e.text.MoveEnd(selectionExtend)
|
e.text.MoveLineEnd(selectionExtend)
|
||||||
e.dragging = false
|
e.dragging = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -378,9 +378,9 @@ func (e *Selectable) command(gtx layout.Context, k key.Event) {
|
|||||||
case key.NamePageDown:
|
case key.NamePageDown:
|
||||||
e.text.MovePages(+1, selAct)
|
e.text.MovePages(+1, selAct)
|
||||||
case key.NameHome:
|
case key.NameHome:
|
||||||
e.text.MoveStart(selAct)
|
e.text.MoveLineStart(selAct)
|
||||||
case key.NameEnd:
|
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)
|
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.
|
// 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.closestToRune(e.caret.start)
|
||||||
caret = e.closestToLineCol(caret.lineCol.line, 0)
|
caret = e.closestToLineCol(caret.lineCol.line, 0)
|
||||||
e.caret.start = caret.runes
|
e.caret.start = caret.runes
|
||||||
@@ -650,9 +669,9 @@ func (e *textView) MoveStart(selAct selectionAction) {
|
|||||||
e.clampCursorToGraphemes()
|
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.
|
// 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.closestToRune(e.caret.start)
|
||||||
caret = e.closestToLineCol(caret.lineCol.line, math.MaxInt)
|
caret = e.closestToLineCol(caret.lineCol.line, math.MaxInt)
|
||||||
e.caret.start = caret.runes
|
e.caret.start = caret.runes
|
||||||
|
|||||||
Reference in New Issue
Block a user