mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-03 00:16:15 +00:00
Compare commits
88 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f200f0e9a3 | |||
| 1ae2b9b8fe | |||
| fe4bf00c70 | |||
| 8107ec2206 | |||
| d2db4f6875 | |||
| 971f86ea7e | |||
| e1fbb189e5 | |||
| a206e5e847 | |||
| e025ed1344 | |||
| b576252963 | |||
| 1d0d5f0383 | |||
| 26cddb00b1 | |||
| 0cbbacc45a | |||
| 1d95c7c6b3 | |||
| 7337c06daf | |||
| 94355e5201 | |||
| ea456f42c7 | |||
| 8daff13af6 | |||
| aa158e0c9c | |||
| 520efdfa75 | |||
| d7a1ec7461 | |||
| d4c5e54375 | |||
| 44ac50506d | |||
| 38e4b1c6a4 | |||
| 5d886b4d7f | |||
| c7277581f8 | |||
| a5f7e7b2c7 | |||
| 0781e62b56 | |||
| 95f63c66b6 | |||
| 6efcb65c4b | |||
| f6e9f6861d | |||
| 6c6cc157c4 | |||
| 8cf449034c | |||
| 6722c7960a | |||
| 97044e53b5 | |||
| af6dda67a5 | |||
| 042ed4ab49 | |||
| 6aa027136e | |||
| b1db32ef72 | |||
| 9dceca6c95 | |||
| 7293fa8a41 | |||
| 82cbb7b8da | |||
| 6c19821a6c | |||
| 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 |
+2
-2
@@ -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
|
||||||
@@ -71,4 +71,4 @@ tasks:
|
|||||||
CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-macos GOOS=darwin CGO_ENABLED=1 go build ./...
|
CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-macos GOOS=darwin CGO_ENABLED=1 go build ./...
|
||||||
- test_ios: |
|
- test_ios: |
|
||||||
cd gio
|
cd gio
|
||||||
CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-ios GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go build -tags ios ./...
|
CGO_CFLAGS=-Wno-deprecated-module-dot-map CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-ios GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go build -tags ios ./...
|
||||||
|
|||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
# SPDX-License-Identifier: Unlicense OR MIT
|
# SPDX-License-Identifier: Unlicense OR MIT
|
||||||
image: freebsd/13.x
|
image: freebsd/latest
|
||||||
packages:
|
packages:
|
||||||
- libX11
|
- libX11
|
||||||
- libxkbcommon
|
- libxkbcommon
|
||||||
@@ -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: |
|
||||||
|
|||||||
@@ -259,6 +259,10 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setHighRefreshRate() {
|
private void setHighRefreshRate() {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Context context = getContext();
|
Context context = getContext();
|
||||||
Display display = context.getDisplay();
|
Display display = context.getDisplay();
|
||||||
Display.Mode[] supportedModes = display.getSupportedModes();
|
Display.Mode[] supportedModes = display.getSupportedModes();
|
||||||
|
|||||||
+16
@@ -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
|
||||||
@@ -120,6 +124,18 @@ func DataDir() (string, error) {
|
|||||||
return dataDir()
|
return dataDir()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Main must be called last from the program main function.
|
||||||
|
// On most platforms Main blocks forever, for Android and
|
||||||
|
// iOS it returns immediately to give control of the main
|
||||||
|
// thread back to the system.
|
||||||
|
//
|
||||||
|
// Calling Main is necessary because some operating systems
|
||||||
|
// require control of the main thread of the program for
|
||||||
|
// running windows.
|
||||||
|
func Main() {
|
||||||
|
osMain()
|
||||||
|
}
|
||||||
|
|
||||||
func (FrameEvent) ImplementsEvent() {}
|
func (FrameEvent) ImplementsEvent() {}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
+21
-5
@@ -33,12 +33,28 @@ For example:
|
|||||||
A program must keep receiving events from the event channel until
|
A program must keep receiving events from the event channel until
|
||||||
[DestroyEvent] is received.
|
[DestroyEvent] is received.
|
||||||
|
|
||||||
# Main Thread
|
# Main
|
||||||
|
|
||||||
Some GUI platform need access to the main thread of the program. To avoid a
|
The Main function must be called from a program's main function, to hand over
|
||||||
deadlock on such platforms, at least one Window must have its Event method
|
control of the main thread to operating systems that need it.
|
||||||
called by the main goroutine. It doesn't have to be any particular Window;
|
|
||||||
even a destroyed Window suffices.
|
Because Main is also blocking on some platforms, the event loop of a Window must run in a goroutine.
|
||||||
|
|
||||||
|
For example, to display a blank but otherwise functional window:
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "gioui.org/app"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
go func() {
|
||||||
|
w := app.NewWindow()
|
||||||
|
for {
|
||||||
|
w.Event()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
app.Main()
|
||||||
|
}
|
||||||
|
|
||||||
# Permissions
|
# Permissions
|
||||||
|
|
||||||
|
|||||||
+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];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -274,6 +274,7 @@ const (
|
|||||||
WM_SETFOCUS = 0x0007
|
WM_SETFOCUS = 0x0007
|
||||||
WM_SHOWWINDOW = 0x0018
|
WM_SHOWWINDOW = 0x0018
|
||||||
WM_SIZE = 0x0005
|
WM_SIZE = 0x0005
|
||||||
|
WM_STYLECHANGED = 0x007D
|
||||||
WM_SYSKEYDOWN = 0x0104
|
WM_SYSKEYDOWN = 0x0104
|
||||||
WM_SYSKEYUP = 0x0105
|
WM_SYSKEYUP = 0x0105
|
||||||
WM_RBUTTONDOWN = 0x0204
|
WM_RBUTTONDOWN = 0x0204
|
||||||
|
|||||||
+3
-2
@@ -4,14 +4,15 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
syscall "golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
|
|
||||||
type logger struct{}
|
type logger struct{}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
kernel32 = syscall.NewLazyDLL("kernel32")
|
kernel32 = syscall.NewLazySystemDLL("kernel32")
|
||||||
outputDebugStringW = kernel32.NewProc("OutputDebugStringW")
|
outputDebugStringW = kernel32.NewProc("OutputDebugStringW")
|
||||||
debugView *logger
|
debugView *logger
|
||||||
)
|
)
|
||||||
|
|||||||
+2
-2
@@ -12,12 +12,12 @@ 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 {
|
||||||
CAMetalLayer *l = [CAMetalLayer layer];
|
CAMetalLayer *l = [CAMetalLayer layer];
|
||||||
l.autoresizingMask = kCALayerHeightSizable|kCALayerWidthSizable;
|
l.autoresizingMask = kCALayerHeightSizable|kCALayerWidthSizable;
|
||||||
l.needsDisplayOnBoundsChange = YES;
|
l.needsDisplayOnBoundsChange = YES;
|
||||||
l.presentsWithTransaction = YES;
|
l.presentsWithTransaction = presentWithTrans;
|
||||||
return l;
|
return l;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,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)
|
||||||
@@ -201,8 +195,6 @@ type driver interface {
|
|||||||
Configure([]Option)
|
Configure([]Option)
|
||||||
// SetCursor updates the current cursor to name.
|
// SetCursor updates the current cursor to name.
|
||||||
SetCursor(cursor pointer.Cursor)
|
SetCursor(cursor pointer.Cursor)
|
||||||
// Wakeup wakes up the event loop and sends a WakeupEvent.
|
|
||||||
// Wakeup()
|
|
||||||
// Perform actions on the window.
|
// Perform actions on the window.
|
||||||
Perform(system.Action)
|
Perform(system.Action)
|
||||||
// EditorStateChanged notifies the driver that the editor state changed.
|
// EditorStateChanged notifies the driver that the editor state changed.
|
||||||
|
|||||||
+10
-4
@@ -575,10 +575,7 @@ func Java_org_gioui_GioView_onFrameCallback(env *C.JNIEnv, class C.jclass, view
|
|||||||
if !exist {
|
if !exist {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if w.visible && w.animating {
|
w.draw(env, false)
|
||||||
w.draw(env, false)
|
|
||||||
callVoidMethod(env, w.view, gioView.postFrameCallback)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Java_org_gioui_GioView_onBack
|
//export Java_org_gioui_GioView_onBack
|
||||||
@@ -882,6 +879,9 @@ func (w *window) draw(env *C.JNIEnv, sync bool) {
|
|||||||
},
|
},
|
||||||
Sync: sync,
|
Sync: sync,
|
||||||
})
|
})
|
||||||
|
if w.animating {
|
||||||
|
callVoidMethod(env, w.view, gioView.postFrameCallback)
|
||||||
|
}
|
||||||
a11yActive, err := callBooleanMethod(env, w.view, gioView.isA11yActive)
|
a11yActive, err := callBooleanMethod(env, w.view, gioView.isA11yActive)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -1317,6 +1317,9 @@ func findClass(env *C.JNIEnv, name string) C.jclass {
|
|||||||
return C.jni_FindClass(env, cn)
|
return C.jni_FindClass(env, cn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func osMain() {
|
||||||
|
}
|
||||||
|
|
||||||
func newWindow(window *callbacks, options []Option) {
|
func newWindow(window *callbacks, options []Option) {
|
||||||
mainWindow.in <- windowAndConfig{window, options}
|
mainWindow.in <- windowAndConfig{window, options}
|
||||||
<-mainWindow.windows
|
<-mainWindow.windows
|
||||||
@@ -1492,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{})
|
||||||
|
}
|
||||||
|
|||||||
+13
-6
@@ -15,8 +15,8 @@ __attribute__ ((visibility ("hidden"))) void gio_hideCursor();
|
|||||||
__attribute__ ((visibility ("hidden"))) void gio_showCursor();
|
__attribute__ ((visibility ("hidden"))) void gio_showCursor();
|
||||||
__attribute__ ((visibility ("hidden"))) void gio_setCursor(NSUInteger curID);
|
__attribute__ ((visibility ("hidden"))) void gio_setCursor(NSUInteger curID);
|
||||||
|
|
||||||
static int isMainThread() {
|
static bool isMainThread() {
|
||||||
return [NSThread isMainThread] ? 1 : 0;
|
return [NSThread isMainThread];
|
||||||
}
|
}
|
||||||
|
|
||||||
static NSUInteger nsstringLength(CFTypeRef cstr) {
|
static NSUInteger nsstringLength(CFTypeRef cstr) {
|
||||||
@@ -75,6 +75,10 @@ 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 isMainThread() {
|
if isMainThread() {
|
||||||
@@ -87,10 +91,6 @@ func runOnMain(f func()) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func isMainThread() bool {
|
|
||||||
return C.isMainThread() != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
//export gio_dispatchMainFuncs
|
//export gio_dispatchMainFuncs
|
||||||
func gio_dispatchMainFuncs() {
|
func gio_dispatchMainFuncs() {
|
||||||
for {
|
for {
|
||||||
@@ -263,3 +263,10 @@ func windowSetCursor(from, to pointer.Cursor) pointer.Cursor {
|
|||||||
C.gio_setCursor(C.NSUInteger(macosCursorID[to]))
|
C.gio_setCursor(C.NSUInteger(macosCursorID[to]))
|
||||||
return to
|
return to
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *window) wakeup() {
|
||||||
|
runOnMain(func() {
|
||||||
|
w.loop.Wakeup()
|
||||||
|
w.loop.FlushEvents()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
#include "_cgo_export.h"
|
||||||
|
|
||||||
|
void gio_wakeupMainThread(void) {
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
gio_dispatchMainFuncs();
|
||||||
|
});
|
||||||
|
}
|
||||||
+50
-8
@@ -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"
|
||||||
@@ -388,17 +396,51 @@ func newWindow(win *callbacks, options []Option) {
|
|||||||
<-mainWindow.windows
|
<-mainWindow.windows
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_runMain
|
var mainMode = mainModeUndefined
|
||||||
func gio_runMain() {
|
|
||||||
runMain()
|
const (
|
||||||
|
mainModeUndefined = iota
|
||||||
|
mainModeExe
|
||||||
|
mainModeLibrary
|
||||||
|
)
|
||||||
|
|
||||||
|
func osMain() {
|
||||||
|
if !isMainThread() {
|
||||||
|
panic("app.Main must be run on the main goroutine")
|
||||||
|
}
|
||||||
|
switch mainMode {
|
||||||
|
case mainModeUndefined:
|
||||||
|
mainMode = mainModeExe
|
||||||
|
var argv []*C.char
|
||||||
|
for _, arg := range os.Args {
|
||||||
|
a := C.CString(arg)
|
||||||
|
defer C.free(unsafe.Pointer(a))
|
||||||
|
argv = append(argv, a)
|
||||||
|
}
|
||||||
|
C.gio_applicationMain(C.int(len(argv)), unsafe.SliceData(argv))
|
||||||
|
case mainModeExe:
|
||||||
|
panic("app.Main may be called only once")
|
||||||
|
case mainModeLibrary:
|
||||||
|
// Do nothing, we're embedded as a library.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) wakeup() {
|
//export gio_runMain
|
||||||
runOnMain(func() {
|
func gio_runMain() {
|
||||||
w.loop.Wakeup()
|
if !isMainThread() {
|
||||||
w.loop.FlushEvents()
|
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{})
|
||||||
|
}
|
||||||
|
|||||||
+23
-5
@@ -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) {
|
||||||
@@ -277,8 +281,22 @@ void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
|
|||||||
v.handle = handle;
|
v.handle = handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
void gio_wakeupMainThread(void) {
|
@interface _gioAppDelegate : UIResponder <UIApplicationDelegate>
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
@property (strong, nonatomic) UIWindow *window;
|
||||||
gio_dispatchMainFuncs();
|
@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]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,6 +741,10 @@ func (w *window) navigationColor(c color.NRGBA) {
|
|||||||
theme.Set("content", fmt.Sprintf("#%06X", []uint8{rgba.R, rgba.G, rgba.B}))
|
theme.Set("content", fmt.Sprintf("#%06X", []uint8{rgba.R, rgba.G, rgba.B}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func osMain() {
|
||||||
|
select {}
|
||||||
|
}
|
||||||
|
|
||||||
func translateKey(k string) (key.Name, bool) {
|
func translateKey(k string) (key.Name, bool) {
|
||||||
var n key.Name
|
var n key.Name
|
||||||
|
|
||||||
@@ -818,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())
|
||||||
|
}
|
||||||
|
|||||||
+266
-207
@@ -39,8 +39,8 @@ import (
|
|||||||
#define MOUSE_DOWN 3
|
#define MOUSE_DOWN 3
|
||||||
#define MOUSE_SCROLL 4
|
#define MOUSE_SCROLL 4
|
||||||
|
|
||||||
__attribute__ ((visibility ("hidden"))) void gio_initApp(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);
|
||||||
|
|
||||||
@@ -109,6 +109,14 @@ static void makeKeyAndOrderFront(CFTypeRef windowRef) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void makeFirstResponder(CFTypeRef windowRef, CFTypeRef viewRef) {
|
||||||
|
@autoreleasepool {
|
||||||
|
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||||
|
NSView *view = (__bridge NSView *)viewRef;
|
||||||
|
[window makeFirstResponder:view];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void toggleFullScreen(CFTypeRef windowRef) {
|
static void toggleFullScreen(CFTypeRef windowRef) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||||
@@ -195,6 +203,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;
|
||||||
@@ -230,6 +246,13 @@ static int isWindowZoomed(CFTypeRef windowRef) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int isWindowMiniaturized(CFTypeRef windowRef) {
|
||||||
|
@autoreleasepool {
|
||||||
|
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||||
|
return window.miniaturized ? 1 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void zoomWindow(CFTypeRef windowRef) {
|
static void zoomWindow(CFTypeRef windowRef) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||||
@@ -290,13 +313,18 @@ static void invalidateCharacterCoordinates(CFTypeRef viewRef) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dispatchEvent(void) {
|
static void interpretKeyEvents(CFTypeRef viewRef, CFTypeRef eventRef) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny
|
NSView *view = (__bridge NSView *)viewRef;
|
||||||
untilDate:[NSDate distantFuture]
|
NSEvent *event = (__bridge NSEvent *)eventRef;
|
||||||
inMode:NSDefaultRunLoopMode
|
[view interpretKeyEvents:[NSArray arrayWithObject:event]];
|
||||||
dequeue:YES];
|
}
|
||||||
[NSApp sendEvent:event];
|
}
|
||||||
|
|
||||||
|
static int isMiniaturized(CFTypeRef windowRef) {
|
||||||
|
@autoreleasepool {
|
||||||
|
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||||
|
return window.miniaturized ? 1 : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
@@ -320,7 +348,6 @@ type window struct {
|
|||||||
view C.CFTypeRef
|
view C.CFTypeRef
|
||||||
w *callbacks
|
w *callbacks
|
||||||
anim bool
|
anim bool
|
||||||
visible bool
|
|
||||||
displayLink *displayLink
|
displayLink *displayLink
|
||||||
// redraw is a single entry channel for making sure only one
|
// redraw is a single entry channel for making sure only one
|
||||||
// display link redraw request is in flight.
|
// display link redraw request is in flight.
|
||||||
@@ -328,21 +355,29 @@ type window struct {
|
|||||||
cursor pointer.Cursor
|
cursor pointer.Cursor
|
||||||
pointerBtns pointer.Buttons
|
pointerBtns pointer.Buttons
|
||||||
loop *eventLoop
|
loop *eventLoop
|
||||||
|
lastMods C.NSUInteger
|
||||||
|
|
||||||
scale float32
|
scale float32
|
||||||
config Config
|
config Config
|
||||||
|
|
||||||
|
keysDown map[key.Name]struct{}
|
||||||
|
// cmdKeys is for storing the current key event while
|
||||||
|
// waiting for a doCommandBySelector.
|
||||||
|
cmdKeys cmdKeys
|
||||||
}
|
}
|
||||||
|
|
||||||
// launched is closed after gio_initApp returns.
|
type cmdKeys struct {
|
||||||
|
eventStr string
|
||||||
|
eventMods key.Modifiers
|
||||||
|
}
|
||||||
|
|
||||||
|
// launched is closed when applicationDidFinishLaunching is called.
|
||||||
var launched = make(chan struct{})
|
var launched = make(chan struct{})
|
||||||
|
|
||||||
// nextTopLeft is the offset to use for the next window's call to
|
// nextTopLeft is the offset to use for the next window's call to
|
||||||
// cascadeTopLeftFromPoint.
|
// cascadeTopLeftFromPoint.
|
||||||
var nextTopLeft C.NSPoint
|
var nextTopLeft C.NSPoint
|
||||||
|
|
||||||
// mainThreadWindow is the window currently in control of the main thread.
|
|
||||||
var mainThreadWindow *window
|
|
||||||
|
|
||||||
func windowFor(h C.uintptr_t) *window {
|
func windowFor(h C.uintptr_t) *window {
|
||||||
return cgo.Handle(h).Value().(*window)
|
return cgo.Handle(h).Value().(*window)
|
||||||
}
|
}
|
||||||
@@ -372,11 +407,23 @@ func (w *window) WriteClipboard(mime string, s []byte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) updateWindowMode() {
|
func (w *window) updateWindowMode() {
|
||||||
|
w.scale = float32(C.getViewBackingScale(w.view))
|
||||||
|
wf, hf := float32(C.viewWidth(w.view)), float32(C.viewHeight(w.view))
|
||||||
|
w.config.Size = image.Point{
|
||||||
|
X: int(wf*w.scale + .5),
|
||||||
|
Y: int(hf*w.scale + .5),
|
||||||
|
}
|
||||||
|
w.config.Mode = Windowed
|
||||||
|
window := C.windowForView(w.view)
|
||||||
|
if window == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
style := int(C.getWindowStyleMask(C.windowForView(w.view)))
|
style := int(C.getWindowStyleMask(C.windowForView(w.view)))
|
||||||
if style&C.NSWindowStyleMaskFullScreen != 0 {
|
switch {
|
||||||
|
case style&C.NSWindowStyleMaskFullScreen != 0:
|
||||||
w.config.Mode = Fullscreen
|
w.config.Mode = Fullscreen
|
||||||
} else {
|
case C.isWindowZoomed(window) != 0:
|
||||||
w.config.Mode = Windowed
|
w.config.Mode = Maximized
|
||||||
}
|
}
|
||||||
w.config.Decorated = style&C.NSWindowStyleMaskFullSizeContentView == 0
|
w.config.Decorated = style&C.NSWindowStyleMaskFullSizeContentView == 0
|
||||||
}
|
}
|
||||||
@@ -384,102 +431,82 @@ func (w *window) updateWindowMode() {
|
|||||||
func (w *window) Configure(options []Option) {
|
func (w *window) Configure(options []Option) {
|
||||||
screenScale := float32(C.getScreenBackingScale())
|
screenScale := float32(C.getScreenBackingScale())
|
||||||
cfg := configFor(screenScale)
|
cfg := configFor(screenScale)
|
||||||
prev := w.config
|
|
||||||
w.updateWindowMode()
|
|
||||||
cnf := w.config
|
cnf := w.config
|
||||||
cnf.apply(cfg, options)
|
cnf.apply(cfg, options)
|
||||||
window := C.windowForView(w.view)
|
window := C.windowForView(w.view)
|
||||||
|
|
||||||
|
mask := C.getWindowStyleMask(window)
|
||||||
|
fullscreen := mask&C.NSWindowStyleMaskFullScreen != 0
|
||||||
switch cnf.Mode {
|
switch cnf.Mode {
|
||||||
case Fullscreen:
|
case Fullscreen:
|
||||||
switch prev.Mode {
|
if C.isWindowMiniaturized(window) != 0 {
|
||||||
case Fullscreen:
|
|
||||||
case Minimized:
|
|
||||||
C.unhideWindow(window)
|
C.unhideWindow(window)
|
||||||
fallthrough
|
}
|
||||||
default:
|
if !fullscreen {
|
||||||
w.config.Mode = Fullscreen
|
|
||||||
C.toggleFullScreen(window)
|
C.toggleFullScreen(window)
|
||||||
}
|
}
|
||||||
case Minimized:
|
case Minimized:
|
||||||
switch prev.Mode {
|
C.hideWindow(window)
|
||||||
case Minimized, Fullscreen:
|
|
||||||
default:
|
|
||||||
w.config.Mode = Minimized
|
|
||||||
C.hideWindow(window)
|
|
||||||
}
|
|
||||||
case Maximized:
|
case Maximized:
|
||||||
switch prev.Mode {
|
if C.isWindowMiniaturized(window) != 0 {
|
||||||
case Fullscreen:
|
|
||||||
case Minimized:
|
|
||||||
C.unhideWindow(window)
|
C.unhideWindow(window)
|
||||||
fallthrough
|
}
|
||||||
default:
|
if fullscreen {
|
||||||
w.config.Mode = Maximized
|
C.toggleFullScreen(window)
|
||||||
w.setTitle(prev, cnf)
|
}
|
||||||
if C.isWindowZoomed(window) == 0 {
|
w.setTitle(cnf.Title)
|
||||||
C.zoomWindow(window)
|
if C.isWindowZoomed(window) == 0 {
|
||||||
}
|
C.zoomWindow(window)
|
||||||
}
|
}
|
||||||
case Windowed:
|
case Windowed:
|
||||||
switch prev.Mode {
|
if C.isWindowMiniaturized(window) != 0 {
|
||||||
case Fullscreen:
|
|
||||||
C.toggleFullScreen(window)
|
|
||||||
case Minimized:
|
|
||||||
C.unhideWindow(window)
|
C.unhideWindow(window)
|
||||||
case Maximized:
|
|
||||||
if C.isWindowZoomed(window) != 0 {
|
|
||||||
C.zoomWindow(window)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
w.config.Mode = Windowed
|
if fullscreen {
|
||||||
w.setTitle(prev, cnf)
|
C.toggleFullScreen(window)
|
||||||
if prev.Size != cnf.Size {
|
|
||||||
w.config.Size = cnf.Size
|
|
||||||
cnf.Size = cnf.Size.Div(int(screenScale))
|
|
||||||
C.setSize(window, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y))
|
|
||||||
}
|
}
|
||||||
if prev.MinSize != cnf.MinSize {
|
w.setTitle(cnf.Title)
|
||||||
w.config.MinSize = cnf.MinSize
|
w.config.Size = cnf.Size
|
||||||
cnf.MinSize = cnf.MinSize.Div(int(screenScale))
|
cnf.Size = cnf.Size.Div(int(screenScale))
|
||||||
C.setMinSize(window, C.CGFloat(cnf.MinSize.X), C.CGFloat(cnf.MinSize.Y))
|
C.setSize(window, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y))
|
||||||
}
|
w.config.MinSize = cnf.MinSize
|
||||||
if prev.MaxSize != cnf.MaxSize {
|
cnf.MinSize = cnf.MinSize.Div(int(screenScale))
|
||||||
w.config.MaxSize = cnf.MaxSize
|
C.setMinSize(window, C.CGFloat(cnf.MinSize.X), C.CGFloat(cnf.MinSize.Y))
|
||||||
cnf.MaxSize = cnf.MaxSize.Div(int(screenScale))
|
w.config.MaxSize = cnf.MaxSize
|
||||||
|
cnf.MaxSize = cnf.MaxSize.Div(int(screenScale))
|
||||||
|
if cnf.MaxSize != (image.Point{}) {
|
||||||
C.setMaxSize(window, C.CGFloat(cnf.MaxSize.X), C.CGFloat(cnf.MaxSize.Y))
|
C.setMaxSize(window, C.CGFloat(cnf.MaxSize.X), C.CGFloat(cnf.MaxSize.Y))
|
||||||
}
|
}
|
||||||
}
|
if C.isWindowZoomed(window) != 0 {
|
||||||
if cnf.Decorated != prev.Decorated {
|
C.zoomWindow(window)
|
||||||
w.config.Decorated = cnf.Decorated
|
|
||||||
mask := C.getWindowStyleMask(window)
|
|
||||||
style := C.NSWindowStyleMask(C.NSWindowStyleMaskTitled | C.NSWindowStyleMaskResizable | C.NSWindowStyleMaskMiniaturizable | C.NSWindowStyleMaskClosable)
|
|
||||||
style = C.NSWindowStyleMaskFullSizeContentView
|
|
||||||
mask &^= style
|
|
||||||
barTrans := C.int(C.NO)
|
|
||||||
titleVis := C.NSWindowTitleVisibility(C.NSWindowTitleVisible)
|
|
||||||
if !cnf.Decorated {
|
|
||||||
mask |= style
|
|
||||||
barTrans = C.YES
|
|
||||||
titleVis = C.NSWindowTitleHidden
|
|
||||||
}
|
}
|
||||||
C.setWindowTitlebarAppearsTransparent(window, barTrans)
|
|
||||||
C.setWindowTitleVisibility(window, titleVis)
|
|
||||||
C.setWindowStyleMask(window, mask)
|
|
||||||
C.setWindowStandardButtonHidden(window, C.NSWindowCloseButton, barTrans)
|
|
||||||
C.setWindowStandardButtonHidden(window, C.NSWindowMiniaturizeButton, barTrans)
|
|
||||||
C.setWindowStandardButtonHidden(window, C.NSWindowZoomButton, barTrans)
|
|
||||||
}
|
}
|
||||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
style := C.NSWindowStyleMask(C.NSWindowStyleMaskTitled | C.NSWindowStyleMaskResizable | C.NSWindowStyleMaskMiniaturizable | C.NSWindowStyleMaskClosable)
|
||||||
|
style = C.NSWindowStyleMaskFullSizeContentView
|
||||||
|
mask &^= style
|
||||||
|
barTrans := C.int(C.NO)
|
||||||
|
titleVis := C.NSWindowTitleVisibility(C.NSWindowTitleVisible)
|
||||||
|
if !cnf.Decorated {
|
||||||
|
mask |= style
|
||||||
|
barTrans = C.YES
|
||||||
|
titleVis = C.NSWindowTitleHidden
|
||||||
|
}
|
||||||
|
C.setWindowTitlebarAppearsTransparent(window, barTrans)
|
||||||
|
C.setWindowTitleVisibility(window, titleVis)
|
||||||
|
C.setWindowStyleMask(window, mask)
|
||||||
|
C.setWindowStandardButtonHidden(window, C.NSWindowCloseButton, barTrans)
|
||||||
|
C.setWindowStandardButtonHidden(window, C.NSWindowMiniaturizeButton, barTrans)
|
||||||
|
C.setWindowStandardButtonHidden(window, C.NSWindowZoomButton, barTrans)
|
||||||
|
// When toggling the titlebar, the layer doesn't update its frame
|
||||||
|
// until the next resize. Force it.
|
||||||
|
C.resetLayerFrame(w.view)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) setTitle(prev, cnf Config) {
|
func (w *window) setTitle(title string) {
|
||||||
if prev.Title != cnf.Title {
|
w.config.Title = title
|
||||||
w.config.Title = cnf.Title
|
titleC := stringToNSString(title)
|
||||||
title := stringToNSString(cnf.Title)
|
defer C.CFRelease(titleC)
|
||||||
defer C.CFRelease(title)
|
C.setTitle(C.windowForView(w.view), titleC)
|
||||||
C.setTitle(C.windowForView(w.view), title)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Perform(acts system.Action) {
|
func (w *window) Perform(acts system.Action) {
|
||||||
@@ -522,7 +549,8 @@ func (w *window) SetInputHint(_ key.InputHint) {}
|
|||||||
|
|
||||||
func (w *window) SetAnimating(anim bool) {
|
func (w *window) SetAnimating(anim bool) {
|
||||||
w.anim = anim
|
w.anim = anim
|
||||||
if w.anim && w.visible {
|
window := C.windowForView(w.view)
|
||||||
|
if w.anim && window != 0 && C.isMiniaturized(window) == 0 {
|
||||||
w.displayLink.Start()
|
w.displayLink.Start()
|
||||||
} else {
|
} else {
|
||||||
w.displayLink.Stop()
|
w.displayLink.Stop()
|
||||||
@@ -540,23 +568,92 @@ func (w *window) runOnMain(f func()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onKeys
|
//export gio_onKeys
|
||||||
func gio_onKeys(h C.uintptr_t, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger, keyDown C.bool) {
|
func gio_onKeys(h C.uintptr_t, event C.CFTypeRef, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger, keyDown C.bool) {
|
||||||
|
w := windowFor(h)
|
||||||
|
if w.keysDown == nil {
|
||||||
|
w.keysDown = make(map[key.Name]struct{})
|
||||||
|
}
|
||||||
str := nsstringToString(cstr)
|
str := nsstringToString(cstr)
|
||||||
kmods := convertMods(mods)
|
kmods := convertMods(mods)
|
||||||
ks := key.Release
|
ks := key.Release
|
||||||
if keyDown {
|
if keyDown {
|
||||||
ks = key.Press
|
ks = key.Press
|
||||||
|
w.cmdKeys.eventStr = str
|
||||||
|
w.cmdKeys.eventMods = kmods
|
||||||
|
C.interpretKeyEvents(w.view, event)
|
||||||
}
|
}
|
||||||
w := windowFor(h)
|
|
||||||
for _, k := range str {
|
for _, k := range str {
|
||||||
if n, ok := convertKey(k); ok {
|
if n, ok := convertKey(k); ok {
|
||||||
w.ProcessEvent(key.Event{
|
ke := key.Event{
|
||||||
Name: n,
|
Name: n,
|
||||||
Modifiers: kmods,
|
Modifiers: kmods,
|
||||||
State: ks,
|
State: ks,
|
||||||
|
}
|
||||||
|
if keyDown {
|
||||||
|
w.keysDown[ke.Name] = struct{}{}
|
||||||
|
if _, isCmd := convertCommandKey(k); isCmd || kmods.Contain(key.ModCommand) {
|
||||||
|
// doCommandBySelector already processed the event.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, pressed := w.keysDown[n]; !pressed {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
delete(w.keysDown, n)
|
||||||
|
}
|
||||||
|
w.ProcessEvent(ke)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//export gio_onCommandBySelector
|
||||||
|
func gio_onCommandBySelector(h C.uintptr_t) C.bool {
|
||||||
|
w := windowFor(h)
|
||||||
|
ev := w.cmdKeys
|
||||||
|
w.cmdKeys = cmdKeys{}
|
||||||
|
handled := false
|
||||||
|
for _, k := range ev.eventStr {
|
||||||
|
n, ok := convertCommandKey(k)
|
||||||
|
if !ok && ev.eventMods.Contain(key.ModCommand) {
|
||||||
|
n, ok = convertKey(k)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ke := key.Event{
|
||||||
|
Name: n,
|
||||||
|
Modifiers: ev.eventMods,
|
||||||
|
State: key.Press,
|
||||||
|
}
|
||||||
|
handled = w.processEvent(ke) || handled
|
||||||
|
}
|
||||||
|
return C.bool(handled)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export gio_onFlagsChanged
|
||||||
|
func gio_onFlagsChanged(h C.uintptr_t, curMods C.NSUInteger) {
|
||||||
|
w := windowFor(h)
|
||||||
|
|
||||||
|
mods := []C.NSUInteger{C.NSControlKeyMask, C.NSAlternateKeyMask, C.NSShiftKeyMask, C.NSCommandKeyMask}
|
||||||
|
keys := []key.Name{key.NameCtrl, key.NameAlt, key.NameShift, key.NameCommand}
|
||||||
|
|
||||||
|
for i, mod := range mods {
|
||||||
|
wasPressed := w.lastMods&mod != 0
|
||||||
|
isPressed := curMods&mod != 0
|
||||||
|
|
||||||
|
if wasPressed != isPressed {
|
||||||
|
st := key.Release
|
||||||
|
if isPressed {
|
||||||
|
st = key.Press
|
||||||
|
}
|
||||||
|
w.ProcessEvent(key.Event{
|
||||||
|
Name: keys[i],
|
||||||
|
State: st,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.lastMods = curMods
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onText
|
//export gio_onText
|
||||||
@@ -749,6 +846,15 @@ func gio_substringForProposedRange(h C.uintptr_t, crng C.NSRange, actual C.NSRan
|
|||||||
//export gio_insertText
|
//export gio_insertText
|
||||||
func gio_insertText(h C.uintptr_t, cstr C.CFTypeRef, crng C.NSRange) {
|
func gio_insertText(h C.uintptr_t, cstr C.CFTypeRef, crng C.NSRange) {
|
||||||
w := windowFor(h)
|
w := windowFor(h)
|
||||||
|
str := nsstringToString(cstr)
|
||||||
|
// macOS IME in some cases calls insertText for command keys such as backspace
|
||||||
|
// instead of doCommandBySelector.
|
||||||
|
for _, r := range str {
|
||||||
|
if _, ok := convertCommandKey(r); ok {
|
||||||
|
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
state := w.w.EditorState()
|
state := w.w.EditorState()
|
||||||
rng := state.compose
|
rng := state.compose
|
||||||
if rng.Start == -1 {
|
if rng.Start == -1 {
|
||||||
@@ -760,7 +866,6 @@ func gio_insertText(h C.uintptr_t, cstr C.CFTypeRef, crng C.NSRange) {
|
|||||||
End: state.RunesIndex(int(crng.location + crng.length)),
|
End: state.RunesIndex(int(crng.location + crng.length)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
str := nsstringToString(cstr)
|
|
||||||
w.w.EditorReplace(rng, str)
|
w.w.EditorReplace(rng, str)
|
||||||
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
|
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
|
||||||
start := rng.Start
|
start := rng.Start
|
||||||
@@ -801,24 +906,19 @@ func gio_firstRectForCharacterRange(h C.uintptr_t, crng C.NSRange, actual C.NSRa
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) draw() {
|
func (w *window) draw() {
|
||||||
|
cnf := w.config
|
||||||
|
w.updateWindowMode()
|
||||||
|
if w.config != cnf {
|
||||||
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
|
}
|
||||||
select {
|
select {
|
||||||
case <-w.redraw:
|
case <-w.redraw:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
w.visible = true
|
|
||||||
if w.anim {
|
if w.anim {
|
||||||
w.SetAnimating(w.anim)
|
w.SetAnimating(w.anim)
|
||||||
}
|
}
|
||||||
w.scale = float32(C.getViewBackingScale(w.view))
|
sz := w.config.Size
|
||||||
wf, hf := float32(C.viewWidth(w.view)), float32(C.viewHeight(w.view))
|
|
||||||
sz := image.Point{
|
|
||||||
X: int(wf*w.scale + .5),
|
|
||||||
Y: int(hf*w.scale + .5),
|
|
||||||
}
|
|
||||||
if sz != w.config.Size {
|
|
||||||
w.config.Size = sz
|
|
||||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
|
||||||
}
|
|
||||||
if sz.X == 0 || sz.Y == 0 {
|
if sz.X == 0 || sz.Y == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -826,7 +926,7 @@ func (w *window) draw() {
|
|||||||
w.ProcessEvent(frameEvent{
|
w.ProcessEvent(frameEvent{
|
||||||
FrameEvent: FrameEvent{
|
FrameEvent: FrameEvent{
|
||||||
Now: time.Now(),
|
Now: time.Now(),
|
||||||
Size: w.config.Size,
|
Size: sz,
|
||||||
Metric: cfg,
|
Metric: cfg,
|
||||||
},
|
},
|
||||||
Sync: true,
|
Sync: true,
|
||||||
@@ -834,52 +934,29 @@ func (w *window) draw() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) ProcessEvent(e event.Event) {
|
func (w *window) ProcessEvent(e event.Event) {
|
||||||
w.w.ProcessEvent(e)
|
w.processEvent(e)
|
||||||
// The main thread window deliver events in Event.
|
}
|
||||||
if w != mainThreadWindow {
|
|
||||||
w.loop.FlushEvents()
|
func (w *window) processEvent(e event.Event) bool {
|
||||||
}
|
handled := w.w.ProcessEvent(e)
|
||||||
|
w.loop.FlushEvents()
|
||||||
|
return handled
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Event() event.Event {
|
func (w *window) Event() event.Event {
|
||||||
if !isMainThread() {
|
return w.loop.Event()
|
||||||
return w.loop.Event()
|
|
||||||
}
|
|
||||||
mainThreadWindow = w
|
|
||||||
defer func() { mainThreadWindow = nil }()
|
|
||||||
for {
|
|
||||||
if evt, ok := w.loop.win.nextEvent(); ok {
|
|
||||||
return evt
|
|
||||||
}
|
|
||||||
C.dispatchEvent()
|
|
||||||
gio_dispatchMainFuncs()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Invalidate() {
|
func (w *window) Invalidate() {
|
||||||
if isMainThread() {
|
|
||||||
mainThreadWindow = w
|
|
||||||
defer func() { mainThreadWindow = nil }()
|
|
||||||
}
|
|
||||||
w.loop.Invalidate()
|
w.loop.Invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Run(f func()) {
|
func (w *window) Run(f func()) {
|
||||||
if isMainThread() {
|
|
||||||
mainThreadWindow = w
|
|
||||||
defer func() { mainThreadWindow = nil }()
|
|
||||||
}
|
|
||||||
w.loop.Run(f)
|
w.loop.Run(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Frame(frame *op.Ops) {
|
func (w *window) Frame(frame *op.Ops) {
|
||||||
if !isMainThread() {
|
w.loop.Frame(frame)
|
||||||
w.loop.Frame(frame)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mainThreadWindow = w
|
|
||||||
defer func() { mainThreadWindow = nil }()
|
|
||||||
w.loop.win.ProcessFrame(frame, nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func configFor(scale float32) unit.Metric {
|
func configFor(scale float32) unit.Metric {
|
||||||
@@ -897,7 +974,6 @@ func gio_onAttached(h C.uintptr_t, attached C.int) {
|
|||||||
w.ProcessEvent(AppKitViewEvent{View: uintptr(w.view), Layer: uintptr(layer)})
|
w.ProcessEvent(AppKitViewEvent{View: uintptr(w.view), Layer: uintptr(layer)})
|
||||||
} else {
|
} else {
|
||||||
w.ProcessEvent(AppKitViewEvent{})
|
w.ProcessEvent(AppKitViewEvent{})
|
||||||
w.visible = false
|
|
||||||
w.SetAnimating(w.anim)
|
w.SetAnimating(w.anim)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -912,64 +988,31 @@ func gio_onDestroy(h C.uintptr_t) {
|
|||||||
w.view = 0
|
w.view = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onHide
|
//export gio_onFinishLaunching
|
||||||
func gio_onHide(h C.uintptr_t) {
|
func gio_onFinishLaunching() {
|
||||||
w := windowFor(h)
|
close(launched)
|
||||||
w.visible = false
|
|
||||||
w.SetAnimating(w.anim)
|
|
||||||
}
|
|
||||||
|
|
||||||
//export gio_onShow
|
|
||||||
func gio_onShow(h C.uintptr_t) {
|
|
||||||
w := windowFor(h)
|
|
||||||
w.draw()
|
|
||||||
}
|
|
||||||
|
|
||||||
//export gio_onFullscreen
|
|
||||||
func gio_onFullscreen(h C.uintptr_t) {
|
|
||||||
w := windowFor(h)
|
|
||||||
w.config.Mode = Fullscreen
|
|
||||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
|
||||||
}
|
|
||||||
|
|
||||||
//export gio_onWindowed
|
|
||||||
func gio_onWindowed(h C.uintptr_t) {
|
|
||||||
w := windowFor(h)
|
|
||||||
w.config.Mode = Windowed
|
|
||||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWindow(win *callbacks, options []Option) {
|
func newWindow(win *callbacks, options []Option) {
|
||||||
w := &window{
|
<-launched
|
||||||
redraw: make(chan struct{}, 1),
|
res := make(chan struct{})
|
||||||
w: win,
|
|
||||||
}
|
|
||||||
w.loop = newEventLoop(w.w, w.wakeup)
|
|
||||||
if isMainThread() {
|
|
||||||
mainThreadWindow = w
|
|
||||||
defer func() { mainThreadWindow = nil }()
|
|
||||||
select {
|
|
||||||
case <-launched:
|
|
||||||
default:
|
|
||||||
// If we're the main thread, initialize the GUI.
|
|
||||||
C.gio_initApp()
|
|
||||||
close(launched)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
<-launched
|
|
||||||
}
|
|
||||||
res := make(chan struct{}, 1)
|
|
||||||
runOnMain(func() {
|
runOnMain(func() {
|
||||||
|
w := &window{
|
||||||
|
redraw: make(chan struct{}, 1),
|
||||||
|
w: win,
|
||||||
|
}
|
||||||
|
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
|
||||||
}
|
}
|
||||||
window := C.gio_createWindow(w.view, 0, 0, 0, 0, 0, 0)
|
window := C.gio_createWindow(w.view, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y), 0, 0, 0, 0)
|
||||||
// Release our reference now that the NSWindow has it.
|
// Release our reference now that the NSWindow has it.
|
||||||
C.CFRelease(w.view)
|
C.CFRelease(w.view)
|
||||||
w.updateWindowMode()
|
|
||||||
w.Configure(options)
|
w.Configure(options)
|
||||||
if nextTopLeft.x == 0 && nextTopLeft.y == 0 {
|
if nextTopLeft.x == 0 && nextTopLeft.y == 0 {
|
||||||
// cascadeTopLeftFromPoint treats (0, 0) as a no-op,
|
// cascadeTopLeftFromPoint treats (0, 0) as a no-op,
|
||||||
@@ -977,14 +1020,19 @@ func newWindow(win *callbacks, options []Option) {
|
|||||||
nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft)
|
nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft)
|
||||||
}
|
}
|
||||||
nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft)
|
nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft)
|
||||||
|
C.makeFirstResponder(window, w.view)
|
||||||
// makeKeyAndOrderFront assumes ownership of our window reference.
|
// makeKeyAndOrderFront assumes ownership of our window reference.
|
||||||
C.makeKeyAndOrderFront(window)
|
C.makeKeyAndOrderFront(window)
|
||||||
})
|
})
|
||||||
<-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")
|
||||||
}
|
}
|
||||||
@@ -997,9 +1045,7 @@ func (w *window) init() error {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.runOnMain(func() {
|
w.runOnMain(func() {
|
||||||
if w.visible {
|
C.setNeedsDisplay(w.view)
|
||||||
C.setNeedsDisplay(w.view)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
w.displayLink = dl
|
w.displayLink = dl
|
||||||
@@ -1012,10 +1058,17 @@ func (w *window) init() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertKey(k rune) (key.Name, bool) {
|
func osMain() {
|
||||||
|
if !isMainThread() {
|
||||||
|
panic("app.Main must run on the main goroutine")
|
||||||
|
}
|
||||||
|
C.gio_main()
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertCommandKey(k rune) (key.Name, bool) {
|
||||||
var n key.Name
|
var n key.Name
|
||||||
switch k {
|
switch k {
|
||||||
case 0x1b:
|
case '\x1b': // ASCII escape.
|
||||||
n = key.NameEscape
|
n = key.NameEscape
|
||||||
case C.NSLeftArrowFunctionKey:
|
case C.NSLeftArrowFunctionKey:
|
||||||
n = key.NameLeftArrow
|
n = key.NameLeftArrow
|
||||||
@@ -1025,22 +1078,36 @@ func convertKey(k rune) (key.Name, bool) {
|
|||||||
n = key.NameUpArrow
|
n = key.NameUpArrow
|
||||||
case C.NSDownArrowFunctionKey:
|
case C.NSDownArrowFunctionKey:
|
||||||
n = key.NameDownArrow
|
n = key.NameDownArrow
|
||||||
case 0xd:
|
case '\r':
|
||||||
n = key.NameReturn
|
n = key.NameReturn
|
||||||
case 0x3:
|
case '\x03':
|
||||||
n = key.NameEnter
|
n = key.NameEnter
|
||||||
case C.NSHomeFunctionKey:
|
case C.NSHomeFunctionKey:
|
||||||
n = key.NameHome
|
n = key.NameHome
|
||||||
case C.NSEndFunctionKey:
|
case C.NSEndFunctionKey:
|
||||||
n = key.NameEnd
|
n = key.NameEnd
|
||||||
case 0x7f:
|
case '\x7f', '\b':
|
||||||
n = key.NameDeleteBackward
|
n = key.NameDeleteBackward
|
||||||
case C.NSDeleteFunctionKey:
|
case C.NSDeleteFunctionKey:
|
||||||
n = key.NameDeleteForward
|
n = key.NameDeleteForward
|
||||||
|
case '\t', 0x19:
|
||||||
|
n = key.NameTab
|
||||||
case C.NSPageUpFunctionKey:
|
case C.NSPageUpFunctionKey:
|
||||||
n = key.NamePageUp
|
n = key.NamePageUp
|
||||||
case C.NSPageDownFunctionKey:
|
case C.NSPageDownFunctionKey:
|
||||||
n = key.NamePageDown
|
n = key.NamePageDown
|
||||||
|
default:
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return n, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertKey(k rune) (key.Name, bool) {
|
||||||
|
if n, ok := convertCommandKey(k); ok {
|
||||||
|
return n, true
|
||||||
|
}
|
||||||
|
var n key.Name
|
||||||
|
switch k {
|
||||||
case C.NSF1FunctionKey:
|
case C.NSF1FunctionKey:
|
||||||
n = key.NameF1
|
n = key.NameF1
|
||||||
case C.NSF2FunctionKey:
|
case C.NSF2FunctionKey:
|
||||||
@@ -1065,8 +1132,6 @@ func convertKey(k rune) (key.Name, bool) {
|
|||||||
n = key.NameF11
|
n = key.NameF11
|
||||||
case C.NSF12FunctionKey:
|
case C.NSF12FunctionKey:
|
||||||
n = key.NameF12
|
n = key.NameF12
|
||||||
case 0x09, 0x19:
|
|
||||||
n = key.NameTab
|
|
||||||
case 0x20:
|
case 0x20:
|
||||||
n = key.NameSpace
|
n = key.NameSpace
|
||||||
default:
|
default:
|
||||||
@@ -1096,14 +1161,8 @@ func convertMods(mods C.NSUInteger) key.Modifiers {
|
|||||||
return kmods
|
return kmods
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) wakeup() {
|
|
||||||
runOnMain(func() {
|
|
||||||
w.loop.Wakeup()
|
|
||||||
if w != mainThreadWindow {
|
|
||||||
w.loop.FlushEvents()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (AppKitViewEvent) implementsViewEvent() {}
|
func (AppKitViewEvent) implementsViewEvent() {}
|
||||||
func (AppKitViewEvent) ImplementsEvent() {}
|
func (AppKitViewEvent) ImplementsEvent() {}
|
||||||
|
func (a AppKitViewEvent) Valid() bool {
|
||||||
|
return a != (AppKitViewEvent{})
|
||||||
|
}
|
||||||
|
|||||||
+48
-261
@@ -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,28 +16,29 @@ __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
|
||||||
- (void)windowWillMiniaturize:(NSNotification *)notification {
|
- (void)windowWillMiniaturize:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
GioView *view = (GioView *)window.contentView;
|
GioView *view = (GioView *)window.contentView;
|
||||||
gio_onHide(view.handle);
|
gio_onDraw(view.handle);
|
||||||
}
|
}
|
||||||
- (void)windowDidDeminiaturize:(NSNotification *)notification {
|
- (void)windowDidDeminiaturize:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
GioView *view = (GioView *)window.contentView;
|
GioView *view = (GioView *)window.contentView;
|
||||||
gio_onShow(view.handle);
|
gio_onDraw(view.handle);
|
||||||
}
|
}
|
||||||
- (void)windowWillEnterFullScreen:(NSNotification *)notification {
|
- (void)windowWillEnterFullScreen:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
GioView *view = (GioView *)window.contentView;
|
GioView *view = (GioView *)window.contentView;
|
||||||
gio_onFullscreen(view.handle);
|
gio_onDraw(view.handle);
|
||||||
}
|
}
|
||||||
- (void)windowWillExitFullScreen:(NSNotification *)notification {
|
- (void)windowWillExitFullScreen:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
GioView *view = (GioView *)window.contentView;
|
GioView *view = (GioView *)window.contentView;
|
||||||
gio_onWindowed(view.handle);
|
gio_onDraw(view.handle);
|
||||||
}
|
}
|
||||||
- (void)windowDidChangeScreen:(NSNotification *)notification {
|
- (void)windowDidChangeScreen:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -69,212 +74,6 @@ static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFl
|
|||||||
gio_onMouse(view.handle, (__bridge CFTypeRef)event, typ, event.buttonNumber, p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]);
|
gio_onMouse(view.handle, (__bridge CFTypeRef)event, typ, event.buttonNumber, p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@interface GioApplication: NSApplication
|
|
||||||
@end
|
|
||||||
|
|
||||||
// Variables for tracking resizes.
|
|
||||||
static struct {
|
|
||||||
NSPoint dir;
|
|
||||||
NSEvent *lastMouseDown;
|
|
||||||
NSPoint off;
|
|
||||||
} resizeState = {};
|
|
||||||
|
|
||||||
static NSBitmapImageRep *nsImageBitmap(NSImage *img) {
|
|
||||||
NSArray<NSImageRep *> *reps = img.representations;
|
|
||||||
if ([reps count] == 0) {
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
NSImageRep *rep = reps[0];
|
|
||||||
if (![rep isKindOfClass:[NSBitmapImageRep class]]) {
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
return (NSBitmapImageRep *)rep;
|
|
||||||
}
|
|
||||||
|
|
||||||
static NSCursor *lookupPrivateNSCursor(SEL name) {
|
|
||||||
if (![NSCursor respondsToSelector:name]) {
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
id obj = [NSCursor performSelector:name];
|
|
||||||
if (![obj isKindOfClass:[NSCursor class]]) {
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
return (NSCursor *)obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
static BOOL isEqualNSCursor(NSCursor *c1, SEL name2) {
|
|
||||||
NSCursor *c2 = lookupPrivateNSCursor(name2);
|
|
||||||
if (c2 == nil || !NSEqualPoints(c1.hotSpot, c2.hotSpot)) {
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
NSImage *img1 = c1.image;
|
|
||||||
NSImage *img2 = c2.image;
|
|
||||||
if (!NSEqualSizes(img1.size, img2.size)) {
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
NSBitmapImageRep *bit1 = nsImageBitmap(img1);
|
|
||||||
NSBitmapImageRep *bit2 = nsImageBitmap(img2);
|
|
||||||
if (bit1 == nil || bit2 == nil) {
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
NSInteger n1 = bit1.numberOfPlanes*bit1.bytesPerPlane;
|
|
||||||
NSInteger n2 = bit1.numberOfPlanes*bit1.bytesPerPlane;
|
|
||||||
if (n1 != n2) {
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
if (memcmp(bit1.bitmapData, bit2.bitmapData, n1) != 0) {
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
@implementation GioApplication
|
|
||||||
- (NSEvent *)nextEventMatchingMask:(NSEventMask)mask
|
|
||||||
untilDate:(NSDate *)expiration
|
|
||||||
inMode:(NSRunLoopMode)mode
|
|
||||||
dequeue:(BOOL)deqFlag {
|
|
||||||
if ([mode isEqualToString:NSEventTrackingRunLoopMode]) {
|
|
||||||
NSEvent *l = resizeState.lastMouseDown;
|
|
||||||
if (l != nil) {
|
|
||||||
//lastMouseDown = nil;
|
|
||||||
NSCursor *cur = [NSCursor currentSystemCursor];
|
|
||||||
NSPoint dir = {};
|
|
||||||
NSPoint off = {};
|
|
||||||
NSSize wsz = [l window].frame.size;
|
|
||||||
NSPoint center = NSMakePoint(wsz.width/2, wsz.height/2);
|
|
||||||
NSPoint p = [l locationInWindow];
|
|
||||||
if (p.x >= center.x) {
|
|
||||||
dir.x = 1;
|
|
||||||
off.x = p.x - wsz.width;
|
|
||||||
} else {
|
|
||||||
dir.x = -1;
|
|
||||||
off.x = p.x;
|
|
||||||
}
|
|
||||||
if (p.y >= center.y) {
|
|
||||||
dir.y = 1;
|
|
||||||
off.y = p.y - wsz.height;
|
|
||||||
} else {
|
|
||||||
dir.y = -1;
|
|
||||||
off.y = p.y;
|
|
||||||
}
|
|
||||||
// The button down coordinate distinguish the four quadrants. Use the
|
|
||||||
// cursor image to determine the precise direction.
|
|
||||||
SEL nw = @selector(_windowResizeNorthWestCursor);
|
|
||||||
SEL n = @selector(_windowResizeNorthCursor);
|
|
||||||
SEL ne = @selector(_windowResizeNorthEastCursor);
|
|
||||||
SEL e = @selector(_windowResizeEastCursor);
|
|
||||||
SEL se = @selector(_windowResizeSouthEastCursor);
|
|
||||||
SEL s = @selector(_windowResizeSouthCursor);
|
|
||||||
SEL sw = @selector(_windowResizeSouthWestCursor);
|
|
||||||
SEL w = @selector(_windowResizeWestCursor);
|
|
||||||
SEL ns = @selector(_windowResizeNorthSouthCursor);
|
|
||||||
SEL ew = @selector(_windowResizeEastWestCursor);
|
|
||||||
SEL nwse = @selector(_windowResizeNorthWestSouthEastCursor);
|
|
||||||
SEL nesw = @selector(_windowResizeNorthEastSouthWestCursor);
|
|
||||||
BOOL match = YES;
|
|
||||||
if (dir.x != 0 && (isEqualNSCursor(cur, ew) || isEqualNSCursor(cur, w) || isEqualNSCursor(cur, e))) {
|
|
||||||
dir.y = 0;
|
|
||||||
}
|
|
||||||
if (dir.y != 0 && (isEqualNSCursor(cur, ns) || isEqualNSCursor(cur, s) || isEqualNSCursor(cur, n))) {
|
|
||||||
dir.x = 0;
|
|
||||||
}
|
|
||||||
// If none of the cursors matched, we may deduce that the resize
|
|
||||||
// direction is one of the corners. However, to ensure that at least
|
|
||||||
// one cursor matches, check the corner cursors.
|
|
||||||
if (dir.x == 1 && dir.y == 1) {
|
|
||||||
if (!isEqualNSCursor(cur, nesw) && !isEqualNSCursor(cur, sw)) {
|
|
||||||
dir = NSZeroPoint;
|
|
||||||
}
|
|
||||||
} else if (dir.x == 1 && dir.y == -1) {
|
|
||||||
if (!isEqualNSCursor(cur, nwse) && !isEqualNSCursor(cur, nw)) {
|
|
||||||
dir = NSZeroPoint;
|
|
||||||
}
|
|
||||||
} else if (dir.x == -1 && dir.y == 1) {
|
|
||||||
if (!isEqualNSCursor(cur, nwse) && !isEqualNSCursor(cur, se)) {
|
|
||||||
dir = NSZeroPoint;
|
|
||||||
}
|
|
||||||
} else if (dir.x == -1 && dir.y == -1) {
|
|
||||||
if (!isEqualNSCursor(cur, nesw) && !isEqualNSCursor(cur, ne)) {
|
|
||||||
dir = NSZeroPoint;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!NSEqualPoints(dir, NSZeroPoint)) {
|
|
||||||
NSEvent *cancel = [NSEvent mouseEventWithType:NSEventTypeLeftMouseUp
|
|
||||||
location:l.locationInWindow
|
|
||||||
modifierFlags:l.modifierFlags
|
|
||||||
timestamp:l.timestamp
|
|
||||||
windowNumber:l.windowNumber
|
|
||||||
context:l.context
|
|
||||||
eventNumber:l.eventNumber
|
|
||||||
clickCount:l.clickCount
|
|
||||||
pressure:l.pressure];
|
|
||||||
resizeState.off = off;
|
|
||||||
resizeState.dir = dir;
|
|
||||||
return cancel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [super nextEventMatchingMask:mask untilDate:expiration inMode:mode dequeue:deqFlag];
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface GioWindow: NSWindow
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GioWindow
|
|
||||||
- (void)sendEvent:(NSEvent *)evt {
|
|
||||||
if (evt.type == NSEventTypeLeftMouseDown) {
|
|
||||||
resizeState.lastMouseDown = evt;
|
|
||||||
}
|
|
||||||
NSPoint dir = resizeState.dir;
|
|
||||||
if (NSEqualPoints(dir, NSZeroPoint)) {
|
|
||||||
[super sendEvent:evt];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch (evt.type) {
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
case NSEventTypeLeftMouseUp:
|
|
||||||
resizeState.dir = NSZeroPoint;
|
|
||||||
resizeState.lastMouseDown = nil;
|
|
||||||
return;
|
|
||||||
case NSEventTypeLeftMouseDragged:
|
|
||||||
// Ok to proceed.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
NSPoint loc = evt.locationInWindow;
|
|
||||||
NSPoint off = resizeState.off;
|
|
||||||
loc.x -= off.x;
|
|
||||||
loc.y -= off.y;
|
|
||||||
NSRect frame = [self frame];
|
|
||||||
NSSize min = [self minSize];
|
|
||||||
NSSize max = [self maxSize];
|
|
||||||
CGFloat width = frame.size.width;
|
|
||||||
if (dir.x > 0) {
|
|
||||||
width = loc.x;
|
|
||||||
} else if (dir.x < 0) {
|
|
||||||
width -= loc.x;
|
|
||||||
}
|
|
||||||
width = MIN(max.width, MAX(min.width, width));
|
|
||||||
if (dir.x < 0) {
|
|
||||||
frame.origin.x += frame.size.width - width;
|
|
||||||
}
|
|
||||||
frame.size.width = width;
|
|
||||||
CGFloat height = frame.size.height;
|
|
||||||
if (dir.y > 0) {
|
|
||||||
height = loc.y;
|
|
||||||
} else if (dir.y < 0) {
|
|
||||||
height -= loc.y;
|
|
||||||
}
|
|
||||||
height = MIN(max.height, MAX(min.height, height));
|
|
||||||
if (dir.y < 0) {
|
|
||||||
frame.origin.y += frame.size.height - height;
|
|
||||||
}
|
|
||||||
frame.size.height = height;
|
|
||||||
[self setFrame:frame display:YES animate:NO];
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GioView
|
@implementation GioView
|
||||||
- (void)setFrameSize:(NSSize)newSize {
|
- (void)setFrameSize:(NSSize)newSize {
|
||||||
[super setFrameSize:newSize];
|
[super setFrameSize:newSize];
|
||||||
@@ -290,7 +89,7 @@ static BOOL isEqualNSCursor(NSCursor *c1, SEL name2) {
|
|||||||
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;
|
||||||
}
|
}
|
||||||
@@ -333,20 +132,24 @@ static BOOL isEqualNSCursor(NSCursor *c1, SEL name2) {
|
|||||||
handleMouse(self, event, MOUSE_SCROLL, dx, dy);
|
handleMouse(self, event, MOUSE_SCROLL, dx, dy);
|
||||||
}
|
}
|
||||||
- (void)keyDown:(NSEvent *)event {
|
- (void)keyDown:(NSEvent *)event {
|
||||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
|
||||||
NSString *keys = [event charactersIgnoringModifiers];
|
NSString *keys = [event charactersIgnoringModifiers];
|
||||||
gio_onKeys(self.handle, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true);
|
gio_onKeys(self.handle, (__bridge CFTypeRef)event, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true);
|
||||||
|
}
|
||||||
|
- (void)flagsChanged:(NSEvent *)event {
|
||||||
|
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
||||||
|
gio_onFlagsChanged(self.handle, [event modifierFlags]);
|
||||||
}
|
}
|
||||||
- (void)keyUp:(NSEvent *)event {
|
- (void)keyUp:(NSEvent *)event {
|
||||||
NSString *keys = [event charactersIgnoringModifiers];
|
NSString *keys = [event charactersIgnoringModifiers];
|
||||||
gio_onKeys(self.handle, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], false);
|
gio_onKeys(self.handle, (__bridge CFTypeRef)event, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], false);
|
||||||
}
|
}
|
||||||
- (void)insertText:(id)string {
|
- (void)insertText:(id)string {
|
||||||
gio_onText(self.handle, (__bridge CFTypeRef)string);
|
gio_onText(self.handle, (__bridge CFTypeRef)string);
|
||||||
}
|
}
|
||||||
- (void)doCommandBySelector:(SEL)sel {
|
- (void)doCommandBySelector:(SEL)action {
|
||||||
// Don't pass commands up the responder chain.
|
if (!gio_onCommandBySelector(self.handle)) {
|
||||||
// They will end up in a beep.
|
[super doCommandBySelector:action];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
- (BOOL)hasMarkedText {
|
- (BOOL)hasMarkedText {
|
||||||
int res = gio_hasMarkedText(self.handle);
|
int res = gio_hasMarkedText(self.handle);
|
||||||
@@ -402,14 +205,22 @@ static BOOL isEqualNSCursor(NSCursor *c1, SEL name2) {
|
|||||||
return [[self window] convertRectToScreen:r];
|
return [[self window] convertRectToScreen:r];
|
||||||
}
|
}
|
||||||
- (void)applicationWillUnhide:(NSNotification *)notification {
|
- (void)applicationWillUnhide:(NSNotification *)notification {
|
||||||
gio_onShow(self.handle);
|
gio_onDraw(self.handle);
|
||||||
}
|
}
|
||||||
- (void)applicationDidHide:(NSNotification *)notification {
|
- (void)applicationDidHide:(NSNotification *)notification {
|
||||||
gio_onHide(self.handle);
|
gio_onDraw(self.handle);
|
||||||
}
|
}
|
||||||
- (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
|
||||||
@@ -460,11 +271,14 @@ void gio_showCursor() {
|
|||||||
// some cursors are not public, this tries to use a private cursor
|
// some cursors are not public, this tries to use a private cursor
|
||||||
// and uses fallback when the use of private cursor fails.
|
// and uses fallback when the use of private cursor fails.
|
||||||
static void trySetPrivateCursor(SEL cursorName, NSCursor* fallback) {
|
static void trySetPrivateCursor(SEL cursorName, NSCursor* fallback) {
|
||||||
NSCursor *cur = lookupPrivateNSCursor(cursorName);
|
if ([NSCursor respondsToSelector:cursorName]) {
|
||||||
if (cur == nil) {
|
id object = [NSCursor performSelector:cursorName];
|
||||||
cur = fallback;
|
if ([object isKindOfClass:[NSCursor class]]) {
|
||||||
|
[(NSCursor*)object set];
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
[cur set];
|
[fallback set];
|
||||||
}
|
}
|
||||||
|
|
||||||
void gio_setCursor(NSUInteger curID) {
|
void gio_setCursor(NSUInteger curID) {
|
||||||
@@ -563,7 +377,7 @@ CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGF
|
|||||||
NSMiniaturizableWindowMask |
|
NSMiniaturizableWindowMask |
|
||||||
NSClosableWindowMask;
|
NSClosableWindowMask;
|
||||||
|
|
||||||
GioWindow* window = [[GioWindow alloc] initWithContentRect:rect
|
NSWindow* window = [[NSWindow alloc] initWithContentRect:rect
|
||||||
styleMask:styleMask
|
styleMask:styleMask
|
||||||
backing:NSBackingStoreBuffered
|
backing:NSBackingStoreBuffered
|
||||||
defer:NO];
|
defer:NO];
|
||||||
@@ -576,16 +390,16 @@ CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGF
|
|||||||
[window setAcceptsMouseMovedEvents:YES];
|
[window setAcceptsMouseMovedEvents:YES];
|
||||||
NSView *view = (__bridge NSView *)viewRef;
|
NSView *view = (__bridge NSView *)viewRef;
|
||||||
[window setContentView:view];
|
[window setContentView:view];
|
||||||
[window makeFirstResponder:view];
|
|
||||||
window.delegate = globalWindowDel;
|
window.delegate = globalWindowDel;
|
||||||
return (__bridge_retained CFTypeRef)window;
|
return (__bridge_retained CFTypeRef)window;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
@@ -612,24 +426,13 @@ void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
|
|||||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||||
[NSApp activateIgnoringOtherApps:YES];
|
[NSApp activateIgnoringOtherApps:YES];
|
||||||
// Force the [NSApp run] call to return.
|
gio_onFinishLaunching();
|
||||||
[NSApp stop:nil];
|
|
||||||
NSEvent *dummy = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
|
|
||||||
location:NSZeroPoint
|
|
||||||
modifierFlags:0
|
|
||||||
timestamp:0
|
|
||||||
windowNumber:0
|
|
||||||
context:nil
|
|
||||||
subtype:0
|
|
||||||
data1:0
|
|
||||||
data2:0];
|
|
||||||
[NSApp postEvent:dummy atStart:YES];
|
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
void gio_initApp() {
|
void gio_main() {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
[GioApplication sharedApplication];
|
[NSApplication sharedApplication];
|
||||||
GioAppDelegate *del = [[GioAppDelegate alloc] init];
|
GioAppDelegate *del = [[GioAppDelegate alloc] init];
|
||||||
[NSApp setDelegate:del];
|
[NSApp setDelegate:del];
|
||||||
|
|
||||||
@@ -651,22 +454,6 @@ void gio_initApp() {
|
|||||||
|
|
||||||
globalWindowDel = [[GioWindowDelegate alloc] init];
|
globalWindowDel = [[GioWindowDelegate alloc] init];
|
||||||
|
|
||||||
// Runs until stopped by applicationDidFinishLaunching.
|
|
||||||
[NSApp run];
|
[NSApp run];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void gio_wakeupMainThread(void) {
|
|
||||||
@autoreleasepool {
|
|
||||||
NSEvent *dummy = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
|
|
||||||
location:NSZeroPoint
|
|
||||||
modifierFlags:0
|
|
||||||
timestamp:0
|
|
||||||
windowNumber:0
|
|
||||||
context:nil
|
|
||||||
subtype:0
|
|
||||||
data1:0
|
|
||||||
data2:0];
|
|
||||||
[NSApp postEvent:dummy atStart:YES];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+10
-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,13 @@ 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() {
|
||||||
|
select {}
|
||||||
|
}
|
||||||
|
|
||||||
type windowDriver func(*callbacks, []Option) error
|
type windowDriver func(*callbacks, []Option) error
|
||||||
|
|
||||||
@@ -53,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",
|
||||||
|
|||||||
+20
-24
@@ -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
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1366,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() {
|
||||||
@@ -1374,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
|
||||||
}
|
}
|
||||||
@@ -1399,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1416,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()) {
|
||||||
@@ -1515,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)
|
||||||
}
|
}
|
||||||
@@ -1639,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,
|
||||||
@@ -1648,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) {
|
||||||
|
|||||||
+63
-59
@@ -46,17 +46,13 @@ type window struct {
|
|||||||
cursorIn bool
|
cursorIn bool
|
||||||
cursor syscall.Handle
|
cursor syscall.Handle
|
||||||
|
|
||||||
// placement saves the previous window position when in full screen mode.
|
|
||||||
placement *windows.WindowPlacement
|
|
||||||
|
|
||||||
animating bool
|
animating bool
|
||||||
|
|
||||||
borderSize image.Point
|
borderSize image.Point
|
||||||
config Config
|
config Config
|
||||||
loop *eventLoop
|
// frameDims stores the last seen window frame width and height.
|
||||||
|
frameDims image.Point
|
||||||
// invMu avoids the race between destroying the window and Invalidate.
|
loop *eventLoop
|
||||||
invMu sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const _WM_WAKEUP = windows.WM_USER + iota
|
const _WM_WAKEUP = windows.WM_USER + iota
|
||||||
@@ -85,6 +81,10 @@ var resources struct {
|
|||||||
cursor syscall.Handle
|
cursor syscall.Handle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func osMain() {
|
||||||
|
select {}
|
||||||
|
}
|
||||||
|
|
||||||
func newWindow(win *callbacks, options []Option) {
|
func newWindow(win *callbacks, options []Option) {
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
@@ -107,8 +107,8 @@ func newWindow(win *callbacks, options []Option) {
|
|||||||
}
|
}
|
||||||
winMap.Store(w.hwnd, w)
|
winMap.Store(w.hwnd, w)
|
||||||
defer winMap.Delete(w.hwnd)
|
defer winMap.Delete(w.hwnd)
|
||||||
w.ProcessEvent(Win32ViewEvent{HWND: uintptr(w.hwnd)})
|
|
||||||
w.Configure(options)
|
w.Configure(options)
|
||||||
|
w.ProcessEvent(Win32ViewEvent{HWND: uintptr(w.hwnd)})
|
||||||
windows.SetForegroundWindow(w.hwnd)
|
windows.SetForegroundWindow(w.hwnd)
|
||||||
windows.SetFocus(w.hwnd)
|
windows.SetFocus(w.hwnd)
|
||||||
// Since the window class for the cursor is null,
|
// Since the window class for the cursor is null,
|
||||||
@@ -184,21 +184,39 @@ func (w *window) init() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// update() handles changes done by the user, and updates the configuration.
|
// update handles changes done by the user, and updates the configuration.
|
||||||
// It reads the window style and size/position and updates w.config.
|
// It reads the window style and size/position and updates w.config.
|
||||||
// If anything has changed it emits a ConfigEvent to notify the application.
|
// If anything has changed it emits a ConfigEvent to notify the application.
|
||||||
func (w *window) update() {
|
func (w *window) update() {
|
||||||
cr := windows.GetClientRect(w.hwnd)
|
p := windows.GetWindowPlacement(w.hwnd)
|
||||||
w.config.Size = image.Point{
|
if !p.IsMinimized() {
|
||||||
X: int(cr.Right - cr.Left),
|
r := windows.GetWindowRect(w.hwnd)
|
||||||
Y: int(cr.Bottom - cr.Top),
|
cr := windows.GetClientRect(w.hwnd)
|
||||||
|
w.config.Size = image.Point{
|
||||||
|
X: int(cr.Right - cr.Left),
|
||||||
|
Y: int(cr.Bottom - cr.Top),
|
||||||
|
}
|
||||||
|
w.frameDims = image.Point{
|
||||||
|
X: int(r.Right - r.Left),
|
||||||
|
Y: int(r.Bottom - r.Top),
|
||||||
|
}.Sub(w.config.Size)
|
||||||
}
|
}
|
||||||
|
|
||||||
w.borderSize = image.Pt(
|
w.borderSize = image.Pt(
|
||||||
windows.GetSystemMetrics(windows.SM_CXSIZEFRAME),
|
windows.GetSystemMetrics(windows.SM_CXSIZEFRAME),
|
||||||
windows.GetSystemMetrics(windows.SM_CYSIZEFRAME),
|
windows.GetSystemMetrics(windows.SM_CYSIZEFRAME),
|
||||||
)
|
)
|
||||||
|
style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE)
|
||||||
|
switch {
|
||||||
|
case p.IsMaximized() && style&windows.WS_OVERLAPPEDWINDOW != 0:
|
||||||
|
w.config.Mode = Maximized
|
||||||
|
case p.IsMaximized():
|
||||||
|
w.config.Mode = Fullscreen
|
||||||
|
default:
|
||||||
|
w.config.Mode = Windowed
|
||||||
|
}
|
||||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
|
w.draw(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
|
func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
|
||||||
@@ -296,15 +314,15 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
case windows.WM_DESTROY:
|
case windows.WM_DESTROY:
|
||||||
w.ProcessEvent(Win32ViewEvent{})
|
w.ProcessEvent(Win32ViewEvent{})
|
||||||
w.ProcessEvent(DestroyEvent{})
|
w.ProcessEvent(DestroyEvent{})
|
||||||
|
w.w = nil
|
||||||
if w.hdc != 0 {
|
if w.hdc != 0 {
|
||||||
windows.ReleaseDC(w.hdc)
|
windows.ReleaseDC(w.hdc)
|
||||||
w.hdc = 0
|
w.hdc = 0
|
||||||
}
|
}
|
||||||
w.invMu.Lock()
|
|
||||||
// The system destroys the HWND for us.
|
// The system destroys the HWND for us.
|
||||||
w.hwnd = 0
|
w.hwnd = 0
|
||||||
w.invMu.Unlock()
|
|
||||||
windows.PostQuitMessage(0)
|
windows.PostQuitMessage(0)
|
||||||
|
return 0
|
||||||
case windows.WM_NCCALCSIZE:
|
case windows.WM_NCCALCSIZE:
|
||||||
if w.config.Decorated {
|
if w.config.Decorated {
|
||||||
// Let Windows handle decorations.
|
// Let Windows handle decorations.
|
||||||
@@ -329,37 +347,31 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
return 0
|
return 0
|
||||||
case windows.WM_PAINT:
|
case windows.WM_PAINT:
|
||||||
w.draw(true)
|
w.draw(true)
|
||||||
|
case windows.WM_STYLECHANGED:
|
||||||
|
w.update()
|
||||||
|
case windows.WM_WINDOWPOSCHANGED:
|
||||||
|
w.update()
|
||||||
case windows.WM_SIZE:
|
case windows.WM_SIZE:
|
||||||
w.update()
|
w.update()
|
||||||
switch wParam {
|
|
||||||
case windows.SIZE_MINIMIZED:
|
|
||||||
w.config.Mode = Minimized
|
|
||||||
case windows.SIZE_MAXIMIZED:
|
|
||||||
w.config.Mode = Maximized
|
|
||||||
case windows.SIZE_RESTORED:
|
|
||||||
if w.config.Mode != Fullscreen {
|
|
||||||
w.config.Mode = Windowed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case windows.WM_GETMINMAXINFO:
|
case windows.WM_GETMINMAXINFO:
|
||||||
mm := (*windows.MinMaxInfo)(unsafe.Pointer(lParam))
|
mm := (*windows.MinMaxInfo)(unsafe.Pointer(lParam))
|
||||||
var bw, bh int32
|
|
||||||
|
var frameDims image.Point
|
||||||
if w.config.Decorated {
|
if w.config.Decorated {
|
||||||
r := windows.GetWindowRect(w.hwnd)
|
frameDims = w.frameDims
|
||||||
cr := windows.GetClientRect(w.hwnd)
|
|
||||||
bw = r.Right - r.Left - (cr.Right - cr.Left)
|
|
||||||
bh = r.Bottom - r.Top - (cr.Bottom - cr.Top)
|
|
||||||
}
|
}
|
||||||
if p := w.config.MinSize; p.X > 0 || p.Y > 0 {
|
if p := w.config.MinSize; p.X > 0 || p.Y > 0 {
|
||||||
|
p = p.Add(frameDims)
|
||||||
mm.PtMinTrackSize = windows.Point{
|
mm.PtMinTrackSize = windows.Point{
|
||||||
X: int32(p.X) + bw,
|
X: int32(p.X),
|
||||||
Y: int32(p.Y) + bh,
|
Y: int32(p.Y),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if p := w.config.MaxSize; p.X > 0 || p.Y > 0 {
|
if p := w.config.MaxSize; p.X > 0 || p.Y > 0 {
|
||||||
|
p = p.Add(frameDims)
|
||||||
mm.PtMaxTrackSize = windows.Point{
|
mm.PtMaxTrackSize = windows.Point{
|
||||||
X: int32(p.X) + bw,
|
X: int32(p.X),
|
||||||
Y: int32(p.Y) + bh,
|
Y: int32(p.Y),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
@@ -451,9 +463,6 @@ func getModifiers() key.Modifiers {
|
|||||||
// hitTest returns the non-client area hit by the point, needed to
|
// hitTest returns the non-client area hit by the point, needed to
|
||||||
// process WM_NCHITTEST.
|
// process WM_NCHITTEST.
|
||||||
func (w *window) hitTest(x, y int) uintptr {
|
func (w *window) hitTest(x, y int) uintptr {
|
||||||
if w.config.Mode == Fullscreen {
|
|
||||||
return windows.HTCLIENT
|
|
||||||
}
|
|
||||||
if w.config.Mode != Windowed {
|
if w.config.Mode != Windowed {
|
||||||
// Only windowed mode should allow resizing.
|
// Only windowed mode should allow resizing.
|
||||||
return windows.HTCLIENT
|
return windows.HTCLIENT
|
||||||
@@ -563,7 +572,8 @@ func (w *window) runLoop() {
|
|||||||
loop:
|
loop:
|
||||||
for {
|
for {
|
||||||
anim := w.animating
|
anim := w.animating
|
||||||
if anim && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) {
|
p := windows.GetWindowPlacement(w.hwnd)
|
||||||
|
if anim && !p.IsMinimized() && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) {
|
||||||
w.draw(false)
|
w.draw(false)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -616,13 +626,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)
|
||||||
}
|
}
|
||||||
@@ -693,8 +696,13 @@ func (w *window) readClipboard() error {
|
|||||||
func (w *window) Configure(options []Option) {
|
func (w *window) Configure(options []Option) {
|
||||||
dpi := windows.GetSystemDPI()
|
dpi := windows.GetSystemDPI()
|
||||||
metric := configForDPI(dpi)
|
metric := configForDPI(dpi)
|
||||||
w.config.apply(metric, options)
|
cnf := w.config
|
||||||
windows.SetWindowText(w.hwnd, w.config.Title)
|
cnf.apply(metric, options)
|
||||||
|
w.config.Title = cnf.Title
|
||||||
|
w.config.Decorated = cnf.Decorated
|
||||||
|
w.config.MinSize = cnf.MinSize
|
||||||
|
w.config.MaxSize = cnf.MaxSize
|
||||||
|
windows.SetWindowText(w.hwnd, cnf.Title)
|
||||||
|
|
||||||
style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE)
|
style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE)
|
||||||
var showMode int32
|
var showMode int32
|
||||||
@@ -702,7 +710,7 @@ func (w *window) Configure(options []Option) {
|
|||||||
swpStyle := uintptr(windows.SWP_NOZORDER | windows.SWP_FRAMECHANGED)
|
swpStyle := uintptr(windows.SWP_NOZORDER | windows.SWP_FRAMECHANGED)
|
||||||
winStyle := uintptr(windows.WS_OVERLAPPEDWINDOW)
|
winStyle := uintptr(windows.WS_OVERLAPPEDWINDOW)
|
||||||
style &^= winStyle
|
style &^= winStyle
|
||||||
switch w.config.Mode {
|
switch cnf.Mode {
|
||||||
case Minimized:
|
case Minimized:
|
||||||
style |= winStyle
|
style |= winStyle
|
||||||
swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
|
swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
|
||||||
@@ -717,13 +725,13 @@ func (w *window) Configure(options []Option) {
|
|||||||
style |= winStyle
|
style |= winStyle
|
||||||
showMode = windows.SW_SHOWNORMAL
|
showMode = windows.SW_SHOWNORMAL
|
||||||
// Get target for client area size.
|
// Get target for client area size.
|
||||||
width = int32(w.config.Size.X)
|
width = int32(cnf.Size.X)
|
||||||
height = int32(w.config.Size.Y)
|
height = int32(cnf.Size.Y)
|
||||||
// Get the current window size and position.
|
// Get the current window size and position.
|
||||||
wr := windows.GetWindowRect(w.hwnd)
|
wr := windows.GetWindowRect(w.hwnd)
|
||||||
x = wr.Left
|
x = wr.Left
|
||||||
y = wr.Top
|
y = wr.Top
|
||||||
if w.config.Decorated {
|
if cnf.Decorated {
|
||||||
// Compute client size and position. Note that the client size is
|
// Compute client size and position. Note that the client size is
|
||||||
// equal to the window size when we are in control of decorations.
|
// equal to the window size when we are in control of decorations.
|
||||||
r := windows.Rect{
|
r := windows.Rect{
|
||||||
@@ -733,25 +741,18 @@ func (w *window) Configure(options []Option) {
|
|||||||
windows.AdjustWindowRectEx(&r, uint32(style), 0, dwExStyle)
|
windows.AdjustWindowRectEx(&r, uint32(style), 0, dwExStyle)
|
||||||
width = r.Right - r.Left
|
width = r.Right - r.Left
|
||||||
height = r.Bottom - r.Top
|
height = r.Bottom - r.Top
|
||||||
}
|
} else {
|
||||||
if !w.config.Decorated {
|
|
||||||
// Enable drop shadows when we draw decorations.
|
// Enable drop shadows when we draw decorations.
|
||||||
windows.DwmExtendFrameIntoClientArea(w.hwnd, windows.Margins{-1, -1, -1, -1})
|
windows.DwmExtendFrameIntoClientArea(w.hwnd, windows.Margins{-1, -1, -1, -1})
|
||||||
}
|
}
|
||||||
|
|
||||||
case Fullscreen:
|
case Fullscreen:
|
||||||
swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
|
swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
|
||||||
mi := windows.GetMonitorInfo(w.hwnd)
|
|
||||||
x, y = mi.Monitor.Left, mi.Monitor.Top
|
|
||||||
width = mi.Monitor.Right - mi.Monitor.Left
|
|
||||||
height = mi.Monitor.Bottom - mi.Monitor.Top
|
|
||||||
showMode = windows.SW_SHOWMAXIMIZED
|
showMode = windows.SW_SHOWMAXIMIZED
|
||||||
}
|
}
|
||||||
windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style)
|
windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style)
|
||||||
windows.SetWindowPos(w.hwnd, 0, x, y, width, height, swpStyle)
|
windows.SetWindowPos(w.hwnd, 0, x, y, width, height, swpStyle)
|
||||||
windows.ShowWindow(w.hwnd, showMode)
|
windows.ShowWindow(w.hwnd, showMode)
|
||||||
|
|
||||||
w.update()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) WriteClipboard(mime string, s []byte) {
|
func (w *window) WriteClipboard(mime string, s []byte) {
|
||||||
@@ -989,3 +990,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{})
|
||||||
|
}
|
||||||
|
|||||||
+10
-14
@@ -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
|
||||||
|
|||||||
+1
-1
@@ -25,6 +25,6 @@ func runMain() {
|
|||||||
// Indirect call, since the linker does not know the address of main when
|
// Indirect call, since the linker does not know the address of main when
|
||||||
// laying down this package.
|
// laying down this package.
|
||||||
fn := mainMain
|
fn := mainMain
|
||||||
go fn()
|
fn()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -175,7 +175,7 @@ func (c *vkContext) refresh(surf vk.Surface, width, height int) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
minExt, maxExt := caps.MinExtent(), caps.MaxExtent()
|
minExt, maxExt := vk.SurfaceCapabilitiesMinExtent(caps), vk.SurfaceCapabilitiesMaxExtent(caps)
|
||||||
if width < minExt.X || maxExt.X < width || height < minExt.Y || maxExt.Y < height {
|
if width < minExt.X || maxExt.X < width || height < minExt.Y || maxExt.Y < height {
|
||||||
return errOutOfDate
|
return errOutOfDate
|
||||||
}
|
}
|
||||||
|
|||||||
+130
-81
@@ -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,18 @@ 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.
|
// gpuErr tracks the GPU error that is to be reported when
|
||||||
basic basicDriver
|
// the window is closed.
|
||||||
once sync.Once
|
gpuErr error
|
||||||
|
|
||||||
|
// invMu protects mayInvalidate.
|
||||||
|
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 +110,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 +222,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))
|
||||||
@@ -223,7 +230,8 @@ func (w *Window) processFrame(frame *op.Ops, ack chan<- struct{}) {
|
|||||||
w.lastFrame.deco.Add(wrapper)
|
w.lastFrame.deco.Add(wrapper)
|
||||||
if err := w.validateAndProcess(w.lastFrame.size, w.lastFrame.sync, wrapper, ack); err != nil {
|
if err := w.validateAndProcess(w.lastFrame.size, w.lastFrame.sync, wrapper, ack); err != nil {
|
||||||
w.destroyGPU()
|
w.destroyGPU()
|
||||||
w.driver.ProcessEvent(DestroyEvent{Err: err})
|
w.gpuErr = err
|
||||||
|
w.driver.Perform(system.ActionClose)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.updateState()
|
w.updateState()
|
||||||
@@ -273,8 +281,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 +295,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 +317,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 +390,13 @@ 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.invMu.Lock()
|
||||||
|
defer c.w.invMu.Unlock()
|
||||||
|
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 +563,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 +593,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -614,15 +643,21 @@ func (w *Window) processEvent(e event.Event) bool {
|
|||||||
e2.Size = e2.Size.Sub(offset)
|
e2.Size = e2.Size.Sub(offset)
|
||||||
w.coalesced.frame = &e2
|
w.coalesced.frame = &e2
|
||||||
case DestroyEvent:
|
case DestroyEvent:
|
||||||
|
if w.gpuErr != nil {
|
||||||
|
e2.Err = w.gpuErr
|
||||||
|
}
|
||||||
w.destroyGPU()
|
w.destroyGPU()
|
||||||
|
w.invMu.Lock()
|
||||||
|
w.mayInvalidate = false
|
||||||
w.driver = nil
|
w.driver = nil
|
||||||
|
w.invMu.Unlock()
|
||||||
if q := w.timer.quit; q != nil {
|
if q := w.timer.quit; q != nil {
|
||||||
q <- struct{}{}
|
q <- struct{}{}
|
||||||
<-q
|
<-q
|
||||||
}
|
}
|
||||||
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,6 +665,7 @@ func (w *Window) processEvent(e event.Event) bool {
|
|||||||
}
|
}
|
||||||
w.coalesced.view = &e2
|
w.coalesced.view = &e2
|
||||||
case ConfigEvent:
|
case ConfigEvent:
|
||||||
|
w.decorations.Decorations.Maximized = e2.Config.Mode == Maximized
|
||||||
wasFocused := w.decorations.Config.Focused
|
wasFocused := w.decorations.Config.Focused
|
||||||
w.decorations.Config = e2.Config
|
w.decorations.Config = e2.Config
|
||||||
e2.Config = w.effectiveConfig()
|
e2.Config = w.effectiveConfig()
|
||||||
@@ -683,55 +719,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.
|
||||||
// Note: if more than one Window is active, at least one must have
|
|
||||||
// its Event called from the main goroutine that runs the main
|
|
||||||
// function. This is necessary because some operating system GUI
|
|
||||||
// implementations require control of the main thread.
|
|
||||||
// For this reason, it is allowed to call Event even after a
|
|
||||||
// DestroyEvent has been received.
|
|
||||||
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 initialization 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() {
|
||||||
@@ -769,7 +811,6 @@ func (w *Window) decorate(e FrameEvent, o *op.Ops) image.Point {
|
|||||||
default:
|
default:
|
||||||
panic(fmt.Errorf("unknown WindowMode %v", m))
|
panic(fmt.Errorf("unknown WindowMode %v", m))
|
||||||
}
|
}
|
||||||
deco.Perform(actions)
|
|
||||||
gtx := layout.Context{
|
gtx := layout.Context{
|
||||||
Ops: o,
|
Ops: o,
|
||||||
Now: e.Now,
|
Now: e.Now,
|
||||||
@@ -779,8 +820,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)
|
||||||
@@ -827,6 +872,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)
|
||||||
})
|
})
|
||||||
|
|||||||
Generated
+15
-31
@@ -9,11 +9,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1701721028,
|
"lastModified": 1733430059,
|
||||||
"narHash": "sha256-2z4YrdHPLoMZNWR1MPOjNZMqPg057i1eZXaYI6RTahQ=",
|
"narHash": "sha256-o3O5tjrMMebRLuHQt7BbEw3jZgWRW5vnOptNXv8WdO4=",
|
||||||
"owner": "tadfisher",
|
"owner": "tadfisher",
|
||||||
"repo": "android-nixpkgs",
|
"repo": "android-nixpkgs",
|
||||||
"rev": "c923f9ec0f4dd0d7dc725dc5b73fbf03658e50dd",
|
"rev": "d2f3c1ea99c0bea9d28a0e59daeb482f50d4cd35",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -27,15 +27,14 @@
|
|||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"android",
|
"android",
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
],
|
]
|
||||||
"systems": "systems"
|
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1701697687,
|
"lastModified": 1728330715,
|
||||||
"narHash": "sha256-dLLE5wQBVv+pIb4bWmKFSw2DvLVyuEk0F7ng6hpZPSU=",
|
"narHash": "sha256-xRJ2nPOXb//u1jaBnDP56M7v5ldavjbtR6lfGqSvcKg=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "devshell",
|
"repo": "devshell",
|
||||||
"rev": "c3bd77911391eb1638af6ce773de86da57ee6df5",
|
"rev": "dd6b80932022cea34a019e2bb32f6fa9e494dfef",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -46,14 +45,14 @@
|
|||||||
},
|
},
|
||||||
"flake-utils": {
|
"flake-utils": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"systems": "systems_2"
|
"systems": "systems"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1701680307,
|
"lastModified": 1731533236,
|
||||||
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -64,16 +63,16 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1701282334,
|
"lastModified": 1733261153,
|
||||||
"narHash": "sha256-MxCVrXY6v4QmfTwIysjjaX0XUhqBbxTWWB4HXtDYsdk=",
|
"narHash": "sha256-eq51hyiaIwtWo19fPEeE0Zr2s83DYMKJoukNLgGGpek=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "057f9aecfb71c4437d2b27d3323df7f93c010b7e",
|
"rev": "b681065d0919f7eb5309a93cea2cfa84dec9aa88",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"ref": "23.11",
|
"ref": "nixos-24.11",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
@@ -98,21 +97,6 @@
|
|||||||
"repo": "default",
|
"repo": "default",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"systems_2": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
description = "Gio build environment";
|
description = "Gio build environment";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/23.11";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
|
||||||
android.url = "github:tadfisher/android-nixpkgs";
|
android.url = "github:tadfisher/android-nixpkgs";
|
||||||
android.inputs.nixpkgs.follows = "nixpkgs";
|
android.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
@@ -47,12 +47,6 @@
|
|||||||
xorg.libXfixes
|
xorg.libXfixes
|
||||||
libGL
|
libGL
|
||||||
pkg-config
|
pkg-config
|
||||||
] else if stdenv.isDarwin then [
|
|
||||||
darwin.apple_sdk_11_0.frameworks.Foundation
|
|
||||||
darwin.apple_sdk_11_0.frameworks.Metal
|
|
||||||
darwin.apple_sdk_11_0.frameworks.QuartzCore
|
|
||||||
darwin.apple_sdk_11_0.frameworks.AppKit
|
|
||||||
darwin.apple_sdk_11_0.MacOSX-SDK
|
|
||||||
] else [ ]);
|
] else [ ]);
|
||||||
} // (if stdenv.isLinux then {
|
} // (if stdenv.isLinux then {
|
||||||
LD_LIBRARY_PATH = "${vulkan-loader}/lib";
|
LD_LIBRARY_PATH = "${vulkan-loader}/lib";
|
||||||
|
|||||||
+1
-1
@@ -34,7 +34,7 @@ type Font struct {
|
|||||||
// Face is an opaque handle to a typeface. The concrete implementation depends
|
// Face is an opaque handle to a typeface. The concrete implementation depends
|
||||||
// upon the kind of font and shaper in use.
|
// upon the kind of font and shaper in use.
|
||||||
type Face interface {
|
type Face interface {
|
||||||
Face() font.Face
|
Face() *font.Face
|
||||||
}
|
}
|
||||||
|
|
||||||
// Typeface identifies a list of font families to attempt to use for displaying
|
// Typeface identifies a list of font families to attempt to use for displaying
|
||||||
|
|||||||
+41
-43
@@ -16,23 +16,21 @@ import (
|
|||||||
_ "image/png"
|
_ "image/png"
|
||||||
|
|
||||||
giofont "gioui.org/font"
|
giofont "gioui.org/font"
|
||||||
"github.com/go-text/typesetting/font"
|
fontapi "github.com/go-text/typesetting/font"
|
||||||
fontapi "github.com/go-text/typesetting/opentype/api/font"
|
"github.com/go-text/typesetting/font/opentype"
|
||||||
"github.com/go-text/typesetting/opentype/api/metadata"
|
|
||||||
"github.com/go-text/typesetting/opentype/loader"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Face is a thread-safe representation of a loaded font. For efficiency, applications
|
// Face is a thread-safe representation of a loaded font. For efficiency, applications
|
||||||
// should construct a face for any given font file once, reusing it across different
|
// should construct a face for any given font file once, reusing it across different
|
||||||
// text shapers.
|
// text shapers.
|
||||||
type Face struct {
|
type Face struct {
|
||||||
face font.Font
|
face *fontapi.Font
|
||||||
font giofont.Font
|
font giofont.Font
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse constructs a Face from source bytes.
|
// Parse constructs a Face from source bytes.
|
||||||
func Parse(src []byte) (Face, error) {
|
func Parse(src []byte) (Face, error) {
|
||||||
ld, err := loader.NewLoader(bytes.NewReader(src))
|
ld, err := opentype.NewLoader(bytes.NewReader(src))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Face{}, err
|
return Face{}, err
|
||||||
}
|
}
|
||||||
@@ -49,11 +47,11 @@ func Parse(src []byte) (Face, error) {
|
|||||||
// ParseCollection parse an Opentype font file, with support for collections.
|
// ParseCollection parse an Opentype font file, with support for collections.
|
||||||
// Single font files are supported, returning a slice with length 1.
|
// Single font files are supported, returning a slice with length 1.
|
||||||
// The returned fonts are automatically wrapped in a text.FontFace with
|
// The returned fonts are automatically wrapped in a text.FontFace with
|
||||||
// inferred font metadata.
|
// inferred font font.
|
||||||
// BUG(whereswaldon): the only Variant that can be detected automatically is
|
// BUG(whereswaldon): the only Variant that can be detected automatically is
|
||||||
// "Mono".
|
// "Mono".
|
||||||
func ParseCollection(src []byte) ([]giofont.FontFace, error) {
|
func ParseCollection(src []byte) ([]giofont.FontFace, error) {
|
||||||
lds, err := loader.NewLoaders(bytes.NewReader(src))
|
lds, err := opentype.NewLoaders(bytes.NewReader(src))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -76,7 +74,7 @@ func ParseCollection(src []byte) ([]giofont.FontFace, error) {
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DescriptionToFont(md metadata.Description) giofont.Font {
|
func DescriptionToFont(md fontapi.Description) giofont.Font {
|
||||||
return giofont.Font{
|
return giofont.Font{
|
||||||
Typeface: giofont.Typeface(md.Family),
|
Typeface: giofont.Typeface(md.Family),
|
||||||
Style: gioStyle(md.Aspect.Style),
|
Style: gioStyle(md.Aspect.Style),
|
||||||
@@ -84,30 +82,30 @@ func DescriptionToFont(md metadata.Description) giofont.Font {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func FontToDescription(font giofont.Font) metadata.Description {
|
func FontToDescription(font giofont.Font) fontapi.Description {
|
||||||
return metadata.Description{
|
return fontapi.Description{
|
||||||
Family: string(font.Typeface),
|
Family: string(font.Typeface),
|
||||||
Aspect: metadata.Aspect{
|
Aspect: fontapi.Aspect{
|
||||||
Style: mdStyle(font.Style),
|
Style: mdStyle(font.Style),
|
||||||
Weight: mdWeight(font.Weight),
|
Weight: mdWeight(font.Weight),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseLoader parses the contents of the loader into a face and its metadata.
|
// parseLoader parses the contents of the loader into a face and its font.
|
||||||
func parseLoader(ld *loader.Loader) (font.Font, giofont.Font, error) {
|
func parseLoader(ld *opentype.Loader) (*fontapi.Font, giofont.Font, error) {
|
||||||
ft, err := fontapi.NewFont(ld)
|
ft, err := fontapi.NewFont(ld)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, giofont.Font{}, err
|
return nil, giofont.Font{}, err
|
||||||
}
|
}
|
||||||
data := DescriptionToFont(metadata.Metadata(ld))
|
data := DescriptionToFont(ft.Describe())
|
||||||
return ft, data, nil
|
return ft, data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Face returns a thread-unsafe wrapper for this Face suitable for use by a single shaper.
|
// Face returns a thread-unsafe wrapper for this Face suitable for use by a single shaper.
|
||||||
// Face many be invoked any number of times and is safe so long as each return value is
|
// Face many be invoked any number of times and is safe so long as each return value is
|
||||||
// only used by one goroutine.
|
// only used by one goroutine.
|
||||||
func (f Face) Face() font.Face {
|
func (f Face) Face() *fontapi.Face {
|
||||||
return &fontapi.Face{Font: f.face}
|
return &fontapi.Face{Font: f.face}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,74 +117,74 @@ func (f Face) Font() giofont.Font {
|
|||||||
return f.font
|
return f.font
|
||||||
}
|
}
|
||||||
|
|
||||||
func gioStyle(s metadata.Style) giofont.Style {
|
func gioStyle(s fontapi.Style) giofont.Style {
|
||||||
switch s {
|
switch s {
|
||||||
case metadata.StyleItalic:
|
case fontapi.StyleItalic:
|
||||||
return giofont.Italic
|
return giofont.Italic
|
||||||
case metadata.StyleNormal:
|
case fontapi.StyleNormal:
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
return giofont.Regular
|
return giofont.Regular
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mdStyle(g giofont.Style) metadata.Style {
|
func mdStyle(g giofont.Style) fontapi.Style {
|
||||||
switch g {
|
switch g {
|
||||||
case giofont.Italic:
|
case giofont.Italic:
|
||||||
return metadata.StyleItalic
|
return fontapi.StyleItalic
|
||||||
case giofont.Regular:
|
case giofont.Regular:
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
return metadata.StyleNormal
|
return fontapi.StyleNormal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func gioWeight(w metadata.Weight) giofont.Weight {
|
func gioWeight(w fontapi.Weight) giofont.Weight {
|
||||||
switch w {
|
switch w {
|
||||||
case metadata.WeightThin:
|
case fontapi.WeightThin:
|
||||||
return giofont.Thin
|
return giofont.Thin
|
||||||
case metadata.WeightExtraLight:
|
case fontapi.WeightExtraLight:
|
||||||
return giofont.ExtraLight
|
return giofont.ExtraLight
|
||||||
case metadata.WeightLight:
|
case fontapi.WeightLight:
|
||||||
return giofont.Light
|
return giofont.Light
|
||||||
case metadata.WeightNormal:
|
case fontapi.WeightNormal:
|
||||||
return giofont.Normal
|
return giofont.Normal
|
||||||
case metadata.WeightMedium:
|
case fontapi.WeightMedium:
|
||||||
return giofont.Medium
|
return giofont.Medium
|
||||||
case metadata.WeightSemibold:
|
case fontapi.WeightSemibold:
|
||||||
return giofont.SemiBold
|
return giofont.SemiBold
|
||||||
case metadata.WeightBold:
|
case fontapi.WeightBold:
|
||||||
return giofont.Bold
|
return giofont.Bold
|
||||||
case metadata.WeightExtraBold:
|
case fontapi.WeightExtraBold:
|
||||||
return giofont.ExtraBold
|
return giofont.ExtraBold
|
||||||
case metadata.WeightBlack:
|
case fontapi.WeightBlack:
|
||||||
return giofont.Black
|
return giofont.Black
|
||||||
default:
|
default:
|
||||||
return giofont.Normal
|
return giofont.Normal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mdWeight(g giofont.Weight) metadata.Weight {
|
func mdWeight(g giofont.Weight) fontapi.Weight {
|
||||||
switch g {
|
switch g {
|
||||||
case giofont.Thin:
|
case giofont.Thin:
|
||||||
return metadata.WeightThin
|
return fontapi.WeightThin
|
||||||
case giofont.ExtraLight:
|
case giofont.ExtraLight:
|
||||||
return metadata.WeightExtraLight
|
return fontapi.WeightExtraLight
|
||||||
case giofont.Light:
|
case giofont.Light:
|
||||||
return metadata.WeightLight
|
return fontapi.WeightLight
|
||||||
case giofont.Normal:
|
case giofont.Normal:
|
||||||
return metadata.WeightNormal
|
return fontapi.WeightNormal
|
||||||
case giofont.Medium:
|
case giofont.Medium:
|
||||||
return metadata.WeightMedium
|
return fontapi.WeightMedium
|
||||||
case giofont.SemiBold:
|
case giofont.SemiBold:
|
||||||
return metadata.WeightSemibold
|
return fontapi.WeightSemibold
|
||||||
case giofont.Bold:
|
case giofont.Bold:
|
||||||
return metadata.WeightBold
|
return fontapi.WeightBold
|
||||||
case giofont.ExtraBold:
|
case giofont.ExtraBold:
|
||||||
return metadata.WeightExtraBold
|
return fontapi.WeightExtraBold
|
||||||
case giofont.Black:
|
case giofont.Black:
|
||||||
return metadata.WeightBlack
|
return fontapi.WeightBlack
|
||||||
default:
|
default:
|
||||||
return metadata.WeightNormal
|
return fontapi.WeightNormal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+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,14 @@
|
|||||||
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/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.2.1
|
||||||
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95
|
golang.org/x/exp v0.0.0-20240707233637-46b078467d37
|
||||||
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91
|
golang.org/x/exp/shiny v0.0.0-20240707233637-46b078467d37
|
||||||
golang.org/x/image v0.5.0
|
golang.org/x/image v0.18.0
|
||||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64
|
golang.org/x/sys v0.22.0
|
||||||
|
golang.org/x/text v0.16.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require golang.org/x/text v0.7.0
|
|
||||||
|
|||||||
@@ -1,43 +1,19 @@
|
|||||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
|
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
|
||||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
|
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
|
||||||
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
||||||
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc=
|
|
||||||
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.2.1 h1:x0jMOGyO3d1qFAPI0j4GSsh7M0Q3Ypjzr4+CEVg82V8=
|
||||||
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k=
|
github.com/go-text/typesetting v0.2.1/go.mod h1:mTOxEwasOFpAMBjEQDhdWRckoLLeI/+qrQeBCTGEt6M=
|
||||||
github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI=
|
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||||
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 h1:sBdrWpxhGDdTAYNqbgBLAR+ULAPPhfgncLr1X0lyWtg=
|
golang.org/x/exp/shiny v0.0.0-20240707233637-46b078467d37 h1:SOSg7+sueresE4IbmmGM60GmlIys+zNX63d6/J4CMtU=
|
||||||
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
golang.org/x/exp/shiny v0.0.0-20240707233637-46b078467d37/go.mod h1:3F+MieQB7dRYLTmnncoFbb1crS5lfQoTfDgQy6K4N0o=
|
||||||
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 h1:ryT6Nf0R83ZgD8WnFFdfI8wCeyqgdXWN4+CkFVNPAT0=
|
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
||||||
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8=
|
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||||
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
|
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||||
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
|
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM=
|
|
||||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|
||||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
|
|||||||
-2193
File diff suppressed because it is too large
Load Diff
-129
@@ -1,129 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
package gpu
|
|
||||||
|
|
||||||
import (
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"gioui.org/cpu"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This file contains code specific to running compute shaders on the CPU.
|
|
||||||
|
|
||||||
// dispatcher dispatches CPU compute programs across multiple goroutines.
|
|
||||||
type dispatcher struct {
|
|
||||||
// done is notified when a worker completes its work slice.
|
|
||||||
done chan struct{}
|
|
||||||
// work receives work slice indices. It is closed when the dispatcher is released.
|
|
||||||
work chan work
|
|
||||||
// dispatch receives compute jobs, which is then split among workers.
|
|
||||||
dispatch chan dispatch
|
|
||||||
// sync receives notification when a Sync completes.
|
|
||||||
sync chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type work struct {
|
|
||||||
ctx *cpu.DispatchContext
|
|
||||||
index int
|
|
||||||
}
|
|
||||||
|
|
||||||
type dispatch struct {
|
|
||||||
_type jobType
|
|
||||||
program *cpu.ProgramInfo
|
|
||||||
descSet unsafe.Pointer
|
|
||||||
x, y, z int
|
|
||||||
}
|
|
||||||
|
|
||||||
type jobType uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
jobDispatch jobType = iota
|
|
||||||
jobBarrier
|
|
||||||
jobSync
|
|
||||||
)
|
|
||||||
|
|
||||||
func newDispatcher(workers int) *dispatcher {
|
|
||||||
d := &dispatcher{
|
|
||||||
work: make(chan work, workers),
|
|
||||||
done: make(chan struct{}, workers),
|
|
||||||
// Leave some room to avoid blocking calls to Dispatch.
|
|
||||||
dispatch: make(chan dispatch, 20),
|
|
||||||
sync: make(chan struct{}),
|
|
||||||
}
|
|
||||||
for i := 0; i < workers; i++ {
|
|
||||||
go d.worker()
|
|
||||||
}
|
|
||||||
go d.dispatcher()
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dispatcher) dispatcher() {
|
|
||||||
defer close(d.work)
|
|
||||||
var free []*cpu.DispatchContext
|
|
||||||
defer func() {
|
|
||||||
for _, ctx := range free {
|
|
||||||
ctx.Free()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
var used []*cpu.DispatchContext
|
|
||||||
for job := range d.dispatch {
|
|
||||||
switch job._type {
|
|
||||||
case jobDispatch:
|
|
||||||
if len(free) == 0 {
|
|
||||||
free = append(free, cpu.NewDispatchContext())
|
|
||||||
}
|
|
||||||
ctx := free[len(free)-1]
|
|
||||||
free = free[:len(free)-1]
|
|
||||||
used = append(used, ctx)
|
|
||||||
ctx.Prepare(cap(d.work), job.program, job.descSet, job.x, job.y, job.z)
|
|
||||||
for i := 0; i < cap(d.work); i++ {
|
|
||||||
d.work <- work{
|
|
||||||
ctx: ctx,
|
|
||||||
index: i,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case jobBarrier:
|
|
||||||
// Wait for all outstanding dispatches to complete.
|
|
||||||
for i := 0; i < len(used)*cap(d.work); i++ {
|
|
||||||
<-d.done
|
|
||||||
}
|
|
||||||
free = append(free, used...)
|
|
||||||
used = used[:0]
|
|
||||||
case jobSync:
|
|
||||||
d.sync <- struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dispatcher) worker() {
|
|
||||||
thread := cpu.NewThreadContext()
|
|
||||||
defer thread.Free()
|
|
||||||
for w := range d.work {
|
|
||||||
w.ctx.Dispatch(w.index, thread)
|
|
||||||
d.done <- struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dispatcher) Barrier() {
|
|
||||||
d.dispatch <- dispatch{_type: jobBarrier}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dispatcher) Sync() {
|
|
||||||
d.dispatch <- dispatch{_type: jobSync}
|
|
||||||
<-d.sync
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dispatcher) Dispatch(program *cpu.ProgramInfo, descSet unsafe.Pointer, x, y, z int) {
|
|
||||||
d.dispatch <- dispatch{
|
|
||||||
_type: jobDispatch,
|
|
||||||
program: program,
|
|
||||||
descSet: descSet,
|
|
||||||
x: x,
|
|
||||||
y: y,
|
|
||||||
z: z,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dispatcher) Stop() {
|
|
||||||
close(d.dispatch)
|
|
||||||
}
|
|
||||||
+115
-94
@@ -9,11 +9,11 @@ package gpu
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
@@ -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
|
||||||
@@ -343,13 +343,12 @@ func New(api API) (GPU, error) {
|
|||||||
func NewWithDevice(d driver.Device) (GPU, error) {
|
func NewWithDevice(d driver.Device) (GPU, error) {
|
||||||
d.BeginFrame(nil, false, image.Point{})
|
d.BeginFrame(nil, false, image.Point{})
|
||||||
defer d.EndFrame()
|
defer d.EndFrame()
|
||||||
forceCompute := os.Getenv("GIORENDERER") == "forcecompute"
|
|
||||||
feats := d.Caps().Features
|
feats := d.Caps().Features
|
||||||
switch {
|
switch {
|
||||||
case !forceCompute && feats.Has(driver.FeatureFloatRenderTargets) && feats.Has(driver.FeatureSRGB):
|
case feats.Has(driver.FeatureFloatRenderTargets) && feats.Has(driver.FeatureSRGB):
|
||||||
return newGPU(d)
|
return newGPU(d)
|
||||||
}
|
}
|
||||||
return newCompute(d)
|
return nil, errors.New("no available GPU driver")
|
||||||
}
|
}
|
||||||
|
|
||||||
func newGPU(ctx driver.Device) (*gpu, error) {
|
func newGPU(ctx driver.Device) (*gpu, error) {
|
||||||
@@ -560,12 +559,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 +594,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 +866,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 +931,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 +1056,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 +1101,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 +1231,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 +1245,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 +1270,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 +1278,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 {
|
||||||
@@ -1474,7 +1483,7 @@ func (d *drawOps) buildVerts(pathData []byte, tr f32.Affine2D, outline bool, str
|
|||||||
// as needed and feeds them to the supplied splitter.
|
// as needed and feeds them to the supplied splitter.
|
||||||
func decodeToOutlineQuads(qs *quadSplitter, tr f32.Affine2D, pathData []byte) {
|
func decodeToOutlineQuads(qs *quadSplitter, tr f32.Affine2D, pathData []byte) {
|
||||||
for len(pathData) >= scene.CommandSize+4 {
|
for len(pathData) >= scene.CommandSize+4 {
|
||||||
qs.contour = bo.Uint32(pathData)
|
qs.contour = binary.LittleEndian.Uint32(pathData)
|
||||||
cmd := ops.DecodeCommand(pathData[4:])
|
cmd := ops.DecodeCommand(pathData[4:])
|
||||||
switch cmd.Op() {
|
switch cmd.Op() {
|
||||||
case scene.OpLine:
|
case scene.OpLine:
|
||||||
@@ -1569,3 +1578,15 @@ func isPureOffset(t f32.Affine2D) bool {
|
|||||||
a, b, _, d, e, _ := t.Elems()
|
a, b, _, d, e, _ := t.Elems()
|
||||||
return a == 1 && b == 0 && d == 0 && e == 1
|
return a == 1 && b == 0 && d == 0 && e == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newShaders(ctx driver.Device, vsrc, fsrc shader.Sources) (vert driver.VertexShader, frag driver.FragmentShader, err error) {
|
||||||
|
vert, err = ctx.NewVertexShader(vsrc)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
frag, err = ctx.NewFragmentShader(fsrc)
|
||||||
|
if err != nil {
|
||||||
|
vert.Release()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
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) {
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -862,8 +862,8 @@ func (b *Backend) BindUniforms(buffer driver.Buffer) {
|
|||||||
buf := buffer.(*Buffer)
|
buf := buffer.(*Buffer)
|
||||||
cmdBuf := b.currentCmdBuf()
|
cmdBuf := b.currentCmdBuf()
|
||||||
for _, s := range b.pipe.pushRanges {
|
for _, s := range b.pipe.pushRanges {
|
||||||
off := s.Offset()
|
off := vk.PushConstantRangeOffset(s)
|
||||||
vk.CmdPushConstants(cmdBuf, b.pipe.desc.layout, s.StageFlags(), off, buf.store[off:off+s.Size()])
|
vk.CmdPushConstants(cmdBuf, b.pipe.desc.layout, vk.PushConstantRangeStageFlags(s), off, buf.store[off:off+vk.PushConstantRangeSize(s)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+48
-26
@@ -9,8 +9,6 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
syscall "golang.org/x/sys/windows"
|
syscall "golang.org/x/sys/windows"
|
||||||
|
|
||||||
"gioui.org/internal/gl"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -24,23 +22,23 @@ type (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
libEGL = syscall.NewLazyDLL("libEGL.dll")
|
libEGL = syscall.DLL{}
|
||||||
_eglChooseConfig = libEGL.NewProc("eglChooseConfig")
|
_eglChooseConfig *syscall.Proc
|
||||||
_eglCreateContext = libEGL.NewProc("eglCreateContext")
|
_eglCreateContext *syscall.Proc
|
||||||
_eglCreateWindowSurface = libEGL.NewProc("eglCreateWindowSurface")
|
_eglCreateWindowSurface *syscall.Proc
|
||||||
_eglDestroyContext = libEGL.NewProc("eglDestroyContext")
|
_eglDestroyContext *syscall.Proc
|
||||||
_eglDestroySurface = libEGL.NewProc("eglDestroySurface")
|
_eglDestroySurface *syscall.Proc
|
||||||
_eglGetConfigAttrib = libEGL.NewProc("eglGetConfigAttrib")
|
_eglGetConfigAttrib *syscall.Proc
|
||||||
_eglGetDisplay = libEGL.NewProc("eglGetDisplay")
|
_eglGetDisplay *syscall.Proc
|
||||||
_eglGetError = libEGL.NewProc("eglGetError")
|
_eglGetError *syscall.Proc
|
||||||
_eglInitialize = libEGL.NewProc("eglInitialize")
|
_eglInitialize *syscall.Proc
|
||||||
_eglMakeCurrent = libEGL.NewProc("eglMakeCurrent")
|
_eglMakeCurrent *syscall.Proc
|
||||||
_eglReleaseThread = libEGL.NewProc("eglReleaseThread")
|
_eglReleaseThread *syscall.Proc
|
||||||
_eglSwapInterval = libEGL.NewProc("eglSwapInterval")
|
_eglSwapInterval *syscall.Proc
|
||||||
_eglSwapBuffers = libEGL.NewProc("eglSwapBuffers")
|
_eglSwapBuffers *syscall.Proc
|
||||||
_eglTerminate = libEGL.NewProc("eglTerminate")
|
_eglTerminate *syscall.Proc
|
||||||
_eglQueryString = libEGL.NewProc("eglQueryString")
|
_eglQueryString *syscall.Proc
|
||||||
_eglWaitClient = libEGL.NewProc("eglWaitClient")
|
_eglWaitClient *syscall.Proc
|
||||||
)
|
)
|
||||||
|
|
||||||
var loadOnce sync.Once
|
var loadOnce sync.Once
|
||||||
@@ -54,21 +52,45 @@ func loadEGL() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadDLLs() error {
|
func loadDLLs() error {
|
||||||
if err := loadDLL(libEGL, "libEGL.dll"); err != nil {
|
if err := loadDLL(&libEGL, "libEGL.dll"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := loadDLL(gl.LibGLESv2, "libGLESv2.dll"); err != nil {
|
|
||||||
return err
|
procs := map[string]**syscall.Proc{
|
||||||
|
"eglChooseConfig": &_eglChooseConfig,
|
||||||
|
"eglCreateContext": &_eglCreateContext,
|
||||||
|
"eglCreateWindowSurface": &_eglCreateWindowSurface,
|
||||||
|
"eglDestroyContext": &_eglDestroyContext,
|
||||||
|
"eglDestroySurface": &_eglDestroySurface,
|
||||||
|
"eglGetConfigAttrib": &_eglGetConfigAttrib,
|
||||||
|
"eglGetDisplay": &_eglGetDisplay,
|
||||||
|
"eglGetError": &_eglGetError,
|
||||||
|
"eglInitialize": &_eglInitialize,
|
||||||
|
"eglMakeCurrent": &_eglMakeCurrent,
|
||||||
|
"eglReleaseThread": &_eglReleaseThread,
|
||||||
|
"eglSwapInterval": &_eglSwapInterval,
|
||||||
|
"eglSwapBuffers": &_eglSwapBuffers,
|
||||||
|
"eglTerminate": &_eglTerminate,
|
||||||
|
"eglQueryString": &_eglQueryString,
|
||||||
|
"eglWaitClient": &_eglWaitClient,
|
||||||
}
|
}
|
||||||
// d3dcompiler_47.dll is needed internally for shader compilation to function.
|
for name, proc := range procs {
|
||||||
return loadDLL(syscall.NewLazyDLL("d3dcompiler_47.dll"), "d3dcompiler_47.dll")
|
p, err := libEGL.FindProc(name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to locate %s in %s: %w", name, libEGL.Name, err)
|
||||||
|
}
|
||||||
|
*proc = p
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadDLL(dll *syscall.LazyDLL, name string) error {
|
func loadDLL(dll *syscall.DLL, name string) error {
|
||||||
err := dll.Load()
|
handle, err := syscall.LoadLibraryEx(name, 0, syscall.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("egl: failed to load %s: %v", name, err)
|
return fmt.Errorf("egl: failed to load %s: %v", name, err)
|
||||||
}
|
}
|
||||||
|
dll.Handle = handle
|
||||||
|
dll.Name = name
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+210
-89
@@ -3,103 +3,217 @@
|
|||||||
package gl
|
package gl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func loadGLESv2Procs() error {
|
||||||
|
dllName := "libGLESv2.dll"
|
||||||
|
handle, err := windows.LoadLibraryEx(dllName, 0, windows.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("gl: failed to load %s: %v", dllName, err)
|
||||||
|
}
|
||||||
|
gles := windows.DLL{Handle: handle, Name: dllName}
|
||||||
|
// d3dcompiler_47.dll is needed internally for shader compilation to function.
|
||||||
|
dllName = "d3dcompiler_47.dll"
|
||||||
|
_, err = windows.LoadLibraryEx(dllName, 0, windows.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("gl: failed to load %s: %v", dllName, err)
|
||||||
|
}
|
||||||
|
procs := map[string]**windows.Proc{
|
||||||
|
"glActiveTexture": &_glActiveTexture,
|
||||||
|
"glAttachShader": &_glAttachShader,
|
||||||
|
"glBeginQuery": &_glBeginQuery,
|
||||||
|
"glBindAttribLocation": &_glBindAttribLocation,
|
||||||
|
"glBindBuffer": &_glBindBuffer,
|
||||||
|
"glBindBufferBase": &_glBindBufferBase,
|
||||||
|
"glBindFramebuffer": &_glBindFramebuffer,
|
||||||
|
"glBindRenderbuffer": &_glBindRenderbuffer,
|
||||||
|
"glBindTexture": &_glBindTexture,
|
||||||
|
"glBindVertexArray": &_glBindVertexArray,
|
||||||
|
"glBlendEquation": &_glBlendEquation,
|
||||||
|
"glBlendFuncSeparate": &_glBlendFuncSeparate,
|
||||||
|
"glBufferData": &_glBufferData,
|
||||||
|
"glBufferSubData": &_glBufferSubData,
|
||||||
|
"glCheckFramebufferStatus": &_glCheckFramebufferStatus,
|
||||||
|
"glClear": &_glClear,
|
||||||
|
"glClearColor": &_glClearColor,
|
||||||
|
"glClearDepthf": &_glClearDepthf,
|
||||||
|
"glDeleteQueries": &_glDeleteQueries,
|
||||||
|
"glDeleteVertexArrays": &_glDeleteVertexArrays,
|
||||||
|
"glCompileShader": &_glCompileShader,
|
||||||
|
"glCopyTexSubImage2D": &_glCopyTexSubImage2D,
|
||||||
|
"glGenerateMipmap": &_glGenerateMipmap,
|
||||||
|
"glGenBuffers": &_glGenBuffers,
|
||||||
|
"glGenFramebuffers": &_glGenFramebuffers,
|
||||||
|
"glGenVertexArrays": &_glGenVertexArrays,
|
||||||
|
"glGetUniformBlockIndex": &_glGetUniformBlockIndex,
|
||||||
|
"glCreateProgram": &_glCreateProgram,
|
||||||
|
"glGenRenderbuffers": &_glGenRenderbuffers,
|
||||||
|
"glCreateShader": &_glCreateShader,
|
||||||
|
"glGenTextures": &_glGenTextures,
|
||||||
|
"glDeleteBuffers": &_glDeleteBuffers,
|
||||||
|
"glDeleteFramebuffers": &_glDeleteFramebuffers,
|
||||||
|
"glDeleteProgram": &_glDeleteProgram,
|
||||||
|
"glDeleteShader": &_glDeleteShader,
|
||||||
|
"glDeleteRenderbuffers": &_glDeleteRenderbuffers,
|
||||||
|
"glDeleteTextures": &_glDeleteTextures,
|
||||||
|
"glDepthFunc": &_glDepthFunc,
|
||||||
|
"glDepthMask": &_glDepthMask,
|
||||||
|
"glDisableVertexAttribArray": &_glDisableVertexAttribArray,
|
||||||
|
"glDisable": &_glDisable,
|
||||||
|
"glDrawArrays": &_glDrawArrays,
|
||||||
|
"glDrawElements": &_glDrawElements,
|
||||||
|
"glEnable": &_glEnable,
|
||||||
|
"glEnableVertexAttribArray": &_glEnableVertexAttribArray,
|
||||||
|
"glEndQuery": &_glEndQuery,
|
||||||
|
"glFinish": &_glFinish,
|
||||||
|
"glFlush": &_glFlush,
|
||||||
|
"glFramebufferRenderbuffer": &_glFramebufferRenderbuffer,
|
||||||
|
"glFramebufferTexture2D": &_glFramebufferTexture2D,
|
||||||
|
"glGenQueries": &_glGenQueries,
|
||||||
|
"glGetError": &_glGetError,
|
||||||
|
"glGetRenderbufferParameteriv": &_glGetRenderbufferParameteriv,
|
||||||
|
"glGetFloatv": &_glGetFloatv,
|
||||||
|
"glGetFramebufferAttachmentParameteriv": &_glGetFramebufferAttachmentParameteriv,
|
||||||
|
"glGetIntegerv": &_glGetIntegerv,
|
||||||
|
"glGetIntegeri_v": &_glGetIntegeri_v,
|
||||||
|
"glGetProgramiv": &_glGetProgramiv,
|
||||||
|
"glGetProgramInfoLog": &_glGetProgramInfoLog,
|
||||||
|
"glGetQueryObjectuiv": &_glGetQueryObjectuiv,
|
||||||
|
"glGetShaderiv": &_glGetShaderiv,
|
||||||
|
"glGetShaderInfoLog": &_glGetShaderInfoLog,
|
||||||
|
"glGetString": &_glGetString,
|
||||||
|
"glGetUniformLocation": &_glGetUniformLocation,
|
||||||
|
"glGetVertexAttribiv": &_glGetVertexAttribiv,
|
||||||
|
"glGetVertexAttribPointerv": &_glGetVertexAttribPointerv,
|
||||||
|
"glInvalidateFramebuffer": &_glInvalidateFramebuffer,
|
||||||
|
"glIsEnabled": &_glIsEnabled,
|
||||||
|
"glLinkProgram": &_glLinkProgram,
|
||||||
|
"glPixelStorei": &_glPixelStorei,
|
||||||
|
"glReadPixels": &_glReadPixels,
|
||||||
|
"glRenderbufferStorage": &_glRenderbufferStorage,
|
||||||
|
"glScissor": &_glScissor,
|
||||||
|
"glShaderSource": &_glShaderSource,
|
||||||
|
"glTexImage2D": &_glTexImage2D,
|
||||||
|
"glTexStorage2D": &_glTexStorage2D,
|
||||||
|
"glTexSubImage2D": &_glTexSubImage2D,
|
||||||
|
"glTexParameteri": &_glTexParameteri,
|
||||||
|
"glUniformBlockBinding": &_glUniformBlockBinding,
|
||||||
|
"glUniform1f": &_glUniform1f,
|
||||||
|
"glUniform1i": &_glUniform1i,
|
||||||
|
"glUniform2f": &_glUniform2f,
|
||||||
|
"glUniform3f": &_glUniform3f,
|
||||||
|
"glUniform4f": &_glUniform4f,
|
||||||
|
"glUseProgram": &_glUseProgram,
|
||||||
|
"glVertexAttribPointer": &_glVertexAttribPointer,
|
||||||
|
"glViewport": &_glViewport,
|
||||||
|
}
|
||||||
|
for name, proc := range procs {
|
||||||
|
p, err := gles.FindProc(name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to locate %s in %s: %w", name, gles.Name, err)
|
||||||
|
}
|
||||||
|
*proc = p
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
LibGLESv2 = windows.NewLazyDLL("libGLESv2.dll")
|
glInitOnce sync.Once
|
||||||
_glActiveTexture = LibGLESv2.NewProc("glActiveTexture")
|
_glActiveTexture *windows.Proc
|
||||||
_glAttachShader = LibGLESv2.NewProc("glAttachShader")
|
_glAttachShader *windows.Proc
|
||||||
_glBeginQuery = LibGLESv2.NewProc("glBeginQuery")
|
_glBeginQuery *windows.Proc
|
||||||
_glBindAttribLocation = LibGLESv2.NewProc("glBindAttribLocation")
|
_glBindAttribLocation *windows.Proc
|
||||||
_glBindBuffer = LibGLESv2.NewProc("glBindBuffer")
|
_glBindBuffer *windows.Proc
|
||||||
_glBindBufferBase = LibGLESv2.NewProc("glBindBufferBase")
|
_glBindBufferBase *windows.Proc
|
||||||
_glBindFramebuffer = LibGLESv2.NewProc("glBindFramebuffer")
|
_glBindFramebuffer *windows.Proc
|
||||||
_glBindRenderbuffer = LibGLESv2.NewProc("glBindRenderbuffer")
|
_glBindRenderbuffer *windows.Proc
|
||||||
_glBindTexture = LibGLESv2.NewProc("glBindTexture")
|
_glBindTexture *windows.Proc
|
||||||
_glBindVertexArray = LibGLESv2.NewProc("glBindVertexArray")
|
_glBindVertexArray *windows.Proc
|
||||||
_glBlendEquation = LibGLESv2.NewProc("glBlendEquation")
|
_glBlendEquation *windows.Proc
|
||||||
_glBlendFuncSeparate = LibGLESv2.NewProc("glBlendFuncSeparate")
|
_glBlendFuncSeparate *windows.Proc
|
||||||
_glBufferData = LibGLESv2.NewProc("glBufferData")
|
_glBufferData *windows.Proc
|
||||||
_glBufferSubData = LibGLESv2.NewProc("glBufferSubData")
|
_glBufferSubData *windows.Proc
|
||||||
_glCheckFramebufferStatus = LibGLESv2.NewProc("glCheckFramebufferStatus")
|
_glCheckFramebufferStatus *windows.Proc
|
||||||
_glClear = LibGLESv2.NewProc("glClear")
|
_glClear *windows.Proc
|
||||||
_glClearColor = LibGLESv2.NewProc("glClearColor")
|
_glClearColor *windows.Proc
|
||||||
_glClearDepthf = LibGLESv2.NewProc("glClearDepthf")
|
_glClearDepthf *windows.Proc
|
||||||
_glDeleteQueries = LibGLESv2.NewProc("glDeleteQueries")
|
_glDeleteQueries *windows.Proc
|
||||||
_glDeleteVertexArrays = LibGLESv2.NewProc("glDeleteVertexArrays")
|
_glDeleteVertexArrays *windows.Proc
|
||||||
_glCompileShader = LibGLESv2.NewProc("glCompileShader")
|
_glCompileShader *windows.Proc
|
||||||
_glCopyTexSubImage2D = LibGLESv2.NewProc("glCopyTexSubImage2D")
|
_glCopyTexSubImage2D *windows.Proc
|
||||||
_glGenerateMipmap = LibGLESv2.NewProc("glGenerateMipmap")
|
_glGenerateMipmap *windows.Proc
|
||||||
_glGenBuffers = LibGLESv2.NewProc("glGenBuffers")
|
_glGenBuffers *windows.Proc
|
||||||
_glGenFramebuffers = LibGLESv2.NewProc("glGenFramebuffers")
|
_glGenFramebuffers *windows.Proc
|
||||||
_glGenVertexArrays = LibGLESv2.NewProc("glGenVertexArrays")
|
_glGenVertexArrays *windows.Proc
|
||||||
_glGetUniformBlockIndex = LibGLESv2.NewProc("glGetUniformBlockIndex")
|
_glGetUniformBlockIndex *windows.Proc
|
||||||
_glCreateProgram = LibGLESv2.NewProc("glCreateProgram")
|
_glCreateProgram *windows.Proc
|
||||||
_glGenRenderbuffers = LibGLESv2.NewProc("glGenRenderbuffers")
|
_glGenRenderbuffers *windows.Proc
|
||||||
_glCreateShader = LibGLESv2.NewProc("glCreateShader")
|
_glCreateShader *windows.Proc
|
||||||
_glGenTextures = LibGLESv2.NewProc("glGenTextures")
|
_glGenTextures *windows.Proc
|
||||||
_glDeleteBuffers = LibGLESv2.NewProc("glDeleteBuffers")
|
_glDeleteBuffers *windows.Proc
|
||||||
_glDeleteFramebuffers = LibGLESv2.NewProc("glDeleteFramebuffers")
|
_glDeleteFramebuffers *windows.Proc
|
||||||
_glDeleteProgram = LibGLESv2.NewProc("glDeleteProgram")
|
_glDeleteProgram *windows.Proc
|
||||||
_glDeleteShader = LibGLESv2.NewProc("glDeleteShader")
|
_glDeleteShader *windows.Proc
|
||||||
_glDeleteRenderbuffers = LibGLESv2.NewProc("glDeleteRenderbuffers")
|
_glDeleteRenderbuffers *windows.Proc
|
||||||
_glDeleteTextures = LibGLESv2.NewProc("glDeleteTextures")
|
_glDeleteTextures *windows.Proc
|
||||||
_glDepthFunc = LibGLESv2.NewProc("glDepthFunc")
|
_glDepthFunc *windows.Proc
|
||||||
_glDepthMask = LibGLESv2.NewProc("glDepthMask")
|
_glDepthMask *windows.Proc
|
||||||
_glDisableVertexAttribArray = LibGLESv2.NewProc("glDisableVertexAttribArray")
|
_glDisableVertexAttribArray *windows.Proc
|
||||||
_glDisable = LibGLESv2.NewProc("glDisable")
|
_glDisable *windows.Proc
|
||||||
_glDrawArrays = LibGLESv2.NewProc("glDrawArrays")
|
_glDrawArrays *windows.Proc
|
||||||
_glDrawElements = LibGLESv2.NewProc("glDrawElements")
|
_glDrawElements *windows.Proc
|
||||||
_glEnable = LibGLESv2.NewProc("glEnable")
|
_glEnable *windows.Proc
|
||||||
_glEnableVertexAttribArray = LibGLESv2.NewProc("glEnableVertexAttribArray")
|
_glEnableVertexAttribArray *windows.Proc
|
||||||
_glEndQuery = LibGLESv2.NewProc("glEndQuery")
|
_glEndQuery *windows.Proc
|
||||||
_glFinish = LibGLESv2.NewProc("glFinish")
|
_glFinish *windows.Proc
|
||||||
_glFlush = LibGLESv2.NewProc("glFlush")
|
_glFlush *windows.Proc
|
||||||
_glFramebufferRenderbuffer = LibGLESv2.NewProc("glFramebufferRenderbuffer")
|
_glFramebufferRenderbuffer *windows.Proc
|
||||||
_glFramebufferTexture2D = LibGLESv2.NewProc("glFramebufferTexture2D")
|
_glFramebufferTexture2D *windows.Proc
|
||||||
_glGenQueries = LibGLESv2.NewProc("glGenQueries")
|
_glGenQueries *windows.Proc
|
||||||
_glGetError = LibGLESv2.NewProc("glGetError")
|
_glGetError *windows.Proc
|
||||||
_glGetRenderbufferParameteriv = LibGLESv2.NewProc("glGetRenderbufferParameteriv")
|
_glGetRenderbufferParameteriv *windows.Proc
|
||||||
_glGetFloatv = LibGLESv2.NewProc("glGetFloatv")
|
_glGetFloatv *windows.Proc
|
||||||
_glGetFramebufferAttachmentParameteriv = LibGLESv2.NewProc("glGetFramebufferAttachmentParameteriv")
|
_glGetFramebufferAttachmentParameteriv *windows.Proc
|
||||||
_glGetIntegerv = LibGLESv2.NewProc("glGetIntegerv")
|
_glGetIntegerv *windows.Proc
|
||||||
_glGetIntegeri_v = LibGLESv2.NewProc("glGetIntegeri_v")
|
_glGetIntegeri_v *windows.Proc
|
||||||
_glGetProgramiv = LibGLESv2.NewProc("glGetProgramiv")
|
_glGetProgramiv *windows.Proc
|
||||||
_glGetProgramInfoLog = LibGLESv2.NewProc("glGetProgramInfoLog")
|
_glGetProgramInfoLog *windows.Proc
|
||||||
_glGetQueryObjectuiv = LibGLESv2.NewProc("glGetQueryObjectuiv")
|
_glGetQueryObjectuiv *windows.Proc
|
||||||
_glGetShaderiv = LibGLESv2.NewProc("glGetShaderiv")
|
_glGetShaderiv *windows.Proc
|
||||||
_glGetShaderInfoLog = LibGLESv2.NewProc("glGetShaderInfoLog")
|
_glGetShaderInfoLog *windows.Proc
|
||||||
_glGetString = LibGLESv2.NewProc("glGetString")
|
_glGetString *windows.Proc
|
||||||
_glGetUniformLocation = LibGLESv2.NewProc("glGetUniformLocation")
|
_glGetUniformLocation *windows.Proc
|
||||||
_glGetVertexAttribiv = LibGLESv2.NewProc("glGetVertexAttribiv")
|
_glGetVertexAttribiv *windows.Proc
|
||||||
_glGetVertexAttribPointerv = LibGLESv2.NewProc("glGetVertexAttribPointerv")
|
_glGetVertexAttribPointerv *windows.Proc
|
||||||
_glInvalidateFramebuffer = LibGLESv2.NewProc("glInvalidateFramebuffer")
|
_glInvalidateFramebuffer *windows.Proc
|
||||||
_glIsEnabled = LibGLESv2.NewProc("glIsEnabled")
|
_glIsEnabled *windows.Proc
|
||||||
_glLinkProgram = LibGLESv2.NewProc("glLinkProgram")
|
_glLinkProgram *windows.Proc
|
||||||
_glPixelStorei = LibGLESv2.NewProc("glPixelStorei")
|
_glPixelStorei *windows.Proc
|
||||||
_glReadPixels = LibGLESv2.NewProc("glReadPixels")
|
_glReadPixels *windows.Proc
|
||||||
_glRenderbufferStorage = LibGLESv2.NewProc("glRenderbufferStorage")
|
_glRenderbufferStorage *windows.Proc
|
||||||
_glScissor = LibGLESv2.NewProc("glScissor")
|
_glScissor *windows.Proc
|
||||||
_glShaderSource = LibGLESv2.NewProc("glShaderSource")
|
_glShaderSource *windows.Proc
|
||||||
_glTexImage2D = LibGLESv2.NewProc("glTexImage2D")
|
_glTexImage2D *windows.Proc
|
||||||
_glTexStorage2D = LibGLESv2.NewProc("glTexStorage2D")
|
_glTexStorage2D *windows.Proc
|
||||||
_glTexSubImage2D = LibGLESv2.NewProc("glTexSubImage2D")
|
_glTexSubImage2D *windows.Proc
|
||||||
_glTexParameteri = LibGLESv2.NewProc("glTexParameteri")
|
_glTexParameteri *windows.Proc
|
||||||
_glUniformBlockBinding = LibGLESv2.NewProc("glUniformBlockBinding")
|
_glUniformBlockBinding *windows.Proc
|
||||||
_glUniform1f = LibGLESv2.NewProc("glUniform1f")
|
_glUniform1f *windows.Proc
|
||||||
_glUniform1i = LibGLESv2.NewProc("glUniform1i")
|
_glUniform1i *windows.Proc
|
||||||
_glUniform2f = LibGLESv2.NewProc("glUniform2f")
|
_glUniform2f *windows.Proc
|
||||||
_glUniform3f = LibGLESv2.NewProc("glUniform3f")
|
_glUniform3f *windows.Proc
|
||||||
_glUniform4f = LibGLESv2.NewProc("glUniform4f")
|
_glUniform4f *windows.Proc
|
||||||
_glUseProgram = LibGLESv2.NewProc("glUseProgram")
|
_glUseProgram *windows.Proc
|
||||||
_glVertexAttribPointer = LibGLESv2.NewProc("glVertexAttribPointer")
|
_glVertexAttribPointer *windows.Proc
|
||||||
_glViewport = LibGLESv2.NewProc("glViewport")
|
_glViewport *windows.Proc
|
||||||
)
|
)
|
||||||
|
|
||||||
type Functions struct {
|
type Functions struct {
|
||||||
@@ -115,7 +229,11 @@ func NewFunctions(ctx Context, forceES bool) (*Functions, error) {
|
|||||||
if ctx != nil {
|
if ctx != nil {
|
||||||
panic("non-nil context")
|
panic("non-nil context")
|
||||||
}
|
}
|
||||||
return new(Functions), nil
|
var err error
|
||||||
|
glInitOnce.Do(func() {
|
||||||
|
err = loadGLESv2Procs()
|
||||||
|
})
|
||||||
|
return new(Functions), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) ActiveTexture(t Enum) {
|
func (c *Functions) ActiveTexture(t Enum) {
|
||||||
@@ -361,6 +479,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 {
|
||||||
|
|||||||
@@ -1893,27 +1893,27 @@ func BuildWriteDescriptorSetBuffer(set DescriptorSet, binding int, typ Descripto
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r PushConstantRange) StageFlags() ShaderStageFlags {
|
func PushConstantRangeStageFlags(r PushConstantRange) ShaderStageFlags {
|
||||||
return r.stageFlags
|
return r.stageFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r PushConstantRange) Offset() int {
|
func PushConstantRangeOffset(r PushConstantRange) int {
|
||||||
return int(r.offset)
|
return int(r.offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r PushConstantRange) Size() int {
|
func PushConstantRangeSize(r PushConstantRange) int {
|
||||||
return int(r.size)
|
return int(r.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p QueueFamilyProperties) Flags() QueueFlags {
|
func QueueFamilyPropertiesFlags(p QueueFamilyProperties) QueueFlags {
|
||||||
return p.queueFlags
|
return p.queueFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c SurfaceCapabilities) MinExtent() image.Point {
|
func SurfaceCapabilitiesMinExtent(c SurfaceCapabilities) image.Point {
|
||||||
return image.Pt(int(c.minImageExtent.width), int(c.minImageExtent.height))
|
return image.Pt(int(c.minImageExtent.width), int(c.minImageExtent.height))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c SurfaceCapabilities) MaxExtent() image.Point {
|
func SurfaceCapabilitiesMaxExtent(c SurfaceCapabilities) image.Point {
|
||||||
return image.Pt(int(c.maxImageExtent.width), int(c.maxImageExtent.height))
|
return image.Pt(int(c.maxImageExtent.width), int(c.maxImageExtent.height))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+35
-36
@@ -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
|
||||||
@@ -61,7 +60,7 @@ type Router struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Source implements the interface between a Router and user interface widgets.
|
// Source implements the interface between a Router and user interface widgets.
|
||||||
// The value Source is disabled.
|
// The zero-value Source is disabled.
|
||||||
type Source struct {
|
type Source struct {
|
||||||
r *Router
|
r *Router
|
||||||
}
|
}
|
||||||
@@ -172,22 +171,22 @@ func (q *Router) Source() Source {
|
|||||||
|
|
||||||
// Execute a command.
|
// Execute a command.
|
||||||
func (s Source) Execute(c Command) {
|
func (s Source) Execute(c Command) {
|
||||||
if !s.Enabled() {
|
if !s.enabled() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.r.execute(c)
|
s.r.execute(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enabled reports whether the source is enabled. Only enabled
|
// enabled reports whether the source is enabled. Only enabled
|
||||||
// Sources deliver events and respond to commands.
|
// Sources deliver events and respond to commands.
|
||||||
func (s Source) Enabled() bool {
|
func (s Source) enabled() bool {
|
||||||
return s.r != nil
|
return s.r != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Focused reports whether tag is focused, according to the most recent
|
// Focused reports whether tag is focused, according to the most recent
|
||||||
// [key.FocusEvent] delivered.
|
// [key.FocusEvent] delivered.
|
||||||
func (s Source) Focused(tag event.Tag) bool {
|
func (s Source) Focused(tag event.Tag) bool {
|
||||||
if !s.Enabled() {
|
if !s.enabled() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return s.r.state().keyState.focus == tag
|
return s.r.state().keyState.focus == tag
|
||||||
@@ -195,7 +194,7 @@ func (s Source) Focused(tag event.Tag) bool {
|
|||||||
|
|
||||||
// Event returns the next event that matches at least one of filters.
|
// Event returns the next event that matches at least one of filters.
|
||||||
func (s Source) Event(filters ...event.Filter) (event.Event, bool) {
|
func (s Source) Event(filters ...event.Filter) (event.Event, bool) {
|
||||||
if !s.Enabled() {
|
if !s.enabled() {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
return s.r.Event(filters...)
|
return s.r.Event(filters...)
|
||||||
@@ -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)
|
||||||
|
|||||||
+17
-3
@@ -5,6 +5,7 @@ package layout
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/input"
|
"gioui.org/io/input"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
@@ -28,6 +29,7 @@ type Context struct {
|
|||||||
// Interested users must look up and populate these values manually.
|
// Interested users must look up and populate these values manually.
|
||||||
Locale system.Locale
|
Locale system.Locale
|
||||||
|
|
||||||
|
disabled bool
|
||||||
input.Source
|
input.Source
|
||||||
*op.Ops
|
*op.Ops
|
||||||
}
|
}
|
||||||
@@ -42,9 +44,21 @@ func (c Context) Sp(v unit.Sp) int {
|
|||||||
return c.Metric.Sp(v)
|
return c.Metric.Sp(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disabled returns a copy of this context with a disabled Source,
|
func (c Context) Event(filters ...event.Filter) (event.Event, bool) {
|
||||||
// blocking widgets from changing its state and receiving events.
|
if c.disabled {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return c.Source.Event(filters...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enabled reports whether this context is enabled. Disabled contexts
|
||||||
|
// don't report events.
|
||||||
|
func (c Context) Enabled() bool {
|
||||||
|
return !c.disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disabled returns a copy of this context that don't deliver any events.
|
||||||
func (c Context) Disabled() Context {
|
func (c Context) Disabled() Context {
|
||||||
c.Source = input.Source{}
|
c.disabled = true
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|||||||
+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,
|
||||||
|
|||||||
+42
-89
@@ -4,6 +4,7 @@ package text
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
@@ -11,10 +12,9 @@ import (
|
|||||||
|
|
||||||
"github.com/go-text/typesetting/di"
|
"github.com/go-text/typesetting/di"
|
||||||
"github.com/go-text/typesetting/font"
|
"github.com/go-text/typesetting/font"
|
||||||
|
gotextot "github.com/go-text/typesetting/font/opentype"
|
||||||
"github.com/go-text/typesetting/fontscan"
|
"github.com/go-text/typesetting/fontscan"
|
||||||
"github.com/go-text/typesetting/language"
|
"github.com/go-text/typesetting/language"
|
||||||
"github.com/go-text/typesetting/opentype/api"
|
|
||||||
"github.com/go-text/typesetting/opentype/api/metadata"
|
|
||||||
"github.com/go-text/typesetting/shaping"
|
"github.com/go-text/typesetting/shaping"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
"golang.org/x/image/math/fixed"
|
"golang.org/x/image/math/fixed"
|
||||||
@@ -200,7 +200,7 @@ type runLayout struct {
|
|||||||
// Direction is the layout direction of the glyphs.
|
// Direction is the layout direction of the glyphs.
|
||||||
Direction system.TextDirection
|
Direction system.TextDirection
|
||||||
// face is the font face that the ID of each Glyph in the Layout refers to.
|
// face is the font face that the ID of each Glyph in the Layout refers to.
|
||||||
face font.Face
|
face *font.Face
|
||||||
// truncator indicates that this run is a text truncator standing in for remaining
|
// truncator indicates that this run is a text truncator standing in for remaining
|
||||||
// text.
|
// text.
|
||||||
truncator bool
|
truncator bool
|
||||||
@@ -210,8 +210,8 @@ type runLayout struct {
|
|||||||
type shaperImpl struct {
|
type shaperImpl struct {
|
||||||
// Fields for tracking fonts/faces.
|
// Fields for tracking fonts/faces.
|
||||||
fontMap *fontscan.FontMap
|
fontMap *fontscan.FontMap
|
||||||
faces []font.Face
|
faces []*font.Face
|
||||||
faceToIndex map[font.Font]int
|
faceToIndex map[*font.Font]int
|
||||||
faceMeta []giofont.Font
|
faceMeta []giofont.Font
|
||||||
defaultFaces []string
|
defaultFaces []string
|
||||||
logger interface {
|
logger interface {
|
||||||
@@ -253,7 +253,7 @@ func newShaperImpl(systemFonts bool, collection []FontFace) *shaperImpl {
|
|||||||
var shaper shaperImpl
|
var shaper shaperImpl
|
||||||
shaper.logger = newDebugLogger()
|
shaper.logger = newDebugLogger()
|
||||||
shaper.fontMap = fontscan.NewFontMap(shaper.logger)
|
shaper.fontMap = fontscan.NewFontMap(shaper.logger)
|
||||||
shaper.faceToIndex = make(map[font.Font]int)
|
shaper.faceToIndex = make(map[*font.Font]int)
|
||||||
if systemFonts {
|
if systemFonts {
|
||||||
str, err := os.UserCacheDir()
|
str, err := os.UserCacheDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -276,11 +276,12 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *shaperImpl) addFace(f font.Face, md giofont.Font) {
|
func (s *shaperImpl) addFace(f *font.Face, md giofont.Font) {
|
||||||
if _, ok := s.faceToIndex[f.Font]; ok {
|
if _, ok := s.faceToIndex[f.Font]; ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -375,11 +376,11 @@ func (s *shaperImpl) splitBidi(input shaping.Input) []shaping.Input {
|
|||||||
// ResolveFace allows shaperImpl to implement shaping.FontMap, wrapping its fontMap
|
// ResolveFace allows shaperImpl to implement shaping.FontMap, wrapping its fontMap
|
||||||
// field and ensuring that any faces loaded as part of the search are registered with
|
// field and ensuring that any faces loaded as part of the search are registered with
|
||||||
// ids so that they can be referred to by a GlyphID.
|
// ids so that they can be referred to by a GlyphID.
|
||||||
func (s *shaperImpl) ResolveFace(r rune) font.Face {
|
func (s *shaperImpl) ResolveFace(r rune) *font.Face {
|
||||||
face := s.fontMap.ResolveFace(r)
|
face := s.fontMap.ResolveFace(r)
|
||||||
if face != nil {
|
if face != nil {
|
||||||
family, aspect := s.fontMap.FontMetadata(face.Font)
|
family, aspect := s.fontMap.FontMetadata(face.Font)
|
||||||
md := opentype.DescriptionToFont(metadata.Description{
|
md := opentype.DescriptionToFont(font.Description{
|
||||||
Family: family,
|
Family: family,
|
||||||
Aspect: aspect,
|
Aspect: aspect,
|
||||||
})
|
})
|
||||||
@@ -489,9 +490,11 @@ func wrapPolicyToGoText(p WrapPolicy) shaping.LineBreakPolicy {
|
|||||||
// shapeAndWrapText invokes the text shaper and returns wrapped lines in the shaper's native format.
|
// shapeAndWrapText invokes the text shaper and returns wrapped lines in the shaper's native format.
|
||||||
func (s *shaperImpl) shapeAndWrapText(params Parameters, txt []rune) (_ []shaping.Line, truncated int) {
|
func (s *shaperImpl) shapeAndWrapText(params Parameters, txt []rune) (_ []shaping.Line, truncated int) {
|
||||||
wc := shaping.WrapConfig{
|
wc := shaping.WrapConfig{
|
||||||
TruncateAfterLines: params.MaxLines,
|
Direction: mapDirection(params.Locale.Direction),
|
||||||
TextContinues: params.forceTruncate,
|
TruncateAfterLines: params.MaxLines,
|
||||||
BreakPolicy: wrapPolicyToGoText(params.WrapPolicy),
|
TextContinues: params.forceTruncate,
|
||||||
|
BreakPolicy: wrapPolicyToGoText(params.WrapPolicy),
|
||||||
|
DisableTrailingWhitespaceTrim: params.DisableSpaceTrim,
|
||||||
}
|
}
|
||||||
families := s.defaultFaces
|
families := s.defaultFaces
|
||||||
if params.Font.Typeface != "" {
|
if params.Font.Typeface != "" {
|
||||||
@@ -661,7 +664,7 @@ func (s *shaperImpl) Shape(pathOps *op.Ops, gs []Glyph) clip.PathSpec {
|
|||||||
scaleFactor := fixedToFloat(ppem) / float32(face.Upem())
|
scaleFactor := fixedToFloat(ppem) / float32(face.Upem())
|
||||||
glyphData := face.GlyphData(gid)
|
glyphData := face.GlyphData(gid)
|
||||||
switch glyphData := glyphData.(type) {
|
switch glyphData := glyphData.(type) {
|
||||||
case api.GlyphOutline:
|
case font.GlyphOutline:
|
||||||
outline := glyphData
|
outline := glyphData
|
||||||
// Move to glyph position.
|
// Move to glyph position.
|
||||||
pos := f32.Point{
|
pos := f32.Point{
|
||||||
@@ -676,9 +679,9 @@ func (s *shaperImpl) Shape(pathOps *op.Ops, gs []Glyph) clip.PathSpec {
|
|||||||
for _, fseg := range outline.Segments {
|
for _, fseg := range outline.Segments {
|
||||||
nargs := 1
|
nargs := 1
|
||||||
switch fseg.Op {
|
switch fseg.Op {
|
||||||
case api.SegmentOpQuadTo:
|
case gotextot.SegmentOpQuadTo:
|
||||||
nargs = 2
|
nargs = 2
|
||||||
case api.SegmentOpCubeTo:
|
case gotextot.SegmentOpCubeTo:
|
||||||
nargs = 3
|
nargs = 3
|
||||||
}
|
}
|
||||||
var args [3]f32.Point
|
var args [3]f32.Point
|
||||||
@@ -693,13 +696,13 @@ func (s *shaperImpl) Shape(pathOps *op.Ops, gs []Glyph) clip.PathSpec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch fseg.Op {
|
switch fseg.Op {
|
||||||
case api.SegmentOpMoveTo:
|
case gotextot.SegmentOpMoveTo:
|
||||||
builder.Move(args[0])
|
builder.Move(args[0])
|
||||||
case api.SegmentOpLineTo:
|
case gotextot.SegmentOpLineTo:
|
||||||
builder.Line(args[0])
|
builder.Line(args[0])
|
||||||
case api.SegmentOpQuadTo:
|
case gotextot.SegmentOpQuadTo:
|
||||||
builder.Quad(args[0], args[1])
|
builder.Quad(args[0], args[1])
|
||||||
case api.SegmentOpCubeTo:
|
case gotextot.SegmentOpCubeTo:
|
||||||
builder.Cube(args[0], args[1], args[2])
|
builder.Cube(args[0], args[1], args[2])
|
||||||
default:
|
default:
|
||||||
panic("unsupported segment op")
|
panic("unsupported segment op")
|
||||||
@@ -740,16 +743,16 @@ func (s *shaperImpl) Bitmaps(ops *op.Ops, gs []Glyph) op.CallOp {
|
|||||||
}
|
}
|
||||||
glyphData := face.GlyphData(gid)
|
glyphData := face.GlyphData(gid)
|
||||||
switch glyphData := glyphData.(type) {
|
switch glyphData := glyphData.(type) {
|
||||||
case api.GlyphBitmap:
|
case font.GlyphBitmap:
|
||||||
var imgOp paint.ImageOp
|
var imgOp paint.ImageOp
|
||||||
var imgSize image.Point
|
var imgSize image.Point
|
||||||
bitmapData, ok := s.bitmapGlyphCache.Get(g.ID)
|
bitmapData, ok := s.bitmapGlyphCache.Get(g.ID)
|
||||||
if !ok {
|
if !ok {
|
||||||
var img image.Image
|
var img image.Image
|
||||||
switch glyphData.Format {
|
switch glyphData.Format {
|
||||||
case api.PNG, api.JPG, api.TIFF:
|
case font.PNG, font.JPG, font.TIFF:
|
||||||
img, _, _ = image.Decode(bytes.NewReader(glyphData.Data))
|
img, _, _ = image.Decode(bytes.NewReader(glyphData.Data))
|
||||||
case api.BlackAndWhite:
|
case font.BlackAndWhite:
|
||||||
// This is a complex family of uncompressed bitmaps that don't seem to be
|
// This is a complex family of uncompressed bitmaps that don't seem to be
|
||||||
// very common in practice. We can try adding support later if needed.
|
// very common in practice. We can try adding support later if needed.
|
||||||
fallthrough
|
fallthrough
|
||||||
@@ -805,7 +808,7 @@ type langConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// toInput converts its parameters into a shaping.Input.
|
// toInput converts its parameters into a shaping.Input.
|
||||||
func toInput(face font.Face, ppem fixed.Int26_6, lc langConfig, runes []rune) shaping.Input {
|
func toInput(face *font.Face, ppem fixed.Int26_6, lc langConfig, runes []rune) shaping.Input {
|
||||||
var input shaping.Input
|
var input shaping.Input
|
||||||
input.Direction = lc.Direction
|
input.Direction = lc.Direction
|
||||||
input.Text = runes
|
input.Text = runes
|
||||||
@@ -865,13 +868,14 @@ func toGioGlyphs(in []shaping.Glyph, ppem fixed.Int26_6, faceIdx int) []glyph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// toLine converts the output into a Line with the provided dominant text direction.
|
// toLine converts the output into a Line with the provided dominant text direction.
|
||||||
func toLine(faceToIndex map[font.Font]int, o shaping.Line, dir system.TextDirection) line {
|
func toLine(faceToIndex map[*font.Font]int, o shaping.Line, dir system.TextDirection) line {
|
||||||
if len(o) < 1 {
|
if len(o) < 1 {
|
||||||
return line{}
|
return line{}
|
||||||
}
|
}
|
||||||
line := line{
|
line := line{
|
||||||
runs: make([]runLayout, len(o)),
|
runs: make([]runLayout, len(o)),
|
||||||
direction: dir,
|
direction: dir,
|
||||||
|
visualOrder: make([]int, len(o)),
|
||||||
}
|
}
|
||||||
maxSize := fixed.Int26_6(0)
|
maxSize := fixed.Int26_6(0)
|
||||||
for i := range o {
|
for i := range o {
|
||||||
@@ -879,7 +883,7 @@ func toLine(faceToIndex map[font.Font]int, o shaping.Line, dir system.TextDirect
|
|||||||
if run.Size > maxSize {
|
if run.Size > maxSize {
|
||||||
maxSize = run.Size
|
maxSize = run.Size
|
||||||
}
|
}
|
||||||
var font font.Font
|
var font *font.Font
|
||||||
if run.Face != nil {
|
if run.Face != nil {
|
||||||
font = run.Face.Font
|
font = run.Face.Font
|
||||||
}
|
}
|
||||||
@@ -889,11 +893,13 @@ func toLine(faceToIndex map[font.Font]int, o shaping.Line, dir system.TextDirect
|
|||||||
Count: run.Runes.Count,
|
Count: run.Runes.Count,
|
||||||
Offset: line.runeCount,
|
Offset: line.runeCount,
|
||||||
},
|
},
|
||||||
Direction: unmapDirection(run.Direction),
|
Direction: unmapDirection(run.Direction),
|
||||||
face: run.Face,
|
face: run.Face,
|
||||||
Advance: run.Advance,
|
Advance: run.Advance,
|
||||||
PPEM: run.Size,
|
PPEM: run.Size,
|
||||||
|
VisualPosition: int(run.VisualIndex),
|
||||||
}
|
}
|
||||||
|
line.visualOrder[run.VisualIndex] = i
|
||||||
line.runeCount += run.Runes.Count
|
line.runeCount += run.Runes.Count
|
||||||
line.width += run.Advance
|
line.width += run.Advance
|
||||||
if line.ascent < run.LineBounds.Ascent {
|
if line.ascent < run.LineBounds.Ascent {
|
||||||
@@ -904,64 +910,11 @@ func toLine(faceToIndex map[font.Font]int, o shaping.Line, dir system.TextDirect
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
line.lineHeight = maxSize
|
line.lineHeight = maxSize
|
||||||
computeVisualOrder(&line)
|
|
||||||
return line
|
|
||||||
}
|
|
||||||
|
|
||||||
// computeVisualOrder will populate the Line's VisualOrder field and the
|
|
||||||
// VisualPosition field of each element in Runs.
|
|
||||||
func computeVisualOrder(l *line) {
|
|
||||||
l.visualOrder = make([]int, len(l.runs))
|
|
||||||
const none = -1
|
|
||||||
bidiRangeStart := none
|
|
||||||
|
|
||||||
// visPos returns the visual position for an individual logically-indexed
|
|
||||||
// run in this line, taking only the line's overall text direction into
|
|
||||||
// account.
|
|
||||||
visPos := func(logicalIndex int) int {
|
|
||||||
if l.direction.Progression() == system.TowardOrigin {
|
|
||||||
return len(l.runs) - 1 - logicalIndex
|
|
||||||
}
|
|
||||||
return logicalIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolveBidi populated the line's VisualOrder fields for the elements in the
|
|
||||||
// half-open range [bidiRangeStart:bidiRangeEnd) indicating that those elements
|
|
||||||
// should be displayed in reverse-visual order.
|
|
||||||
resolveBidi := func(bidiRangeStart, bidiRangeEnd int) {
|
|
||||||
firstVisual := bidiRangeEnd - 1
|
|
||||||
// Just found the end of a bidi range.
|
|
||||||
for startIdx := bidiRangeStart; startIdx < bidiRangeEnd; startIdx++ {
|
|
||||||
pos := visPos(firstVisual)
|
|
||||||
l.runs[startIdx].VisualPosition = pos
|
|
||||||
l.visualOrder[pos] = startIdx
|
|
||||||
firstVisual--
|
|
||||||
}
|
|
||||||
bidiRangeStart = none
|
|
||||||
}
|
|
||||||
for runIdx, run := range l.runs {
|
|
||||||
if run.Direction.Progression() != l.direction.Progression() {
|
|
||||||
if bidiRangeStart == none {
|
|
||||||
bidiRangeStart = runIdx
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
} else if bidiRangeStart != none {
|
|
||||||
// Just found the end of a bidi range.
|
|
||||||
resolveBidi(bidiRangeStart, runIdx)
|
|
||||||
bidiRangeStart = none
|
|
||||||
}
|
|
||||||
pos := visPos(runIdx)
|
|
||||||
l.runs[runIdx].VisualPosition = pos
|
|
||||||
l.visualOrder[pos] = runIdx
|
|
||||||
}
|
|
||||||
if bidiRangeStart != none {
|
|
||||||
// We ended iteration within a bidi segment, resolve it.
|
|
||||||
resolveBidi(bidiRangeStart, len(l.runs))
|
|
||||||
}
|
|
||||||
// Iterate and resolve the X of each run.
|
// Iterate and resolve the X of each run.
|
||||||
x := fixed.Int26_6(0)
|
x := fixed.Int26_6(0)
|
||||||
for _, runIdx := range l.visualOrder {
|
for _, runIdx := range line.visualOrder {
|
||||||
l.runs[runIdx].X = x
|
line.runs[runIdx].X = x
|
||||||
x += l.runs[runIdx].Advance
|
x += line.runs[runIdx].Advance
|
||||||
}
|
}
|
||||||
|
return line
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package text
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"reflect"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -450,120 +449,6 @@ func TestToLine(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestComputeVisualOrder(t *testing.T) {
|
|
||||||
type testcase struct {
|
|
||||||
name string
|
|
||||||
input line
|
|
||||||
expectedVisualOrder []int
|
|
||||||
}
|
|
||||||
for _, tc := range []testcase{
|
|
||||||
{
|
|
||||||
name: "ltr",
|
|
||||||
input: line{
|
|
||||||
direction: system.LTR,
|
|
||||||
runs: []runLayout{
|
|
||||||
{Direction: system.LTR},
|
|
||||||
{Direction: system.LTR},
|
|
||||||
{Direction: system.LTR},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedVisualOrder: []int{0, 1, 2},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "rtl",
|
|
||||||
input: line{
|
|
||||||
direction: system.RTL,
|
|
||||||
runs: []runLayout{
|
|
||||||
{Direction: system.RTL},
|
|
||||||
{Direction: system.RTL},
|
|
||||||
{Direction: system.RTL},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedVisualOrder: []int{2, 1, 0},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "bidi-ltr",
|
|
||||||
input: line{
|
|
||||||
direction: system.LTR,
|
|
||||||
runs: []runLayout{
|
|
||||||
{Direction: system.LTR},
|
|
||||||
{Direction: system.RTL},
|
|
||||||
{Direction: system.RTL},
|
|
||||||
{Direction: system.RTL},
|
|
||||||
{Direction: system.LTR},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedVisualOrder: []int{0, 3, 2, 1, 4},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "bidi-ltr-complex",
|
|
||||||
input: line{
|
|
||||||
direction: system.LTR,
|
|
||||||
runs: []runLayout{
|
|
||||||
{Direction: system.RTL},
|
|
||||||
{Direction: system.RTL},
|
|
||||||
{Direction: system.LTR},
|
|
||||||
{Direction: system.RTL},
|
|
||||||
{Direction: system.RTL},
|
|
||||||
{Direction: system.LTR},
|
|
||||||
{Direction: system.RTL},
|
|
||||||
{Direction: system.RTL},
|
|
||||||
{Direction: system.LTR},
|
|
||||||
{Direction: system.RTL},
|
|
||||||
{Direction: system.RTL},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedVisualOrder: []int{1, 0, 2, 4, 3, 5, 7, 6, 8, 10, 9},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "bidi-rtl",
|
|
||||||
input: line{
|
|
||||||
direction: system.RTL,
|
|
||||||
runs: []runLayout{
|
|
||||||
{Direction: system.RTL},
|
|
||||||
{Direction: system.LTR},
|
|
||||||
{Direction: system.LTR},
|
|
||||||
{Direction: system.LTR},
|
|
||||||
{Direction: system.RTL},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedVisualOrder: []int{4, 1, 2, 3, 0},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "bidi-rtl-complex",
|
|
||||||
input: line{
|
|
||||||
direction: system.RTL,
|
|
||||||
runs: []runLayout{
|
|
||||||
{Direction: system.LTR},
|
|
||||||
{Direction: system.LTR},
|
|
||||||
{Direction: system.RTL},
|
|
||||||
{Direction: system.LTR},
|
|
||||||
{Direction: system.LTR},
|
|
||||||
{Direction: system.RTL},
|
|
||||||
{Direction: system.LTR},
|
|
||||||
{Direction: system.LTR},
|
|
||||||
{Direction: system.RTL},
|
|
||||||
{Direction: system.LTR},
|
|
||||||
{Direction: system.LTR},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedVisualOrder: []int{9, 10, 8, 6, 7, 5, 3, 4, 2, 0, 1},
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
computeVisualOrder(&tc.input)
|
|
||||||
if !reflect.DeepEqual(tc.input.visualOrder, tc.expectedVisualOrder) {
|
|
||||||
t.Errorf("expected visual order %v, got %v", tc.expectedVisualOrder, tc.input.visualOrder)
|
|
||||||
}
|
|
||||||
for i, visualIndex := range tc.input.visualOrder {
|
|
||||||
if pos := tc.input.runs[visualIndex].VisualPosition; pos != i {
|
|
||||||
t.Errorf("line.VisualOrder[%d]=%d, but line.Runs[%d].VisualPosition=%d", i, visualIndex, visualIndex, pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func FuzzLayout(f *testing.F) {
|
func FuzzLayout(f *testing.F) {
|
||||||
ltrFace, _ := opentype.Parse(goregular.TTF)
|
ltrFace, _ := opentype.Parse(goregular.TTF)
|
||||||
rtlFace, _ := opentype.Parse(nsareg.TTF)
|
rtlFace, _ := opentype.Parse(nsareg.TTF)
|
||||||
|
|||||||
+14
-1
@@ -76,6 +76,11 @@ type Parameters struct {
|
|||||||
// text with a MaxLines. It is unexported because this behavior only makes sense for the
|
// text with a MaxLines. It is unexported because this behavior only makes sense for the
|
||||||
// shaper to control when it iterates paragraphs of text.
|
// shaper to control when it iterates paragraphs of text.
|
||||||
forceTruncate bool
|
forceTruncate bool
|
||||||
|
|
||||||
|
// DisableSpaceTrim prevents the width of the final whitespace glyph on a line from being zeroed.
|
||||||
|
// This is desirable for text editors (so that the whitespace can be selected), but is undesirable
|
||||||
|
// for ordinary display text.
|
||||||
|
DisableSpaceTrim bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type FontFace = giofont.FontFace
|
type FontFace = giofont.FontFace
|
||||||
@@ -199,7 +204,15 @@ func (f Flags) String() string {
|
|||||||
|
|
||||||
type GlyphID uint64
|
type GlyphID uint64
|
||||||
|
|
||||||
// Shaper converts strings of text into glyphs that can be displayed.
|
// Shaper converts strings of text into glyphs that can be displayed. The same
|
||||||
|
// Shaper should not be used in different goroutines.
|
||||||
|
//
|
||||||
|
// The Shaper controls text layout and has a cache, implemented as a map, and
|
||||||
|
// so laying out text in two different goroutines can easily result in
|
||||||
|
// concurrent access to said map, resulting in a panic.
|
||||||
|
//
|
||||||
|
// Practically speaking, this means you should use different Shapers for
|
||||||
|
// different top-level windows.
|
||||||
type Shaper struct {
|
type Shaper struct {
|
||||||
config struct {
|
config struct {
|
||||||
disableSystemFonts bool
|
disableSystemFonts bool
|
||||||
|
|||||||
+6
-23
@@ -11,8 +11,11 @@ import (
|
|||||||
|
|
||||||
// Decorations handles the states of window decorations.
|
// Decorations handles the states of window decorations.
|
||||||
type Decorations struct {
|
type Decorations struct {
|
||||||
|
// Maximized controls the look and behaviour of the maximize
|
||||||
|
// button. It is the user's responsibility to set Maximized
|
||||||
|
// according to the window state reported through [app.ConfigEvent].
|
||||||
|
Maximized bool
|
||||||
clicks map[int]*Clickable
|
clicks map[int]*Clickable
|
||||||
maximized bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LayoutMove lays out the widget that makes a window movable.
|
// LayoutMove lays out the widget that makes a window movable.
|
||||||
@@ -40,17 +43,6 @@ func (d *Decorations) Clickable(action system.Action) *Clickable {
|
|||||||
return click
|
return click
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform updates the decorations as if the specified actions were
|
|
||||||
// performed by the user.
|
|
||||||
func (d *Decorations) Perform(actions system.Action) {
|
|
||||||
if actions&system.ActionMaximize != 0 {
|
|
||||||
d.maximized = true
|
|
||||||
}
|
|
||||||
if actions&(system.ActionUnmaximize|system.ActionMinimize|system.ActionFullscreen) != 0 {
|
|
||||||
d.maximized = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the state and return the set of actions activated by the user.
|
// Update the state and return the set of actions activated by the user.
|
||||||
func (d *Decorations) Update(gtx layout.Context) system.Action {
|
func (d *Decorations) Update(gtx layout.Context) system.Action {
|
||||||
var actions system.Action
|
var actions system.Action
|
||||||
@@ -60,21 +52,12 @@ func (d *Decorations) Update(gtx layout.Context) system.Action {
|
|||||||
}
|
}
|
||||||
action := system.Action(1 << idx)
|
action := system.Action(1 << idx)
|
||||||
switch {
|
switch {
|
||||||
case action == system.ActionMaximize && d.maximized:
|
case action == system.ActionMaximize && d.Maximized:
|
||||||
action = system.ActionUnmaximize
|
action = system.ActionUnmaximize
|
||||||
case action == system.ActionUnmaximize && !d.maximized:
|
case action == system.ActionUnmaximize && !d.Maximized:
|
||||||
action = system.ActionMaximize
|
action = system.ActionMaximize
|
||||||
}
|
}
|
||||||
switch action {
|
|
||||||
case system.ActionMaximize, system.ActionUnmaximize:
|
|
||||||
d.maximized = !d.maximized
|
|
||||||
}
|
|
||||||
actions |= action
|
actions |= action
|
||||||
}
|
}
|
||||||
return actions
|
return actions
|
||||||
}
|
}
|
||||||
|
|
||||||
// Maximized returns whether the window is maximized.
|
|
||||||
func (d *Decorations) Maximized() bool {
|
|
||||||
return d.maximized
|
|
||||||
}
|
|
||||||
|
|||||||
+21
-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
|
||||||
}
|
}
|
||||||
@@ -603,6 +610,7 @@ func (e *Editor) initBuffer() {
|
|||||||
e.text.SingleLine = e.SingleLine
|
e.text.SingleLine = e.SingleLine
|
||||||
e.text.Mask = e.Mask
|
e.text.Mask = e.Mask
|
||||||
e.text.WrapPolicy = e.WrapPolicy
|
e.text.WrapPolicy = e.WrapPolicy
|
||||||
|
e.text.DisableSpaceTrim = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the state of the editor in response to input events. Update consumes editor
|
// Update the state of the editor in response to input events. Update consumes editor
|
||||||
|
|||||||
+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)
|
||||||
|
|||||||
+65
-47
@@ -2,12 +2,18 @@ package widget
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"image"
|
||||||
|
"image/png"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
nsareg "eliasnaur.com/font/noto/sans/arabic/regular"
|
nsareg "eliasnaur.com/font/noto/sans/arabic/regular"
|
||||||
"gioui.org/font"
|
"gioui.org/font"
|
||||||
"gioui.org/font/opentype"
|
"gioui.org/font/opentype"
|
||||||
|
"gioui.org/gpu/headless"
|
||||||
|
"gioui.org/layout"
|
||||||
|
"gioui.org/op"
|
||||||
"gioui.org/text"
|
"gioui.org/text"
|
||||||
"golang.org/x/image/font/gofont/goregular"
|
"golang.org/x/image/font/gofont/goregular"
|
||||||
"golang.org/x/image/math/fixed"
|
"golang.org/x/image/math/fixed"
|
||||||
@@ -16,11 +22,11 @@ import (
|
|||||||
// makePosTestText returns two bidi samples of shaped text at the given
|
// makePosTestText returns two bidi samples of shaped text at the given
|
||||||
// font size and wrapped to the given line width. The runeLimit, if nonzero,
|
// font size and wrapped to the given line width. The runeLimit, if nonzero,
|
||||||
// truncates the sample text to ensure shorter output for expensive tests.
|
// truncates the sample text to ensure shorter output for expensive tests.
|
||||||
func makePosTestText(fontSize, lineWidth int, alignOpposite bool) (source string, bidiLTR, bidiRTL []text.Glyph) {
|
func makePosTestText(fontSize, lineWidth int, alignOpposite bool) (shaper *text.Shaper, source string, bidiLTR, bidiRTL []text.Glyph) {
|
||||||
ltrFace, _ := opentype.Parse(goregular.TTF)
|
ltrFace, _ := opentype.Parse(goregular.TTF)
|
||||||
rtlFace, _ := opentype.Parse(nsareg.TTF)
|
rtlFace, _ := opentype.Parse(nsareg.TTF)
|
||||||
|
|
||||||
shaper := text.NewShaper(text.NoSystemFonts(), text.WithCollection([]font.FontFace{
|
shaper = text.NewShaper(text.NoSystemFonts(), text.WithCollection([]font.FontFace{
|
||||||
{
|
{
|
||||||
Font: font.Font{Typeface: "LTR"},
|
Font: font.Font{Typeface: "LTR"},
|
||||||
Face: ltrFace,
|
Face: ltrFace,
|
||||||
@@ -58,7 +64,7 @@ func makePosTestText(fontSize, lineWidth int, alignOpposite bool) (source string
|
|||||||
for g, ok := shaper.NextGlyph(); ok; g, ok = shaper.NextGlyph() {
|
for g, ok := shaper.NextGlyph(); ok; g, ok = shaper.NextGlyph() {
|
||||||
bidiRTL = append(bidiRTL, g)
|
bidiRTL = append(bidiRTL, g)
|
||||||
}
|
}
|
||||||
return bidiSource, bidiLTR, bidiRTL
|
return shaper, bidiSource, bidiLTR, bidiRTL
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeAccountingTestText shapes text designed to stress rune accounting
|
// makeAccountingTestText shapes text designed to stress rune accounting
|
||||||
@@ -260,7 +266,7 @@ func TestIndexPositionWhitespace(t *testing.T) {
|
|||||||
func TestIndexPositionBidi(t *testing.T) {
|
func TestIndexPositionBidi(t *testing.T) {
|
||||||
fontSize := 16
|
fontSize := 16
|
||||||
lineWidth := fontSize * 10
|
lineWidth := fontSize * 10
|
||||||
_, bidiLTRText, bidiRTLText := makePosTestText(fontSize, lineWidth, false)
|
shaper, _, bidiLTRText, bidiRTLText := makePosTestText(fontSize, lineWidth, false)
|
||||||
type testcase struct {
|
type testcase struct {
|
||||||
name string
|
name string
|
||||||
glyphs []text.Glyph
|
glyphs []text.Glyph
|
||||||
@@ -284,13 +290,12 @@ 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.
|
// Line 0
|
||||||
|
5718, 6344, 6914, 7484, 7769, 8339, 8909, 9162, 9674, 10186, 5718, 5452, 4649, 4057, 3759, 3338, 3072, 2304, 1536, 768, 0,
|
||||||
7886, 7118, 6350, 5582, 4814, 4529, 4231, 3933, 3667, 2300, 2585, 3155, 3667, 2300, 2015, 1709, 1117, 266, // Positions on line 1.
|
// Line 1
|
||||||
|
9170, 8872, 8574, 8308, 6941, 7226, 7796, 8308, 6941, 6675, 6369, 5777, 4926, 4660, 3892, 3124, 2356, 1588, 1303, 788, 406, 0,
|
||||||
8794, 8026, 7258, 6490, 5722, 5437, 4922, 4540, 4134, 3868, 0, 290, 860, 1430, 1715, 1989, 2559, 3071, 3583, // Positions on line 2.
|
// Line 2
|
||||||
|
324, 614, 1184, 1754, 2039, 2313, 2883, 3395, 3907, 4192, 4762, 5332, 5902, 324, 0,
|
||||||
324, 894, 1464, 2034, 324, 0, // Positions on line 3.
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
@@ -337,6 +342,35 @@ func TestIndexPositionBidi(t *testing.T) {
|
|||||||
printPositions(t, gi.positions)
|
printPositions(t, gi.positions)
|
||||||
if t.Failed() {
|
if t.Failed() {
|
||||||
printGlyphs(t, tc.glyphs)
|
printGlyphs(t, tc.glyphs)
|
||||||
|
width := lineWidth
|
||||||
|
height := 100
|
||||||
|
cap := image.NewRGBA(image.Rect(0, 0, width, height))
|
||||||
|
w, _ := headless.NewWindow(width, height)
|
||||||
|
defer w.Release()
|
||||||
|
ops := new(op.Ops)
|
||||||
|
gtx := layout.Context{
|
||||||
|
Constraints: layout.Constraints{Max: image.Pt(width, height)},
|
||||||
|
Ops: ops,
|
||||||
|
}
|
||||||
|
it := textIterator{viewport: image.Rectangle{Max: image.Point{X: width, Y: height}}}
|
||||||
|
for _, g := range tc.glyphs {
|
||||||
|
it.processGlyph(g, true)
|
||||||
|
}
|
||||||
|
var glyphs [32]text.Glyph
|
||||||
|
line := glyphs[:0]
|
||||||
|
for _, g := range gi.glyphs {
|
||||||
|
var ok bool
|
||||||
|
if line, ok = it.paintGlyph(gtx, shaper, g, line); !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Frame(ops)
|
||||||
|
w.Screenshot(cap)
|
||||||
|
b := new(bytes.Buffer)
|
||||||
|
_ = png.Encode(b, cap)
|
||||||
|
screenshotName := tc.name + ".png"
|
||||||
|
_ = os.WriteFile(screenshotName, b.Bytes(), 0o644)
|
||||||
|
t.Logf("wrote %q", screenshotName)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -345,8 +379,8 @@ func TestIndexPositionBidi(t *testing.T) {
|
|||||||
func TestIndexPositionLines(t *testing.T) {
|
func TestIndexPositionLines(t *testing.T) {
|
||||||
fontSize := 16
|
fontSize := 16
|
||||||
lineWidth := fontSize * 10
|
lineWidth := fontSize * 10
|
||||||
source1, bidiLTRText, bidiRTLText := makePosTestText(fontSize, lineWidth, false)
|
_, source1, bidiLTRText, bidiRTLText := makePosTestText(fontSize, lineWidth, false)
|
||||||
source2, bidiLTRTextOpp, bidiRTLTextOpp := makePosTestText(fontSize, lineWidth, true)
|
_, source2, bidiLTRTextOpp, bidiRTLTextOpp := makePosTestText(fontSize, lineWidth, true)
|
||||||
type testcase struct {
|
type testcase struct {
|
||||||
name string
|
name string
|
||||||
source string
|
source string
|
||||||
@@ -379,7 +413,7 @@ func TestIndexPositionLines(t *testing.T) {
|
|||||||
xOff: fixed.Int26_6(0),
|
xOff: fixed.Int26_6(0),
|
||||||
yOff: 60,
|
yOff: 60,
|
||||||
glyphs: 18,
|
glyphs: 18,
|
||||||
width: fixed.Int26_6(8813),
|
width: fixed.Int26_6(8528),
|
||||||
ascent: fixed.Int26_6(1407),
|
ascent: fixed.Int26_6(1407),
|
||||||
descent: fixed.Int26_6(756),
|
descent: fixed.Int26_6(756),
|
||||||
},
|
},
|
||||||
@@ -401,32 +435,24 @@ func TestIndexPositionLines(t *testing.T) {
|
|||||||
{
|
{
|
||||||
xOff: fixed.Int26_6(0),
|
xOff: fixed.Int26_6(0),
|
||||||
yOff: 22,
|
yOff: 22,
|
||||||
glyphs: 15,
|
glyphs: 20,
|
||||||
width: fixed.Int26_6(7133),
|
width: fixed.Int26_6(10186),
|
||||||
ascent: fixed.Int26_6(1407),
|
ascent: fixed.Int26_6(1407),
|
||||||
descent: fixed.Int26_6(756),
|
descent: fixed.Int26_6(756),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
xOff: fixed.Int26_6(0),
|
xOff: fixed.Int26_6(0),
|
||||||
yOff: 41,
|
yOff: 41,
|
||||||
glyphs: 15,
|
glyphs: 19,
|
||||||
width: fixed.Int26_6(7886),
|
width: fixed.Int26_6(9170),
|
||||||
ascent: fixed.Int26_6(1407),
|
ascent: fixed.Int26_6(1407),
|
||||||
descent: fixed.Int26_6(756),
|
descent: fixed.Int26_6(756),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
xOff: fixed.Int26_6(0),
|
xOff: fixed.Int26_6(0),
|
||||||
yOff: 60,
|
yOff: 60,
|
||||||
glyphs: 18,
|
glyphs: 13,
|
||||||
width: fixed.Int26_6(8794),
|
width: fixed.Int26_6(5902),
|
||||||
ascent: fixed.Int26_6(1407),
|
|
||||||
descent: fixed.Int26_6(756),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
xOff: fixed.Int26_6(0),
|
|
||||||
yOff: 79,
|
|
||||||
glyphs: 4,
|
|
||||||
width: fixed.Int26_6(2034),
|
|
||||||
ascent: fixed.Int26_6(968),
|
ascent: fixed.Int26_6(968),
|
||||||
descent: fixed.Int26_6(216),
|
descent: fixed.Int26_6(216),
|
||||||
},
|
},
|
||||||
@@ -454,10 +480,10 @@ func TestIndexPositionLines(t *testing.T) {
|
|||||||
descent: fixed.Int26_6(756),
|
descent: fixed.Int26_6(756),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
xOff: fixed.Int26_6(1427),
|
xOff: fixed.Int26_6(1712),
|
||||||
yOff: 60,
|
yOff: 60,
|
||||||
glyphs: 18,
|
glyphs: 18,
|
||||||
width: fixed.Int26_6(8813),
|
width: fixed.Int26_6(8528),
|
||||||
ascent: fixed.Int26_6(1407),
|
ascent: fixed.Int26_6(1407),
|
||||||
descent: fixed.Int26_6(756),
|
descent: fixed.Int26_6(756),
|
||||||
},
|
},
|
||||||
@@ -477,34 +503,26 @@ func TestIndexPositionLines(t *testing.T) {
|
|||||||
glyphs: bidiRTLTextOpp,
|
glyphs: bidiRTLTextOpp,
|
||||||
expectedLines: []lineInfo{
|
expectedLines: []lineInfo{
|
||||||
{
|
{
|
||||||
xOff: fixed.Int26_6(3107),
|
xOff: fixed.Int26_6(54),
|
||||||
yOff: 22,
|
yOff: 22,
|
||||||
glyphs: 15,
|
glyphs: 20,
|
||||||
width: fixed.Int26_6(7133),
|
width: fixed.Int26_6(10186),
|
||||||
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(1070),
|
||||||
yOff: 41,
|
yOff: 41,
|
||||||
glyphs: 15,
|
glyphs: 19,
|
||||||
width: fixed.Int26_6(7886),
|
width: fixed.Int26_6(9170),
|
||||||
ascent: fixed.Int26_6(1407),
|
ascent: fixed.Int26_6(1407),
|
||||||
descent: fixed.Int26_6(756),
|
descent: fixed.Int26_6(756),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
xOff: fixed.Int26_6(1446),
|
xOff: fixed.Int26_6(4338),
|
||||||
yOff: 60,
|
yOff: 60,
|
||||||
glyphs: 18,
|
glyphs: 13,
|
||||||
width: fixed.Int26_6(8794),
|
width: fixed.Int26_6(5902),
|
||||||
ascent: fixed.Int26_6(1407),
|
|
||||||
descent: fixed.Int26_6(756),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
xOff: fixed.Int26_6(8206),
|
|
||||||
yOff: 79,
|
|
||||||
glyphs: 4,
|
|
||||||
width: fixed.Int26_6(2034),
|
|
||||||
ascent: fixed.Int26_6(968),
|
ascent: fixed.Int26_6(968),
|
||||||
descent: fixed.Int26_6(216),
|
descent: fixed.Int26_6(216),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ func (d DecorationsStyle) layoutDecorations(gtx layout.Context) layout.Dimension
|
|||||||
case system.ActionMinimize:
|
case system.ActionMinimize:
|
||||||
w = minimizeWindow
|
w = minimizeWindow
|
||||||
case system.ActionMaximize:
|
case system.ActionMaximize:
|
||||||
if d.Decorations.Maximized() {
|
if d.Decorations.Maximized {
|
||||||
w = maximizedWindow
|
w = maximizedWindow
|
||||||
} else {
|
} else {
|
||||||
w = maximizeWindow
|
w = maximizeWindow
|
||||||
|
|||||||
@@ -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)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ type Palette struct {
|
|||||||
ContrastFg color.NRGBA
|
ContrastFg color.NRGBA
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Theme holds the general theme of an app or window. Different top-level
|
||||||
|
// windows should have different instances of Theme (with different Shapers;
|
||||||
|
// see the godoc for [text.Shaper]), though their other fields can be equal.
|
||||||
type Theme struct {
|
type Theme struct {
|
||||||
Shaper *text.Shaper
|
Shaper *text.Shaper
|
||||||
Palette
|
Palette
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+30
-4
@@ -61,6 +61,9 @@ type textView struct {
|
|||||||
Truncator string
|
Truncator string
|
||||||
// WrapPolicy configures how displayed text will be broken into lines.
|
// WrapPolicy configures how displayed text will be broken into lines.
|
||||||
WrapPolicy text.WrapPolicy
|
WrapPolicy text.WrapPolicy
|
||||||
|
// DisableSpaceTrim configures whether trailing whitespace on a line will have its
|
||||||
|
// width zeroed. Set to true for editors, but false for non-editable text.
|
||||||
|
DisableSpaceTrim bool
|
||||||
// Mask replaces the visual display of each rune in the contents with the given rune.
|
// Mask replaces the visual display of each rune in the contents with the given rune.
|
||||||
// Newline characters are not masked. When non-zero, the unmasked contents
|
// Newline characters are not masked. When non-zero, the unmasked contents
|
||||||
// are accessed by Len, Text, and SetText.
|
// are accessed by Len, Text, and SetText.
|
||||||
@@ -285,6 +288,10 @@ func (e *textView) Layout(gtx layout.Context, lt *text.Shaper, font font.Font, s
|
|||||||
e.params.LineHeightScale = e.LineHeightScale
|
e.params.LineHeightScale = e.LineHeightScale
|
||||||
e.invalidate()
|
e.invalidate()
|
||||||
}
|
}
|
||||||
|
if e.DisableSpaceTrim != e.params.DisableSpaceTrim {
|
||||||
|
e.params.DisableSpaceTrim = e.DisableSpaceTrim
|
||||||
|
e.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
e.makeValid()
|
e.makeValid()
|
||||||
|
|
||||||
@@ -639,9 +646,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 +676,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