mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-04 08:55:35 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 718be79d9e | |||
| 0073e1a167 | |||
| 32ecec5538 | |||
| 6eb33b8a56 | |||
| 4617526e12 | |||
| dbc7a900bd | |||
| fb3ae95b28 |
+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.24.2.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
curl -s https://dl.google.com/go/go1.19.11.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
|
||||||
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 ./...
|
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/latest
|
image: freebsd/13.x
|
||||||
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.24.2.freebsd-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
curl https://dl.google.com/go/go1.19.11.freebsd-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||||
- test_gio: |
|
- test_gio: |
|
||||||
cd gio
|
cd gio
|
||||||
go test ./...
|
go test ./...
|
||||||
|
|||||||
+1
-3
@@ -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.24.2.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
curl -s https://dl.google.com/go/go1.19.11.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,8 +80,6 @@ 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.24.2.src.tar.gz | tar -C /home/build/sdk -xzf -
|
curl https://dl.google.com/go/go1.19.11.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: |
|
||||||
|
|||||||
+1
-5
@@ -65,8 +65,8 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal
|
|||||||
private final InputMethodManager imm;
|
private final InputMethodManager imm;
|
||||||
private final float scrollXScale;
|
private final float scrollXScale;
|
||||||
private final float scrollYScale;
|
private final float scrollYScale;
|
||||||
private final AccessibilityManager accessManager;
|
|
||||||
private int keyboardHint;
|
private int keyboardHint;
|
||||||
|
private AccessibilityManager accessManager;
|
||||||
|
|
||||||
private long nhandle;
|
private long nhandle;
|
||||||
|
|
||||||
@@ -259,10 +259,6 @@ 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();
|
||||||
|
|||||||
+9
-93
@@ -3,16 +3,9 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"image"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"gioui.org/io/input"
|
|
||||||
"gioui.org/layout"
|
|
||||||
"gioui.org/op"
|
|
||||||
"gioui.org/unit"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// extraArgs contains extra arguments to append to
|
// extraArgs contains extra arguments to append to
|
||||||
@@ -27,88 +20,23 @@ var extraArgs string
|
|||||||
// On Android ID is the package property of AndroidManifest.xml,
|
// On Android ID is the package property of AndroidManifest.xml,
|
||||||
// on iOS ID is the CFBundleIdentifier of the app Info.plist,
|
// on iOS ID is the CFBundleIdentifier of the app Info.plist,
|
||||||
// on Wayland it is the toplevel app_id,
|
// on Wayland it is the toplevel app_id,
|
||||||
// on X11 it is the X11 XClassHint.
|
// on X11 it is the X11 XClassHint
|
||||||
//
|
//
|
||||||
// ID is set by the [gioui.org/cmd/gogio] tool or manually with the -X linker flag. For example,
|
// ID is set by the gogio tool or manually with the -X linker flag. For example,
|
||||||
//
|
//
|
||||||
// go build -ldflags="-X 'gioui.org/app.ID=org.gioui.example.Kitchen'" .
|
// go build -ldflags="-X 'gioui.org/app.ID=org.gioui.example.Kitchen'" .
|
||||||
//
|
//
|
||||||
// Note that ID is treated as a constant, and that changing it at runtime
|
// Note that ID is treated as a constant, and that changing it at runtime
|
||||||
// is not supported. The default value of ID is filepath.Base(os.Args[0]).
|
// is not supported. Default value of ID is filepath.Base(os.Args[0]).
|
||||||
var ID = ""
|
var ID = ""
|
||||||
|
|
||||||
// A FrameEvent requests a new frame in the form of a list of
|
func init() {
|
||||||
// operations that describes the window content.
|
if extraArgs != "" {
|
||||||
type FrameEvent struct {
|
args := strings.Split(extraArgs, "|")
|
||||||
// Now is the current animation. Use Now instead of time.Now to
|
os.Args = append(os.Args, args...)
|
||||||
// synchronize animation and to avoid the time.Now call overhead.
|
|
||||||
Now time.Time
|
|
||||||
// Metric converts device independent dp and sp to device pixels.
|
|
||||||
Metric unit.Metric
|
|
||||||
// Size is the dimensions of the window.
|
|
||||||
Size image.Point
|
|
||||||
// Insets represent the space occupied by system decorations and controls.
|
|
||||||
Insets Insets
|
|
||||||
// Frame completes the FrameEvent by drawing the graphical operations
|
|
||||||
// from ops into the window.
|
|
||||||
Frame func(frame *op.Ops)
|
|
||||||
// Source is the interface between the window and widgets.
|
|
||||||
Source input.Source
|
|
||||||
}
|
|
||||||
|
|
||||||
// ViewEvent provides handles to the underlying window objects for the
|
|
||||||
// current display protocol.
|
|
||||||
type ViewEvent interface {
|
|
||||||
implementsViewEvent()
|
|
||||||
ImplementsEvent()
|
|
||||||
// Valid will return true when the ViewEvent does contains valid handles.
|
|
||||||
// If a window receives an invalid ViewEvent, it should deinitialize any
|
|
||||||
// state referring to handles from a previous ViewEvent.
|
|
||||||
Valid() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insets is the space taken up by
|
|
||||||
// system decoration such as translucent
|
|
||||||
// system bars and software keyboards.
|
|
||||||
type Insets struct {
|
|
||||||
// Values are in pixels.
|
|
||||||
Top, Bottom, Left, Right unit.Dp
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewContext is shorthand for
|
|
||||||
//
|
|
||||||
// layout.Context{
|
|
||||||
// Ops: ops,
|
|
||||||
// Now: e.Now,
|
|
||||||
// Source: e.Source,
|
|
||||||
// Metric: e.Metric,
|
|
||||||
// Constraints: layout.Exact(e.Size),
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// NewContext calls ops.Reset and adjusts ops for e.Insets.
|
|
||||||
func NewContext(ops *op.Ops, e FrameEvent) layout.Context {
|
|
||||||
ops.Reset()
|
|
||||||
|
|
||||||
size := e.Size
|
|
||||||
|
|
||||||
if e.Insets != (Insets{}) {
|
|
||||||
left := e.Metric.Dp(e.Insets.Left)
|
|
||||||
top := e.Metric.Dp(e.Insets.Top)
|
|
||||||
op.Offset(image.Point{
|
|
||||||
X: left,
|
|
||||||
Y: top,
|
|
||||||
}).Add(ops)
|
|
||||||
|
|
||||||
size.X -= left + e.Metric.Dp(e.Insets.Right)
|
|
||||||
size.Y -= top + e.Metric.Dp(e.Insets.Bottom)
|
|
||||||
}
|
}
|
||||||
|
if ID == "" {
|
||||||
return layout.Context{
|
ID = filepath.Base(os.Args[0])
|
||||||
Ops: ops,
|
|
||||||
Now: e.Now,
|
|
||||||
Source: e.Source,
|
|
||||||
Metric: e.Metric,
|
|
||||||
Constraints: layout.Exact(size),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,15 +63,3 @@ func DataDir() (string, error) {
|
|||||||
func Main() {
|
func Main() {
|
||||||
osMain()
|
osMain()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (FrameEvent) ImplementsEvent() {}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
if extraArgs != "" {
|
|
||||||
args := strings.Split(extraArgs, "|")
|
|
||||||
os.Args = append(os.Args, args...)
|
|
||||||
}
|
|
||||||
if ID == "" {
|
|
||||||
ID = filepath.Base(os.Args[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+18
-11
@@ -8,20 +8,21 @@ See https://gioui.org for instructions to set up and run Gio programs.
|
|||||||
|
|
||||||
# Windows
|
# Windows
|
||||||
|
|
||||||
A Window is run by calling its Event method in a loop. The first time a
|
Create a new Window by calling NewWindow. On mobile platforms or when Gio
|
||||||
method on Window is called, a new GUI window is created and shown. On mobile
|
is embedded in another project, NewWindow merely connects with a previously
|
||||||
platforms or when Gio is embedded in another project, Window merely connects
|
created window.
|
||||||
with a previously created GUI window.
|
|
||||||
|
|
||||||
The most important event is [FrameEvent] that prompts an update of the window
|
A Window is run by calling NextEvent in a loop. The most important event is
|
||||||
contents.
|
FrameEvent that prompts an update of the window contents.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
w := new(app.Window)
|
import "gioui.org/unit"
|
||||||
|
|
||||||
|
w := app.NewWindow()
|
||||||
for {
|
for {
|
||||||
e := w.Event()
|
e := w.NextEvent()
|
||||||
if e, ok := e.(app.FrameEvent); ok {
|
if e, ok := e.(system.FrameEvent); ok {
|
||||||
ops.Reset()
|
ops.Reset()
|
||||||
// Add operations to ops.
|
// Add operations to ops.
|
||||||
...
|
...
|
||||||
@@ -31,7 +32,7 @@ For example:
|
|||||||
}
|
}
|
||||||
|
|
||||||
A program must keep receiving events from the event channel until
|
A program must keep receiving events from the event channel until
|
||||||
[DestroyEvent] is received.
|
DestroyEvent is received.
|
||||||
|
|
||||||
# Main
|
# Main
|
||||||
|
|
||||||
@@ -50,12 +51,18 @@ For example, to display a blank but otherwise functional window:
|
|||||||
go func() {
|
go func() {
|
||||||
w := app.NewWindow()
|
w := app.NewWindow()
|
||||||
for {
|
for {
|
||||||
w.Event()
|
w.NextEvent()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
app.Main()
|
app.Main()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Event queue
|
||||||
|
|
||||||
|
A FrameEvent's Queue method returns an event.Queue implementation that distributes
|
||||||
|
incoming events to the event handlers declared in the last frame.
|
||||||
|
See the gioui.org/io/event package for more information about event handlers.
|
||||||
|
|
||||||
# Permissions
|
# Permissions
|
||||||
|
|
||||||
The packages under gioui.org/app/permission should be imported
|
The packages under gioui.org/app/permission should be imported
|
||||||
|
|||||||
+6
-4
@@ -17,8 +17,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type androidContext struct {
|
type androidContext struct {
|
||||||
win *window
|
win *window
|
||||||
eglSurf egl.NativeWindowType
|
eglSurf egl.NativeWindowType
|
||||||
|
width, height int
|
||||||
*egl.Context
|
*egl.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,8 +45,9 @@ 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, _, _ := c.win.nativeWindow()
|
win, width, height := 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +55,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); err != nil {
|
if err := c.Context.CreateSurface(c.eglSurf, c.width, c.height); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.eglSurf = nil
|
c.eglSurf = nil
|
||||||
|
|||||||
+13
-20
@@ -38,24 +38,7 @@ func init() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return &wlContext{Context: ctx, win: w}, nil
|
||||||
surf, width, height := w.surface()
|
|
||||||
if surf == nil {
|
|
||||||
return nil, errors.New("wayland: no surface")
|
|
||||||
}
|
|
||||||
eglWin := C.wl_egl_window_create(surf, C.int(width), C.int(height))
|
|
||||||
if eglWin == nil {
|
|
||||||
return nil, errors.New("wayland: wl_egl_window_create failed")
|
|
||||||
}
|
|
||||||
eglSurf := egl.NativeWindowType(uintptr(unsafe.Pointer(eglWin)))
|
|
||||||
if err := ctx.CreateSurface(eglSurf); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// We're in charge of the frame callbacks, don't let eglSwapBuffers
|
|
||||||
// wait for callbacks that may never arrive.
|
|
||||||
ctx.EnableVSync(false)
|
|
||||||
|
|
||||||
return &wlContext{Context: ctx, win: w, eglWin: eglWin}, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,12 +54,22 @@ func (c *wlContext) Release() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *wlContext) Refresh() error {
|
func (c *wlContext) Refresh() error {
|
||||||
|
c.Context.ReleaseSurface()
|
||||||
|
if c.eglWin != nil {
|
||||||
|
C.wl_egl_window_destroy(c.eglWin)
|
||||||
|
c.eglWin = nil
|
||||||
|
}
|
||||||
surf, width, height := c.win.surface()
|
surf, width, height := c.win.surface()
|
||||||
if surf == nil {
|
if surf == nil {
|
||||||
return errors.New("wayland: no surface")
|
return errors.New("wayland: no surface")
|
||||||
}
|
}
|
||||||
C.wl_egl_window_resize(c.eglWin, C.int(width), C.int(height), 0, 0)
|
eglWin := C.wl_egl_window_create(surf, C.int(width), C.int(height))
|
||||||
return nil
|
if eglWin == nil {
|
||||||
|
return errors.New("wayland: wl_egl_window_create failed")
|
||||||
|
}
|
||||||
|
c.eglWin = eglWin
|
||||||
|
eglSurf := egl.NativeWindowType(uintptr(unsafe.Pointer(eglWin)))
|
||||||
|
return c.Context.CreateSurface(eglSurf, width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *wlContext) Lock() error {
|
func (c *wlContext) Lock() error {
|
||||||
|
|||||||
+17
-12
@@ -5,6 +5,8 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
|
||||||
"gioui.org/internal/egl"
|
"gioui.org/internal/egl"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -22,18 +24,6 @@ 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
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -47,6 +37,21 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-12
@@ -25,18 +25,6 @@ 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,6 +37,17 @@ 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
|
||||||
|
}
|
||||||
|
c.Context.EnableVSync(true)
|
||||||
|
c.Context.ReleaseCurrent()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ package app
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"gioui.org/gpu"
|
"gioui.org/gpu"
|
||||||
|
|||||||
+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(BOOL presentWithTrans) {
|
CALayer *gio_layerFactory(void) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
return [CALayer layer];
|
return [CALayer layer];
|
||||||
}
|
}
|
||||||
|
|||||||
-145
@@ -1,145 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"unicode"
|
|
||||||
"unicode/utf16"
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"gioui.org/io/input"
|
|
||||||
"gioui.org/io/key"
|
|
||||||
)
|
|
||||||
|
|
||||||
type editorState struct {
|
|
||||||
input.EditorState
|
|
||||||
compose key.Range
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *editorState) Replace(r key.Range, text string) {
|
|
||||||
if r.Start > r.End {
|
|
||||||
r.Start, r.End = r.End, r.Start
|
|
||||||
}
|
|
||||||
runes := []rune(text)
|
|
||||||
newEnd := r.Start + len(runes)
|
|
||||||
adjust := func(pos int) int {
|
|
||||||
switch {
|
|
||||||
case newEnd < pos && pos <= r.End:
|
|
||||||
return newEnd
|
|
||||||
case r.End < pos:
|
|
||||||
diff := newEnd - r.End
|
|
||||||
return pos + diff
|
|
||||||
}
|
|
||||||
return pos
|
|
||||||
}
|
|
||||||
e.Selection.Start = adjust(e.Selection.Start)
|
|
||||||
e.Selection.End = adjust(e.Selection.End)
|
|
||||||
if e.compose.Start != -1 {
|
|
||||||
e.compose.Start = adjust(e.compose.Start)
|
|
||||||
e.compose.End = adjust(e.compose.End)
|
|
||||||
}
|
|
||||||
s := e.Snippet
|
|
||||||
if r.End < s.Start || r.Start > s.End {
|
|
||||||
// Discard snippet if it doesn't overlap with replacement.
|
|
||||||
s = key.Snippet{
|
|
||||||
Range: key.Range{
|
|
||||||
Start: r.Start,
|
|
||||||
End: r.Start,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var newSnippet []rune
|
|
||||||
snippet := []rune(s.Text)
|
|
||||||
// Append first part of existing snippet.
|
|
||||||
if end := r.Start - s.Start; end > 0 {
|
|
||||||
newSnippet = append(newSnippet, snippet[:end]...)
|
|
||||||
}
|
|
||||||
// Append replacement.
|
|
||||||
newSnippet = append(newSnippet, runes...)
|
|
||||||
// Append last part of existing snippet.
|
|
||||||
if start := r.End; start < s.End {
|
|
||||||
newSnippet = append(newSnippet, snippet[start-s.Start:]...)
|
|
||||||
}
|
|
||||||
// Adjust snippet range to include replacement.
|
|
||||||
if r.Start < s.Start {
|
|
||||||
s.Start = r.Start
|
|
||||||
}
|
|
||||||
s.End = s.Start + len(newSnippet)
|
|
||||||
s.Text = string(newSnippet)
|
|
||||||
e.Snippet = s
|
|
||||||
}
|
|
||||||
|
|
||||||
// UTF16Index converts the given index in runes into an index in utf16 characters.
|
|
||||||
func (e *editorState) UTF16Index(runes int) int {
|
|
||||||
if runes == -1 {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
if runes < e.Snippet.Start {
|
|
||||||
// Assume runes before sippet are one UTF-16 character each.
|
|
||||||
return runes
|
|
||||||
}
|
|
||||||
chars := e.Snippet.Start
|
|
||||||
runes -= e.Snippet.Start
|
|
||||||
for _, r := range e.Snippet.Text {
|
|
||||||
if runes == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
runes--
|
|
||||||
chars++
|
|
||||||
if r1, _ := utf16.EncodeRune(r); r1 != unicode.ReplacementChar {
|
|
||||||
chars++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Assume runes after snippets are one UTF-16 character each.
|
|
||||||
return chars + runes
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunesIndex converts the given index in utf16 characters to an index in runes.
|
|
||||||
func (e *editorState) RunesIndex(chars int) int {
|
|
||||||
if chars == -1 {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
if chars < e.Snippet.Start {
|
|
||||||
// Assume runes before offset are one UTF-16 character each.
|
|
||||||
return chars
|
|
||||||
}
|
|
||||||
runes := e.Snippet.Start
|
|
||||||
chars -= e.Snippet.Start
|
|
||||||
for _, r := range e.Snippet.Text {
|
|
||||||
if chars == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
chars--
|
|
||||||
runes++
|
|
||||||
if r1, _ := utf16.EncodeRune(r); r1 != unicode.ReplacementChar {
|
|
||||||
chars--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Assume runes after snippets are one UTF-16 character each.
|
|
||||||
return runes + chars
|
|
||||||
}
|
|
||||||
|
|
||||||
// areSnippetsConsistent reports whether the content of the old snippet is
|
|
||||||
// consistent with the content of the new.
|
|
||||||
func areSnippetsConsistent(old, new key.Snippet) bool {
|
|
||||||
// Compute the overlapping range.
|
|
||||||
r := old.Range
|
|
||||||
r.Start = max(r.Start, new.Start)
|
|
||||||
r.End = max(r.End, r.Start)
|
|
||||||
r.End = min(r.End, new.End)
|
|
||||||
return snippetSubstring(old, r) == snippetSubstring(new, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func snippetSubstring(s key.Snippet, r key.Range) string {
|
|
||||||
for r.Start > s.Start && r.Start < s.End {
|
|
||||||
_, n := utf8.DecodeRuneInString(s.Text)
|
|
||||||
s.Text = s.Text[n:]
|
|
||||||
s.Start++
|
|
||||||
}
|
|
||||||
for r.End < s.End && r.End > s.Start {
|
|
||||||
_, n := utf8.DecodeLastRuneInString(s.Text)
|
|
||||||
s.Text = s.Text[:len(s.Text)-n]
|
|
||||||
s.End--
|
|
||||||
}
|
|
||||||
return s.Text
|
|
||||||
}
|
|
||||||
+4
-7
@@ -6,14 +6,13 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gioui.org/f32"
|
|
||||||
"testing"
|
"testing"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"gioui.org/font"
|
"gioui.org/font"
|
||||||
"gioui.org/font/gofont"
|
"gioui.org/font/gofont"
|
||||||
"gioui.org/io/input"
|
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
|
"gioui.org/io/router"
|
||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
"gioui.org/text"
|
"gioui.org/text"
|
||||||
@@ -32,16 +31,15 @@ func FuzzIME(f *testing.F) {
|
|||||||
f.Fuzz(func(t *testing.T, cmds []byte) {
|
f.Fuzz(func(t *testing.T, cmds []byte) {
|
||||||
cache := text.NewShaper(text.WithCollection(gofont.Collection()))
|
cache := text.NewShaper(text.WithCollection(gofont.Collection()))
|
||||||
e := new(widget.Editor)
|
e := new(widget.Editor)
|
||||||
|
e.Focus()
|
||||||
|
|
||||||
var r input.Router
|
var r router.Router
|
||||||
gtx := layout.Context{Ops: new(op.Ops), Source: r.Source()}
|
gtx := layout.Context{Ops: new(op.Ops), Queue: &r}
|
||||||
gtx.Execute(key.FocusCmd{Tag: e})
|
|
||||||
// Layout once to register focus.
|
// Layout once to register focus.
|
||||||
e.Layout(gtx, cache, font.Font{}, unit.Sp(10), op.CallOp{}, op.CallOp{})
|
e.Layout(gtx, cache, font.Font{}, unit.Sp(10), op.CallOp{}, op.CallOp{})
|
||||||
r.Frame(gtx.Ops)
|
r.Frame(gtx.Ops)
|
||||||
|
|
||||||
var state editorState
|
var state editorState
|
||||||
state.Selection.Transform = f32.AffineId()
|
|
||||||
const (
|
const (
|
||||||
cmdReplace = iota
|
cmdReplace = iota
|
||||||
cmdSelect
|
cmdSelect
|
||||||
@@ -141,7 +139,6 @@ func FuzzIME(f *testing.F) {
|
|||||||
|
|
||||||
func TestEditorIndices(t *testing.T) {
|
func TestEditorIndices(t *testing.T) {
|
||||||
var s editorState
|
var s editorState
|
||||||
s.Selection.Transform = f32.AffineId()
|
|
||||||
const str = "Hello, 😀"
|
const str = "Hello, 😀"
|
||||||
s.Snippet = key.Snippet{
|
s.Snippet = key.Snippet{
|
||||||
Text: str,
|
Text: str,
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
|
// Package points standard output, standard error and the standard
|
||||||
|
// library package log to the platform logger.
|
||||||
|
package log
|
||||||
|
|
||||||
|
var appID = "gio"
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
package app
|
package log
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#cgo LDFLAGS: -llog
|
#cgo LDFLAGS: -llog
|
||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
// 1024 is the truncation limit from android/log.h, plus a \n.
|
// 1024 is the truncation limit from android/log.h, plus a \n.
|
||||||
const logLineLimit = 1024
|
const logLineLimit = 1024
|
||||||
|
|
||||||
var logTag = C.CString(ID)
|
var logTag = C.CString(appID)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Android's logcat already includes timestamps.
|
// Android's logcat already includes timestamps.
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
//go:build darwin && ios
|
//go:build darwin && ios
|
||||||
// +build darwin,ios
|
// +build darwin,ios
|
||||||
|
|
||||||
package app
|
package log
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#cgo CFLAGS: -Werror -fmodules -fobjc-arc -x objective-c
|
#cgo CFLAGS: -Werror -fmodules -fobjc-arc -x objective-c
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
package app
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
syscall "golang.org/x/sys/windows"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type logger struct{}
|
type logger struct{}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
kernel32 = syscall.NewLazySystemDLL("kernel32")
|
kernel32 = syscall.NewLazyDLL("kernel32")
|
||||||
outputDebugStringW = kernel32.NewProc("OutputDebugStringW")
|
outputDebugStringW = kernel32.NewProc("OutputDebugStringW")
|
||||||
debugView *logger
|
debugView *logger
|
||||||
)
|
)
|
||||||
+38
-141
@@ -108,74 +108,6 @@ type MonitorInfo struct {
|
|||||||
Flags uint32
|
Flags uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
type POINTER_INPUT_TYPE int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
PT_POINTER POINTER_INPUT_TYPE = 1
|
|
||||||
PT_TOUCH POINTER_INPUT_TYPE = 2
|
|
||||||
PT_PEN POINTER_INPUT_TYPE = 3
|
|
||||||
PT_MOUSE POINTER_INPUT_TYPE = 4
|
|
||||||
PT_TOUCHPAD POINTER_INPUT_TYPE = 5
|
|
||||||
)
|
|
||||||
|
|
||||||
type POINTER_INFO_POINTER_FLAGS int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
POINTER_FLAG_NEW POINTER_INFO_POINTER_FLAGS = 0x00000001
|
|
||||||
POINTER_FLAG_INRANGE POINTER_INFO_POINTER_FLAGS = 0x00000002
|
|
||||||
POINTER_FLAG_INCONTACT POINTER_INFO_POINTER_FLAGS = 0x00000004
|
|
||||||
POINTER_FLAG_FIRSTBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000010
|
|
||||||
POINTER_FLAG_SECONDBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000020
|
|
||||||
POINTER_FLAG_THIRDBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000040
|
|
||||||
POINTER_FLAG_FOURTHBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000080
|
|
||||||
POINTER_FLAG_FIFTHBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000100
|
|
||||||
POINTER_FLAG_PRIMARY POINTER_INFO_POINTER_FLAGS = 0x00002000
|
|
||||||
POINTER_FLAG_CONFIDENCE POINTER_INFO_POINTER_FLAGS = 0x00004000
|
|
||||||
POINTER_FLAG_CANCELED POINTER_INFO_POINTER_FLAGS = 0x00008000
|
|
||||||
POINTER_FLAG_DOWN POINTER_INFO_POINTER_FLAGS = 0x00010000
|
|
||||||
POINTER_FLAG_UPDATE POINTER_INFO_POINTER_FLAGS = 0x00020000
|
|
||||||
POINTER_FLAG_UP POINTER_INFO_POINTER_FLAGS = 0x00040000
|
|
||||||
POINTER_FLAG_WHEEL POINTER_INFO_POINTER_FLAGS = 0x00080000
|
|
||||||
POINTER_FLAG_HWHEEL POINTER_INFO_POINTER_FLAGS = 0x00100000
|
|
||||||
POINTER_FLAG_CAPTURECHANGED POINTER_INFO_POINTER_FLAGS = 0x00200000
|
|
||||||
POINTER_FLAG_HASTRANSFORM POINTER_INFO_POINTER_FLAGS = 0x00400000
|
|
||||||
)
|
|
||||||
|
|
||||||
type POINTER_BUTTON_CHANGE_TYPE int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
POINTER_CHANGE_NONE POINTER_BUTTON_CHANGE_TYPE = 0
|
|
||||||
POINTER_CHANGE_FIRSTBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 1
|
|
||||||
POINTER_CHANGE_FIRSTBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 2
|
|
||||||
POINTER_CHANGE_SECONDBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 3
|
|
||||||
POINTER_CHANGE_SECONDBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 4
|
|
||||||
POINTER_CHANGE_THIRDBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 5
|
|
||||||
POINTER_CHANGE_THIRDBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 6
|
|
||||||
POINTER_CHANGE_FOURTHBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 7
|
|
||||||
POINTER_CHANGE_FOURTHBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 8
|
|
||||||
POINTER_CHANGE_FIFTHBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 9
|
|
||||||
POINTER_CHANGE_FIFTHBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 10
|
|
||||||
)
|
|
||||||
|
|
||||||
type PointerInfo struct {
|
|
||||||
PointerType POINTER_INPUT_TYPE
|
|
||||||
PointerId uint32
|
|
||||||
FrameId uint32
|
|
||||||
PointerFlags POINTER_INFO_POINTER_FLAGS
|
|
||||||
SourceDevice syscall.Handle
|
|
||||||
HwndTarget syscall.Handle
|
|
||||||
PtPixelLocation Point
|
|
||||||
PtHimetricLocation Point
|
|
||||||
PtPixelLocationRaw Point
|
|
||||||
PtHimetricLocationRaw Point
|
|
||||||
DwTime uint32
|
|
||||||
HistoryCount uint32
|
|
||||||
InputData int32
|
|
||||||
DwKeyStates uint32
|
|
||||||
PerformanceCount uint64
|
|
||||||
ButtonChangeType POINTER_BUTTON_CHANGE_TYPE
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TRUE = 1
|
TRUE = 1
|
||||||
|
|
||||||
@@ -312,51 +244,44 @@ const (
|
|||||||
|
|
||||||
UNICODE_NOCHAR = 65535
|
UNICODE_NOCHAR = 65535
|
||||||
|
|
||||||
WM_CANCELMODE = 0x001F
|
WM_CANCELMODE = 0x001F
|
||||||
WM_CHAR = 0x0102
|
WM_CHAR = 0x0102
|
||||||
WM_CLOSE = 0x0010
|
WM_CLOSE = 0x0010
|
||||||
WM_CREATE = 0x0001
|
WM_CREATE = 0x0001
|
||||||
WM_DPICHANGED = 0x02E0
|
WM_DPICHANGED = 0x02E0
|
||||||
WM_DESTROY = 0x0002
|
WM_DESTROY = 0x0002
|
||||||
WM_ERASEBKGND = 0x0014
|
WM_ERASEBKGND = 0x0014
|
||||||
WM_GETMINMAXINFO = 0x0024
|
WM_GETMINMAXINFO = 0x0024
|
||||||
WM_IME_COMPOSITION = 0x010F
|
WM_IME_COMPOSITION = 0x010F
|
||||||
WM_IME_ENDCOMPOSITION = 0x010E
|
WM_IME_ENDCOMPOSITION = 0x010E
|
||||||
WM_IME_STARTCOMPOSITION = 0x010D
|
WM_IME_STARTCOMPOSITION = 0x010D
|
||||||
WM_KEYDOWN = 0x0100
|
WM_KEYDOWN = 0x0100
|
||||||
WM_KEYUP = 0x0101
|
WM_KEYUP = 0x0101
|
||||||
WM_KILLFOCUS = 0x0008
|
WM_KILLFOCUS = 0x0008
|
||||||
WM_LBUTTONDOWN = 0x0201
|
WM_LBUTTONDOWN = 0x0201
|
||||||
WM_LBUTTONUP = 0x0202
|
WM_LBUTTONUP = 0x0202
|
||||||
WM_MBUTTONDOWN = 0x0207
|
WM_MBUTTONDOWN = 0x0207
|
||||||
WM_MBUTTONUP = 0x0208
|
WM_MBUTTONUP = 0x0208
|
||||||
WM_MOUSEMOVE = 0x0200
|
WM_MOUSEMOVE = 0x0200
|
||||||
WM_MOUSEWHEEL = 0x020A
|
WM_MOUSEWHEEL = 0x020A
|
||||||
WM_MOUSEHWHEEL = 0x020E
|
WM_MOUSEHWHEEL = 0x020E
|
||||||
WM_NCACTIVATE = 0x0086
|
WM_NCACTIVATE = 0x0086
|
||||||
WM_NCHITTEST = 0x0084
|
WM_NCHITTEST = 0x0084
|
||||||
WM_NCCALCSIZE = 0x0083
|
WM_NCCALCSIZE = 0x0083
|
||||||
WM_PAINT = 0x000F
|
WM_PAINT = 0x000F
|
||||||
WM_POINTERCAPTURECHANGED = 0x024C
|
WM_QUIT = 0x0012
|
||||||
WM_POINTERDOWN = 0x0246
|
WM_SETCURSOR = 0x0020
|
||||||
WM_POINTERUP = 0x0247
|
WM_SETFOCUS = 0x0007
|
||||||
WM_POINTERUPDATE = 0x0245
|
WM_SHOWWINDOW = 0x0018
|
||||||
WM_POINTERWHEEL = 0x024E
|
WM_SIZE = 0x0005
|
||||||
WM_POINTERHWHEEL = 0x024F
|
WM_SYSKEYDOWN = 0x0104
|
||||||
WM_QUIT = 0x0012
|
WM_SYSKEYUP = 0x0105
|
||||||
WM_RBUTTONDOWN = 0x0204
|
WM_RBUTTONDOWN = 0x0204
|
||||||
WM_RBUTTONUP = 0x0205
|
WM_RBUTTONUP = 0x0205
|
||||||
WM_SETCURSOR = 0x0020
|
WM_TIMER = 0x0113
|
||||||
WM_SETFOCUS = 0x0007
|
WM_UNICHAR = 0x0109
|
||||||
WM_SHOWWINDOW = 0x0018
|
WM_USER = 0x0400
|
||||||
WM_SIZE = 0x0005
|
WM_WINDOWPOSCHANGED = 0x0047
|
||||||
WM_STYLECHANGED = 0x007D
|
|
||||||
WM_SYSKEYDOWN = 0x0104
|
|
||||||
WM_SYSKEYUP = 0x0105
|
|
||||||
WM_TIMER = 0x0113
|
|
||||||
WM_UNICHAR = 0x0109
|
|
||||||
WM_USER = 0x0400
|
|
||||||
WM_WINDOWPOSCHANGED = 0x0047
|
|
||||||
|
|
||||||
WS_CLIPCHILDREN = 0x02000000
|
WS_CLIPCHILDREN = 0x02000000
|
||||||
WS_CLIPSIBLINGS = 0x04000000
|
WS_CLIPSIBLINGS = 0x04000000
|
||||||
@@ -420,7 +345,6 @@ var (
|
|||||||
_DestroyWindow = user32.NewProc("DestroyWindow")
|
_DestroyWindow = user32.NewProc("DestroyWindow")
|
||||||
_DispatchMessage = user32.NewProc("DispatchMessageW")
|
_DispatchMessage = user32.NewProc("DispatchMessageW")
|
||||||
_EmptyClipboard = user32.NewProc("EmptyClipboard")
|
_EmptyClipboard = user32.NewProc("EmptyClipboard")
|
||||||
_EnableMouseInPointer = user32.NewProc("EnableMouseInPointer")
|
|
||||||
_GetWindowRect = user32.NewProc("GetWindowRect")
|
_GetWindowRect = user32.NewProc("GetWindowRect")
|
||||||
_GetClientRect = user32.NewProc("GetClientRect")
|
_GetClientRect = user32.NewProc("GetClientRect")
|
||||||
_GetClipboardData = user32.NewProc("GetClipboardData")
|
_GetClipboardData = user32.NewProc("GetClipboardData")
|
||||||
@@ -430,7 +354,6 @@ var (
|
|||||||
_GetMessage = user32.NewProc("GetMessageW")
|
_GetMessage = user32.NewProc("GetMessageW")
|
||||||
_GetMessageTime = user32.NewProc("GetMessageTime")
|
_GetMessageTime = user32.NewProc("GetMessageTime")
|
||||||
_GetMonitorInfo = user32.NewProc("GetMonitorInfoW")
|
_GetMonitorInfo = user32.NewProc("GetMonitorInfoW")
|
||||||
_GetPointerInfo = user32.NewProc("GetPointerInfo")
|
|
||||||
_GetSystemMetrics = user32.NewProc("GetSystemMetrics")
|
_GetSystemMetrics = user32.NewProc("GetSystemMetrics")
|
||||||
_GetWindowLong = user32.NewProc("GetWindowLongPtrW")
|
_GetWindowLong = user32.NewProc("GetWindowLongPtrW")
|
||||||
_GetWindowLong32 = user32.NewProc("GetWindowLongW")
|
_GetWindowLong32 = user32.NewProc("GetWindowLongW")
|
||||||
@@ -448,7 +371,6 @@ var (
|
|||||||
_PostQuitMessage = user32.NewProc("PostQuitMessage")
|
_PostQuitMessage = user32.NewProc("PostQuitMessage")
|
||||||
_ReleaseCapture = user32.NewProc("ReleaseCapture")
|
_ReleaseCapture = user32.NewProc("ReleaseCapture")
|
||||||
_RegisterClassExW = user32.NewProc("RegisterClassExW")
|
_RegisterClassExW = user32.NewProc("RegisterClassExW")
|
||||||
_RegisterTouchWindow = user32.NewProc("RegisterTouchWindow")
|
|
||||||
_ReleaseDC = user32.NewProc("ReleaseDC")
|
_ReleaseDC = user32.NewProc("ReleaseDC")
|
||||||
_ScreenToClient = user32.NewProc("ScreenToClient")
|
_ScreenToClient = user32.NewProc("ScreenToClient")
|
||||||
_ShowWindow = user32.NewProc("ShowWindow")
|
_ShowWindow = user32.NewProc("ShowWindow")
|
||||||
@@ -522,31 +444,6 @@ func CreateWindowEx(dwExStyle uint32, lpClassName uint16, lpWindowName string, d
|
|||||||
return syscall.Handle(hwnd), nil
|
return syscall.Handle(hwnd), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPointerInfo(pointerId uint32) (PointerInfo, error) {
|
|
||||||
var info PointerInfo
|
|
||||||
r1, _, err := _GetPointerInfo.Call(uintptr(pointerId), uintptr(unsafe.Pointer(&info)))
|
|
||||||
if r1 == 0 {
|
|
||||||
return PointerInfo{}, fmt.Errorf("GetPointerInfo failed: %v", err)
|
|
||||||
}
|
|
||||||
return info, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterTouchWindow(hwnd syscall.Handle, flags uint32) error {
|
|
||||||
r1, _, err := _RegisterTouchWindow.Call(uintptr(hwnd), uintptr(flags))
|
|
||||||
if r1 == 0 {
|
|
||||||
return fmt.Errorf("RegisterTouchWindow failed: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func EnableMouseInPointer(enable uint) error {
|
|
||||||
r1, _, err := _EnableMouseInPointer.Call(uintptr(enable))
|
|
||||||
if r1 == 0 {
|
|
||||||
return fmt.Errorf("EnableMouseInPointer failed: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func DefWindowProc(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
|
func DefWindowProc(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
|
||||||
r, _, _ := _DefWindowProc.Call(uintptr(hwnd), uintptr(msg), wparam, lparam)
|
r, _, _ := _DefWindowProc.Call(uintptr(hwnd), uintptr(msg), wparam, lparam)
|
||||||
return r
|
return r
|
||||||
|
|||||||
@@ -238,17 +238,17 @@ func (x *Context) UpdateMask(depressed, latched, locked, depressedGroup, latched
|
|||||||
C.xkb_layout_index_t(depressedGroup), C.xkb_layout_index_t(latchedGroup), C.xkb_layout_index_t(lockedGroup))
|
C.xkb_layout_index_t(depressedGroup), C.xkb_layout_index_t(latchedGroup), C.xkb_layout_index_t(lockedGroup))
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertKeysym(s C.xkb_keysym_t) (key.Name, bool) {
|
func convertKeysym(s C.xkb_keysym_t) (string, bool) {
|
||||||
if 'a' <= s && s <= 'z' {
|
if 'a' <= s && s <= 'z' {
|
||||||
return key.Name(rune(s - 'a' + 'A')), true
|
return string(rune(s - 'a' + 'A')), true
|
||||||
}
|
}
|
||||||
if C.XKB_KEY_KP_0 <= s && s <= C.XKB_KEY_KP_9 {
|
if C.XKB_KEY_KP_0 <= s && s <= C.XKB_KEY_KP_9 {
|
||||||
return key.Name(rune(s - C.XKB_KEY_KP_0 + '0')), true
|
return string(rune(s - C.XKB_KEY_KP_0 + '0')), true
|
||||||
}
|
}
|
||||||
if ' ' < s && s <= '~' {
|
if ' ' < s && s <= '~' {
|
||||||
return key.Name(rune(s)), true
|
return string(rune(s)), true
|
||||||
}
|
}
|
||||||
var n key.Name
|
var n string
|
||||||
switch s {
|
switch s {
|
||||||
case C.XKB_KEY_Escape:
|
case C.XKB_KEY_Escape:
|
||||||
n = key.NameEscape
|
n = key.NameEscape
|
||||||
|
|||||||
+2
-3
@@ -60,9 +60,8 @@ static void presentDrawable(CFTypeRef queueRef, CFTypeRef drawableRef) {
|
|||||||
id<MTLDrawable> drawable = (__bridge id<MTLDrawable>)drawableRef;
|
id<MTLDrawable> drawable = (__bridge id<MTLDrawable>)drawableRef;
|
||||||
id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)queueRef;
|
id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)queueRef;
|
||||||
id<MTLCommandBuffer> cmdBuffer = [queue commandBuffer];
|
id<MTLCommandBuffer> cmdBuffer = [queue commandBuffer];
|
||||||
|
[cmdBuffer presentDrawable:drawable];
|
||||||
[cmdBuffer commit];
|
[cmdBuffer commit];
|
||||||
[cmdBuffer waitUntilScheduled];
|
|
||||||
[drawable present];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +95,7 @@ func newMtlContext(w *window) (*mtlContext, error) {
|
|||||||
return nil, errors.New("metal: CAMetalLayer construction failed")
|
return nil, errors.New("metal: CAMetalLayer construction failed")
|
||||||
}
|
}
|
||||||
queue := C.newCommandQueue(dev)
|
queue := C.newCommandQueue(dev)
|
||||||
if queue == 0 {
|
if layer == 0 {
|
||||||
C.CFRelease(dev)
|
C.CFRelease(dev)
|
||||||
C.CFRelease(layer)
|
C.CFRelease(layer)
|
||||||
return nil, errors.New("metal: [MTLDevice newCommandQueue] failed")
|
return nil, errors.New("metal: [MTLDevice newCommandQueue] failed")
|
||||||
|
|||||||
+1
-4
@@ -21,10 +21,7 @@ Class gio_layerClass(void) {
|
|||||||
static CFTypeRef getMetalLayer(CFTypeRef viewRef) {
|
static CFTypeRef getMetalLayer(CFTypeRef viewRef) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
UIView *view = (__bridge UIView *)viewRef;
|
UIView *view = (__bridge UIView *)viewRef;
|
||||||
CAMetalLayer *l = (CAMetalLayer *)view.layer;
|
return CFBridgingRetain(view.layer);
|
||||||
l.needsDisplayOnBoundsChange = YES;
|
|
||||||
l.presentsWithTransaction = YES;
|
|
||||||
return CFBridgingRetain(l);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-6
@@ -12,13 +12,9 @@ package app
|
|||||||
#import <QuartzCore/CAMetalLayer.h>
|
#import <QuartzCore/CAMetalLayer.h>
|
||||||
#include <CoreFoundation/CoreFoundation.h>
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
|
|
||||||
CALayer *gio_layerFactory(BOOL presentWithTrans) {
|
CALayer *gio_layerFactory(void) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
CAMetalLayer *l = [CAMetalLayer layer];
|
return [CAMetalLayer layer];
|
||||||
l.autoresizingMask = kCALayerHeightSizable|kCALayerWidthSizable;
|
|
||||||
l.needsDisplayOnBoundsChange = YES;
|
|
||||||
l.presentsWithTransaction = presentWithTrans;
|
|
||||||
return l;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
"gioui.org/io/event"
|
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/op"
|
|
||||||
|
|
||||||
"gioui.org/gpu"
|
"gioui.org/gpu"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
@@ -45,8 +43,6 @@ type Config struct {
|
|||||||
CustomRenderer bool
|
CustomRenderer bool
|
||||||
// Decorated reports whether window decorations are provided automatically.
|
// Decorated reports whether window decorations are provided automatically.
|
||||||
Decorated bool
|
Decorated bool
|
||||||
// Focused reports whether has the keyboard focus.
|
|
||||||
Focused bool
|
|
||||||
// decoHeight is the height of the fallback decoration for platforms such
|
// decoHeight is the height of the fallback decoration for platforms such
|
||||||
// as Wayland that may need fallback client-side decorations.
|
// as Wayland that may need fallback client-side decorations.
|
||||||
decoHeight unit.Dp
|
decoHeight unit.Dp
|
||||||
@@ -135,30 +131,8 @@ func (o Orientation) String() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// eventLoop implements the functionality required for drivers where
|
|
||||||
// window event loops must run on a separate thread.
|
|
||||||
type eventLoop struct {
|
|
||||||
win *callbacks
|
|
||||||
// wakeup is the callback to wake up the event loop.
|
|
||||||
wakeup func()
|
|
||||||
// driverFuncs is a channel of functions to run the next
|
|
||||||
// time the window loop waits for events.
|
|
||||||
driverFuncs chan func()
|
|
||||||
// invalidates is notified when an invalidate is requested by the client.
|
|
||||||
invalidates chan struct{}
|
|
||||||
// immediateInvalidates is an optimistic invalidates that doesn't require a wakeup.
|
|
||||||
immediateInvalidates chan struct{}
|
|
||||||
// events is where the platform backend delivers events bound for the
|
|
||||||
// user program.
|
|
||||||
events chan event.Event
|
|
||||||
frames chan *op.Ops
|
|
||||||
frameAck chan struct{}
|
|
||||||
// delivering avoids re-entrant event delivery.
|
|
||||||
delivering bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type frameEvent struct {
|
type frameEvent struct {
|
||||||
FrameEvent
|
system.FrameEvent
|
||||||
|
|
||||||
Sync bool
|
Sync bool
|
||||||
}
|
}
|
||||||
@@ -173,13 +147,9 @@ type context interface {
|
|||||||
Unlock()
|
Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
// 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)
|
||||||
@@ -190,27 +160,23 @@ type driver interface {
|
|||||||
// ReadClipboard requests the clipboard content.
|
// ReadClipboard requests the clipboard content.
|
||||||
ReadClipboard()
|
ReadClipboard()
|
||||||
// WriteClipboard requests a clipboard write.
|
// WriteClipboard requests a clipboard write.
|
||||||
WriteClipboard(mime string, s []byte)
|
WriteClipboard(s string)
|
||||||
// Configure the window.
|
// Configure the window.
|
||||||
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.
|
||||||
EditorStateChanged(old, new editorState)
|
EditorStateChanged(old, new editorState)
|
||||||
// Run a function on the window thread.
|
|
||||||
Run(f func())
|
|
||||||
// Frame receives a frame.
|
|
||||||
Frame(frame *op.Ops)
|
|
||||||
// ProcessEvent processes an event.
|
|
||||||
ProcessEvent(e event.Event)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type windowRendezvous struct {
|
type windowRendezvous struct {
|
||||||
in chan windowAndConfig
|
in chan windowAndConfig
|
||||||
out chan windowAndConfig
|
out chan windowAndConfig
|
||||||
windows chan struct{}
|
errs chan error
|
||||||
}
|
}
|
||||||
|
|
||||||
type windowAndConfig struct {
|
type windowAndConfig struct {
|
||||||
@@ -220,137 +186,32 @@ type windowAndConfig struct {
|
|||||||
|
|
||||||
func newWindowRendezvous() *windowRendezvous {
|
func newWindowRendezvous() *windowRendezvous {
|
||||||
wr := &windowRendezvous{
|
wr := &windowRendezvous{
|
||||||
in: make(chan windowAndConfig),
|
in: make(chan windowAndConfig),
|
||||||
out: make(chan windowAndConfig),
|
out: make(chan windowAndConfig),
|
||||||
windows: make(chan struct{}),
|
errs: make(chan error),
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
in := wr.in
|
var main windowAndConfig
|
||||||
var window windowAndConfig
|
|
||||||
var out chan windowAndConfig
|
var out chan windowAndConfig
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case w := <-in:
|
case w := <-wr.in:
|
||||||
window = w
|
var err error
|
||||||
|
if main.window != nil {
|
||||||
|
err = errors.New("multiple windows are not supported")
|
||||||
|
}
|
||||||
|
wr.errs <- err
|
||||||
|
main = w
|
||||||
out = wr.out
|
out = wr.out
|
||||||
case out <- window:
|
case out <- main:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return wr
|
return wr
|
||||||
}
|
}
|
||||||
|
|
||||||
func newEventLoop(w *callbacks, wakeup func()) *eventLoop {
|
func (wakeupEvent) ImplementsEvent() {}
|
||||||
return &eventLoop{
|
func (ConfigEvent) ImplementsEvent() {}
|
||||||
win: w,
|
|
||||||
wakeup: wakeup,
|
|
||||||
events: make(chan event.Event),
|
|
||||||
invalidates: make(chan struct{}, 1),
|
|
||||||
immediateInvalidates: make(chan struct{}),
|
|
||||||
frames: make(chan *op.Ops),
|
|
||||||
frameAck: make(chan struct{}),
|
|
||||||
driverFuncs: make(chan func(), 1),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Frame receives a frame and waits for its processing. It is called by
|
|
||||||
// the client goroutine.
|
|
||||||
func (e *eventLoop) Frame(frame *op.Ops) {
|
|
||||||
e.frames <- frame
|
|
||||||
<-e.frameAck
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event returns the next available event. It is called by the client
|
|
||||||
// goroutine.
|
|
||||||
func (e *eventLoop) Event() event.Event {
|
|
||||||
for {
|
|
||||||
evt := <-e.events
|
|
||||||
// Receiving a flushEvent indicates to the platform backend that
|
|
||||||
// all previous events have been processed by the user program.
|
|
||||||
if _, ok := evt.(flushEvent); ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return evt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invalidate requests invalidation of the window. It is called by the client
|
|
||||||
// goroutine.
|
|
||||||
func (e *eventLoop) Invalidate() {
|
|
||||||
select {
|
|
||||||
case e.immediateInvalidates <- struct{}{}:
|
|
||||||
// The event loop was waiting, no need for a wakeup.
|
|
||||||
case e.invalidates <- struct{}{}:
|
|
||||||
// The event loop is sleeping, wake it up.
|
|
||||||
e.wakeup()
|
|
||||||
default:
|
|
||||||
// A redraw is pending.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run f in the window loop thread. It is called by the client goroutine.
|
|
||||||
func (e *eventLoop) Run(f func()) {
|
|
||||||
e.driverFuncs <- f
|
|
||||||
e.wakeup()
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlushEvents delivers pending events to the client.
|
|
||||||
func (e *eventLoop) FlushEvents() {
|
|
||||||
if e.delivering {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
e.delivering = true
|
|
||||||
defer func() { e.delivering = false }()
|
|
||||||
for {
|
|
||||||
evt, ok := e.win.nextEvent()
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
e.deliverEvent(evt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *eventLoop) deliverEvent(evt event.Event) {
|
|
||||||
var frames <-chan *op.Ops
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case f := <-e.driverFuncs:
|
|
||||||
f()
|
|
||||||
case frame := <-frames:
|
|
||||||
// The client called FrameEvent.Frame.
|
|
||||||
frames = nil
|
|
||||||
e.win.ProcessFrame(frame, e.frameAck)
|
|
||||||
case e.events <- evt:
|
|
||||||
switch evt.(type) {
|
|
||||||
case flushEvent, DestroyEvent:
|
|
||||||
// DestroyEvents are not flushed.
|
|
||||||
return
|
|
||||||
case FrameEvent:
|
|
||||||
frames = e.frames
|
|
||||||
}
|
|
||||||
evt = theFlushEvent
|
|
||||||
case <-e.invalidates:
|
|
||||||
e.win.Invalidate()
|
|
||||||
case <-e.immediateInvalidates:
|
|
||||||
e.win.Invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *eventLoop) Wakeup() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case f := <-e.driverFuncs:
|
|
||||||
f()
|
|
||||||
case <-e.invalidates:
|
|
||||||
e.win.Invalidate()
|
|
||||||
case <-e.immediateInvalidates:
|
|
||||||
e.win.Invalidate()
|
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func walkActions(actions system.Action, do func(system.Action)) {
|
func walkActions(actions system.Action, do func(system.Action)) {
|
||||||
for a := system.Action(1); actions != 0; a <<= 1 {
|
for a := system.Action(1); actions != 0; a <<= 1 {
|
||||||
@@ -360,6 +221,3 @@ func walkActions(actions system.Action, do func(system.Action)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wakeupEvent) ImplementsEvent() {}
|
|
||||||
func (ConfigEvent) ImplementsEvent() {}
|
|
||||||
|
|||||||
+97
-140
@@ -123,36 +123,31 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"io"
|
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/cgo"
|
"runtime/cgo"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf16"
|
"unicode/utf16"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"gioui.org/internal/f32color"
|
"gioui.org/internal/f32color"
|
||||||
"gioui.org/op"
|
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/io/event"
|
"gioui.org/io/clipboard"
|
||||||
"gioui.org/io/input"
|
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
|
"gioui.org/io/router"
|
||||||
"gioui.org/io/semantic"
|
"gioui.org/io/semantic"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
"gioui.org/io/transfer"
|
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
)
|
)
|
||||||
|
|
||||||
type window struct {
|
type window struct {
|
||||||
callbacks *callbacks
|
callbacks *callbacks
|
||||||
loop *eventLoop
|
|
||||||
|
|
||||||
view C.jobject
|
view C.jobject
|
||||||
handle cgo.Handle
|
handle cgo.Handle
|
||||||
@@ -161,19 +156,18 @@ type window struct {
|
|||||||
fontScale float32
|
fontScale float32
|
||||||
insets pixelInsets
|
insets pixelInsets
|
||||||
|
|
||||||
visible bool
|
stage system.Stage
|
||||||
started bool
|
started bool
|
||||||
animating bool
|
animating bool
|
||||||
|
|
||||||
win *C.ANativeWindow
|
win *C.ANativeWindow
|
||||||
config Config
|
config Config
|
||||||
inputHint key.InputHint
|
|
||||||
|
|
||||||
semantic struct {
|
semantic struct {
|
||||||
hoverID input.SemanticID
|
hoverID router.SemanticID
|
||||||
rootID input.SemanticID
|
rootID router.SemanticID
|
||||||
focusID input.SemanticID
|
focusID router.SemanticID
|
||||||
diffs []input.SemanticID
|
diffs []router.SemanticID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,9 +199,9 @@ type pixelInsets struct {
|
|||||||
top, bottom, left, right int
|
top, bottom, left, right int
|
||||||
}
|
}
|
||||||
|
|
||||||
// AndroidViewEvent is sent whenever the Window's underlying Android view
|
// ViewEvent is sent whenever the Window's underlying Android view
|
||||||
// changes.
|
// changes.
|
||||||
type AndroidViewEvent struct {
|
type ViewEvent struct {
|
||||||
// View is a JNI global reference to the android.view.View
|
// View is a JNI global reference to the android.view.View
|
||||||
// instance backing the Window. The reference is valid until
|
// instance backing the Window. The reference is valid until
|
||||||
// the next ViewEvent is received.
|
// the next ViewEvent is received.
|
||||||
@@ -491,30 +485,24 @@ func Java_org_gioui_GioView_onCreateView(env *C.JNIEnv, class C.jclass, view C.j
|
|||||||
})
|
})
|
||||||
view = C.jni_NewGlobalRef(env, view)
|
view = C.jni_NewGlobalRef(env, view)
|
||||||
wopts := <-mainWindow.out
|
wopts := <-mainWindow.out
|
||||||
var cnf Config
|
|
||||||
w, ok := windows[wopts.window]
|
w, ok := windows[wopts.window]
|
||||||
if !ok {
|
if !ok {
|
||||||
w = &window{
|
w = &window{
|
||||||
callbacks: wopts.window,
|
callbacks: wopts.window,
|
||||||
}
|
}
|
||||||
w.loop = newEventLoop(w.callbacks, w.wakeup)
|
|
||||||
w.callbacks.SetDriver(w)
|
|
||||||
cnf.apply(unit.Metric{}, wopts.options)
|
|
||||||
windows[wopts.window] = w
|
windows[wopts.window] = w
|
||||||
} else {
|
|
||||||
cnf = w.config
|
|
||||||
}
|
}
|
||||||
mainWindow.windows <- struct{}{}
|
|
||||||
if w.view != 0 {
|
if w.view != 0 {
|
||||||
w.detach(env)
|
w.detach(env)
|
||||||
}
|
}
|
||||||
w.view = view
|
w.view = view
|
||||||
w.visible = false
|
|
||||||
w.handle = cgo.NewHandle(w)
|
w.handle = cgo.NewHandle(w)
|
||||||
|
w.callbacks.SetDriver(w)
|
||||||
w.loadConfig(env, class)
|
w.loadConfig(env, class)
|
||||||
w.setConfig(env, cnf)
|
w.Configure(wopts.options)
|
||||||
w.SetInputHint(w.inputHint)
|
w.SetInputHint(key.HintAny)
|
||||||
w.processEvent(AndroidViewEvent{View: uintptr(view)})
|
w.setStage(system.StagePaused)
|
||||||
|
w.callbacks.Event(ViewEvent{View: uintptr(view)})
|
||||||
return C.jlong(w.handle)
|
return C.jlong(w.handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,7 +516,7 @@ func Java_org_gioui_GioView_onDestroyView(env *C.JNIEnv, class C.jclass, handle
|
|||||||
func Java_org_gioui_GioView_onStopView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
func Java_org_gioui_GioView_onStopView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
||||||
w := cgo.Handle(handle).Value().(*window)
|
w := cgo.Handle(handle).Value().(*window)
|
||||||
w.started = false
|
w.started = false
|
||||||
w.visible = false
|
w.setStage(system.StagePaused)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Java_org_gioui_GioView_onStartView
|
//export Java_org_gioui_GioView_onStartView
|
||||||
@@ -544,7 +532,7 @@ func Java_org_gioui_GioView_onStartView(env *C.JNIEnv, class C.jclass, handle C.
|
|||||||
func Java_org_gioui_GioView_onSurfaceDestroyed(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
func Java_org_gioui_GioView_onSurfaceDestroyed(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
||||||
w := cgo.Handle(handle).Value().(*window)
|
w := cgo.Handle(handle).Value().(*window)
|
||||||
w.win = nil
|
w.win = nil
|
||||||
w.visible = false
|
w.setStage(system.StagePaused)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Java_org_gioui_GioView_onSurfaceChanged
|
//export Java_org_gioui_GioView_onSurfaceChanged
|
||||||
@@ -566,7 +554,9 @@ func Java_org_gioui_GioView_onLowMemory(env *C.JNIEnv, class C.jclass) {
|
|||||||
func Java_org_gioui_GioView_onConfigurationChanged(env *C.JNIEnv, class C.jclass, view C.jlong) {
|
func Java_org_gioui_GioView_onConfigurationChanged(env *C.JNIEnv, class C.jclass, view C.jlong) {
|
||||||
w := cgo.Handle(view).Value().(*window)
|
w := cgo.Handle(view).Value().(*window)
|
||||||
w.loadConfig(env, class)
|
w.loadConfig(env, class)
|
||||||
w.draw(env, true)
|
if w.stage >= system.StageInactive {
|
||||||
|
w.draw(env, true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Java_org_gioui_GioView_onFrameCallback
|
//export Java_org_gioui_GioView_onFrameCallback
|
||||||
@@ -575,13 +565,19 @@ func Java_org_gioui_GioView_onFrameCallback(env *C.JNIEnv, class C.jclass, view
|
|||||||
if !exist {
|
if !exist {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.draw(env, false)
|
if w.stage < system.StageInactive {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if w.animating {
|
||||||
|
w.draw(env, false)
|
||||||
|
callVoidMethod(env, w.view, gioView.postFrameCallback)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Java_org_gioui_GioView_onBack
|
//export Java_org_gioui_GioView_onBack
|
||||||
func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong) C.jboolean {
|
func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong) C.jboolean {
|
||||||
w := cgo.Handle(view).Value().(*window)
|
w := cgo.Handle(view).Value().(*window)
|
||||||
if w.processEvent(key.Event{Name: key.NameBack}) {
|
if w.callbacks.Event(key.Event{Name: key.NameBack}) {
|
||||||
return C.JNI_TRUE
|
return C.JNI_TRUE
|
||||||
}
|
}
|
||||||
return C.JNI_FALSE
|
return C.JNI_FALSE
|
||||||
@@ -590,8 +586,7 @@ func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong)
|
|||||||
//export Java_org_gioui_GioView_onFocusChange
|
//export Java_org_gioui_GioView_onFocusChange
|
||||||
func Java_org_gioui_GioView_onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) {
|
func Java_org_gioui_GioView_onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) {
|
||||||
w := cgo.Handle(view).Value().(*window)
|
w := cgo.Handle(view).Value().(*window)
|
||||||
w.config.Focused = focus == C.JNI_TRUE
|
w.callbacks.Event(key.FocusEvent{Focus: focus == C.JNI_TRUE})
|
||||||
w.processEvent(ConfigEvent{Config: w.config})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Java_org_gioui_GioView_onWindowInsets
|
//export Java_org_gioui_GioView_onWindowInsets
|
||||||
@@ -603,7 +598,9 @@ func Java_org_gioui_GioView_onWindowInsets(env *C.JNIEnv, class C.jclass, view C
|
|||||||
left: int(left),
|
left: int(left),
|
||||||
right: int(right),
|
right: int(right),
|
||||||
}
|
}
|
||||||
w.draw(env, true)
|
if w.stage >= system.StageInactive {
|
||||||
|
w.draw(env, true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Java_org_gioui_GioView_initializeAccessibilityNodeInfo
|
//export Java_org_gioui_GioView_initializeAccessibilityNodeInfo
|
||||||
@@ -664,35 +661,7 @@ func Java_org_gioui_GioView_onClearA11yFocus(env *C.JNIEnv, class C.jclass, view
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) ProcessEvent(e event.Event) {
|
func (w *window) initAccessibilityNodeInfo(env *C.JNIEnv, sem router.SemanticNode, off image.Point, info C.jobject) error {
|
||||||
w.processEvent(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) processEvent(e event.Event) bool {
|
|
||||||
if !w.callbacks.ProcessEvent(e) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
w.loop.FlushEvents()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) Event() event.Event {
|
|
||||||
return w.loop.Event()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) Invalidate() {
|
|
||||||
w.loop.Invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) Run(f func()) {
|
|
||||||
w.loop.Run(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) Frame(frame *op.Ops) {
|
|
||||||
w.loop.Frame(frame)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) initAccessibilityNodeInfo(env *C.JNIEnv, sem input.SemanticNode, off image.Point, info C.jobject) error {
|
|
||||||
for _, ch := range sem.Children {
|
for _, ch := range sem.Children {
|
||||||
err := callVoidMethod(env, info, android.accessibilityNodeInfo.addChild, jvalue(w.view), jvalue(w.virtualIDFor(ch.ID)))
|
err := callVoidMethod(env, info, android.accessibilityNodeInfo.addChild, jvalue(w.view), jvalue(w.virtualIDFor(ch.ID)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -735,7 +704,7 @@ func (w *window) initAccessibilityNodeInfo(env *C.JNIEnv, sem input.SemanticNode
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if d.Gestures&input.ClickGesture != 0 {
|
if d.Gestures&router.ClickGesture != 0 {
|
||||||
addAction(ACTION_CLICK)
|
addAction(ACTION_CLICK)
|
||||||
}
|
}
|
||||||
clsName := android.strings.androidViewView
|
clsName := android.strings.androidViewView
|
||||||
@@ -780,23 +749,25 @@ func (w *window) initAccessibilityNodeInfo(env *C.JNIEnv, sem input.SemanticNode
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) virtualIDFor(id input.SemanticID) C.jint {
|
func (w *window) virtualIDFor(id router.SemanticID) C.jint {
|
||||||
|
// TODO: Android virtual IDs are 32-bit Java integers, but childID is a int64.
|
||||||
if id == w.semantic.rootID {
|
if id == w.semantic.rootID {
|
||||||
return HOST_VIEW_ID
|
return HOST_VIEW_ID
|
||||||
}
|
}
|
||||||
return C.jint(id)
|
return C.jint(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) semIDFor(virtID C.jint) input.SemanticID {
|
func (w *window) semIDFor(virtID C.jint) router.SemanticID {
|
||||||
if virtID == HOST_VIEW_ID {
|
if virtID == HOST_VIEW_ID {
|
||||||
return w.semantic.rootID
|
return w.semantic.rootID
|
||||||
}
|
}
|
||||||
return input.SemanticID(virtID)
|
return router.SemanticID(virtID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) detach(env *C.JNIEnv) {
|
func (w *window) detach(env *C.JNIEnv) {
|
||||||
callVoidMethod(env, w.view, gioView.unregister)
|
callVoidMethod(env, w.view, gioView.unregister)
|
||||||
w.processEvent(AndroidViewEvent{})
|
w.callbacks.Event(ViewEvent{})
|
||||||
|
w.callbacks.SetDriver(nil)
|
||||||
w.handle.Delete()
|
w.handle.Delete()
|
||||||
C.jni_DeleteGlobalRef(env, w.view)
|
C.jni_DeleteGlobalRef(env, w.view)
|
||||||
w.view = 0
|
w.view = 0
|
||||||
@@ -807,10 +778,18 @@ func (w *window) setVisible(env *C.JNIEnv) {
|
|||||||
if width == 0 || height == 0 {
|
if width == 0 || height == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.visible = true
|
w.setStage(system.StageRunning)
|
||||||
w.draw(env, true)
|
w.draw(env, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *window) setStage(stage system.Stage) {
|
||||||
|
if stage == w.stage {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.stage = stage
|
||||||
|
w.callbacks.Event(system.StageEvent{stage})
|
||||||
|
}
|
||||||
|
|
||||||
func (w *window) setVisual(visID int) error {
|
func (w *window) setVisual(visID int) error {
|
||||||
if C.ANativeWindow_setBuffersGeometry(w.win, 0, 0, C.int32_t(visID)) != 0 {
|
if C.ANativeWindow_setBuffersGeometry(w.win, 0, 0, C.int32_t(visID)) != 0 {
|
||||||
return errors.New("ANativeWindow_setBuffersGeometry failed")
|
return errors.New("ANativeWindow_setBuffersGeometry failed")
|
||||||
@@ -847,13 +826,10 @@ func (w *window) SetAnimating(anim bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) draw(env *C.JNIEnv, sync bool) {
|
func (w *window) draw(env *C.JNIEnv, sync bool) {
|
||||||
if !w.visible {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
size := image.Pt(int(C.ANativeWindow_getWidth(w.win)), int(C.ANativeWindow_getHeight(w.win)))
|
size := image.Pt(int(C.ANativeWindow_getWidth(w.win)), int(C.ANativeWindow_getHeight(w.win)))
|
||||||
if size != w.config.Size {
|
if size != w.config.Size {
|
||||||
w.config.Size = size
|
w.config.Size = size
|
||||||
w.processEvent(ConfigEvent{Config: w.config})
|
w.callbacks.Event(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
if size.X == 0 || size.Y == 0 {
|
if size.X == 0 || size.Y == 0 {
|
||||||
return
|
return
|
||||||
@@ -861,14 +837,14 @@ func (w *window) draw(env *C.JNIEnv, sync bool) {
|
|||||||
const inchPrDp = 1.0 / 160
|
const inchPrDp = 1.0 / 160
|
||||||
ppdp := float32(w.dpi) * inchPrDp
|
ppdp := float32(w.dpi) * inchPrDp
|
||||||
dppp := unit.Dp(1.0 / ppdp)
|
dppp := unit.Dp(1.0 / ppdp)
|
||||||
insets := Insets{
|
insets := system.Insets{
|
||||||
Top: unit.Dp(w.insets.top) * dppp,
|
Top: unit.Dp(w.insets.top) * dppp,
|
||||||
Bottom: unit.Dp(w.insets.bottom) * dppp,
|
Bottom: unit.Dp(w.insets.bottom) * dppp,
|
||||||
Left: unit.Dp(w.insets.left) * dppp,
|
Left: unit.Dp(w.insets.left) * dppp,
|
||||||
Right: unit.Dp(w.insets.right) * dppp,
|
Right: unit.Dp(w.insets.right) * dppp,
|
||||||
}
|
}
|
||||||
w.processEvent(frameEvent{
|
w.callbacks.Event(frameEvent{
|
||||||
FrameEvent: FrameEvent{
|
FrameEvent: system.FrameEvent{
|
||||||
Now: time.Now(),
|
Now: time.Now(),
|
||||||
Size: w.config.Size,
|
Size: w.config.Size,
|
||||||
Insets: insets,
|
Insets: insets,
|
||||||
@@ -879,9 +855,6 @@ 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)
|
||||||
@@ -925,8 +898,8 @@ func runInJVM(jvm *C.JavaVM, f func(env *C.JNIEnv)) {
|
|||||||
f(env)
|
f(env)
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertKeyCode(code C.jint) (key.Name, bool) {
|
func convertKeyCode(code C.jint) (string, bool) {
|
||||||
var n key.Name
|
var n string
|
||||||
switch code {
|
switch code {
|
||||||
case C.AKEYCODE_FORWARD_DEL:
|
case C.AKEYCODE_FORWARD_DEL:
|
||||||
n = key.NameDeleteForward
|
n = key.NameDeleteForward
|
||||||
@@ -970,7 +943,7 @@ func Java_org_gioui_GioView_onKeyEvent(env *C.JNIEnv, class C.jclass, handle C.j
|
|||||||
if pressed == C.JNI_TRUE {
|
if pressed == C.JNI_TRUE {
|
||||||
state = key.Press
|
state = key.Press
|
||||||
}
|
}
|
||||||
w.processEvent(key.Event{Name: n, State: state})
|
w.callbacks.Event(key.Event{Name: n, State: state})
|
||||||
}
|
}
|
||||||
if pressed == C.JNI_TRUE && r != 0 && r != '\n' { // Checking for "\n" to prevent duplication with key.NameEnter (gio#224).
|
if pressed == C.JNI_TRUE && r != 0 && r != '\n' { // Checking for "\n" to prevent duplication with key.NameEnter (gio#224).
|
||||||
w.callbacks.EditorInsert(string(rune(r)))
|
w.callbacks.EditorInsert(string(rune(r)))
|
||||||
@@ -1020,7 +993,7 @@ func Java_org_gioui_GioView_onTouchEvent(env *C.JNIEnv, class C.jclass, handle C
|
|||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.processEvent(pointer.Event{
|
w.callbacks.Event(pointer.Event{
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Source: src,
|
Source: src,
|
||||||
Buttons: btns,
|
Buttons: btns,
|
||||||
@@ -1172,8 +1145,6 @@ func (w *window) ShowTextInput(show bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) SetInputHint(mode key.InputHint) {
|
func (w *window) SetInputHint(mode key.InputHint) {
|
||||||
w.inputHint = mode
|
|
||||||
|
|
||||||
// Constants defined at https://developer.android.com/reference/android/text/InputType.
|
// Constants defined at https://developer.android.com/reference/android/text/InputType.
|
||||||
const (
|
const (
|
||||||
TYPE_NULL = 0
|
TYPE_NULL = 0
|
||||||
@@ -1320,14 +1291,14 @@ func findClass(env *C.JNIEnv, name string) C.jclass {
|
|||||||
func osMain() {
|
func osMain() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWindow(window *callbacks, options []Option) {
|
func newWindow(window *callbacks, options []Option) error {
|
||||||
mainWindow.in <- windowAndConfig{window, options}
|
mainWindow.in <- windowAndConfig{window, options}
|
||||||
<-mainWindow.windows
|
return <-mainWindow.errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) WriteClipboard(mime string, s []byte) {
|
func (w *window) WriteClipboard(s string) {
|
||||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||||
jstr := javaString(env, string(s))
|
jstr := javaString(env, s)
|
||||||
callStaticVoidMethod(env, android.gioCls, android.mwriteClipboard,
|
callStaticVoidMethod(env, android.gioCls, android.mwriteClipboard,
|
||||||
jvalue(android.appCtx), jvalue(jstr))
|
jvalue(android.appCtx), jvalue(jstr))
|
||||||
})
|
})
|
||||||
@@ -1341,54 +1312,45 @@ func (w *window) ReadClipboard() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
content := goString(env, C.jstring(c))
|
content := goString(env, C.jstring(c))
|
||||||
w.processEvent(transfer.DataEvent{
|
w.callbacks.Event(clipboard.Event{Text: content})
|
||||||
Type: "application/text",
|
|
||||||
Open: func() io.ReadCloser {
|
|
||||||
return io.NopCloser(strings.NewReader(content))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Configure(options []Option) {
|
func (w *window) Configure(options []Option) {
|
||||||
cnf := w.config
|
|
||||||
cnf.apply(unit.Metric{}, options)
|
|
||||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||||
w.setConfig(env, cnf)
|
prev := w.config
|
||||||
})
|
cnf := w.config
|
||||||
}
|
cnf.apply(unit.Metric{}, options)
|
||||||
|
// Decorations are never disabled.
|
||||||
|
cnf.Decorated = true
|
||||||
|
|
||||||
func (w *window) setConfig(env *C.JNIEnv, cnf Config) {
|
if prev.Orientation != cnf.Orientation {
|
||||||
prev := w.config
|
w.config.Orientation = cnf.Orientation
|
||||||
// Decorations are never disabled.
|
setOrientation(env, w.view, cnf.Orientation)
|
||||||
cnf.Decorated = true
|
|
||||||
|
|
||||||
if prev.Orientation != cnf.Orientation {
|
|
||||||
w.config.Orientation = cnf.Orientation
|
|
||||||
setOrientation(env, w.view, cnf.Orientation)
|
|
||||||
}
|
|
||||||
if prev.NavigationColor != cnf.NavigationColor {
|
|
||||||
w.config.NavigationColor = cnf.NavigationColor
|
|
||||||
setNavigationColor(env, w.view, cnf.NavigationColor)
|
|
||||||
}
|
|
||||||
if prev.StatusColor != cnf.StatusColor {
|
|
||||||
w.config.StatusColor = cnf.StatusColor
|
|
||||||
setStatusColor(env, w.view, cnf.StatusColor)
|
|
||||||
}
|
|
||||||
if prev.Mode != cnf.Mode {
|
|
||||||
switch cnf.Mode {
|
|
||||||
case Fullscreen:
|
|
||||||
callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_TRUE)
|
|
||||||
w.config.Mode = Fullscreen
|
|
||||||
case Windowed:
|
|
||||||
callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_FALSE)
|
|
||||||
w.config.Mode = Windowed
|
|
||||||
}
|
}
|
||||||
}
|
if prev.NavigationColor != cnf.NavigationColor {
|
||||||
if cnf.Decorated != prev.Decorated {
|
w.config.NavigationColor = cnf.NavigationColor
|
||||||
w.config.Decorated = cnf.Decorated
|
setNavigationColor(env, w.view, cnf.NavigationColor)
|
||||||
}
|
}
|
||||||
w.processEvent(ConfigEvent{Config: w.config})
|
if prev.StatusColor != cnf.StatusColor {
|
||||||
|
w.config.StatusColor = cnf.StatusColor
|
||||||
|
setStatusColor(env, w.view, cnf.StatusColor)
|
||||||
|
}
|
||||||
|
if prev.Mode != cnf.Mode {
|
||||||
|
switch cnf.Mode {
|
||||||
|
case Fullscreen:
|
||||||
|
callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_TRUE)
|
||||||
|
w.config.Mode = Fullscreen
|
||||||
|
case Windowed:
|
||||||
|
callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_FALSE)
|
||||||
|
w.config.Mode = Windowed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cnf.Decorated != prev.Decorated {
|
||||||
|
w.config.Decorated = cnf.Decorated
|
||||||
|
}
|
||||||
|
w.callbacks.Event(ConfigEvent{Config: w.config})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Perform(system.Action) {}
|
func (w *window) Perform(system.Action) {}
|
||||||
@@ -1399,10 +1361,9 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) wakeup() {
|
func (w *window) Wakeup() {
|
||||||
runOnMain(func(env *C.JNIEnv) {
|
runOnMain(func(env *C.JNIEnv) {
|
||||||
w.loop.Wakeup()
|
w.callbacks.Event(wakeupEvent{})
|
||||||
w.loop.FlushEvents()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1493,8 +1454,4 @@ func Java_org_gioui_Gio_scheduleMainFuncs(env *C.JNIEnv, cls C.jclass) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (AndroidViewEvent) implementsViewEvent() {}
|
func (_ ViewEvent) ImplementsEvent() {}
|
||||||
func (AndroidViewEvent) ImplementsEvent() {}
|
|
||||||
func (a AndroidViewEvent) Valid() bool {
|
|
||||||
return a != (AndroidViewEvent{})
|
|
||||||
}
|
|
||||||
|
|||||||
+19
-17
@@ -5,7 +5,7 @@ package app
|
|||||||
/*
|
/*
|
||||||
#include <Foundation/Foundation.h>
|
#include <Foundation/Foundation.h>
|
||||||
|
|
||||||
__attribute__ ((visibility ("hidden"))) void gio_runOnMain(uintptr_t h);
|
__attribute__ ((visibility ("hidden"))) void gio_wakeupMainThread(void);
|
||||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createDisplayLink(void);
|
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createDisplayLink(void);
|
||||||
__attribute__ ((visibility ("hidden"))) void gio_releaseDisplayLink(CFTypeRef dl);
|
__attribute__ ((visibility ("hidden"))) void gio_releaseDisplayLink(CFTypeRef dl);
|
||||||
__attribute__ ((visibility ("hidden"))) int gio_startDisplayLink(CFTypeRef dl);
|
__attribute__ ((visibility ("hidden"))) int gio_startDisplayLink(CFTypeRef dl);
|
||||||
@@ -40,10 +40,8 @@ static CFTypeRef newNSString(unichar *chars, NSUInteger length) {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"runtime/cgo"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@@ -75,25 +73,30 @@ type displayLink struct {
|
|||||||
// displayLinks maps CFTypeRefs to *displayLinks.
|
// displayLinks maps CFTypeRefs to *displayLinks.
|
||||||
var displayLinks sync.Map
|
var displayLinks sync.Map
|
||||||
|
|
||||||
func isMainThread() bool {
|
var mainFuncs = make(chan func(), 1)
|
||||||
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 C.isMainThread() {
|
||||||
f()
|
f()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
C.gio_runOnMain(C.uintptr_t(cgo.NewHandle(f)))
|
go func() {
|
||||||
|
mainFuncs <- f
|
||||||
|
C.gio_wakeupMainThread()
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_runFunc
|
//export gio_dispatchMainFuncs
|
||||||
func gio_runFunc(h C.uintptr_t) {
|
func gio_dispatchMainFuncs() {
|
||||||
handle := cgo.Handle(h)
|
for {
|
||||||
defer handle.Delete()
|
select {
|
||||||
f := handle.Value().(func())
|
case f := <-mainFuncs:
|
||||||
f()
|
f()
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// nsstringToString converts a NSString to a Go string.
|
// nsstringToString converts a NSString to a Go string.
|
||||||
@@ -257,9 +260,8 @@ func windowSetCursor(from, to pointer.Cursor) pointer.Cursor {
|
|||||||
return to
|
return to
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) wakeup() {
|
func (w *window) Wakeup() {
|
||||||
runOnMain(func() {
|
runOnMain(func() {
|
||||||
w.loop.Wakeup()
|
w.w.Event(wakeupEvent{})
|
||||||
w.loop.FlushEvents()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
#include "_cgo_export.h"
|
#include "_cgo_export.h"
|
||||||
|
|
||||||
void gio_runOnMain(uintptr_t h) {
|
void gio_wakeupMainThread(void) {
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
gio_runFunc(h);
|
gio_dispatchMainFuncs();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+65
-152
@@ -12,9 +12,6 @@ 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);
|
|
||||||
|
|
||||||
struct drawParams {
|
struct drawParams {
|
||||||
CGFloat dpi, sdpi;
|
CGFloat dpi, sdpi;
|
||||||
CGFloat width, height;
|
CGFloat width, height;
|
||||||
@@ -22,7 +19,6 @@ 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) {
|
||||||
@@ -31,18 +27,13 @@ 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) {
|
||||||
@@ -81,27 +72,21 @@ import "C"
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"image"
|
"image"
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/cgo"
|
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf16"
|
"unicode/utf16"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/io/event"
|
"gioui.org/io/clipboard"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
"gioui.org/io/transfer"
|
|
||||||
"gioui.org/op"
|
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UIKitViewEvent struct {
|
type ViewEvent struct {
|
||||||
// ViewController is a CFTypeRef for the UIViewController backing a Window.
|
// ViewController is a CFTypeRef for the UIViewController backing a Window.
|
||||||
ViewController uintptr
|
ViewController uintptr
|
||||||
}
|
}
|
||||||
@@ -110,17 +95,18 @@ type window struct {
|
|||||||
view C.CFTypeRef
|
view C.CFTypeRef
|
||||||
w *callbacks
|
w *callbacks
|
||||||
displayLink *displayLink
|
displayLink *displayLink
|
||||||
loop *eventLoop
|
|
||||||
|
|
||||||
hidden bool
|
visible bool
|
||||||
cursor pointer.Cursor
|
cursor pointer.Cursor
|
||||||
config Config
|
config Config
|
||||||
|
|
||||||
pointerMap []C.CFTypeRef
|
pointerMap []C.CFTypeRef
|
||||||
}
|
}
|
||||||
|
|
||||||
var mainWindow = newWindowRendezvous()
|
var mainWindow = newWindowRendezvous()
|
||||||
|
|
||||||
|
var views = make(map[C.CFTypeRef]*window)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Darwin requires UI operations happen on the main thread only.
|
// Darwin requires UI operations happen on the main thread only.
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
@@ -128,59 +114,55 @@ func init() {
|
|||||||
|
|
||||||
//export onCreate
|
//export onCreate
|
||||||
func onCreate(view, controller C.CFTypeRef) {
|
func onCreate(view, controller C.CFTypeRef) {
|
||||||
wopts := <-mainWindow.out
|
|
||||||
w := &window{
|
w := &window{
|
||||||
view: view,
|
view: view,
|
||||||
w: wopts.window,
|
|
||||||
}
|
}
|
||||||
w.loop = newEventLoop(w.w, w.wakeup)
|
|
||||||
w.w.SetDriver(w)
|
|
||||||
mainWindow.windows <- struct{}{}
|
|
||||||
dl, err := newDisplayLink(func() {
|
dl, err := newDisplayLink(func() {
|
||||||
w.draw(false)
|
w.draw(false)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.w.ProcessEvent(DestroyEvent{Err: err})
|
panic(err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
w.displayLink = dl
|
w.displayLink = dl
|
||||||
C.gio_viewSetHandle(view, C.uintptr_t(cgo.NewHandle(w)))
|
wopts := <-mainWindow.out
|
||||||
|
w.w = wopts.window
|
||||||
|
w.w.SetDriver(w)
|
||||||
|
views[view] = w
|
||||||
w.Configure(wopts.options)
|
w.Configure(wopts.options)
|
||||||
w.ProcessEvent(UIKitViewEvent{ViewController: uintptr(controller)})
|
w.w.Event(system.StageEvent{Stage: system.StagePaused})
|
||||||
}
|
w.w.Event(ViewEvent{ViewController: uintptr(controller)})
|
||||||
|
|
||||||
func viewFor(h C.uintptr_t) *window {
|
|
||||||
return cgo.Handle(h).Value().(*window)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onDraw
|
//export gio_onDraw
|
||||||
func gio_onDraw(h C.uintptr_t) {
|
func gio_onDraw(view C.CFTypeRef) {
|
||||||
w := viewFor(h)
|
w := views[view]
|
||||||
w.draw(true)
|
w.draw(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) draw(sync bool) {
|
func (w *window) draw(sync bool) {
|
||||||
if w.hidden {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
params := C.viewDrawParams(w.view)
|
params := C.viewDrawParams(w.view)
|
||||||
if params.width == 0 || params.height == 0 {
|
if params.width == 0 || params.height == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
wasVisible := w.visible
|
||||||
|
w.visible = true
|
||||||
|
if !wasVisible {
|
||||||
|
w.w.Event(system.StageEvent{Stage: system.StageRunning})
|
||||||
|
}
|
||||||
const inchPrDp = 1.0 / 163
|
const inchPrDp = 1.0 / 163
|
||||||
m := unit.Metric{
|
m := unit.Metric{
|
||||||
PxPerDp: float32(params.dpi) * inchPrDp,
|
PxPerDp: float32(params.dpi) * inchPrDp,
|
||||||
PxPerSp: float32(params.sdpi) * inchPrDp,
|
PxPerSp: float32(params.sdpi) * inchPrDp,
|
||||||
}
|
}
|
||||||
dppp := unit.Dp(1. / m.PxPerDp)
|
dppp := unit.Dp(1. / m.PxPerDp)
|
||||||
w.ProcessEvent(frameEvent{
|
w.w.Event(frameEvent{
|
||||||
FrameEvent: FrameEvent{
|
FrameEvent: system.FrameEvent{
|
||||||
Now: time.Now(),
|
Now: time.Now(),
|
||||||
Size: image.Point{
|
Size: image.Point{
|
||||||
X: int(params.width + .5),
|
X: int(params.width + .5),
|
||||||
Y: int(params.height + .5),
|
Y: int(params.height + .5),
|
||||||
},
|
},
|
||||||
Insets: Insets{
|
Insets: system.Insets{
|
||||||
Top: unit.Dp(params.top) * dppp,
|
Top: unit.Dp(params.top) * dppp,
|
||||||
Bottom: unit.Dp(params.bottom) * dppp,
|
Bottom: unit.Dp(params.bottom) * dppp,
|
||||||
Left: unit.Dp(params.left) * dppp,
|
Left: unit.Dp(params.left) * dppp,
|
||||||
@@ -193,34 +175,26 @@ func (w *window) draw(sync bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//export onStop
|
//export onStop
|
||||||
func onStop(h C.uintptr_t) {
|
func onStop(view C.CFTypeRef) {
|
||||||
w := viewFor(h)
|
w := views[view]
|
||||||
w.hidden = true
|
w.visible = false
|
||||||
}
|
w.w.Event(system.StageEvent{Stage: system.StagePaused})
|
||||||
|
|
||||||
//export onStart
|
|
||||||
func onStart(h C.uintptr_t) {
|
|
||||||
w := viewFor(h)
|
|
||||||
w.hidden = false
|
|
||||||
w.draw(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onDestroy
|
//export onDestroy
|
||||||
func onDestroy(h C.uintptr_t) {
|
func onDestroy(view C.CFTypeRef) {
|
||||||
w := viewFor(h)
|
w := views[view]
|
||||||
w.ProcessEvent(UIKitViewEvent{})
|
delete(views, view)
|
||||||
w.ProcessEvent(DestroyEvent{})
|
w.w.Event(ViewEvent{})
|
||||||
|
w.w.Event(system.DestroyEvent{})
|
||||||
w.displayLink.Close()
|
w.displayLink.Close()
|
||||||
w.displayLink = nil
|
|
||||||
cgo.Handle(h).Delete()
|
|
||||||
w.view = 0
|
w.view = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onFocus
|
//export onFocus
|
||||||
func onFocus(h C.uintptr_t, focus int) {
|
func onFocus(view C.CFTypeRef, focus int) {
|
||||||
w := viewFor(h)
|
w := views[view]
|
||||||
w.config.Focused = focus != 0
|
w.w.Event(key.FocusEvent{Focus: focus != 0})
|
||||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onLowMemory
|
//export onLowMemory
|
||||||
@@ -230,38 +204,38 @@ func onLowMemory() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//export onUpArrow
|
//export onUpArrow
|
||||||
func onUpArrow(h C.uintptr_t) {
|
func onUpArrow(view C.CFTypeRef) {
|
||||||
viewFor(h).onKeyCommand(key.NameUpArrow)
|
views[view].onKeyCommand(key.NameUpArrow)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onDownArrow
|
//export onDownArrow
|
||||||
func onDownArrow(h C.uintptr_t) {
|
func onDownArrow(view C.CFTypeRef) {
|
||||||
viewFor(h).onKeyCommand(key.NameDownArrow)
|
views[view].onKeyCommand(key.NameDownArrow)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onLeftArrow
|
//export onLeftArrow
|
||||||
func onLeftArrow(h C.uintptr_t) {
|
func onLeftArrow(view C.CFTypeRef) {
|
||||||
viewFor(h).onKeyCommand(key.NameLeftArrow)
|
views[view].onKeyCommand(key.NameLeftArrow)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onRightArrow
|
//export onRightArrow
|
||||||
func onRightArrow(h C.uintptr_t) {
|
func onRightArrow(view C.CFTypeRef) {
|
||||||
viewFor(h).onKeyCommand(key.NameRightArrow)
|
views[view].onKeyCommand(key.NameRightArrow)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onDeleteBackward
|
//export onDeleteBackward
|
||||||
func onDeleteBackward(h C.uintptr_t) {
|
func onDeleteBackward(view C.CFTypeRef) {
|
||||||
viewFor(h).onKeyCommand(key.NameDeleteBackward)
|
views[view].onKeyCommand(key.NameDeleteBackward)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onText
|
//export onText
|
||||||
func onText(h C.uintptr_t, str C.CFTypeRef) {
|
func onText(view, str C.CFTypeRef) {
|
||||||
w := viewFor(h)
|
w := views[view]
|
||||||
w.w.EditorInsert(nsstringToString(str))
|
w.w.EditorInsert(nsstringToString(str))
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onTouch
|
//export onTouch
|
||||||
func onTouch(h C.uintptr_t, last C.int, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) {
|
func onTouch(last C.int, view, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) {
|
||||||
var kind pointer.Kind
|
var kind pointer.Kind
|
||||||
switch phase {
|
switch phase {
|
||||||
case C.UITouchPhaseBegan:
|
case C.UITouchPhaseBegan:
|
||||||
@@ -275,10 +249,10 @@ func onTouch(h C.uintptr_t, last C.int, touchRef C.CFTypeRef, phase C.NSInteger,
|
|||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w := viewFor(h)
|
w := views[view]
|
||||||
t := time.Duration(float64(ti) * float64(time.Second))
|
t := time.Duration(float64(ti) * float64(time.Second))
|
||||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||||
w.ProcessEvent(pointer.Event{
|
w.w.Event(pointer.Event{
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
PointerID: w.lookupTouch(last != 0, touchRef),
|
PointerID: w.lookupTouch(last != 0, touchRef),
|
||||||
@@ -291,16 +265,11 @@ func (w *window) ReadClipboard() {
|
|||||||
cstr := C.readClipboard()
|
cstr := C.readClipboard()
|
||||||
defer C.CFRelease(cstr)
|
defer C.CFRelease(cstr)
|
||||||
content := nsstringToString(cstr)
|
content := nsstringToString(cstr)
|
||||||
w.ProcessEvent(transfer.DataEvent{
|
w.w.Event(clipboard.Event{Text: content})
|
||||||
Type: "application/text",
|
|
||||||
Open: func() io.ReadCloser {
|
|
||||||
return io.NopCloser(strings.NewReader(content))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) WriteClipboard(mime string, s []byte) {
|
func (w *window) WriteClipboard(s string) {
|
||||||
u16 := utf16.Encode([]rune(string(s)))
|
u16 := utf16.Encode([]rune(s))
|
||||||
var chars *C.unichar
|
var chars *C.unichar
|
||||||
if len(u16) > 0 {
|
if len(u16) > 0 {
|
||||||
chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
|
chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
|
||||||
@@ -311,7 +280,7 @@ func (w *window) WriteClipboard(mime string, s []byte) {
|
|||||||
func (w *window) Configure([]Option) {
|
func (w *window) Configure([]Option) {
|
||||||
// Decorations are never disabled.
|
// Decorations are never disabled.
|
||||||
w.config.Decorated = true
|
w.config.Decorated = true
|
||||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
w.w.Event(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) EditorStateChanged(old, new editorState) {}
|
func (w *window) EditorStateChanged(old, new editorState) {}
|
||||||
@@ -319,6 +288,10 @@ func (w *window) EditorStateChanged(old, new editorState) {}
|
|||||||
func (w *window) Perform(system.Action) {}
|
func (w *window) Perform(system.Action) {}
|
||||||
|
|
||||||
func (w *window) SetAnimating(anim bool) {
|
func (w *window) SetAnimating(anim bool) {
|
||||||
|
v := w.view
|
||||||
|
if v == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
if anim {
|
if anim {
|
||||||
w.displayLink.Start()
|
w.displayLink.Start()
|
||||||
} else {
|
} else {
|
||||||
@@ -330,8 +303,8 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
|||||||
w.cursor = windowSetCursor(w.cursor, cursor)
|
w.cursor = windowSetCursor(w.cursor, cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) onKeyCommand(name key.Name) {
|
func (w *window) onKeyCommand(name string) {
|
||||||
w.ProcessEvent(key.Event{
|
w.w.Event(key.Event{
|
||||||
Name: name,
|
Name: name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -370,77 +343,17 @@ func (w *window) ShowTextInput(show bool) {
|
|||||||
|
|
||||||
func (w *window) SetInputHint(_ key.InputHint) {}
|
func (w *window) SetInputHint(_ key.InputHint) {}
|
||||||
|
|
||||||
func (w *window) ProcessEvent(e event.Event) {
|
func newWindow(win *callbacks, options []Option) error {
|
||||||
w.w.ProcessEvent(e)
|
|
||||||
w.loop.FlushEvents()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) Event() event.Event {
|
|
||||||
return w.loop.Event()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) Invalidate() {
|
|
||||||
w.loop.Invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) Run(f func()) {
|
|
||||||
w.loop.Run(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) Frame(frame *op.Ops) {
|
|
||||||
w.loop.Frame(frame)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newWindow(win *callbacks, options []Option) {
|
|
||||||
mainWindow.in <- windowAndConfig{win, options}
|
mainWindow.in <- windowAndConfig{win, options}
|
||||||
<-mainWindow.windows
|
return <-mainWindow.errs
|
||||||
}
|
}
|
||||||
|
|
||||||
var mainMode = mainModeUndefined
|
|
||||||
|
|
||||||
const (
|
|
||||||
mainModeUndefined = iota
|
|
||||||
mainModeExe
|
|
||||||
mainModeLibrary
|
|
||||||
)
|
|
||||||
|
|
||||||
func osMain() {
|
func osMain() {
|
||||||
if !isMainThread() {
|
|
||||||
panic("app.Main must be run on the main goroutine")
|
|
||||||
}
|
|
||||||
switch mainMode {
|
|
||||||
case mainModeUndefined:
|
|
||||||
mainMode = mainModeExe
|
|
||||||
var argv []*C.char
|
|
||||||
for _, arg := range os.Args {
|
|
||||||
a := C.CString(arg)
|
|
||||||
defer C.free(unsafe.Pointer(a))
|
|
||||||
argv = append(argv, a)
|
|
||||||
}
|
|
||||||
C.gio_applicationMain(C.int(len(argv)), unsafe.SliceData(argv))
|
|
||||||
case mainModeExe:
|
|
||||||
panic("app.Main may be called only once")
|
|
||||||
case mainModeLibrary:
|
|
||||||
// Do nothing, we're embedded as a library.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_runMain
|
//export gio_runMain
|
||||||
func gio_runMain() {
|
func gio_runMain() {
|
||||||
if !isMainThread() {
|
runMain()
|
||||||
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 (_ ViewEvent) ImplementsEvent() {}
|
||||||
func (UIKitViewEvent) ImplementsEvent() {}
|
|
||||||
func (u UIKitViewEvent) Valid() bool {
|
|
||||||
return u != (UIKitViewEvent{})
|
|
||||||
}
|
|
||||||
|
|||||||
+22
-51
@@ -11,7 +11,6 @@
|
|||||||
__attribute__ ((visibility ("hidden"))) Class gio_layerClass(void);
|
__attribute__ ((visibility ("hidden"))) Class gio_layerClass(void);
|
||||||
|
|
||||||
@interface GioView: UIView <UIKeyInput>
|
@interface GioView: UIView <UIKeyInput>
|
||||||
@property uintptr_t handle;
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation GioViewController
|
@implementation GioViewController
|
||||||
@@ -26,13 +25,12 @@ 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];
|
||||||
#if !TARGET_OS_TV
|
#ifndef 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
|
||||||
@@ -45,7 +43,6 @@ 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
|
||||||
@@ -57,33 +54,33 @@ CGFloat _keyboardHeight;
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)applicationWillEnterForeground:(UIApplication *)application {
|
- (void)applicationWillEnterForeground:(UIApplication *)application {
|
||||||
GioView *view = (GioView *)self.view.subviews[0];
|
UIView *drawView = self.view.subviews[0];
|
||||||
if (view != nil) {
|
if (drawView != nil) {
|
||||||
onStart(view.handle);
|
gio_onDraw((__bridge CFTypeRef)drawView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
||||||
GioView *view = (GioView *)self.view.subviews[0];
|
UIView *drawView = self.view.subviews[0];
|
||||||
if (view != nil) {
|
if (drawView != nil) {
|
||||||
onStop(view.handle);
|
onStop((__bridge CFTypeRef)drawView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewDidDisappear:(BOOL)animated {
|
- (void)viewDidDisappear:(BOOL)animated {
|
||||||
[super viewDidDisappear:animated];
|
[super viewDidDisappear:animated];
|
||||||
GioView *view = (GioView *)self.view.subviews[0];
|
CFTypeRef viewRef = (__bridge CFTypeRef)self.view.subviews[0];
|
||||||
onDestroy(view.handle);
|
onDestroy(viewRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewDidLayoutSubviews {
|
- (void)viewDidLayoutSubviews {
|
||||||
[super viewDidLayoutSubviews];
|
[super viewDidLayoutSubviews];
|
||||||
GioView *view = (GioView *)self.view.subviews[0];
|
UIView *view = self.view.subviews[0];
|
||||||
CGRect frame = self.view.bounds;
|
CGRect frame = self.view.bounds;
|
||||||
// Adjust view bounds to make room for the keyboard.
|
// Adjust view bounds to make room for the keyboard.
|
||||||
frame.size.height -= _keyboardHeight;
|
frame.size.height -= _keyboardHeight;
|
||||||
view.frame = frame;
|
view.frame = frame;
|
||||||
gio_onDraw(view.handle);
|
gio_onDraw((__bridge CFTypeRef)view);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)didReceiveMemoryWarning {
|
- (void)didReceiveMemoryWarning {
|
||||||
@@ -91,7 +88,6 @@ 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];
|
||||||
@@ -103,13 +99,13 @@ 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, UIView *view, NSSet<UITouch *> *touches, UIEvent *event) {
|
||||||
CGFloat scale = view.contentScaleFactor;
|
CGFloat scale = view.contentScaleFactor;
|
||||||
NSUInteger i = 0;
|
NSUInteger i = 0;
|
||||||
NSUInteger n = [touches count];
|
NSUInteger n = [touches count];
|
||||||
|
CFTypeRef viewRef = (__bridge CFTypeRef)view;
|
||||||
for (UITouch *touch in touches) {
|
for (UITouch *touch in touches) {
|
||||||
CFTypeRef touchRef = (__bridge CFTypeRef)touch;
|
CFTypeRef touchRef = (__bridge CFTypeRef)touch;
|
||||||
i++;
|
i++;
|
||||||
@@ -120,7 +116,7 @@ static void handleTouches(int last, GioView *view, NSSet<UITouch *> *touches, UI
|
|||||||
CGPoint loc = [coalescedTouch locationInView:view];
|
CGPoint loc = [coalescedTouch locationInView:view];
|
||||||
j++;
|
j++;
|
||||||
int lastTouch = last && i == n && j == m;
|
int lastTouch = last && i == n && j == m;
|
||||||
onTouch(view.handle, lastTouch, touchRef, touch.phase, loc.x*scale, loc.y*scale, [coalescedTouch timestamp]);
|
onTouch(lastTouch, viewRef, touchRef, touch.phase, loc.x*scale, loc.y*scale, [coalescedTouch timestamp]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -155,13 +151,13 @@ NSArray<UIKeyCommand *> *_keyCommands;
|
|||||||
|
|
||||||
- (void)onWindowDidBecomeKey:(NSNotification *)note {
|
- (void)onWindowDidBecomeKey:(NSNotification *)note {
|
||||||
if (self.isFirstResponder) {
|
if (self.isFirstResponder) {
|
||||||
onFocus(self.handle, YES);
|
onFocus((__bridge CFTypeRef)self, YES);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)onWindowDidResignKey:(NSNotification *)note {
|
- (void)onWindowDidResignKey:(NSNotification *)note {
|
||||||
if (self.isFirstResponder) {
|
if (self.isFirstResponder) {
|
||||||
onFocus(self.handle, NO);
|
onFocus((__bridge CFTypeRef)self, NO);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +178,7 @@ NSArray<UIKeyCommand *> *_keyCommands;
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)insertText:(NSString *)text {
|
- (void)insertText:(NSString *)text {
|
||||||
onText(self.handle, (__bridge CFTypeRef)text);
|
onText((__bridge CFTypeRef)self, (__bridge CFTypeRef)text);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)canBecomeFirstResponder {
|
- (BOOL)canBecomeFirstResponder {
|
||||||
@@ -194,23 +190,23 @@ NSArray<UIKeyCommand *> *_keyCommands;
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)deleteBackward {
|
- (void)deleteBackward {
|
||||||
onDeleteBackward(self.handle);
|
onDeleteBackward((__bridge CFTypeRef)self);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)onUpArrow {
|
- (void)onUpArrow {
|
||||||
onUpArrow(self.handle);
|
onUpArrow((__bridge CFTypeRef)self);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)onDownArrow {
|
- (void)onDownArrow {
|
||||||
onDownArrow(self.handle);
|
onDownArrow((__bridge CFTypeRef)self);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)onLeftArrow {
|
- (void)onLeftArrow {
|
||||||
onLeftArrow(self.handle);
|
onLeftArrow((__bridge CFTypeRef)self);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)onRightArrow {
|
- (void)onRightArrow {
|
||||||
onRightArrow(self.handle);
|
onRightArrow((__bridge CFTypeRef)self);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray<UIKeyCommand *> *)keyCommands {
|
- (NSArray<UIKeyCommand *> *)keyCommands {
|
||||||
@@ -275,28 +271,3 @@ void gio_showCursor() {
|
|||||||
void gio_setCursor(NSUInteger curID) {
|
void gio_setCursor(NSUInteger curID) {
|
||||||
// Not supported.
|
// Not supported.
|
||||||
}
|
}
|
||||||
|
|
||||||
void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
|
|
||||||
GioView *v = (__bridge GioView *)viewRef;
|
|
||||||
v.handle = handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
@interface _gioAppDelegate : UIResponder <UIApplicationDelegate>
|
|
||||||
@property (strong, nonatomic) UIWindow *window;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation _gioAppDelegate
|
|
||||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
|
||||||
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
|
||||||
GioViewController *controller = [[GioViewController alloc] initWithNibName:nil bundle:nil];
|
|
||||||
self.window.rootViewController = controller;
|
|
||||||
[self.window makeKeyAndVisible];
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
int gio_applicationMain(int argc, char *argv[]) {
|
|
||||||
@autoreleasepool {
|
|
||||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass([_gioAppDelegate class]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+96
-99
@@ -6,7 +6,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"io"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall/js"
|
"syscall/js"
|
||||||
"time"
|
"time"
|
||||||
@@ -14,18 +13,16 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"gioui.org/internal/f32color"
|
"gioui.org/internal/f32color"
|
||||||
"gioui.org/op"
|
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/io/event"
|
"gioui.org/io/clipboard"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
"gioui.org/io/transfer"
|
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
)
|
)
|
||||||
|
|
||||||
type JSViewEvent struct {
|
type ViewEvent struct {
|
||||||
Element js.Value
|
Element js.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,6 +53,9 @@ type window struct {
|
|||||||
composing bool
|
composing bool
|
||||||
requestFocus bool
|
requestFocus bool
|
||||||
|
|
||||||
|
chanAnimation chan struct{}
|
||||||
|
chanRedraw chan struct{}
|
||||||
|
|
||||||
config Config
|
config Config
|
||||||
inset f32.Point
|
inset f32.Point
|
||||||
scale float32
|
scale float32
|
||||||
@@ -68,7 +68,7 @@ type window struct {
|
|||||||
contextStatus contextStatus
|
contextStatus contextStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWindow(win *callbacks, options []Option) {
|
func newWindow(win *callbacks, options []Option) error {
|
||||||
doc := js.Global().Get("document")
|
doc := js.Global().Get("document")
|
||||||
cont := getContainer(doc)
|
cont := getContainer(doc)
|
||||||
cnv := createCanvas(doc)
|
cnv := createCanvas(doc)
|
||||||
@@ -83,9 +83,7 @@ func newWindow(win *callbacks, options []Option) {
|
|||||||
head: doc.Get("head"),
|
head: doc.Get("head"),
|
||||||
clipboard: js.Global().Get("navigator").Get("clipboard"),
|
clipboard: js.Global().Get("navigator").Get("clipboard"),
|
||||||
wakeups: make(chan struct{}, 1),
|
wakeups: make(chan struct{}, 1),
|
||||||
w: win,
|
|
||||||
}
|
}
|
||||||
w.w.SetDriver(w)
|
|
||||||
w.requestAnimationFrame = w.window.Get("requestAnimationFrame")
|
w.requestAnimationFrame = w.window.Get("requestAnimationFrame")
|
||||||
w.browserHistory = w.window.Get("history")
|
w.browserHistory = w.window.Get("history")
|
||||||
w.visualViewport = w.window.Get("visualViewport")
|
w.visualViewport = w.window.Get("visualViewport")
|
||||||
@@ -95,28 +93,42 @@ func newWindow(win *callbacks, options []Option) {
|
|||||||
if screen := w.window.Get("screen"); screen.Truthy() {
|
if screen := w.window.Get("screen"); screen.Truthy() {
|
||||||
w.screenOrientation = screen.Get("orientation")
|
w.screenOrientation = screen.Get("orientation")
|
||||||
}
|
}
|
||||||
|
w.chanAnimation = make(chan struct{}, 1)
|
||||||
|
w.chanRedraw = make(chan struct{}, 1)
|
||||||
w.redraw = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
w.redraw = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
||||||
w.draw(false)
|
w.chanAnimation <- struct{}{}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.clipboardCallback = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
w.clipboardCallback = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
||||||
content := args[0].String()
|
content := args[0].String()
|
||||||
w.processEvent(transfer.DataEvent{
|
go win.Event(clipboard.Event{Text: content})
|
||||||
Type: "application/text",
|
|
||||||
Open: func() io.ReadCloser {
|
|
||||||
return io.NopCloser(strings.NewReader(content))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListeners()
|
w.addEventListeners()
|
||||||
w.addHistory()
|
w.addHistory()
|
||||||
|
w.w = win
|
||||||
|
|
||||||
w.Configure(options)
|
go func() {
|
||||||
w.blur()
|
defer w.cleanup()
|
||||||
w.processEvent(JSViewEvent{Element: cont})
|
w.w.SetDriver(w)
|
||||||
w.resize()
|
w.Configure(options)
|
||||||
w.draw(true)
|
w.blur()
|
||||||
|
w.w.Event(ViewEvent{Element: cont})
|
||||||
|
w.w.Event(system.StageEvent{Stage: system.StageRunning})
|
||||||
|
w.resize()
|
||||||
|
w.draw(true)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-w.wakeups:
|
||||||
|
w.w.Event(wakeupEvent{})
|
||||||
|
case <-w.chanAnimation:
|
||||||
|
w.animCallback()
|
||||||
|
case <-w.chanRedraw:
|
||||||
|
w.draw(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getContainer(doc js.Value) js.Value {
|
func getContainer(doc js.Value) js.Value {
|
||||||
@@ -176,12 +188,12 @@ func (w *window) addEventListeners() {
|
|||||||
w.cnv.Set("width", 0)
|
w.cnv.Set("width", 0)
|
||||||
w.cnv.Set("height", 0)
|
w.cnv.Set("height", 0)
|
||||||
w.resize()
|
w.resize()
|
||||||
w.draw(true)
|
w.requestRedraw()
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.visualViewport, "resize", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.visualViewport, "resize", func(this js.Value, args []js.Value) interface{} {
|
||||||
w.resize()
|
w.resize()
|
||||||
w.draw(true)
|
w.requestRedraw()
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.window, "contextmenu", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.window, "contextmenu", func(this js.Value, args []js.Value) interface{} {
|
||||||
@@ -189,11 +201,22 @@ func (w *window) addEventListeners() {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.window, "popstate", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.window, "popstate", func(this js.Value, args []js.Value) interface{} {
|
||||||
if w.processEvent(key.Event{Name: key.NameBack}) {
|
if w.w.Event(key.Event{Name: key.NameBack}) {
|
||||||
return w.browserHistory.Call("forward")
|
return w.browserHistory.Call("forward")
|
||||||
}
|
}
|
||||||
return w.browserHistory.Call("back")
|
return w.browserHistory.Call("back")
|
||||||
})
|
})
|
||||||
|
w.addEventListener(w.document, "visibilitychange", func(this js.Value, args []js.Value) interface{} {
|
||||||
|
ev := system.StageEvent{}
|
||||||
|
switch w.document.Get("visibilityState").String() {
|
||||||
|
case "hidden", "prerender", "unloaded":
|
||||||
|
ev.Stage = system.StagePaused
|
||||||
|
default:
|
||||||
|
ev.Stage = system.StageRunning
|
||||||
|
}
|
||||||
|
w.w.Event(ev)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
w.addEventListener(w.cnv, "mousemove", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.cnv, "mousemove", func(this js.Value, args []js.Value) interface{} {
|
||||||
w.pointerEvent(pointer.Move, 0, 0, args[0])
|
w.pointerEvent(pointer.Move, 0, 0, args[0])
|
||||||
return nil
|
return nil
|
||||||
@@ -251,20 +274,18 @@ func (w *window) addEventListeners() {
|
|||||||
w.touches[i] = js.Null()
|
w.touches[i] = js.Null()
|
||||||
}
|
}
|
||||||
w.touches = w.touches[:0]
|
w.touches = w.touches[:0]
|
||||||
w.processEvent(pointer.Event{
|
w.w.Event(pointer.Event{
|
||||||
Kind: pointer.Cancel,
|
Kind: pointer.Cancel,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.tarea, "focus", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.tarea, "focus", func(this js.Value, args []js.Value) interface{} {
|
||||||
w.config.Focused = true
|
w.w.Event(key.FocusEvent{Focus: true})
|
||||||
w.processEvent(ConfigEvent{Config: w.config})
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.tarea, "blur", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.tarea, "blur", func(this js.Value, args []js.Value) interface{} {
|
||||||
w.config.Focused = false
|
w.w.Event(key.FocusEvent{Focus: false})
|
||||||
w.processEvent(ConfigEvent{Config: w.config})
|
|
||||||
w.blur()
|
w.blur()
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -353,50 +374,10 @@ func (w *window) keyEvent(e js.Value, ks key.State) {
|
|||||||
Modifiers: modifiersFor(e),
|
Modifiers: modifiersFor(e),
|
||||||
State: ks,
|
State: ks,
|
||||||
}
|
}
|
||||||
w.processEvent(cmd)
|
w.w.Event(cmd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) ProcessEvent(e event.Event) {
|
|
||||||
w.processEvent(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) processEvent(e event.Event) bool {
|
|
||||||
if !w.w.ProcessEvent(e) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case w.wakeups <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) Event() event.Event {
|
|
||||||
for {
|
|
||||||
evt, ok := w.w.nextEvent()
|
|
||||||
if ok {
|
|
||||||
if _, destroy := evt.(DestroyEvent); destroy {
|
|
||||||
w.cleanup()
|
|
||||||
}
|
|
||||||
return evt
|
|
||||||
}
|
|
||||||
<-w.wakeups
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) Invalidate() {
|
|
||||||
w.w.Invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) Run(f func()) {
|
|
||||||
f()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) Frame(frame *op.Ops) {
|
|
||||||
w.w.ProcessFrame(frame, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// modifiersFor returns the modifier set for a DOM MouseEvent or
|
// modifiersFor returns the modifier set for a DOM MouseEvent or
|
||||||
// KeyEvent.
|
// KeyEvent.
|
||||||
func modifiersFor(e js.Value) key.Modifiers {
|
func modifiersFor(e js.Value) key.Modifiers {
|
||||||
@@ -444,7 +425,7 @@ func (w *window) touchEvent(kind pointer.Kind, e js.Value) {
|
|||||||
X: float32(x) * scale,
|
X: float32(x) * scale,
|
||||||
Y: float32(y) * scale,
|
Y: float32(y) * scale,
|
||||||
}
|
}
|
||||||
w.processEvent(pointer.Event{
|
w.w.Event(pointer.Event{
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
Position: pos,
|
Position: pos,
|
||||||
@@ -494,7 +475,7 @@ func (w *window) pointerEvent(kind pointer.Kind, dx, dy float32, e js.Value) {
|
|||||||
if jbtns&4 != 0 {
|
if jbtns&4 != 0 {
|
||||||
btns |= pointer.ButtonTertiary
|
btns |= pointer.ButtonTertiary
|
||||||
}
|
}
|
||||||
w.processEvent(pointer.Event{
|
w.w.Event(pointer.Event{
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Buttons: btns,
|
Buttons: btns,
|
||||||
@@ -521,6 +502,17 @@ func (w *window) funcOf(f func(this js.Value, args []js.Value) interface{}) js.F
|
|||||||
return jsf
|
return jsf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *window) animCallback() {
|
||||||
|
anim := w.animating
|
||||||
|
w.animRequested = anim
|
||||||
|
if anim {
|
||||||
|
w.requestAnimationFrame.Invoke(w.redraw)
|
||||||
|
}
|
||||||
|
if anim {
|
||||||
|
w.draw(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (w *window) EditorStateChanged(old, new editorState) {}
|
func (w *window) EditorStateChanged(old, new editorState) {}
|
||||||
|
|
||||||
func (w *window) SetAnimating(anim bool) {
|
func (w *window) SetAnimating(anim bool) {
|
||||||
@@ -541,14 +533,14 @@ func (w *window) ReadClipboard() {
|
|||||||
w.clipboard.Call("readText", w.clipboard).Call("then", w.clipboardCallback)
|
w.clipboard.Call("readText", w.clipboard).Call("then", w.clipboardCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) WriteClipboard(mime string, s []byte) {
|
func (w *window) WriteClipboard(s string) {
|
||||||
if w.clipboard.IsUndefined() {
|
if w.clipboard.IsUndefined() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if w.clipboard.Get("writeText").IsUndefined() {
|
if w.clipboard.Get("writeText").IsUndefined() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.clipboard.Call("writeText", string(s))
|
w.clipboard.Call("writeText", s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Configure(options []Option) {
|
func (w *window) Configure(options []Option) {
|
||||||
@@ -576,7 +568,7 @@ func (w *window) Configure(options []Option) {
|
|||||||
if cnf.Decorated != prev.Decorated {
|
if cnf.Decorated != prev.Decorated {
|
||||||
w.config.Decorated = cnf.Decorated
|
w.config.Decorated = cnf.Decorated
|
||||||
}
|
}
|
||||||
w.processEvent(ConfigEvent{Config: w.config})
|
w.w.Event(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Perform(system.Action) {}
|
func (w *window) Perform(system.Action) {}
|
||||||
@@ -615,14 +607,23 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
|||||||
style.Set("cursor", webCursor[cursor])
|
style.Set("cursor", webCursor[cursor])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *window) Wakeup() {
|
||||||
|
select {
|
||||||
|
case w.wakeups <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (w *window) ShowTextInput(show bool) {
|
func (w *window) ShowTextInput(show bool) {
|
||||||
// Run in a goroutine to avoid a deadlock if the
|
// Run in a goroutine to avoid a deadlock if the
|
||||||
// focus change result in an event.
|
// focus change result in an event.
|
||||||
if show {
|
go func() {
|
||||||
w.focus()
|
if show {
|
||||||
} else {
|
w.focus()
|
||||||
w.blur()
|
} else {
|
||||||
}
|
w.blur()
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) SetInputHint(mode key.InputHint) {
|
func (w *window) SetInputHint(mode key.InputHint) {
|
||||||
@@ -639,7 +640,7 @@ func (w *window) resize() {
|
|||||||
}
|
}
|
||||||
if size != w.config.Size {
|
if size != w.config.Size {
|
||||||
w.config.Size = size
|
w.config.Size = size
|
||||||
w.processEvent(ConfigEvent{Config: w.config})
|
w.w.Event(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
if vx, vy := w.visualViewport.Get("width"), w.visualViewport.Get("height"); !vx.IsUndefined() && !vy.IsUndefined() {
|
if vx, vy := w.visualViewport.Get("width"), w.visualViewport.Get("height"); !vx.IsUndefined() && !vy.IsUndefined() {
|
||||||
@@ -659,20 +660,13 @@ func (w *window) draw(sync bool) {
|
|||||||
if w.contextStatus == contextStatusLost {
|
if w.contextStatus == contextStatusLost {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
anim := w.animating
|
|
||||||
w.animRequested = anim
|
|
||||||
if anim {
|
|
||||||
w.requestAnimationFrame.Invoke(w.redraw)
|
|
||||||
} else if !sync {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
size, insets, metric := w.getConfig()
|
size, insets, metric := w.getConfig()
|
||||||
if metric == (unit.Metric{}) || size.X == 0 || size.Y == 0 {
|
if metric == (unit.Metric{}) || size.X == 0 || size.Y == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.processEvent(frameEvent{
|
w.w.Event(frameEvent{
|
||||||
FrameEvent: FrameEvent{
|
FrameEvent: system.FrameEvent{
|
||||||
Now: time.Now(),
|
Now: time.Now(),
|
||||||
Size: size,
|
Size: size,
|
||||||
Insets: insets,
|
Insets: insets,
|
||||||
@@ -682,10 +676,10 @@ func (w *window) draw(sync bool) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) getConfig() (image.Point, Insets, unit.Metric) {
|
func (w *window) getConfig() (image.Point, system.Insets, unit.Metric) {
|
||||||
invscale := unit.Dp(1. / w.scale)
|
invscale := unit.Dp(1. / w.scale)
|
||||||
return image.Pt(w.config.Size.X, w.config.Size.Y),
|
return image.Pt(w.config.Size.X, w.config.Size.Y),
|
||||||
Insets{
|
system.Insets{
|
||||||
Bottom: unit.Dp(w.inset.Y) * invscale,
|
Bottom: unit.Dp(w.inset.Y) * invscale,
|
||||||
Right: unit.Dp(w.inset.X) * invscale,
|
Right: unit.Dp(w.inset.X) * invscale,
|
||||||
}, unit.Metric{
|
}, unit.Metric{
|
||||||
@@ -741,12 +735,19 @@ func (w *window) navigationColor(c color.NRGBA) {
|
|||||||
theme.Set("content", fmt.Sprintf("#%06X", []uint8{rgba.R, rgba.G, rgba.B}))
|
theme.Set("content", fmt.Sprintf("#%06X", []uint8{rgba.R, rgba.G, rgba.B}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *window) requestRedraw() {
|
||||||
|
select {
|
||||||
|
case w.chanRedraw <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func osMain() {
|
func osMain() {
|
||||||
select {}
|
select {}
|
||||||
}
|
}
|
||||||
|
|
||||||
func translateKey(k string) (key.Name, bool) {
|
func translateKey(k string) (string, bool) {
|
||||||
var n key.Name
|
var n string
|
||||||
|
|
||||||
switch k {
|
switch k {
|
||||||
case "ArrowUp":
|
case "ArrowUp":
|
||||||
@@ -813,15 +814,11 @@ func translateKey(k string) (key.Name, bool) {
|
|||||||
r, s := utf8.DecodeRuneInString(k)
|
r, s := utf8.DecodeRuneInString(k)
|
||||||
// If there is exactly one printable character, return that.
|
// If there is exactly one printable character, return that.
|
||||||
if s == len(k) && unicode.IsPrint(r) {
|
if s == len(k) && unicode.IsPrint(r) {
|
||||||
return key.Name(strings.ToUpper(k)), true
|
return strings.ToUpper(k), true
|
||||||
}
|
}
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
return n, true
|
return n, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (JSViewEvent) implementsViewEvent() {}
|
func (_ ViewEvent) ImplementsEvent() {}
|
||||||
func (JSViewEvent) ImplementsEvent() {}
|
|
||||||
func (j JSViewEvent) Valid() bool {
|
|
||||||
return !(j.Element.IsNull() || j.Element.IsUndefined())
|
|
||||||
}
|
|
||||||
|
|||||||
+322
-494
File diff suppressed because it is too large
Load Diff
+67
-120
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
#include "_cgo_export.h"
|
#include "_cgo_export.h"
|
||||||
|
|
||||||
__attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(BOOL presentWithTrans);
|
__attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(void);
|
||||||
|
|
||||||
@interface GioAppDelegate : NSObject<NSApplicationDelegate>
|
@interface GioAppDelegate : NSObject<NSApplicationDelegate>
|
||||||
@end
|
@end
|
||||||
@@ -14,55 +14,40 @@ __attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(BOOL presentWi
|
|||||||
@interface GioWindowDelegate : NSObject<NSWindowDelegate>
|
@interface GioWindowDelegate : NSObject<NSWindowDelegate>
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GioView : NSView <CALayerDelegate,NSTextInputClient>
|
|
||||||
@property uintptr_t handle;
|
|
||||||
@property BOOL presentWithTrans;
|
|
||||||
@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;
|
gio_onHide((__bridge CFTypeRef)window.contentView);
|
||||||
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;
|
gio_onShow((__bridge CFTypeRef)window.contentView);
|
||||||
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;
|
gio_onFullscreen((__bridge CFTypeRef)window.contentView);
|
||||||
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;
|
gio_onWindowed((__bridge CFTypeRef)window.contentView);
|
||||||
gio_onDraw(view.handle);
|
|
||||||
}
|
}
|
||||||
- (void)windowDidChangeScreen:(NSNotification *)notification {
|
- (void)windowDidChangeScreen:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
CGDirectDisplayID dispID = [[[window screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue];
|
CGDirectDisplayID dispID = [[[window screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue];
|
||||||
GioView *view = (GioView *)window.contentView;
|
CFTypeRef view = (__bridge CFTypeRef)window.contentView;
|
||||||
gio_onChangeScreen(view.handle, dispID);
|
gio_onChangeScreen(view, dispID);
|
||||||
}
|
}
|
||||||
- (void)windowDidBecomeKey:(NSNotification *)notification {
|
- (void)windowDidBecomeKey:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
GioView *view = (GioView *)window.contentView;
|
gio_onFocus((__bridge CFTypeRef)window.contentView, 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;
|
gio_onFocus((__bridge CFTypeRef)window.contentView, 0);
|
||||||
if ([window firstResponder] == view) {
|
|
||||||
gio_onFocus(view.handle, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) {
|
static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) {
|
||||||
NSPoint p = [view convertPoint:[event locationInWindow] fromView:nil];
|
NSPoint p = [view convertPoint:[event locationInWindow] fromView:nil];
|
||||||
if (!event.hasPreciseScrollingDeltas) {
|
if (!event.hasPreciseScrollingDeltas) {
|
||||||
// dx and dy are in rows and columns.
|
// dx and dy are in rows and columns.
|
||||||
@@ -71,9 +56,12 @@ static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFl
|
|||||||
}
|
}
|
||||||
// Origin is in the lower left corner. Convert to upper left.
|
// Origin is in the lower left corner. Convert to upper left.
|
||||||
CGFloat height = view.bounds.size.height;
|
CGFloat height = view.bounds.size.height;
|
||||||
gio_onMouse(view.handle, (__bridge CFTypeRef)event, typ, event.buttonNumber, p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]);
|
gio_onMouse((__bridge CFTypeRef)view, (__bridge CFTypeRef)event, typ, event.buttonNumber, p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@interface GioView : NSView <CALayerDelegate,NSTextInputClient>
|
||||||
|
@end
|
||||||
|
|
||||||
@implementation GioView
|
@implementation GioView
|
||||||
- (void)setFrameSize:(NSSize)newSize {
|
- (void)setFrameSize:(NSSize)newSize {
|
||||||
[super setFrameSize:newSize];
|
[super setFrameSize:newSize];
|
||||||
@@ -82,19 +70,21 @@ static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFl
|
|||||||
// drawRect is called when OpenGL is used, displayLayer otherwise.
|
// drawRect is called when OpenGL is used, displayLayer otherwise.
|
||||||
// Don't know why.
|
// Don't know why.
|
||||||
- (void)drawRect:(NSRect)r {
|
- (void)drawRect:(NSRect)r {
|
||||||
gio_onDraw(self.handle);
|
gio_onDraw((__bridge CFTypeRef)self);
|
||||||
}
|
}
|
||||||
- (void)displayLayer:(CALayer *)layer {
|
- (void)displayLayer:(CALayer *)layer {
|
||||||
layer.contentsScale = self.window.backingScaleFactor;
|
layer.contentsScale = self.window.backingScaleFactor;
|
||||||
gio_onDraw(self.handle);
|
gio_onDraw((__bridge CFTypeRef)self);
|
||||||
}
|
}
|
||||||
- (CALayer *)makeBackingLayer {
|
- (CALayer *)makeBackingLayer {
|
||||||
CALayer *layer = gio_layerFactory(self.presentWithTrans);
|
CALayer *layer = gio_layerFactory();
|
||||||
layer.delegate = self;
|
layer.delegate = self;
|
||||||
return layer;
|
return layer;
|
||||||
}
|
}
|
||||||
- (void)viewDidMoveToWindow {
|
- (void)viewDidMoveToWindow {
|
||||||
gio_onAttached(self.handle, self.window != nil ? 1 : 0);
|
if (self.window == nil) {
|
||||||
|
gio_onClose((__bridge CFTypeRef)self);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
- (void)mouseDown:(NSEvent *)event {
|
- (void)mouseDown:(NSEvent *)event {
|
||||||
handleMouse(self, event, MOUSE_DOWN, 0, 0);
|
handleMouse(self, event, MOUSE_DOWN, 0, 0);
|
||||||
@@ -132,37 +122,34 @@ static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFl
|
|||||||
handleMouse(self, event, MOUSE_SCROLL, dx, dy);
|
handleMouse(self, event, MOUSE_SCROLL, dx, dy);
|
||||||
}
|
}
|
||||||
- (void)keyDown:(NSEvent *)event {
|
- (void)keyDown:(NSEvent *)event {
|
||||||
NSString *keys = [event charactersIgnoringModifiers];
|
|
||||||
gio_onKeys(self.handle, (__bridge CFTypeRef)event, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true);
|
|
||||||
}
|
|
||||||
- (void)flagsChanged:(NSEvent *)event {
|
|
||||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
||||||
gio_onFlagsChanged(self.handle, [event modifierFlags]);
|
NSString *keys = [event charactersIgnoringModifiers];
|
||||||
|
gio_onKeys((__bridge CFTypeRef)self, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true);
|
||||||
}
|
}
|
||||||
- (void)keyUp:(NSEvent *)event {
|
- (void)keyUp:(NSEvent *)event {
|
||||||
NSString *keys = [event charactersIgnoringModifiers];
|
NSString *keys = [event charactersIgnoringModifiers];
|
||||||
gio_onKeys(self.handle, (__bridge CFTypeRef)event, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], false);
|
gio_onKeys((__bridge CFTypeRef)self, (__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((__bridge CFTypeRef)self, (__bridge CFTypeRef)string);
|
||||||
}
|
}
|
||||||
- (void)doCommandBySelector:(SEL)action {
|
- (void)doCommandBySelector:(SEL)sel {
|
||||||
if (!gio_onCommandBySelector(self.handle)) {
|
// Don't pass commands up the responder chain.
|
||||||
[super doCommandBySelector:action];
|
// They will end up in a beep.
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)hasMarkedText {
|
- (BOOL)hasMarkedText {
|
||||||
int res = gio_hasMarkedText(self.handle);
|
int res = gio_hasMarkedText((__bridge CFTypeRef)self);
|
||||||
return res ? YES : NO;
|
return res ? YES : NO;
|
||||||
}
|
}
|
||||||
- (NSRange)markedRange {
|
- (NSRange)markedRange {
|
||||||
return gio_markedRange(self.handle);
|
return gio_markedRange((__bridge CFTypeRef)self);
|
||||||
}
|
}
|
||||||
- (NSRange)selectedRange {
|
- (NSRange)selectedRange {
|
||||||
return gio_selectedRange(self.handle);
|
return gio_selectedRange((__bridge CFTypeRef)self);
|
||||||
}
|
}
|
||||||
- (void)unmarkText {
|
- (void)unmarkText {
|
||||||
gio_unmarkText(self.handle);
|
gio_unmarkText((__bridge CFTypeRef)self);
|
||||||
}
|
}
|
||||||
- (void)setMarkedText:(id)string
|
- (void)setMarkedText:(id)string
|
||||||
selectedRange:(NSRange)selRange
|
selectedRange:(NSRange)selRange
|
||||||
@@ -174,14 +161,14 @@ static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFl
|
|||||||
} else {
|
} else {
|
||||||
str = string;
|
str = string;
|
||||||
}
|
}
|
||||||
gio_setMarkedText(self.handle, (__bridge CFTypeRef)str, selRange, replaceRange);
|
gio_setMarkedText((__bridge CFTypeRef)self, (__bridge CFTypeRef)str, selRange, replaceRange);
|
||||||
}
|
}
|
||||||
- (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText {
|
- (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range
|
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range
|
||||||
actualRange:(NSRangePointer)actualRange {
|
actualRange:(NSRangePointer)actualRange {
|
||||||
NSString *str = CFBridgingRelease(gio_substringForProposedRange(self.handle, range, actualRange));
|
NSString *str = CFBridgingRelease(gio_substringForProposedRange((__bridge CFTypeRef)self, range, actualRange));
|
||||||
return [[NSAttributedString alloc] initWithString:str attributes:nil];
|
return [[NSAttributedString alloc] initWithString:str attributes:nil];
|
||||||
}
|
}
|
||||||
- (void)insertText:(id)string
|
- (void)insertText:(id)string
|
||||||
@@ -193,34 +180,17 @@ static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFl
|
|||||||
} else {
|
} else {
|
||||||
str = string;
|
str = string;
|
||||||
}
|
}
|
||||||
gio_insertText(self.handle, (__bridge CFTypeRef)str, replaceRange);
|
gio_insertText((__bridge CFTypeRef)self, (__bridge CFTypeRef)str, replaceRange);
|
||||||
}
|
}
|
||||||
- (NSUInteger)characterIndexForPoint:(NSPoint)p {
|
- (NSUInteger)characterIndexForPoint:(NSPoint)p {
|
||||||
return gio_characterIndexForPoint(self.handle, p);
|
return gio_characterIndexForPoint((__bridge CFTypeRef)self, p);
|
||||||
}
|
}
|
||||||
- (NSRect)firstRectForCharacterRange:(NSRange)rng
|
- (NSRect)firstRectForCharacterRange:(NSRange)rng
|
||||||
actualRange:(NSRangePointer)actual {
|
actualRange:(NSRangePointer)actual {
|
||||||
NSRect r = gio_firstRectForCharacterRange(self.handle, rng, actual);
|
NSRect r = gio_firstRectForCharacterRange((__bridge CFTypeRef)self, rng, actual);
|
||||||
r = [self convertRect:r toView:nil];
|
r = [self convertRect:r toView:nil];
|
||||||
return [[self window] convertRectToScreen:r];
|
return [[self window] convertRectToScreen:r];
|
||||||
}
|
}
|
||||||
- (void)applicationWillUnhide:(NSNotification *)notification {
|
|
||||||
gio_onDraw(self.handle);
|
|
||||||
}
|
|
||||||
- (void)applicationDidHide:(NSNotification *)notification {
|
|
||||||
gio_onDraw(self.handle);
|
|
||||||
}
|
|
||||||
- (void)dealloc {
|
|
||||||
gio_onDestroy(self.handle);
|
|
||||||
}
|
|
||||||
- (BOOL) becomeFirstResponder {
|
|
||||||
gio_onFocus(self.handle, 1);
|
|
||||||
return [super becomeFirstResponder];
|
|
||||||
}
|
|
||||||
- (BOOL) resignFirstResponder {
|
|
||||||
gio_onFocus(self.handle, 0);
|
|
||||||
return [super resignFirstResponder];
|
|
||||||
}
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
// Delegates are weakly referenced from their peers. Nothing
|
// Delegates are weakly referenced from their peers. Nothing
|
||||||
@@ -270,7 +240,7 @@ 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) {
|
void gio_trySetPrivateCursor(SEL cursorName, NSCursor* fallback) {
|
||||||
if ([NSCursor respondsToSelector:cursorName]) {
|
if ([NSCursor respondsToSelector:cursorName]) {
|
||||||
id object = [NSCursor performSelector:cursorName];
|
id object = [NSCursor performSelector:cursorName];
|
||||||
if ([object isKindOfClass:[NSCursor class]]) {
|
if ([object isKindOfClass:[NSCursor class]]) {
|
||||||
@@ -302,7 +272,7 @@ void gio_setCursor(NSUInteger curID) {
|
|||||||
break;
|
break;
|
||||||
case 6: // pointer.CursorAllScroll
|
case 6: // pointer.CursorAllScroll
|
||||||
// For some reason, using _moveCursor fails on Monterey.
|
// For some reason, using _moveCursor fails on Monterey.
|
||||||
// trySetPrivateCursor(@selector(_moveCursor), NSCursor.arrowCursor);
|
// gio_trySetPrivateCursor(@selector(_moveCursor), NSCursor.arrowCursor);
|
||||||
[NSCursor.arrowCursor set];
|
[NSCursor.arrowCursor set];
|
||||||
break;
|
break;
|
||||||
case 7: // pointer.CursorColResize
|
case 7: // pointer.CursorColResize
|
||||||
@@ -312,31 +282,33 @@ void gio_setCursor(NSUInteger curID) {
|
|||||||
[NSCursor.resizeUpDownCursor set];
|
[NSCursor.resizeUpDownCursor set];
|
||||||
break;
|
break;
|
||||||
case 9: // pointer.CursorGrab
|
case 9: // pointer.CursorGrab
|
||||||
[NSCursor.openHandCursor set];
|
// [NSCursor.openHandCursor set];
|
||||||
|
gio_trySetPrivateCursor(@selector(openHandCursor), NSCursor.arrowCursor);
|
||||||
break;
|
break;
|
||||||
case 10: // pointer.CursorGrabbing
|
case 10: // pointer.CursorGrabbing
|
||||||
[NSCursor.closedHandCursor set];
|
// [NSCursor.closedHandCursor set];
|
||||||
|
gio_trySetPrivateCursor(@selector(closedHandCursor), NSCursor.arrowCursor);
|
||||||
break;
|
break;
|
||||||
case 11: // pointer.CursorNotAllowed
|
case 11: // pointer.CursorNotAllowed
|
||||||
[NSCursor.operationNotAllowedCursor set];
|
[NSCursor.operationNotAllowedCursor set];
|
||||||
break;
|
break;
|
||||||
case 12: // pointer.CursorWait
|
case 12: // pointer.CursorWait
|
||||||
trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
gio_trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
||||||
break;
|
break;
|
||||||
case 13: // pointer.CursorProgress
|
case 13: // pointer.CursorProgress
|
||||||
trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
gio_trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
||||||
break;
|
break;
|
||||||
case 14: // pointer.CursorNorthWestResize
|
case 14: // pointer.CursorNorthWestResize
|
||||||
trySetPrivateCursor(@selector(_windowResizeNorthWestCursor), NSCursor.resizeUpDownCursor);
|
gio_trySetPrivateCursor(@selector(_windowResizeNorthWestCursor), NSCursor.resizeUpDownCursor);
|
||||||
break;
|
break;
|
||||||
case 15: // pointer.CursorNorthEastResize
|
case 15: // pointer.CursorNorthEastResize
|
||||||
trySetPrivateCursor(@selector(_windowResizeNorthEastCursor), NSCursor.resizeUpDownCursor);
|
gio_trySetPrivateCursor(@selector(_windowResizeNorthEastCursor), NSCursor.resizeUpDownCursor);
|
||||||
break;
|
break;
|
||||||
case 16: // pointer.CursorSouthWestResize
|
case 16: // pointer.CursorSouthWestResize
|
||||||
trySetPrivateCursor(@selector(_windowResizeSouthWestCursor), NSCursor.resizeUpDownCursor);
|
gio_trySetPrivateCursor(@selector(_windowResizeSouthWestCursor), NSCursor.resizeUpDownCursor);
|
||||||
break;
|
break;
|
||||||
case 17: // pointer.CursorSouthEastResize
|
case 17: // pointer.CursorSouthEastResize
|
||||||
trySetPrivateCursor(@selector(_windowResizeSouthEastCursor), NSCursor.resizeUpDownCursor);
|
gio_trySetPrivateCursor(@selector(_windowResizeSouthEastCursor), NSCursor.resizeUpDownCursor);
|
||||||
break;
|
break;
|
||||||
case 18: // pointer.CursorNorthSouthResize
|
case 18: // pointer.CursorNorthSouthResize
|
||||||
[NSCursor.resizeUpDownCursor set];
|
[NSCursor.resizeUpDownCursor set];
|
||||||
@@ -357,10 +329,10 @@ void gio_setCursor(NSUInteger curID) {
|
|||||||
[NSCursor.resizeDownCursor set];
|
[NSCursor.resizeDownCursor set];
|
||||||
break;
|
break;
|
||||||
case 24: // pointer.CursorNorthEastSouthWestResize
|
case 24: // pointer.CursorNorthEastSouthWestResize
|
||||||
trySetPrivateCursor(@selector(_windowResizeNorthEastSouthWestCursor), NSCursor.resizeUpDownCursor);
|
gio_trySetPrivateCursor(@selector(_windowResizeNorthEastSouthWestCursor), NSCursor.resizeUpDownCursor);
|
||||||
break;
|
break;
|
||||||
case 25: // pointer.CursorNorthWestSouthEastResize
|
case 25: // pointer.CursorNorthWestSouthEastResize
|
||||||
trySetPrivateCursor(@selector(_windowResizeNorthWestSouthEastCursor), NSCursor.resizeUpDownCursor);
|
gio_trySetPrivateCursor(@selector(_windowResizeNorthWestSouthEastCursor), NSCursor.resizeUpDownCursor);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
[NSCursor.arrowCursor set];
|
[NSCursor.arrowCursor set];
|
||||||
@@ -369,7 +341,7 @@ void gio_setCursor(NSUInteger curID) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height) {
|
CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
NSRect rect = NSMakeRect(0, 0, width, height);
|
NSRect rect = NSMakeRect(0, 0, width, height);
|
||||||
NSUInteger styleMask = NSTitledWindowMask |
|
NSUInteger styleMask = NSTitledWindowMask |
|
||||||
@@ -381,45 +353,42 @@ CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height) {
|
|||||||
styleMask:styleMask
|
styleMask:styleMask
|
||||||
backing:NSBackingStoreBuffered
|
backing:NSBackingStoreBuffered
|
||||||
defer:NO];
|
defer:NO];
|
||||||
|
if (minWidth > 0 || minHeight > 0) {
|
||||||
|
window.contentMinSize = NSMakeSize(minWidth, minHeight);
|
||||||
|
}
|
||||||
|
if (maxWidth > 0 || maxHeight > 0) {
|
||||||
|
window.contentMaxSize = NSMakeSize(maxWidth, maxHeight);
|
||||||
|
}
|
||||||
[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(int presentWithTrans) {
|
CFTypeRef gio_createView(void) {
|
||||||
@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;
|
||||||
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:view
|
|
||||||
selector:@selector(applicationWillUnhide:)
|
|
||||||
name:NSApplicationWillUnhideNotification
|
|
||||||
object:nil];
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:view
|
|
||||||
selector:@selector(applicationDidHide:)
|
|
||||||
name:NSApplicationDidHideNotification
|
|
||||||
object:nil];
|
|
||||||
return CFBridgingRetain(view);
|
return CFBridgingRetain(view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
|
|
||||||
@autoreleasepool {
|
|
||||||
GioView *v = (__bridge GioView *)viewRef;
|
|
||||||
v.handle = handle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@implementation GioAppDelegate
|
@implementation GioAppDelegate
|
||||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||||
[NSApp activateIgnoringOtherApps:YES];
|
[NSApp activateIgnoringOtherApps:YES];
|
||||||
|
gio_onFinishLaunching();
|
||||||
|
}
|
||||||
|
- (void)applicationDidHide:(NSNotification *)aNotification {
|
||||||
|
gio_onAppHide();
|
||||||
|
}
|
||||||
|
- (void)applicationWillUnhide:(NSNotification *)notification {
|
||||||
|
gio_onAppShow();
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -450,25 +419,3 @@ void gio_main() {
|
|||||||
[NSApp run];
|
[NSApp run];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@interface AppListener : NSObject
|
|
||||||
@end
|
|
||||||
|
|
||||||
static AppListener *appListener;
|
|
||||||
|
|
||||||
@implementation AppListener
|
|
||||||
- (void)launchFinished:(NSNotification *)notification {
|
|
||||||
appListener = nil;
|
|
||||||
gio_onFinishLaunching();
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
void gio_init() {
|
|
||||||
@autoreleasepool {
|
|
||||||
appListener = [[AppListener alloc] init];
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:appListener
|
|
||||||
selector:@selector(launchFinished:)
|
|
||||||
name:NSApplicationDidFinishLaunchingNotification
|
|
||||||
object:nil];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+12
-11
@@ -12,6 +12,13 @@ import (
|
|||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ViewEvent provides handles to the underlying window objects for the
|
||||||
|
// current display protocol.
|
||||||
|
type ViewEvent interface {
|
||||||
|
implementsViewEvent()
|
||||||
|
ImplementsEvent()
|
||||||
|
}
|
||||||
|
|
||||||
type X11ViewEvent struct {
|
type X11ViewEvent struct {
|
||||||
// Display is a pointer to the X11 Display created by XOpenDisplay.
|
// Display is a pointer to the X11 Display created by XOpenDisplay.
|
||||||
Display unsafe.Pointer
|
Display unsafe.Pointer
|
||||||
@@ -21,9 +28,6 @@ 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.
|
||||||
@@ -34,9 +38,6 @@ type WaylandViewEvent struct {
|
|||||||
|
|
||||||
func (WaylandViewEvent) implementsViewEvent() {}
|
func (WaylandViewEvent) implementsViewEvent() {}
|
||||||
func (WaylandViewEvent) ImplementsEvent() {}
|
func (WaylandViewEvent) ImplementsEvent() {}
|
||||||
func (w WaylandViewEvent) Valid() bool {
|
|
||||||
return w != (WaylandViewEvent{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func osMain() {
|
func osMain() {
|
||||||
select {}
|
select {}
|
||||||
@@ -48,7 +49,7 @@ type windowDriver func(*callbacks, []Option) error
|
|||||||
// let each driver initialize these variables with their own version of createWindow.
|
// let each driver initialize these variables with their own version of createWindow.
|
||||||
var wlDriver, x11Driver windowDriver
|
var wlDriver, x11Driver windowDriver
|
||||||
|
|
||||||
func newWindow(window *callbacks, options []Option) {
|
func newWindow(window *callbacks, options []Option) error {
|
||||||
var errFirst error
|
var errFirst error
|
||||||
for _, d := range []windowDriver{wlDriver, x11Driver} {
|
for _, d := range []windowDriver{wlDriver, x11Driver} {
|
||||||
if d == nil {
|
if d == nil {
|
||||||
@@ -56,16 +57,16 @@ func newWindow(window *callbacks, options []Option) {
|
|||||||
}
|
}
|
||||||
err := d(window, options)
|
err := d(window, options)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
if errFirst == nil {
|
if errFirst == nil {
|
||||||
errFirst = err
|
errFirst = err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if errFirst == nil {
|
if errFirst != nil {
|
||||||
errFirst = errors.New("app: no window driver available")
|
return errFirst
|
||||||
}
|
}
|
||||||
window.ProcessEvent(DestroyEvent{Err: errFirst})
|
return errors.New("app: no window driver available")
|
||||||
}
|
}
|
||||||
|
|
||||||
// xCursor contains mapping from pointer.Cursor to XCursor.
|
// xCursor contains mapping from pointer.Cursor to XCursor.
|
||||||
|
|||||||
+131
-202
@@ -15,7 +15,6 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -26,12 +25,10 @@ import (
|
|||||||
"gioui.org/app/internal/xkb"
|
"gioui.org/app/internal/xkb"
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/internal/fling"
|
"gioui.org/internal/fling"
|
||||||
"gioui.org/io/event"
|
"gioui.org/io/clipboard"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
"gioui.org/io/transfer"
|
|
||||||
"gioui.org/op"
|
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -100,9 +97,7 @@ type wlDisplay struct {
|
|||||||
read, write int
|
read, write int
|
||||||
}
|
}
|
||||||
|
|
||||||
repeat repeatState
|
repeat repeatState
|
||||||
poller poller
|
|
||||||
readClipClose chan struct{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type wlSeat struct {
|
type wlSeat struct {
|
||||||
@@ -116,8 +111,6 @@ type wlSeat struct {
|
|||||||
|
|
||||||
// The most recent input serial.
|
// The most recent input serial.
|
||||||
serial C.uint32_t
|
serial C.uint32_t
|
||||||
// The most recent pointer enter serial.
|
|
||||||
pointerSerial C.uint32_t
|
|
||||||
|
|
||||||
pointerFocus *window
|
pointerFocus *window
|
||||||
keyboardFocus *window
|
keyboardFocus *window
|
||||||
@@ -144,7 +137,7 @@ type repeatState struct {
|
|||||||
delay time.Duration
|
delay time.Duration
|
||||||
|
|
||||||
key uint32
|
key uint32
|
||||||
win *window
|
win *callbacks
|
||||||
stopC chan struct{}
|
stopC chan struct{}
|
||||||
|
|
||||||
start time.Duration
|
start time.Duration
|
||||||
@@ -156,6 +149,7 @@ type repeatState struct {
|
|||||||
type window struct {
|
type window struct {
|
||||||
w *callbacks
|
w *callbacks
|
||||||
disp *wlDisplay
|
disp *wlDisplay
|
||||||
|
seat *wlSeat
|
||||||
surf *C.struct_wl_surface
|
surf *C.struct_wl_surface
|
||||||
wmSurf *C.struct_xdg_surface
|
wmSurf *C.struct_xdg_surface
|
||||||
topLvl *C.struct_xdg_toplevel
|
topLvl *C.struct_xdg_toplevel
|
||||||
@@ -200,10 +194,12 @@ type window struct {
|
|||||||
dir f32.Point
|
dir f32.Point
|
||||||
}
|
}
|
||||||
|
|
||||||
configured bool
|
stage system.Stage
|
||||||
|
dead bool
|
||||||
lastFrameCallback *C.struct_wl_callback
|
lastFrameCallback *C.struct_wl_callback
|
||||||
|
|
||||||
animating bool
|
animating bool
|
||||||
|
redraw bool
|
||||||
// The most recent configure serial waiting to be ack'ed.
|
// The most recent configure serial waiting to be ack'ed.
|
||||||
serial C.uint32_t
|
serial C.uint32_t
|
||||||
scale int
|
scale int
|
||||||
@@ -213,11 +209,9 @@ type window struct {
|
|||||||
wsize image.Point // window config size before going fullscreen or maximized
|
wsize image.Point // window config size before going fullscreen or maximized
|
||||||
inCompositor bool // window is moving or being resized
|
inCompositor bool // window is moving or being resized
|
||||||
|
|
||||||
clipReads chan transfer.DataEvent
|
clipReads chan clipboard.Event
|
||||||
|
|
||||||
wakeups chan struct{}
|
wakeups chan struct{}
|
||||||
|
|
||||||
closing bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type poller struct {
|
type poller struct {
|
||||||
@@ -266,17 +260,25 @@ func newWLWindow(callbacks *callbacks, options []Option) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
w.w = callbacks
|
w.w = callbacks
|
||||||
w.w.SetDriver(w)
|
go func() {
|
||||||
|
defer d.destroy()
|
||||||
|
defer w.destroy()
|
||||||
|
|
||||||
// Finish and commit setup from createNativeWindow.
|
w.w.SetDriver(w)
|
||||||
w.Configure(options)
|
|
||||||
w.draw(true)
|
|
||||||
C.wl_surface_commit(w.surf)
|
|
||||||
|
|
||||||
w.ProcessEvent(WaylandViewEvent{
|
// Finish and commit setup from createNativeWindow.
|
||||||
Display: unsafe.Pointer(w.display()),
|
w.Configure(options)
|
||||||
Surface: unsafe.Pointer(w.surf),
|
C.wl_surface_commit(w.surf)
|
||||||
})
|
|
||||||
|
w.w.Event(WaylandViewEvent{
|
||||||
|
Display: unsafe.Pointer(w.display()),
|
||||||
|
Surface: unsafe.Pointer(w.surf),
|
||||||
|
})
|
||||||
|
|
||||||
|
err := w.loop()
|
||||||
|
w.w.Event(WaylandViewEvent{})
|
||||||
|
w.w.Event(system.DestroyEvent{Err: err})
|
||||||
|
}()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,7 +354,7 @@ func (d *wlDisplay) createNativeWindow(options []Option) (*window, error) {
|
|||||||
ppdp: ppdp,
|
ppdp: ppdp,
|
||||||
ppsp: ppdp,
|
ppsp: ppdp,
|
||||||
wakeups: make(chan struct{}, 1),
|
wakeups: make(chan struct{}, 1),
|
||||||
clipReads: make(chan transfer.DataEvent, 1),
|
clipReads: make(chan clipboard.Event, 1),
|
||||||
}
|
}
|
||||||
w.surf = C.wl_compositor_create_surface(d.compositor)
|
w.surf = C.wl_compositor_create_surface(d.compositor)
|
||||||
if w.surf == nil {
|
if w.surf == nil {
|
||||||
@@ -547,15 +549,15 @@ func gio_onSeatName(data unsafe.Pointer, seat *C.struct_wl_seat, name *C.char) {
|
|||||||
func gio_onXdgSurfaceConfigure(data unsafe.Pointer, wmSurf *C.struct_xdg_surface, serial C.uint32_t) {
|
func gio_onXdgSurfaceConfigure(data unsafe.Pointer, wmSurf *C.struct_xdg_surface, serial C.uint32_t) {
|
||||||
w := callbackLoad(data).(*window)
|
w := callbackLoad(data).(*window)
|
||||||
w.serial = serial
|
w.serial = serial
|
||||||
|
w.redraw = true
|
||||||
C.xdg_surface_ack_configure(wmSurf, serial)
|
C.xdg_surface_ack_configure(wmSurf, serial)
|
||||||
w.configured = true
|
w.setStage(system.StageRunning)
|
||||||
w.draw(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onToplevelClose
|
//export gio_onToplevelClose
|
||||||
func gio_onToplevelClose(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel) {
|
func gio_onToplevelClose(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel) {
|
||||||
w := callbackLoad(data).(*window)
|
w := callbackLoad(data).(*window)
|
||||||
w.closing = true
|
w.dead = true
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onToplevelConfigure
|
//export gio_onToplevelConfigure
|
||||||
@@ -584,8 +586,8 @@ func gio_onToplevelDecorationConfigure(data unsafe.Pointer, deco *C.struct_zxdg_
|
|||||||
} else {
|
} else {
|
||||||
w.size.Y += int(w.config.decoHeight)
|
w.size.Y += int(w.config.decoHeight)
|
||||||
}
|
}
|
||||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
w.w.Event(ConfigEvent{Config: w.config})
|
||||||
w.draw(true)
|
w.redraw = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -643,7 +645,7 @@ func gio_onSurfaceEnter(data unsafe.Pointer, surf *C.struct_wl_surface, output *
|
|||||||
if w.config.Mode == Minimized {
|
if w.config.Mode == Minimized {
|
||||||
// Minimized window got brought back up: it is no longer so.
|
// Minimized window got brought back up: it is no longer so.
|
||||||
w.config.Mode = Windowed
|
w.config.Mode = Windowed
|
||||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
w.w.Event(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -788,7 +790,7 @@ func gio_onTouchDown(data unsafe.Pointer, touch *C.struct_wl_touch, serial, t C.
|
|||||||
X: fromFixed(x) * float32(w.scale),
|
X: fromFixed(x) * float32(w.scale),
|
||||||
Y: fromFixed(y) * float32(w.scale),
|
Y: fromFixed(y) * float32(w.scale),
|
||||||
}
|
}
|
||||||
w.ProcessEvent(pointer.Event{
|
w.w.Event(pointer.Event{
|
||||||
Kind: pointer.Press,
|
Kind: pointer.Press,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
Position: w.lastTouch,
|
Position: w.lastTouch,
|
||||||
@@ -804,7 +806,7 @@ func gio_onTouchUp(data unsafe.Pointer, touch *C.struct_wl_touch, serial, t C.ui
|
|||||||
s.serial = serial
|
s.serial = serial
|
||||||
w := s.touchFoci[id]
|
w := s.touchFoci[id]
|
||||||
delete(s.touchFoci, id)
|
delete(s.touchFoci, id)
|
||||||
w.ProcessEvent(pointer.Event{
|
w.w.Event(pointer.Event{
|
||||||
Kind: pointer.Release,
|
Kind: pointer.Release,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
Position: w.lastTouch,
|
Position: w.lastTouch,
|
||||||
@@ -822,7 +824,7 @@ func gio_onTouchMotion(data unsafe.Pointer, touch *C.struct_wl_touch, t C.uint32
|
|||||||
X: fromFixed(x) * float32(w.scale),
|
X: fromFixed(x) * float32(w.scale),
|
||||||
Y: fromFixed(y) * float32(w.scale),
|
Y: fromFixed(y) * float32(w.scale),
|
||||||
}
|
}
|
||||||
w.ProcessEvent(pointer.Event{
|
w.w.Event(pointer.Event{
|
||||||
Kind: pointer.Move,
|
Kind: pointer.Move,
|
||||||
Position: w.lastTouch,
|
Position: w.lastTouch,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
@@ -841,7 +843,7 @@ func gio_onTouchCancel(data unsafe.Pointer, touch *C.struct_wl_touch) {
|
|||||||
s := callbackLoad(data).(*wlSeat)
|
s := callbackLoad(data).(*wlSeat)
|
||||||
for id, w := range s.touchFoci {
|
for id, w := range s.touchFoci {
|
||||||
delete(s.touchFoci, id)
|
delete(s.touchFoci, id)
|
||||||
w.ProcessEvent(pointer.Event{
|
w.w.Event(pointer.Event{
|
||||||
Kind: pointer.Cancel,
|
Kind: pointer.Cancel,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
})
|
})
|
||||||
@@ -852,8 +854,8 @@ func gio_onTouchCancel(data unsafe.Pointer, touch *C.struct_wl_touch) {
|
|||||||
func gio_onPointerEnter(data unsafe.Pointer, pointer *C.struct_wl_pointer, serial C.uint32_t, surf *C.struct_wl_surface, x, y C.wl_fixed_t) {
|
func gio_onPointerEnter(data unsafe.Pointer, pointer *C.struct_wl_pointer, serial C.uint32_t, surf *C.struct_wl_surface, x, y C.wl_fixed_t) {
|
||||||
s := callbackLoad(data).(*wlSeat)
|
s := callbackLoad(data).(*wlSeat)
|
||||||
s.serial = serial
|
s.serial = serial
|
||||||
s.pointerSerial = serial
|
|
||||||
w := callbackLoad(unsafe.Pointer(surf)).(*window)
|
w := callbackLoad(unsafe.Pointer(surf)).(*window)
|
||||||
|
w.seat = s
|
||||||
s.pointerFocus = w
|
s.pointerFocus = w
|
||||||
w.setCursor(pointer, serial)
|
w.setCursor(pointer, serial)
|
||||||
w.lastPos = f32.Point{X: fromFixed(x), Y: fromFixed(y)}
|
w.lastPos = f32.Point{X: fromFixed(x), Y: fromFixed(y)}
|
||||||
@@ -862,12 +864,12 @@ func gio_onPointerEnter(data unsafe.Pointer, pointer *C.struct_wl_pointer, seria
|
|||||||
//export gio_onPointerLeave
|
//export gio_onPointerLeave
|
||||||
func gio_onPointerLeave(data unsafe.Pointer, p *C.struct_wl_pointer, serial C.uint32_t, surf *C.struct_wl_surface) {
|
func gio_onPointerLeave(data unsafe.Pointer, p *C.struct_wl_pointer, serial C.uint32_t, surf *C.struct_wl_surface) {
|
||||||
w := callbackLoad(unsafe.Pointer(surf)).(*window)
|
w := callbackLoad(unsafe.Pointer(surf)).(*window)
|
||||||
|
w.seat = nil
|
||||||
s := callbackLoad(data).(*wlSeat)
|
s := callbackLoad(data).(*wlSeat)
|
||||||
s.serial = serial
|
s.serial = serial
|
||||||
s.pointerFocus = nil
|
|
||||||
if w.inCompositor {
|
if w.inCompositor {
|
||||||
w.inCompositor = false
|
w.inCompositor = false
|
||||||
w.ProcessEvent(pointer.Event{Kind: pointer.Cancel})
|
w.w.Event(pointer.Event{Kind: pointer.Cancel})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -884,13 +886,11 @@ func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t,
|
|||||||
s := callbackLoad(data).(*wlSeat)
|
s := callbackLoad(data).(*wlSeat)
|
||||||
s.serial = serial
|
s.serial = serial
|
||||||
w := s.pointerFocus
|
w := s.pointerFocus
|
||||||
// From Linux: include/uapi/linux/input-event-codes.h
|
// From linux-event-codes.h.
|
||||||
const (
|
const (
|
||||||
BTN_LEFT = 0x110
|
BTN_LEFT = 0x110
|
||||||
BTN_RIGHT = 0x111
|
BTN_RIGHT = 0x111
|
||||||
BTN_MIDDLE = 0x112
|
BTN_MIDDLE = 0x112
|
||||||
BTN_SIDE = 0x113
|
|
||||||
BTN_EXTRA = 0x114
|
|
||||||
)
|
)
|
||||||
var btn pointer.Buttons
|
var btn pointer.Buttons
|
||||||
switch wbtn {
|
switch wbtn {
|
||||||
@@ -900,10 +900,6 @@ func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t,
|
|||||||
btn = pointer.ButtonSecondary
|
btn = pointer.ButtonSecondary
|
||||||
case BTN_MIDDLE:
|
case BTN_MIDDLE:
|
||||||
btn = pointer.ButtonTertiary
|
btn = pointer.ButtonTertiary
|
||||||
case BTN_SIDE:
|
|
||||||
btn = pointer.ButtonQuaternary
|
|
||||||
case BTN_EXTRA:
|
|
||||||
btn = pointer.ButtonQuinary
|
|
||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -934,7 +930,7 @@ func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t,
|
|||||||
}
|
}
|
||||||
w.flushScroll()
|
w.flushScroll()
|
||||||
w.resetFling()
|
w.resetFling()
|
||||||
w.ProcessEvent(pointer.Event{
|
w.w.Event(pointer.Event{
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Buttons: w.pointerBtns,
|
Buttons: w.pointerBtns,
|
||||||
@@ -970,9 +966,6 @@ func gio_onPointerAxis(data unsafe.Pointer, p *C.struct_wl_pointer, t, axis C.ui
|
|||||||
func gio_onPointerFrame(data unsafe.Pointer, p *C.struct_wl_pointer) {
|
func gio_onPointerFrame(data unsafe.Pointer, p *C.struct_wl_pointer) {
|
||||||
s := callbackLoad(data).(*wlSeat)
|
s := callbackLoad(data).(*wlSeat)
|
||||||
w := s.pointerFocus
|
w := s.pointerFocus
|
||||||
if w == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.flushScroll()
|
w.flushScroll()
|
||||||
w.flushFling()
|
w.flushFling()
|
||||||
}
|
}
|
||||||
@@ -1025,34 +1018,23 @@ func gio_onPointerAxisDiscrete(data unsafe.Pointer, p *C.struct_wl_pointer, axis
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) ReadClipboard() {
|
func (w *window) ReadClipboard() {
|
||||||
if w.disp.readClipClose != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.disp.readClipClose = make(chan struct{})
|
|
||||||
r, err := w.disp.readClipboard()
|
r, err := w.disp.readClipboard()
|
||||||
|
// Send empty responses on unavailable clipboards or errors.
|
||||||
if r == nil || err != nil {
|
if r == nil || err != nil {
|
||||||
|
w.w.Event(clipboard.Event{})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Don't let slow clipboard transfers block event loop.
|
// Don't let slow clipboard transfers block event loop.
|
||||||
go func() {
|
go func() {
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
data, _ := io.ReadAll(r)
|
data, _ := io.ReadAll(r)
|
||||||
e := transfer.DataEvent{
|
w.clipReads <- clipboard.Event{Text: string(data)}
|
||||||
Type: "application/text",
|
w.Wakeup()
|
||||||
Open: func() io.ReadCloser {
|
|
||||||
return io.NopCloser(bytes.NewReader(data))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case w.clipReads <- e:
|
|
||||||
w.disp.wakeup()
|
|
||||||
case <-w.disp.readClipClose:
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) WriteClipboard(mime string, s []byte) {
|
func (w *window) WriteClipboard(s string) {
|
||||||
w.disp.writeClipboard(s)
|
w.disp.writeClipboard([]byte(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Configure(options []Option) {
|
func (w *window) Configure(options []Option) {
|
||||||
@@ -1110,7 +1092,8 @@ func (w *window) Configure(options []Option) {
|
|||||||
w.config.MaxSize = cnf.MaxSize
|
w.config.MaxSize = cnf.MaxSize
|
||||||
w.setWindowConstraints()
|
w.setWindowConstraints()
|
||||||
}
|
}
|
||||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
w.w.Event(ConfigEvent{Config: w.config})
|
||||||
|
w.redraw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) setWindowConstraints() {
|
func (w *window) setWindowConstraints() {
|
||||||
@@ -1147,23 +1130,22 @@ 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.closing = true
|
w.dead = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) move(serial C.uint32_t) {
|
func (w *window) move(serial C.uint32_t) {
|
||||||
s := w.disp.seat
|
s := w.seat
|
||||||
if w.inCompositor || s.pointerFocus != w {
|
if !w.inCompositor && s != nil {
|
||||||
return
|
w.inCompositor = true
|
||||||
|
C.xdg_toplevel_move(w.topLvl, s.seat, serial)
|
||||||
}
|
}
|
||||||
w.inCompositor = true
|
|
||||||
C.xdg_toplevel_move(w.topLvl, s.seat, serial)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) resize(serial, edge C.uint32_t) {
|
func (w *window) resize(serial, edge C.uint32_t) {
|
||||||
s := w.disp.seat
|
s := w.seat
|
||||||
if w.inCompositor || s.pointerFocus != w {
|
if w.inCompositor || s == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.inCompositor = true
|
w.inCompositor = true
|
||||||
@@ -1176,12 +1158,11 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) updateCursor() {
|
func (w *window) updateCursor() {
|
||||||
s := w.disp.seat
|
ptr := w.disp.seat.pointer
|
||||||
ptr := s.pointer
|
if ptr == nil {
|
||||||
if ptr == nil || s.pointerFocus != w {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.setCursor(ptr, s.pointerSerial)
|
w.setCursor(ptr, w.serial)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) setCursor(pointer *C.struct_wl_pointer, serial C.uint32_t) {
|
func (w *window) setCursor(pointer *C.struct_wl_pointer, serial C.uint32_t) {
|
||||||
@@ -1190,7 +1171,7 @@ func (w *window) setCursor(pointer *C.struct_wl_pointer, serial C.uint32_t) {
|
|||||||
c = w.cursor.cursor
|
c = w.cursor.cursor
|
||||||
}
|
}
|
||||||
if c == nil {
|
if c == nil {
|
||||||
C.wl_pointer_set_cursor(pointer, serial, nil, 0, 0)
|
C.wl_pointer_set_cursor(pointer, w.serial, nil, 0, 0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Get images[0].
|
// Get images[0].
|
||||||
@@ -1232,8 +1213,7 @@ func gio_onKeyboardEnter(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, se
|
|||||||
w := callbackLoad(unsafe.Pointer(surf)).(*window)
|
w := callbackLoad(unsafe.Pointer(surf)).(*window)
|
||||||
s.keyboardFocus = w
|
s.keyboardFocus = w
|
||||||
s.disp.repeat.Stop(0)
|
s.disp.repeat.Stop(0)
|
||||||
w.config.Focused = true
|
w.w.Event(key.FocusEvent{Focus: true})
|
||||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onKeyboardLeave
|
//export gio_onKeyboardLeave
|
||||||
@@ -1242,8 +1222,7 @@ func gio_onKeyboardLeave(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, se
|
|||||||
s.serial = serial
|
s.serial = serial
|
||||||
s.disp.repeat.Stop(0)
|
s.disp.repeat.Stop(0)
|
||||||
w := s.keyboardFocus
|
w := s.keyboardFocus
|
||||||
w.config.Focused = false
|
w.w.Event(key.FocusEvent{Focus: false})
|
||||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onKeyboardKey
|
//export gio_onKeyboardKey
|
||||||
@@ -1261,7 +1240,7 @@ func gio_onKeyboardKey(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, seri
|
|||||||
// There's no support for IME yet.
|
// There's no support for IME yet.
|
||||||
w.w.EditorInsert(ee.Text)
|
w.w.EditorInsert(ee.Text)
|
||||||
} else {
|
} else {
|
||||||
w.ProcessEvent(e)
|
w.w.Event(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if state != C.WL_KEYBOARD_KEY_STATE_PRESSED {
|
if state != C.WL_KEYBOARD_KEY_STATE_PRESSED {
|
||||||
@@ -1296,7 +1275,7 @@ func (r *repeatState) Start(w *window, keyCode uint32, t time.Duration) {
|
|||||||
r.now = 0
|
r.now = 0
|
||||||
r.stopC = stopC
|
r.stopC = stopC
|
||||||
r.key = keyCode
|
r.key = keyCode
|
||||||
r.win = w
|
r.win = w.w
|
||||||
rate, delay := r.rate, r.delay
|
rate, delay := r.rate, r.delay
|
||||||
go func() {
|
go func() {
|
||||||
timer := time.NewTimer(delay)
|
timer := time.NewTimer(delay)
|
||||||
@@ -1354,9 +1333,9 @@ func (r *repeatState) Repeat(d *wlDisplay) {
|
|||||||
for _, e := range d.xkb.DispatchKey(r.key, key.Press) {
|
for _, e := range d.xkb.DispatchKey(r.key, key.Press) {
|
||||||
if ee, ok := e.(key.EditEvent); ok {
|
if ee, ok := e.(key.EditEvent); ok {
|
||||||
// There's no support for IME yet.
|
// There's no support for IME yet.
|
||||||
r.win.w.EditorInsert(ee.Text)
|
r.win.EditorInsert(ee.Text)
|
||||||
} else {
|
} else {
|
||||||
r.win.ProcessEvent(e)
|
r.win.Event(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
r.last += delay
|
r.last += delay
|
||||||
@@ -1369,68 +1348,28 @@ func gio_onFrameDone(data unsafe.Pointer, callback *C.struct_wl_callback, t C.ui
|
|||||||
w := callbackLoad(data).(*window)
|
w := callbackLoad(data).(*window)
|
||||||
if w.lastFrameCallback == callback {
|
if w.lastFrameCallback == callback {
|
||||||
w.lastFrameCallback = nil
|
w.lastFrameCallback = nil
|
||||||
w.draw(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) close(err error) {
|
func (w *window) loop() error {
|
||||||
w.ProcessEvent(WaylandViewEvent{})
|
var p poller
|
||||||
w.ProcessEvent(DestroyEvent{Err: err})
|
|
||||||
w.destroy()
|
|
||||||
w.disp.destroy()
|
|
||||||
w.disp = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) dispatch() {
|
|
||||||
if w.disp == nil {
|
|
||||||
<-w.wakeups
|
|
||||||
w.w.Invalidate()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := w.disp.dispatch(); err != nil || w.closing {
|
|
||||||
w.close(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case e := <-w.clipReads:
|
|
||||||
w.disp.readClipClose = nil
|
|
||||||
w.ProcessEvent(e)
|
|
||||||
case <-w.wakeups:
|
|
||||||
w.w.Invalidate()
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) ProcessEvent(e event.Event) {
|
|
||||||
w.w.ProcessEvent(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) Event() event.Event {
|
|
||||||
for {
|
for {
|
||||||
evt, ok := w.w.nextEvent()
|
if err := w.disp.dispatch(&p); err != nil {
|
||||||
if !ok {
|
return err
|
||||||
w.dispatch()
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
return evt
|
select {
|
||||||
|
case e := <-w.clipReads:
|
||||||
|
w.w.Event(e)
|
||||||
|
case <-w.wakeups:
|
||||||
|
w.w.Event(wakeupEvent{})
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
if w.dead {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
w.draw()
|
||||||
}
|
}
|
||||||
}
|
return nil
|
||||||
|
|
||||||
func (w *window) Invalidate() {
|
|
||||||
select {
|
|
||||||
case w.wakeups <- struct{}{}:
|
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.disp.wakeup()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) Run(f func()) {
|
|
||||||
f()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) Frame(frame *op.Ops) {
|
|
||||||
w.w.ProcessFrame(frame, nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// bindDataDevice initializes the dataDev field if and only if both
|
// bindDataDevice initializes the dataDev field if and only if both
|
||||||
@@ -1446,21 +1385,13 @@ func (d *wlDisplay) bindDataDevice() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *wlDisplay) dispatch() error {
|
func (d *wlDisplay) dispatch(p *poller) error {
|
||||||
// wl_display_prepare_read records the current thread for
|
|
||||||
// use in wl_display_read_events or wl_display_cancel_events.
|
|
||||||
runtime.LockOSThread()
|
|
||||||
defer runtime.UnlockOSThread()
|
|
||||||
|
|
||||||
dispfd := C.wl_display_get_fd(d.disp)
|
dispfd := C.wl_display_get_fd(d.disp)
|
||||||
// Poll for events and notifications.
|
// Poll for events and notifications.
|
||||||
pollfds := append(d.poller.pollfds[:0],
|
pollfds := append(p.pollfds[:0],
|
||||||
syscall.PollFd{Fd: int32(dispfd), Events: syscall.POLLIN | syscall.POLLERR},
|
syscall.PollFd{Fd: int32(dispfd), Events: syscall.POLLIN | syscall.POLLERR},
|
||||||
syscall.PollFd{Fd: int32(d.notify.read), Events: syscall.POLLIN | syscall.POLLERR},
|
syscall.PollFd{Fd: int32(d.notify.read), Events: syscall.POLLIN | syscall.POLLERR},
|
||||||
)
|
)
|
||||||
for C.wl_display_prepare_read(d.disp) != 0 {
|
|
||||||
C.wl_display_dispatch_pending(d.disp)
|
|
||||||
}
|
|
||||||
dispFd := &pollfds[0]
|
dispFd := &pollfds[0]
|
||||||
if ret, err := C.wl_display_flush(d.disp); ret < 0 {
|
if ret, err := C.wl_display_flush(d.disp); ret < 0 {
|
||||||
if err != syscall.EAGAIN {
|
if err != syscall.EAGAIN {
|
||||||
@@ -1471,25 +1402,11 @@ func (d *wlDisplay) dispatch() error {
|
|||||||
dispFd.Events |= syscall.POLLOUT
|
dispFd.Events |= syscall.POLLOUT
|
||||||
}
|
}
|
||||||
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
||||||
C.wl_display_cancel_read(d.disp)
|
|
||||||
return fmt.Errorf("wayland: poll failed: %v", err)
|
return fmt.Errorf("wayland: poll failed: %v", err)
|
||||||
}
|
}
|
||||||
if dispFd.Revents&(syscall.POLLERR|syscall.POLLHUP) != 0 {
|
|
||||||
C.wl_display_cancel_read(d.disp)
|
|
||||||
return errors.New("wayland: display file descriptor gone")
|
|
||||||
}
|
|
||||||
// Handle events.
|
|
||||||
if dispFd.Revents&syscall.POLLIN != 0 {
|
|
||||||
if ret, err := C.wl_display_read_events(d.disp); ret < 0 {
|
|
||||||
return fmt.Errorf("wayland: wl_display_read_events failed: %v", err)
|
|
||||||
}
|
|
||||||
C.wl_display_dispatch_pending(d.disp)
|
|
||||||
} else {
|
|
||||||
C.wl_display_cancel_read(d.disp)
|
|
||||||
}
|
|
||||||
// Clear notifications.
|
// Clear notifications.
|
||||||
for {
|
for {
|
||||||
_, err := syscall.Read(d.notify.read, d.poller.buf[:])
|
_, err := syscall.Read(d.notify.read, p.buf[:])
|
||||||
if err == syscall.EAGAIN {
|
if err == syscall.EAGAIN {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -1497,15 +1414,29 @@ func (d *wlDisplay) dispatch() error {
|
|||||||
return fmt.Errorf("wayland: read from notify pipe failed: %v", err)
|
return fmt.Errorf("wayland: read from notify pipe failed: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Handle events
|
||||||
|
switch {
|
||||||
|
case dispFd.Revents&syscall.POLLIN != 0:
|
||||||
|
if ret, err := C.wl_display_dispatch(d.disp); ret < 0 {
|
||||||
|
return fmt.Errorf("wayland: wl_display_dispatch failed: %v", err)
|
||||||
|
}
|
||||||
|
case dispFd.Revents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
||||||
|
return errors.New("wayland: display file descriptor gone")
|
||||||
|
}
|
||||||
d.repeat.Repeat(d)
|
d.repeat.Repeat(d)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *window) Wakeup() {
|
||||||
|
select {
|
||||||
|
case w.wakeups <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
w.disp.wakeup()
|
||||||
|
}
|
||||||
|
|
||||||
func (w *window) SetAnimating(anim bool) {
|
func (w *window) SetAnimating(anim bool) {
|
||||||
w.animating = anim
|
w.animating = anim
|
||||||
if anim {
|
|
||||||
w.draw(false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wakeup wakes up the event loop through the notification pipe.
|
// Wakeup wakes up the event loop through the notification pipe.
|
||||||
@@ -1517,10 +1448,6 @@ 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)
|
||||||
}
|
}
|
||||||
@@ -1645,15 +1572,7 @@ func (w *window) flushScroll() {
|
|||||||
if total == (f32.Point{}) {
|
if total == (f32.Point{}) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if w.scroll.steps == (image.Point{}) {
|
w.w.Event(pointer.Event{
|
||||||
w.fling.xExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.X)
|
|
||||||
w.fling.yExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.Y)
|
|
||||||
}
|
|
||||||
// Zero scroll distance prior to calling ProcessEvent, otherwise we may recursively
|
|
||||||
// re-process the scroll distance.
|
|
||||||
w.scroll.dist = f32.Point{}
|
|
||||||
w.scroll.steps = image.Point{}
|
|
||||||
w.ProcessEvent(pointer.Event{
|
|
||||||
Kind: pointer.Scroll,
|
Kind: pointer.Scroll,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Buttons: w.pointerBtns,
|
Buttons: w.pointerBtns,
|
||||||
@@ -1662,6 +1581,12 @@ 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) {
|
||||||
@@ -1670,7 +1595,7 @@ func (w *window) onPointerMotion(x, y C.wl_fixed_t, t C.uint32_t) {
|
|||||||
X: fromFixed(x) * float32(w.scale),
|
X: fromFixed(x) * float32(w.scale),
|
||||||
Y: fromFixed(y) * float32(w.scale),
|
Y: fromFixed(y) * float32(w.scale),
|
||||||
}
|
}
|
||||||
w.ProcessEvent(pointer.Event{
|
w.w.Event(pointer.Event{
|
||||||
Kind: pointer.Move,
|
Kind: pointer.Move,
|
||||||
Position: w.lastPos,
|
Position: w.lastPos,
|
||||||
Buttons: w.pointerBtns,
|
Buttons: w.pointerBtns,
|
||||||
@@ -1691,8 +1616,7 @@ func (w *window) systemGesture() (*C.struct_wl_cursor, C.uint32_t) {
|
|||||||
if w.config.Mode != Windowed || w.config.Decorated {
|
if w.config.Mode != Windowed || w.config.Decorated {
|
||||||
return nil, 0
|
return nil, 0
|
||||||
}
|
}
|
||||||
_, cfg := w.getConfig()
|
border := w.w.w.metric.Dp(3)
|
||||||
border := cfg.Dp(3)
|
|
||||||
x, y, size := int(w.lastPos.X), int(w.lastPos.Y), w.config.Size
|
x, y, size := int(w.lastPos.X), int(w.lastPos.Y), w.config.Size
|
||||||
north := y <= border
|
north := y <= border
|
||||||
south := y >= size.Y-border
|
south := y >= size.Y-border
|
||||||
@@ -1746,10 +1670,13 @@ func (w *window) updateOutputs() {
|
|||||||
if found && scale != w.scale {
|
if found && scale != w.scale {
|
||||||
w.scale = scale
|
w.scale = scale
|
||||||
C.wl_surface_set_buffer_scale(w.surf, C.int32_t(w.scale))
|
C.wl_surface_set_buffer_scale(w.surf, C.int32_t(w.scale))
|
||||||
w.draw(true)
|
w.redraw = true
|
||||||
}
|
}
|
||||||
if found {
|
if !found {
|
||||||
w.draw(true)
|
w.setStage(system.StagePaused)
|
||||||
|
} else {
|
||||||
|
w.setStage(system.StageRunning)
|
||||||
|
w.redraw = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1761,10 +1688,7 @@ func (w *window) getConfig() (image.Point, unit.Metric) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) draw(sync bool) {
|
func (w *window) draw() {
|
||||||
if !w.configured {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.flushScroll()
|
w.flushScroll()
|
||||||
size, cfg := w.getConfig()
|
size, cfg := w.getConfig()
|
||||||
if cfg == (unit.Metric{}) {
|
if cfg == (unit.Metric{}) {
|
||||||
@@ -1772,9 +1696,11 @@ func (w *window) draw(sync bool) {
|
|||||||
}
|
}
|
||||||
if size != w.config.Size {
|
if size != w.config.Size {
|
||||||
w.config.Size = size
|
w.config.Size = size
|
||||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
w.w.Event(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
anim := w.animating || w.fling.anim.Active()
|
anim := w.animating || w.fling.anim.Active()
|
||||||
|
sync := w.redraw
|
||||||
|
w.redraw = false
|
||||||
// Draw animation only when not waiting for frame callback.
|
// Draw animation only when not waiting for frame callback.
|
||||||
redrawAnim := anim && w.lastFrameCallback == nil
|
redrawAnim := anim && w.lastFrameCallback == nil
|
||||||
if !redrawAnim && !sync {
|
if !redrawAnim && !sync {
|
||||||
@@ -1785,8 +1711,8 @@ func (w *window) draw(sync bool) {
|
|||||||
// Use the surface as listener data for gio_onFrameDone.
|
// Use the surface as listener data for gio_onFrameDone.
|
||||||
C.wl_callback_add_listener(w.lastFrameCallback, &C.gio_callback_listener, unsafe.Pointer(w.surf))
|
C.wl_callback_add_listener(w.lastFrameCallback, &C.gio_callback_listener, unsafe.Pointer(w.surf))
|
||||||
}
|
}
|
||||||
w.ProcessEvent(frameEvent{
|
w.w.Event(frameEvent{
|
||||||
FrameEvent: FrameEvent{
|
FrameEvent: system.FrameEvent{
|
||||||
Now: time.Now(),
|
Now: time.Now(),
|
||||||
Size: w.config.Size,
|
Size: w.config.Size,
|
||||||
Metric: cfg,
|
Metric: cfg,
|
||||||
@@ -1795,6 +1721,14 @@ func (w *window) draw(sync bool) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *window) setStage(s system.Stage) {
|
||||||
|
if s == w.stage {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.stage = s
|
||||||
|
w.w.Event(system.StageEvent{Stage: s})
|
||||||
|
}
|
||||||
|
|
||||||
func (w *window) display() *C.struct_wl_display {
|
func (w *window) display() *C.struct_wl_display {
|
||||||
return w.disp.disp
|
return w.disp.disp
|
||||||
}
|
}
|
||||||
@@ -1886,10 +1820,6 @@ func newWLDisplay() (*wlDisplay, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *wlDisplay) destroy() {
|
func (d *wlDisplay) destroy() {
|
||||||
if d.readClipClose != nil {
|
|
||||||
close(d.readClipClose)
|
|
||||||
d.readClipClose = nil
|
|
||||||
}
|
|
||||||
if d.notify.write != 0 {
|
if d.notify.write != 0 {
|
||||||
syscall.Close(d.notify.write)
|
syscall.Close(d.notify.write)
|
||||||
d.notify.write = 0
|
d.notify.write = 0
|
||||||
@@ -1931,7 +1861,6 @@ func (d *wlDisplay) destroy() {
|
|||||||
if d.disp != nil {
|
if d.disp != nil {
|
||||||
C.wl_display_disconnect(d.disp)
|
C.wl_display_disconnect(d.disp)
|
||||||
callbackDelete(unsafe.Pointer(d.disp))
|
callbackDelete(unsafe.Pointer(d.disp))
|
||||||
d.disp = nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+163
-246
@@ -6,7 +6,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"io"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -19,39 +18,40 @@ import (
|
|||||||
syscall "golang.org/x/sys/windows"
|
syscall "golang.org/x/sys/windows"
|
||||||
|
|
||||||
"gioui.org/app/internal/windows"
|
"gioui.org/app/internal/windows"
|
||||||
"gioui.org/op"
|
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
gowindows "golang.org/x/sys/windows"
|
gowindows "golang.org/x/sys/windows"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/io/event"
|
"gioui.org/io/clipboard"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
"gioui.org/io/transfer"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Win32ViewEvent struct {
|
type ViewEvent struct {
|
||||||
HWND uintptr
|
HWND uintptr
|
||||||
}
|
}
|
||||||
|
|
||||||
type window struct {
|
type window struct {
|
||||||
hwnd syscall.Handle
|
hwnd syscall.Handle
|
||||||
hdc syscall.Handle
|
hdc syscall.Handle
|
||||||
w *callbacks
|
w *callbacks
|
||||||
|
stage system.Stage
|
||||||
|
pointerBtns pointer.Buttons
|
||||||
|
|
||||||
// cursorIn tracks whether the cursor was inside the window according
|
// cursorIn tracks whether the cursor was inside the window according
|
||||||
// to the most recent WM_SETCURSOR.
|
// to the most recent WM_SETCURSOR.
|
||||||
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
|
||||||
|
focused bool
|
||||||
|
|
||||||
borderSize image.Point
|
borderSize image.Point
|
||||||
config Config
|
config Config
|
||||||
// frameDims stores the last seen window frame width and height.
|
|
||||||
frameDims image.Point
|
|
||||||
loop *eventLoop
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const _WM_WAKEUP = windows.WM_USER + iota
|
const _WM_WAKEUP = windows.WM_USER + iota
|
||||||
@@ -84,38 +84,36 @@ func osMain() {
|
|||||||
select {}
|
select {}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWindow(win *callbacks, options []Option) {
|
func newWindow(window *callbacks, options []Option) error {
|
||||||
done := make(chan struct{})
|
cerr := make(chan error)
|
||||||
go func() {
|
go func() {
|
||||||
// GetMessage and PeekMessage can filter on a window HWND, but
|
// GetMessage and PeekMessage can filter on a window HWND, but
|
||||||
// then thread-specific messages such as WM_QUIT are ignored.
|
// then thread-specific messages such as WM_QUIT are ignored.
|
||||||
// Instead lock the thread so window messages arrive through
|
// Instead lock the thread so window messages arrive through
|
||||||
// unfiltered GetMessage calls.
|
// unfiltered GetMessage calls.
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
|
w, err := createNativeWindow()
|
||||||
w := &window{
|
|
||||||
w: win,
|
|
||||||
}
|
|
||||||
w.loop = newEventLoop(w.w, w.wakeup)
|
|
||||||
w.w.SetDriver(w)
|
|
||||||
err := w.init()
|
|
||||||
done <- struct{}{}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.ProcessEvent(DestroyEvent{Err: err})
|
cerr <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
cerr <- nil
|
||||||
winMap.Store(w.hwnd, w)
|
winMap.Store(w.hwnd, w)
|
||||||
defer winMap.Delete(w.hwnd)
|
defer winMap.Delete(w.hwnd)
|
||||||
|
w.w = window
|
||||||
|
w.w.SetDriver(w)
|
||||||
|
w.w.Event(ViewEvent{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,
|
||||||
// set it here to show the cursor.
|
// set it here to show the cursor.
|
||||||
w.SetCursor(pointer.CursorDefault)
|
w.SetCursor(pointer.CursorDefault)
|
||||||
w.runLoop()
|
if err := w.loop(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
<-done
|
return <-cerr
|
||||||
}
|
}
|
||||||
|
|
||||||
// initResources initializes the resources global.
|
// initResources initializes the resources global.
|
||||||
@@ -150,13 +148,13 @@ func initResources() error {
|
|||||||
|
|
||||||
const dwExStyle = windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE
|
const dwExStyle = windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE
|
||||||
|
|
||||||
func (w *window) init() error {
|
func createNativeWindow() (*window, error) {
|
||||||
var resErr error
|
var resErr error
|
||||||
resources.once.Do(func() {
|
resources.once.Do(func() {
|
||||||
resErr = initResources()
|
resErr = initResources()
|
||||||
})
|
})
|
||||||
if resErr != nil {
|
if resErr != nil {
|
||||||
return resErr
|
return nil, resErr
|
||||||
}
|
}
|
||||||
const dwStyle = windows.WS_OVERLAPPEDWINDOW
|
const dwStyle = windows.WS_OVERLAPPEDWINDOW
|
||||||
|
|
||||||
@@ -172,56 +170,33 @@ func (w *window) init() error {
|
|||||||
resources.handle,
|
resources.handle,
|
||||||
0)
|
0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := windows.RegisterTouchWindow(hwnd, 0); err != nil {
|
w := &window{
|
||||||
return err
|
hwnd: hwnd,
|
||||||
}
|
|
||||||
if err := windows.EnableMouseInPointer(1); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
w.hdc, err = windows.GetDC(hwnd)
|
w.hdc, err = windows.GetDC(hwnd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
windows.DestroyWindow(hwnd)
|
return nil, err
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
w.hwnd = hwnd
|
return w, 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() {
|
||||||
p := windows.GetWindowPlacement(w.hwnd)
|
cr := windows.GetClientRect(w.hwnd)
|
||||||
if !p.IsMinimized() {
|
w.config.Size = image.Point{
|
||||||
r := windows.GetWindowRect(w.hwnd)
|
X: int(cr.Right - cr.Left),
|
||||||
cr := windows.GetClientRect(w.hwnd)
|
Y: int(cr.Bottom - cr.Top),
|
||||||
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)
|
w.w.Event(ConfigEvent{Config: w.config})
|
||||||
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.draw(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
|
func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
|
||||||
@@ -262,7 +237,7 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
e.State = key.Release
|
e.State = key.Release
|
||||||
}
|
}
|
||||||
|
|
||||||
w.ProcessEvent(e)
|
w.w.Event(e)
|
||||||
|
|
||||||
if (wParam == windows.VK_F10) && (msg == windows.WM_SYSKEYDOWN || msg == windows.WM_SYSKEYUP) {
|
if (wParam == windows.VK_F10) && (msg == windows.WM_SYSKEYDOWN || msg == windows.WM_SYSKEYUP) {
|
||||||
// Reserve F10 for ourselves, and don't let it open the system menu. Other Windows programs
|
// Reserve F10 for ourselves, and don't let it open the system menu. Other Windows programs
|
||||||
@@ -270,42 +245,36 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case windows.WM_POINTERDOWN, windows.WM_POINTERUP, windows.WM_POINTERUPDATE, windows.WM_POINTERCAPTURECHANGED:
|
case windows.WM_LBUTTONDOWN:
|
||||||
pid := getPointerIDwParam(wParam)
|
w.pointerButton(pointer.ButtonPrimary, true, lParam, getModifiers())
|
||||||
pi, err := windows.GetPointerInfo(uint32(pid))
|
case windows.WM_LBUTTONUP:
|
||||||
if err != nil {
|
w.pointerButton(pointer.ButtonPrimary, false, lParam, getModifiers())
|
||||||
panic(err)
|
case windows.WM_RBUTTONDOWN:
|
||||||
}
|
w.pointerButton(pointer.ButtonSecondary, true, lParam, getModifiers())
|
||||||
switch msg {
|
case windows.WM_RBUTTONUP:
|
||||||
case windows.WM_POINTERDOWN:
|
w.pointerButton(pointer.ButtonSecondary, false, lParam, getModifiers())
|
||||||
windows.SetCapture(w.hwnd)
|
case windows.WM_MBUTTONDOWN:
|
||||||
case windows.WM_POINTERUP:
|
w.pointerButton(pointer.ButtonTertiary, true, lParam, getModifiers())
|
||||||
windows.ReleaseCapture()
|
case windows.WM_MBUTTONUP:
|
||||||
}
|
w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers())
|
||||||
|
|
||||||
kind := pointer.Move
|
|
||||||
switch pi.ButtonChangeType {
|
|
||||||
case windows.POINTER_CHANGE_FIRSTBUTTON_DOWN, windows.POINTER_CHANGE_SECONDBUTTON_DOWN, windows.POINTER_CHANGE_THIRDBUTTON_DOWN, windows.POINTER_CHANGE_FOURTHBUTTON_DOWN, windows.POINTER_CHANGE_FIFTHBUTTON_DOWN:
|
|
||||||
kind = pointer.Press
|
|
||||||
case windows.POINTER_CHANGE_FIRSTBUTTON_UP, windows.POINTER_CHANGE_SECONDBUTTON_UP, windows.POINTER_CHANGE_THIRDBUTTON_UP, windows.POINTER_CHANGE_FOURTHBUTTON_UP, windows.POINTER_CHANGE_FIFTHBUTTON_UP:
|
|
||||||
kind = pointer.Release
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pi.PointerFlags&windows.POINTER_FLAG_CANCELED != 0) || (msg == windows.WM_POINTERCAPTURECHANGED) {
|
|
||||||
kind = pointer.Cancel
|
|
||||||
}
|
|
||||||
|
|
||||||
w.pointerUpdate(pi, pid, kind, lParam)
|
|
||||||
case windows.WM_CANCELMODE:
|
case windows.WM_CANCELMODE:
|
||||||
w.ProcessEvent(pointer.Event{
|
w.w.Event(pointer.Event{
|
||||||
Kind: pointer.Cancel,
|
Kind: pointer.Cancel,
|
||||||
})
|
})
|
||||||
case windows.WM_SETFOCUS:
|
case windows.WM_SETFOCUS:
|
||||||
w.config.Focused = true
|
w.focused = true
|
||||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
w.w.Event(key.FocusEvent{Focus: true})
|
||||||
case windows.WM_KILLFOCUS:
|
case windows.WM_KILLFOCUS:
|
||||||
w.config.Focused = false
|
w.focused = false
|
||||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
w.w.Event(key.FocusEvent{Focus: false})
|
||||||
|
case windows.WM_NCACTIVATE:
|
||||||
|
if w.stage >= system.StageInactive {
|
||||||
|
if wParam == windows.TRUE {
|
||||||
|
w.setStage(system.StageRunning)
|
||||||
|
} else {
|
||||||
|
w.setStage(system.StageInactive)
|
||||||
|
}
|
||||||
|
}
|
||||||
case windows.WM_NCHITTEST:
|
case windows.WM_NCHITTEST:
|
||||||
if w.config.Decorated {
|
if w.config.Decorated {
|
||||||
// Let the system handle it.
|
// Let the system handle it.
|
||||||
@@ -315,14 +284,24 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
np := windows.Point{X: int32(x), Y: int32(y)}
|
np := windows.Point{X: int32(x), Y: int32(y)}
|
||||||
windows.ScreenToClient(w.hwnd, &np)
|
windows.ScreenToClient(w.hwnd, &np)
|
||||||
return w.hitTest(int(np.X), int(np.Y))
|
return w.hitTest(int(np.X), int(np.Y))
|
||||||
case windows.WM_POINTERWHEEL:
|
case windows.WM_MOUSEMOVE:
|
||||||
|
x, y := coordsFromlParam(lParam)
|
||||||
|
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||||
|
w.w.Event(pointer.Event{
|
||||||
|
Kind: pointer.Move,
|
||||||
|
Source: pointer.Mouse,
|
||||||
|
Position: p,
|
||||||
|
Buttons: w.pointerBtns,
|
||||||
|
Time: windows.GetMessageTime(),
|
||||||
|
Modifiers: getModifiers(),
|
||||||
|
})
|
||||||
|
case windows.WM_MOUSEWHEEL:
|
||||||
w.scrollEvent(wParam, lParam, false, getModifiers())
|
w.scrollEvent(wParam, lParam, false, getModifiers())
|
||||||
case windows.WM_POINTERHWHEEL:
|
case windows.WM_MOUSEHWHEEL:
|
||||||
w.scrollEvent(wParam, lParam, true, getModifiers())
|
w.scrollEvent(wParam, lParam, true, getModifiers())
|
||||||
case windows.WM_DESTROY:
|
case windows.WM_DESTROY:
|
||||||
w.ProcessEvent(Win32ViewEvent{})
|
w.w.Event(ViewEvent{})
|
||||||
w.ProcessEvent(DestroyEvent{})
|
w.w.Event(system.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
|
||||||
@@ -330,7 +309,6 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
// The system destroys the HWND for us.
|
// The system destroys the HWND for us.
|
||||||
w.hwnd = 0
|
w.hwnd = 0
|
||||||
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.
|
||||||
@@ -349,37 +327,46 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
// Adjust window position to avoid the extra padding in maximized
|
// Adjust window position to avoid the extra padding in maximized
|
||||||
// state. See https://devblogs.microsoft.com/oldnewthing/20150304-00/?p=44543.
|
// state. See https://devblogs.microsoft.com/oldnewthing/20150304-00/?p=44543.
|
||||||
// Note that trying to do the adjustment in WM_GETMINMAXINFO is ignored by Windows.
|
// Note that trying to do the adjustment in WM_GETMINMAXINFO is ignored by Windows.
|
||||||
szp := (*windows.NCCalcSizeParams)(unsafe.Pointer(lParam))
|
szp := (*windows.NCCalcSizeParams)(unsafe.Pointer(uintptr(lParam)))
|
||||||
mi := windows.GetMonitorInfo(w.hwnd)
|
mi := windows.GetMonitorInfo(w.hwnd)
|
||||||
szp.Rgrc[0] = mi.WorkArea
|
szp.Rgrc[0] = mi.WorkArea
|
||||||
return 0
|
return 0
|
||||||
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
|
||||||
|
w.setStage(system.StagePaused)
|
||||||
|
case windows.SIZE_MAXIMIZED:
|
||||||
|
w.config.Mode = Maximized
|
||||||
|
w.setStage(system.StageRunning)
|
||||||
|
case windows.SIZE_RESTORED:
|
||||||
|
if w.config.Mode != Fullscreen {
|
||||||
|
w.config.Mode = Windowed
|
||||||
|
}
|
||||||
|
w.setStage(system.StageRunning)
|
||||||
|
}
|
||||||
case windows.WM_GETMINMAXINFO:
|
case windows.WM_GETMINMAXINFO:
|
||||||
mm := (*windows.MinMaxInfo)(unsafe.Pointer(lParam))
|
mm := (*windows.MinMaxInfo)(unsafe.Pointer(uintptr(lParam)))
|
||||||
|
var bw, bh int32
|
||||||
var frameDims image.Point
|
|
||||||
if w.config.Decorated {
|
if w.config.Decorated {
|
||||||
frameDims = w.frameDims
|
r := windows.GetWindowRect(w.hwnd)
|
||||||
|
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),
|
X: int32(p.X) + bw,
|
||||||
Y: int32(p.Y),
|
Y: int32(p.Y) + bh,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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),
|
X: int32(p.X) + bw,
|
||||||
Y: int32(p.Y),
|
Y: int32(p.Y) + bh,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
@@ -390,8 +377,7 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
return windows.TRUE
|
return windows.TRUE
|
||||||
}
|
}
|
||||||
case _WM_WAKEUP:
|
case _WM_WAKEUP:
|
||||||
w.loop.Wakeup()
|
w.w.Event(wakeupEvent{})
|
||||||
w.loop.FlushEvents()
|
|
||||||
case windows.WM_IME_STARTCOMPOSITION:
|
case windows.WM_IME_STARTCOMPOSITION:
|
||||||
imc := windows.ImmGetContext(w.hwnd)
|
imc := windows.ImmGetContext(w.hwnd)
|
||||||
if imc == 0 {
|
if imc == 0 {
|
||||||
@@ -471,6 +457,9 @@ 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
|
||||||
@@ -507,28 +496,34 @@ func (w *window) hitTest(x, y int) uintptr {
|
|||||||
return windows.HTCLIENT
|
return windows.HTCLIENT
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) pointerUpdate(pi windows.PointerInfo, pid pointer.ID, kind pointer.Kind, lParam uintptr) {
|
func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) {
|
||||||
if !w.config.Focused {
|
if !w.focused {
|
||||||
windows.SetFocus(w.hwnd)
|
windows.SetFocus(w.hwnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
src := pointer.Touch
|
var kind pointer.Kind
|
||||||
if pi.PointerType == windows.PT_MOUSE {
|
if press {
|
||||||
src = pointer.Mouse
|
kind = pointer.Press
|
||||||
|
if w.pointerBtns == 0 {
|
||||||
|
windows.SetCapture(w.hwnd)
|
||||||
|
}
|
||||||
|
w.pointerBtns |= btn
|
||||||
|
} else {
|
||||||
|
kind = pointer.Release
|
||||||
|
w.pointerBtns &^= btn
|
||||||
|
if w.pointerBtns == 0 {
|
||||||
|
windows.ReleaseCapture()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
x, y := coordsFromlParam(lParam)
|
x, y := coordsFromlParam(lParam)
|
||||||
np := windows.Point{X: int32(x), Y: int32(y)}
|
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||||
windows.ScreenToClient(w.hwnd, &np)
|
w.w.Event(pointer.Event{
|
||||||
p := f32.Point{X: float32(np.X), Y: float32(np.Y)}
|
|
||||||
w.ProcessEvent(pointer.Event{
|
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Source: src,
|
Source: pointer.Mouse,
|
||||||
Position: p,
|
Position: p,
|
||||||
PointerID: pid,
|
Buttons: w.pointerBtns,
|
||||||
Buttons: getPointerButtons(pi),
|
|
||||||
Time: windows.GetMessageTime(),
|
Time: windows.GetMessageTime(),
|
||||||
Modifiers: getModifiers(),
|
Modifiers: kmods,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -539,12 +534,6 @@ func coordsFromlParam(lParam uintptr) (int, int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.Modifiers) {
|
func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.Modifiers) {
|
||||||
pid := getPointerIDwParam(wParam)
|
|
||||||
pi, err := windows.GetPointerInfo(uint32(pid))
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
x, y := coordsFromlParam(lParam)
|
x, y := coordsFromlParam(lParam)
|
||||||
// The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast
|
// The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast
|
||||||
// to other mouse events.
|
// to other mouse events.
|
||||||
@@ -563,11 +552,11 @@ func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.
|
|||||||
sp.Y = -dist
|
sp.Y = -dist
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
w.ProcessEvent(pointer.Event{
|
w.w.Event(pointer.Event{
|
||||||
Kind: pointer.Scroll,
|
Kind: pointer.Scroll,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Position: p,
|
Position: p,
|
||||||
Buttons: getPointerButtons(pi),
|
Buttons: w.pointerBtns,
|
||||||
Scroll: sp,
|
Scroll: sp,
|
||||||
Modifiers: kmods,
|
Modifiers: kmods,
|
||||||
Time: windows.GetMessageTime(),
|
Time: windows.GetMessageTime(),
|
||||||
@@ -575,19 +564,18 @@ func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/
|
// Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/
|
||||||
func (w *window) runLoop() {
|
func (w *window) loop() error {
|
||||||
msg := new(windows.Msg)
|
msg := new(windows.Msg)
|
||||||
loop:
|
loop:
|
||||||
for {
|
for {
|
||||||
anim := w.animating
|
anim := w.animating
|
||||||
p := windows.GetWindowPlacement(w.hwnd)
|
if anim && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) {
|
||||||
if anim && !p.IsMinimized() && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) {
|
|
||||||
w.draw(false)
|
w.draw(false)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
switch ret := windows.GetMessage(msg, 0, 0, 0); ret {
|
switch ret := windows.GetMessage(msg, 0, 0, 0); ret {
|
||||||
case -1:
|
case -1:
|
||||||
panic(errors.New("GetMessage failed"))
|
return errors.New("GetMessage failed")
|
||||||
case 0:
|
case 0:
|
||||||
// WM_QUIT received.
|
// WM_QUIT received.
|
||||||
break loop
|
break loop
|
||||||
@@ -595,6 +583,7 @@ loop:
|
|||||||
windows.TranslateMessage(msg)
|
windows.TranslateMessage(msg)
|
||||||
windows.DispatchMessage(msg)
|
windows.DispatchMessage(msg)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) EditorStateChanged(old, new editorState) {
|
func (w *window) EditorStateChanged(old, new editorState) {
|
||||||
@@ -612,41 +601,27 @@ func (w *window) SetAnimating(anim bool) {
|
|||||||
w.animating = anim
|
w.animating = anim
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) ProcessEvent(e event.Event) {
|
func (w *window) Wakeup() {
|
||||||
w.w.ProcessEvent(e)
|
|
||||||
w.loop.FlushEvents()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) Event() event.Event {
|
|
||||||
return w.loop.Event()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) Invalidate() {
|
|
||||||
w.loop.Invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) Run(f func()) {
|
|
||||||
w.loop.Run(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) Frame(frame *op.Ops) {
|
|
||||||
w.loop.Frame(frame)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) wakeup() {
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *window) setStage(s system.Stage) {
|
||||||
|
if s != w.stage {
|
||||||
|
w.stage = s
|
||||||
|
w.w.Event(system.StageEvent{Stage: s})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (w *window) draw(sync bool) {
|
func (w *window) draw(sync bool) {
|
||||||
if w.config.Size.X == 0 || w.config.Size.Y == 0 {
|
if w.config.Size.X == 0 || w.config.Size.Y == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dpi := windows.GetWindowDPI(w.hwnd)
|
dpi := windows.GetWindowDPI(w.hwnd)
|
||||||
cfg := configForDPI(dpi)
|
cfg := configForDPI(dpi)
|
||||||
w.ProcessEvent(frameEvent{
|
w.w.Event(frameEvent{
|
||||||
FrameEvent: FrameEvent{
|
FrameEvent: system.FrameEvent{
|
||||||
Now: time.Now(),
|
Now: time.Now(),
|
||||||
Size: w.config.Size,
|
Size: w.config.Size,
|
||||||
Metric: cfg,
|
Metric: cfg,
|
||||||
@@ -692,25 +667,15 @@ func (w *window) readClipboard() error {
|
|||||||
}
|
}
|
||||||
defer windows.GlobalUnlock(mem)
|
defer windows.GlobalUnlock(mem)
|
||||||
content := gowindows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr)))
|
content := gowindows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr)))
|
||||||
w.ProcessEvent(transfer.DataEvent{
|
w.w.Event(clipboard.Event{Text: content})
|
||||||
Type: "application/text",
|
|
||||||
Open: func() io.ReadCloser {
|
|
||||||
return io.NopCloser(strings.NewReader(content))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Configure(options []Option) {
|
func (w *window) Configure(options []Option) {
|
||||||
dpi := windows.GetSystemDPI()
|
dpi := windows.GetSystemDPI()
|
||||||
metric := configForDPI(dpi)
|
metric := configForDPI(dpi)
|
||||||
cnf := w.config
|
w.config.apply(metric, options)
|
||||||
cnf.apply(metric, options)
|
windows.SetWindowText(w.hwnd, w.config.Title)
|
||||||
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
|
||||||
@@ -718,7 +683,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 cnf.Mode {
|
switch w.config.Mode {
|
||||||
case Minimized:
|
case Minimized:
|
||||||
style |= winStyle
|
style |= winStyle
|
||||||
swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
|
swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
|
||||||
@@ -733,13 +698,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(cnf.Size.X)
|
width = int32(w.config.Size.X)
|
||||||
height = int32(cnf.Size.Y)
|
height = int32(w.config.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 cnf.Decorated {
|
if w.config.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{
|
||||||
@@ -749,31 +714,29 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable window resizing if MinSize and MaxSize are equal.
|
|
||||||
if cnf.MaxSize != (image.Point{}) && cnf.MinSize == cnf.MaxSize {
|
|
||||||
style &^= windows.WS_MAXIMIZEBOX
|
|
||||||
style &^= windows.WS_THICKFRAME
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: these invocation all trigger the windows callback method which may process a pending system.ActionCenter
|
|
||||||
// action, so SetWindowPos should come first so as to not "overwrite" system.ActionCenter.
|
|
||||||
windows.SetWindowPos(w.hwnd, 0, x, y, width, height, swpStyle)
|
|
||||||
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.ShowWindow(w.hwnd, showMode)
|
windows.ShowWindow(w.hwnd, showMode)
|
||||||
|
|
||||||
|
w.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) WriteClipboard(mime string, s []byte) {
|
func (w *window) WriteClipboard(s string) {
|
||||||
w.writeClipboard(string(s))
|
w.writeClipboard(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) writeClipboard(s string) error {
|
func (w *window) writeClipboard(s string) error {
|
||||||
@@ -901,11 +864,11 @@ func (w *window) raise() {
|
|||||||
windows.SWP_NOMOVE|windows.SWP_NOSIZE|windows.SWP_SHOWWINDOW)
|
windows.SWP_NOMOVE|windows.SWP_NOSIZE|windows.SWP_SHOWWINDOW)
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertKeyCode(code uintptr) (key.Name, bool) {
|
func convertKeyCode(code uintptr) (string, bool) {
|
||||||
if '0' <= code && code <= '9' || 'A' <= code && code <= 'Z' {
|
if '0' <= code && code <= '9' || 'A' <= code && code <= 'Z' {
|
||||||
return key.Name(rune(code)), true
|
return string(rune(code)), true
|
||||||
}
|
}
|
||||||
var r key.Name
|
var r string
|
||||||
|
|
||||||
switch code {
|
switch code {
|
||||||
case windows.VK_ESCAPE:
|
case windows.VK_ESCAPE:
|
||||||
@@ -1005,50 +968,4 @@ func configForDPI(dpi int) unit.Metric {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Win32ViewEvent) implementsViewEvent() {}
|
func (_ ViewEvent) ImplementsEvent() {}
|
||||||
func (Win32ViewEvent) ImplementsEvent() {}
|
|
||||||
func (w Win32ViewEvent) Valid() bool {
|
|
||||||
return w != (Win32ViewEvent{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// LOWORD (minwindef.h)
|
|
||||||
func loWord(val uint32) uint16 {
|
|
||||||
return uint16(val & 0xFFFF)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET_POINTERID_WPARAM (winuser.h)
|
|
||||||
func getPointerIDwParam(wParam uintptr) pointer.ID {
|
|
||||||
return pointer.ID(loWord(uint32(wParam)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPointerButtons(pi windows.PointerInfo) pointer.Buttons {
|
|
||||||
var btns pointer.Buttons
|
|
||||||
|
|
||||||
if pi.PointerFlags&windows.POINTER_FLAG_FIRSTBUTTON != 0 {
|
|
||||||
btns |= pointer.ButtonPrimary
|
|
||||||
} else {
|
|
||||||
btns &^= pointer.ButtonPrimary
|
|
||||||
}
|
|
||||||
if pi.PointerFlags&windows.POINTER_FLAG_SECONDBUTTON != 0 {
|
|
||||||
btns |= pointer.ButtonSecondary
|
|
||||||
} else {
|
|
||||||
btns &^= pointer.ButtonSecondary
|
|
||||||
}
|
|
||||||
if pi.PointerFlags&windows.POINTER_FLAG_THIRDBUTTON != 0 {
|
|
||||||
btns |= pointer.ButtonTertiary
|
|
||||||
} else {
|
|
||||||
btns &^= pointer.ButtonTertiary
|
|
||||||
}
|
|
||||||
if pi.PointerFlags&windows.POINTER_FLAG_FOURTHBUTTON != 0 {
|
|
||||||
btns |= pointer.ButtonQuaternary
|
|
||||||
} else {
|
|
||||||
btns &^= pointer.ButtonQuaternary
|
|
||||||
}
|
|
||||||
if pi.PointerFlags&windows.POINTER_FLAG_FIFTHBUTTON != 0 {
|
|
||||||
btns |= pointer.ButtonQuinary
|
|
||||||
} else {
|
|
||||||
btns &^= pointer.ButtonQuinary
|
|
||||||
}
|
|
||||||
|
|
||||||
return btns
|
|
||||||
}
|
|
||||||
|
|||||||
+86
-117
@@ -26,25 +26,20 @@ package app
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"io"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/io/event"
|
"gioui.org/io/clipboard"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
"gioui.org/io/transfer"
|
|
||||||
"gioui.org/op"
|
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
|
|
||||||
syscall "golang.org/x/sys/unix"
|
syscall "golang.org/x/sys/unix"
|
||||||
@@ -96,10 +91,12 @@ type x11Window struct {
|
|||||||
// _NET_WM_STATE_MAXIMIZED_VERT
|
// _NET_WM_STATE_MAXIMIZED_VERT
|
||||||
wmStateMaximizedVert C.Atom
|
wmStateMaximizedVert C.Atom
|
||||||
}
|
}
|
||||||
|
stage system.Stage
|
||||||
metric unit.Metric
|
metric unit.Metric
|
||||||
notify struct {
|
notify struct {
|
||||||
read, write int
|
read, write int
|
||||||
}
|
}
|
||||||
|
dead bool
|
||||||
|
|
||||||
animating bool
|
animating bool
|
||||||
|
|
||||||
@@ -112,8 +109,6 @@ type x11Window struct {
|
|||||||
config Config
|
config Config
|
||||||
|
|
||||||
wakeups chan struct{}
|
wakeups chan struct{}
|
||||||
handler x11EventHandler
|
|
||||||
buf [100]byte
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -156,8 +151,8 @@ func (w *x11Window) ReadClipboard() {
|
|||||||
C.XConvertSelection(w.x, w.atoms.clipboard, w.atoms.utf8string, w.atoms.clipboardContent, w.xw, C.CurrentTime)
|
C.XConvertSelection(w.x, w.atoms.clipboard, w.atoms.utf8string, w.atoms.clipboardContent, w.xw, C.CurrentTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *x11Window) WriteClipboard(mime string, s []byte) {
|
func (w *x11Window) WriteClipboard(s string) {
|
||||||
w.clipboard.content = s
|
w.clipboard.content = []byte(s)
|
||||||
C.XSetSelectionOwner(w.x, w.atoms.clipboard, w.xw, C.CurrentTime)
|
C.XSetSelectionOwner(w.x, w.atoms.clipboard, w.xw, C.CurrentTime)
|
||||||
C.XSetSelectionOwner(w.x, w.atoms.primary, w.xw, C.CurrentTime)
|
C.XSetSelectionOwner(w.x, w.atoms.primary, w.xw, C.CurrentTime)
|
||||||
}
|
}
|
||||||
@@ -237,7 +232,7 @@ func (w *x11Window) Configure(options []Option) {
|
|||||||
if cnf.Decorated != prev.Decorated {
|
if cnf.Decorated != prev.Decorated {
|
||||||
w.config.Decorated = cnf.Decorated
|
w.config.Decorated = cnf.Decorated
|
||||||
}
|
}
|
||||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
w.w.Event(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *x11Window) setTitle(prev, cnf Config) {
|
func (w *x11Window) setTitle(prev, cnf Config) {
|
||||||
@@ -380,36 +375,7 @@ func (w *x11Window) sendWMStateEvent(action C.long, atom1, atom2 C.ulong) {
|
|||||||
|
|
||||||
var x11OneByte = make([]byte, 1)
|
var x11OneByte = make([]byte, 1)
|
||||||
|
|
||||||
func (w *x11Window) ProcessEvent(e event.Event) {
|
func (w *x11Window) Wakeup() {
|
||||||
w.w.ProcessEvent(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *x11Window) shutdown(err error) {
|
|
||||||
w.ProcessEvent(X11ViewEvent{})
|
|
||||||
w.ProcessEvent(DestroyEvent{Err: err})
|
|
||||||
w.destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *x11Window) Event() event.Event {
|
|
||||||
for {
|
|
||||||
evt, ok := w.w.nextEvent()
|
|
||||||
if !ok {
|
|
||||||
w.dispatch()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return evt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *x11Window) Run(f func()) {
|
|
||||||
f()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *x11Window) Frame(frame *op.Ops) {
|
|
||||||
w.w.ProcessFrame(frame, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *x11Window) Invalidate() {
|
|
||||||
select {
|
select {
|
||||||
case w.wakeups <- struct{}{}:
|
case w.wakeups <- struct{}{}:
|
||||||
default:
|
default:
|
||||||
@@ -427,20 +393,16 @@ func (w *x11Window) window() (C.Window, int, int) {
|
|||||||
return w.xw, w.config.Size.X, w.config.Size.Y
|
return w.xw, w.config.Size.X, w.config.Size.Y
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *x11Window) dispatch() {
|
func (w *x11Window) setStage(s system.Stage) {
|
||||||
if w.x == nil {
|
if s == w.stage {
|
||||||
// Only Invalidate can wake us up.
|
|
||||||
<-w.wakeups
|
|
||||||
w.w.Invalidate()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
w.stage = s
|
||||||
|
w.w.Event(system.StageEvent{Stage: s})
|
||||||
|
}
|
||||||
|
|
||||||
select {
|
func (w *x11Window) loop() {
|
||||||
case <-w.wakeups:
|
h := x11EventHandler{w: w, xev: new(C.XEvent), text: make([]byte, 4)}
|
||||||
w.w.Invalidate()
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
xfd := C.XConnectionNumber(w.x)
|
xfd := C.XConnectionNumber(w.x)
|
||||||
|
|
||||||
// Poll for events and notifications.
|
// Poll for events and notifications.
|
||||||
@@ -450,54 +412,60 @@ func (w *x11Window) dispatch() {
|
|||||||
}
|
}
|
||||||
xEvents := &pollfds[0].Revents
|
xEvents := &pollfds[0].Revents
|
||||||
// Plenty of room for a backlog of notifications.
|
// Plenty of room for a backlog of notifications.
|
||||||
|
buf := make([]byte, 100)
|
||||||
|
|
||||||
var syn, anim bool
|
loop:
|
||||||
// Check for pending draw events before checking animation or blocking.
|
for !w.dead {
|
||||||
// This fixes an issue on Xephyr where on startup XPending() > 0 but
|
var syn, anim bool
|
||||||
// poll will still block. This also prevents no-op calls to poll.
|
// Check for pending draw events before checking animation or blocking.
|
||||||
syn = w.handler.handleEvents()
|
// This fixes an issue on Xephyr where on startup XPending() > 0 but
|
||||||
if w.x == nil {
|
// poll will still block. This also prevents no-op calls to poll.
|
||||||
// handleEvents received a close request and destroyed the window.
|
if syn = h.handleEvents(); !syn {
|
||||||
return
|
anim = w.animating
|
||||||
}
|
if !anim {
|
||||||
if !syn {
|
// Clear poll events.
|
||||||
anim = w.animating
|
*xEvents = 0
|
||||||
if !anim {
|
// Wait for X event or gio notification.
|
||||||
// Clear poll events.
|
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
||||||
*xEvents = 0
|
panic(fmt.Errorf("x11 loop: poll failed: %w", err))
|
||||||
// Wait for X event or gio notification.
|
}
|
||||||
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
switch {
|
||||||
panic(fmt.Errorf("x11 loop: poll failed: %w", err))
|
case *xEvents&syscall.POLLIN != 0:
|
||||||
}
|
syn = h.handleEvents()
|
||||||
switch {
|
if w.dead {
|
||||||
case *xEvents&syscall.POLLIN != 0:
|
break loop
|
||||||
syn = w.handler.handleEvents()
|
}
|
||||||
if w.x == nil {
|
case *xEvents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
||||||
return
|
break loop
|
||||||
}
|
}
|
||||||
case *xEvents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
// Clear notifications.
|
||||||
// Clear notifications.
|
for {
|
||||||
for {
|
_, err := syscall.Read(w.notify.read, buf)
|
||||||
_, err := syscall.Read(w.notify.read, w.buf[:])
|
if err == syscall.EAGAIN {
|
||||||
if err == syscall.EAGAIN {
|
break
|
||||||
break
|
}
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("x11 loop: read from notify pipe failed: %w", err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
select {
|
||||||
panic(fmt.Errorf("x11 loop: read from notify pipe failed: %w", err))
|
case <-w.wakeups:
|
||||||
|
w.w.Event(wakeupEvent{})
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anim || syn) && w.config.Size.X != 0 && w.config.Size.Y != 0 {
|
||||||
|
w.w.Event(frameEvent{
|
||||||
|
FrameEvent: system.FrameEvent{
|
||||||
|
Now: time.Now(),
|
||||||
|
Size: w.config.Size,
|
||||||
|
Metric: w.metric,
|
||||||
|
},
|
||||||
|
Sync: syn,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (anim || syn) && w.config.Size.X != 0 && w.config.Size.Y != 0 {
|
|
||||||
w.ProcessEvent(frameEvent{
|
|
||||||
FrameEvent: FrameEvent{
|
|
||||||
Now: time.Now(),
|
|
||||||
Size: w.config.Size,
|
|
||||||
Metric: w.metric,
|
|
||||||
},
|
|
||||||
Sync: syn,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -516,7 +484,6 @@ func (w *x11Window) destroy() {
|
|||||||
}
|
}
|
||||||
C.XDestroyWindow(w.x, w.xw)
|
C.XDestroyWindow(w.x, w.xw)
|
||||||
C.XCloseDisplay(w.x)
|
C.XCloseDisplay(w.x)
|
||||||
w.x = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// atom is a wrapper around XInternAtom. Callers should cache the result
|
// atom is a wrapper around XInternAtom. Callers should cache the result
|
||||||
@@ -574,7 +541,7 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
// There's no support for IME yet.
|
// There's no support for IME yet.
|
||||||
w.w.EditorInsert(ee.Text)
|
w.w.EditorInsert(ee.Text)
|
||||||
} else {
|
} else {
|
||||||
w.ProcessEvent(e)
|
w.w.Event(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case C.ButtonPress, C.ButtonRelease:
|
case C.ButtonPress, C.ButtonRelease:
|
||||||
@@ -636,10 +603,10 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
w.pointerBtns &^= btn
|
w.pointerBtns &^= btn
|
||||||
}
|
}
|
||||||
ev.Buttons = w.pointerBtns
|
ev.Buttons = w.pointerBtns
|
||||||
w.ProcessEvent(ev)
|
w.w.Event(ev)
|
||||||
case C.MotionNotify:
|
case C.MotionNotify:
|
||||||
mevt := (*C.XMotionEvent)(unsafe.Pointer(xev))
|
mevt := (*C.XMotionEvent)(unsafe.Pointer(xev))
|
||||||
w.ProcessEvent(pointer.Event{
|
w.w.Event(pointer.Event{
|
||||||
Kind: pointer.Move,
|
Kind: pointer.Move,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Buttons: w.pointerBtns,
|
Buttons: w.pointerBtns,
|
||||||
@@ -654,16 +621,14 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
// redraw only on the last expose event
|
// redraw only on the last expose event
|
||||||
redraw = (*C.XExposeEvent)(unsafe.Pointer(xev)).count == 0
|
redraw = (*C.XExposeEvent)(unsafe.Pointer(xev)).count == 0
|
||||||
case C.FocusIn:
|
case C.FocusIn:
|
||||||
w.config.Focused = true
|
w.w.Event(key.FocusEvent{Focus: true})
|
||||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
|
||||||
case C.FocusOut:
|
case C.FocusOut:
|
||||||
w.config.Focused = false
|
w.w.Event(key.FocusEvent{Focus: false})
|
||||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
|
||||||
case C.ConfigureNotify: // window configuration change
|
case C.ConfigureNotify: // window configuration change
|
||||||
cevt := (*C.XConfigureEvent)(unsafe.Pointer(xev))
|
cevt := (*C.XConfigureEvent)(unsafe.Pointer(xev))
|
||||||
if sz := image.Pt(int(cevt.width), int(cevt.height)); sz != w.config.Size {
|
if sz := image.Pt(int(cevt.width), int(cevt.height)); sz != w.config.Size {
|
||||||
w.config.Size = sz
|
w.config.Size = sz
|
||||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
w.w.Event(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
// redraw will be done by a later expose event
|
// redraw will be done by a later expose event
|
||||||
case C.SelectionNotify:
|
case C.SelectionNotify:
|
||||||
@@ -685,12 +650,7 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
str := C.GoStringN((*C.char)(unsafe.Pointer(text.value)), C.int(text.nitems))
|
str := C.GoStringN((*C.char)(unsafe.Pointer(text.value)), C.int(text.nitems))
|
||||||
w.ProcessEvent(transfer.DataEvent{
|
w.w.Event(clipboard.Event{Text: str})
|
||||||
Type: "application/text",
|
|
||||||
Open: func() io.ReadCloser {
|
|
||||||
return io.NopCloser(strings.NewReader(str))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
case C.SelectionRequest:
|
case C.SelectionRequest:
|
||||||
cevt := (*C.XSelectionRequestEvent)(unsafe.Pointer(xev))
|
cevt := (*C.XSelectionRequestEvent)(unsafe.Pointer(xev))
|
||||||
if (cevt.selection != w.atoms.clipboard && cevt.selection != w.atoms.primary) || cevt.property == C.None {
|
if (cevt.selection != w.atoms.clipboard && cevt.selection != w.atoms.primary) || cevt.property == C.None {
|
||||||
@@ -744,7 +704,7 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
cevt := (*C.XClientMessageEvent)(unsafe.Pointer(xev))
|
cevt := (*C.XClientMessageEvent)(unsafe.Pointer(xev))
|
||||||
switch *(*C.long)(unsafe.Pointer(&cevt.data)) {
|
switch *(*C.long)(unsafe.Pointer(&cevt.data)) {
|
||||||
case C.long(w.atoms.evDelWindow):
|
case C.long(w.atoms.evDelWindow):
|
||||||
w.shutdown(nil)
|
w.dead = true
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -752,7 +712,9 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
return redraw
|
return redraw
|
||||||
}
|
}
|
||||||
|
|
||||||
var x11Threads sync.Once
|
var (
|
||||||
|
x11Threads sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
x11Driver = newX11Window
|
x11Driver = newX11Window
|
||||||
@@ -824,10 +786,8 @@ func newX11Window(gioWin *callbacks, options []Option) error {
|
|||||||
wakeups: make(chan struct{}, 1),
|
wakeups: make(chan struct{}, 1),
|
||||||
config: Config{Size: cnf.Size},
|
config: Config{Size: cnf.Size},
|
||||||
}
|
}
|
||||||
w.handler = x11EventHandler{w: w, xev: new(C.XEvent), text: make([]byte, 4)}
|
|
||||||
w.notify.read = pipe[0]
|
w.notify.read = pipe[0]
|
||||||
w.notify.write = pipe[1]
|
w.notify.write = pipe[1]
|
||||||
w.w.SetDriver(w)
|
|
||||||
|
|
||||||
if err := w.updateXkbKeymap(); err != nil {
|
if err := w.updateXkbKeymap(); err != nil {
|
||||||
w.destroy()
|
w.destroy()
|
||||||
@@ -863,10 +823,19 @@ func newX11Window(gioWin *callbacks, options []Option) error {
|
|||||||
// extensions
|
// extensions
|
||||||
C.XSetWMProtocols(dpy, win, &w.atoms.evDelWindow, 1)
|
C.XSetWMProtocols(dpy, win, &w.atoms.evDelWindow, 1)
|
||||||
|
|
||||||
// make the window visible on the screen
|
go func() {
|
||||||
C.XMapWindow(dpy, win)
|
w.w.SetDriver(w)
|
||||||
w.Configure(options)
|
|
||||||
w.ProcessEvent(X11ViewEvent{Display: unsafe.Pointer(dpy), Window: uintptr(win)})
|
// make the window visible on the screen
|
||||||
|
C.XMapWindow(dpy, win)
|
||||||
|
w.Configure(options)
|
||||||
|
w.w.Event(X11ViewEvent{Display: unsafe.Pointer(dpy), Window: uintptr(win)})
|
||||||
|
w.setStage(system.StageRunning)
|
||||||
|
w.loop()
|
||||||
|
w.w.Event(X11ViewEvent{})
|
||||||
|
w.w.Event(system.DestroyEvent{Err: nil})
|
||||||
|
w.destroy()
|
||||||
|
}()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
package app
|
|
||||||
|
|
||||||
// DestroyEvent is the last event sent through
|
|
||||||
// a window event channel.
|
|
||||||
type DestroyEvent struct {
|
|
||||||
// Err is nil for normal window closures. If a
|
|
||||||
// window is prematurely closed, Err is the cause.
|
|
||||||
Err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (DestroyEvent) ImplementsEvent() {}
|
|
||||||
+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 := vk.SurfaceCapabilitiesMinExtent(caps), vk.SurfaceCapabilitiesMaxExtent(caps)
|
minExt, maxExt := caps.MinExtent(), caps.MaxExtent()
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|||||||
+619
-404
File diff suppressed because it is too large
Load Diff
+1
-10
@@ -30,15 +30,6 @@ func NewAffine2D(sx, hx, ox, hy, sy, oy float32) Affine2D {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AffineId returns an identity transformation matrix that represents no transformation
|
|
||||||
// when applied.
|
|
||||||
func AffineId() Affine2D {
|
|
||||||
return NewAffine2D(
|
|
||||||
1, 0, 0,
|
|
||||||
0, 1, 0,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Offset the transformation.
|
// Offset the transformation.
|
||||||
func (a Affine2D) Offset(offset Point) Affine2D {
|
func (a Affine2D) Offset(offset Point) Affine2D {
|
||||||
return Affine2D{
|
return Affine2D{
|
||||||
@@ -123,7 +114,7 @@ func (a Affine2D) Elems() (sx, hx, ox, hy, sy, oy float32) {
|
|||||||
|
|
||||||
// Split a transform into two parts, one which is pure offset and the
|
// Split a transform into two parts, one which is pure offset and the
|
||||||
// other representing the scaling, shearing and rotation part.
|
// other representing the scaling, shearing and rotation part.
|
||||||
func (a Affine2D) Split() (srs Affine2D, offset Point) {
|
func (a *Affine2D) Split() (srs Affine2D, offset Point) {
|
||||||
return Affine2D{
|
return Affine2D{
|
||||||
a: a.a, b: a.b, c: 0,
|
a: a.a, b: a.b, c: 0,
|
||||||
d: a.d, e: a.e, f: 0,
|
d: a.d, e: a.e, f: 0,
|
||||||
|
|||||||
+30
-160
@@ -27,11 +27,11 @@ func TestTransformOffset(t *testing.T) {
|
|||||||
p := Point{X: 1, Y: 2}
|
p := Point{X: 1, Y: 2}
|
||||||
o := Point{X: 2, Y: -3}
|
o := Point{X: 2, Y: -3}
|
||||||
|
|
||||||
r := AffineId().Offset(o).Transform(p)
|
r := Affine2D{}.Offset(o).Transform(p)
|
||||||
if !eq(r, Pt(3, -1)) {
|
if !eq(r, Pt(3, -1)) {
|
||||||
t.Errorf("offset transformation mismatch: have %v, want {3 -1}", r)
|
t.Errorf("offset transformation mismatch: have %v, want {3 -1}", r)
|
||||||
}
|
}
|
||||||
i := AffineId().Offset(o).Invert().Transform(r)
|
i := Affine2D{}.Offset(o).Invert().Transform(r)
|
||||||
if !eq(i, p) {
|
if !eq(i, p) {
|
||||||
t.Errorf("offset transformation inverse mismatch: have %v, want %v", i, p)
|
t.Errorf("offset transformation inverse mismatch: have %v, want %v", i, p)
|
||||||
}
|
}
|
||||||
@@ -51,9 +51,6 @@ func TestString(t *testing.T) {
|
|||||||
}, {
|
}, {
|
||||||
in: NewAffine2D(29.142342, 31.4123412, 37.53152, 43.51324213, 47.123412, 53.14312342),
|
in: NewAffine2D(29.142342, 31.4123412, 37.53152, 43.51324213, 47.123412, 53.14312342),
|
||||||
exp: "[[29.1423 31.4123 37.5315] [43.5132 47.1234 53.1431]]",
|
exp: "[[29.1423 31.4123 37.5315] [43.5132 47.1234 53.1431]]",
|
||||||
}, {
|
|
||||||
in: AffineId(),
|
|
||||||
exp: "[[1 0 0] [0 1 0]]",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
@@ -67,11 +64,11 @@ func TestTransformScale(t *testing.T) {
|
|||||||
p := Point{X: 1, Y: 2}
|
p := Point{X: 1, Y: 2}
|
||||||
s := Point{X: -1, Y: 2}
|
s := Point{X: -1, Y: 2}
|
||||||
|
|
||||||
r := AffineId().Scale(Point{}, s).Transform(p)
|
r := Affine2D{}.Scale(Point{}, s).Transform(p)
|
||||||
if !eq(r, Pt(-1, 4)) {
|
if !eq(r, Pt(-1, 4)) {
|
||||||
t.Errorf("scale transformation mismatch: have %v, want {-1 4}", r)
|
t.Errorf("scale transformation mismatch: have %v, want {-1 4}", r)
|
||||||
}
|
}
|
||||||
i := AffineId().Scale(Point{}, s).Invert().Transform(r)
|
i := Affine2D{}.Scale(Point{}, s).Invert().Transform(r)
|
||||||
if !eq(i, p) {
|
if !eq(i, p) {
|
||||||
t.Errorf("scale transformation inverse mismatch: have %v, want %v", i, p)
|
t.Errorf("scale transformation inverse mismatch: have %v, want %v", i, p)
|
||||||
}
|
}
|
||||||
@@ -81,11 +78,11 @@ func TestTransformRotate(t *testing.T) {
|
|||||||
p := Point{X: 1, Y: 0}
|
p := Point{X: 1, Y: 0}
|
||||||
a := float32(math.Pi / 2)
|
a := float32(math.Pi / 2)
|
||||||
|
|
||||||
r := AffineId().Rotate(Point{}, a).Transform(p)
|
r := Affine2D{}.Rotate(Point{}, a).Transform(p)
|
||||||
if !eq(r, Pt(0, 1)) {
|
if !eq(r, Pt(0, 1)) {
|
||||||
t.Errorf("rotate transformation mismatch: have %v, want {0 1}", r)
|
t.Errorf("rotate transformation mismatch: have %v, want {0 1}", r)
|
||||||
}
|
}
|
||||||
i := AffineId().Rotate(Point{}, a).Invert().Transform(r)
|
i := Affine2D{}.Rotate(Point{}, a).Invert().Transform(r)
|
||||||
if !eq(i, p) {
|
if !eq(i, p) {
|
||||||
t.Errorf("rotate transformation inverse mismatch: have %v, want %v", i, p)
|
t.Errorf("rotate transformation inverse mismatch: have %v, want %v", i, p)
|
||||||
}
|
}
|
||||||
@@ -94,11 +91,11 @@ func TestTransformRotate(t *testing.T) {
|
|||||||
func TestTransformShear(t *testing.T) {
|
func TestTransformShear(t *testing.T) {
|
||||||
p := Point{X: 1, Y: 1}
|
p := Point{X: 1, Y: 1}
|
||||||
|
|
||||||
r := AffineId().Shear(Point{}, math.Pi/4, 0).Transform(p)
|
r := Affine2D{}.Shear(Point{}, math.Pi/4, 0).Transform(p)
|
||||||
if !eq(r, Pt(2, 1)) {
|
if !eq(r, Pt(2, 1)) {
|
||||||
t.Errorf("shear transformation mismatch: have %v, want {2 1}", r)
|
t.Errorf("shear transformation mismatch: have %v, want {2 1}", r)
|
||||||
}
|
}
|
||||||
i := AffineId().Shear(Point{}, math.Pi/4, 0).Invert().Transform(r)
|
i := Affine2D{}.Shear(Point{}, math.Pi/4, 0).Invert().Transform(r)
|
||||||
if !eq(i, p) {
|
if !eq(i, p) {
|
||||||
t.Errorf("shear transformation inverse mismatch: have %v, want %v", i, p)
|
t.Errorf("shear transformation inverse mismatch: have %v, want %v", i, p)
|
||||||
}
|
}
|
||||||
@@ -110,11 +107,11 @@ func TestTransformMultiply(t *testing.T) {
|
|||||||
s := Point{X: -1, Y: 2}
|
s := Point{X: -1, Y: 2}
|
||||||
a := float32(-math.Pi / 2)
|
a := float32(-math.Pi / 2)
|
||||||
|
|
||||||
r := AffineId().Offset(o).Scale(Point{}, s).Rotate(Point{}, a).Shear(Point{}, math.Pi/4, 0).Transform(p)
|
r := Affine2D{}.Offset(o).Scale(Point{}, s).Rotate(Point{}, a).Shear(Point{}, math.Pi/4, 0).Transform(p)
|
||||||
if !eq(r, Pt(1, 3)) {
|
if !eq(r, Pt(1, 3)) {
|
||||||
t.Errorf("complex transformation mismatch: have %v, want {1 3}", r)
|
t.Errorf("complex transformation mismatch: have %v, want {1 3}", r)
|
||||||
}
|
}
|
||||||
i := AffineId().Offset(o).Scale(Point{}, s).Rotate(Point{}, a).Shear(Point{}, math.Pi/4, 0).Invert().Transform(r)
|
i := Affine2D{}.Offset(o).Scale(Point{}, s).Rotate(Point{}, a).Shear(Point{}, math.Pi/4, 0).Invert().Transform(r)
|
||||||
if !eq(i, p) {
|
if !eq(i, p) {
|
||||||
t.Errorf("complex transformation inverse mismatch: have %v, want %v", i, p)
|
t.Errorf("complex transformation inverse mismatch: have %v, want %v", i, p)
|
||||||
}
|
}
|
||||||
@@ -166,7 +163,7 @@ func TestPrimes(t *testing.T) {
|
|||||||
func TestTransformScaleAround(t *testing.T) {
|
func TestTransformScaleAround(t *testing.T) {
|
||||||
p := Pt(-1, -1)
|
p := Pt(-1, -1)
|
||||||
target := Pt(-6, -13)
|
target := Pt(-6, -13)
|
||||||
pt := AffineId().Scale(Pt(4, 5), Pt(2, 3)).Transform(p)
|
pt := Affine2D{}.Scale(Pt(4, 5), Pt(2, 3)).Transform(p)
|
||||||
if !eq(pt, target) {
|
if !eq(pt, target) {
|
||||||
t.Log(pt, "!=", target)
|
t.Log(pt, "!=", target)
|
||||||
t.Error("Scale not as expected")
|
t.Error("Scale not as expected")
|
||||||
@@ -175,7 +172,7 @@ func TestTransformScaleAround(t *testing.T) {
|
|||||||
|
|
||||||
func TestTransformRotateAround(t *testing.T) {
|
func TestTransformRotateAround(t *testing.T) {
|
||||||
p := Pt(-1, -1)
|
p := Pt(-1, -1)
|
||||||
pt := AffineId().Rotate(Pt(1, 1), -math.Pi/2).Transform(p)
|
pt := Affine2D{}.Rotate(Pt(1, 1), -math.Pi/2).Transform(p)
|
||||||
target := Pt(-1, 3)
|
target := Pt(-1, 3)
|
||||||
if !eq(pt, target) {
|
if !eq(pt, target) {
|
||||||
t.Log(pt, "!=", target)
|
t.Log(pt, "!=", target)
|
||||||
@@ -184,12 +181,12 @@ func TestTransformRotateAround(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMulOrder(t *testing.T) {
|
func TestMulOrder(t *testing.T) {
|
||||||
A := AffineId().Offset(Pt(100, 100))
|
A := Affine2D{}.Offset(Pt(100, 100))
|
||||||
B := AffineId().Scale(Point{}, Pt(2, 2))
|
B := Affine2D{}.Scale(Point{}, Pt(2, 2))
|
||||||
_ = A
|
_ = A
|
||||||
_ = B
|
_ = B
|
||||||
|
|
||||||
T1 := AffineId().Offset(Pt(100, 100)).Scale(Point{}, Pt(2, 2))
|
T1 := Affine2D{}.Offset(Pt(100, 100)).Scale(Point{}, Pt(2, 2))
|
||||||
T2 := B.Mul(A)
|
T2 := B.Mul(A)
|
||||||
|
|
||||||
if T1 != T2 {
|
if T1 != T2 {
|
||||||
@@ -202,9 +199,9 @@ func TestMulOrder(t *testing.T) {
|
|||||||
func BenchmarkTransformOffset(b *testing.B) {
|
func BenchmarkTransformOffset(b *testing.B) {
|
||||||
p := Point{X: 1, Y: 2}
|
p := Point{X: 1, Y: 2}
|
||||||
o := Point{X: 0.5, Y: 0.5}
|
o := Point{X: 0.5, Y: 0.5}
|
||||||
aff := AffineId().Offset(o)
|
aff := Affine2D{}.Offset(o)
|
||||||
|
|
||||||
for b.Loop() {
|
for i := 0; i < b.N; i++ {
|
||||||
p = aff.Transform(p)
|
p = aff.Transform(p)
|
||||||
}
|
}
|
||||||
_ = p
|
_ = p
|
||||||
@@ -213,8 +210,8 @@ func BenchmarkTransformOffset(b *testing.B) {
|
|||||||
func BenchmarkTransformScale(b *testing.B) {
|
func BenchmarkTransformScale(b *testing.B) {
|
||||||
p := Point{X: 1, Y: 2}
|
p := Point{X: 1, Y: 2}
|
||||||
s := Point{X: 0.5, Y: 0.5}
|
s := Point{X: 0.5, Y: 0.5}
|
||||||
aff := AffineId().Scale(Point{}, s)
|
aff := Affine2D{}.Scale(Point{}, s)
|
||||||
for b.Loop() {
|
for i := 0; i < b.N; i++ {
|
||||||
p = aff.Transform(p)
|
p = aff.Transform(p)
|
||||||
}
|
}
|
||||||
_ = p
|
_ = p
|
||||||
@@ -223,163 +220,36 @@ func BenchmarkTransformScale(b *testing.B) {
|
|||||||
func BenchmarkTransformRotate(b *testing.B) {
|
func BenchmarkTransformRotate(b *testing.B) {
|
||||||
p := Point{X: 1, Y: 2}
|
p := Point{X: 1, Y: 2}
|
||||||
a := float32(math.Pi / 2)
|
a := float32(math.Pi / 2)
|
||||||
aff := AffineId().Rotate(Point{}, a)
|
aff := Affine2D{}.Rotate(Point{}, a)
|
||||||
for b.Loop() {
|
for i := 0; i < b.N; i++ {
|
||||||
p = aff.Transform(p)
|
p = aff.Transform(p)
|
||||||
}
|
}
|
||||||
_ = p
|
_ = p
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkTransformTranslateMultiply(b *testing.B) {
|
func BenchmarkTransformTranslateMultiply(b *testing.B) {
|
||||||
a := AffineId().Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
|
a := Affine2D{}.Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
|
||||||
t := AffineId().Offset(Point{X: 0.5, Y: 0.5})
|
t := Affine2D{}.Offset(Point{X: 0.5, Y: 0.5})
|
||||||
|
|
||||||
for b.Loop() {
|
for i := 0; i < b.N; i++ {
|
||||||
a = a.Mul(t)
|
a = a.Mul(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkTransformScaleMultiply(b *testing.B) {
|
func BenchmarkTransformScaleMultiply(b *testing.B) {
|
||||||
a := AffineId().Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
|
a := Affine2D{}.Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
|
||||||
t := AffineId().Offset(Point{X: 0.5, Y: 0.5}).Scale(Point{}, Point{X: 0.4, Y: -0.5})
|
t := Affine2D{}.Offset(Point{X: 0.5, Y: 0.5}).Scale(Point{}, Point{X: 0.4, Y: -0.5})
|
||||||
|
|
||||||
for b.Loop() {
|
for i := 0; i < b.N; i++ {
|
||||||
a = a.Mul(t)
|
a = a.Mul(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkTransformMultiply(b *testing.B) {
|
func BenchmarkTransformMultiply(b *testing.B) {
|
||||||
a := AffineId().Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
|
a := Affine2D{}.Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
|
||||||
t := AffineId().Offset(Point{X: 0.5, Y: 0.5}).Rotate(Point{}, math.Pi/7)
|
t := Affine2D{}.Offset(Point{X: 0.5, Y: 0.5}).Rotate(Point{}, math.Pi/7)
|
||||||
|
|
||||||
for b.Loop() {
|
for i := 0; i < b.N; i++ {
|
||||||
a = a.Mul(t)
|
a = a.Mul(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewAffine2D(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
sx, hx, ox, hy, sy, oy float32
|
|
||||||
expected Affine2D
|
|
||||||
}{
|
|
||||||
{1, 0, 0, 0, 1, 0, AffineId()},
|
|
||||||
{2, 0, 5, 0, 3, 7, Affine2D{a: 1, b: 0, c: 5, d: 0, e: 2, f: 7}},
|
|
||||||
{-1, 2, 3, 4, -5, 6, Affine2D{a: -2, b: 2, c: 3, d: 4, e: -6, f: 6}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range tests {
|
|
||||||
got := NewAffine2D(test.sx, test.hx, test.ox, test.hy, test.sy, test.oy)
|
|
||||||
if !eqaff(got, test.expected) {
|
|
||||||
t.Errorf(
|
|
||||||
"Test %d: NewAffine2D(%v, %v, %v, %v, %v, %v) = %v, want %v",
|
|
||||||
i, test.sx, test.hx, test.ox, test.hy, test.sy, test.oy, got, test.expected,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAffineId(t *testing.T) {
|
|
||||||
id := AffineId()
|
|
||||||
|
|
||||||
testPoints := []Point{
|
|
||||||
{0, 0},
|
|
||||||
{1, 0},
|
|
||||||
{0, 1},
|
|
||||||
{-1, -1},
|
|
||||||
{10, 20},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range testPoints {
|
|
||||||
transformed := id.Transform(p)
|
|
||||||
if !eq(transformed, p) {
|
|
||||||
t.Errorf("Identity transform changed point: %v -> %v", p, transformed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestElems(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
aff Affine2D
|
|
||||||
sx, hx, ox, hy, sy, oy float32
|
|
||||||
}{
|
|
||||||
{AffineId(), 1, 0, 0, 0, 1, 0},
|
|
||||||
{Affine2D{a: 1, b: 2, c: 3, d: 4, e: 5, f: 6}, 2, 2, 3, 4, 6, 6},
|
|
||||||
{NewAffine2D(7, 8, 9, 10, 11, 12), 7, 8, 9, 10, 11, 12},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range tests {
|
|
||||||
sx, hx, ox, hy, sy, oy := test.aff.Elems()
|
|
||||||
if sx != test.sx || hx != test.hx || ox != test.ox ||
|
|
||||||
hy != test.hy || sy != test.sy || oy != test.oy {
|
|
||||||
t.Errorf(
|
|
||||||
"Test %d: %v.Elems() = (%v, %v, %v, %v, %v, %v), want (%v, %v, %v, %v, %v, %v)",
|
|
||||||
i, test.aff, sx, hx, ox, hy, sy, oy, test.sx, test.hx, test.ox, test.hy, test.sy, test.oy,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSplit(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
aff Affine2D
|
|
||||||
expectedSRS Affine2D
|
|
||||||
expectedOffset Point
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
AffineId(),
|
|
||||||
AffineId(),
|
|
||||||
Point{0, 0},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Affine2D{a: 1, b: 2, c: 3, d: 4, e: 5, f: 6},
|
|
||||||
Affine2D{a: 1, b: 2, c: 0, d: 4, e: 5, f: 0},
|
|
||||||
Point{3, 6},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
NewAffine2D(2, 0, 10, 0, 3, 20),
|
|
||||||
NewAffine2D(2, 0, 0, 0, 3, 0),
|
|
||||||
Point{10, 20},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range tests {
|
|
||||||
srs, offset := test.aff.Split()
|
|
||||||
if !eqaff(srs, test.expectedSRS) || !eq(offset, test.expectedOffset) {
|
|
||||||
t.Errorf(
|
|
||||||
"Test %d: %v.Split() = (%v, %v), want (%v, %v)",
|
|
||||||
i, test.aff, srs, offset, test.expectedSRS, test.expectedOffset,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShear(t *testing.T) {
|
|
||||||
p := Pt(2, 3)
|
|
||||||
origin := Pt(1, 1)
|
|
||||||
|
|
||||||
shearX := AffineId().Shear(origin, math.Pi/4, 0)
|
|
||||||
resultX := shearX.Transform(p)
|
|
||||||
expectedX := Pt(4, 3)
|
|
||||||
|
|
||||||
if !eq(resultX, expectedX) {
|
|
||||||
t.Errorf("Shear around origin in X: got %v, want %v", resultX, expectedX)
|
|
||||||
}
|
|
||||||
|
|
||||||
inverseX := shearX.Invert().Transform(resultX)
|
|
||||||
if !eq(inverseX, p) {
|
|
||||||
t.Errorf("Inverse shear X: got %v, want %v", inverseX, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
shearY := AffineId().Shear(origin, 0, math.Pi/4)
|
|
||||||
resultY := shearY.Transform(p)
|
|
||||||
expectedY := Pt(2, 4)
|
|
||||||
|
|
||||||
if !eq(resultY, expectedY) {
|
|
||||||
t.Errorf("Shear around origin in Y: got %v, want %v", resultY, expectedY)
|
|
||||||
}
|
|
||||||
|
|
||||||
inverseY := shearY.Invert().Transform(resultY)
|
|
||||||
if !eq(inverseY, p) {
|
|
||||||
t.Errorf("Inverse shear Y: got %v, want %v", inverseY, p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Generated
+67
-39
@@ -1,58 +1,86 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
|
"android": {
|
||||||
|
"inputs": {
|
||||||
|
"devshell": "devshell",
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1659298920,
|
||||||
|
"narHash": "sha256-LgRMge8BZUG15EN43iDJOlnEMX1dvRprB7SaoNqgibU=",
|
||||||
|
"owner": "tadfisher",
|
||||||
|
"repo": "android-nixpkgs",
|
||||||
|
"rev": "d4f20a3cd4ce961bb23b48447457f6810d69ae5e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "tadfisher",
|
||||||
|
"repo": "android-nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"devshell": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": [
|
||||||
|
"android",
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"nixpkgs": [
|
||||||
|
"android",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1658746384,
|
||||||
|
"narHash": "sha256-CCJcoMOcXyZFrV1ag4XMTpAPjLWb4Anbv+ktXFI1ry0=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "devshell",
|
||||||
|
"rev": "0ffc7937bb5e8141af03d462b468bd071eb18e1b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "devshell",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1656928814,
|
||||||
|
"narHash": "sha256-RIFfgBuKz6Hp89yRr7+NR5tzIAbn52h8vT6vXkYjZoM=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "7e2a3b3dfd9af950a856d66b0a7d01e3c18aa249",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1747953325,
|
"lastModified": 1659305579,
|
||||||
"narHash": "sha256-y2ZtlIlNTuVJUZCqzZAhIw5rrKP4DOSklev6c8PyCkQ=",
|
"narHash": "sha256-SFeQTmh7hc9Y2fSkooHaoS8mDfPa04sfmUCtQ8MA6Pg=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "55d1f923c480dadce40f5231feb472e81b0bab48",
|
"rev": "5857574d45925585baffde730369414319228a84",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"ref": "nixos-25.05",
|
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": "nixpkgs",
|
"android": "android",
|
||||||
"utils": "utils"
|
"nixpkgs": "nixpkgs"
|
||||||
}
|
|
||||||
},
|
|
||||||
"systems": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"utils": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1731533236,
|
|
||||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,38 +3,42 @@
|
|||||||
description = "Gio build environment";
|
description = "Gio build environment";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
|
nixpkgs.url = "github:NixOS/nixpkgs";
|
||||||
utils.url = "github:numtide/flake-utils";
|
android.url = "github:tadfisher/android-nixpkgs";
|
||||||
|
android.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, utils }:
|
outputs = { self, nixpkgs, android }:
|
||||||
utils.lib.eachDefaultSystem (system:
|
let
|
||||||
let
|
supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-darwin" ];
|
||||||
pkgs = import nixpkgs {
|
forAllSystems = f: nixpkgs.lib.genAttrs supportedSystems (system: f system);
|
||||||
inherit system;
|
in
|
||||||
|
{
|
||||||
# allow unfree Android packages.
|
devShells = forAllSystems
|
||||||
config.allowUnfree = true;
|
(system:
|
||||||
# accept the Android SDK license.
|
let
|
||||||
config.android_sdk.accept_license = true;
|
pkgs = import nixpkgs {
|
||||||
};
|
inherit system;
|
||||||
in {
|
|
||||||
devShells = let
|
|
||||||
android-sdk = let
|
|
||||||
androidComposition = pkgs.androidenv.composeAndroidPackages {
|
|
||||||
platformVersions = [ "latest" ];
|
|
||||||
abiVersions = [ "armeabi-v7a" "arm64-v8a" ];
|
|
||||||
# Omit the deprecated tools package.
|
|
||||||
toolsVersion = null;
|
|
||||||
includeNDK = true;
|
|
||||||
};
|
};
|
||||||
in androidComposition.androidsdk;
|
android-sdk = android.sdk.${system} (sdkPkgs: with sdkPkgs;
|
||||||
in {
|
[
|
||||||
default = with pkgs;
|
build-tools-31-0-0
|
||||||
mkShell (rec {
|
cmdline-tools-latest
|
||||||
ANDROID_HOME = "${android-sdk}/libexec/android-sdk";
|
platform-tools
|
||||||
packages = [ android-sdk jdk clang ]
|
platforms-android-31
|
||||||
++ (if stdenv.isLinux then [
|
ndk-bundle
|
||||||
|
]);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
default = with pkgs; mkShell
|
||||||
|
({
|
||||||
|
ANDROID_SDK_ROOT = "${android-sdk}/share/android-sdk";
|
||||||
|
JAVA_HOME = jdk8.home;
|
||||||
|
packages = [
|
||||||
|
android-sdk
|
||||||
|
jdk8
|
||||||
|
clang
|
||||||
|
] ++ (if stdenv.isLinux then [
|
||||||
vulkan-headers
|
vulkan-headers
|
||||||
libxkbcommon
|
libxkbcommon
|
||||||
wayland
|
wayland
|
||||||
@@ -42,13 +46,18 @@
|
|||||||
xorg.libXcursor
|
xorg.libXcursor
|
||||||
xorg.libXfixes
|
xorg.libXfixes
|
||||||
libGL
|
libGL
|
||||||
pkg-config
|
pkgconfig
|
||||||
] else
|
] else if stdenv.isDarwin then [
|
||||||
[ ]);
|
darwin.apple_sdk_11_0.frameworks.Foundation
|
||||||
} // (if stdenv.isLinux then {
|
darwin.apple_sdk_11_0.frameworks.Metal
|
||||||
LD_LIBRARY_PATH = "${vulkan-loader}/lib";
|
darwin.apple_sdk_11_0.frameworks.QuartzCore
|
||||||
} else
|
darwin.apple_sdk_11_0.frameworks.AppKit
|
||||||
{ }));
|
darwin.apple_sdk_11_0.MacOSX-SDK
|
||||||
};
|
] else [ ]);
|
||||||
});
|
} // (if stdenv.isLinux then {
|
||||||
|
LD_LIBRARY_PATH = "${vulkan-loader}/lib";
|
||||||
|
} else { }));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+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
|
||||||
|
|||||||
+43
-41
@@ -16,21 +16,23 @@ import (
|
|||||||
_ "image/png"
|
_ "image/png"
|
||||||
|
|
||||||
giofont "gioui.org/font"
|
giofont "gioui.org/font"
|
||||||
fontapi "github.com/go-text/typesetting/font"
|
"github.com/go-text/typesetting/font"
|
||||||
"github.com/go-text/typesetting/font/opentype"
|
fontapi "github.com/go-text/typesetting/opentype/api/font"
|
||||||
|
"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 *fontapi.Font
|
face font.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 := opentype.NewLoader(bytes.NewReader(src))
|
ld, err := loader.NewLoader(bytes.NewReader(src))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Face{}, err
|
return Face{}, err
|
||||||
}
|
}
|
||||||
@@ -47,11 +49,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 font.
|
// inferred font metadata.
|
||||||
// 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 := opentype.NewLoaders(bytes.NewReader(src))
|
lds, err := loader.NewLoaders(bytes.NewReader(src))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -74,7 +76,7 @@ func ParseCollection(src []byte) ([]giofont.FontFace, error) {
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DescriptionToFont(md fontapi.Description) giofont.Font {
|
func DescriptionToFont(md metadata.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),
|
||||||
@@ -82,30 +84,30 @@ func DescriptionToFont(md fontapi.Description) giofont.Font {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func FontToDescription(font giofont.Font) fontapi.Description {
|
func FontToDescription(font giofont.Font) metadata.Description {
|
||||||
return fontapi.Description{
|
return metadata.Description{
|
||||||
Family: string(font.Typeface),
|
Family: string(font.Typeface),
|
||||||
Aspect: fontapi.Aspect{
|
Aspect: metadata.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 font.
|
// parseLoader parses the contents of the loader into a face and its metadata.
|
||||||
func parseLoader(ld *opentype.Loader) (*fontapi.Font, giofont.Font, error) {
|
func parseLoader(ld *loader.Loader) (font.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(ft.Describe())
|
data := DescriptionToFont(metadata.Metadata(ld))
|
||||||
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() *fontapi.Face {
|
func (f Face) Face() font.Face {
|
||||||
return &fontapi.Face{Font: f.face}
|
return &fontapi.Face{Font: f.face}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,74 +119,74 @@ func (f Face) Font() giofont.Font {
|
|||||||
return f.font
|
return f.font
|
||||||
}
|
}
|
||||||
|
|
||||||
func gioStyle(s fontapi.Style) giofont.Style {
|
func gioStyle(s metadata.Style) giofont.Style {
|
||||||
switch s {
|
switch s {
|
||||||
case fontapi.StyleItalic:
|
case metadata.StyleItalic:
|
||||||
return giofont.Italic
|
return giofont.Italic
|
||||||
case fontapi.StyleNormal:
|
case metadata.StyleNormal:
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
return giofont.Regular
|
return giofont.Regular
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mdStyle(g giofont.Style) fontapi.Style {
|
func mdStyle(g giofont.Style) metadata.Style {
|
||||||
switch g {
|
switch g {
|
||||||
case giofont.Italic:
|
case giofont.Italic:
|
||||||
return fontapi.StyleItalic
|
return metadata.StyleItalic
|
||||||
case giofont.Regular:
|
case giofont.Regular:
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
return fontapi.StyleNormal
|
return metadata.StyleNormal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func gioWeight(w fontapi.Weight) giofont.Weight {
|
func gioWeight(w metadata.Weight) giofont.Weight {
|
||||||
switch w {
|
switch w {
|
||||||
case fontapi.WeightThin:
|
case metadata.WeightThin:
|
||||||
return giofont.Thin
|
return giofont.Thin
|
||||||
case fontapi.WeightExtraLight:
|
case metadata.WeightExtraLight:
|
||||||
return giofont.ExtraLight
|
return giofont.ExtraLight
|
||||||
case fontapi.WeightLight:
|
case metadata.WeightLight:
|
||||||
return giofont.Light
|
return giofont.Light
|
||||||
case fontapi.WeightNormal:
|
case metadata.WeightNormal:
|
||||||
return giofont.Normal
|
return giofont.Normal
|
||||||
case fontapi.WeightMedium:
|
case metadata.WeightMedium:
|
||||||
return giofont.Medium
|
return giofont.Medium
|
||||||
case fontapi.WeightSemibold:
|
case metadata.WeightSemibold:
|
||||||
return giofont.SemiBold
|
return giofont.SemiBold
|
||||||
case fontapi.WeightBold:
|
case metadata.WeightBold:
|
||||||
return giofont.Bold
|
return giofont.Bold
|
||||||
case fontapi.WeightExtraBold:
|
case metadata.WeightExtraBold:
|
||||||
return giofont.ExtraBold
|
return giofont.ExtraBold
|
||||||
case fontapi.WeightBlack:
|
case metadata.WeightBlack:
|
||||||
return giofont.Black
|
return giofont.Black
|
||||||
default:
|
default:
|
||||||
return giofont.Normal
|
return giofont.Normal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mdWeight(g giofont.Weight) fontapi.Weight {
|
func mdWeight(g giofont.Weight) metadata.Weight {
|
||||||
switch g {
|
switch g {
|
||||||
case giofont.Thin:
|
case giofont.Thin:
|
||||||
return fontapi.WeightThin
|
return metadata.WeightThin
|
||||||
case giofont.ExtraLight:
|
case giofont.ExtraLight:
|
||||||
return fontapi.WeightExtraLight
|
return metadata.WeightExtraLight
|
||||||
case giofont.Light:
|
case giofont.Light:
|
||||||
return fontapi.WeightLight
|
return metadata.WeightLight
|
||||||
case giofont.Normal:
|
case giofont.Normal:
|
||||||
return fontapi.WeightNormal
|
return metadata.WeightNormal
|
||||||
case giofont.Medium:
|
case giofont.Medium:
|
||||||
return fontapi.WeightMedium
|
return metadata.WeightMedium
|
||||||
case giofont.SemiBold:
|
case giofont.SemiBold:
|
||||||
return fontapi.WeightSemibold
|
return metadata.WeightSemibold
|
||||||
case giofont.Bold:
|
case giofont.Bold:
|
||||||
return fontapi.WeightBold
|
return metadata.WeightBold
|
||||||
case giofont.ExtraBold:
|
case giofont.ExtraBold:
|
||||||
return fontapi.WeightExtraBold
|
return metadata.WeightExtraBold
|
||||||
case giofont.Black:
|
case giofont.Black:
|
||||||
return fontapi.WeightBlack
|
return metadata.WeightBlack
|
||||||
default:
|
default:
|
||||||
return fontapi.WeightNormal
|
return metadata.WeightNormal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+62
-80
@@ -18,7 +18,6 @@ import (
|
|||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/internal/fling"
|
"gioui.org/internal/fling"
|
||||||
"gioui.org/io/event"
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/input"
|
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
@@ -38,19 +37,15 @@ type Hover struct {
|
|||||||
|
|
||||||
// Add the gesture to detect hovering over the current pointer area.
|
// Add the gesture to detect hovering over the current pointer area.
|
||||||
func (h *Hover) Add(ops *op.Ops) {
|
func (h *Hover) Add(ops *op.Ops) {
|
||||||
event.Op(ops, h)
|
pointer.InputOp{
|
||||||
|
Tag: h,
|
||||||
|
Kinds: pointer.Enter | pointer.Leave,
|
||||||
|
}.Add(ops)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update state and report whether a pointer is inside the area.
|
// Update state and report whether a pointer is inside the area.
|
||||||
func (h *Hover) Update(q input.Source) bool {
|
func (h *Hover) Update(q event.Queue) bool {
|
||||||
for {
|
for _, ev := range q.Events(h) {
|
||||||
ev, ok := q.Event(pointer.Filter{
|
|
||||||
Target: h,
|
|
||||||
Kinds: pointer.Enter | pointer.Leave | pointer.Cancel,
|
|
||||||
})
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
e, ok := ev.(pointer.Event)
|
e, ok := ev.(pointer.Event)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
@@ -112,6 +107,7 @@ type Drag struct {
|
|||||||
pressed bool
|
pressed bool
|
||||||
pid pointer.ID
|
pid pointer.ID
|
||||||
start f32.Point
|
start f32.Point
|
||||||
|
grab bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scroll detects scroll gestures and reduces them to
|
// Scroll detects scroll gestures and reduces them to
|
||||||
@@ -119,9 +115,11 @@ type Drag struct {
|
|||||||
// movements as well as drag and fling touch gestures.
|
// movements as well as drag and fling touch gestures.
|
||||||
type Scroll struct {
|
type Scroll struct {
|
||||||
dragging bool
|
dragging bool
|
||||||
|
axis Axis
|
||||||
estimator fling.Extrapolation
|
estimator fling.Extrapolation
|
||||||
flinger fling.Animation
|
flinger fling.Animation
|
||||||
pid pointer.ID
|
pid pointer.ID
|
||||||
|
grab bool
|
||||||
last int
|
last int
|
||||||
// Leftover scroll.
|
// Leftover scroll.
|
||||||
scroll float32
|
scroll float32
|
||||||
@@ -163,7 +161,10 @@ const touchSlop = unit.Dp(3)
|
|||||||
|
|
||||||
// Add the handler to the operation list to receive click events.
|
// Add the handler to the operation list to receive click events.
|
||||||
func (c *Click) Add(ops *op.Ops) {
|
func (c *Click) Add(ops *op.Ops) {
|
||||||
event.Op(ops, c)
|
pointer.InputOp{
|
||||||
|
Tag: c,
|
||||||
|
Kinds: pointer.Press | pointer.Release | pointer.Enter | pointer.Leave,
|
||||||
|
}.Add(ops)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hovered returns whether a pointer is inside the area.
|
// Hovered returns whether a pointer is inside the area.
|
||||||
@@ -176,16 +177,10 @@ func (c *Click) Pressed() bool {
|
|||||||
return c.pressed
|
return c.pressed
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update state and return the next click events, if any.
|
// Update state and return the click events.
|
||||||
func (c *Click) Update(q input.Source) (ClickEvent, bool) {
|
func (c *Click) Update(q event.Queue) []ClickEvent {
|
||||||
for {
|
var events []ClickEvent
|
||||||
evt, ok := q.Event(pointer.Filter{
|
for _, evt := range q.Events(c) {
|
||||||
Target: c,
|
|
||||||
Kinds: pointer.Press | pointer.Release | pointer.Enter | pointer.Leave | pointer.Cancel,
|
|
||||||
})
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
e, ok := evt.(pointer.Event)
|
e, ok := evt.(pointer.Event)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
@@ -197,15 +192,9 @@ func (c *Click) Update(q input.Source) (ClickEvent, bool) {
|
|||||||
}
|
}
|
||||||
c.pressed = false
|
c.pressed = false
|
||||||
if !c.entered || c.hovered {
|
if !c.entered || c.hovered {
|
||||||
return ClickEvent{
|
events = append(events, ClickEvent{Kind: KindClick, Position: e.Position.Round(), Source: e.Source, Modifiers: e.Modifiers, NumClicks: c.clicks})
|
||||||
Kind: KindClick,
|
|
||||||
Position: e.Position.Round(),
|
|
||||||
Source: e.Source,
|
|
||||||
Modifiers: e.Modifiers,
|
|
||||||
NumClicks: c.clicks,
|
|
||||||
}, true
|
|
||||||
} else {
|
} else {
|
||||||
return ClickEvent{Kind: KindCancel}, true
|
events = append(events, ClickEvent{Kind: KindCancel})
|
||||||
}
|
}
|
||||||
case pointer.Cancel:
|
case pointer.Cancel:
|
||||||
wasPressed := c.pressed
|
wasPressed := c.pressed
|
||||||
@@ -213,7 +202,7 @@ func (c *Click) Update(q input.Source) (ClickEvent, bool) {
|
|||||||
c.hovered = false
|
c.hovered = false
|
||||||
c.entered = false
|
c.entered = false
|
||||||
if wasPressed {
|
if wasPressed {
|
||||||
return ClickEvent{Kind: KindCancel}, true
|
events = append(events, ClickEvent{Kind: KindCancel})
|
||||||
}
|
}
|
||||||
case pointer.Press:
|
case pointer.Press:
|
||||||
if c.pressed {
|
if c.pressed {
|
||||||
@@ -235,7 +224,7 @@ func (c *Click) Update(q input.Source) (ClickEvent, bool) {
|
|||||||
c.clicks = 1
|
c.clicks = 1
|
||||||
}
|
}
|
||||||
c.clickedAt = e.Time
|
c.clickedAt = e.Time
|
||||||
return ClickEvent{Kind: KindPress, Position: e.Position.Round(), Source: e.Source, Modifiers: e.Modifiers, NumClicks: c.clicks}, true
|
events = append(events, ClickEvent{Kind: KindPress, Position: e.Position.Round(), Source: e.Source, Modifiers: e.Modifiers, NumClicks: c.clicks})
|
||||||
case pointer.Leave:
|
case pointer.Leave:
|
||||||
if !c.pressed {
|
if !c.pressed {
|
||||||
c.pid = e.PointerID
|
c.pid = e.PointerID
|
||||||
@@ -253,16 +242,25 @@ func (c *Click) Update(q input.Source) (ClickEvent, bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ClickEvent{}, false
|
return events
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ClickEvent) ImplementsEvent() {}
|
func (ClickEvent) ImplementsEvent() {}
|
||||||
|
|
||||||
// Add the handler to the operation list to receive scroll events.
|
// Add the handler to the operation list to receive scroll events.
|
||||||
// The bounds variable refers to the scrolling boundaries
|
// The bounds variable refers to the scrolling boundaries
|
||||||
// as defined in [pointer.Filter].
|
// as defined in io/pointer.InputOp.
|
||||||
func (s *Scroll) Add(ops *op.Ops) {
|
func (s *Scroll) Add(ops *op.Ops, bounds image.Rectangle) {
|
||||||
event.Op(ops, s)
|
oph := pointer.InputOp{
|
||||||
|
Tag: s,
|
||||||
|
Grab: s.grab,
|
||||||
|
Kinds: pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll,
|
||||||
|
ScrollBounds: bounds,
|
||||||
|
}
|
||||||
|
oph.Add(ops)
|
||||||
|
if s.flinger.Active() {
|
||||||
|
op.InvalidateOp{}.Add(ops)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop any remaining fling movement.
|
// Stop any remaining fling movement.
|
||||||
@@ -271,19 +269,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, scrollx, scrolly pointer.ScrollRange) int {
|
func (s *Scroll) Update(cfg unit.Metric, q event.Queue, t time.Time, axis Axis) int {
|
||||||
total := 0
|
if s.axis != axis {
|
||||||
f := pointer.Filter{
|
s.axis = axis
|
||||||
Target: s,
|
return 0
|
||||||
Kinds: pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll | pointer.Cancel,
|
|
||||||
ScrollX: scrollx,
|
|
||||||
ScrollY: scrolly,
|
|
||||||
}
|
}
|
||||||
for {
|
total := 0
|
||||||
evt, ok := q.Event(f)
|
for _, evt := range q.Events(s) {
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
e, ok := evt.(pointer.Event)
|
e, ok := evt.(pointer.Event)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
@@ -300,7 +292,7 @@ func (s *Scroll) Update(cfg unit.Metric, q input.Source, t time.Time, axis Axis,
|
|||||||
}
|
}
|
||||||
s.Stop()
|
s.Stop()
|
||||||
s.estimator = fling.Extrapolation{}
|
s.estimator = fling.Extrapolation{}
|
||||||
v := s.val(axis, e.Position)
|
v := s.val(e.Position)
|
||||||
s.last = int(math.Round(float64(v)))
|
s.last = int(math.Round(float64(v)))
|
||||||
s.estimator.Sample(e.Time, v)
|
s.estimator.Sample(e.Time, v)
|
||||||
s.dragging = true
|
s.dragging = true
|
||||||
@@ -316,14 +308,13 @@ func (s *Scroll) Update(cfg unit.Metric, q input.Source, t time.Time, axis Axis,
|
|||||||
fallthrough
|
fallthrough
|
||||||
case pointer.Cancel:
|
case pointer.Cancel:
|
||||||
s.dragging = false
|
s.dragging = false
|
||||||
|
s.grab = false
|
||||||
case pointer.Scroll:
|
case pointer.Scroll:
|
||||||
switch axis {
|
switch s.axis {
|
||||||
case Horizontal:
|
case Horizontal:
|
||||||
s.scroll += e.Scroll.X
|
s.scroll += e.Scroll.X
|
||||||
case Vertical:
|
case Vertical:
|
||||||
s.scroll += e.Scroll.Y
|
s.scroll += e.Scroll.Y
|
||||||
case Both:
|
|
||||||
s.scroll += e.Scroll.X + e.Scroll.Y
|
|
||||||
}
|
}
|
||||||
iscroll := int(s.scroll)
|
iscroll := int(s.scroll)
|
||||||
s.scroll -= float32(iscroll)
|
s.scroll -= float32(iscroll)
|
||||||
@@ -332,14 +323,14 @@ func (s *Scroll) Update(cfg unit.Metric, q input.Source, t time.Time, axis Axis,
|
|||||||
if !s.dragging || s.pid != e.PointerID {
|
if !s.dragging || s.pid != e.PointerID {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
val := s.val(axis, e.Position)
|
val := s.val(e.Position)
|
||||||
s.estimator.Sample(e.Time, val)
|
s.estimator.Sample(e.Time, val)
|
||||||
v := int(math.Round(float64(val)))
|
v := int(math.Round(float64(val)))
|
||||||
dist := s.last - v
|
dist := s.last - v
|
||||||
if e.Priority < pointer.Grabbed {
|
if e.Priority < pointer.Grabbed {
|
||||||
slop := cfg.Dp(touchSlop)
|
slop := cfg.Dp(touchSlop)
|
||||||
if dist := dist; dist >= slop || -slop >= dist {
|
if dist := dist; dist >= slop || -slop >= dist {
|
||||||
q.Execute(pointer.GrabCmd{Tag: s, ID: e.PointerID})
|
s.grab = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
s.last = v
|
s.last = v
|
||||||
@@ -348,22 +339,14 @@ func (s *Scroll) Update(cfg unit.Metric, q input.Source, t time.Time, axis Axis,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
total += s.flinger.Tick(t)
|
total += s.flinger.Tick(t)
|
||||||
if s.flinger.Active() {
|
|
||||||
q.Execute(op.InvalidateCmd{})
|
|
||||||
}
|
|
||||||
return total
|
return total
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Scroll) val(axis Axis, p f32.Point) float32 {
|
func (s *Scroll) val(p f32.Point) float32 {
|
||||||
switch axis {
|
if s.axis == Horizontal {
|
||||||
case Horizontal:
|
|
||||||
return p.X
|
return p.X
|
||||||
case Vertical:
|
} else {
|
||||||
return p.Y
|
return p.Y
|
||||||
case Both:
|
|
||||||
return p.X + p.Y
|
|
||||||
default:
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,20 +364,18 @@ func (s *Scroll) State() ScrollState {
|
|||||||
|
|
||||||
// Add the handler to the operation list to receive drag events.
|
// Add the handler to the operation list to receive drag events.
|
||||||
func (d *Drag) Add(ops *op.Ops) {
|
func (d *Drag) Add(ops *op.Ops) {
|
||||||
event.Op(ops, d)
|
pointer.InputOp{
|
||||||
|
Tag: d,
|
||||||
|
Grab: d.grab,
|
||||||
|
Kinds: pointer.Press | pointer.Drag | pointer.Release,
|
||||||
|
}.Add(ops)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update state and return the next drag event, if any.
|
// Update state and return the drag events.
|
||||||
func (d *Drag) Update(cfg unit.Metric, q input.Source, axis Axis) (pointer.Event, bool) {
|
func (d *Drag) Update(cfg unit.Metric, q event.Queue, axis Axis) []pointer.Event {
|
||||||
for {
|
var events []pointer.Event
|
||||||
ev, ok := q.Event(pointer.Filter{
|
for _, e := range q.Events(d) {
|
||||||
Target: d,
|
e, ok := e.(pointer.Event)
|
||||||
Kinds: pointer.Press | pointer.Drag | pointer.Release | pointer.Cancel,
|
|
||||||
})
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
e, ok := ev.(pointer.Event)
|
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -427,7 +408,7 @@ func (d *Drag) Update(cfg unit.Metric, q input.Source, axis Axis) (pointer.Event
|
|||||||
diff := e.Position.Sub(d.start)
|
diff := e.Position.Sub(d.start)
|
||||||
slop := cfg.Dp(touchSlop)
|
slop := cfg.Dp(touchSlop)
|
||||||
if diff.X*diff.X+diff.Y*diff.Y > float32(slop*slop) {
|
if diff.X*diff.X+diff.Y*diff.Y > float32(slop*slop) {
|
||||||
q.Execute(pointer.GrabCmd{Tag: d, ID: e.PointerID})
|
d.grab = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case pointer.Release, pointer.Cancel:
|
case pointer.Release, pointer.Cancel:
|
||||||
@@ -436,12 +417,13 @@ func (d *Drag) Update(cfg unit.Metric, q input.Source, axis Axis) (pointer.Event
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
d.dragging = false
|
d.dragging = false
|
||||||
|
d.grab = false
|
||||||
}
|
}
|
||||||
|
|
||||||
return e, true
|
events = append(events, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
return pointer.Event{}, false
|
return events
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dragging reports whether it is currently in use.
|
// Dragging reports whether it is currently in use.
|
||||||
|
|||||||
+17
-17
@@ -9,8 +9,8 @@ import (
|
|||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/io/event"
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/input"
|
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
|
"gioui.org/io/router"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
"gioui.org/op/clip"
|
"gioui.org/op/clip"
|
||||||
)
|
)
|
||||||
@@ -22,21 +22,20 @@ func TestHover(t *testing.T) {
|
|||||||
stack := clip.Rect(rect).Push(ops)
|
stack := clip.Rect(rect).Push(ops)
|
||||||
h.Add(ops)
|
h.Add(ops)
|
||||||
stack.Pop()
|
stack.Pop()
|
||||||
r := new(input.Router)
|
r := new(router.Router)
|
||||||
h.Update(r.Source())
|
|
||||||
r.Frame(ops)
|
r.Frame(ops)
|
||||||
|
|
||||||
r.Queue(
|
r.Queue(
|
||||||
pointer.Event{Kind: pointer.Move, Position: f32.Pt(30, 30)},
|
pointer.Event{Kind: pointer.Move, Position: f32.Pt(30, 30)},
|
||||||
)
|
)
|
||||||
if !h.Update(r.Source()) {
|
if !h.Update(r) {
|
||||||
t.Fatal("expected hovered")
|
t.Fatal("expected hovered")
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Queue(
|
r.Queue(
|
||||||
pointer.Event{Kind: pointer.Move, Position: f32.Pt(50, 50)},
|
pointer.Event{Kind: pointer.Move, Position: f32.Pt(50, 50)},
|
||||||
)
|
)
|
||||||
if h.Update(r.Source()) {
|
if h.Update(r) {
|
||||||
t.Fatal("expected not hovered")
|
t.Fatal("expected not hovered")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,21 +71,12 @@ func TestMouseClicks(t *testing.T) {
|
|||||||
var ops op.Ops
|
var ops op.Ops
|
||||||
click.Add(&ops)
|
click.Add(&ops)
|
||||||
|
|
||||||
var r input.Router
|
var r router.Router
|
||||||
click.Update(r.Source())
|
|
||||||
r.Frame(&ops)
|
r.Frame(&ops)
|
||||||
r.Queue(tc.events...)
|
r.Queue(tc.events...)
|
||||||
|
|
||||||
var clicks []ClickEvent
|
events := click.Update(&r)
|
||||||
for {
|
clicks := filterMouseClicks(events)
|
||||||
ev, ok := click.Update(r.Source())
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if ev.Kind == KindClick {
|
|
||||||
clicks = append(clicks, ev)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if got, want := len(clicks), len(tc.clicks); got != want {
|
if got, want := len(clicks), len(tc.clicks); got != want {
|
||||||
t.Fatalf("got %d mouse clicks, expected %d", got, want)
|
t.Fatalf("got %d mouse clicks, expected %d", got, want)
|
||||||
}
|
}
|
||||||
@@ -116,3 +106,13 @@ func mouseClickEvents(times ...time.Duration) []event.Event {
|
|||||||
}
|
}
|
||||||
return events
|
return events
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func filterMouseClicks(events []ClickEvent) []ClickEvent {
|
||||||
|
var clicks []ClickEvent
|
||||||
|
for _, ev := range events {
|
||||||
|
if ev.Kind == KindClick {
|
||||||
|
clicks = append(clicks, ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clicks
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
module gioui.org
|
module gioui.org
|
||||||
|
|
||||||
go 1.23.8
|
go 1.19
|
||||||
|
|
||||||
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.3.0
|
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
|
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95
|
||||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0
|
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91
|
||||||
golang.org/x/image v0.26.0
|
golang.org/x/image v0.5.0
|
||||||
golang.org/x/sys v0.33.0
|
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64
|
||||||
golang.org/x/text v0.24.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require golang.org/x/text v0.7.0
|
||||||
|
|||||||
@@ -1,19 +1,43 @@
|
|||||||
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.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML4iF4=
|
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 h1:FQivqchis6bE2/9uF70M2gmmLpe82esEm2QadL0TEJo=
|
||||||
github.com/go-text/typesetting v0.3.0/go.mod h1:qjZLkhRgOEYMhU9eHBr3AR4sfnGJvOXNLt8yRAySFuY=
|
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k=
|
||||||
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
|
github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI=
|
||||||
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 h1:tMSqXTK+AQdW3LpCbfatHSRPHeW6+2WuxaVQuHftn80=
|
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 h1:sBdrWpxhGDdTAYNqbgBLAR+ULAPPhfgncLr1X0lyWtg=
|
||||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8=
|
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||||
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
|
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 h1:ryT6Nf0R83ZgD8WnFFdfI8wCeyqgdXWN4+CkFVNPAT0=
|
||||||
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
|
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8=
|
||||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
|
||||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
|
||||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
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=
|
||||||
|
|||||||
+13
-17
@@ -4,16 +4,12 @@ package gpu
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
|
||||||
|
"gioui.org/internal/f32"
|
||||||
)
|
)
|
||||||
|
|
||||||
type textureCacheKey struct {
|
type resourceCache struct {
|
||||||
filter byte
|
res map[interface{}]resourceCacheValue
|
||||||
handle any
|
|
||||||
}
|
|
||||||
|
|
||||||
type textureCache struct {
|
|
||||||
res map[textureCacheKey]resourceCacheValue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type resourceCacheValue struct {
|
type resourceCacheValue struct {
|
||||||
@@ -35,19 +31,19 @@ type opCache struct {
|
|||||||
type opCacheValue struct {
|
type opCacheValue struct {
|
||||||
data pathData
|
data pathData
|
||||||
|
|
||||||
bounds image.Rectangle
|
bounds f32.Rectangle
|
||||||
// the fields below are handled by opCache
|
// the fields below are handled by opCache
|
||||||
key opKey
|
key opKey
|
||||||
keep bool
|
keep bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTextureCache() *textureCache {
|
func newResourceCache() *resourceCache {
|
||||||
return &textureCache{
|
return &resourceCache{
|
||||||
res: make(map[textureCacheKey]resourceCacheValue),
|
res: make(map[interface{}]resourceCacheValue),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *textureCache) get(key textureCacheKey) (resource, bool) {
|
func (r *resourceCache) get(key interface{}) (resource, bool) {
|
||||||
v, exists := r.res[key]
|
v, exists := r.res[key]
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, false
|
return nil, false
|
||||||
@@ -59,17 +55,17 @@ func (r *textureCache) get(key textureCacheKey) (resource, bool) {
|
|||||||
return v.resource, exists
|
return v.resource, exists
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *textureCache) put(key textureCacheKey, val resource) {
|
func (r *resourceCache) put(key interface{}, val resource) {
|
||||||
v, exists := r.res[key]
|
v, exists := r.res[key]
|
||||||
if exists && v.used {
|
if exists && v.used {
|
||||||
panic(fmt.Errorf("key exists, %v", key))
|
panic(fmt.Errorf("key exists, %p", key))
|
||||||
}
|
}
|
||||||
v.used = true
|
v.used = true
|
||||||
v.resource = val
|
v.resource = val
|
||||||
r.res[key] = v
|
r.res[key] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *textureCache) frame() {
|
func (r *resourceCache) frame() {
|
||||||
for k, v := range r.res {
|
for k, v := range r.res {
|
||||||
if v.used {
|
if v.used {
|
||||||
v.used = false
|
v.used = false
|
||||||
@@ -81,7 +77,7 @@ func (r *textureCache) frame() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *textureCache) release() {
|
func (r *resourceCache) release() {
|
||||||
for _, v := range r.res {
|
for _, v := range r.res {
|
||||||
v.resource.release()
|
v.resource.release()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
|
package gpu
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func BenchmarkResourceCache(b *testing.B) {
|
||||||
|
offset := 0
|
||||||
|
const N = 100
|
||||||
|
|
||||||
|
cache := newResourceCache()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
// half are the same and half updated
|
||||||
|
for k := 0; k < N; k++ {
|
||||||
|
cache.put(offset+k, nullResource{})
|
||||||
|
}
|
||||||
|
cache.frame()
|
||||||
|
offset += N / 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type nullResource struct{}
|
||||||
|
|
||||||
|
func (nullResource) release() {}
|
||||||
+6
-6
@@ -19,10 +19,10 @@ type quadSplitter struct {
|
|||||||
|
|
||||||
func encodeQuadTo(data []byte, meta uint32, from, ctrl, to f32.Point) {
|
func encodeQuadTo(data []byte, meta uint32, from, ctrl, to f32.Point) {
|
||||||
// inlined code:
|
// inlined code:
|
||||||
// encodeVertex(data, meta, 1, -1, from, ctrl, to)
|
// encodeVertex(data, meta, -1, 1, from, ctrl, to)
|
||||||
// encodeVertex(data[vertStride:], meta, 1, 1, from, ctrl, to)
|
// encodeVertex(data[vertStride:], meta, 1, 1, from, ctrl, to)
|
||||||
// encodeVertex(data[vertStride*2:], meta, -1, -1, from, ctrl, to)
|
// encodeVertex(data[vertStride*2:], meta, -1, -1, from, ctrl, to)
|
||||||
// encodeVertex(data[vertStride*3:], meta, -1, 1, from, ctrl, to)
|
// encodeVertex(data[vertStride*3:], meta, 1, -1, from, ctrl, to)
|
||||||
// this code needs to stay in sync with `vertex.encode`.
|
// this code needs to stay in sync with `vertex.encode`.
|
||||||
|
|
||||||
bo := binary.LittleEndian
|
bo := binary.LittleEndian
|
||||||
@@ -48,10 +48,10 @@ func encodeQuadTo(data []byte, meta uint32, from, ctrl, to f32.Point) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
nwCorner = 1*0.5 + 0*0.25
|
nwCorner = 1*0.25 + 0*0.5
|
||||||
neCorner = 1*0.5 + 1*0.25
|
neCorner = 1*0.25 + 1*0.5
|
||||||
swCorner = 0*0.5 + 0*0.25
|
swCorner = 0*0.25 + 0*0.5
|
||||||
seCorner = 0*0.5 + 1*0.25
|
seCorner = 0*0.25 + 1*0.5
|
||||||
)
|
)
|
||||||
|
|
||||||
func encodeVertex(data []byte, meta uint32, cornerx, cornery int16, from, ctrl, to f32.Point) {
|
func encodeVertex(data []byte, meta uint32, cornerx, cornery int16, from, ctrl, to f32.Point) {
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
func BenchmarkEncodeQuadTo(b *testing.B) {
|
func BenchmarkEncodeQuadTo(b *testing.B) {
|
||||||
var data [vertStride * 4]byte
|
var data [vertStride * 4]byte
|
||||||
for i := 0; b.Loop(); i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
v := float32(i)
|
v := float32(i)
|
||||||
encodeQuadTo(data[:], 123,
|
encodeQuadTo(data[:], 123,
|
||||||
f32.Point{X: v, Y: v},
|
f32.Point{X: v, Y: v},
|
||||||
|
|||||||
+2202
File diff suppressed because it is too large
Load Diff
+129
@@ -0,0 +1,129 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
+193
-213
@@ -9,13 +9,12 @@ package gpu
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"math"
|
"math"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"slices"
|
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
@@ -45,10 +44,14 @@ type GPU interface {
|
|||||||
Clear(color color.NRGBA)
|
Clear(color color.NRGBA)
|
||||||
// Frame draws the graphics operations from op into a viewport of target.
|
// Frame draws the graphics operations from op into a viewport of target.
|
||||||
Frame(frame *op.Ops, target RenderTarget, viewport image.Point) error
|
Frame(frame *op.Ops, target RenderTarget, viewport image.Point) error
|
||||||
|
// Profile returns the last available profiling information. Profiling
|
||||||
|
// information is requested when Frame sees an io/profile.Op, and the result
|
||||||
|
// is available through Profile at some later time.
|
||||||
|
Profile() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type gpu struct {
|
type gpu struct {
|
||||||
cache *textureCache
|
cache *resourceCache
|
||||||
|
|
||||||
profile string
|
profile string
|
||||||
timers *timers
|
timers *timers
|
||||||
@@ -70,6 +73,7 @@ type renderer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type drawOps struct {
|
type drawOps struct {
|
||||||
|
profile bool
|
||||||
reader ops.Reader
|
reader ops.Reader
|
||||||
states []f32.Affine2D
|
states []f32.Affine2D
|
||||||
transStack []f32.Affine2D
|
transStack []f32.Affine2D
|
||||||
@@ -118,17 +122,17 @@ type drawState struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type pathOp struct {
|
type pathOp struct {
|
||||||
off image.Point
|
off f32.Point
|
||||||
// rect tracks whether the clip stack can be represented by a
|
// rect tracks whether the clip stack can be represented by a
|
||||||
// pixel-aligned rectangle.
|
// pixel-aligned rectangle.
|
||||||
rect bool
|
rect bool
|
||||||
// clip is the union of all
|
// clip is the union of all
|
||||||
// later clip rectangles.
|
// later clip rectangles.
|
||||||
clip image.Rectangle
|
clip image.Rectangle
|
||||||
bounds image.Rectangle
|
bounds f32.Rectangle
|
||||||
// intersect is the intersection of bounds and all
|
// intersect is the intersection of bounds and all
|
||||||
// previous clip bounds.
|
// previous clip bounds.
|
||||||
intersect image.Rectangle
|
intersect f32.Rectangle
|
||||||
pathKey opKey
|
pathKey opKey
|
||||||
path bool
|
path bool
|
||||||
pathVerts []byte
|
pathVerts []byte
|
||||||
@@ -190,7 +194,7 @@ const (
|
|||||||
// imageOpData is the shadow of paint.ImageOp.
|
// imageOpData is the shadow of paint.ImageOp.
|
||||||
type imageOpData struct {
|
type imageOpData struct {
|
||||||
src *image.RGBA
|
src *image.RGBA
|
||||||
handle any
|
handle interface{}
|
||||||
filter byte
|
filter byte
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +205,7 @@ type linearGradientOpData struct {
|
|||||||
color2 color.NRGBA
|
color2 color.NRGBA
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeImageOp(data []byte, refs []any) imageOpData {
|
func decodeImageOp(data []byte, refs []interface{}) imageOpData {
|
||||||
handle := refs[1]
|
handle := refs[1]
|
||||||
if handle == nil {
|
if handle == nil {
|
||||||
return imageOpData{}
|
return imageOpData{}
|
||||||
@@ -262,7 +266,7 @@ type texture struct {
|
|||||||
type blitter struct {
|
type blitter struct {
|
||||||
ctx driver.Device
|
ctx driver.Device
|
||||||
viewport image.Point
|
viewport image.Point
|
||||||
pipelines [2][3]*pipeline
|
pipelines [3]*pipeline
|
||||||
colUniforms *blitColUniforms
|
colUniforms *blitColUniforms
|
||||||
texUniforms *blitTexUniforms
|
texUniforms *blitTexUniforms
|
||||||
linearGradientUniforms *blitLinearGradientUniforms
|
linearGradientUniforms *blitLinearGradientUniforms
|
||||||
@@ -344,17 +348,18 @@ 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 feats.Has(driver.FeatureFloatRenderTargets) && feats.Has(driver.FeatureSRGB):
|
case !forceCompute && feats.Has(driver.FeatureFloatRenderTargets) && feats.Has(driver.FeatureSRGB):
|
||||||
return newGPU(d)
|
return newGPU(d)
|
||||||
}
|
}
|
||||||
return nil, errors.New("no available GPU driver")
|
return newCompute(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newGPU(ctx driver.Device) (*gpu, error) {
|
func newGPU(ctx driver.Device) (*gpu, error) {
|
||||||
g := &gpu{
|
g := &gpu{
|
||||||
cache: newTextureCache(),
|
cache: newResourceCache(),
|
||||||
}
|
}
|
||||||
g.drawOps.pathCache = newOpCache()
|
g.drawOps.pathCache = newOpCache()
|
||||||
if err := g.init(ctx); err != nil {
|
if err := g.init(ctx); err != nil {
|
||||||
@@ -394,7 +399,7 @@ func (g *gpu) collect(viewport image.Point, frameOps *op.Ops) {
|
|||||||
g.renderer.pather.viewport = viewport
|
g.renderer.pather.viewport = viewport
|
||||||
g.drawOps.reset(viewport)
|
g.drawOps.reset(viewport)
|
||||||
g.drawOps.collect(frameOps, viewport)
|
g.drawOps.collect(frameOps, viewport)
|
||||||
if false && g.timers == nil && g.ctx.Caps().Features.Has(driver.FeatureTimers) {
|
if g.drawOps.profile && g.timers == nil && g.ctx.Caps().Features.Has(driver.FeatureTimers) {
|
||||||
g.frameStart = time.Now()
|
g.frameStart = time.Now()
|
||||||
g.timers = newTimers(g.ctx)
|
g.timers = newTimers(g.ctx)
|
||||||
g.stencilTimer = g.timers.newTimer()
|
g.stencilTimer = g.timers.newTimer()
|
||||||
@@ -420,9 +425,9 @@ func (g *gpu) frame(target RenderTarget) error {
|
|||||||
g.stencilTimer.end()
|
g.stencilTimer.end()
|
||||||
g.coverTimer.begin()
|
g.coverTimer.begin()
|
||||||
g.renderer.uploadImages(g.cache, g.drawOps.imageOps)
|
g.renderer.uploadImages(g.cache, g.drawOps.imageOps)
|
||||||
g.renderer.prepareDrawOps(g.drawOps.imageOps)
|
g.renderer.prepareDrawOps(g.cache, g.drawOps.imageOps)
|
||||||
g.drawOps.layers = g.renderer.packLayers(g.drawOps.layers)
|
g.drawOps.layers = g.renderer.packLayers(g.drawOps.layers)
|
||||||
g.renderer.drawLayers(g.drawOps.layers, g.drawOps.imageOps)
|
g.renderer.drawLayers(g.cache, g.drawOps.layers, g.drawOps.imageOps)
|
||||||
d := driver.LoadDesc{
|
d := driver.LoadDesc{
|
||||||
ClearColor: g.drawOps.clearColor,
|
ClearColor: g.drawOps.clearColor,
|
||||||
}
|
}
|
||||||
@@ -432,14 +437,14 @@ func (g *gpu) frame(target RenderTarget) error {
|
|||||||
}
|
}
|
||||||
g.ctx.BeginRenderPass(defFBO, d)
|
g.ctx.BeginRenderPass(defFBO, d)
|
||||||
g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
|
g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
|
||||||
g.renderer.drawOps(false, image.Point{}, g.renderer.blitter.viewport, g.drawOps.imageOps)
|
g.renderer.drawOps(g.cache, false, image.Point{}, g.renderer.blitter.viewport, g.drawOps.imageOps)
|
||||||
g.coverTimer.end()
|
g.coverTimer.end()
|
||||||
g.ctx.EndRenderPass()
|
g.ctx.EndRenderPass()
|
||||||
g.cleanupTimer.begin()
|
g.cleanupTimer.begin()
|
||||||
g.cache.frame()
|
g.cache.frame()
|
||||||
g.drawOps.pathCache.frame()
|
g.drawOps.pathCache.frame()
|
||||||
g.cleanupTimer.end()
|
g.cleanupTimer.end()
|
||||||
if false && g.timers.ready() {
|
if g.drawOps.profile && g.timers.ready() {
|
||||||
st, covt, cleant := g.stencilTimer.Elapsed, g.coverTimer.Elapsed, g.cleanupTimer.Elapsed
|
st, covt, cleant := g.stencilTimer.Elapsed, g.coverTimer.Elapsed, g.cleanupTimer.Elapsed
|
||||||
ft := st + covt + cleant
|
ft := st + covt + cleant
|
||||||
q := 100 * time.Microsecond
|
q := 100 * time.Microsecond
|
||||||
@@ -455,8 +460,12 @@ func (g *gpu) Profile() string {
|
|||||||
return g.profile
|
return g.profile
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *renderer) texHandle(cache *textureCache, data imageOpData) driver.Texture {
|
func (r *renderer) texHandle(cache *resourceCache, data imageOpData) driver.Texture {
|
||||||
key := textureCacheKey{
|
type cachekey struct {
|
||||||
|
filter byte
|
||||||
|
handle any
|
||||||
|
}
|
||||||
|
key := cachekey{
|
||||||
filter: data.filter,
|
filter: data.filter,
|
||||||
handle: data.handle,
|
handle: data.handle,
|
||||||
}
|
}
|
||||||
@@ -548,7 +557,7 @@ func newBlitter(ctx driver.Device) *blitter {
|
|||||||
b.texUniforms = new(blitTexUniforms)
|
b.texUniforms = new(blitTexUniforms)
|
||||||
b.linearGradientUniforms = new(blitLinearGradientUniforms)
|
b.linearGradientUniforms = new(blitLinearGradientUniforms)
|
||||||
pipelines, err := createColorPrograms(ctx, gio.Shader_blit_vert, gio.Shader_blit_frag,
|
pipelines, err := createColorPrograms(ctx, gio.Shader_blit_vert, gio.Shader_blit_frag,
|
||||||
[3]any{b.colUniforms, b.linearGradientUniforms, b.texUniforms},
|
[3]interface{}{b.colUniforms, b.linearGradientUniforms, b.texUniforms},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -560,24 +569,12 @@ 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 {
|
||||||
for _, p := range p {
|
p.Release()
|
||||||
p.Release()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.Sources, uniforms [3]any) (pipelines [2][3]*pipeline, err error) {
|
func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.Sources, uniforms [3]interface{}) ([3]*pipeline, error) {
|
||||||
defer func() {
|
var pipelines [3]*pipeline
|
||||||
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,
|
||||||
@@ -595,76 +592,86 @@ 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])
|
||||||
fsh, err := b.NewFragmentShader(fsSrc[materialTexture])
|
if err != nil {
|
||||||
if err != nil {
|
return pipelines, err
|
||||||
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()
|
||||||
var vertBuffer *uniformBuffer
|
pipe, err := b.NewPipeline(driver.PipelineDesc{
|
||||||
fsh, err := b.NewFragmentShader(fsSrc[materialColor])
|
VertexShader: vsh,
|
||||||
if err != nil {
|
FragmentShader: fsh,
|
||||||
return pipelines, err
|
BlendDesc: blend,
|
||||||
}
|
VertexLayout: layout,
|
||||||
defer fsh.Release()
|
PixelFormat: driver.TextureFormatOutput,
|
||||||
pipe, err := b.NewPipeline(driver.PipelineDesc{
|
Topology: driver.TopologyTriangleStrip,
|
||||||
VertexShader: vsh,
|
})
|
||||||
FragmentShader: fsh,
|
if err != nil {
|
||||||
BlendDesc: blend,
|
return pipelines, err
|
||||||
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
|
||||||
var vertBuffer *uniformBuffer
|
if u := uniforms[materialTexture]; u != nil {
|
||||||
fsh, err := b.NewFragmentShader(fsSrc[materialLinearGradient])
|
vertBuffer = newUniformBuffer(b, u)
|
||||||
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
|
||||||
}
|
}
|
||||||
@@ -823,7 +830,7 @@ func (r *renderer) packLayers(layers []opacityLayer) []opacityLayer {
|
|||||||
layers[l.parent].clip = b.Union(l.clip)
|
layers[l.parent].clip = b.Union(l.clip)
|
||||||
}
|
}
|
||||||
if l.clip.Empty() {
|
if l.clip.Empty() {
|
||||||
layers = slices.Delete(layers, i, i+1)
|
layers = append(layers[:i], layers[i+1:]...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Pack layers.
|
// Pack layers.
|
||||||
@@ -846,7 +853,7 @@ func (r *renderer) packLayers(layers []opacityLayer) []opacityLayer {
|
|||||||
return layers
|
return layers
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *renderer) drawLayers(layers []opacityLayer, ops []imageOp) {
|
func (r *renderer) drawLayers(cache *resourceCache, layers []opacityLayer, ops []imageOp) {
|
||||||
if len(r.layers.sizes) == 0 {
|
if len(r.layers.sizes) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -867,12 +874,12 @@ 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.Dx(), v.Dy())
|
r.ctx.Viewport(v.Min.X, v.Min.Y, v.Max.X, v.Max.Y)
|
||||||
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(cache, true, l.clip.Min.Mul(-1), l.clip.Size(), ops[l.opStart:l.opEnd])
|
||||||
sr := f32.FRect(v)
|
sr := f32.FRect(v)
|
||||||
uvScale, uvOffset := texSpaceTransform(sr, f.size)
|
uvScale, uvOffset := texSpaceTransform(sr, f.size)
|
||||||
uvTrans := f32.AffineId().Scale(f32.Point{}, uvScale).Offset(uvOffset)
|
uvTrans := f32.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset)
|
||||||
// Replace layer ops with one textured op.
|
// Replace layer ops with one textured op.
|
||||||
ops[l.opStart] = imageOp{
|
ops[l.opStart] = imageOp{
|
||||||
clip: l.clip,
|
clip: l.clip,
|
||||||
@@ -892,6 +899,7 @@ func (r *renderer) drawLayers(layers []opacityLayer, ops []imageOp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *drawOps) reset(viewport image.Point) {
|
func (d *drawOps) reset(viewport image.Point) {
|
||||||
|
d.profile = false
|
||||||
d.viewport = viewport
|
d.viewport = viewport
|
||||||
d.imageOps = d.imageOps[:0]
|
d.imageOps = d.imageOps[:0]
|
||||||
d.pathOps = d.pathOps[:0]
|
d.pathOps = d.pathOps[:0]
|
||||||
@@ -902,14 +910,16 @@ func (d *drawOps) reset(viewport image.Point) {
|
|||||||
d.opacityStack = d.opacityStack[:0]
|
d.opacityStack = d.opacityStack[:0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *drawOps) collect(root *op.Ops, viewportSize image.Point) {
|
func (d *drawOps) collect(root *op.Ops, viewport image.Point) {
|
||||||
viewport := image.Rectangle{Max: viewportSize}
|
viewf := f32.Rectangle{
|
||||||
|
Max: f32.Point{X: float32(viewport.X), Y: float32(viewport.Y)},
|
||||||
|
}
|
||||||
var ops *ops.Ops
|
var ops *ops.Ops
|
||||||
if root != nil {
|
if root != nil {
|
||||||
ops = &root.Internal
|
ops = &root.Internal
|
||||||
}
|
}
|
||||||
d.reader.Reset(ops)
|
d.reader.Reset(ops)
|
||||||
d.collectOps(&d.reader, viewport)
|
d.collectOps(&d.reader, viewf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *drawOps) buildPaths(ctx driver.Device) {
|
func (d *drawOps) buildPaths(ctx driver.Device) {
|
||||||
@@ -930,7 +940,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 image.Rectangle, off image.Point) {
|
func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey opKey, bounds f32.Rectangle, off f32.Point, push bool) {
|
||||||
npath := d.newPathOp()
|
npath := d.newPathOp()
|
||||||
*npath = pathOp{
|
*npath = pathOp{
|
||||||
parent: state.cpath,
|
parent: state.cpath,
|
||||||
@@ -955,9 +965,7 @@ func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey opKey, bounds
|
|||||||
|
|
||||||
func (d *drawOps) save(id int, state f32.Affine2D) {
|
func (d *drawOps) save(id int, state f32.Affine2D) {
|
||||||
if extra := id - len(d.states) + 1; extra > 0 {
|
if extra := id - len(d.states) + 1; extra > 0 {
|
||||||
for range extra {
|
d.states = append(d.states, make([]f32.Affine2D, extra)...)
|
||||||
d.states = append(d.states, f32.AffineId())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
d.states[id] = state
|
d.states[id] = state
|
||||||
}
|
}
|
||||||
@@ -971,14 +979,13 @@ func (k opKey) SetTransform(t f32.Affine2D) opKey {
|
|||||||
return k
|
return k
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *drawOps) collectOps(r *ops.Reader, viewport image.Rectangle) {
|
func (d *drawOps) collectOps(r *ops.Reader, viewport f32.Rectangle) {
|
||||||
var quads quadsOp
|
var (
|
||||||
state := drawState{
|
quads quadsOp
|
||||||
t: f32.AffineId(),
|
state drawState
|
||||||
}
|
)
|
||||||
reset := func() {
|
reset := func() {
|
||||||
state = drawState{
|
state = drawState{
|
||||||
t: f32.AffineId(),
|
|
||||||
color: color.NRGBA{A: 0xff},
|
color: color.NRGBA{A: 0xff},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -986,6 +993,8 @@ func (d *drawOps) collectOps(r *ops.Reader, viewport image.Rectangle) {
|
|||||||
loop:
|
loop:
|
||||||
for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
|
for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
|
||||||
switch ops.OpType(encOp.Data[0]) {
|
switch ops.OpType(encOp.Data[0]) {
|
||||||
|
case ops.TypeProfile:
|
||||||
|
d.profile = true
|
||||||
case ops.TypeTransform:
|
case ops.TypeTransform:
|
||||||
dop, push := ops.DecodeTransform(encOp.Data)
|
dop, push := ops.DecodeTransform(encOp.Data)
|
||||||
if push {
|
if push {
|
||||||
@@ -1033,8 +1042,8 @@ loop:
|
|||||||
var op ops.ClipOp
|
var op ops.ClipOp
|
||||||
op.Decode(encOp.Data)
|
op.Decode(encOp.Data)
|
||||||
quads.key.outline = op.Outline
|
quads.key.outline = op.Outline
|
||||||
bounds := op.Bounds
|
bounds := f32.FRect(op.Bounds)
|
||||||
trans, off := transformOffset(state.t)
|
trans, off := state.t.Split()
|
||||||
if len(quads.aux) > 0 {
|
if len(quads.aux) > 0 {
|
||||||
// There is a clipping path, build the gpu data and update the
|
// There is a clipping path, build the gpu data and update the
|
||||||
// cache key such that it will be equal only if the transform is the
|
// cache key such that it will be equal only if the transform is the
|
||||||
@@ -1045,11 +1054,11 @@ loop:
|
|||||||
// Why is this not used for the offset shapes?
|
// Why is this not used for the offset shapes?
|
||||||
bounds = v.bounds
|
bounds = v.bounds
|
||||||
} else {
|
} else {
|
||||||
newPathData, newBounds := d.buildVerts(
|
var pathData []byte
|
||||||
|
pathData, bounds = d.buildVerts(
|
||||||
quads.aux, trans, quads.key.outline, quads.key.strokeWidth,
|
quads.aux, trans, quads.key.outline, quads.key.strokeWidth,
|
||||||
)
|
)
|
||||||
quads.aux = newPathData
|
quads.aux = pathData
|
||||||
bounds = newBounds.Round()
|
|
||||||
// add it to the cache, without GPU data, so the transform can be
|
// add it to the cache, without GPU data, so the transform can be
|
||||||
// reused.
|
// reused.
|
||||||
d.pathCache.put(quads.key, opCacheValue{bounds: bounds})
|
d.pathCache.put(quads.key, opCacheValue{bounds: bounds})
|
||||||
@@ -1057,9 +1066,8 @@ loop:
|
|||||||
} else {
|
} else {
|
||||||
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}
|
||||||
quads.key = quads.key.SetTransform(trans)
|
|
||||||
}
|
}
|
||||||
d.addClipPath(&state, quads.aux, quads.key, bounds, off)
|
d.addClipPath(&state, quads.aux, quads.key, bounds, off, true)
|
||||||
quads = quadsOp{}
|
quads = quadsOp{}
|
||||||
case ops.TypePopClip:
|
case ops.TypePopClip:
|
||||||
state.cpath = state.cpath.parent
|
state.cpath = state.cpath.parent
|
||||||
@@ -1081,21 +1089,21 @@ loop:
|
|||||||
// Transform (if needed) the painting rectangle and if so generate a clip path,
|
// Transform (if needed) the painting rectangle and if so generate a clip path,
|
||||||
// for those cases also compute a partialTrans that maps texture coordinates between
|
// for those cases also compute a partialTrans that maps texture coordinates between
|
||||||
// the new bounding rectangle and the transformed original paint rectangle.
|
// the new bounding rectangle and the transformed original paint rectangle.
|
||||||
t, off := transformOffset(state.t)
|
t, off := state.t.Split()
|
||||||
// Fill the clip area, unless the material is a (bounded) image.
|
// Fill the clip area, unless the material is a (bounded) image.
|
||||||
// TODO: Find a tighter bound.
|
// TODO: Find a tighter bound.
|
||||||
inf := int(1e6)
|
inf := float32(1e6)
|
||||||
dst := image.Rect(-inf, -inf, inf, inf)
|
dst := f32.Rect(-inf, -inf, inf, inf)
|
||||||
if state.matType == materialTexture {
|
if state.matType == materialTexture {
|
||||||
sz := state.image.src.Rect.Size()
|
sz := state.image.src.Rect.Size()
|
||||||
dst = image.Rectangle{Max: sz}
|
dst = f32.Rectangle{Max: layout.FPt(sz)}
|
||||||
}
|
}
|
||||||
clipData, bnd, partialTrans := d.boundsForTransformedRect(dst, t)
|
clipData, bnd, partialTrans := d.boundsForTransformedRect(dst, t)
|
||||||
bounds := viewport.Intersect(bnd.Add(off))
|
cl := viewport.Intersect(bnd.Add(off))
|
||||||
if state.cpath != nil {
|
if state.cpath != nil {
|
||||||
bounds = state.cpath.intersect.Intersect(bounds)
|
cl = state.cpath.intersect.Intersect(cl)
|
||||||
}
|
}
|
||||||
if bounds.Empty() {
|
if cl.Empty() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1103,10 +1111,11 @@ loop:
|
|||||||
// The paint operation is sheared or rotated, add a clip path representing
|
// The paint operation is sheared or rotated, add a clip path representing
|
||||||
// this transformed rectangle.
|
// this transformed rectangle.
|
||||||
k := opKey{Key: encOp.Key}
|
k := opKey{Key: encOp.Key}
|
||||||
k = k.SetTransform(t)
|
k.SetTransform(t) // TODO: This call has no effect.
|
||||||
d.addClipPath(&state, clipData, k, bnd, off)
|
d.addClipPath(&state, clipData, k, bnd, off, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bounds := cl.Round()
|
||||||
mat := state.materialFor(bnd, off, partialTrans, bounds)
|
mat := state.materialFor(bnd, off, partialTrans, bounds)
|
||||||
|
|
||||||
rect := state.cpath == nil || state.cpath.rect
|
rect := state.cpath == nil || state.cpath.rect
|
||||||
@@ -1160,10 +1169,9 @@ func expandPathOp(p *pathOp, clip image.Rectangle) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *drawState) materialFor(rect image.Rectangle, off image.Point, partTrans f32.Affine2D, clip image.Rectangle) material {
|
func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32.Affine2D, clip image.Rectangle) material {
|
||||||
m := material{
|
m := material{
|
||||||
opacity: 1.,
|
opacity: 1.,
|
||||||
uvTrans: f32.AffineId(),
|
|
||||||
}
|
}
|
||||||
switch d.matType {
|
switch d.matType {
|
||||||
case materialColor:
|
case materialColor:
|
||||||
@@ -1180,7 +1188,7 @@ func (d *drawState) materialFor(rect image.Rectangle, off image.Point, partTrans
|
|||||||
m.uvTrans = partTrans.Mul(gradientSpaceTransform(clip, off, d.stop1, d.stop2))
|
m.uvTrans = partTrans.Mul(gradientSpaceTransform(clip, off, d.stop1, d.stop2))
|
||||||
case materialTexture:
|
case materialTexture:
|
||||||
m.material = materialTexture
|
m.material = materialTexture
|
||||||
dr := rect.Add(off)
|
dr := rect.Add(off).Round()
|
||||||
sz := d.image.src.Bounds().Size()
|
sz := d.image.src.Bounds().Size()
|
||||||
sr := f32.Rectangle{
|
sr := f32.Rectangle{
|
||||||
Max: f32.Point{
|
Max: f32.Point{
|
||||||
@@ -1197,13 +1205,13 @@ func (d *drawState) materialFor(rect image.Rectangle, off image.Point, partTrans
|
|||||||
sr.Min.Y += float32(clip.Min.Y-dr.Min.Y) * sdy / dy
|
sr.Min.Y += float32(clip.Min.Y-dr.Min.Y) * sdy / dy
|
||||||
sr.Max.Y -= float32(dr.Max.Y-clip.Max.Y) * sdy / dy
|
sr.Max.Y -= float32(dr.Max.Y-clip.Max.Y) * sdy / dy
|
||||||
uvScale, uvOffset := texSpaceTransform(sr, sz)
|
uvScale, uvOffset := texSpaceTransform(sr, sz)
|
||||||
m.uvTrans = partTrans.Mul(f32.AffineId().Scale(f32.Point{}, uvScale).Offset(uvOffset))
|
m.uvTrans = partTrans.Mul(f32.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset))
|
||||||
m.data = d.image
|
m.data = d.image
|
||||||
}
|
}
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *renderer) uploadImages(cache *textureCache, ops []imageOp) {
|
func (r *renderer) uploadImages(cache *resourceCache, ops []imageOp) {
|
||||||
for i := range ops {
|
for i := range ops {
|
||||||
img := &ops[i]
|
img := &ops[i]
|
||||||
m := img.material
|
m := img.material
|
||||||
@@ -1213,7 +1221,7 @@ func (r *renderer) uploadImages(cache *textureCache, ops []imageOp) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *renderer) prepareDrawOps(ops []imageOp) {
|
func (r *renderer) prepareDrawOps(cache *resourceCache, ops []imageOp) {
|
||||||
for _, img := range ops {
|
for _, img := range ops {
|
||||||
m := img.material
|
m := img.material
|
||||||
switch m.material {
|
switch m.material {
|
||||||
@@ -1234,7 +1242,7 @@ func (r *renderer) prepareDrawOps(ops []imageOp) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *renderer) drawOps(isFBO bool, opOff, viewport image.Point, ops []imageOp) {
|
func (r *renderer) drawOps(cache *resourceCache, isFBO bool, opOff image.Point, 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]
|
||||||
@@ -1248,13 +1256,9 @@ func (r *renderer) drawOps(isFBO bool, opOff, viewport image.Point, ops []imageO
|
|||||||
|
|
||||||
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[fboIdx][m.material]
|
p := r.blitter.pipelines[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)
|
||||||
@@ -1273,7 +1277,7 @@ func (r *renderer) drawOps(isFBO bool, opOff, viewport image.Point, ops []imageO
|
|||||||
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[fboIdx][m.material]
|
p := r.pather.coverer.pipelines[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)
|
||||||
@@ -1281,11 +1285,7 @@ func (r *renderer) drawOps(isFBO bool, opOff, viewport image.Point, ops []imageO
|
|||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
fboIdx := 0
|
p := b.pipelines[mat]
|
||||||
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 {
|
||||||
@@ -1318,7 +1318,7 @@ func (b *blitter) blit(mat materialType, fbo bool, col f32color.RGBA, col1, col2
|
|||||||
|
|
||||||
// newUniformBuffer creates a new GPU uniform buffer backed by the
|
// newUniformBuffer creates a new GPU uniform buffer backed by the
|
||||||
// structure uniformBlock points to.
|
// structure uniformBlock points to.
|
||||||
func newUniformBuffer(b driver.Device, uniformBlock any) *uniformBuffer {
|
func newUniformBuffer(b driver.Device, uniformBlock interface{}) *uniformBuffer {
|
||||||
ref := reflect.ValueOf(uniformBlock)
|
ref := reflect.ValueOf(uniformBlock)
|
||||||
// Determine the size of the uniforms structure, *uniforms.
|
// Determine the size of the uniforms structure, *uniforms.
|
||||||
size := ref.Elem().Type().Size()
|
size := ref.Elem().Type().Size()
|
||||||
@@ -1365,19 +1365,19 @@ func texSpaceTransform(r f32.Rectangle, bounds image.Point) (f32.Point, f32.Poin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// gradientSpaceTransform transforms stop1 and stop2 to [(0,0), (1,1)].
|
// gradientSpaceTransform transforms stop1 and stop2 to [(0,0), (1,1)].
|
||||||
func gradientSpaceTransform(clip image.Rectangle, off image.Point, stop1, stop2 f32.Point) f32.Affine2D {
|
func gradientSpaceTransform(clip image.Rectangle, off f32.Point, stop1, stop2 f32.Point) f32.Affine2D {
|
||||||
d := stop2.Sub(stop1)
|
d := stop2.Sub(stop1)
|
||||||
l := float32(math.Sqrt(float64(d.X*d.X + d.Y*d.Y)))
|
l := float32(math.Sqrt(float64(d.X*d.X + d.Y*d.Y)))
|
||||||
a := float32(math.Atan2(float64(-d.Y), float64(d.X)))
|
a := float32(math.Atan2(float64(-d.Y), float64(d.X)))
|
||||||
|
|
||||||
// TODO: optimize
|
// TODO: optimize
|
||||||
zp := f32.Point{}
|
zp := f32.Point{}
|
||||||
return f32.AffineId().
|
return f32.Affine2D{}.
|
||||||
Scale(zp, layout.FPt(clip.Size())). // scale to pixel space
|
Scale(zp, layout.FPt(clip.Size())). // scale to pixel space
|
||||||
Offset(zp.Sub(f32.FPt(off)).Add(layout.FPt(clip.Min))). // offset to clip space
|
Offset(zp.Sub(off).Add(layout.FPt(clip.Min))). // offset to clip space
|
||||||
Offset(zp.Sub(stop1)). // offset to first stop point
|
Offset(zp.Sub(stop1)). // offset to first stop point
|
||||||
Rotate(zp, a). // rotate to align gradient
|
Rotate(zp, a). // rotate to align gradient
|
||||||
Scale(zp, f32.Pt(1/l, 1/l)) // scale gradient to right size
|
Scale(zp, f32.Pt(1/l, 1/l)) // scale gradient to right size
|
||||||
}
|
}
|
||||||
|
|
||||||
// clipSpaceTransform returns the scale and offset that transforms the given
|
// clipSpaceTransform returns the scale and offset that transforms the given
|
||||||
@@ -1486,7 +1486,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 = binary.LittleEndian.Uint32(pathData)
|
qs.contour = bo.Uint32(pathData)
|
||||||
cmd := ops.DecodeCommand(pathData[4:])
|
cmd := ops.DecodeCommand(pathData[4:])
|
||||||
switch cmd.Op() {
|
switch cmd.Op() {
|
||||||
case scene.OpLine:
|
case scene.OpLine:
|
||||||
@@ -1521,38 +1521,37 @@ func decodeToOutlineQuads(qs *quadSplitter, tr f32.Affine2D, pathData []byte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// create GPU vertices for transformed r, find the bounds and establish texture transform.
|
// create GPU vertices for transformed r, find the bounds and establish texture transform.
|
||||||
func (d *drawOps) boundsForTransformedRect(r image.Rectangle, tr f32.Affine2D) (aux []byte, bnd image.Rectangle, ptr f32.Affine2D) {
|
func (d *drawOps) boundsForTransformedRect(r f32.Rectangle, tr f32.Affine2D) (aux []byte, bnd f32.Rectangle, ptr f32.Affine2D) {
|
||||||
ptr = f32.AffineId()
|
if isPureOffset(tr) {
|
||||||
if tr == f32.AffineId() {
|
// fast-path to allow blitting of pure rectangles
|
||||||
// fast-path to allow blitting of pure rectangles.
|
_, _, ox, _, _, oy := tr.Elems()
|
||||||
bnd = r
|
off := f32.Pt(ox, oy)
|
||||||
|
bnd.Min = r.Min.Add(off)
|
||||||
|
bnd.Max = r.Max.Add(off)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// transform all corners, find new bounds
|
// transform all corners, find new bounds
|
||||||
corners := [4]f32.Point{
|
corners := [4]f32.Point{
|
||||||
tr.Transform(f32.FPt(r.Min)), tr.Transform(f32.Pt(float32(r.Max.X), float32(r.Min.Y))),
|
tr.Transform(r.Min), tr.Transform(f32.Pt(r.Max.X, r.Min.Y)),
|
||||||
tr.Transform(f32.FPt(r.Max)), tr.Transform(f32.Pt(float32(r.Min.X), float32(r.Max.Y))),
|
tr.Transform(r.Max), tr.Transform(f32.Pt(r.Min.X, r.Max.Y)),
|
||||||
}
|
|
||||||
fBounds := f32.Rectangle{
|
|
||||||
Min: f32.Pt(math.MaxFloat32, math.MaxFloat32),
|
|
||||||
Max: f32.Pt(-math.MaxFloat32, -math.MaxFloat32),
|
|
||||||
}
|
}
|
||||||
|
bnd.Min = f32.Pt(math.MaxFloat32, math.MaxFloat32)
|
||||||
|
bnd.Max = f32.Pt(-math.MaxFloat32, -math.MaxFloat32)
|
||||||
for _, c := range corners {
|
for _, c := range corners {
|
||||||
if c.X < fBounds.Min.X {
|
if c.X < bnd.Min.X {
|
||||||
fBounds.Min.X = c.X
|
bnd.Min.X = c.X
|
||||||
}
|
}
|
||||||
if c.Y < fBounds.Min.Y {
|
if c.Y < bnd.Min.Y {
|
||||||
fBounds.Min.Y = c.Y
|
bnd.Min.Y = c.Y
|
||||||
}
|
}
|
||||||
if c.X > fBounds.Max.X {
|
if c.X > bnd.Max.X {
|
||||||
fBounds.Max.X = c.X
|
bnd.Max.X = c.X
|
||||||
}
|
}
|
||||||
if c.Y > fBounds.Max.Y {
|
if c.Y > bnd.Max.Y {
|
||||||
fBounds.Max.Y = c.Y
|
bnd.Max.Y = c.Y
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bnd = fBounds.Round()
|
|
||||||
|
|
||||||
// build the GPU vertices
|
// build the GPU vertices
|
||||||
l := len(d.vertCache)
|
l := len(d.vertCache)
|
||||||
@@ -1566,38 +1565,19 @@ func (d *drawOps) boundsForTransformedRect(r image.Rectangle, tr f32.Affine2D) (
|
|||||||
|
|
||||||
// establish the transform mapping from bounds rectangle to transformed corners
|
// establish the transform mapping from bounds rectangle to transformed corners
|
||||||
var P1, P2, P3 f32.Point
|
var P1, P2, P3 f32.Point
|
||||||
P1.X = (corners[1].X - fBounds.Min.X) / (fBounds.Max.X - fBounds.Min.X)
|
P1.X = (corners[1].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X)
|
||||||
P1.Y = (corners[1].Y - fBounds.Min.Y) / (fBounds.Max.Y - fBounds.Min.Y)
|
P1.Y = (corners[1].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y)
|
||||||
P2.X = (corners[2].X - fBounds.Min.X) / (fBounds.Max.X - fBounds.Min.X)
|
P2.X = (corners[2].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X)
|
||||||
P2.Y = (corners[2].Y - fBounds.Min.Y) / (fBounds.Max.Y - fBounds.Min.Y)
|
P2.Y = (corners[2].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y)
|
||||||
P3.X = (corners[3].X - fBounds.Min.X) / (fBounds.Max.X - fBounds.Min.X)
|
P3.X = (corners[3].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X)
|
||||||
P3.Y = (corners[3].Y - fBounds.Min.Y) / (fBounds.Max.Y - fBounds.Min.Y)
|
P3.Y = (corners[3].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y)
|
||||||
sx, sy := P2.X-P3.X, P2.Y-P3.Y
|
sx, sy := P2.X-P3.X, P2.Y-P3.Y
|
||||||
ptr = f32.NewAffine2D(sx, P2.X-P1.X, P1.X-sx, sy, P2.Y-P1.Y, P1.Y-sy).Invert()
|
ptr = f32.NewAffine2D(sx, P2.X-P1.X, P1.X-sx, sy, P2.Y-P1.Y, P1.Y-sy).Invert()
|
||||||
|
|
||||||
return aux, bnd, ptr
|
|
||||||
}
|
|
||||||
|
|
||||||
// transformOffset a transform into two parts, one which is pure integer offset
|
|
||||||
// and the other representing the scaling, shearing and rotation and fractional
|
|
||||||
// offset.
|
|
||||||
func transformOffset(t f32.Affine2D) (f32.Affine2D, image.Point) {
|
|
||||||
sx, hx, ox, hy, sy, oy := t.Elems()
|
|
||||||
iox, fox := math.Modf(float64(ox))
|
|
||||||
ioy, foy := math.Modf(float64(oy))
|
|
||||||
ft := f32.NewAffine2D(sx, hx, float32(fox), hy, sy, float32(foy))
|
|
||||||
ip := image.Pt(int(iox), int(ioy))
|
|
||||||
return ft, ip
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isPureOffset(t f32.Affine2D) bool {
|
||||||
|
a, b, _, d, e, _ := t.Elems()
|
||||||
|
return a == 1 && b == 0 && d == 0 && e == 1
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,10 +21,8 @@ import (
|
|||||||
|
|
||||||
var dumpImages = flag.Bool("saveimages", false, "save test images")
|
var dumpImages = flag.Bool("saveimages", false, "save test images")
|
||||||
|
|
||||||
var (
|
var clearCol = color.NRGBA{A: 0xff, R: 0xde, G: 0xad, B: 0xbe}
|
||||||
clearCol = color.NRGBA{A: 0xff, R: 0xde, G: 0xad, B: 0xbe}
|
var clearColExpect = f32color.NRGBAToRGBA(clearCol)
|
||||||
clearColExpect = f32color.NRGBAToRGBA(clearCol)
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFramebufferClear(t *testing.T) {
|
func TestFramebufferClear(t *testing.T) {
|
||||||
b := newDriver(t)
|
b := newDriver(t)
|
||||||
@@ -204,5 +202,5 @@ func saveImage(file string, img image.Image) error {
|
|||||||
if err := png.Encode(&buf, img); err != nil {
|
if err := png.Encode(&buf, img); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return os.WriteFile(file, buf.Bytes(), 0o666)
|
return os.WriteFile(file, buf.Bytes(), 0666)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,7 +152,9 @@ func newDirect3D11Device(api driver.Direct3D11) (driver.Device, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport image.Point) driver.Texture {
|
func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport image.Point) driver.Texture {
|
||||||
var renderTarget *d3d11.RenderTargetView
|
var (
|
||||||
|
renderTarget *d3d11.RenderTargetView
|
||||||
|
)
|
||||||
if target != nil {
|
if target != nil {
|
||||||
switch t := target.(type) {
|
switch t := target.(type) {
|
||||||
case driver.Direct3D11RenderTarget:
|
case driver.Direct3D11RenderTarget:
|
||||||
@@ -227,7 +229,10 @@ func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, min
|
|||||||
// Flags required by ID3D11DeviceContext::GenerateMips.
|
// Flags required by ID3D11DeviceContext::GenerateMips.
|
||||||
bindFlags |= d3d11.BIND_SHADER_RESOURCE | d3d11.BIND_RENDER_TARGET
|
bindFlags |= d3d11.BIND_SHADER_RESOURCE | d3d11.BIND_RENDER_TARGET
|
||||||
miscFlags |= d3d11.RESOURCE_MISC_GENERATE_MIPS
|
miscFlags |= d3d11.RESOURCE_MISC_GENERATE_MIPS
|
||||||
dim := max(height, width)
|
dim := width
|
||||||
|
if height > dim {
|
||||||
|
dim = height
|
||||||
|
}
|
||||||
log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1
|
log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1
|
||||||
nmipmaps = log2 + 1
|
nmipmaps = log2 + 1
|
||||||
}
|
}
|
||||||
@@ -797,7 +802,7 @@ func (t *Texture) ReadPixels(src image.Rectangle, pixels []byte, stride int) err
|
|||||||
mapSize := dstPitch * h
|
mapSize := dstPitch * h
|
||||||
data := sliceOf(resMap.PData, mapSize)
|
data := sliceOf(resMap.PData, mapSize)
|
||||||
width := w * 4
|
width := w * 4
|
||||||
for r := range h {
|
for r := 0; r < h; r++ {
|
||||||
pixels := pixels[r*srcPitch:]
|
pixels := pixels[r*srcPitch:]
|
||||||
copy(pixels[:width], data[r*dstPitch:])
|
copy(pixels[:width], data[r*dstPitch:])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,10 +96,8 @@ type BlendFactor uint8
|
|||||||
|
|
||||||
type Topology uint8
|
type Topology uint8
|
||||||
|
|
||||||
type (
|
type TextureFilter uint8
|
||||||
TextureFilter uint8
|
type TextureFormat uint8
|
||||||
TextureFormat uint8
|
|
||||||
)
|
|
||||||
|
|
||||||
type BufferBinding uint8
|
type BufferBinding uint8
|
||||||
|
|
||||||
@@ -219,7 +217,7 @@ func flipImageY(stride, height int, pixels []byte) {
|
|||||||
// Flip image in y-direction. OpenGL's origin is in the lower
|
// Flip image in y-direction. OpenGL's origin is in the lower
|
||||||
// left corner.
|
// left corner.
|
||||||
row := make([]uint8, stride)
|
row := make([]uint8, stride)
|
||||||
for y := range height / 2 {
|
for y := 0; y < height/2; y++ {
|
||||||
y1 := height - y - 1
|
y1 := height - y - 1
|
||||||
dest := y1 * stride
|
dest := y1 * stride
|
||||||
src := y * stride
|
src := y * stride
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"math/bits"
|
"math/bits"
|
||||||
"runtime"
|
"runtime"
|
||||||
"slices"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
@@ -718,7 +717,10 @@ func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, min
|
|||||||
if mipmap {
|
if mipmap {
|
||||||
nmipmaps := 1
|
nmipmaps := 1
|
||||||
if mipmap {
|
if mipmap {
|
||||||
dim := max(height, width)
|
dim := width
|
||||||
|
if height > dim {
|
||||||
|
dim = height
|
||||||
|
}
|
||||||
log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1
|
log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1
|
||||||
nmipmaps = log2 + 1
|
nmipmaps = log2 + 1
|
||||||
}
|
}
|
||||||
@@ -1130,7 +1132,7 @@ func (b *Backend) setupVertexArrays() {
|
|||||||
enabled[inp.Location] = true
|
enabled[inp.Location] = true
|
||||||
b.glstate.vertexAttribPointer(b.funcs, buf.obj, inp.Location, l.Size, gltyp, false, p.layout.Stride, buf.offset+l.Offset)
|
b.glstate.vertexAttribPointer(b.funcs, buf.obj, inp.Location, l.Size, gltyp, false, p.layout.Stride, buf.offset+l.Offset)
|
||||||
}
|
}
|
||||||
for i := range max {
|
for i := 0; i < max; i++ {
|
||||||
b.glstate.setVertexAttribArray(b.funcs, i, enabled[i])
|
b.glstate.setVertexAttribArray(b.funcs, i, enabled[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1173,7 +1175,7 @@ func (t *texture) ReadPixels(src image.Rectangle, pixels []byte, stride int) err
|
|||||||
} else {
|
} else {
|
||||||
tmp := make([]byte, w*h*4)
|
tmp := make([]byte, w*h*4)
|
||||||
t.backend.funcs.ReadPixels(src.Min.X, src.Min.Y, w, h, gl.RGBA, gl.UNSIGNED_BYTE, tmp)
|
t.backend.funcs.ReadPixels(src.Min.X, src.Min.Y, w, h, gl.RGBA, gl.UNSIGNED_BYTE, tmp)
|
||||||
for y := range h {
|
for y := 0; y < h; y++ {
|
||||||
copy(pixels[y*stride:], tmp[y*w*4:])
|
copy(pixels[y*stride:], tmp[y*w*4:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1355,7 +1357,12 @@ func alphaTripleFor(ver [2]int) textureTriple {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func hasExtension(exts []string, ext string) bool {
|
func hasExtension(exts []string, ext string) bool {
|
||||||
return slices.Contains(exts, ext)
|
for _, e := range exts {
|
||||||
|
if ext == e {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func firstBufferType(typ driver.BufferBinding) gl.Enum {
|
func firstBufferType(typ driver.BufferBinding) gl.Enum {
|
||||||
|
|||||||
@@ -66,8 +66,8 @@ func BenchmarkDrawUICached(b *testing.B) {
|
|||||||
defer w.Release()
|
defer w.Release()
|
||||||
drawCore(gtx, th)
|
drawCore(gtx, th)
|
||||||
w.Frame(gtx.Ops)
|
w.Frame(gtx.Ops)
|
||||||
|
b.ResetTimer()
|
||||||
for b.Loop() {
|
for i := 0; i < b.N; i++ {
|
||||||
w.Frame(gtx.Ops)
|
w.Frame(gtx.Ops)
|
||||||
}
|
}
|
||||||
finishBenchmark(b, w)
|
finishBenchmark(b, w)
|
||||||
@@ -83,12 +83,12 @@ func BenchmarkDrawUI(b *testing.B) {
|
|||||||
drawCore(gtx, th)
|
drawCore(gtx, th)
|
||||||
w.Frame(gtx.Ops)
|
w.Frame(gtx.Ops)
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
for i := 0; b.Loop(); i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
resetOps(gtx)
|
resetOps(gtx)
|
||||||
|
|
||||||
off := float32(math.Mod(float64(i)/10, 10))
|
off := float32(math.Mod(float64(i)/10, 10))
|
||||||
t := op.Affine(f32.AffineId().Offset(f32.Pt(off, off))).Push(gtx.Ops)
|
t := op.Affine(f32.Affine2D{}.Offset(f32.Pt(off, off))).Push(gtx.Ops)
|
||||||
|
|
||||||
drawCore(gtx, th)
|
drawCore(gtx, th)
|
||||||
|
|
||||||
@@ -105,12 +105,12 @@ func BenchmarkDrawUITransformed(b *testing.B) {
|
|||||||
drawCore(gtx, th)
|
drawCore(gtx, th)
|
||||||
w.Frame(gtx.Ops)
|
w.Frame(gtx.Ops)
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
for i := 0; b.Loop(); i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
resetOps(gtx)
|
resetOps(gtx)
|
||||||
|
|
||||||
angle := float32(math.Mod(float64(i)/1000, 0.05))
|
angle := float32(math.Mod(float64(i)/1000, 0.05))
|
||||||
a := f32.AffineId().Shear(f32.Point{}, angle, angle).Rotate(f32.Point{}, angle)
|
a := f32.Affine2D{}.Shear(f32.Point{}, angle, angle).Rotate(f32.Point{}, angle)
|
||||||
t := op.Affine(a).Push(gtx.Ops)
|
t := op.Affine(a).Push(gtx.Ops)
|
||||||
|
|
||||||
drawCore(gtx, th)
|
drawCore(gtx, th)
|
||||||
@@ -130,8 +130,8 @@ func Benchmark1000Circles(b *testing.B) {
|
|||||||
draw1000Circles(gtx)
|
draw1000Circles(gtx)
|
||||||
w.Frame(gtx.Ops)
|
w.Frame(gtx.Ops)
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
for b.Loop() {
|
for i := 0; i < b.N; i++ {
|
||||||
resetOps(gtx)
|
resetOps(gtx)
|
||||||
draw1000Circles(gtx)
|
draw1000Circles(gtx)
|
||||||
w.Frame(gtx.Ops)
|
w.Frame(gtx.Ops)
|
||||||
@@ -147,8 +147,8 @@ func Benchmark1000CirclesInstanced(b *testing.B) {
|
|||||||
draw1000CirclesInstanced(gtx)
|
draw1000CirclesInstanced(gtx)
|
||||||
w.Frame(gtx.Ops)
|
w.Frame(gtx.Ops)
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
for b.Loop() {
|
for i := 0; i < b.N; i++ {
|
||||||
resetOps(gtx)
|
resetOps(gtx)
|
||||||
draw1000CirclesInstanced(gtx)
|
draw1000CirclesInstanced(gtx)
|
||||||
w.Frame(gtx.Ops)
|
w.Frame(gtx.Ops)
|
||||||
@@ -158,9 +158,9 @@ func Benchmark1000CirclesInstanced(b *testing.B) {
|
|||||||
|
|
||||||
func draw1000Circles(gtx layout.Context) {
|
func draw1000Circles(gtx layout.Context) {
|
||||||
ops := gtx.Ops
|
ops := gtx.Ops
|
||||||
for x := range 100 {
|
for x := 0; x < 100; x++ {
|
||||||
op.Offset(image.Pt(x*10, 0)).Add(ops)
|
op.Offset(image.Pt(x*10, 0)).Add(ops)
|
||||||
for y := range 10 {
|
for y := 0; y < 10; y++ {
|
||||||
paint.FillShape(ops,
|
paint.FillShape(ops,
|
||||||
color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120},
|
color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120},
|
||||||
clip.RRect{Rect: image.Rect(0, 0, 10, 10), NE: 5, SE: 5, SW: 5, NW: 5}.Op(ops),
|
clip.RRect{Rect: image.Rect(0, 0, 10, 10), NE: 5, SE: 5, SW: 5, NW: 5}.Op(ops),
|
||||||
@@ -179,9 +179,9 @@ func draw1000CirclesInstanced(gtx layout.Context) {
|
|||||||
cl.Pop()
|
cl.Pop()
|
||||||
c := r.Stop()
|
c := r.Stop()
|
||||||
|
|
||||||
for x := range 100 {
|
for x := 0; x < 100; x++ {
|
||||||
op.Offset(image.Pt(x*10, 0)).Add(ops)
|
op.Offset(image.Pt(x*10, 0)).Add(ops)
|
||||||
for y := range 10 {
|
for y := 0; y < 10; y++ {
|
||||||
paint.ColorOp{Color: color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120}}.Add(ops)
|
paint.ColorOp{Color: color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120}}.Add(ops)
|
||||||
c.Add(ops)
|
c.Add(ops)
|
||||||
op.Offset(image.Pt(0, 100)).Add(ops)
|
op.Offset(image.Pt(0, 100)).Add(ops)
|
||||||
@@ -204,9 +204,9 @@ func drawIndividualShapes(gtx layout.Context, th *material.Theme) chan op.CallOp
|
|||||||
go func() {
|
go func() {
|
||||||
ops := &op1
|
ops := &op1
|
||||||
c := op.Record(ops)
|
c := op.Record(ops)
|
||||||
for x := range 9 {
|
for x := 0; x < 9; x++ {
|
||||||
op.Offset(image.Pt(x*50, 0)).Add(ops)
|
op.Offset(image.Pt(x*50, 0)).Add(ops)
|
||||||
for y := range 9 {
|
for y := 0; y < 9; y++ {
|
||||||
paint.FillShape(ops,
|
paint.FillShape(ops,
|
||||||
color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120},
|
color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120},
|
||||||
clip.RRect{Rect: image.Rect(0, 0, 25, 25), NE: 10, SE: 10, SW: 10, NW: 10}.Op(ops),
|
clip.RRect{Rect: image.Rect(0, 0, 25, 25), NE: 10, SE: 10, SW: 10, NW: 10}.Op(ops),
|
||||||
@@ -233,8 +233,8 @@ func drawShapeInstances(gtx layout.Context, th *material.Theme) chan op.CallOp {
|
|||||||
|
|
||||||
squares.Add(ops)
|
squares.Add(ops)
|
||||||
rad := float32(0)
|
rad := float32(0)
|
||||||
for x := range 20 {
|
for x := 0; x < 20; x++ {
|
||||||
for y := range 20 {
|
for y := 0; y < 20; y++ {
|
||||||
t := op.Offset(image.Pt(x*50+25, y*50+25)).Push(ops)
|
t := op.Offset(image.Pt(x*50+25, y*50+25)).Push(ops)
|
||||||
c.Add(ops)
|
c.Add(ops)
|
||||||
t.Pop()
|
t.Pop()
|
||||||
@@ -253,7 +253,7 @@ func drawText(gtx layout.Context, th *material.Theme) chan op.CallOp {
|
|||||||
c := op.Record(ops)
|
c := op.Record(ops)
|
||||||
|
|
||||||
txt := material.H6(th, "")
|
txt := material.H6(th, "")
|
||||||
for x := range 40 {
|
for x := 0; x < 40; x++ {
|
||||||
txt.Text = textRows[x]
|
txt.Text = textRows[x]
|
||||||
t := op.Offset(image.Pt(0, 24*x)).Push(ops)
|
t := op.Offset(image.Pt(0, 24*x)).Push(ops)
|
||||||
gtx.Ops = ops
|
gtx.Ops = ops
|
||||||
|
|||||||
@@ -40,25 +40,6 @@ func TestPaintClippedRect(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPaintClippedRectOffset(t *testing.T) {
|
|
||||||
run(t, func(o *op.Ops) {
|
|
||||||
defer op.Affine(f32.AffineId().Offset(f32.Pt(0.5, 0.5))).Push(o).Pop()
|
|
||||||
defer clip.RRect{Rect: image.Rect(25, 25, 60, 60)}.Push(o).Pop()
|
|
||||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 50, 50)).Op())
|
|
||||||
}, func(r result) {
|
|
||||||
r.expect(0, 0, transparent)
|
|
||||||
r.expect(24, 35, transparent)
|
|
||||||
r.expect(24, 24, transparent)
|
|
||||||
r.expect(25, 25, color.RGBA{R: 137, A: 64})
|
|
||||||
r.expect(25, 35, color.RGBA{R: 187, A: 128})
|
|
||||||
r.expect(35, 25, color.RGBA{R: 187, A: 128})
|
|
||||||
r.expect(50, 50, color.RGBA{R: 137, A: 64})
|
|
||||||
r.expect(51, 51, transparent)
|
|
||||||
r.expect(50, 0, transparent)
|
|
||||||
r.expect(10, 50, transparent)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPaintClippedCircle(t *testing.T) {
|
func TestPaintClippedCircle(t *testing.T) {
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
const r = 10
|
const r = 10
|
||||||
@@ -151,7 +132,8 @@ func TestTexturedStrokeClipped(t *testing.T) {
|
|||||||
defer clip.RRect{Rect: image.Rect(-30, -30, 60, 60)}.Push(o).Pop()
|
defer clip.RRect{Rect: image.Rect(-30, -30, 60, 60)}.Push(o).Pop()
|
||||||
defer op.Offset(image.Pt(-10, -10)).Push(o).Pop()
|
defer op.Offset(image.Pt(-10, -10)).Push(o).Pop()
|
||||||
paint.PaintOp{}.Add(o)
|
paint.PaintOp{}.Add(o)
|
||||||
}, nil)
|
}, func(r result) {
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTexturedStroke(t *testing.T) {
|
func TestTexturedStroke(t *testing.T) {
|
||||||
@@ -164,7 +146,8 @@ func TestTexturedStroke(t *testing.T) {
|
|||||||
}.Op().Push(o).Pop()
|
}.Op().Push(o).Pop()
|
||||||
defer op.Offset(image.Pt(-10, -10)).Push(o).Pop()
|
defer op.Offset(image.Pt(-10, -10)).Push(o).Pop()
|
||||||
paint.PaintOp{}.Add(o)
|
paint.PaintOp{}.Add(o)
|
||||||
}, nil)
|
}, func(r result) {
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPaintClippedTexture(t *testing.T) {
|
func TestPaintClippedTexture(t *testing.T) {
|
||||||
@@ -195,6 +178,7 @@ func TestStrokedPathZeroWidth(t *testing.T) {
|
|||||||
paint.Fill(o, red)
|
paint.Fill(o, red)
|
||||||
cl.Pop()
|
cl.Pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
}, func(r result) {
|
}, func(r result) {
|
||||||
r.expect(0, 0, transparent)
|
r.expect(0, 0, transparent)
|
||||||
r.expect(10, 50, colornames.Black)
|
r.expect(10, 50, colornames.Black)
|
||||||
@@ -268,7 +252,8 @@ func TestPathReuse(t *testing.T) {
|
|||||||
stroke := clip.Stroke{Path: spec, Width: 3}.Op().Push(o)
|
stroke := clip.Stroke{Path: spec, Width: 3}.Op().Push(o)
|
||||||
paint.Fill(o, color.NRGBA{B: 0xFF, A: 0xFF})
|
paint.Fill(o, color.NRGBA{B: 0xFF, A: 0xFF})
|
||||||
stroke.Pop()
|
stroke.Pop()
|
||||||
}, nil)
|
}, func(r result) {
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathInterleave(t *testing.T) {
|
func TestPathInterleave(t *testing.T) {
|
||||||
@@ -305,22 +290,6 @@ func TestStrokedRect(t *testing.T) {
|
|||||||
Width: 5,
|
Width: 5,
|
||||||
}.Op(),
|
}.Op(),
|
||||||
)
|
)
|
||||||
}, nil)
|
}, func(r result) {
|
||||||
}
|
})
|
||||||
|
|
||||||
func TestInstancedRects(t *testing.T) {
|
|
||||||
run(t, func(o *op.Ops) {
|
|
||||||
macro := op.Record(o)
|
|
||||||
clip := clip.Rect{Max: image.Pt(20, 20)}.Push(o)
|
|
||||||
paint.ColorOp{Color: color.NRGBA{R: 0x80, A: 0xFF}}.Add(o)
|
|
||||||
paint.PaintOp{}.Add(o)
|
|
||||||
clip.Pop()
|
|
||||||
c := macro.Stop()
|
|
||||||
|
|
||||||
for range 2 {
|
|
||||||
op.Affine(f32.AffineId().Rotate(f32.Pt(0, 0), .2)).Add(o)
|
|
||||||
c.Add(o)
|
|
||||||
op.Offset(image.Pt(20, 20)).Add(o)
|
|
||||||
}
|
|
||||||
}, nil)
|
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 787 B |
Binary file not shown.
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 334 B |
Binary file not shown.
|
Before Width: | Height: | Size: 435 B |
@@ -24,6 +24,7 @@ func TestTransformMacro(t *testing.T) {
|
|||||||
c := constSqPath()
|
c := constSqPath()
|
||||||
|
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
|
|
||||||
// render the first Stacked item
|
// render the first Stacked item
|
||||||
m1 := op.Record(o)
|
m1 := op.Record(o)
|
||||||
dr := image.Rect(0, 0, 128, 50)
|
dr := image.Rect(0, 0, 128, 50)
|
||||||
@@ -87,10 +88,10 @@ func TestNoClipFromPaint(t *testing.T) {
|
|||||||
// ensure that a paint operation does not pollute the state
|
// ensure that a paint operation does not pollute the state
|
||||||
// by leaving any clip paths in place.
|
// by leaving any clip paths in place.
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
a := f32.AffineId().Rotate(f32.Pt(20, 20), math.Pi/4)
|
a := f32.Affine2D{}.Rotate(f32.Pt(20, 20), math.Pi/4)
|
||||||
defer op.Affine(a).Push(o).Pop()
|
defer op.Affine(a).Push(o).Pop()
|
||||||
paint.FillShape(o, red, clip.Rect(image.Rect(10, 10, 30, 30)).Op())
|
paint.FillShape(o, red, clip.Rect(image.Rect(10, 10, 30, 30)).Op())
|
||||||
a = f32.AffineId().Rotate(f32.Pt(20, 20), -math.Pi/4)
|
a = f32.Affine2D{}.Rotate(f32.Pt(20, 20), -math.Pi/4)
|
||||||
defer op.Affine(a).Push(o).Pop()
|
defer op.Affine(a).Push(o).Pop()
|
||||||
|
|
||||||
paint.FillShape(o, black, clip.Rect(image.Rect(0, 0, 50, 50)).Op())
|
paint.FillShape(o, black, clip.Rect(image.Rect(0, 0, 50, 50)).Op())
|
||||||
@@ -109,7 +110,7 @@ func TestDeferredPaint(t *testing.T) {
|
|||||||
paint.PaintOp{}.Add(o)
|
paint.PaintOp{}.Add(o)
|
||||||
cl.Pop()
|
cl.Pop()
|
||||||
|
|
||||||
t := op.Affine(f32.AffineId().Offset(f32.Pt(20, 20))).Push(o)
|
t := op.Affine(f32.Affine2D{}.Offset(f32.Pt(20, 20))).Push(o)
|
||||||
m := op.Record(o)
|
m := op.Record(o)
|
||||||
cl2 := clip.Rect(image.Rect(0, 0, 80, 80)).Op().Push(o)
|
cl2 := clip.Rect(image.Rect(0, 0, 80, 80)).Op().Push(o)
|
||||||
paint.ColorOp{Color: color.NRGBA{A: 0x60, R: 0xff, G: 0xff}}.Add(o)
|
paint.ColorOp{Color: color.NRGBA{A: 0x60, R: 0xff, G: 0xff}}.Add(o)
|
||||||
@@ -119,11 +120,12 @@ func TestDeferredPaint(t *testing.T) {
|
|||||||
op.Defer(o, paintMacro)
|
op.Defer(o, paintMacro)
|
||||||
t.Pop()
|
t.Pop()
|
||||||
|
|
||||||
defer op.Affine(f32.AffineId().Offset(f32.Pt(10, 10))).Push(o).Pop()
|
defer op.Affine(f32.Affine2D{}.Offset(f32.Pt(10, 10))).Push(o).Pop()
|
||||||
defer clip.Rect(image.Rect(0, 0, 80, 80)).Op().Push(o).Pop()
|
defer clip.Rect(image.Rect(0, 0, 80, 80)).Op().Push(o).Pop()
|
||||||
paint.ColorOp{Color: color.NRGBA{A: 0x60, B: 0xff}}.Add(o)
|
paint.ColorOp{Color: color.NRGBA{A: 0x60, B: 0xff}}.Add(o)
|
||||||
paint.PaintOp{}.Add(o)
|
paint.PaintOp{}.Add(o)
|
||||||
}, nil)
|
}, func(r result) {
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func constSqPath() clip.Op {
|
func constSqPath() clip.Op {
|
||||||
@@ -141,10 +143,8 @@ func constSqPath() clip.Op {
|
|||||||
|
|
||||||
func constSqCirc() clip.Op {
|
func constSqCirc() clip.Op {
|
||||||
innerOps := new(op.Ops)
|
innerOps := new(op.Ops)
|
||||||
return clip.RRect{
|
return clip.RRect{Rect: image.Rect(0, 0, 40, 40),
|
||||||
Rect: image.Rect(0, 0, 40, 40),
|
NW: 20, NE: 20, SW: 20, SE: 20}.Op(innerOps)
|
||||||
NW: 20, NE: 20, SW: 20, SE: 20,
|
|
||||||
}.Op(innerOps)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawChild(ops *op.Ops, text clip.Op) op.CallOp {
|
func drawChild(ops *op.Ops, text clip.Op) op.CallOp {
|
||||||
@@ -260,7 +260,7 @@ func TestLinearGradient(t *testing.T) {
|
|||||||
Color2: g.To,
|
Color2: g.To,
|
||||||
}.Add(ops)
|
}.Add(ops)
|
||||||
cl := clip.RRect{Rect: gr.Round()}.Push(ops)
|
cl := clip.RRect{Rect: gr.Round()}.Push(ops)
|
||||||
t1 := op.Affine(f32.AffineId().Offset(pixelAligned.Min)).Push(ops)
|
t1 := op.Affine(f32.Affine2D{}.Offset(pixelAligned.Min)).Push(ops)
|
||||||
t2 := scale(pixelAligned.Dx()/128, 1).Push(ops)
|
t2 := scale(pixelAligned.Dx()/128, 1).Push(ops)
|
||||||
paint.PaintOp{}.Add(ops)
|
paint.PaintOp{}.Add(ops)
|
||||||
t2.Pop()
|
t2.Pop()
|
||||||
@@ -323,7 +323,7 @@ func TestLinearGradientAngled(t *testing.T) {
|
|||||||
cl = clip.Rect(image.Rect(0, 64, 64, 128)).Push(ops)
|
cl = clip.Rect(image.Rect(0, 64, 64, 128)).Push(ops)
|
||||||
paint.PaintOp{}.Add(ops)
|
paint.PaintOp{}.Add(ops)
|
||||||
cl.Pop()
|
cl.Pop()
|
||||||
}, nil)
|
}, func(r result) {})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestZeroImage(t *testing.T) {
|
func TestZeroImage(t *testing.T) {
|
||||||
@@ -363,7 +363,7 @@ func TestImageRGBA_ScaleLinear(t *testing.T) {
|
|||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
w := newWindow(t, 128, 128)
|
w := newWindow(t, 128, 128)
|
||||||
defer clip.Rect{Max: image.Pt(128, 128)}.Push(o).Pop()
|
defer clip.Rect{Max: image.Pt(128, 128)}.Push(o).Pop()
|
||||||
op.Affine(f32.AffineId().Scale(f32.Point{}, f32.Pt(64, 64))).Add(o)
|
op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(64, 64))).Add(o)
|
||||||
|
|
||||||
im := image.NewRGBA(image.Rect(0, 0, 2, 2))
|
im := image.NewRGBA(image.Rect(0, 0, 2, 2))
|
||||||
im.Set(0, 0, colornames.Red)
|
im.Set(0, 0, colornames.Red)
|
||||||
@@ -397,7 +397,7 @@ func TestImageRGBA_ScaleLinear(t *testing.T) {
|
|||||||
func TestImageRGBA_ScaleNearest(t *testing.T) {
|
func TestImageRGBA_ScaleNearest(t *testing.T) {
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
w := newWindow(t, 128, 128)
|
w := newWindow(t, 128, 128)
|
||||||
op.Affine(f32.AffineId().Scale(f32.Point{}, f32.Pt(64, 64))).Add(o)
|
op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(64, 64))).Add(o)
|
||||||
|
|
||||||
im := image.NewRGBA(image.Rect(0, 0, 2, 2))
|
im := image.NewRGBA(image.Rect(0, 0, 2, 2))
|
||||||
im.Set(0, 0, colornames.Red)
|
im.Set(0, 0, colornames.Red)
|
||||||
@@ -483,16 +483,17 @@ 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 exercise the glClear optimization.
|
// Fill screen to exercize 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{B: 255, A: 255}, clip.Ellipse(image.Rectangle{Min: image.Pt(20+20, 10), Max: image.Pt(50+64, 128)}).Op(ops))
|
paint.FillShape(ops, color.NRGBA{G: 255, A: 255}, clip.Rect{Min: image.Pt(50+20, 10), Max: image.Pt(50+64, 128)}.Op())
|
||||||
opc3.Pop()
|
opc3.Pop()
|
||||||
}, nil)
|
}, func(r result) {
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// lerp calculates linear interpolation with color b and p.
|
// lerp calculates linear interpolation with color b and p.
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func TestPaintOffset(t *testing.T) {
|
|||||||
|
|
||||||
func TestPaintRotate(t *testing.T) {
|
func TestPaintRotate(t *testing.T) {
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
a := f32.AffineId().Rotate(f32.Pt(40, 40), -math.Pi/8)
|
a := f32.Affine2D{}.Rotate(f32.Pt(40, 40), -math.Pi/8)
|
||||||
defer op.Affine(a).Push(o).Pop()
|
defer op.Affine(a).Push(o).Pop()
|
||||||
paint.FillShape(o, red, clip.Rect(image.Rect(20, 20, 60, 60)).Op())
|
paint.FillShape(o, red, clip.Rect(image.Rect(20, 20, 60, 60)).Op())
|
||||||
}, func(r result) {
|
}, func(r result) {
|
||||||
@@ -42,7 +42,7 @@ func TestPaintRotate(t *testing.T) {
|
|||||||
|
|
||||||
func TestPaintShear(t *testing.T) {
|
func TestPaintShear(t *testing.T) {
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
a := f32.AffineId().Shear(f32.Point{}, math.Pi/4, 0)
|
a := f32.Affine2D{}.Shear(f32.Point{}, math.Pi/4, 0)
|
||||||
defer op.Affine(a).Push(o).Pop()
|
defer op.Affine(a).Push(o).Pop()
|
||||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 40, 40)).Op())
|
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 40, 40)).Op())
|
||||||
}, func(r result) {
|
}, func(r result) {
|
||||||
@@ -79,7 +79,7 @@ func TestClipOffset(t *testing.T) {
|
|||||||
|
|
||||||
func TestClipScale(t *testing.T) {
|
func TestClipScale(t *testing.T) {
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
a := f32.AffineId().Scale(f32.Point{}, f32.Pt(2, 2)).Offset(f32.Pt(10, 10))
|
a := f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(2, 2)).Offset(f32.Pt(10, 10))
|
||||||
defer op.Affine(a).Push(o).Pop()
|
defer op.Affine(a).Push(o).Pop()
|
||||||
defer clip.RRect{Rect: image.Rect(10, 10, 20, 20)}.Push(o).Pop()
|
defer clip.RRect{Rect: image.Rect(10, 10, 20, 20)}.Push(o).Pop()
|
||||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 1000, 1000)).Op())
|
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 1000, 1000)).Op())
|
||||||
@@ -93,7 +93,7 @@ func TestClipScale(t *testing.T) {
|
|||||||
|
|
||||||
func TestClipRotate(t *testing.T) {
|
func TestClipRotate(t *testing.T) {
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
defer op.Affine(f32.AffineId().Rotate(f32.Pt(40, 40), -math.Pi/4)).Push(o).Pop()
|
defer op.Affine(f32.Affine2D{}.Rotate(f32.Pt(40, 40), -math.Pi/4)).Push(o).Pop()
|
||||||
defer clip.RRect{Rect: image.Rect(30, 30, 50, 50)}.Push(o).Pop()
|
defer clip.RRect{Rect: image.Rect(30, 30, 50, 50)}.Push(o).Pop()
|
||||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 40, 100, 100)).Op())
|
paint.FillShape(o, red, clip.Rect(image.Rect(0, 40, 100, 100)).Op())
|
||||||
}, func(r result) {
|
}, func(r result) {
|
||||||
@@ -121,7 +121,7 @@ func TestOffsetScaleTexture(t *testing.T) {
|
|||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
defer op.Offset(image.Pt(15, 15)).Push(o).Pop()
|
defer op.Offset(image.Pt(15, 15)).Push(o).Pop()
|
||||||
squares.Add(o)
|
squares.Add(o)
|
||||||
defer op.Affine(f32.AffineId().Scale(f32.Point{}, f32.Pt(2, 1))).Push(o).Pop()
|
defer op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(2, 1))).Push(o).Pop()
|
||||||
defer scale(50.0/512, 50.0/512).Push(o).Pop()
|
defer scale(50.0/512, 50.0/512).Push(o).Pop()
|
||||||
paint.PaintOp{}.Add(o)
|
paint.PaintOp{}.Add(o)
|
||||||
}, func(r result) {
|
}, func(r result) {
|
||||||
@@ -133,7 +133,7 @@ func TestOffsetScaleTexture(t *testing.T) {
|
|||||||
func TestRotateTexture(t *testing.T) {
|
func TestRotateTexture(t *testing.T) {
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
squares.Add(o)
|
squares.Add(o)
|
||||||
a := f32.AffineId().Offset(f32.Pt(30, 30)).Rotate(f32.Pt(40, 40), math.Pi/4)
|
a := f32.Affine2D{}.Offset(f32.Pt(30, 30)).Rotate(f32.Pt(40, 40), math.Pi/4)
|
||||||
defer op.Affine(a).Push(o).Pop()
|
defer op.Affine(a).Push(o).Pop()
|
||||||
defer scale(20.0/512, 20.0/512).Push(o).Pop()
|
defer scale(20.0/512, 20.0/512).Push(o).Pop()
|
||||||
paint.PaintOp{}.Add(o)
|
paint.PaintOp{}.Add(o)
|
||||||
@@ -146,10 +146,10 @@ func TestRotateTexture(t *testing.T) {
|
|||||||
func TestRotateClipTexture(t *testing.T) {
|
func TestRotateClipTexture(t *testing.T) {
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
squares.Add(o)
|
squares.Add(o)
|
||||||
a := f32.AffineId().Rotate(f32.Pt(40, 40), math.Pi/8)
|
a := f32.Affine2D{}.Rotate(f32.Pt(40, 40), math.Pi/8)
|
||||||
defer op.Affine(a).Push(o).Pop()
|
defer op.Affine(a).Push(o).Pop()
|
||||||
defer clip.RRect{Rect: image.Rect(30, 30, 50, 50)}.Push(o).Pop()
|
defer clip.RRect{Rect: image.Rect(30, 30, 50, 50)}.Push(o).Pop()
|
||||||
defer op.Affine(f32.AffineId().Offset(f32.Pt(10, 10))).Push(o).Pop()
|
defer op.Affine(f32.Affine2D{}.Offset(f32.Pt(10, 10))).Push(o).Pop()
|
||||||
defer scale(60.0/512, 60.0/512).Push(o).Pop()
|
defer scale(60.0/512, 60.0/512).Push(o).Pop()
|
||||||
paint.PaintOp{}.Add(o)
|
paint.PaintOp{}.Add(o)
|
||||||
}, func(r result) {
|
}, func(r result) {
|
||||||
@@ -168,7 +168,7 @@ func TestComplicatedTransform(t *testing.T) {
|
|||||||
|
|
||||||
defer clip.RRect{Rect: image.Rect(0, 0, 100, 100), SE: 50, SW: 50, NW: 50, NE: 50}.Push(o).Pop()
|
defer clip.RRect{Rect: image.Rect(0, 0, 100, 100), SE: 50, SW: 50, NW: 50, NE: 50}.Push(o).Pop()
|
||||||
|
|
||||||
a := f32.AffineId().Shear(f32.Point{}, math.Pi/4, 0)
|
a := f32.Affine2D{}.Shear(f32.Point{}, math.Pi/4, 0)
|
||||||
defer op.Affine(a).Push(o).Pop()
|
defer op.Affine(a).Push(o).Pop()
|
||||||
defer clip.RRect{Rect: image.Rect(0, 0, 50, 40)}.Push(o).Pop()
|
defer clip.RRect{Rect: image.Rect(0, 0, 50, 40)}.Push(o).Pop()
|
||||||
|
|
||||||
@@ -182,13 +182,13 @@ func TestComplicatedTransform(t *testing.T) {
|
|||||||
func TestTransformOrder(t *testing.T) {
|
func TestTransformOrder(t *testing.T) {
|
||||||
// check the ordering of operations bot in affine and in gpu stack.
|
// check the ordering of operations bot in affine and in gpu stack.
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
a := f32.AffineId().Offset(f32.Pt(64, 64))
|
a := f32.Affine2D{}.Offset(f32.Pt(64, 64))
|
||||||
defer op.Affine(a).Push(o).Pop()
|
defer op.Affine(a).Push(o).Pop()
|
||||||
|
|
||||||
b := f32.AffineId().Scale(f32.Point{}, f32.Pt(8, 8))
|
b := f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(8, 8))
|
||||||
defer op.Affine(b).Push(o).Pop()
|
defer op.Affine(b).Push(o).Pop()
|
||||||
|
|
||||||
c := f32.AffineId().Offset(f32.Pt(-10, -10)).Scale(f32.Point{}, f32.Pt(0.5, 0.5))
|
c := f32.Affine2D{}.Offset(f32.Pt(-10, -10)).Scale(f32.Point{}, f32.Pt(0.5, 0.5))
|
||||||
defer op.Affine(c).Push(o).Pop()
|
defer op.Affine(c).Push(o).Pop()
|
||||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 20, 20)).Op())
|
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 20, 20)).Op())
|
||||||
}, func(r result) {
|
}, func(r result) {
|
||||||
|
|||||||
@@ -49,8 +49,8 @@ func buildSquares(size int) paint.ImageOp {
|
|||||||
sub := size / 4
|
sub := size / 4
|
||||||
im := image.NewNRGBA(image.Rect(0, 0, size, size))
|
im := image.NewNRGBA(image.Rect(0, 0, size, size))
|
||||||
c1, c2 := image.NewUniform(colornames.Green), image.NewUniform(colornames.Blue)
|
c1, c2 := image.NewUniform(colornames.Green), image.NewUniform(colornames.Blue)
|
||||||
for r := range 4 {
|
for r := 0; r < 4; r++ {
|
||||||
for c := range 4 {
|
for c := 0; c < 4; c++ {
|
||||||
c1, c2 = c2, c1
|
c1, c2 = c2, c1
|
||||||
draw.Draw(im, image.Rect(r*sub, c*sub, r*sub+sub, c*sub+sub), c1, image.Point{}, draw.Over)
|
draw.Draw(im, image.Rect(r*sub, c*sub, r*sub+sub, c*sub+sub), c1, image.Point{}, draw.Over)
|
||||||
}
|
}
|
||||||
@@ -78,7 +78,7 @@ func run(t *testing.T, f func(o *op.Ops), c func(r result)) {
|
|||||||
var img *image.RGBA
|
var img *image.RGBA
|
||||||
var err error
|
var err error
|
||||||
ops := new(op.Ops)
|
ops := new(op.Ops)
|
||||||
for i := range 3 {
|
for i := 0; i < 3; i++ {
|
||||||
ops.Reset()
|
ops.Reset()
|
||||||
img, err = drawImage(t, 128, ops, f)
|
img, err = drawImage(t, 128, ops, f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -90,9 +90,7 @@ func run(t *testing.T, f func(o *op.Ops), c func(r result)) {
|
|||||||
name := fmt.Sprintf("%s-%d-bad.png", t.Name(), i)
|
name := fmt.Sprintf("%s-%d-bad.png", t.Name(), i)
|
||||||
saveImage(t, name, img)
|
saveImage(t, name, img)
|
||||||
}
|
}
|
||||||
if c != nil {
|
c(result{t: t, img: img})
|
||||||
c(result{t: t, img: img})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +151,7 @@ func verifyRef(t *testing.T, img *image.RGBA, frame int) (ok bool) {
|
|||||||
}
|
}
|
||||||
path = filepath.Join("refs", path+".png")
|
path = filepath.Join("refs", path+".png")
|
||||||
if *dumpImages {
|
if *dumpImages {
|
||||||
if err := os.MkdirAll(filepath.Dir(path), 0o766); err != nil {
|
if err := os.MkdirAll(filepath.Dir(path), 0766); err != nil {
|
||||||
if !os.IsExist(err) {
|
if !os.IsExist(err) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@@ -287,7 +285,7 @@ func saveImage(t testing.TB, file string, img *image.RGBA) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := os.WriteFile(file, buf.Bytes(), 0o666); err != nil {
|
if err := os.WriteFile(file, buf.Bytes(), 0666); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -302,5 +300,5 @@ func newWindow(t testing.TB, width, height int) *headless.Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func scale(sx, sy float32) op.TransformOp {
|
func scale(sx, sy float32) op.TransformOp {
|
||||||
return op.Affine(f32.AffineId().Scale(f32.Point{}, f32.Pt(sx, sy)))
|
return op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(sx, sy)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 := vk.PushConstantRangeOffset(s)
|
off := s.Offset()
|
||||||
vk.CmdPushConstants(cmdBuf, b.pipe.desc.layout, vk.PushConstantRangeStageFlags(s), off, buf.store[off:off+vk.PushConstantRangeSize(s)])
|
vk.CmdPushConstants(cmdBuf, b.pipe.desc.layout, s.StageFlags(), off, buf.store[off:off+s.Size()])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -10,10 +10,10 @@ import (
|
|||||||
func BenchmarkPacker(b *testing.B) {
|
func BenchmarkPacker(b *testing.B) {
|
||||||
var p packer
|
var p packer
|
||||||
p.maxDims = image.Point{X: 4096, Y: 4096}
|
p.maxDims = image.Point{X: 4096, Y: 4096}
|
||||||
for i := 0; b.Loop(); i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
p.clear()
|
p.clear()
|
||||||
p.newPage()
|
p.newPage()
|
||||||
for k := range 500 {
|
for k := 0; k < 500; k++ {
|
||||||
_, ok := p.tryAdd(xy(k))
|
_, ok := p.tryAdd(xy(k))
|
||||||
if !ok {
|
if !ok {
|
||||||
b.Fatal("add failed", i, k, xy(k))
|
b.Fatal("add failed", i, k, xy(k))
|
||||||
|
|||||||
+8
-14
@@ -30,7 +30,7 @@ type pather struct {
|
|||||||
|
|
||||||
type coverer struct {
|
type coverer struct {
|
||||||
ctx driver.Device
|
ctx driver.Device
|
||||||
pipelines [2][3]*pipeline
|
pipelines [3]*pipeline
|
||||||
texUniforms *coverTexUniforms
|
texUniforms *coverTexUniforms
|
||||||
colUniforms *coverColUniforms
|
colUniforms *coverColUniforms
|
||||||
linearGradientUniforms *coverLinearGradientUniforms
|
linearGradientUniforms *coverLinearGradientUniforms
|
||||||
@@ -150,7 +150,7 @@ func newCoverer(ctx driver.Device) *coverer {
|
|||||||
c.texUniforms = new(coverTexUniforms)
|
c.texUniforms = new(coverTexUniforms)
|
||||||
c.linearGradientUniforms = new(coverLinearGradientUniforms)
|
c.linearGradientUniforms = new(coverLinearGradientUniforms)
|
||||||
pipelines, err := createColorPrograms(ctx, gio.Shader_cover_vert, gio.Shader_cover_frag,
|
pipelines, err := createColorPrograms(ctx, gio.Shader_cover_vert, gio.Shader_cover_frag,
|
||||||
[3]any{c.colUniforms, c.linearGradientUniforms, c.texUniforms},
|
[3]interface{}{c.colUniforms, c.linearGradientUniforms, c.texUniforms},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -162,7 +162,7 @@ func newCoverer(ctx driver.Device) *coverer {
|
|||||||
func newStenciler(ctx driver.Device) *stenciler {
|
func newStenciler(ctx driver.Device) *stenciler {
|
||||||
// Allocate a suitably large index buffer for drawing paths.
|
// Allocate a suitably large index buffer for drawing paths.
|
||||||
indices := make([]uint16, pathBatchSize*6)
|
indices := make([]uint16, pathBatchSize*6)
|
||||||
for i := range pathBatchSize {
|
for i := 0; i < pathBatchSize; i++ {
|
||||||
i := uint16(i)
|
i := uint16(i)
|
||||||
indices[i*6+0] = i*4 + 0
|
indices[i*6+0] = i*4 + 0
|
||||||
indices[i*6+1] = i*4 + 1
|
indices[i*6+1] = i*4 + 1
|
||||||
@@ -309,9 +309,7 @@ func (p *pather) release() {
|
|||||||
|
|
||||||
func (c *coverer) release() {
|
func (c *coverer) release() {
|
||||||
for _, p := range c.pipelines {
|
for _, p := range c.pipelines {
|
||||||
for _, p := range p {
|
p.Release()
|
||||||
p.Release()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,7 +332,7 @@ func (p *pather) begin(sizes []image.Point) {
|
|||||||
p.stenciler.begin(sizes)
|
p.stenciler.begin(sizes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pather) stencilPath(bounds image.Rectangle, offset image.Point, uv image.Point, data pathData) {
|
func (p *pather) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) {
|
||||||
p.stenciler.stencilPath(bounds, offset, uv, data)
|
p.stenciler.stencilPath(bounds, offset, uv, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,14 +351,14 @@ func (s *stenciler) begin(sizes []image.Point) {
|
|||||||
s.fbos.resize(s.ctx, driver.TextureFormatFloat, sizes)
|
s.fbos.resize(s.ctx, driver.TextureFormatFloat, sizes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stenciler) stencilPath(bounds image.Rectangle, offset image.Point, uv image.Point, data pathData) {
|
func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) {
|
||||||
s.ctx.Viewport(uv.X, uv.Y, bounds.Dx(), bounds.Dy())
|
s.ctx.Viewport(uv.X, uv.Y, bounds.Dx(), bounds.Dy())
|
||||||
// Transform UI coordinates to OpenGL coordinates.
|
// Transform UI coordinates to OpenGL coordinates.
|
||||||
texSize := f32.Point{X: float32(bounds.Dx()), Y: float32(bounds.Dy())}
|
texSize := f32.Point{X: float32(bounds.Dx()), Y: float32(bounds.Dy())}
|
||||||
scale := f32.Point{X: 2 / texSize.X, Y: 2 / texSize.Y}
|
scale := f32.Point{X: 2 / texSize.X, Y: 2 / texSize.Y}
|
||||||
orig := f32.Point{X: -1 - float32(bounds.Min.X)*2/texSize.X, Y: -1 - float32(bounds.Min.Y)*2/texSize.Y}
|
orig := f32.Point{X: -1 - float32(bounds.Min.X)*2/texSize.X, Y: -1 - float32(bounds.Min.Y)*2/texSize.Y}
|
||||||
s.pipeline.uniforms.transform = [4]float32{scale.X, scale.Y, orig.X, orig.Y}
|
s.pipeline.uniforms.transform = [4]float32{scale.X, scale.Y, orig.X, orig.Y}
|
||||||
s.pipeline.uniforms.pathOffset = [2]float32{float32(offset.X), float32(offset.Y)}
|
s.pipeline.uniforms.pathOffset = [2]float32{offset.X, offset.Y}
|
||||||
s.pipeline.pipeline.UploadUniforms(s.ctx)
|
s.pipeline.pipeline.UploadUniforms(s.ctx)
|
||||||
// Draw in batches that fit in uint16 indices.
|
// Draw in batches that fit in uint16 indices.
|
||||||
start := 0
|
start := 0
|
||||||
@@ -407,11 +405,7 @@ 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}
|
||||||
fboIdx := 0
|
c.pipelines[mat].UploadUniforms(c.ctx)
|
||||||
if isFBO {
|
|
||||||
fboIdx = 1
|
|
||||||
}
|
|
||||||
c.pipelines[fboIdx][mat].UploadUniforms(c.ctx)
|
|
||||||
c.ctx.DrawArrays(0, 4)
|
c.ctx.DrawArrays(0, 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Struct returns a byte slice view of a struct.
|
// Struct returns a byte slice view of a struct.
|
||||||
func Struct(s any) []byte {
|
func Struct(s interface{}) []byte {
|
||||||
v := reflect.ValueOf(s)
|
v := reflect.ValueOf(s)
|
||||||
sz := int(v.Elem().Type().Size())
|
sz := int(v.Elem().Type().Size())
|
||||||
return unsafe.Slice((*byte)(unsafe.Pointer(v.Pointer())), sz)
|
return unsafe.Slice((*byte)(unsafe.Pointer(v.Pointer())), sz)
|
||||||
@@ -27,7 +27,7 @@ func Uint32(s []uint32) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Slice returns a byte slice view of a slice.
|
// Slice returns a byte slice view of a slice.
|
||||||
func Slice(s any) []byte {
|
func Slice(s interface{}) []byte {
|
||||||
v := reflect.ValueOf(s)
|
v := reflect.ValueOf(s)
|
||||||
first := v.Index(0)
|
first := v.Index(0)
|
||||||
sz := int(first.Type().Size())
|
sz := int(first.Type().Size())
|
||||||
|
|||||||
+13
-6
@@ -9,16 +9,16 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
"slices"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gioui.org/gpu"
|
"gioui.org/gpu"
|
||||||
)
|
)
|
||||||
|
|
||||||
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,9 +121,11 @@ func (c *Context) VisualID() int {
|
|||||||
return c.eglCtx.visualID
|
return c.eglCtx.visualID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) CreateSurface(win NativeWindowType) error {
|
func (c *Context) CreateSurface(win NativeWindowType, width, height int) 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +157,12 @@ func (c *Context) EnableVSync(enable bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func hasExtension(exts []string, ext string) bool {
|
func hasExtension(exts []string, ext string) bool {
|
||||||
return slices.Contains(exts, ext)
|
for _, e := range exts {
|
||||||
|
if ext == e {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func createContext(disp _EGLDisplay) (*eglContext, error) {
|
func createContext(disp _EGLDisplay) (*eglContext, error) {
|
||||||
|
|||||||
+27
-49
@@ -9,6 +9,8 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
syscall "golang.org/x/sys/windows"
|
syscall "golang.org/x/sys/windows"
|
||||||
|
|
||||||
|
"gioui.org/internal/gl"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -22,23 +24,23 @@ type (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
libEGL = syscall.DLL{}
|
libEGL = syscall.NewLazyDLL("libEGL.dll")
|
||||||
_eglChooseConfig *syscall.Proc
|
_eglChooseConfig = libEGL.NewProc("eglChooseConfig")
|
||||||
_eglCreateContext *syscall.Proc
|
_eglCreateContext = libEGL.NewProc("eglCreateContext")
|
||||||
_eglCreateWindowSurface *syscall.Proc
|
_eglCreateWindowSurface = libEGL.NewProc("eglCreateWindowSurface")
|
||||||
_eglDestroyContext *syscall.Proc
|
_eglDestroyContext = libEGL.NewProc("eglDestroyContext")
|
||||||
_eglDestroySurface *syscall.Proc
|
_eglDestroySurface = libEGL.NewProc("eglDestroySurface")
|
||||||
_eglGetConfigAttrib *syscall.Proc
|
_eglGetConfigAttrib = libEGL.NewProc("eglGetConfigAttrib")
|
||||||
_eglGetDisplay *syscall.Proc
|
_eglGetDisplay = libEGL.NewProc("eglGetDisplay")
|
||||||
_eglGetError *syscall.Proc
|
_eglGetError = libEGL.NewProc("eglGetError")
|
||||||
_eglInitialize *syscall.Proc
|
_eglInitialize = libEGL.NewProc("eglInitialize")
|
||||||
_eglMakeCurrent *syscall.Proc
|
_eglMakeCurrent = libEGL.NewProc("eglMakeCurrent")
|
||||||
_eglReleaseThread *syscall.Proc
|
_eglReleaseThread = libEGL.NewProc("eglReleaseThread")
|
||||||
_eglSwapInterval *syscall.Proc
|
_eglSwapInterval = libEGL.NewProc("eglSwapInterval")
|
||||||
_eglSwapBuffers *syscall.Proc
|
_eglSwapBuffers = libEGL.NewProc("eglSwapBuffers")
|
||||||
_eglTerminate *syscall.Proc
|
_eglTerminate = libEGL.NewProc("eglTerminate")
|
||||||
_eglQueryString *syscall.Proc
|
_eglQueryString = libEGL.NewProc("eglQueryString")
|
||||||
_eglWaitClient *syscall.Proc
|
_eglWaitClient = libEGL.NewProc("eglWaitClient")
|
||||||
)
|
)
|
||||||
|
|
||||||
var loadOnce sync.Once
|
var loadOnce sync.Once
|
||||||
@@ -52,45 +54,21 @@ 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 {
|
||||||
procs := map[string]**syscall.Proc{
|
return err
|
||||||
"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,
|
|
||||||
}
|
}
|
||||||
for name, proc := range procs {
|
// d3dcompiler_47.dll is needed internally for shader compilation to function.
|
||||||
p, err := libEGL.FindProc(name)
|
return loadDLL(syscall.NewLazyDLL("d3dcompiler_47.dll"), "d3dcompiler_47.dll")
|
||||||
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.DLL, name string) error {
|
func loadDLL(dll *syscall.LazyDLL, name string) error {
|
||||||
handle, err := syscall.LoadLibraryEx(name, 0, syscall.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)
|
err := dll.Load()
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,6 +164,6 @@ func eglWaitClient() bool {
|
|||||||
|
|
||||||
// issue34474KeepAlive calls runtime.KeepAlive as a
|
// issue34474KeepAlive calls runtime.KeepAlive as a
|
||||||
// workaround for golang.org/issue/34474.
|
// workaround for golang.org/issue/34474.
|
||||||
func issue34474KeepAlive(v any) {
|
func issue34474KeepAlive(v interface{}) {
|
||||||
runtime.KeepAlive(v)
|
runtime.KeepAlive(v)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ type Affine2D = f32.Affine2D
|
|||||||
|
|
||||||
var NewAffine2D = f32.NewAffine2D
|
var NewAffine2D = f32.NewAffine2D
|
||||||
|
|
||||||
var AffineId = f32.AffineId
|
|
||||||
|
|
||||||
// A Rectangle contains the points (X, Y) where Min.X <= X < Max.X,
|
// A Rectangle contains the points (X, Y) where Min.X <= X < Max.X,
|
||||||
// Min.Y <= Y < Max.Y.
|
// Min.Y <= Y < Max.Y.
|
||||||
type Rectangle struct {
|
type Rectangle struct {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ func main() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
printf := func(content string, args ...any) {
|
printf := func(content string, args ...interface{}) {
|
||||||
fmt.Fprintf(&b, content, args...)
|
fmt.Fprintf(&b, content, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ func main() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.WriteFile(*out, data, 0o755)
|
err = os.WriteFile(*out, data, 0755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,17 +41,17 @@ var sink RGBA
|
|||||||
|
|
||||||
func BenchmarkLinearFromSRGB(b *testing.B) {
|
func BenchmarkLinearFromSRGB(b *testing.B) {
|
||||||
b.Run("opaque", func(b *testing.B) {
|
b.Run("opaque", func(b *testing.B) {
|
||||||
for i := 0; b.Loop(); i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
sink = LinearFromSRGB(color.NRGBA{R: byte(i), G: byte(i >> 8), B: byte(i >> 16), A: 0xFF})
|
sink = LinearFromSRGB(color.NRGBA{R: byte(i), G: byte(i >> 8), B: byte(i >> 16), A: 0xFF})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
b.Run("translucent", func(b *testing.B) {
|
b.Run("translucent", func(b *testing.B) {
|
||||||
for i := 0; b.Loop(); i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
sink = LinearFromSRGB(color.NRGBA{R: byte(i), G: byte(i >> 8), B: byte(i >> 16), A: 0x50})
|
sink = LinearFromSRGB(color.NRGBA{R: byte(i), G: byte(i >> 8), B: byte(i >> 16), A: 0x50})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
b.Run("transparent", func(b *testing.B) {
|
b.Run("transparent", func(b *testing.B) {
|
||||||
for i := 0; b.Loop(); i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
sink = LinearFromSRGB(color.NRGBA{R: byte(i), G: byte(i >> 8), B: byte(i >> 16), A: 0x00})
|
sink = LinearFromSRGB(color.NRGBA{R: byte(i), G: byte(i >> 8), B: byte(i >> 16), A: 0x00})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ func (e *Extrapolation) Estimate() Estimate {
|
|||||||
first := e.get(0)
|
first := e.get(0)
|
||||||
t := first.t
|
t := first.t
|
||||||
// Walk backwards collecting samples.
|
// Walk backwards collecting samples.
|
||||||
for i := range e.samples {
|
for i := 0; i < len(e.samples); i++ {
|
||||||
p := e.get(-i)
|
p := e.get(-i)
|
||||||
age := first.t - p.t
|
age := first.t - p.t
|
||||||
if age >= maxAge || t-p.t >= maxSampleGap {
|
if age >= maxAge || t-p.t >= maxSampleGap {
|
||||||
@@ -172,9 +172,9 @@ func decomposeQR(A *matrix) (*matrix, *matrix, bool) {
|
|||||||
// https://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process
|
// https://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process
|
||||||
Q := newMatrix(A.rows, A.cols) // Column-major.
|
Q := newMatrix(A.rows, A.cols) // Column-major.
|
||||||
Rt := newMatrix(A.rows, A.rows) // R transposed, row-major.
|
Rt := newMatrix(A.rows, A.rows) // R transposed, row-major.
|
||||||
for i := range Q.rows {
|
for i := 0; i < Q.rows; i++ {
|
||||||
// Copy A column.
|
// Copy A column.
|
||||||
for j := range Q.cols {
|
for j := 0; j < Q.cols; j++ {
|
||||||
Q.set(i, j, A.get(i, j))
|
Q.set(i, j, A.get(i, j))
|
||||||
}
|
}
|
||||||
// Subtract projections. Note that int the projection
|
// Subtract projections. Note that int the projection
|
||||||
@@ -184,9 +184,9 @@ func decomposeQR(A *matrix) (*matrix, *matrix, bool) {
|
|||||||
// the normalized column e replaces u, where <e, e> = 1:
|
// the normalized column e replaces u, where <e, e> = 1:
|
||||||
//
|
//
|
||||||
// proje a = <e, a>/<e, e> e = <e, a> e
|
// proje a = <e, a>/<e, e> e = <e, a> e
|
||||||
for j := range i {
|
for j := 0; j < i; j++ {
|
||||||
d := dot(Q.col(j), Q.col(i))
|
d := dot(Q.col(j), Q.col(i))
|
||||||
for k := range Q.cols {
|
for k := 0; k < Q.cols; k++ {
|
||||||
Q.set(i, k, Q.get(i, k)-d*Q.get(j, k))
|
Q.set(i, k, Q.get(i, k)-d*Q.get(j, k))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,7 +197,7 @@ func decomposeQR(A *matrix) (*matrix, *matrix, bool) {
|
|||||||
return nil, nil, false
|
return nil, nil, false
|
||||||
}
|
}
|
||||||
invNorm := 1 / n
|
invNorm := 1 / n
|
||||||
for j := range Q.cols {
|
for j := 0; j < Q.cols; j++ {
|
||||||
Q.set(i, j, Q.get(i, j)*invNorm)
|
Q.set(i, j, Q.get(i, j)*invNorm)
|
||||||
}
|
}
|
||||||
// Update Rt.
|
// Update Rt.
|
||||||
@@ -261,8 +261,8 @@ func (m *matrix) approxEqual(m2 *matrix) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const epsilon = 0.00001
|
const epsilon = 0.00001
|
||||||
for row := range m.rows {
|
for row := 0; row < m.rows; row++ {
|
||||||
for col := range m.cols {
|
for col := 0; col < m.cols; col++ {
|
||||||
d := m2.get(row, col) - m.get(row, col)
|
d := m2.get(row, col) - m.get(row, col)
|
||||||
if d < -epsilon || d > epsilon {
|
if d < -epsilon || d > epsilon {
|
||||||
return false
|
return false
|
||||||
@@ -278,8 +278,8 @@ func (m *matrix) transpose() *matrix {
|
|||||||
cols: m.rows,
|
cols: m.rows,
|
||||||
data: make([]float32, len(m.data)),
|
data: make([]float32, len(m.data)),
|
||||||
}
|
}
|
||||||
for i := range m.rows {
|
for i := 0; i < m.rows; i++ {
|
||||||
for j := range m.cols {
|
for j := 0; j < m.cols; j++ {
|
||||||
t.set(j, i, m.get(i, j))
|
t.set(j, i, m.get(i, j))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,10 +295,10 @@ func (m *matrix) mul(m2 *matrix) *matrix {
|
|||||||
cols: m2.cols,
|
cols: m2.cols,
|
||||||
data: make([]float32, m.rows*m2.cols),
|
data: make([]float32, m.rows*m2.cols),
|
||||||
}
|
}
|
||||||
for i := range mm.rows {
|
for i := 0; i < mm.rows; i++ {
|
||||||
for j := range mm.cols {
|
for j := 0; j < mm.cols; j++ {
|
||||||
var v float32
|
var v float32
|
||||||
for k := range m.rows {
|
for k := 0; k < m.rows; k++ {
|
||||||
v += m.get(k, j) * m2.get(i, k)
|
v += m.get(k, j) * m2.get(i, k)
|
||||||
}
|
}
|
||||||
mm.set(i, j, v)
|
mm.set(i, j, v)
|
||||||
@@ -309,8 +309,8 @@ func (m *matrix) mul(m2 *matrix) *matrix {
|
|||||||
|
|
||||||
func (m *matrix) String() string {
|
func (m *matrix) String() string {
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
for i := range m.rows {
|
for i := 0; i < m.rows; i++ {
|
||||||
for j := range m.cols {
|
for j := 0; j < m.cols; j++ {
|
||||||
v := m.get(i, j)
|
v := m.get(i, j)
|
||||||
b.WriteString(strconv.FormatFloat(float64(v), 'g', -1, 32))
|
b.WriteString(strconv.FormatFloat(float64(v), 'g', -1, 32))
|
||||||
b.WriteString(", ")
|
b.WriteString(", ")
|
||||||
|
|||||||
@@ -247,11 +247,9 @@ func (f *Functions) getExtension(name string) js.Value {
|
|||||||
func (f *Functions) ActiveTexture(t Enum) {
|
func (f *Functions) ActiveTexture(t Enum) {
|
||||||
f._activeTexture.Invoke(int(t))
|
f._activeTexture.Invoke(int(t))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) AttachShader(p Program, s Shader) {
|
func (f *Functions) AttachShader(p Program, s Shader) {
|
||||||
f._attachShader.Invoke(js.Value(p), js.Value(s))
|
f._attachShader.Invoke(js.Value(p), js.Value(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BeginQuery(target Enum, query Query) {
|
func (f *Functions) BeginQuery(target Enum, query Query) {
|
||||||
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
||||||
f._beginQuery.Invoke(int(target), js.Value(query))
|
f._beginQuery.Invoke(int(target), js.Value(query))
|
||||||
@@ -259,47 +257,36 @@ func (f *Functions) BeginQuery(target Enum, query Query) {
|
|||||||
f.EXT_disjoint_timer_query.Call("beginQueryEXT", int(target), js.Value(query))
|
f.EXT_disjoint_timer_query.Call("beginQueryEXT", int(target), js.Value(query))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BindAttribLocation(p Program, a Attrib, name string) {
|
func (f *Functions) BindAttribLocation(p Program, a Attrib, name string) {
|
||||||
f._bindAttribLocation.Invoke(js.Value(p), int(a), name)
|
f._bindAttribLocation.Invoke(js.Value(p), int(a), name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BindBuffer(target Enum, b Buffer) {
|
func (f *Functions) BindBuffer(target Enum, b Buffer) {
|
||||||
f._bindBuffer.Invoke(int(target), js.Value(b))
|
f._bindBuffer.Invoke(int(target), js.Value(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BindBufferBase(target Enum, index int, b Buffer) {
|
func (f *Functions) BindBufferBase(target Enum, index int, b Buffer) {
|
||||||
f._bindBufferBase.Invoke(int(target), index, js.Value(b))
|
f._bindBufferBase.Invoke(int(target), index, js.Value(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BindFramebuffer(target Enum, fb Framebuffer) {
|
func (f *Functions) BindFramebuffer(target Enum, fb Framebuffer) {
|
||||||
f._bindFramebuffer.Invoke(int(target), js.Value(fb))
|
f._bindFramebuffer.Invoke(int(target), js.Value(fb))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BindRenderbuffer(target Enum, rb Renderbuffer) {
|
func (f *Functions) BindRenderbuffer(target Enum, rb Renderbuffer) {
|
||||||
f._bindRenderbuffer.Invoke(int(target), js.Value(rb))
|
f._bindRenderbuffer.Invoke(int(target), js.Value(rb))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BindTexture(target Enum, t Texture) {
|
func (f *Functions) BindTexture(target Enum, t Texture) {
|
||||||
f._bindTexture.Invoke(int(target), js.Value(t))
|
f._bindTexture.Invoke(int(target), js.Value(t))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BindImageTexture(unit int, t Texture, level int, layered bool, layer int, access, format Enum) {
|
func (f *Functions) BindImageTexture(unit int, t Texture, level int, layered bool, layer int, access, format Enum) {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BindVertexArray(a VertexArray) {
|
func (f *Functions) BindVertexArray(a VertexArray) {
|
||||||
panic("not supported")
|
panic("not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BlendEquation(mode Enum) {
|
func (f *Functions) BlendEquation(mode Enum) {
|
||||||
f._blendEquation.Invoke(int(mode))
|
f._blendEquation.Invoke(int(mode))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BlendFuncSeparate(srcRGB, dstRGB, srcA, dstA Enum) {
|
func (f *Functions) BlendFuncSeparate(srcRGB, dstRGB, srcA, dstA Enum) {
|
||||||
f._blendFunc.Invoke(int(srcRGB), int(dstRGB), int(srcA), int(dstA))
|
f._blendFunc.Invoke(int(srcRGB), int(dstRGB), int(srcA), int(dstA))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BufferData(target Enum, size int, usage Enum, data []byte) {
|
func (f *Functions) BufferData(target Enum, size int, usage Enum, data []byte) {
|
||||||
if data == nil {
|
if data == nil {
|
||||||
f._bufferData.Invoke(int(target), size, int(usage))
|
f._bufferData.Invoke(int(target), size, int(usage))
|
||||||
@@ -310,11 +297,9 @@ func (f *Functions) BufferData(target Enum, size int, usage Enum, data []byte) {
|
|||||||
f._bufferData.Invoke(int(target), f.byteArrayOf(data), int(usage))
|
f._bufferData.Invoke(int(target), f.byteArrayOf(data), int(usage))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
|
func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
|
||||||
f._bufferSubData.Invoke(int(target), offset, f.byteArrayOf(src))
|
f._bufferSubData.Invoke(int(target), offset, f.byteArrayOf(src))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) CheckFramebufferStatus(target Enum) Enum {
|
func (f *Functions) CheckFramebufferStatus(target Enum) Enum {
|
||||||
status := Enum(f._checkFramebufferStatus.Invoke(int(target)).Int())
|
status := Enum(f._checkFramebufferStatus.Invoke(int(target)).Int())
|
||||||
if status != FRAMEBUFFER_COMPLETE && f.Ctx.Call("isContextLost").Bool() {
|
if status != FRAMEBUFFER_COMPLETE && f.Ctx.Call("isContextLost").Bool() {
|
||||||
@@ -323,71 +308,54 @@ func (f *Functions) CheckFramebufferStatus(target Enum) Enum {
|
|||||||
}
|
}
|
||||||
return status
|
return status
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) Clear(mask Enum) {
|
func (f *Functions) Clear(mask Enum) {
|
||||||
f._clear.Invoke(int(mask))
|
f._clear.Invoke(int(mask))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) ClearColor(red, green, blue, alpha float32) {
|
func (f *Functions) ClearColor(red, green, blue, alpha float32) {
|
||||||
f._clearColor.Invoke(red, green, blue, alpha)
|
f._clearColor.Invoke(red, green, blue, alpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) ClearDepthf(d float32) {
|
func (f *Functions) ClearDepthf(d float32) {
|
||||||
f._clearDepth.Invoke(d)
|
f._clearDepth.Invoke(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) CompileShader(s Shader) {
|
func (f *Functions) CompileShader(s Shader) {
|
||||||
f._compileShader.Invoke(js.Value(s))
|
f._compileShader.Invoke(js.Value(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) CopyTexSubImage2D(target Enum, level, xoffset, yoffset, x, y, width, height int) {
|
func (f *Functions) CopyTexSubImage2D(target Enum, level, xoffset, yoffset, x, y, width, height int) {
|
||||||
f._copyTexSubImage2D.Invoke(int(target), level, xoffset, yoffset, x, y, width, height)
|
f._copyTexSubImage2D.Invoke(int(target), level, xoffset, yoffset, x, y, width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) CreateBuffer() Buffer {
|
func (f *Functions) CreateBuffer() Buffer {
|
||||||
return Buffer(f._createBuffer.Invoke())
|
return Buffer(f._createBuffer.Invoke())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) CreateFramebuffer() Framebuffer {
|
func (f *Functions) CreateFramebuffer() Framebuffer {
|
||||||
return Framebuffer(f._createFramebuffer.Invoke())
|
return Framebuffer(f._createFramebuffer.Invoke())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) CreateProgram() Program {
|
func (f *Functions) CreateProgram() Program {
|
||||||
return Program(f._createProgram.Invoke())
|
return Program(f._createProgram.Invoke())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) CreateQuery() Query {
|
func (f *Functions) CreateQuery() Query {
|
||||||
return Query(f._createQuery.Invoke())
|
return Query(f._createQuery.Invoke())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) CreateRenderbuffer() Renderbuffer {
|
func (f *Functions) CreateRenderbuffer() Renderbuffer {
|
||||||
return Renderbuffer(f._createRenderbuffer.Invoke())
|
return Renderbuffer(f._createRenderbuffer.Invoke())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) CreateShader(ty Enum) Shader {
|
func (f *Functions) CreateShader(ty Enum) Shader {
|
||||||
return Shader(f._createShader.Invoke(int(ty)))
|
return Shader(f._createShader.Invoke(int(ty)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) CreateTexture() Texture {
|
func (f *Functions) CreateTexture() Texture {
|
||||||
return Texture(f._createTexture.Invoke())
|
return Texture(f._createTexture.Invoke())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) CreateVertexArray() VertexArray {
|
func (f *Functions) CreateVertexArray() VertexArray {
|
||||||
panic("not supported")
|
panic("not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DeleteBuffer(v Buffer) {
|
func (f *Functions) DeleteBuffer(v Buffer) {
|
||||||
f._deleteBuffer.Invoke(js.Value(v))
|
f._deleteBuffer.Invoke(js.Value(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DeleteFramebuffer(v Framebuffer) {
|
func (f *Functions) DeleteFramebuffer(v Framebuffer) {
|
||||||
f._deleteFramebuffer.Invoke(js.Value(v))
|
f._deleteFramebuffer.Invoke(js.Value(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DeleteProgram(p Program) {
|
func (f *Functions) DeleteProgram(p Program) {
|
||||||
f._deleteProgram.Invoke(js.Value(p))
|
f._deleteProgram.Invoke(js.Value(p))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DeleteQuery(query Query) {
|
func (f *Functions) DeleteQuery(query Query) {
|
||||||
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
||||||
f._deleteQuery.Invoke(js.Value(query))
|
f._deleteQuery.Invoke(js.Value(query))
|
||||||
@@ -395,59 +363,45 @@ func (f *Functions) DeleteQuery(query Query) {
|
|||||||
f.EXT_disjoint_timer_query.Call("deleteQueryEXT", js.Value(query))
|
f.EXT_disjoint_timer_query.Call("deleteQueryEXT", js.Value(query))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DeleteShader(s Shader) {
|
func (f *Functions) DeleteShader(s Shader) {
|
||||||
f._deleteShader.Invoke(js.Value(s))
|
f._deleteShader.Invoke(js.Value(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DeleteRenderbuffer(v Renderbuffer) {
|
func (f *Functions) DeleteRenderbuffer(v Renderbuffer) {
|
||||||
f._deleteRenderbuffer.Invoke(js.Value(v))
|
f._deleteRenderbuffer.Invoke(js.Value(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DeleteTexture(v Texture) {
|
func (f *Functions) DeleteTexture(v Texture) {
|
||||||
f._deleteTexture.Invoke(js.Value(v))
|
f._deleteTexture.Invoke(js.Value(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DeleteVertexArray(a VertexArray) {
|
func (f *Functions) DeleteVertexArray(a VertexArray) {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DepthFunc(fn Enum) {
|
func (f *Functions) DepthFunc(fn Enum) {
|
||||||
f._depthFunc.Invoke(int(fn))
|
f._depthFunc.Invoke(int(fn))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DepthMask(mask bool) {
|
func (f *Functions) DepthMask(mask bool) {
|
||||||
f._depthMask.Invoke(mask)
|
f._depthMask.Invoke(mask)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DisableVertexAttribArray(a Attrib) {
|
func (f *Functions) DisableVertexAttribArray(a Attrib) {
|
||||||
f._disableVertexAttribArray.Invoke(int(a))
|
f._disableVertexAttribArray.Invoke(int(a))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) Disable(cap Enum) {
|
func (f *Functions) Disable(cap Enum) {
|
||||||
f._disable.Invoke(int(cap))
|
f._disable.Invoke(int(cap))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DrawArrays(mode Enum, first, count int) {
|
func (f *Functions) DrawArrays(mode Enum, first, count int) {
|
||||||
f._drawArrays.Invoke(int(mode), first, count)
|
f._drawArrays.Invoke(int(mode), first, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DrawElements(mode Enum, count int, ty Enum, offset int) {
|
func (f *Functions) DrawElements(mode Enum, count int, ty Enum, offset int) {
|
||||||
f._drawElements.Invoke(int(mode), count, int(ty), offset)
|
f._drawElements.Invoke(int(mode), count, int(ty), offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DispatchCompute(x, y, z int) {
|
func (f *Functions) DispatchCompute(x, y, z int) {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) Enable(cap Enum) {
|
func (f *Functions) Enable(cap Enum) {
|
||||||
f._enable.Invoke(int(cap))
|
f._enable.Invoke(int(cap))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) EnableVertexAttribArray(a Attrib) {
|
func (f *Functions) EnableVertexAttribArray(a Attrib) {
|
||||||
f._enableVertexAttribArray.Invoke(int(a))
|
f._enableVertexAttribArray.Invoke(int(a))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) EndQuery(target Enum) {
|
func (f *Functions) EndQuery(target Enum) {
|
||||||
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
||||||
f._endQuery.Invoke(int(target))
|
f._endQuery.Invoke(int(target))
|
||||||
@@ -455,36 +409,28 @@ func (f *Functions) EndQuery(target Enum) {
|
|||||||
f.EXT_disjoint_timer_query.Call("endQueryEXT", int(target))
|
f.EXT_disjoint_timer_query.Call("endQueryEXT", int(target))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) Finish() {
|
func (f *Functions) Finish() {
|
||||||
f._finish.Invoke()
|
f._finish.Invoke()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) Flush() {
|
func (f *Functions) Flush() {
|
||||||
f._flush.Invoke()
|
f._flush.Invoke()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer) {
|
func (f *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer) {
|
||||||
f._framebufferRenderbuffer.Invoke(int(target), int(attachment), int(renderbuffertarget), js.Value(renderbuffer))
|
f._framebufferRenderbuffer.Invoke(int(target), int(attachment), int(renderbuffertarget), js.Value(renderbuffer))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int) {
|
func (f *Functions) FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int) {
|
||||||
f._framebufferTexture2D.Invoke(int(target), int(attachment), int(texTarget), js.Value(t), level)
|
f._framebufferTexture2D.Invoke(int(target), int(attachment), int(texTarget), js.Value(t), level)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GenerateMipmap(target Enum) {
|
func (f *Functions) GenerateMipmap(target Enum) {
|
||||||
f._generateMipmap.Invoke(int(target))
|
f._generateMipmap.Invoke(int(target))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetError() Enum {
|
func (f *Functions) GetError() Enum {
|
||||||
// Avoid slow getError calls. See gio#179.
|
// Avoid slow getError calls. See gio#179.
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetRenderbufferParameteri(target, pname Enum) int {
|
func (f *Functions) GetRenderbufferParameteri(target, pname Enum) int {
|
||||||
return paramVal(f._getRenderbufferParameteri.Invoke(int(pname)))
|
return paramVal(f._getRenderbufferParameteri.Invoke(int(pname)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int {
|
func (f *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int {
|
||||||
if !f.isWebGL2 && pname == FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING {
|
if !f.isWebGL2 && pname == FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING {
|
||||||
// FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING is only available on WebGL 2
|
// FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING is only available on WebGL 2
|
||||||
@@ -492,7 +438,6 @@ func (f *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname
|
|||||||
}
|
}
|
||||||
return paramVal(f._getFramebufferAttachmentParameter.Invoke(int(target), int(attachment), int(pname)))
|
return paramVal(f._getFramebufferAttachmentParameter.Invoke(int(target), int(attachment), int(pname)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetBinding(pname Enum) Object {
|
func (f *Functions) GetBinding(pname Enum) Object {
|
||||||
obj := f._getParameter.Invoke(int(pname))
|
obj := f._getParameter.Invoke(int(pname))
|
||||||
if !obj.Truthy() {
|
if !obj.Truthy() {
|
||||||
@@ -500,7 +445,6 @@ func (f *Functions) GetBinding(pname Enum) Object {
|
|||||||
}
|
}
|
||||||
return Object(obj)
|
return Object(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetBindingi(pname Enum, idx int) Object {
|
func (f *Functions) GetBindingi(pname Enum, idx int) Object {
|
||||||
obj := f._getIndexedParameter.Invoke(int(pname), idx)
|
obj := f._getIndexedParameter.Invoke(int(pname), idx)
|
||||||
if !obj.Truthy() {
|
if !obj.Truthy() {
|
||||||
@@ -508,7 +452,6 @@ func (f *Functions) GetBindingi(pname Enum, idx int) Object {
|
|||||||
}
|
}
|
||||||
return Object(obj)
|
return Object(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetInteger(pname Enum) int {
|
func (f *Functions) GetInteger(pname Enum) int {
|
||||||
if !f.isWebGL2 {
|
if !f.isWebGL2 {
|
||||||
switch pname {
|
switch pname {
|
||||||
@@ -518,11 +461,9 @@ func (f *Functions) GetInteger(pname Enum) int {
|
|||||||
}
|
}
|
||||||
return paramVal(f._getParameter.Invoke(int(pname)))
|
return paramVal(f._getParameter.Invoke(int(pname)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetFloat(pname Enum) float32 {
|
func (f *Functions) GetFloat(pname Enum) float32 {
|
||||||
return float32(f._getParameter.Invoke(int(pname)).Float())
|
return float32(f._getParameter.Invoke(int(pname)).Float())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetInteger4(pname Enum) [4]int {
|
func (f *Functions) GetInteger4(pname Enum) [4]int {
|
||||||
arr := f._getParameter.Invoke(int(pname))
|
arr := f._getParameter.Invoke(int(pname))
|
||||||
var res [4]int
|
var res [4]int
|
||||||
@@ -531,7 +472,6 @@ func (f *Functions) GetInteger4(pname Enum) [4]int {
|
|||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetFloat4(pname Enum) [4]float32 {
|
func (f *Functions) GetFloat4(pname Enum) [4]float32 {
|
||||||
arr := f._getParameter.Invoke(int(pname))
|
arr := f._getParameter.Invoke(int(pname))
|
||||||
var res [4]float32
|
var res [4]float32
|
||||||
@@ -540,15 +480,12 @@ func (f *Functions) GetFloat4(pname Enum) [4]float32 {
|
|||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetProgrami(p Program, pname Enum) int {
|
func (f *Functions) GetProgrami(p Program, pname Enum) int {
|
||||||
return paramVal(f._getProgramParameter.Invoke(js.Value(p), int(pname)))
|
return paramVal(f._getProgramParameter.Invoke(js.Value(p), int(pname)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetProgramInfoLog(p Program) string {
|
func (f *Functions) GetProgramInfoLog(p Program) string {
|
||||||
return f._getProgramInfoLog.Invoke(js.Value(p)).String()
|
return f._getProgramInfoLog.Invoke(js.Value(p)).String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetQueryObjectuiv(query Query, pname Enum) uint {
|
func (f *Functions) GetQueryObjectuiv(query Query, pname Enum) uint {
|
||||||
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
||||||
return uint(paramVal(f._getQueryParameter.Invoke(js.Value(query), int(pname))))
|
return uint(paramVal(f._getQueryParameter.Invoke(js.Value(query), int(pname))))
|
||||||
@@ -556,15 +493,12 @@ func (f *Functions) GetQueryObjectuiv(query Query, pname Enum) uint {
|
|||||||
return uint(paramVal(f.EXT_disjoint_timer_query.Call("getQueryObjectEXT", js.Value(query), int(pname))))
|
return uint(paramVal(f.EXT_disjoint_timer_query.Call("getQueryObjectEXT", js.Value(query), int(pname))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetShaderi(s Shader, pname Enum) int {
|
func (f *Functions) GetShaderi(s Shader, pname Enum) int {
|
||||||
return paramVal(f._getShaderParameter.Invoke(js.Value(s), int(pname)))
|
return paramVal(f._getShaderParameter.Invoke(js.Value(s), int(pname)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetShaderInfoLog(s Shader) string {
|
func (f *Functions) GetShaderInfoLog(s Shader) string {
|
||||||
return f._getShaderInfoLog.Invoke(js.Value(s)).String()
|
return f._getShaderInfoLog.Invoke(js.Value(s)).String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetString(pname Enum) string {
|
func (f *Functions) GetString(pname Enum) string {
|
||||||
switch pname {
|
switch pname {
|
||||||
case EXTENSIONS:
|
case EXTENSIONS:
|
||||||
@@ -578,19 +512,15 @@ func (f *Functions) GetString(pname Enum) string {
|
|||||||
return f._getParameter.Invoke(int(pname)).String()
|
return f._getParameter.Invoke(int(pname)).String()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetUniformBlockIndex(p Program, name string) uint {
|
func (f *Functions) GetUniformBlockIndex(p Program, name string) uint {
|
||||||
return uint(paramVal(f._getUniformBlockIndex.Invoke(js.Value(p), name)))
|
return uint(paramVal(f._getUniformBlockIndex.Invoke(js.Value(p), name)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetUniformLocation(p Program, name string) Uniform {
|
func (f *Functions) GetUniformLocation(p Program, name string) Uniform {
|
||||||
return Uniform(f._getUniformLocation.Invoke(js.Value(p), name))
|
return Uniform(f._getUniformLocation.Invoke(js.Value(p), name))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetVertexAttrib(index int, pname Enum) int {
|
func (f *Functions) GetVertexAttrib(index int, pname Enum) int {
|
||||||
return paramVal(f._getVertexAttrib.Invoke(index, int(pname)))
|
return paramVal(f._getVertexAttrib.Invoke(index, int(pname)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetVertexAttribBinding(index int, pname Enum) Object {
|
func (f *Functions) GetVertexAttribBinding(index int, pname Enum) Object {
|
||||||
obj := f._getVertexAttrib.Invoke(index, int(pname))
|
obj := f._getVertexAttrib.Invoke(index, int(pname))
|
||||||
if !obj.Truthy() {
|
if !obj.Truthy() {
|
||||||
@@ -598,11 +528,9 @@ func (f *Functions) GetVertexAttribBinding(index int, pname Enum) Object {
|
|||||||
}
|
}
|
||||||
return Object(obj)
|
return Object(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetVertexAttribPointer(index int, pname Enum) uintptr {
|
func (f *Functions) GetVertexAttribPointer(index int, pname Enum) uintptr {
|
||||||
return uintptr(f._getVertexAttribOffset.Invoke(index, int(pname)).Int())
|
return uintptr(f._getVertexAttribOffset.Invoke(index, int(pname)).Int())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) InvalidateFramebuffer(target, attachment Enum) {
|
func (f *Functions) InvalidateFramebuffer(target, attachment Enum) {
|
||||||
fn := f.Ctx.Get("invalidateFramebuffer")
|
fn := f.Ctx.Get("invalidateFramebuffer")
|
||||||
if !fn.IsUndefined() {
|
if !fn.IsUndefined() {
|
||||||
@@ -613,97 +541,74 @@ func (f *Functions) InvalidateFramebuffer(target, attachment Enum) {
|
|||||||
f._invalidateFramebuffer.Invoke(int(target), f.int32Buf)
|
f._invalidateFramebuffer.Invoke(int(target), f.int32Buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) IsEnabled(cap Enum) bool {
|
func (f *Functions) IsEnabled(cap Enum) bool {
|
||||||
return f._isEnabled.Invoke(int(cap)).Truthy()
|
return f._isEnabled.Invoke(int(cap)).Truthy()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) LinkProgram(p Program) {
|
func (f *Functions) LinkProgram(p Program) {
|
||||||
f._linkProgram.Invoke(js.Value(p))
|
f._linkProgram.Invoke(js.Value(p))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) PixelStorei(pname Enum, param int) {
|
func (f *Functions) PixelStorei(pname Enum, param int) {
|
||||||
f._pixelStorei.Invoke(int(pname), param)
|
f._pixelStorei.Invoke(int(pname), param)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) MemoryBarrier(barriers Enum) {
|
func (f *Functions) MemoryBarrier(barriers Enum) {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) MapBufferRange(target Enum, offset, length int, access Enum) []byte {
|
func (f *Functions) MapBufferRange(target Enum, offset, length int, access Enum) []byte {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) RenderbufferStorage(target, internalformat Enum, width, height int) {
|
func (f *Functions) RenderbufferStorage(target, internalformat Enum, width, height int) {
|
||||||
f._renderbufferStorage.Invoke(int(target), int(internalformat), width, height)
|
f._renderbufferStorage.Invoke(int(target), int(internalformat), width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) ReadPixels(x, y, width, height int, format, ty Enum, data []byte) {
|
func (f *Functions) ReadPixels(x, y, width, height int, format, ty Enum, data []byte) {
|
||||||
ba := f.byteArrayOf(data)
|
ba := f.byteArrayOf(data)
|
||||||
f._readPixels.Invoke(x, y, width, height, int(format), int(ty), ba)
|
f._readPixels.Invoke(x, y, width, height, int(format), int(ty), ba)
|
||||||
js.CopyBytesToGo(data, ba)
|
js.CopyBytesToGo(data, ba)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) Scissor(x, y, width, height int32) {
|
func (f *Functions) Scissor(x, y, width, height int32) {
|
||||||
f._scissor.Invoke(x, y, width, height)
|
f._scissor.Invoke(x, y, width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) ShaderSource(s Shader, src string) {
|
func (f *Functions) ShaderSource(s Shader, src string) {
|
||||||
f._shaderSource.Invoke(js.Value(s), src)
|
f._shaderSource.Invoke(js.Value(s), src)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) TexImage2D(target Enum, level int, internalFormat Enum, width, height int, format, ty Enum) {
|
func (f *Functions) TexImage2D(target Enum, level int, internalFormat Enum, width, height int, format, ty Enum) {
|
||||||
f._texImage2D.Invoke(int(target), int(level), int(internalFormat), int(width), int(height), 0, int(format), int(ty), nil)
|
f._texImage2D.Invoke(int(target), int(level), int(internalFormat), int(width), int(height), 0, int(format), int(ty), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) TexStorage2D(target Enum, levels int, internalFormat Enum, width, height int) {
|
func (f *Functions) TexStorage2D(target Enum, levels int, internalFormat Enum, width, height int) {
|
||||||
f._texStorage2D.Invoke(int(target), levels, int(internalFormat), width, height)
|
f._texStorage2D.Invoke(int(target), levels, int(internalFormat), width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte) {
|
func (f *Functions) TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte) {
|
||||||
f._texSubImage2D.Invoke(int(target), level, x, y, width, height, int(format), int(ty), f.byteArrayOf(data))
|
f._texSubImage2D.Invoke(int(target), level, x, y, width, height, int(format), int(ty), f.byteArrayOf(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) TexParameteri(target, pname Enum, param int) {
|
func (f *Functions) TexParameteri(target, pname Enum, param int) {
|
||||||
f._texParameteri.Invoke(int(target), int(pname), int(param))
|
f._texParameteri.Invoke(int(target), int(pname), int(param))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) UniformBlockBinding(p Program, uniformBlockIndex uint, uniformBlockBinding uint) {
|
func (f *Functions) UniformBlockBinding(p Program, uniformBlockIndex uint, uniformBlockBinding uint) {
|
||||||
f._uniformBlockBinding.Invoke(js.Value(p), int(uniformBlockIndex), int(uniformBlockBinding))
|
f._uniformBlockBinding.Invoke(js.Value(p), int(uniformBlockIndex), int(uniformBlockBinding))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) Uniform1f(dst Uniform, v float32) {
|
func (f *Functions) Uniform1f(dst Uniform, v float32) {
|
||||||
f._uniform1f.Invoke(js.Value(dst), v)
|
f._uniform1f.Invoke(js.Value(dst), v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) Uniform1i(dst Uniform, v int) {
|
func (f *Functions) Uniform1i(dst Uniform, v int) {
|
||||||
f._uniform1i.Invoke(js.Value(dst), v)
|
f._uniform1i.Invoke(js.Value(dst), v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) Uniform2f(dst Uniform, v0, v1 float32) {
|
func (f *Functions) Uniform2f(dst Uniform, v0, v1 float32) {
|
||||||
f._uniform2f.Invoke(js.Value(dst), v0, v1)
|
f._uniform2f.Invoke(js.Value(dst), v0, v1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) Uniform3f(dst Uniform, v0, v1, v2 float32) {
|
func (f *Functions) Uniform3f(dst Uniform, v0, v1, v2 float32) {
|
||||||
f._uniform3f.Invoke(js.Value(dst), v0, v1, v2)
|
f._uniform3f.Invoke(js.Value(dst), v0, v1, v2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) Uniform4f(dst Uniform, v0, v1, v2, v3 float32) {
|
func (f *Functions) Uniform4f(dst Uniform, v0, v1, v2, v3 float32) {
|
||||||
f._uniform4f.Invoke(js.Value(dst), v0, v1, v2, v3)
|
f._uniform4f.Invoke(js.Value(dst), v0, v1, v2, v3)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) UseProgram(p Program) {
|
func (f *Functions) UseProgram(p Program) {
|
||||||
f._useProgram.Invoke(js.Value(p))
|
f._useProgram.Invoke(js.Value(p))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) UnmapBuffer(target Enum) bool {
|
func (f *Functions) UnmapBuffer(target Enum) bool {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int) {
|
func (f *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int) {
|
||||||
f._vertexAttribPointer.Invoke(int(dst), size, int(ty), normalized, stride, offset)
|
f._vertexAttribPointer.Invoke(int(dst), size, int(ty), normalized, stride, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) Viewport(x, y, width, height int) {
|
func (f *Functions) Viewport(x, y, width, height int) {
|
||||||
f._viewport.Invoke(x, y, width, height)
|
f._viewport.Invoke(x, y, width, height)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -665,7 +665,7 @@ func (f *Functions) load(forceES bool) error {
|
|||||||
case runtime.GOOS == "android":
|
case runtime.GOOS == "android":
|
||||||
libNames = []string{"libGLESv2.so", "libGLESv3.so"}
|
libNames = []string{"libGLESv2.so", "libGLESv3.so"}
|
||||||
default:
|
default:
|
||||||
libNames = []string{"libGLESv2.so.2", "libGLESv2.so.3.0"}
|
libNames = []string{"libGLESv2.so.2"}
|
||||||
}
|
}
|
||||||
for _, lib := range libNames {
|
for _, lib := range libNames {
|
||||||
if h := dlopen(lib); h != nil {
|
if h := dlopen(lib); h != nil {
|
||||||
|
|||||||
+91
-306
@@ -3,217 +3,103 @@
|
|||||||
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 (
|
||||||
glInitOnce sync.Once
|
LibGLESv2 = windows.NewLazyDLL("libGLESv2.dll")
|
||||||
_glActiveTexture *windows.Proc
|
_glActiveTexture = LibGLESv2.NewProc("glActiveTexture")
|
||||||
_glAttachShader *windows.Proc
|
_glAttachShader = LibGLESv2.NewProc("glAttachShader")
|
||||||
_glBeginQuery *windows.Proc
|
_glBeginQuery = LibGLESv2.NewProc("glBeginQuery")
|
||||||
_glBindAttribLocation *windows.Proc
|
_glBindAttribLocation = LibGLESv2.NewProc("glBindAttribLocation")
|
||||||
_glBindBuffer *windows.Proc
|
_glBindBuffer = LibGLESv2.NewProc("glBindBuffer")
|
||||||
_glBindBufferBase *windows.Proc
|
_glBindBufferBase = LibGLESv2.NewProc("glBindBufferBase")
|
||||||
_glBindFramebuffer *windows.Proc
|
_glBindFramebuffer = LibGLESv2.NewProc("glBindFramebuffer")
|
||||||
_glBindRenderbuffer *windows.Proc
|
_glBindRenderbuffer = LibGLESv2.NewProc("glBindRenderbuffer")
|
||||||
_glBindTexture *windows.Proc
|
_glBindTexture = LibGLESv2.NewProc("glBindTexture")
|
||||||
_glBindVertexArray *windows.Proc
|
_glBindVertexArray = LibGLESv2.NewProc("glBindVertexArray")
|
||||||
_glBlendEquation *windows.Proc
|
_glBlendEquation = LibGLESv2.NewProc("glBlendEquation")
|
||||||
_glBlendFuncSeparate *windows.Proc
|
_glBlendFuncSeparate = LibGLESv2.NewProc("glBlendFuncSeparate")
|
||||||
_glBufferData *windows.Proc
|
_glBufferData = LibGLESv2.NewProc("glBufferData")
|
||||||
_glBufferSubData *windows.Proc
|
_glBufferSubData = LibGLESv2.NewProc("glBufferSubData")
|
||||||
_glCheckFramebufferStatus *windows.Proc
|
_glCheckFramebufferStatus = LibGLESv2.NewProc("glCheckFramebufferStatus")
|
||||||
_glClear *windows.Proc
|
_glClear = LibGLESv2.NewProc("glClear")
|
||||||
_glClearColor *windows.Proc
|
_glClearColor = LibGLESv2.NewProc("glClearColor")
|
||||||
_glClearDepthf *windows.Proc
|
_glClearDepthf = LibGLESv2.NewProc("glClearDepthf")
|
||||||
_glDeleteQueries *windows.Proc
|
_glDeleteQueries = LibGLESv2.NewProc("glDeleteQueries")
|
||||||
_glDeleteVertexArrays *windows.Proc
|
_glDeleteVertexArrays = LibGLESv2.NewProc("glDeleteVertexArrays")
|
||||||
_glCompileShader *windows.Proc
|
_glCompileShader = LibGLESv2.NewProc("glCompileShader")
|
||||||
_glCopyTexSubImage2D *windows.Proc
|
_glCopyTexSubImage2D = LibGLESv2.NewProc("glCopyTexSubImage2D")
|
||||||
_glGenerateMipmap *windows.Proc
|
_glGenerateMipmap = LibGLESv2.NewProc("glGenerateMipmap")
|
||||||
_glGenBuffers *windows.Proc
|
_glGenBuffers = LibGLESv2.NewProc("glGenBuffers")
|
||||||
_glGenFramebuffers *windows.Proc
|
_glGenFramebuffers = LibGLESv2.NewProc("glGenFramebuffers")
|
||||||
_glGenVertexArrays *windows.Proc
|
_glGenVertexArrays = LibGLESv2.NewProc("glGenVertexArrays")
|
||||||
_glGetUniformBlockIndex *windows.Proc
|
_glGetUniformBlockIndex = LibGLESv2.NewProc("glGetUniformBlockIndex")
|
||||||
_glCreateProgram *windows.Proc
|
_glCreateProgram = LibGLESv2.NewProc("glCreateProgram")
|
||||||
_glGenRenderbuffers *windows.Proc
|
_glGenRenderbuffers = LibGLESv2.NewProc("glGenRenderbuffers")
|
||||||
_glCreateShader *windows.Proc
|
_glCreateShader = LibGLESv2.NewProc("glCreateShader")
|
||||||
_glGenTextures *windows.Proc
|
_glGenTextures = LibGLESv2.NewProc("glGenTextures")
|
||||||
_glDeleteBuffers *windows.Proc
|
_glDeleteBuffers = LibGLESv2.NewProc("glDeleteBuffers")
|
||||||
_glDeleteFramebuffers *windows.Proc
|
_glDeleteFramebuffers = LibGLESv2.NewProc("glDeleteFramebuffers")
|
||||||
_glDeleteProgram *windows.Proc
|
_glDeleteProgram = LibGLESv2.NewProc("glDeleteProgram")
|
||||||
_glDeleteShader *windows.Proc
|
_glDeleteShader = LibGLESv2.NewProc("glDeleteShader")
|
||||||
_glDeleteRenderbuffers *windows.Proc
|
_glDeleteRenderbuffers = LibGLESv2.NewProc("glDeleteRenderbuffers")
|
||||||
_glDeleteTextures *windows.Proc
|
_glDeleteTextures = LibGLESv2.NewProc("glDeleteTextures")
|
||||||
_glDepthFunc *windows.Proc
|
_glDepthFunc = LibGLESv2.NewProc("glDepthFunc")
|
||||||
_glDepthMask *windows.Proc
|
_glDepthMask = LibGLESv2.NewProc("glDepthMask")
|
||||||
_glDisableVertexAttribArray *windows.Proc
|
_glDisableVertexAttribArray = LibGLESv2.NewProc("glDisableVertexAttribArray")
|
||||||
_glDisable *windows.Proc
|
_glDisable = LibGLESv2.NewProc("glDisable")
|
||||||
_glDrawArrays *windows.Proc
|
_glDrawArrays = LibGLESv2.NewProc("glDrawArrays")
|
||||||
_glDrawElements *windows.Proc
|
_glDrawElements = LibGLESv2.NewProc("glDrawElements")
|
||||||
_glEnable *windows.Proc
|
_glEnable = LibGLESv2.NewProc("glEnable")
|
||||||
_glEnableVertexAttribArray *windows.Proc
|
_glEnableVertexAttribArray = LibGLESv2.NewProc("glEnableVertexAttribArray")
|
||||||
_glEndQuery *windows.Proc
|
_glEndQuery = LibGLESv2.NewProc("glEndQuery")
|
||||||
_glFinish *windows.Proc
|
_glFinish = LibGLESv2.NewProc("glFinish")
|
||||||
_glFlush *windows.Proc
|
_glFlush = LibGLESv2.NewProc("glFlush")
|
||||||
_glFramebufferRenderbuffer *windows.Proc
|
_glFramebufferRenderbuffer = LibGLESv2.NewProc("glFramebufferRenderbuffer")
|
||||||
_glFramebufferTexture2D *windows.Proc
|
_glFramebufferTexture2D = LibGLESv2.NewProc("glFramebufferTexture2D")
|
||||||
_glGenQueries *windows.Proc
|
_glGenQueries = LibGLESv2.NewProc("glGenQueries")
|
||||||
_glGetError *windows.Proc
|
_glGetError = LibGLESv2.NewProc("glGetError")
|
||||||
_glGetRenderbufferParameteriv *windows.Proc
|
_glGetRenderbufferParameteriv = LibGLESv2.NewProc("glGetRenderbufferParameteriv")
|
||||||
_glGetFloatv *windows.Proc
|
_glGetFloatv = LibGLESv2.NewProc("glGetFloatv")
|
||||||
_glGetFramebufferAttachmentParameteriv *windows.Proc
|
_glGetFramebufferAttachmentParameteriv = LibGLESv2.NewProc("glGetFramebufferAttachmentParameteriv")
|
||||||
_glGetIntegerv *windows.Proc
|
_glGetIntegerv = LibGLESv2.NewProc("glGetIntegerv")
|
||||||
_glGetIntegeri_v *windows.Proc
|
_glGetIntegeri_v = LibGLESv2.NewProc("glGetIntegeri_v")
|
||||||
_glGetProgramiv *windows.Proc
|
_glGetProgramiv = LibGLESv2.NewProc("glGetProgramiv")
|
||||||
_glGetProgramInfoLog *windows.Proc
|
_glGetProgramInfoLog = LibGLESv2.NewProc("glGetProgramInfoLog")
|
||||||
_glGetQueryObjectuiv *windows.Proc
|
_glGetQueryObjectuiv = LibGLESv2.NewProc("glGetQueryObjectuiv")
|
||||||
_glGetShaderiv *windows.Proc
|
_glGetShaderiv = LibGLESv2.NewProc("glGetShaderiv")
|
||||||
_glGetShaderInfoLog *windows.Proc
|
_glGetShaderInfoLog = LibGLESv2.NewProc("glGetShaderInfoLog")
|
||||||
_glGetString *windows.Proc
|
_glGetString = LibGLESv2.NewProc("glGetString")
|
||||||
_glGetUniformLocation *windows.Proc
|
_glGetUniformLocation = LibGLESv2.NewProc("glGetUniformLocation")
|
||||||
_glGetVertexAttribiv *windows.Proc
|
_glGetVertexAttribiv = LibGLESv2.NewProc("glGetVertexAttribiv")
|
||||||
_glGetVertexAttribPointerv *windows.Proc
|
_glGetVertexAttribPointerv = LibGLESv2.NewProc("glGetVertexAttribPointerv")
|
||||||
_glInvalidateFramebuffer *windows.Proc
|
_glInvalidateFramebuffer = LibGLESv2.NewProc("glInvalidateFramebuffer")
|
||||||
_glIsEnabled *windows.Proc
|
_glIsEnabled = LibGLESv2.NewProc("glIsEnabled")
|
||||||
_glLinkProgram *windows.Proc
|
_glLinkProgram = LibGLESv2.NewProc("glLinkProgram")
|
||||||
_glPixelStorei *windows.Proc
|
_glPixelStorei = LibGLESv2.NewProc("glPixelStorei")
|
||||||
_glReadPixels *windows.Proc
|
_glReadPixels = LibGLESv2.NewProc("glReadPixels")
|
||||||
_glRenderbufferStorage *windows.Proc
|
_glRenderbufferStorage = LibGLESv2.NewProc("glRenderbufferStorage")
|
||||||
_glScissor *windows.Proc
|
_glScissor = LibGLESv2.NewProc("glScissor")
|
||||||
_glShaderSource *windows.Proc
|
_glShaderSource = LibGLESv2.NewProc("glShaderSource")
|
||||||
_glTexImage2D *windows.Proc
|
_glTexImage2D = LibGLESv2.NewProc("glTexImage2D")
|
||||||
_glTexStorage2D *windows.Proc
|
_glTexStorage2D = LibGLESv2.NewProc("glTexStorage2D")
|
||||||
_glTexSubImage2D *windows.Proc
|
_glTexSubImage2D = LibGLESv2.NewProc("glTexSubImage2D")
|
||||||
_glTexParameteri *windows.Proc
|
_glTexParameteri = LibGLESv2.NewProc("glTexParameteri")
|
||||||
_glUniformBlockBinding *windows.Proc
|
_glUniformBlockBinding = LibGLESv2.NewProc("glUniformBlockBinding")
|
||||||
_glUniform1f *windows.Proc
|
_glUniform1f = LibGLESv2.NewProc("glUniform1f")
|
||||||
_glUniform1i *windows.Proc
|
_glUniform1i = LibGLESv2.NewProc("glUniform1i")
|
||||||
_glUniform2f *windows.Proc
|
_glUniform2f = LibGLESv2.NewProc("glUniform2f")
|
||||||
_glUniform3f *windows.Proc
|
_glUniform3f = LibGLESv2.NewProc("glUniform3f")
|
||||||
_glUniform4f *windows.Proc
|
_glUniform4f = LibGLESv2.NewProc("glUniform4f")
|
||||||
_glUseProgram *windows.Proc
|
_glUseProgram = LibGLESv2.NewProc("glUseProgram")
|
||||||
_glVertexAttribPointer *windows.Proc
|
_glVertexAttribPointer = LibGLESv2.NewProc("glVertexAttribPointer")
|
||||||
_glViewport *windows.Proc
|
_glViewport = LibGLESv2.NewProc("glViewport")
|
||||||
)
|
)
|
||||||
|
|
||||||
type Functions struct {
|
type Functions struct {
|
||||||
@@ -223,74 +109,57 @@ type Functions struct {
|
|||||||
uintptrs [100]uintptr
|
uintptrs [100]uintptr
|
||||||
}
|
}
|
||||||
|
|
||||||
type Context any
|
type Context interface{}
|
||||||
|
|
||||||
func NewFunctions(ctx Context, forceES bool) (*Functions, error) {
|
func NewFunctions(ctx Context, forceES bool) (*Functions, error) {
|
||||||
if ctx != nil {
|
if ctx != nil {
|
||||||
panic("non-nil context")
|
panic("non-nil context")
|
||||||
}
|
}
|
||||||
var err error
|
return new(Functions), nil
|
||||||
glInitOnce.Do(func() {
|
|
||||||
err = loadGLESv2Procs()
|
|
||||||
})
|
|
||||||
return new(Functions), err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) ActiveTexture(t Enum) {
|
func (c *Functions) ActiveTexture(t Enum) {
|
||||||
syscall.Syscall(_glActiveTexture.Addr(), 1, uintptr(t), 0, 0)
|
syscall.Syscall(_glActiveTexture.Addr(), 1, uintptr(t), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) AttachShader(p Program, s Shader) {
|
func (c *Functions) AttachShader(p Program, s Shader) {
|
||||||
syscall.Syscall(_glAttachShader.Addr(), 2, uintptr(p.V), uintptr(s.V), 0)
|
syscall.Syscall(_glAttachShader.Addr(), 2, uintptr(p.V), uintptr(s.V), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BeginQuery(target Enum, query Query) {
|
func (f *Functions) BeginQuery(target Enum, query Query) {
|
||||||
syscall.Syscall(_glBeginQuery.Addr(), 2, uintptr(target), uintptr(query.V), 0)
|
syscall.Syscall(_glBeginQuery.Addr(), 2, uintptr(target), uintptr(query.V), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) BindAttribLocation(p Program, a Attrib, name string) {
|
func (c *Functions) BindAttribLocation(p Program, a Attrib, name string) {
|
||||||
cname := cString(name)
|
cname := cString(name)
|
||||||
c0 := &cname[0]
|
c0 := &cname[0]
|
||||||
syscall.Syscall(_glBindAttribLocation.Addr(), 3, uintptr(p.V), uintptr(a), uintptr(unsafe.Pointer(c0)))
|
syscall.Syscall(_glBindAttribLocation.Addr(), 3, uintptr(p.V), uintptr(a), uintptr(unsafe.Pointer(c0)))
|
||||||
issue34474KeepAlive(c)
|
issue34474KeepAlive(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) BindBuffer(target Enum, b Buffer) {
|
func (c *Functions) BindBuffer(target Enum, b Buffer) {
|
||||||
syscall.Syscall(_glBindBuffer.Addr(), 2, uintptr(target), uintptr(b.V), 0)
|
syscall.Syscall(_glBindBuffer.Addr(), 2, uintptr(target), uintptr(b.V), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) BindBufferBase(target Enum, index int, b Buffer) {
|
func (c *Functions) BindBufferBase(target Enum, index int, b Buffer) {
|
||||||
syscall.Syscall(_glBindBufferBase.Addr(), 3, uintptr(target), uintptr(index), uintptr(b.V))
|
syscall.Syscall(_glBindBufferBase.Addr(), 3, uintptr(target), uintptr(index), uintptr(b.V))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) BindFramebuffer(target Enum, fb Framebuffer) {
|
func (c *Functions) BindFramebuffer(target Enum, fb Framebuffer) {
|
||||||
syscall.Syscall(_glBindFramebuffer.Addr(), 2, uintptr(target), uintptr(fb.V), 0)
|
syscall.Syscall(_glBindFramebuffer.Addr(), 2, uintptr(target), uintptr(fb.V), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) BindRenderbuffer(target Enum, rb Renderbuffer) {
|
func (c *Functions) BindRenderbuffer(target Enum, rb Renderbuffer) {
|
||||||
syscall.Syscall(_glBindRenderbuffer.Addr(), 2, uintptr(target), uintptr(rb.V), 0)
|
syscall.Syscall(_glBindRenderbuffer.Addr(), 2, uintptr(target), uintptr(rb.V), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BindImageTexture(unit int, t Texture, level int, layered bool, layer int, access, format Enum) {
|
func (f *Functions) BindImageTexture(unit int, t Texture, level int, layered bool, layer int, access, format Enum) {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) BindTexture(target Enum, t Texture) {
|
func (c *Functions) BindTexture(target Enum, t Texture) {
|
||||||
syscall.Syscall(_glBindTexture.Addr(), 2, uintptr(target), uintptr(t.V), 0)
|
syscall.Syscall(_glBindTexture.Addr(), 2, uintptr(target), uintptr(t.V), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) BindVertexArray(a VertexArray) {
|
func (c *Functions) BindVertexArray(a VertexArray) {
|
||||||
syscall.Syscall(_glBindVertexArray.Addr(), 1, uintptr(a.V), 0, 0)
|
syscall.Syscall(_glBindVertexArray.Addr(), 1, uintptr(a.V), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) BlendEquation(mode Enum) {
|
func (c *Functions) BlendEquation(mode Enum) {
|
||||||
syscall.Syscall(_glBlendEquation.Addr(), 1, uintptr(mode), 0, 0)
|
syscall.Syscall(_glBlendEquation.Addr(), 1, uintptr(mode), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) BlendFuncSeparate(srcRGB, dstRGB, srcA, dstA Enum) {
|
func (c *Functions) BlendFuncSeparate(srcRGB, dstRGB, srcA, dstA Enum) {
|
||||||
syscall.Syscall6(_glBlendFuncSeparate.Addr(), 4, uintptr(srcRGB), uintptr(dstRGB), uintptr(srcA), uintptr(dstA), 0, 0)
|
syscall.Syscall6(_glBlendFuncSeparate.Addr(), 4, uintptr(srcRGB), uintptr(dstRGB), uintptr(srcA), uintptr(dstA), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) BufferData(target Enum, size int, usage Enum, data []byte) {
|
func (c *Functions) BufferData(target Enum, size int, usage Enum, data []byte) {
|
||||||
var p unsafe.Pointer
|
var p unsafe.Pointer
|
||||||
if len(data) > 0 {
|
if len(data) > 0 {
|
||||||
@@ -298,7 +167,6 @@ func (c *Functions) BufferData(target Enum, size int, usage Enum, data []byte) {
|
|||||||
}
|
}
|
||||||
syscall.Syscall6(_glBufferData.Addr(), 4, uintptr(target), uintptr(size), uintptr(p), uintptr(usage), 0, 0)
|
syscall.Syscall6(_glBufferData.Addr(), 4, uintptr(target), uintptr(size), uintptr(p), uintptr(usage), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
|
func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
|
||||||
if n := len(src); n > 0 {
|
if n := len(src); n > 0 {
|
||||||
s0 := &src[0]
|
s0 := &src[0]
|
||||||
@@ -306,118 +174,93 @@ func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
|
|||||||
issue34474KeepAlive(s0)
|
issue34474KeepAlive(s0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) CheckFramebufferStatus(target Enum) Enum {
|
func (c *Functions) CheckFramebufferStatus(target Enum) Enum {
|
||||||
s, _, _ := syscall.Syscall(_glCheckFramebufferStatus.Addr(), 1, uintptr(target), 0, 0)
|
s, _, _ := syscall.Syscall(_glCheckFramebufferStatus.Addr(), 1, uintptr(target), 0, 0)
|
||||||
return Enum(s)
|
return Enum(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) Clear(mask Enum) {
|
func (c *Functions) Clear(mask Enum) {
|
||||||
syscall.Syscall(_glClear.Addr(), 1, uintptr(mask), 0, 0)
|
syscall.Syscall(_glClear.Addr(), 1, uintptr(mask), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) ClearColor(red, green, blue, alpha float32) {
|
func (c *Functions) ClearColor(red, green, blue, alpha float32) {
|
||||||
syscall.Syscall6(_glClearColor.Addr(), 4, uintptr(math.Float32bits(red)), uintptr(math.Float32bits(green)), uintptr(math.Float32bits(blue)), uintptr(math.Float32bits(alpha)), 0, 0)
|
syscall.Syscall6(_glClearColor.Addr(), 4, uintptr(math.Float32bits(red)), uintptr(math.Float32bits(green)), uintptr(math.Float32bits(blue)), uintptr(math.Float32bits(alpha)), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) ClearDepthf(d float32) {
|
func (c *Functions) ClearDepthf(d float32) {
|
||||||
syscall.Syscall(_glClearDepthf.Addr(), 1, uintptr(math.Float32bits(d)), 0, 0)
|
syscall.Syscall(_glClearDepthf.Addr(), 1, uintptr(math.Float32bits(d)), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) CompileShader(s Shader) {
|
func (c *Functions) CompileShader(s Shader) {
|
||||||
syscall.Syscall(_glCompileShader.Addr(), 1, uintptr(s.V), 0, 0)
|
syscall.Syscall(_glCompileShader.Addr(), 1, uintptr(s.V), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) CopyTexSubImage2D(target Enum, level, xoffset, yoffset, x, y, width, height int) {
|
func (f *Functions) CopyTexSubImage2D(target Enum, level, xoffset, yoffset, x, y, width, height int) {
|
||||||
syscall.Syscall9(_glCopyTexSubImage2D.Addr(), 8, uintptr(target), uintptr(level), uintptr(xoffset), uintptr(yoffset), uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0)
|
syscall.Syscall9(_glCopyTexSubImage2D.Addr(), 8, uintptr(target), uintptr(level), uintptr(xoffset), uintptr(yoffset), uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GenerateMipmap(target Enum) {
|
func (f *Functions) GenerateMipmap(target Enum) {
|
||||||
syscall.Syscall(_glGenerateMipmap.Addr(), 1, uintptr(target), 0, 0)
|
syscall.Syscall(_glGenerateMipmap.Addr(), 1, uintptr(target), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) CreateBuffer() Buffer {
|
func (c *Functions) CreateBuffer() Buffer {
|
||||||
var buf uintptr
|
var buf uintptr
|
||||||
syscall.Syscall(_glGenBuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&buf)), 0)
|
syscall.Syscall(_glGenBuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&buf)), 0)
|
||||||
return Buffer{uint(buf)}
|
return Buffer{uint(buf)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) CreateFramebuffer() Framebuffer {
|
func (c *Functions) CreateFramebuffer() Framebuffer {
|
||||||
var fb uintptr
|
var fb uintptr
|
||||||
syscall.Syscall(_glGenFramebuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&fb)), 0)
|
syscall.Syscall(_glGenFramebuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&fb)), 0)
|
||||||
return Framebuffer{uint(fb)}
|
return Framebuffer{uint(fb)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) CreateProgram() Program {
|
func (c *Functions) CreateProgram() Program {
|
||||||
p, _, _ := syscall.Syscall(_glCreateProgram.Addr(), 0, 0, 0, 0)
|
p, _, _ := syscall.Syscall(_glCreateProgram.Addr(), 0, 0, 0, 0)
|
||||||
return Program{uint(p)}
|
return Program{uint(p)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) CreateQuery() Query {
|
func (f *Functions) CreateQuery() Query {
|
||||||
var q uintptr
|
var q uintptr
|
||||||
syscall.Syscall(_glGenQueries.Addr(), 2, 1, uintptr(unsafe.Pointer(&q)), 0)
|
syscall.Syscall(_glGenQueries.Addr(), 2, 1, uintptr(unsafe.Pointer(&q)), 0)
|
||||||
return Query{uint(q)}
|
return Query{uint(q)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) CreateRenderbuffer() Renderbuffer {
|
func (c *Functions) CreateRenderbuffer() Renderbuffer {
|
||||||
var rb uintptr
|
var rb uintptr
|
||||||
syscall.Syscall(_glGenRenderbuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&rb)), 0)
|
syscall.Syscall(_glGenRenderbuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&rb)), 0)
|
||||||
return Renderbuffer{uint(rb)}
|
return Renderbuffer{uint(rb)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) CreateShader(ty Enum) Shader {
|
func (c *Functions) CreateShader(ty Enum) Shader {
|
||||||
s, _, _ := syscall.Syscall(_glCreateShader.Addr(), 1, uintptr(ty), 0, 0)
|
s, _, _ := syscall.Syscall(_glCreateShader.Addr(), 1, uintptr(ty), 0, 0)
|
||||||
return Shader{uint(s)}
|
return Shader{uint(s)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) CreateTexture() Texture {
|
func (c *Functions) CreateTexture() Texture {
|
||||||
var t uintptr
|
var t uintptr
|
||||||
syscall.Syscall(_glGenTextures.Addr(), 2, 1, uintptr(unsafe.Pointer(&t)), 0)
|
syscall.Syscall(_glGenTextures.Addr(), 2, 1, uintptr(unsafe.Pointer(&t)), 0)
|
||||||
return Texture{uint(t)}
|
return Texture{uint(t)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) CreateVertexArray() VertexArray {
|
func (c *Functions) CreateVertexArray() VertexArray {
|
||||||
var t uintptr
|
var t uintptr
|
||||||
syscall.Syscall(_glGenVertexArrays.Addr(), 2, 1, uintptr(unsafe.Pointer(&t)), 0)
|
syscall.Syscall(_glGenVertexArrays.Addr(), 2, 1, uintptr(unsafe.Pointer(&t)), 0)
|
||||||
return VertexArray{uint(t)}
|
return VertexArray{uint(t)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) DeleteBuffer(v Buffer) {
|
func (c *Functions) DeleteBuffer(v Buffer) {
|
||||||
syscall.Syscall(_glDeleteBuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v)), 0)
|
syscall.Syscall(_glDeleteBuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v)), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) DeleteFramebuffer(v Framebuffer) {
|
func (c *Functions) DeleteFramebuffer(v Framebuffer) {
|
||||||
syscall.Syscall(_glDeleteFramebuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
|
syscall.Syscall(_glDeleteFramebuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) DeleteProgram(p Program) {
|
func (c *Functions) DeleteProgram(p Program) {
|
||||||
syscall.Syscall(_glDeleteProgram.Addr(), 1, uintptr(p.V), 0, 0)
|
syscall.Syscall(_glDeleteProgram.Addr(), 1, uintptr(p.V), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DeleteQuery(query Query) {
|
func (f *Functions) DeleteQuery(query Query) {
|
||||||
syscall.Syscall(_glDeleteQueries.Addr(), 2, 1, uintptr(unsafe.Pointer(&query.V)), 0)
|
syscall.Syscall(_glDeleteQueries.Addr(), 2, 1, uintptr(unsafe.Pointer(&query.V)), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) DeleteShader(s Shader) {
|
func (c *Functions) DeleteShader(s Shader) {
|
||||||
syscall.Syscall(_glDeleteShader.Addr(), 1, uintptr(s.V), 0, 0)
|
syscall.Syscall(_glDeleteShader.Addr(), 1, uintptr(s.V), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) DeleteRenderbuffer(v Renderbuffer) {
|
func (c *Functions) DeleteRenderbuffer(v Renderbuffer) {
|
||||||
syscall.Syscall(_glDeleteRenderbuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
|
syscall.Syscall(_glDeleteRenderbuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) DeleteTexture(v Texture) {
|
func (c *Functions) DeleteTexture(v Texture) {
|
||||||
syscall.Syscall(_glDeleteTextures.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
|
syscall.Syscall(_glDeleteTextures.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DeleteVertexArray(array VertexArray) {
|
func (f *Functions) DeleteVertexArray(array VertexArray) {
|
||||||
syscall.Syscall(_glDeleteVertexArrays.Addr(), 2, 1, uintptr(unsafe.Pointer(&array.V)), 0)
|
syscall.Syscall(_glDeleteVertexArrays.Addr(), 2, 1, uintptr(unsafe.Pointer(&array.V)), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) DepthFunc(f Enum) {
|
func (c *Functions) DepthFunc(f Enum) {
|
||||||
syscall.Syscall(_glDepthFunc.Addr(), 1, uintptr(f), 0, 0)
|
syscall.Syscall(_glDepthFunc.Addr(), 1, uintptr(f), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) DepthMask(mask bool) {
|
func (c *Functions) DepthMask(mask bool) {
|
||||||
var m uintptr
|
var m uintptr
|
||||||
if mask {
|
if mask {
|
||||||
@@ -425,55 +268,42 @@ func (c *Functions) DepthMask(mask bool) {
|
|||||||
}
|
}
|
||||||
syscall.Syscall(_glDepthMask.Addr(), 1, m, 0, 0)
|
syscall.Syscall(_glDepthMask.Addr(), 1, m, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) DisableVertexAttribArray(a Attrib) {
|
func (c *Functions) DisableVertexAttribArray(a Attrib) {
|
||||||
syscall.Syscall(_glDisableVertexAttribArray.Addr(), 1, uintptr(a), 0, 0)
|
syscall.Syscall(_glDisableVertexAttribArray.Addr(), 1, uintptr(a), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) Disable(cap Enum) {
|
func (c *Functions) Disable(cap Enum) {
|
||||||
syscall.Syscall(_glDisable.Addr(), 1, uintptr(cap), 0, 0)
|
syscall.Syscall(_glDisable.Addr(), 1, uintptr(cap), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) DrawArrays(mode Enum, first, count int) {
|
func (c *Functions) DrawArrays(mode Enum, first, count int) {
|
||||||
syscall.Syscall(_glDrawArrays.Addr(), 3, uintptr(mode), uintptr(first), uintptr(count))
|
syscall.Syscall(_glDrawArrays.Addr(), 3, uintptr(mode), uintptr(first), uintptr(count))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) DrawElements(mode Enum, count int, ty Enum, offset int) {
|
func (c *Functions) DrawElements(mode Enum, count int, ty Enum, offset int) {
|
||||||
syscall.Syscall6(_glDrawElements.Addr(), 4, uintptr(mode), uintptr(count), uintptr(ty), uintptr(offset), 0, 0)
|
syscall.Syscall6(_glDrawElements.Addr(), 4, uintptr(mode), uintptr(count), uintptr(ty), uintptr(offset), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DispatchCompute(x, y, z int) {
|
func (f *Functions) DispatchCompute(x, y, z int) {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) Enable(cap Enum) {
|
func (c *Functions) Enable(cap Enum) {
|
||||||
syscall.Syscall(_glEnable.Addr(), 1, uintptr(cap), 0, 0)
|
syscall.Syscall(_glEnable.Addr(), 1, uintptr(cap), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) EnableVertexAttribArray(a Attrib) {
|
func (c *Functions) EnableVertexAttribArray(a Attrib) {
|
||||||
syscall.Syscall(_glEnableVertexAttribArray.Addr(), 1, uintptr(a), 0, 0)
|
syscall.Syscall(_glEnableVertexAttribArray.Addr(), 1, uintptr(a), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) EndQuery(target Enum) {
|
func (f *Functions) EndQuery(target Enum) {
|
||||||
syscall.Syscall(_glEndQuery.Addr(), 1, uintptr(target), 0, 0)
|
syscall.Syscall(_glEndQuery.Addr(), 1, uintptr(target), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) Finish() {
|
func (c *Functions) Finish() {
|
||||||
syscall.Syscall(_glFinish.Addr(), 0, 0, 0, 0)
|
syscall.Syscall(_glFinish.Addr(), 0, 0, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) Flush() {
|
func (c *Functions) Flush() {
|
||||||
syscall.Syscall(_glFlush.Addr(), 0, 0, 0, 0)
|
syscall.Syscall(_glFlush.Addr(), 0, 0, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer) {
|
func (c *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer) {
|
||||||
syscall.Syscall6(_glFramebufferRenderbuffer.Addr(), 4, uintptr(target), uintptr(attachment), uintptr(renderbuffertarget), uintptr(renderbuffer.V), 0, 0)
|
syscall.Syscall6(_glFramebufferRenderbuffer.Addr(), 4, uintptr(target), uintptr(attachment), uintptr(renderbuffertarget), uintptr(renderbuffer.V), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int) {
|
func (c *Functions) FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int) {
|
||||||
syscall.Syscall6(_glFramebufferTexture2D.Addr(), 5, uintptr(target), uintptr(attachment), uintptr(texTarget), uintptr(t.V), uintptr(level), 0)
|
syscall.Syscall6(_glFramebufferTexture2D.Addr(), 5, uintptr(target), uintptr(attachment), uintptr(texTarget), uintptr(t.V), uintptr(level), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetUniformBlockIndex(p Program, name string) uint {
|
func (f *Functions) GetUniformBlockIndex(p Program, name string) uint {
|
||||||
cname := cString(name)
|
cname := cString(name)
|
||||||
c0 := &cname[0]
|
c0 := &cname[0]
|
||||||
@@ -481,30 +311,24 @@ func (f *Functions) GetUniformBlockIndex(p Program, name string) uint {
|
|||||||
issue34474KeepAlive(c0)
|
issue34474KeepAlive(c0)
|
||||||
return uint(u)
|
return uint(u)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetBinding(pname Enum) Object {
|
func (c *Functions) GetBinding(pname Enum) Object {
|
||||||
return Object{uint(c.GetInteger(pname))}
|
return Object{uint(c.GetInteger(pname))}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetBindingi(pname Enum, idx int) Object {
|
func (c *Functions) GetBindingi(pname Enum, idx int) Object {
|
||||||
return Object{uint(c.GetIntegeri(pname, idx))}
|
return Object{uint(c.GetIntegeri(pname, idx))}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetError() Enum {
|
func (c *Functions) GetError() Enum {
|
||||||
e, _, _ := syscall.Syscall(_glGetError.Addr(), 0, 0, 0, 0)
|
e, _, _ := syscall.Syscall(_glGetError.Addr(), 0, 0, 0, 0)
|
||||||
return Enum(e)
|
return Enum(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetRenderbufferParameteri(target, pname Enum) int {
|
func (c *Functions) GetRenderbufferParameteri(target, pname Enum) int {
|
||||||
syscall.Syscall(_glGetRenderbufferParameteriv.Addr(), 3, uintptr(target), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
syscall.Syscall(_glGetRenderbufferParameteriv.Addr(), 3, uintptr(target), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
||||||
return int(c.int32s[0])
|
return int(c.int32s[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int {
|
func (c *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int {
|
||||||
syscall.Syscall6(_glGetFramebufferAttachmentParameteriv.Addr(), 4, uintptr(target), uintptr(attachment), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0, 0)
|
syscall.Syscall6(_glGetFramebufferAttachmentParameteriv.Addr(), 4, uintptr(target), uintptr(attachment), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0, 0)
|
||||||
return int(c.int32s[0])
|
return int(c.int32s[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetInteger4(pname Enum) [4]int {
|
func (c *Functions) GetInteger4(pname Enum) [4]int {
|
||||||
syscall.Syscall(_glGetIntegerv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0)
|
syscall.Syscall(_glGetIntegerv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0)
|
||||||
var r [4]int
|
var r [4]int
|
||||||
@@ -513,66 +337,52 @@ func (c *Functions) GetInteger4(pname Enum) [4]int {
|
|||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetInteger(pname Enum) int {
|
func (c *Functions) GetInteger(pname Enum) int {
|
||||||
syscall.Syscall(_glGetIntegerv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0)
|
syscall.Syscall(_glGetIntegerv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0)
|
||||||
return int(c.int32s[0])
|
return int(c.int32s[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetIntegeri(pname Enum, idx int) int {
|
func (c *Functions) GetIntegeri(pname Enum, idx int) int {
|
||||||
syscall.Syscall(_glGetIntegeri_v.Addr(), 3, uintptr(pname), uintptr(idx), uintptr(unsafe.Pointer(&c.int32s[0])))
|
syscall.Syscall(_glGetIntegeri_v.Addr(), 3, uintptr(pname), uintptr(idx), uintptr(unsafe.Pointer(&c.int32s[0])))
|
||||||
return int(c.int32s[0])
|
return int(c.int32s[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetFloat(pname Enum) float32 {
|
func (c *Functions) GetFloat(pname Enum) float32 {
|
||||||
syscall.Syscall(_glGetFloatv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.float32s[0])), 0)
|
syscall.Syscall(_glGetFloatv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.float32s[0])), 0)
|
||||||
return c.float32s[0]
|
return c.float32s[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetFloat4(pname Enum) [4]float32 {
|
func (c *Functions) GetFloat4(pname Enum) [4]float32 {
|
||||||
syscall.Syscall(_glGetFloatv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.float32s[0])), 0)
|
syscall.Syscall(_glGetFloatv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.float32s[0])), 0)
|
||||||
var r [4]float32
|
var r [4]float32
|
||||||
copy(r[:], c.float32s[:])
|
copy(r[:], c.float32s[:])
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetProgrami(p Program, pname Enum) int {
|
func (c *Functions) GetProgrami(p Program, pname Enum) int {
|
||||||
syscall.Syscall(_glGetProgramiv.Addr(), 3, uintptr(p.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
syscall.Syscall(_glGetProgramiv.Addr(), 3, uintptr(p.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
||||||
return int(c.int32s[0])
|
return int(c.int32s[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetQueryObjectuiv(query Query, pname Enum) uint {
|
func (c *Functions) GetQueryObjectuiv(query Query, pname Enum) uint {
|
||||||
syscall.Syscall(_glGetQueryObjectuiv.Addr(), 3, uintptr(query.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
syscall.Syscall(_glGetQueryObjectuiv.Addr(), 3, uintptr(query.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
||||||
return uint(c.int32s[0])
|
return uint(c.int32s[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetShaderi(s Shader, pname Enum) int {
|
func (c *Functions) GetShaderi(s Shader, pname Enum) int {
|
||||||
syscall.Syscall(_glGetShaderiv.Addr(), 3, uintptr(s.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
syscall.Syscall(_glGetShaderiv.Addr(), 3, uintptr(s.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
||||||
return int(c.int32s[0])
|
return int(c.int32s[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetShaderInfoLog(s Shader) string {
|
func (c *Functions) GetShaderInfoLog(s Shader) string {
|
||||||
n := c.GetShaderi(s, INFO_LOG_LENGTH)
|
n := c.GetShaderi(s, INFO_LOG_LENGTH)
|
||||||
buf := make([]byte, n)
|
buf := make([]byte, n)
|
||||||
syscall.Syscall6(_glGetShaderInfoLog.Addr(), 4, uintptr(s.V), uintptr(len(buf)), 0, uintptr(unsafe.Pointer(&buf[0])), 0, 0)
|
syscall.Syscall6(_glGetShaderInfoLog.Addr(), 4, uintptr(s.V), uintptr(len(buf)), 0, uintptr(unsafe.Pointer(&buf[0])), 0, 0)
|
||||||
return string(buf)
|
return string(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetString(pname Enum) string {
|
func (c *Functions) GetString(pname Enum) string {
|
||||||
s, _, _ := syscall.Syscall(_glGetString.Addr(), 1, uintptr(pname), 0, 0)
|
s, _, _ := syscall.Syscall(_glGetString.Addr(), 1, uintptr(pname), 0, 0)
|
||||||
return windows.BytePtrToString((*byte)(unsafe.Pointer(s)))
|
return windows.BytePtrToString((*byte)(unsafe.Pointer(s)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetUniformLocation(p Program, name string) Uniform {
|
func (c *Functions) GetUniformLocation(p Program, name string) Uniform {
|
||||||
cname := cString(name)
|
cname := cString(name)
|
||||||
c0 := &cname[0]
|
c0 := &cname[0]
|
||||||
@@ -580,7 +390,6 @@ func (c *Functions) GetUniformLocation(p Program, name string) Uniform {
|
|||||||
issue34474KeepAlive(c0)
|
issue34474KeepAlive(c0)
|
||||||
return Uniform{int(u)}
|
return Uniform{int(u)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetVertexAttrib(index int, pname Enum) int {
|
func (c *Functions) GetVertexAttrib(index int, pname Enum) int {
|
||||||
syscall.Syscall(_glGetVertexAttribiv.Addr(), 3, uintptr(index), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
syscall.Syscall(_glGetVertexAttribiv.Addr(), 3, uintptr(index), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
||||||
return int(c.int32s[0])
|
return int(c.int32s[0])
|
||||||
@@ -594,7 +403,6 @@ func (c *Functions) GetVertexAttribPointer(index int, pname Enum) uintptr {
|
|||||||
syscall.Syscall(_glGetVertexAttribPointerv.Addr(), 3, uintptr(index), uintptr(pname), uintptr(unsafe.Pointer(&c.uintptrs[0])))
|
syscall.Syscall(_glGetVertexAttribPointerv.Addr(), 3, uintptr(index), uintptr(pname), uintptr(unsafe.Pointer(&c.uintptrs[0])))
|
||||||
return c.uintptrs[0]
|
return c.uintptrs[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) InvalidateFramebuffer(target, attachment Enum) {
|
func (c *Functions) InvalidateFramebuffer(target, attachment Enum) {
|
||||||
addr := _glInvalidateFramebuffer.Addr()
|
addr := _glInvalidateFramebuffer.Addr()
|
||||||
if addr == 0 {
|
if addr == 0 {
|
||||||
@@ -603,99 +411,77 @@ func (c *Functions) InvalidateFramebuffer(target, attachment Enum) {
|
|||||||
}
|
}
|
||||||
syscall.Syscall(addr, 3, uintptr(target), 1, uintptr(unsafe.Pointer(&attachment)))
|
syscall.Syscall(addr, 3, uintptr(target), 1, uintptr(unsafe.Pointer(&attachment)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) IsEnabled(cap Enum) bool {
|
func (f *Functions) IsEnabled(cap Enum) bool {
|
||||||
u, _, _ := syscall.Syscall(_glIsEnabled.Addr(), 1, uintptr(cap), 0, 0)
|
u, _, _ := syscall.Syscall(_glIsEnabled.Addr(), 1, uintptr(cap), 0, 0)
|
||||||
return u == TRUE
|
return u == TRUE
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) LinkProgram(p Program) {
|
func (c *Functions) LinkProgram(p Program) {
|
||||||
syscall.Syscall(_glLinkProgram.Addr(), 1, uintptr(p.V), 0, 0)
|
syscall.Syscall(_glLinkProgram.Addr(), 1, uintptr(p.V), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) PixelStorei(pname Enum, param int) {
|
func (c *Functions) PixelStorei(pname Enum, param int) {
|
||||||
syscall.Syscall(_glPixelStorei.Addr(), 2, uintptr(pname), uintptr(param), 0)
|
syscall.Syscall(_glPixelStorei.Addr(), 2, uintptr(pname), uintptr(param), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) MemoryBarrier(barriers Enum) {
|
func (f *Functions) MemoryBarrier(barriers Enum) {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) MapBufferRange(target Enum, offset, length int, access Enum) []byte {
|
func (f *Functions) MapBufferRange(target Enum, offset, length int, access Enum) []byte {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) ReadPixels(x, y, width, height int, format, ty Enum, data []byte) {
|
func (f *Functions) ReadPixels(x, y, width, height int, format, ty Enum, data []byte) {
|
||||||
d0 := &data[0]
|
d0 := &data[0]
|
||||||
syscall.Syscall9(_glReadPixels.Addr(), 7, uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(format), uintptr(ty), uintptr(unsafe.Pointer(d0)), 0, 0)
|
syscall.Syscall9(_glReadPixels.Addr(), 7, uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(format), uintptr(ty), uintptr(unsafe.Pointer(d0)), 0, 0)
|
||||||
issue34474KeepAlive(d0)
|
issue34474KeepAlive(d0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) RenderbufferStorage(target, internalformat Enum, width, height int) {
|
func (c *Functions) RenderbufferStorage(target, internalformat Enum, width, height int) {
|
||||||
syscall.Syscall6(_glRenderbufferStorage.Addr(), 4, uintptr(target), uintptr(internalformat), uintptr(width), uintptr(height), 0, 0)
|
syscall.Syscall6(_glRenderbufferStorage.Addr(), 4, uintptr(target), uintptr(internalformat), uintptr(width), uintptr(height), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) Scissor(x, y, width, height int32) {
|
func (c *Functions) Scissor(x, y, width, height int32) {
|
||||||
syscall.Syscall6(_glScissor.Addr(), 4, uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0, 0)
|
syscall.Syscall6(_glScissor.Addr(), 4, uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) ShaderSource(s Shader, src string) {
|
func (c *Functions) ShaderSource(s Shader, src string) {
|
||||||
var n uintptr = uintptr(len(src))
|
var n uintptr = uintptr(len(src))
|
||||||
psrc := &src
|
psrc := &src
|
||||||
syscall.Syscall6(_glShaderSource.Addr(), 4, uintptr(s.V), 1, uintptr(unsafe.Pointer(psrc)), uintptr(unsafe.Pointer(&n)), 0, 0)
|
syscall.Syscall6(_glShaderSource.Addr(), 4, uintptr(s.V), 1, uintptr(unsafe.Pointer(psrc)), uintptr(unsafe.Pointer(&n)), 0, 0)
|
||||||
issue34474KeepAlive(psrc)
|
issue34474KeepAlive(psrc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) TexImage2D(target Enum, level int, internalFormat Enum, width int, height int, format Enum, ty Enum) {
|
func (f *Functions) TexImage2D(target Enum, level int, internalFormat Enum, width int, height int, format Enum, ty Enum) {
|
||||||
syscall.Syscall9(_glTexImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(internalFormat), uintptr(width), uintptr(height), 0, uintptr(format), uintptr(ty), 0)
|
syscall.Syscall9(_glTexImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(internalFormat), uintptr(width), uintptr(height), 0, uintptr(format), uintptr(ty), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) TexStorage2D(target Enum, levels int, internalFormat Enum, width, height int) {
|
func (f *Functions) TexStorage2D(target Enum, levels int, internalFormat Enum, width, height int) {
|
||||||
syscall.Syscall6(_glTexStorage2D.Addr(), 5, uintptr(target), uintptr(levels), uintptr(internalFormat), uintptr(width), uintptr(height), 0)
|
syscall.Syscall6(_glTexStorage2D.Addr(), 5, uintptr(target), uintptr(levels), uintptr(internalFormat), uintptr(width), uintptr(height), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte) {
|
func (c *Functions) TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte) {
|
||||||
d0 := &data[0]
|
d0 := &data[0]
|
||||||
syscall.Syscall9(_glTexSubImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(format), uintptr(ty), uintptr(unsafe.Pointer(d0)))
|
syscall.Syscall9(_glTexSubImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(format), uintptr(ty), uintptr(unsafe.Pointer(d0)))
|
||||||
issue34474KeepAlive(d0)
|
issue34474KeepAlive(d0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) TexParameteri(target, pname Enum, param int) {
|
func (c *Functions) TexParameteri(target, pname Enum, param int) {
|
||||||
syscall.Syscall(_glTexParameteri.Addr(), 3, uintptr(target), uintptr(pname), uintptr(param))
|
syscall.Syscall(_glTexParameteri.Addr(), 3, uintptr(target), uintptr(pname), uintptr(param))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) UniformBlockBinding(p Program, uniformBlockIndex uint, uniformBlockBinding uint) {
|
func (f *Functions) UniformBlockBinding(p Program, uniformBlockIndex uint, uniformBlockBinding uint) {
|
||||||
syscall.Syscall(_glUniformBlockBinding.Addr(), 3, uintptr(p.V), uintptr(uniformBlockIndex), uintptr(uniformBlockBinding))
|
syscall.Syscall(_glUniformBlockBinding.Addr(), 3, uintptr(p.V), uintptr(uniformBlockIndex), uintptr(uniformBlockBinding))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) Uniform1f(dst Uniform, v float32) {
|
func (c *Functions) Uniform1f(dst Uniform, v float32) {
|
||||||
syscall.Syscall(_glUniform1f.Addr(), 2, uintptr(dst.V), uintptr(math.Float32bits(v)), 0)
|
syscall.Syscall(_glUniform1f.Addr(), 2, uintptr(dst.V), uintptr(math.Float32bits(v)), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) Uniform1i(dst Uniform, v int) {
|
func (c *Functions) Uniform1i(dst Uniform, v int) {
|
||||||
syscall.Syscall(_glUniform1i.Addr(), 2, uintptr(dst.V), uintptr(v), 0)
|
syscall.Syscall(_glUniform1i.Addr(), 2, uintptr(dst.V), uintptr(v), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) Uniform2f(dst Uniform, v0, v1 float32) {
|
func (c *Functions) Uniform2f(dst Uniform, v0, v1 float32) {
|
||||||
syscall.Syscall(_glUniform2f.Addr(), 3, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)))
|
syscall.Syscall(_glUniform2f.Addr(), 3, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) Uniform3f(dst Uniform, v0, v1, v2 float32) {
|
func (c *Functions) Uniform3f(dst Uniform, v0, v1, v2 float32) {
|
||||||
syscall.Syscall6(_glUniform3f.Addr(), 4, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)), uintptr(math.Float32bits(v2)), 0, 0)
|
syscall.Syscall6(_glUniform3f.Addr(), 4, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)), uintptr(math.Float32bits(v2)), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) Uniform4f(dst Uniform, v0, v1, v2, v3 float32) {
|
func (c *Functions) Uniform4f(dst Uniform, v0, v1, v2, v3 float32) {
|
||||||
syscall.Syscall6(_glUniform4f.Addr(), 5, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)), uintptr(math.Float32bits(v2)), uintptr(math.Float32bits(v3)), 0)
|
syscall.Syscall6(_glUniform4f.Addr(), 5, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)), uintptr(math.Float32bits(v2)), uintptr(math.Float32bits(v3)), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) UseProgram(p Program) {
|
func (c *Functions) UseProgram(p Program) {
|
||||||
syscall.Syscall(_glUseProgram.Addr(), 1, uintptr(p.V), 0, 0)
|
syscall.Syscall(_glUseProgram.Addr(), 1, uintptr(p.V), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) UnmapBuffer(target Enum) bool {
|
func (f *Functions) UnmapBuffer(target Enum) bool {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int) {
|
func (c *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int) {
|
||||||
var norm uintptr
|
var norm uintptr
|
||||||
if normalized {
|
if normalized {
|
||||||
@@ -703,7 +489,6 @@ func (c *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalize
|
|||||||
}
|
}
|
||||||
syscall.Syscall6(_glVertexAttribPointer.Addr(), 6, uintptr(dst), uintptr(size), uintptr(ty), norm, uintptr(stride), uintptr(offset))
|
syscall.Syscall6(_glVertexAttribPointer.Addr(), 6, uintptr(dst), uintptr(size), uintptr(ty), norm, uintptr(stride), uintptr(offset))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) Viewport(x, y, width, height int) {
|
func (c *Functions) Viewport(x, y, width, height int) {
|
||||||
syscall.Syscall6(_glViewport.Addr(), 4, uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0, 0)
|
syscall.Syscall6(_glViewport.Addr(), 4, uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0, 0)
|
||||||
}
|
}
|
||||||
@@ -716,6 +501,6 @@ func cString(s string) []byte {
|
|||||||
|
|
||||||
// issue34474KeepAlive calls runtime.KeepAlive as a
|
// issue34474KeepAlive calls runtime.KeepAlive as a
|
||||||
// workaround for golang.org/issue/34474.
|
// workaround for golang.org/issue/34474.
|
||||||
func issue34474KeepAlive(v any) {
|
func issue34474KeepAlive(v interface{}) {
|
||||||
runtime.KeepAlive(v)
|
runtime.KeepAlive(v)
|
||||||
}
|
}
|
||||||
|
|||||||
+65
-15
@@ -18,7 +18,7 @@ type Ops struct {
|
|||||||
// data contains the serialized operations.
|
// data contains the serialized operations.
|
||||||
data []byte
|
data []byte
|
||||||
// refs hold external references for operations.
|
// refs hold external references for operations.
|
||||||
refs []any
|
refs []interface{}
|
||||||
// stringRefs provides space for string references, pointers to which will
|
// stringRefs provides space for string references, pointers to which will
|
||||||
// be stored in refs. Storing a string directly in refs would cause a heap
|
// be stored in refs. Storing a string directly in refs would cause a heap
|
||||||
// allocation, to store the string header in an interface value. The backing
|
// allocation, to store the string header in an interface value. The backing
|
||||||
@@ -55,19 +55,28 @@ const (
|
|||||||
TypePopTransform
|
TypePopTransform
|
||||||
TypePushOpacity
|
TypePushOpacity
|
||||||
TypePopOpacity
|
TypePopOpacity
|
||||||
|
TypeInvalidate
|
||||||
TypeImage
|
TypeImage
|
||||||
TypePaint
|
TypePaint
|
||||||
TypeColor
|
TypeColor
|
||||||
TypeLinearGradient
|
TypeLinearGradient
|
||||||
TypePass
|
TypePass
|
||||||
TypePopPass
|
TypePopPass
|
||||||
TypeInput
|
TypePointerInput
|
||||||
TypeKeyInputHint
|
TypeClipboardRead
|
||||||
|
TypeClipboardWrite
|
||||||
|
TypeSource
|
||||||
|
TypeTarget
|
||||||
|
TypeOffer
|
||||||
|
TypeKeyInput
|
||||||
|
TypeKeyFocus
|
||||||
|
TypeKeySoftKeyboard
|
||||||
TypeSave
|
TypeSave
|
||||||
TypeLoad
|
TypeLoad
|
||||||
TypeAux
|
TypeAux
|
||||||
TypeClip
|
TypeClip
|
||||||
TypePopClip
|
TypePopClip
|
||||||
|
TypeProfile
|
||||||
TypeCursor
|
TypeCursor
|
||||||
TypePath
|
TypePath
|
||||||
TypeStroke
|
TypeStroke
|
||||||
@@ -76,6 +85,8 @@ const (
|
|||||||
TypeSemanticClass
|
TypeSemanticClass
|
||||||
TypeSemanticSelected
|
TypeSemanticSelected
|
||||||
TypeSemanticEnabled
|
TypeSemanticEnabled
|
||||||
|
TypeSnippet
|
||||||
|
TypeSelection
|
||||||
TypeActionInput
|
TypeActionInput
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -137,13 +148,21 @@ const (
|
|||||||
TypeLinearGradientLen = 1 + 8*2 + 4*2
|
TypeLinearGradientLen = 1 + 8*2 + 4*2
|
||||||
TypePassLen = 1
|
TypePassLen = 1
|
||||||
TypePopPassLen = 1
|
TypePopPassLen = 1
|
||||||
TypeInputLen = 1
|
TypePointerInputLen = 1 + 1 + 1*2 + 2*4 + 2*4
|
||||||
TypeKeyInputHintLen = 1 + 1
|
TypeClipboardReadLen = 1
|
||||||
|
TypeClipboardWriteLen = 1
|
||||||
|
TypeSourceLen = 1
|
||||||
|
TypeTargetLen = 1
|
||||||
|
TypeOfferLen = 1
|
||||||
|
TypeKeyInputLen = 1 + 1
|
||||||
|
TypeKeyFocusLen = 1 + 1
|
||||||
|
TypeKeySoftKeyboardLen = 1 + 1
|
||||||
TypeSaveLen = 1 + 4
|
TypeSaveLen = 1 + 4
|
||||||
TypeLoadLen = 1 + 4
|
TypeLoadLen = 1 + 4
|
||||||
TypeAuxLen = 1
|
TypeAuxLen = 1
|
||||||
TypeClipLen = 1 + 4*4 + 1 + 1
|
TypeClipLen = 1 + 4*4 + 1 + 1
|
||||||
TypePopClipLen = 1
|
TypePopClipLen = 1
|
||||||
|
TypeProfileLen = 1
|
||||||
TypeCursorLen = 2
|
TypeCursorLen = 2
|
||||||
TypePathLen = 8 + 1
|
TypePathLen = 8 + 1
|
||||||
TypeStrokeLen = 1 + 4
|
TypeStrokeLen = 1 + 4
|
||||||
@@ -152,6 +171,8 @@ const (
|
|||||||
TypeSemanticClassLen = 2
|
TypeSemanticClassLen = 2
|
||||||
TypeSemanticSelectedLen = 2
|
TypeSemanticSelectedLen = 2
|
||||||
TypeSemanticEnabledLen = 2
|
TypeSemanticEnabledLen = 2
|
||||||
|
TypeSnippetLen = 1 + 4 + 4
|
||||||
|
TypeSelectionLen = 1 + 2*4 + 2*4 + 4 + 4
|
||||||
TypeActionInputLen = 1 + 1
|
TypeActionInputLen = 1 + 1
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -256,7 +277,7 @@ func PopOp(o *Ops, kind StackKind, sid StackID, macroID uint32) {
|
|||||||
o.stacks[kind].pop(sid)
|
o.stacks[kind].pop(sid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Write1(o *Ops, n int, ref1 any) []byte {
|
func Write1(o *Ops, n int, ref1 interface{}) []byte {
|
||||||
o.data = append(o.data, make([]byte, n)...)
|
o.data = append(o.data, make([]byte, n)...)
|
||||||
o.refs = append(o.refs, ref1)
|
o.refs = append(o.refs, ref1)
|
||||||
return o.data[len(o.data)-n:]
|
return o.data[len(o.data)-n:]
|
||||||
@@ -269,20 +290,20 @@ func Write1String(o *Ops, n int, ref1 string) []byte {
|
|||||||
return o.data[len(o.data)-n:]
|
return o.data[len(o.data)-n:]
|
||||||
}
|
}
|
||||||
|
|
||||||
func Write2(o *Ops, n int, ref1, ref2 any) []byte {
|
func Write2(o *Ops, n int, ref1, ref2 interface{}) []byte {
|
||||||
o.data = append(o.data, make([]byte, n)...)
|
o.data = append(o.data, make([]byte, n)...)
|
||||||
o.refs = append(o.refs, ref1, ref2)
|
o.refs = append(o.refs, ref1, ref2)
|
||||||
return o.data[len(o.data)-n:]
|
return o.data[len(o.data)-n:]
|
||||||
}
|
}
|
||||||
|
|
||||||
func Write2String(o *Ops, n int, ref1 any, ref2 string) []byte {
|
func Write2String(o *Ops, n int, ref1 interface{}, ref2 string) []byte {
|
||||||
o.data = append(o.data, make([]byte, n)...)
|
o.data = append(o.data, make([]byte, n)...)
|
||||||
o.stringRefs = append(o.stringRefs, ref2)
|
o.stringRefs = append(o.stringRefs, ref2)
|
||||||
o.refs = append(o.refs, ref1, &o.stringRefs[len(o.stringRefs)-1])
|
o.refs = append(o.refs, ref1, &o.stringRefs[len(o.stringRefs)-1])
|
||||||
return o.data[len(o.data)-n:]
|
return o.data[len(o.data)-n:]
|
||||||
}
|
}
|
||||||
|
|
||||||
func Write3(o *Ops, n int, ref1, ref2, ref3 any) []byte {
|
func Write3(o *Ops, n int, ref1, ref2, ref3 interface{}) []byte {
|
||||||
o.data = append(o.data, make([]byte, n)...)
|
o.data = append(o.data, make([]byte, n)...)
|
||||||
o.refs = append(o.refs, ref1, ref2, ref3)
|
o.refs = append(o.refs, ref1, ref2, ref3)
|
||||||
return o.data[len(o.data)-n:]
|
return o.data[len(o.data)-n:]
|
||||||
@@ -404,19 +425,28 @@ var opProps = [0x100]opProp{
|
|||||||
TypePopTransform: {Size: TypePopTransformLen, NumRefs: 0},
|
TypePopTransform: {Size: TypePopTransformLen, NumRefs: 0},
|
||||||
TypePushOpacity: {Size: TypePushOpacityLen, NumRefs: 0},
|
TypePushOpacity: {Size: TypePushOpacityLen, NumRefs: 0},
|
||||||
TypePopOpacity: {Size: TypePopOpacityLen, NumRefs: 0},
|
TypePopOpacity: {Size: TypePopOpacityLen, NumRefs: 0},
|
||||||
|
TypeInvalidate: {Size: TypeRedrawLen, NumRefs: 0},
|
||||||
TypeImage: {Size: TypeImageLen, NumRefs: 2},
|
TypeImage: {Size: TypeImageLen, NumRefs: 2},
|
||||||
TypePaint: {Size: TypePaintLen, NumRefs: 0},
|
TypePaint: {Size: TypePaintLen, NumRefs: 0},
|
||||||
TypeColor: {Size: TypeColorLen, NumRefs: 0},
|
TypeColor: {Size: TypeColorLen, NumRefs: 0},
|
||||||
TypeLinearGradient: {Size: TypeLinearGradientLen, NumRefs: 0},
|
TypeLinearGradient: {Size: TypeLinearGradientLen, NumRefs: 0},
|
||||||
TypePass: {Size: TypePassLen, NumRefs: 0},
|
TypePass: {Size: TypePassLen, NumRefs: 0},
|
||||||
TypePopPass: {Size: TypePopPassLen, NumRefs: 0},
|
TypePopPass: {Size: TypePopPassLen, NumRefs: 0},
|
||||||
TypeInput: {Size: TypeInputLen, NumRefs: 1},
|
TypePointerInput: {Size: TypePointerInputLen, NumRefs: 1},
|
||||||
TypeKeyInputHint: {Size: TypeKeyInputHintLen, NumRefs: 1},
|
TypeClipboardRead: {Size: TypeClipboardReadLen, NumRefs: 1},
|
||||||
|
TypeClipboardWrite: {Size: TypeClipboardWriteLen, NumRefs: 1},
|
||||||
|
TypeSource: {Size: TypeSourceLen, NumRefs: 2},
|
||||||
|
TypeTarget: {Size: TypeTargetLen, NumRefs: 2},
|
||||||
|
TypeOffer: {Size: TypeOfferLen, NumRefs: 3},
|
||||||
|
TypeKeyInput: {Size: TypeKeyInputLen, NumRefs: 2},
|
||||||
|
TypeKeyFocus: {Size: TypeKeyFocusLen, NumRefs: 1},
|
||||||
|
TypeKeySoftKeyboard: {Size: TypeKeySoftKeyboardLen, NumRefs: 0},
|
||||||
TypeSave: {Size: TypeSaveLen, NumRefs: 0},
|
TypeSave: {Size: TypeSaveLen, NumRefs: 0},
|
||||||
TypeLoad: {Size: TypeLoadLen, NumRefs: 0},
|
TypeLoad: {Size: TypeLoadLen, NumRefs: 0},
|
||||||
TypeAux: {Size: TypeAuxLen, NumRefs: 0},
|
TypeAux: {Size: TypeAuxLen, NumRefs: 0},
|
||||||
TypeClip: {Size: TypeClipLen, NumRefs: 0},
|
TypeClip: {Size: TypeClipLen, NumRefs: 0},
|
||||||
TypePopClip: {Size: TypePopClipLen, NumRefs: 0},
|
TypePopClip: {Size: TypePopClipLen, NumRefs: 0},
|
||||||
|
TypeProfile: {Size: TypeProfileLen, NumRefs: 1},
|
||||||
TypeCursor: {Size: TypeCursorLen, NumRefs: 0},
|
TypeCursor: {Size: TypeCursorLen, NumRefs: 0},
|
||||||
TypePath: {Size: TypePathLen, NumRefs: 0},
|
TypePath: {Size: TypePathLen, NumRefs: 0},
|
||||||
TypeStroke: {Size: TypeStrokeLen, NumRefs: 0},
|
TypeStroke: {Size: TypeStrokeLen, NumRefs: 0},
|
||||||
@@ -425,6 +455,8 @@ var opProps = [0x100]opProp{
|
|||||||
TypeSemanticClass: {Size: TypeSemanticClassLen, NumRefs: 0},
|
TypeSemanticClass: {Size: TypeSemanticClassLen, NumRefs: 0},
|
||||||
TypeSemanticSelected: {Size: TypeSemanticSelectedLen, NumRefs: 0},
|
TypeSemanticSelected: {Size: TypeSemanticSelectedLen, NumRefs: 0},
|
||||||
TypeSemanticEnabled: {Size: TypeSemanticEnabledLen, NumRefs: 0},
|
TypeSemanticEnabled: {Size: TypeSemanticEnabledLen, NumRefs: 0},
|
||||||
|
TypeSnippet: {Size: TypeSnippetLen, NumRefs: 2},
|
||||||
|
TypeSelection: {Size: TypeSelectionLen, NumRefs: 1},
|
||||||
TypeActionInput: {Size: TypeActionInputLen, NumRefs: 0},
|
TypeActionInput: {Size: TypeActionInputLen, NumRefs: 0},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,6 +489,8 @@ func (t OpType) String() string {
|
|||||||
return "PushOpacity"
|
return "PushOpacity"
|
||||||
case TypePopOpacity:
|
case TypePopOpacity:
|
||||||
return "PopOpacity"
|
return "PopOpacity"
|
||||||
|
case TypeInvalidate:
|
||||||
|
return "Invalidate"
|
||||||
case TypeImage:
|
case TypeImage:
|
||||||
return "Image"
|
return "Image"
|
||||||
case TypePaint:
|
case TypePaint:
|
||||||
@@ -469,10 +503,24 @@ func (t OpType) String() string {
|
|||||||
return "Pass"
|
return "Pass"
|
||||||
case TypePopPass:
|
case TypePopPass:
|
||||||
return "PopPass"
|
return "PopPass"
|
||||||
case TypeInput:
|
case TypePointerInput:
|
||||||
return "Input"
|
return "PointerInput"
|
||||||
case TypeKeyInputHint:
|
case TypeClipboardRead:
|
||||||
return "KeyInputHint"
|
return "ClipboardRead"
|
||||||
|
case TypeClipboardWrite:
|
||||||
|
return "ClipboardWrite"
|
||||||
|
case TypeSource:
|
||||||
|
return "Source"
|
||||||
|
case TypeTarget:
|
||||||
|
return "Target"
|
||||||
|
case TypeOffer:
|
||||||
|
return "Offer"
|
||||||
|
case TypeKeyInput:
|
||||||
|
return "KeyInput"
|
||||||
|
case TypeKeyFocus:
|
||||||
|
return "KeyFocus"
|
||||||
|
case TypeKeySoftKeyboard:
|
||||||
|
return "KeySoftKeyboard"
|
||||||
case TypeSave:
|
case TypeSave:
|
||||||
return "Save"
|
return "Save"
|
||||||
case TypeLoad:
|
case TypeLoad:
|
||||||
@@ -483,6 +531,8 @@ func (t OpType) String() string {
|
|||||||
return "Clip"
|
return "Clip"
|
||||||
case TypePopClip:
|
case TypePopClip:
|
||||||
return "PopClip"
|
return "PopClip"
|
||||||
|
case TypeProfile:
|
||||||
|
return "Profile"
|
||||||
case TypeCursor:
|
case TypeCursor:
|
||||||
return "Cursor"
|
return "Cursor"
|
||||||
case TypePath:
|
case TypePath:
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ type Reader struct {
|
|||||||
type EncodedOp struct {
|
type EncodedOp struct {
|
||||||
Key Key
|
Key Key
|
||||||
Data []byte
|
Data []byte
|
||||||
Refs []any
|
Refs []interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key is a unique key for a given op.
|
// Key is a unique key for a given op.
|
||||||
@@ -175,7 +175,7 @@ func (op *opMacroDef) decode(data []byte) {
|
|||||||
op.endpc.refs = bo.Uint32(data[5:])
|
op.endpc.refs = bo.Uint32(data[5:])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *macroOp) decode(data []byte, refs []any) {
|
func (m *macroOp) decode(data []byte, refs []interface{}) {
|
||||||
if len(data) < TypeCallLen || len(refs) < 1 || OpType(data[0]) != TypeCall {
|
if len(data) < TypeCallLen || len(refs) < 1 || OpType(data[0]) != TypeCall {
|
||||||
panic("invalid op")
|
panic("invalid op")
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-22
@@ -87,7 +87,7 @@ func (qs *StrokeQuads) lineTo(pt f32.Point) {
|
|||||||
func (qs *StrokeQuads) arc(f1, f2 f32.Point, angle float32) {
|
func (qs *StrokeQuads) arc(f1, f2 f32.Point, angle float32) {
|
||||||
pen := qs.pen()
|
pen := qs.pen()
|
||||||
m, segments := ArcTransform(pen, f1.Add(pen), f2.Add(pen), angle)
|
m, segments := ArcTransform(pen, f1.Add(pen), f2.Add(pen), angle)
|
||||||
for range segments {
|
for i := 0; i < segments; i++ {
|
||||||
p0 := qs.pen()
|
p0 := qs.pen()
|
||||||
p1 := m.Transform(p0)
|
p1 := m.Transform(p0)
|
||||||
p2 := m.Transform(p1)
|
p2 := m.Transform(p1)
|
||||||
@@ -327,26 +327,10 @@ 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 == 0) || l == 0 {
|
|
||||||
return f32.Point{}
|
|
||||||
}
|
|
||||||
isVerticalUnit := p.X == 0 && (p.Y == l || p.Y == -l)
|
|
||||||
isHorizontalUnit := p.Y == 0 && (p.X == l || p.X == -l)
|
|
||||||
if isVerticalUnit || isHorizontalUnit {
|
|
||||||
if math.Signbit(float64(l)) {
|
|
||||||
return f32.Point{X: -p.X, Y: -p.Y}
|
|
||||||
} else {
|
|
||||||
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 {
|
||||||
if math.Signbit(float64(l)) {
|
return f32.Point{}
|
||||||
return f32.Point{X: -p.X, Y: -p.Y}
|
|
||||||
} else {
|
|
||||||
return f32.Point{X: p.X, Y: p.Y}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
n := float32(l64 / d)
|
n := float32(l64 / d)
|
||||||
return f32.Point{X: p.X * n, Y: p.Y * n}
|
return f32.Point{X: p.X * n, Y: p.Y * n}
|
||||||
@@ -453,6 +437,7 @@ func flattenQuadBezier(qs StrokeQuads, p0, p1, p2 f32.Point, d, flatness float32
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (qs *StrokeQuads) addLine(p0, ctrl, p1 f32.Point, t, d float32) {
|
func (qs *StrokeQuads) addLine(p0, ctrl, p1 f32.Point, t, d float32) {
|
||||||
|
|
||||||
switch i := len(*qs); i {
|
switch i := len(*qs); i {
|
||||||
case 0:
|
case 0:
|
||||||
p0 = p0.Add(strokePathNorm(p0, ctrl, p1, 0, d))
|
p0 = p0.Add(strokePathNorm(p0, ctrl, p1, 0, d))
|
||||||
@@ -485,6 +470,7 @@ func quadInterp(p, q f32.Point, t float32) f32.Point {
|
|||||||
// quadBezierSplit returns the pair of triplets (from,ctrl,to) Bézier curve,
|
// quadBezierSplit returns the pair of triplets (from,ctrl,to) Bézier curve,
|
||||||
// split before (resp. after) the provided parametric t value.
|
// split before (resp. after) the provided parametric t value.
|
||||||
func quadBezierSplit(p0, p1, p2 f32.Point, t float32) (f32.Point, f32.Point, f32.Point, f32.Point, f32.Point, f32.Point) {
|
func quadBezierSplit(p0, p1, p2 f32.Point, t float32) (f32.Point, f32.Point, f32.Point, f32.Point, f32.Point, f32.Point) {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
b0 = p0
|
b0 = p0
|
||||||
b1 = quadInterp(p0, p1, t)
|
b1 = quadInterp(p0, p1, t)
|
||||||
@@ -588,10 +574,12 @@ func ArcTransform(p, f1, f2 f32.Point, angle float32) (transform f32.Affine2D, s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
θ := angle / float32(segments)
|
var (
|
||||||
ref := f32.AffineId() // transform from absolute frame to ellipse-based one
|
θ = angle / float32(segments)
|
||||||
rot := f32.AffineId() // rotation matrix for each segment
|
ref f32.Affine2D // transform from absolute frame to ellipse-based one
|
||||||
inv := f32.AffineId() // transform from ellipse-based frame to absolute one
|
rot f32.Affine2D // rotation matrix for each segment
|
||||||
|
inv f32.Affine2D // transform from ellipse-based frame to absolute one
|
||||||
|
)
|
||||||
center := f32.Point{
|
center := f32.Point{
|
||||||
X: 0.5 * (f1.X + f2.X),
|
X: 0.5 * (f1.X + f2.X),
|
||||||
Y: 0.5 * (f1.Y + f2.Y),
|
Y: 0.5 * (f1.Y + f2.Y),
|
||||||
|
|||||||
@@ -9,111 +9,6 @@ import (
|
|||||||
"gioui.org/internal/f32"
|
"gioui.org/internal/f32"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNormPt(t *testing.T) {
|
|
||||||
type scenario struct {
|
|
||||||
l float32
|
|
||||||
ptIn, ptOut f32.Point
|
|
||||||
}
|
|
||||||
|
|
||||||
scenarios := []scenario{
|
|
||||||
// l!=0 && X=Y=0
|
|
||||||
{l: 10, ptIn: f32.Point{X: 0, Y: 0}, ptOut: f32.Point{X: 0, Y: 0}},
|
|
||||||
{l: -10, ptIn: f32.Point{X: 0, Y: 0}, ptOut: f32.Point{X: 0, Y: 0}},
|
|
||||||
|
|
||||||
// l>0 & X
|
|
||||||
{l: +20, ptIn: f32.Point{X: +30, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
|
|
||||||
{l: +20, ptIn: f32.Point{X: +20, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
|
|
||||||
{l: +20, ptIn: f32.Point{X: +10, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
|
|
||||||
{l: +20, ptIn: f32.Point{X: -10, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
|
|
||||||
{l: +20, ptIn: f32.Point{X: -20, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
|
|
||||||
{l: +20, ptIn: f32.Point{X: -30, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
|
|
||||||
|
|
||||||
// l<0 & X
|
|
||||||
{l: -20, ptIn: f32.Point{X: +30, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
|
|
||||||
{l: -20, ptIn: f32.Point{X: +20, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
|
|
||||||
{l: -20, ptIn: f32.Point{X: +10, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
|
|
||||||
{l: -20, ptIn: f32.Point{X: -10, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
|
|
||||||
{l: -20, ptIn: f32.Point{X: -20, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
|
|
||||||
{l: -20, ptIn: f32.Point{X: -30, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
|
|
||||||
|
|
||||||
// l>0 & Y
|
|
||||||
{l: +20, ptIn: f32.Point{X: 0, Y: +30}, ptOut: f32.Point{X: 0, Y: +20}},
|
|
||||||
{l: +20, ptIn: f32.Point{X: 0, Y: +20}, ptOut: f32.Point{X: 0, Y: +20}},
|
|
||||||
{l: +20, ptIn: f32.Point{X: 0, Y: +10}, ptOut: f32.Point{X: 0, Y: +20}},
|
|
||||||
{l: +20, ptIn: f32.Point{X: 0, Y: -10}, ptOut: f32.Point{X: 0, Y: -20}},
|
|
||||||
{l: +20, ptIn: f32.Point{X: 0, Y: -20}, ptOut: f32.Point{X: 0, Y: -20}},
|
|
||||||
{l: +20, ptIn: f32.Point{X: 0, Y: -30}, ptOut: f32.Point{X: 0, Y: -20}},
|
|
||||||
|
|
||||||
// l<0 & Y
|
|
||||||
{l: -20, ptIn: f32.Point{X: 0, Y: +30}, ptOut: f32.Point{X: 0, Y: -20}},
|
|
||||||
{l: -20, ptIn: f32.Point{X: 0, Y: +20}, ptOut: f32.Point{X: 0, Y: -20}},
|
|
||||||
{l: -20, ptIn: f32.Point{X: 0, Y: +10}, ptOut: f32.Point{X: 0, Y: -20}},
|
|
||||||
{l: -20, ptIn: f32.Point{X: 0, Y: -10}, ptOut: f32.Point{X: 0, Y: +20}},
|
|
||||||
{l: -20, ptIn: f32.Point{X: 0, Y: -20}, ptOut: f32.Point{X: 0, Y: +20}},
|
|
||||||
{l: -20, ptIn: f32.Point{X: 0, Y: -30}, ptOut: f32.Point{X: 0, Y: +20}},
|
|
||||||
|
|
||||||
// l>0 && X=Y
|
|
||||||
{l: +20, ptIn: f32.Point{X: +90, Y: +90}, ptOut: f32.Point{X: +14.142137, Y: +14.142137}},
|
|
||||||
{l: +20, ptIn: f32.Point{X: +30, Y: +30}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
|
|
||||||
{l: +20, ptIn: f32.Point{X: +20, Y: +20}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
|
|
||||||
{l: +20, ptIn: f32.Point{X: +10, Y: +10}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
|
|
||||||
{l: +20, ptIn: f32.Point{X: -10, Y: -10}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
|
|
||||||
{l: +20, ptIn: f32.Point{X: -20, Y: -20}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
|
|
||||||
{l: +20, ptIn: f32.Point{X: -30, Y: -30}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
|
|
||||||
{l: +20, ptIn: f32.Point{X: -90, Y: -90}, ptOut: f32.Point{X: -14.142137, Y: -14.142137}},
|
|
||||||
|
|
||||||
// l>0 && X=-Y
|
|
||||||
{l: +20, ptIn: f32.Point{X: +90, Y: -90}, ptOut: f32.Point{X: +14.142137, Y: -14.142137}},
|
|
||||||
{l: +20, ptIn: f32.Point{X: +30, Y: -30}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
|
|
||||||
{l: +20, ptIn: f32.Point{X: +20, Y: -20}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
|
|
||||||
{l: +20, ptIn: f32.Point{X: +10, Y: -10}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
|
|
||||||
{l: +20, ptIn: f32.Point{X: -10, Y: +10}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
|
|
||||||
{l: +20, ptIn: f32.Point{X: -20, Y: +20}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
|
|
||||||
{l: +20, ptIn: f32.Point{X: -30, Y: +30}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
|
|
||||||
{l: +20, ptIn: f32.Point{X: -90, Y: +90}, ptOut: f32.Point{X: -14.142137, Y: +14.142137}},
|
|
||||||
|
|
||||||
// l<0 && X=Y
|
|
||||||
{l: -20, ptIn: f32.Point{X: +90, Y: +90}, ptOut: f32.Point{X: -14.142137, Y: -14.142137}},
|
|
||||||
{l: -20, ptIn: f32.Point{X: +30, Y: +30}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
|
|
||||||
{l: -20, ptIn: f32.Point{X: +20, Y: +20}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
|
|
||||||
{l: -20, ptIn: f32.Point{X: +10, Y: +10}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
|
|
||||||
{l: -20, ptIn: f32.Point{X: -10, Y: -10}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
|
|
||||||
{l: -20, ptIn: f32.Point{X: -20, Y: -20}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
|
|
||||||
{l: -20, ptIn: f32.Point{X: -30, Y: -30}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
|
|
||||||
{l: -20, ptIn: f32.Point{X: -90, Y: -90}, ptOut: f32.Point{X: +14.142137, Y: +14.142137}},
|
|
||||||
|
|
||||||
// l<0 && X=-Y
|
|
||||||
{l: -20, ptIn: f32.Point{X: +90, Y: -90}, ptOut: f32.Point{X: -14.142137, Y: +14.142137}},
|
|
||||||
{l: -20, ptIn: f32.Point{X: +30, Y: -30}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
|
|
||||||
{l: -20, ptIn: f32.Point{X: +20, Y: -20}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
|
|
||||||
{l: -20, ptIn: f32.Point{X: +10, Y: -10}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
|
|
||||||
{l: -20, ptIn: f32.Point{X: -10, Y: +10}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
|
|
||||||
{l: -20, ptIn: f32.Point{X: -20, Y: +20}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
|
|
||||||
{l: -20, ptIn: f32.Point{X: -30, Y: +30}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
|
|
||||||
{l: -20, ptIn: f32.Point{X: -90, Y: +90}, ptOut: f32.Point{X: +14.142137, Y: -14.142137}},
|
|
||||||
|
|
||||||
// l!=0 && Hypot=l
|
|
||||||
{l: 5, ptIn: f32.Point{X: 3, Y: 4}, ptOut: f32.Point{X: 3, Y: 4}},
|
|
||||||
{l: 5, ptIn: f32.Point{X: 3, Y: -4}, ptOut: f32.Point{X: 3, Y: -4}},
|
|
||||||
{l: 5, ptIn: f32.Point{X: -3, Y: -4}, ptOut: f32.Point{X: -3, Y: -4}},
|
|
||||||
{l: 5, ptIn: f32.Point{X: -3, Y: 4}, ptOut: f32.Point{X: -3, Y: 4}},
|
|
||||||
{l: -5, ptIn: f32.Point{X: 3, Y: 4}, ptOut: f32.Point{X: -3, Y: -4}},
|
|
||||||
{l: -5, ptIn: f32.Point{X: 3, Y: -4}, ptOut: f32.Point{X: -3, Y: 4}},
|
|
||||||
{l: -5, ptIn: f32.Point{X: -3, Y: -4}, ptOut: f32.Point{X: 3, Y: 4}},
|
|
||||||
{l: -5, ptIn: f32.Point{X: -3, Y: 4}, ptOut: f32.Point{X: 3, Y: -4}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, s := range scenarios {
|
|
||||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
|
||||||
actual := normPt(s.ptIn, s.l)
|
|
||||||
if actual != s.ptOut {
|
|
||||||
t.Errorf("in: %v*%v, expected: %v, actual: %v", s.l, s.ptIn, s.ptOut, actual)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkSplitCubic(b *testing.B) {
|
func BenchmarkSplitCubic(b *testing.B) {
|
||||||
type scenario struct {
|
type scenario struct {
|
||||||
segments int
|
segments int
|
||||||
@@ -157,7 +52,7 @@ func BenchmarkSplitCubic(b *testing.B) {
|
|||||||
from, ctrl0, ctrl1, to := s.from, s.ctrl0, s.ctrl1, s.to
|
from, ctrl0, ctrl1, to := s.from, s.ctrl0, s.ctrl1, s.to
|
||||||
quads := make([]QuadSegment, s.segments)
|
quads := make([]QuadSegment, s.segments)
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for b.Loop() {
|
for i := 0; i < b.N; i++ {
|
||||||
quads = SplitCubic(from, ctrl0, ctrl1, to, quads[:0])
|
quads = SplitCubic(from, ctrl0, ctrl1, to, quads[:0])
|
||||||
}
|
}
|
||||||
if len(quads) != s.segments {
|
if len(quads) != s.segments {
|
||||||
|
|||||||
@@ -385,7 +385,6 @@ static VkResult vkQueuePresentKHR(PFN_vkQueuePresentKHR f, VkQueue queue, const
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -1168,6 +1167,7 @@ func CreateFramebuffer(d Device, rp RenderPass, view ImageView, width, height in
|
|||||||
return nilFramebuffer, fmt.Errorf("vulkan: vkCreateFramebuffer: %w", err)
|
return nilFramebuffer, fmt.Errorf("vulkan: vkCreateFramebuffer: %w", err)
|
||||||
}
|
}
|
||||||
return fbo, nil
|
return fbo, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func DestroyFramebuffer(d Device, f Framebuffer) {
|
func DestroyFramebuffer(d Device, f Framebuffer) {
|
||||||
@@ -1893,27 +1893,27 @@ func BuildWriteDescriptorSetBuffer(set DescriptorSet, binding int, typ Descripto
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func PushConstantRangeStageFlags(r PushConstantRange) ShaderStageFlags {
|
func (r PushConstantRange) StageFlags() ShaderStageFlags {
|
||||||
return r.stageFlags
|
return r.stageFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
func PushConstantRangeOffset(r PushConstantRange) int {
|
func (r PushConstantRange) Offset() int {
|
||||||
return int(r.offset)
|
return int(r.offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func PushConstantRangeSize(r PushConstantRange) int {
|
func (r PushConstantRange) Size() int {
|
||||||
return int(r.size)
|
return int(r.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func QueueFamilyPropertiesFlags(p QueueFamilyProperties) QueueFlags {
|
func (p QueueFamilyProperties) Flags() QueueFlags {
|
||||||
return p.queueFlags
|
return p.queueFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
func SurfaceCapabilitiesMinExtent(c SurfaceCapabilities) image.Point {
|
func (c SurfaceCapabilities) MinExtent() 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 SurfaceCapabilitiesMaxExtent(c SurfaceCapabilities) image.Point {
|
func (c SurfaceCapabilities) MaxExtent() image.Point {
|
||||||
return image.Pt(int(c.maxImageExtent.width), int(c.maxImageExtent.height))
|
return image.Pt(int(c.maxImageExtent.width), int(c.maxImageExtent.height))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ static VkResult vkCreateAndroidSurfaceKHR(PFN_vkCreateAndroidSurfaceKHR f, VkIns
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ static VkResult vkCreateWaylandSurfaceKHR(PFN_vkCreateWaylandSurfaceKHR f, VkIns
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ static VkResult vkCreateXlibSurfaceKHR(PFN_vkCreateXlibSurfaceKHR f, VkInstance
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|||||||
+24
-11
@@ -3,22 +3,35 @@
|
|||||||
package clipboard
|
package clipboard
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"gioui.org/internal/ops"
|
||||||
|
|
||||||
"gioui.org/io/event"
|
"gioui.org/io/event"
|
||||||
|
"gioui.org/op"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WriteCmd copies Text to the clipboard.
|
// Event is generated when the clipboard content is requested.
|
||||||
type WriteCmd struct {
|
type Event struct {
|
||||||
Type string
|
Text string
|
||||||
Data io.ReadCloser
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadCmd requests the text of the clipboard, delivered to
|
// ReadOp requests the text of the clipboard, delivered to
|
||||||
// the handler through an [io/transfer.DataEvent].
|
// the current handler through an Event.
|
||||||
type ReadCmd struct {
|
type ReadOp struct {
|
||||||
Tag event.Tag
|
Tag event.Tag
|
||||||
}
|
}
|
||||||
|
|
||||||
func (WriteCmd) ImplementsCommand() {}
|
// WriteOp copies Text to the clipboard.
|
||||||
func (ReadCmd) ImplementsCommand() {}
|
type WriteOp struct {
|
||||||
|
Text string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h ReadOp) Add(o *op.Ops) {
|
||||||
|
data := ops.Write1(&o.Internal, ops.TypeClipboardReadLen, h.Tag)
|
||||||
|
data[0] = byte(ops.TypeClipboardRead)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h WriteOp) Add(o *op.Ops) {
|
||||||
|
data := ops.Write1String(&o.Internal, ops.TypeClipboardWriteLen, h.Text)
|
||||||
|
data[0] = byte(ops.TypeClipboardWrite)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Event) ImplementsEvent() {}
|
||||||
|
|||||||
+35
-21
@@ -1,33 +1,47 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
// Package event contains types for event handling.
|
/*
|
||||||
|
Package event contains the types for event handling.
|
||||||
|
|
||||||
|
The Queue interface is the protocol for receiving external events.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
var queue event.Queue = ...
|
||||||
|
|
||||||
|
for _, e := range queue.Events(h) {
|
||||||
|
switch e.(type) {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
In general, handlers must be declared before events become
|
||||||
|
available. Other packages such as pointer and key provide
|
||||||
|
the means for declaring handlers for specific event types.
|
||||||
|
|
||||||
|
The following example declares a handler ready for key input:
|
||||||
|
|
||||||
|
import gioui.org/io/key
|
||||||
|
|
||||||
|
ops := new(op.Ops)
|
||||||
|
var h *Handler = ...
|
||||||
|
key.InputOp{Tag: h, Filter: ...}.Add(ops)
|
||||||
|
*/
|
||||||
package event
|
package event
|
||||||
|
|
||||||
import (
|
// Queue maps an event handler key to the events
|
||||||
"gioui.org/internal/ops"
|
// available to the handler.
|
||||||
"gioui.org/op"
|
type Queue interface {
|
||||||
)
|
// Events returns the available events for an
|
||||||
|
// event handler tag.
|
||||||
|
Events(t Tag) []Event
|
||||||
|
}
|
||||||
|
|
||||||
// Tag is the stable identifier for an event handler.
|
// Tag is the stable identifier for an event handler.
|
||||||
// For a handler h, the tag is typically &h.
|
// For a handler h, the tag is typically &h.
|
||||||
type Tag any
|
type Tag interface{}
|
||||||
|
|
||||||
// Event is the marker interface for events.
|
// Event is the marker interface for events.
|
||||||
type Event interface {
|
type Event interface {
|
||||||
ImplementsEvent()
|
ImplementsEvent()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter represents a filter for [Event] types.
|
|
||||||
type Filter interface {
|
|
||||||
ImplementsFilter()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Op declares a tag for input routing at the current transformation
|
|
||||||
// and clip area hierarchy. It panics if tag is nil.
|
|
||||||
func Op(o *op.Ops, tag Tag) {
|
|
||||||
if tag == nil {
|
|
||||||
panic("Tag must be non-nil")
|
|
||||||
}
|
|
||||||
data := ops.Write1(&o.Internal, ops.TypeInputLen, tag)
|
|
||||||
data[0] = byte(ops.TypeInput)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
package input
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"slices"
|
|
||||||
|
|
||||||
"gioui.org/io/clipboard"
|
|
||||||
"gioui.org/io/event"
|
|
||||||
)
|
|
||||||
|
|
||||||
// clipboardState contains the state for clipboard event routing.
|
|
||||||
type clipboardState struct {
|
|
||||||
receivers []event.Tag
|
|
||||||
}
|
|
||||||
|
|
||||||
type clipboardQueue struct {
|
|
||||||
// request avoid read clipboard every frame while waiting.
|
|
||||||
requested bool
|
|
||||||
mime string
|
|
||||||
text []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteClipboard returns the most recent data to be copied
|
|
||||||
// to the clipboard, if any.
|
|
||||||
func (q *clipboardQueue) WriteClipboard() (mime string, content []byte, ok bool) {
|
|
||||||
if q.text == nil {
|
|
||||||
return "", nil, false
|
|
||||||
}
|
|
||||||
content = q.text
|
|
||||||
q.text = nil
|
|
||||||
return q.mime, content, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClipboardRequested reports if any new handler is waiting
|
|
||||||
// to read the clipboard.
|
|
||||||
func (q *clipboardQueue) ClipboardRequested(state clipboardState) bool {
|
|
||||||
req := len(state.receivers) > 0 && q.requested
|
|
||||||
q.requested = false
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *clipboardQueue) Push(state clipboardState, e event.Event) (clipboardState, []taggedEvent) {
|
|
||||||
var evts []taggedEvent
|
|
||||||
for _, r := range state.receivers {
|
|
||||||
evts = append(evts, taggedEvent{tag: r, event: e})
|
|
||||||
}
|
|
||||||
state.receivers = nil
|
|
||||||
return state, evts
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *clipboardQueue) ProcessWriteClipboard(req clipboard.WriteCmd) {
|
|
||||||
defer req.Data.Close()
|
|
||||||
content, err := io.ReadAll(req.Data)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
q.mime = req.Type
|
|
||||||
q.text = content
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *clipboardQueue) ProcessReadClipboard(state clipboardState, tag event.Tag) clipboardState {
|
|
||||||
if slices.Contains(state.receivers, tag) {
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
n := len(state.receivers)
|
|
||||||
state.receivers = append(state.receivers[:n:n], tag)
|
|
||||||
q.requested = true
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
package input
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"gioui.org/io/clipboard"
|
|
||||||
"gioui.org/io/transfer"
|
|
||||||
"gioui.org/op"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestClipboardDuplicateEvent(t *testing.T) {
|
|
||||||
ops, r, handlers := new(op.Ops), new(Router), make([]int, 2)
|
|
||||||
|
|
||||||
// Both must receive the event once.
|
|
||||||
r.Source().Execute(clipboard.ReadCmd{Tag: &handlers[0]})
|
|
||||||
r.Source().Execute(clipboard.ReadCmd{Tag: &handlers[1]})
|
|
||||||
|
|
||||||
event := transfer.DataEvent{
|
|
||||||
Type: "application/text",
|
|
||||||
Open: func() io.ReadCloser {
|
|
||||||
return io.NopCloser(strings.NewReader("Test"))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
r.Queue(event)
|
|
||||||
for i := range handlers {
|
|
||||||
f := transfer.TargetFilter{Target: &handlers[i], Type: "application/text"}
|
|
||||||
assertEventTypeSequence(t, events(r, -1, f), transfer.DataEvent{})
|
|
||||||
}
|
|
||||||
assertClipboardReadCmd(t, r, 0)
|
|
||||||
|
|
||||||
r.Source().Execute(clipboard.ReadCmd{Tag: &handlers[0]})
|
|
||||||
|
|
||||||
r.Frame(ops)
|
|
||||||
// No ClipboardEvent sent
|
|
||||||
assertClipboardReadCmd(t, r, 1)
|
|
||||||
for i := range handlers {
|
|
||||||
f := transfer.TargetFilter{Target: &handlers[i]}
|
|
||||||
assertEventTypeSequence(t, events(r, -1, f))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestQueueProcessReadClipboard(t *testing.T) {
|
|
||||||
ops, r, handler := new(op.Ops), new(Router), make([]int, 2)
|
|
||||||
|
|
||||||
// Request read
|
|
||||||
r.Source().Execute(clipboard.ReadCmd{Tag: &handler[0]})
|
|
||||||
|
|
||||||
assertClipboardReadCmd(t, r, 1)
|
|
||||||
ops.Reset()
|
|
||||||
|
|
||||||
for range 3 {
|
|
||||||
// No ReadCmd
|
|
||||||
// One receiver must still wait for response
|
|
||||||
|
|
||||||
r.Frame(ops)
|
|
||||||
assertClipboardReadDuplicated(t, r, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the clipboard event
|
|
||||||
event := transfer.DataEvent{
|
|
||||||
Type: "application/text",
|
|
||||||
Open: func() io.ReadCloser {
|
|
||||||
return io.NopCloser(strings.NewReader("Text 2"))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
r.Queue(event)
|
|
||||||
assertEventTypeSequence(t, events(r, -1, transfer.TargetFilter{Target: &handler[0], Type: "application/text"}), transfer.DataEvent{})
|
|
||||||
assertClipboardReadCmd(t, r, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestQueueProcessWriteClipboard(t *testing.T) {
|
|
||||||
r := new(Router)
|
|
||||||
|
|
||||||
const mime = "application/text"
|
|
||||||
r.Source().Execute(clipboard.WriteCmd{Type: mime, Data: io.NopCloser(strings.NewReader("Write 1"))})
|
|
||||||
|
|
||||||
assertClipboardWriteCmd(t, r, mime, "Write 1")
|
|
||||||
assertClipboardWriteCmd(t, r, "", "")
|
|
||||||
|
|
||||||
r.Source().Execute(clipboard.WriteCmd{Type: mime, Data: io.NopCloser(strings.NewReader("Write 2"))})
|
|
||||||
|
|
||||||
assertClipboardReadCmd(t, r, 0)
|
|
||||||
assertClipboardWriteCmd(t, r, mime, "Write 2")
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertClipboardReadCmd(t *testing.T, router *Router, expected int) {
|
|
||||||
t.Helper()
|
|
||||||
if got := len(router.state().receivers); got != expected {
|
|
||||||
t.Errorf("unexpected %d receivers, got %d", expected, got)
|
|
||||||
}
|
|
||||||
if router.ClipboardRequested() != (expected > 0) {
|
|
||||||
t.Error("missing requests")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertClipboardReadDuplicated(t *testing.T, router *Router, expected int) {
|
|
||||||
t.Helper()
|
|
||||||
if len(router.state().receivers) != expected {
|
|
||||||
t.Error("receivers removed")
|
|
||||||
}
|
|
||||||
if router.ClipboardRequested() != false {
|
|
||||||
t.Error("duplicated requests")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertClipboardWriteCmd(t *testing.T, router *Router, mimeExp, expected string) {
|
|
||||||
t.Helper()
|
|
||||||
if (router.cqueue.text != nil) != (expected != "") {
|
|
||||||
t.Error("text not defined")
|
|
||||||
}
|
|
||||||
mime, text, ok := router.cqueue.WriteClipboard()
|
|
||||||
if ok != (expected != "") {
|
|
||||||
t.Error("duplicated requests")
|
|
||||||
}
|
|
||||||
if string(mime) != mimeExp {
|
|
||||||
t.Errorf("got MIME type %s, expected %s", mime, mimeExp)
|
|
||||||
}
|
|
||||||
if string(text) != expected {
|
|
||||||
t.Errorf("got text %s, expected %s", text, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
/*
|
|
||||||
Package input implements input routing and tracking of interface
|
|
||||||
state for a window.
|
|
||||||
|
|
||||||
The [Source] is the interface between the window and the widgets
|
|
||||||
of a user interface and is exposed by [gioui.org/app.FrameEvent]
|
|
||||||
received from windows.
|
|
||||||
|
|
||||||
The [Router] is used by [gioui.org/app.Window] to track window state and route
|
|
||||||
events from the platform to event handlers. It is otherwise only
|
|
||||||
useful for using Gio with external window implementations.
|
|
||||||
*/
|
|
||||||
package input
|
|
||||||
-364
@@ -1,364 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
package input
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
"slices"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"gioui.org/f32"
|
|
||||||
"gioui.org/io/event"
|
|
||||||
"gioui.org/io/key"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EditorState represents the state of an editor needed by input handlers.
|
|
||||||
type EditorState struct {
|
|
||||||
Selection struct {
|
|
||||||
Transform f32.Affine2D
|
|
||||||
key.Range
|
|
||||||
key.Caret
|
|
||||||
}
|
|
||||||
Snippet key.Snippet
|
|
||||||
}
|
|
||||||
|
|
||||||
type TextInputState uint8
|
|
||||||
|
|
||||||
type keyQueue struct {
|
|
||||||
order []event.Tag
|
|
||||||
dirOrder []dirFocusEntry
|
|
||||||
hint key.InputHint
|
|
||||||
}
|
|
||||||
|
|
||||||
// keyState is the input state related to key events.
|
|
||||||
type keyState struct {
|
|
||||||
focus event.Tag
|
|
||||||
state TextInputState
|
|
||||||
content EditorState
|
|
||||||
}
|
|
||||||
|
|
||||||
type keyHandler struct {
|
|
||||||
// visible will be true if the InputOp is present
|
|
||||||
// in the current frame.
|
|
||||||
visible bool
|
|
||||||
// reset tracks whether the handler has seen a
|
|
||||||
// focus reset.
|
|
||||||
reset bool
|
|
||||||
hint key.InputHint
|
|
||||||
orderPlusOne int
|
|
||||||
dirOrder int
|
|
||||||
trans f32.Affine2D
|
|
||||||
}
|
|
||||||
|
|
||||||
type keyFilter []key.Filter
|
|
||||||
|
|
||||||
type dirFocusEntry struct {
|
|
||||||
tag event.Tag
|
|
||||||
row int
|
|
||||||
area int
|
|
||||||
bounds image.Rectangle
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
TextInputKeep TextInputState = iota
|
|
||||||
TextInputClose
|
|
||||||
TextInputOpen
|
|
||||||
)
|
|
||||||
|
|
||||||
func (k *keyHandler) inputHint(hint key.InputHint) {
|
|
||||||
k.hint = hint
|
|
||||||
}
|
|
||||||
|
|
||||||
// InputState returns the input state and returns a state
|
|
||||||
// reset to [TextInputKeep].
|
|
||||||
func (s keyState) InputState() (keyState, TextInputState) {
|
|
||||||
state := s.state
|
|
||||||
s.state = TextInputKeep
|
|
||||||
return s, state
|
|
||||||
}
|
|
||||||
|
|
||||||
// InputHint returns the input hint from the focused handler and whether it was
|
|
||||||
// changed since the last call.
|
|
||||||
func (q *keyQueue) InputHint(handlers map[event.Tag]*handler, state keyState) (key.InputHint, bool) {
|
|
||||||
focused, ok := handlers[state.focus]
|
|
||||||
if !ok {
|
|
||||||
return q.hint, false
|
|
||||||
}
|
|
||||||
old := q.hint
|
|
||||||
q.hint = focused.key.hint
|
|
||||||
return q.hint, old != q.hint
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *keyHandler) Reset() {
|
|
||||||
k.visible = false
|
|
||||||
k.orderPlusOne = 0
|
|
||||||
k.hint = key.HintAny
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *keyQueue) Reset() {
|
|
||||||
q.order = q.order[:0]
|
|
||||||
q.dirOrder = q.dirOrder[:0]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *keyHandler) ResetEvent() (event.Event, bool) {
|
|
||||||
if k.reset {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
k.reset = true
|
|
||||||
return key.FocusEvent{Focus: false}, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *keyQueue) Frame(handlers map[event.Tag]*handler, state keyState) keyState {
|
|
||||||
if state.focus != nil {
|
|
||||||
if h, ok := handlers[state.focus]; !ok || !h.filter.focusable || !h.key.visible {
|
|
||||||
// Remove focus from the handler that is no longer focusable.
|
|
||||||
state.focus = nil
|
|
||||||
state.state = TextInputClose
|
|
||||||
}
|
|
||||||
}
|
|
||||||
q.updateFocusLayout(handlers)
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateFocusLayout partitions input handlers handlers into rows
|
|
||||||
// for directional focus moves.
|
|
||||||
//
|
|
||||||
// The approach is greedy: pick the topmost handler and create a row
|
|
||||||
// containing it. Then, extend the handler bounds to a horizontal beam
|
|
||||||
// and add to the row every handler whose center intersect it. Repeat
|
|
||||||
// until no handlers remain.
|
|
||||||
func (q *keyQueue) updateFocusLayout(handlers map[event.Tag]*handler) {
|
|
||||||
order := q.dirOrder
|
|
||||||
// Sort by ascending y position.
|
|
||||||
sort.SliceStable(order, func(i, j int) bool {
|
|
||||||
return order[i].bounds.Min.Y < order[j].bounds.Min.Y
|
|
||||||
})
|
|
||||||
row := 0
|
|
||||||
for len(order) > 0 {
|
|
||||||
h := &order[0]
|
|
||||||
h.row = row
|
|
||||||
bottom := h.bounds.Max.Y
|
|
||||||
end := 1
|
|
||||||
for ; end < len(order); end++ {
|
|
||||||
h := &order[end]
|
|
||||||
center := (h.bounds.Min.Y + h.bounds.Max.Y) / 2
|
|
||||||
if center > bottom {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
h.row = row
|
|
||||||
}
|
|
||||||
// Sort row by ascending x position.
|
|
||||||
sort.SliceStable(order[:end], func(i, j int) bool {
|
|
||||||
return order[i].bounds.Min.X < order[j].bounds.Min.X
|
|
||||||
})
|
|
||||||
order = order[end:]
|
|
||||||
row++
|
|
||||||
}
|
|
||||||
for i, o := range q.dirOrder {
|
|
||||||
handlers[o.tag].key.dirOrder = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MoveFocus attempts to move the focus in the direction of dir.
|
|
||||||
func (q *keyQueue) MoveFocus(handlers map[event.Tag]*handler, state keyState, dir key.FocusDirection) (keyState, []taggedEvent) {
|
|
||||||
if len(q.dirOrder) == 0 {
|
|
||||||
return state, nil
|
|
||||||
}
|
|
||||||
order := 0
|
|
||||||
if state.focus != nil {
|
|
||||||
order = handlers[state.focus].key.dirOrder
|
|
||||||
}
|
|
||||||
focus := q.dirOrder[order]
|
|
||||||
switch dir {
|
|
||||||
case key.FocusForward, key.FocusBackward:
|
|
||||||
if len(q.order) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
order := 0
|
|
||||||
if dir == key.FocusBackward {
|
|
||||||
order = -1
|
|
||||||
}
|
|
||||||
if state.focus != nil {
|
|
||||||
order = handlers[state.focus].key.orderPlusOne - 1
|
|
||||||
if dir == key.FocusForward {
|
|
||||||
order++
|
|
||||||
} else {
|
|
||||||
order--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
order = (order + len(q.order)) % len(q.order)
|
|
||||||
return q.Focus(handlers, state, q.order[order])
|
|
||||||
case key.FocusRight, key.FocusLeft:
|
|
||||||
next := order
|
|
||||||
if state.focus != nil {
|
|
||||||
next = order + 1
|
|
||||||
if dir == key.FocusLeft {
|
|
||||||
next = order - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if 0 <= next && next < len(q.dirOrder) {
|
|
||||||
newFocus := q.dirOrder[next]
|
|
||||||
if newFocus.row == focus.row {
|
|
||||||
return q.Focus(handlers, state, newFocus.tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case key.FocusUp, key.FocusDown:
|
|
||||||
delta := +1
|
|
||||||
if dir == key.FocusUp {
|
|
||||||
delta = -1
|
|
||||||
}
|
|
||||||
nextRow := 0
|
|
||||||
if state.focus != nil {
|
|
||||||
nextRow = focus.row + delta
|
|
||||||
}
|
|
||||||
var closest event.Tag
|
|
||||||
dist := int(1e6)
|
|
||||||
center := (focus.bounds.Min.X + focus.bounds.Max.X) / 2
|
|
||||||
loop:
|
|
||||||
for 0 <= order && order < len(q.dirOrder) {
|
|
||||||
next := q.dirOrder[order]
|
|
||||||
switch next.row {
|
|
||||||
case nextRow:
|
|
||||||
nextCenter := (next.bounds.Min.X + next.bounds.Max.X) / 2
|
|
||||||
d := center - nextCenter
|
|
||||||
if d < 0 {
|
|
||||||
d = -d
|
|
||||||
}
|
|
||||||
if d > dist {
|
|
||||||
break loop
|
|
||||||
}
|
|
||||||
dist = d
|
|
||||||
closest = next.tag
|
|
||||||
case nextRow + delta:
|
|
||||||
break loop
|
|
||||||
}
|
|
||||||
order += delta
|
|
||||||
}
|
|
||||||
if closest != nil {
|
|
||||||
return q.Focus(handlers, state, closest)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return state, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *keyQueue) BoundsFor(k *keyHandler) image.Rectangle {
|
|
||||||
order := k.dirOrder
|
|
||||||
return q.dirOrder[order].bounds
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *keyQueue) AreaFor(k *keyHandler) int {
|
|
||||||
order := k.dirOrder
|
|
||||||
return q.dirOrder[order].area
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *keyFilter) Matches(focus event.Tag, e key.Event, system bool) bool {
|
|
||||||
for _, f := range *k {
|
|
||||||
if keyFilterMatch(focus, f, e, system) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func keyFilterMatch(focus event.Tag, f key.Filter, e key.Event, system bool) bool {
|
|
||||||
if f.Focus != nil && f.Focus != focus {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (f.Name != "" || system) && f.Name != e.Name {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if e.Modifiers&f.Required != f.Required {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if e.Modifiers&^(f.Required|f.Optional) != 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *keyQueue) Focus(handlers map[event.Tag]*handler, state keyState, focus event.Tag) (keyState, []taggedEvent) {
|
|
||||||
if focus == state.focus {
|
|
||||||
return state, nil
|
|
||||||
}
|
|
||||||
state.content = EditorState{}
|
|
||||||
state.content.Selection.Transform = f32.AffineId()
|
|
||||||
var evts []taggedEvent
|
|
||||||
if state.focus != nil {
|
|
||||||
evts = append(evts, taggedEvent{tag: state.focus, event: key.FocusEvent{Focus: false}})
|
|
||||||
}
|
|
||||||
state.focus = focus
|
|
||||||
if state.focus != nil {
|
|
||||||
evts = append(evts, taggedEvent{tag: state.focus, event: key.FocusEvent{Focus: true}})
|
|
||||||
}
|
|
||||||
if state.focus == nil || state.state == TextInputKeep {
|
|
||||||
state.state = TextInputClose
|
|
||||||
}
|
|
||||||
return state, evts
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s keyState) softKeyboard(show bool) keyState {
|
|
||||||
if show {
|
|
||||||
s.state = TextInputOpen
|
|
||||||
} else {
|
|
||||||
s.state = TextInputClose
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *keyFilter) Add(f key.Filter) {
|
|
||||||
if slices.Contains(*k, f) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
*k = append(*k, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *keyFilter) Merge(k2 keyFilter) {
|
|
||||||
*k = append(*k, k2...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *keyQueue) inputOp(tag event.Tag, state *keyHandler, t f32.Affine2D, area int, bounds image.Rectangle) {
|
|
||||||
state.visible = true
|
|
||||||
if state.orderPlusOne == 0 {
|
|
||||||
state.orderPlusOne = len(q.order) + 1
|
|
||||||
q.order = append(q.order, tag)
|
|
||||||
q.dirOrder = append(q.dirOrder, dirFocusEntry{tag: tag, area: area, bounds: bounds})
|
|
||||||
}
|
|
||||||
state.trans = t
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *keyQueue) setSelection(state keyState, req key.SelectionCmd) keyState {
|
|
||||||
if req.Tag != state.focus {
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
state.content.Selection.Range = req.Range
|
|
||||||
state.content.Selection.Caret = req.Caret
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *keyQueue) editorState(handlers map[event.Tag]*handler, state keyState) EditorState {
|
|
||||||
s := state.content
|
|
||||||
if f := state.focus; f != nil {
|
|
||||||
s.Selection.Transform = handlers[f].key.trans
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *keyQueue) setSnippet(state keyState, req key.SnippetCmd) keyState {
|
|
||||||
if req.Tag == state.focus {
|
|
||||||
state.content.Snippet = req.Snippet
|
|
||||||
}
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t TextInputState) String() string {
|
|
||||||
switch t {
|
|
||||||
case TextInputKeep:
|
|
||||||
return "Keep"
|
|
||||||
case TextInputClose:
|
|
||||||
return "Close"
|
|
||||||
case TextInputOpen:
|
|
||||||
return "Open"
|
|
||||||
default:
|
|
||||||
panic("unexpected value")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,332 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
package input
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"gioui.org/f32"
|
|
||||||
"gioui.org/io/event"
|
|
||||||
"gioui.org/io/key"
|
|
||||||
"gioui.org/io/pointer"
|
|
||||||
"gioui.org/op"
|
|
||||||
"gioui.org/op/clip"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAllMatchKeyFilter(t *testing.T) {
|
|
||||||
r := new(Router)
|
|
||||||
r.Event(key.Filter{})
|
|
||||||
ke := key.Event{Name: "A"}
|
|
||||||
r.Queue(ke)
|
|
||||||
// Catch-all gets all non-system events.
|
|
||||||
assertEventSequence(t, events(r, -1, key.Filter{}), ke)
|
|
||||||
|
|
||||||
r = new(Router)
|
|
||||||
r.Event(key.Filter{Name: "A"})
|
|
||||||
r.Queue(SystemEvent{ke})
|
|
||||||
if _, handled := r.WakeupTime(); !handled {
|
|
||||||
t.Errorf("system event was unexpectedly ignored")
|
|
||||||
}
|
|
||||||
// Only specific filters match system events.
|
|
||||||
assertEventSequence(t, events(r, -1, key.Filter{Name: "A"}), ke)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInputHint(t *testing.T) {
|
|
||||||
r := new(Router)
|
|
||||||
if hint, changed := r.TextInputHint(); hint != key.HintAny || changed {
|
|
||||||
t.Fatal("unexpected hint")
|
|
||||||
}
|
|
||||||
ops := new(op.Ops)
|
|
||||||
h := new(int)
|
|
||||||
key.InputHintOp{Tag: h, Hint: key.HintEmail}.Add(ops)
|
|
||||||
r.Frame(ops)
|
|
||||||
if hint, changed := r.TextInputHint(); hint != key.HintAny || changed {
|
|
||||||
t.Fatal("unexpected hint")
|
|
||||||
}
|
|
||||||
r.Source().Execute(key.FocusCmd{Tag: h})
|
|
||||||
if hint, changed := r.TextInputHint(); hint != key.HintEmail || !changed {
|
|
||||||
t.Fatal("unexpected hint")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDeferred(t *testing.T) {
|
|
||||||
r := new(Router)
|
|
||||||
h := new(int)
|
|
||||||
f := []event.Filter{
|
|
||||||
key.FocusFilter{Target: h},
|
|
||||||
key.Filter{Name: "A"},
|
|
||||||
}
|
|
||||||
// Provoke deferring by exhausting events for h.
|
|
||||||
events(r, -1, f...)
|
|
||||||
r.Source().Execute(key.FocusCmd{Tag: h})
|
|
||||||
ke := key.Event{Name: "A"}
|
|
||||||
r.Queue(ke)
|
|
||||||
// All events are deferred at this point.
|
|
||||||
assertEventSequence(t, events(r, -1, f...))
|
|
||||||
r.Frame(new(op.Ops))
|
|
||||||
// But delivered after a frame.
|
|
||||||
assertEventSequence(t, events(r, -1, f...), key.FocusEvent{Focus: true}, ke)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInputWakeup(t *testing.T) {
|
|
||||||
handler := new(int)
|
|
||||||
var ops op.Ops
|
|
||||||
// InputOps shouldn't trigger redraws.
|
|
||||||
event.Op(&ops, handler)
|
|
||||||
|
|
||||||
var r Router
|
|
||||||
// Reset events shouldn't either.
|
|
||||||
evts := events(&r, -1, key.FocusFilter{Target: new(int)}, key.Filter{Name: "A"})
|
|
||||||
assertEventSequence(t, evts, key.FocusEvent{Focus: false})
|
|
||||||
r.Frame(&ops)
|
|
||||||
if _, wake := r.WakeupTime(); wake {
|
|
||||||
t.Errorf("InputOp or the resetting FocusEvent triggered a wakeup")
|
|
||||||
}
|
|
||||||
// And neither does events that don't match anything.
|
|
||||||
r.Queue(key.SnippetEvent{})
|
|
||||||
if _, handled := r.WakeupTime(); handled {
|
|
||||||
t.Errorf("a not-matching event triggered a wakeup")
|
|
||||||
}
|
|
||||||
// However, events that does match should trigger wakeup.
|
|
||||||
r.Queue(key.Event{Name: "A"})
|
|
||||||
if _, handled := r.WakeupTime(); !handled {
|
|
||||||
t.Errorf("a key.Event didn't trigger redraw")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestKeyMultiples(t *testing.T) {
|
|
||||||
handlers := make([]int, 3)
|
|
||||||
r := new(Router)
|
|
||||||
r.Source().Execute(key.SoftKeyboardCmd{Show: true})
|
|
||||||
for i := range handlers {
|
|
||||||
assertEventSequence(t, events(r, 1, key.FocusFilter{Target: &handlers[i]}), key.FocusEvent{Focus: false})
|
|
||||||
}
|
|
||||||
r.Source().Execute(key.FocusCmd{Tag: &handlers[2]})
|
|
||||||
assertEventSequence(t, events(r, -1, key.FocusFilter{Target: &handlers[2]}), key.FocusEvent{Focus: true})
|
|
||||||
assertFocus(t, r, &handlers[2])
|
|
||||||
|
|
||||||
assertKeyboard(t, r, TextInputOpen)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestKeySoftKeyboardNoFocus(t *testing.T) {
|
|
||||||
r := new(Router)
|
|
||||||
|
|
||||||
// It's possible to open the keyboard
|
|
||||||
// without any active focus:
|
|
||||||
r.Source().Execute(key.SoftKeyboardCmd{Show: true})
|
|
||||||
|
|
||||||
assertFocus(t, r, nil)
|
|
||||||
assertKeyboard(t, r, TextInputOpen)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestKeyRemoveFocus(t *testing.T) {
|
|
||||||
handlers := make([]int, 2)
|
|
||||||
r := new(Router)
|
|
||||||
|
|
||||||
filters := func(h event.Tag) []event.Filter {
|
|
||||||
return []event.Filter{
|
|
||||||
key.FocusFilter{Target: h},
|
|
||||||
key.Filter{Focus: h, Name: key.NameTab, Required: key.ModShortcut},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var all []event.Filter
|
|
||||||
for i := range handlers {
|
|
||||||
all = append(all, filters(&handlers[i])...)
|
|
||||||
}
|
|
||||||
assertEventSequence(t, events(r, len(handlers), all...), key.FocusEvent{}, key.FocusEvent{})
|
|
||||||
r.Source().Execute(key.FocusCmd{Tag: &handlers[0]})
|
|
||||||
r.Source().Execute(key.SoftKeyboardCmd{Show: true})
|
|
||||||
|
|
||||||
evt := key.Event{Name: key.NameTab, Modifiers: key.ModShortcut, State: key.Press}
|
|
||||||
r.Queue(evt)
|
|
||||||
|
|
||||||
assertEventSequence(t, events(r, 2, filters(&handlers[0])...), key.FocusEvent{Focus: true}, evt)
|
|
||||||
assertFocus(t, r, &handlers[0])
|
|
||||||
assertKeyboard(t, r, TextInputOpen)
|
|
||||||
|
|
||||||
// Frame removes focus from tags that don't filter for focus events nor mentioned in an InputOp.
|
|
||||||
r.Source().Execute(key.FocusCmd{Tag: new(int)})
|
|
||||||
r.Frame(new(op.Ops))
|
|
||||||
|
|
||||||
assertEventSequence(t, events(r, -1, filters(&handlers[1])...))
|
|
||||||
assertFocus(t, r, nil)
|
|
||||||
assertKeyboard(t, r, TextInputClose)
|
|
||||||
|
|
||||||
// Set focus to InputOp which already
|
|
||||||
// exists in the previous frame:
|
|
||||||
r.Source().Execute(key.FocusCmd{Tag: &handlers[0]})
|
|
||||||
assertFocus(t, r, &handlers[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestKeyFocusedInvisible(t *testing.T) {
|
|
||||||
handlers := make([]int, 2)
|
|
||||||
ops := new(op.Ops)
|
|
||||||
r := new(Router)
|
|
||||||
|
|
||||||
for i := range handlers {
|
|
||||||
assertEventSequence(t, events(r, 1, key.FocusFilter{Target: &handlers[i]}), key.FocusEvent{Focus: false})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set new InputOp with focus:
|
|
||||||
r.Source().Execute(key.FocusCmd{Tag: &handlers[0]})
|
|
||||||
r.Source().Execute(key.SoftKeyboardCmd{Show: true})
|
|
||||||
|
|
||||||
assertEventSequence(t, events(r, 1, key.FocusFilter{Target: &handlers[0]}), key.FocusEvent{Focus: true})
|
|
||||||
assertFocus(t, r, &handlers[0])
|
|
||||||
assertKeyboard(t, r, TextInputOpen)
|
|
||||||
|
|
||||||
// Frame will clear the focus because the handler is not visible.
|
|
||||||
r.Frame(ops)
|
|
||||||
|
|
||||||
for i := range handlers {
|
|
||||||
assertEventSequence(t, events(r, -1, key.FocusFilter{Target: &handlers[i]}))
|
|
||||||
}
|
|
||||||
assertFocus(t, r, nil)
|
|
||||||
assertKeyboard(t, r, TextInputClose)
|
|
||||||
|
|
||||||
r.Frame(ops)
|
|
||||||
r.Frame(ops)
|
|
||||||
|
|
||||||
ops.Reset()
|
|
||||||
|
|
||||||
// Respawn the first element:
|
|
||||||
// It must receive one `Event{Focus: false}`.
|
|
||||||
event.Op(ops, &handlers[0])
|
|
||||||
|
|
||||||
assertEventSequence(t, events(r, -1, key.FocusFilter{Target: &handlers[0]}), key.FocusEvent{Focus: false})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoOps(t *testing.T) {
|
|
||||||
r := new(Router)
|
|
||||||
r.Frame(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDirectionalFocus(t *testing.T) {
|
|
||||||
ops := new(op.Ops)
|
|
||||||
r := new(Router)
|
|
||||||
handlers := []image.Rectangle{
|
|
||||||
image.Rect(10, 10, 50, 50),
|
|
||||||
image.Rect(50, 20, 100, 80),
|
|
||||||
image.Rect(20, 26, 60, 80),
|
|
||||||
image.Rect(10, 60, 50, 100),
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, bounds := range handlers {
|
|
||||||
cl := clip.Rect(bounds).Push(ops)
|
|
||||||
event.Op(ops, &handlers[i])
|
|
||||||
cl.Pop()
|
|
||||||
events(r, -1, key.FocusFilter{Target: &handlers[i]})
|
|
||||||
}
|
|
||||||
r.Frame(ops)
|
|
||||||
|
|
||||||
r.MoveFocus(key.FocusLeft)
|
|
||||||
assertFocus(t, r, &handlers[0])
|
|
||||||
r.MoveFocus(key.FocusLeft)
|
|
||||||
assertFocus(t, r, &handlers[0])
|
|
||||||
r.MoveFocus(key.FocusRight)
|
|
||||||
assertFocus(t, r, &handlers[1])
|
|
||||||
r.MoveFocus(key.FocusRight)
|
|
||||||
assertFocus(t, r, &handlers[1])
|
|
||||||
r.MoveFocus(key.FocusDown)
|
|
||||||
assertFocus(t, r, &handlers[2])
|
|
||||||
r.MoveFocus(key.FocusDown)
|
|
||||||
assertFocus(t, r, &handlers[2])
|
|
||||||
r.MoveFocus(key.FocusLeft)
|
|
||||||
assertFocus(t, r, &handlers[3])
|
|
||||||
r.MoveFocus(key.FocusUp)
|
|
||||||
assertFocus(t, r, &handlers[0])
|
|
||||||
|
|
||||||
r.MoveFocus(key.FocusForward)
|
|
||||||
assertFocus(t, r, &handlers[1])
|
|
||||||
r.MoveFocus(key.FocusBackward)
|
|
||||||
assertFocus(t, r, &handlers[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFocusScroll(t *testing.T) {
|
|
||||||
ops := new(op.Ops)
|
|
||||||
r := new(Router)
|
|
||||||
h := new(int)
|
|
||||||
|
|
||||||
filters := []event.Filter{
|
|
||||||
key.FocusFilter{Target: h},
|
|
||||||
pointer.Filter{
|
|
||||||
Target: h,
|
|
||||||
Kinds: pointer.Scroll,
|
|
||||||
ScrollX: pointer.ScrollRange{Min: -100, Max: +100},
|
|
||||||
ScrollY: pointer.ScrollRange{Min: -100, Max: +100},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
events(r, -1, filters...)
|
|
||||||
parent := clip.Rect(image.Rect(1, 1, 14, 39)).Push(ops)
|
|
||||||
cl := clip.Rect(image.Rect(10, -20, 20, 30)).Push(ops)
|
|
||||||
event.Op(ops, h)
|
|
||||||
// Test that h is scrolled even if behind another handler.
|
|
||||||
event.Op(ops, new(int))
|
|
||||||
cl.Pop()
|
|
||||||
parent.Pop()
|
|
||||||
r.Frame(ops)
|
|
||||||
|
|
||||||
r.MoveFocus(key.FocusLeft)
|
|
||||||
r.RevealFocus(image.Rect(0, 0, 15, 40))
|
|
||||||
evts := events(r, -1, filters...)
|
|
||||||
assertScrollEvent(t, evts[len(evts)-1], f32.Pt(6, -9))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFocusClick(t *testing.T) {
|
|
||||||
ops := new(op.Ops)
|
|
||||||
r := new(Router)
|
|
||||||
h := new(int)
|
|
||||||
|
|
||||||
filters := []event.Filter{
|
|
||||||
key.FocusFilter{Target: h},
|
|
||||||
pointer.Filter{
|
|
||||||
Target: h,
|
|
||||||
Kinds: pointer.Press | pointer.Release | pointer.Cancel,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assertEventPointerTypeSequence(t, events(r, -1, filters...), pointer.Cancel)
|
|
||||||
cl := clip.Rect(image.Rect(0, 0, 10, 10)).Push(ops)
|
|
||||||
event.Op(ops, h)
|
|
||||||
cl.Pop()
|
|
||||||
r.Frame(ops)
|
|
||||||
|
|
||||||
r.MoveFocus(key.FocusLeft)
|
|
||||||
r.ClickFocus()
|
|
||||||
|
|
||||||
assertEventPointerTypeSequence(t, events(r, -1, filters...), pointer.Press, pointer.Release)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoFocus(t *testing.T) {
|
|
||||||
r := new(Router)
|
|
||||||
r.MoveFocus(key.FocusForward)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestKeyRouting(t *testing.T) {
|
|
||||||
r := new(Router)
|
|
||||||
h := new(int)
|
|
||||||
A, B := key.Event{Name: "A"}, key.Event{Name: "B"}
|
|
||||||
// Register filters.
|
|
||||||
events(r, -1, key.Filter{Name: "A"}, key.Filter{Name: "B"})
|
|
||||||
r.Frame(new(op.Ops))
|
|
||||||
r.Queue(A, B)
|
|
||||||
// The handler is not focused, so only B is delivered.
|
|
||||||
assertEventSequence(t, events(r, -1, key.Filter{Focus: h, Name: "A"}, key.Filter{Name: "B"}), B)
|
|
||||||
r.Source().Execute(key.FocusCmd{Tag: h})
|
|
||||||
// A is delivered to the focused handler.
|
|
||||||
assertEventSequence(t, events(r, -1, key.Filter{Focus: h, Name: "A"}, key.Filter{Name: "B"}), A)
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertFocus(t *testing.T, router *Router, expected event.Tag) {
|
|
||||||
t.Helper()
|
|
||||||
if !router.Source().Focused(expected) {
|
|
||||||
t.Errorf("expected %v to be focused", expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertKeyboard(t *testing.T, router *Router, expected TextInputState) {
|
|
||||||
t.Helper()
|
|
||||||
if got := router.state().state; got != expected {
|
|
||||||
t.Errorf("expected %v keyboard, got %v", expected, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,893 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
package input
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
"io"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gioui.org/f32"
|
|
||||||
f32internal "gioui.org/internal/f32"
|
|
||||||
"gioui.org/internal/ops"
|
|
||||||
"gioui.org/io/clipboard"
|
|
||||||
"gioui.org/io/event"
|
|
||||||
"gioui.org/io/key"
|
|
||||||
"gioui.org/io/pointer"
|
|
||||||
"gioui.org/io/semantic"
|
|
||||||
"gioui.org/io/system"
|
|
||||||
"gioui.org/io/transfer"
|
|
||||||
"gioui.org/op"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Router tracks the [io/event.Tag] identifiers of user interface widgets
|
|
||||||
// and routes events to them. [Source] is its interface exposed to widgets.
|
|
||||||
type Router struct {
|
|
||||||
savedTrans []f32.Affine2D
|
|
||||||
transStack []f32.Affine2D
|
|
||||||
handlers map[event.Tag]*handler
|
|
||||||
pointer struct {
|
|
||||||
queue pointerQueue
|
|
||||||
collector pointerCollector
|
|
||||||
}
|
|
||||||
key struct {
|
|
||||||
queue keyQueue
|
|
||||||
// The following fields have the same purpose as the fields in
|
|
||||||
// type handler, but for key.Events.
|
|
||||||
filter keyFilter
|
|
||||||
nextFilter keyFilter
|
|
||||||
scratchFilter keyFilter
|
|
||||||
}
|
|
||||||
cqueue clipboardQueue
|
|
||||||
// states is the list of pending state changes resulting from
|
|
||||||
// incoming events. The first element, if present, contains the state
|
|
||||||
// and events for the current frame.
|
|
||||||
changes []stateChange
|
|
||||||
reader ops.Reader
|
|
||||||
// InvalidateCmd summary.
|
|
||||||
wakeup bool
|
|
||||||
wakeupTime time.Time
|
|
||||||
// Changes queued for next call to Frame.
|
|
||||||
commands []Command
|
|
||||||
// transfers is the pending transfer.DataEvent.Open functions.
|
|
||||||
transfers []io.ReadCloser
|
|
||||||
// deferring is set if command execution and event delivery is deferred
|
|
||||||
// to the next frame.
|
|
||||||
deferring bool
|
|
||||||
// scratchFilters is for garbage-free construction of ephemeral filters.
|
|
||||||
scratchFilters []taggedFilter
|
|
||||||
}
|
|
||||||
|
|
||||||
// Source implements the interface between a Router and user interface widgets.
|
|
||||||
// The zero-value Source is disabled.
|
|
||||||
type Source struct {
|
|
||||||
r *Router
|
|
||||||
disabled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command represents a request such as moving the focus, or initiating a clipboard read.
|
|
||||||
// Commands are queued by calling [Source.Queue].
|
|
||||||
type Command interface {
|
|
||||||
ImplementsCommand()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SemanticNode represents a node in the tree describing the components
|
|
||||||
// contained in a frame.
|
|
||||||
type SemanticNode struct {
|
|
||||||
ID SemanticID
|
|
||||||
ParentID SemanticID
|
|
||||||
Children []SemanticNode
|
|
||||||
Desc SemanticDesc
|
|
||||||
|
|
||||||
areaIdx int
|
|
||||||
}
|
|
||||||
|
|
||||||
// SemanticDesc provides a semantic description of a UI component.
|
|
||||||
type SemanticDesc struct {
|
|
||||||
Class semantic.ClassOp
|
|
||||||
Description string
|
|
||||||
Label string
|
|
||||||
Selected bool
|
|
||||||
Disabled bool
|
|
||||||
Gestures SemanticGestures
|
|
||||||
Bounds image.Rectangle
|
|
||||||
}
|
|
||||||
|
|
||||||
// SemanticGestures is a bit-set of supported gestures.
|
|
||||||
type SemanticGestures int
|
|
||||||
|
|
||||||
const (
|
|
||||||
ClickGesture SemanticGestures = 1 << iota
|
|
||||||
ScrollGesture
|
|
||||||
)
|
|
||||||
|
|
||||||
// SemanticID uniquely identifies a SemanticDescription.
|
|
||||||
//
|
|
||||||
// By convention, the zero value denotes the non-existent ID.
|
|
||||||
type SemanticID uint
|
|
||||||
|
|
||||||
// SystemEvent is a marker for events that have platform specific
|
|
||||||
// side-effects. SystemEvents are never matched by catch-all filters.
|
|
||||||
type SystemEvent struct {
|
|
||||||
Event event.Event
|
|
||||||
}
|
|
||||||
|
|
||||||
// handler contains the per-handler state tracked by a [Router].
|
|
||||||
type handler struct {
|
|
||||||
// active tracks whether the handler was active in the current
|
|
||||||
// frame. Router deletes state belonging to inactive handlers during Frame.
|
|
||||||
active bool
|
|
||||||
pointer pointerHandler
|
|
||||||
key keyHandler
|
|
||||||
// filter the handler has asked for through event handling
|
|
||||||
// in the previous frame. It is used for routing events in the
|
|
||||||
// current frame.
|
|
||||||
filter filter
|
|
||||||
// prevFilter is the filter being built in the current frame.
|
|
||||||
nextFilter filter
|
|
||||||
// processedFilter is the filters that have exhausted available events.
|
|
||||||
processedFilter filter
|
|
||||||
}
|
|
||||||
|
|
||||||
// filter is the union of a set of [io/event.Filters].
|
|
||||||
type filter struct {
|
|
||||||
pointer pointerFilter
|
|
||||||
focusable bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// taggedFilter is a filter for a particular tag.
|
|
||||||
type taggedFilter struct {
|
|
||||||
tag event.Tag
|
|
||||||
filter filter
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateChange represents the new state and outgoing events
|
|
||||||
// resulting from an incoming event.
|
|
||||||
type stateChange struct {
|
|
||||||
// event, if set, is the trigger for the change.
|
|
||||||
event event.Event
|
|
||||||
state inputState
|
|
||||||
events []taggedEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
// inputState represent a immutable snapshot of the state required
|
|
||||||
// to route events.
|
|
||||||
type inputState struct {
|
|
||||||
clipboardState
|
|
||||||
keyState
|
|
||||||
pointerState
|
|
||||||
}
|
|
||||||
|
|
||||||
// taggedEvent represents an event and its target handler.
|
|
||||||
type taggedEvent struct {
|
|
||||||
event event.Event
|
|
||||||
tag event.Tag
|
|
||||||
}
|
|
||||||
|
|
||||||
// Source returns a Source backed by this Router.
|
|
||||||
func (q *Router) Source() Source {
|
|
||||||
return Source{r: q}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute a command.
|
|
||||||
func (s Source) Execute(c Command) {
|
|
||||||
if !s.Enabled() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.r.execute(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disabled returns a copy of this source that don't deliver any events.
|
|
||||||
func (s Source) Disabled() Source {
|
|
||||||
s2 := s
|
|
||||||
s2.disabled = true
|
|
||||||
return s2
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enabled reports whether the source is enabled. Only enabled
|
|
||||||
// Sources deliver events.
|
|
||||||
func (s Source) Enabled() bool {
|
|
||||||
return s.r != nil && !s.disabled
|
|
||||||
}
|
|
||||||
|
|
||||||
// Focused reports whether tag is focused, according to the most recent
|
|
||||||
// [key.FocusEvent] delivered.
|
|
||||||
func (s Source) Focused(tag event.Tag) bool {
|
|
||||||
if !s.Enabled() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return s.r.state().keyState.focus == tag
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event returns the next event that matches at least one of filters.
|
|
||||||
// If the source is disabled, no events will be reported.
|
|
||||||
func (s Source) Event(filters ...event.Filter) (event.Event, bool) {
|
|
||||||
if !s.Enabled() {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return s.r.Event(filters...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Router) Event(filters ...event.Filter) (event.Event, bool) {
|
|
||||||
// Merge filters into scratch filters.
|
|
||||||
q.scratchFilters = q.scratchFilters[:0]
|
|
||||||
q.key.scratchFilter = q.key.scratchFilter[:0]
|
|
||||||
for _, f := range filters {
|
|
||||||
var t event.Tag
|
|
||||||
switch f := f.(type) {
|
|
||||||
case key.Filter:
|
|
||||||
q.key.scratchFilter = append(q.key.scratchFilter, f)
|
|
||||||
continue
|
|
||||||
case transfer.SourceFilter:
|
|
||||||
t = f.Target
|
|
||||||
case transfer.TargetFilter:
|
|
||||||
t = f.Target
|
|
||||||
case key.FocusFilter:
|
|
||||||
t = f.Target
|
|
||||||
case pointer.Filter:
|
|
||||||
t = f.Target
|
|
||||||
}
|
|
||||||
if t == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var filter *filter
|
|
||||||
for i := range q.scratchFilters {
|
|
||||||
s := &q.scratchFilters[i]
|
|
||||||
if s.tag == t {
|
|
||||||
filter = &s.filter
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if filter == nil {
|
|
||||||
n := len(q.scratchFilters)
|
|
||||||
if n < cap(q.scratchFilters) {
|
|
||||||
// Re-use previously allocated filter.
|
|
||||||
q.scratchFilters = q.scratchFilters[:n+1]
|
|
||||||
tf := &q.scratchFilters[n]
|
|
||||||
tf.tag = t
|
|
||||||
filter = &tf.filter
|
|
||||||
filter.Reset()
|
|
||||||
} else {
|
|
||||||
q.scratchFilters = append(q.scratchFilters, taggedFilter{tag: t})
|
|
||||||
filter = &q.scratchFilters[n].filter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
filter.Add(f)
|
|
||||||
}
|
|
||||||
for _, tf := range q.scratchFilters {
|
|
||||||
h := q.stateFor(tf.tag)
|
|
||||||
h.filter.Merge(tf.filter)
|
|
||||||
h.nextFilter.Merge(tf.filter)
|
|
||||||
}
|
|
||||||
q.key.filter = append(q.key.filter, q.key.scratchFilter...)
|
|
||||||
q.key.nextFilter = append(q.key.nextFilter, q.key.scratchFilter...)
|
|
||||||
// Deliver reset event, if any.
|
|
||||||
for _, f := range filters {
|
|
||||||
switch f := f.(type) {
|
|
||||||
case key.FocusFilter:
|
|
||||||
if f.Target == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
h := q.stateFor(f.Target)
|
|
||||||
if reset, ok := h.key.ResetEvent(); ok {
|
|
||||||
return reset, true
|
|
||||||
}
|
|
||||||
case pointer.Filter:
|
|
||||||
if f.Target == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
h := q.stateFor(f.Target)
|
|
||||||
if reset, ok := h.pointer.ResetEvent(); ok && h.filter.pointer.Matches(reset) {
|
|
||||||
return reset, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i := range q.changes {
|
|
||||||
if q.deferring && i > 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
change := &q.changes[i]
|
|
||||||
for j, evt := range change.events {
|
|
||||||
match := false
|
|
||||||
switch e := evt.event.(type) {
|
|
||||||
case key.Event:
|
|
||||||
match = q.key.scratchFilter.Matches(change.state.keyState.focus, e, false)
|
|
||||||
default:
|
|
||||||
for _, tf := range q.scratchFilters {
|
|
||||||
if evt.tag == tf.tag && tf.filter.Matches(evt.event) {
|
|
||||||
match = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if match {
|
|
||||||
change.events = slices.Delete(change.events, j, j+1)
|
|
||||||
// Fast forward state to last matched.
|
|
||||||
q.collapseState(i)
|
|
||||||
return evt.event, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, tf := range q.scratchFilters {
|
|
||||||
h := q.stateFor(tf.tag)
|
|
||||||
h.processedFilter.Merge(tf.filter)
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// collapseState in the interval [1;idx] into q.changes[0].
|
|
||||||
func (q *Router) collapseState(idx int) {
|
|
||||||
if idx == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
first := &q.changes[0]
|
|
||||||
first.state = q.changes[idx].state
|
|
||||||
for _, ch := range q.changes[1 : idx+1] {
|
|
||||||
first.events = append(first.events, ch.events...)
|
|
||||||
}
|
|
||||||
q.changes = append(q.changes[:1], q.changes[idx+1:]...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Frame completes the current frame and starts a new with the
|
|
||||||
// handlers from the frame argument. Remaining events are discarded,
|
|
||||||
// unless they were deferred by a command.
|
|
||||||
func (q *Router) Frame(frame *op.Ops) {
|
|
||||||
var remaining []event.Event
|
|
||||||
if n := len(q.changes); n > 0 {
|
|
||||||
if q.deferring {
|
|
||||||
// Collect events for replay.
|
|
||||||
for _, ch := range q.changes[1:] {
|
|
||||||
remaining = append(remaining, ch.event)
|
|
||||||
}
|
|
||||||
q.changes = append(q.changes[:0], stateChange{state: q.changes[0].state})
|
|
||||||
} else {
|
|
||||||
// Collapse state.
|
|
||||||
state := q.changes[n-1].state
|
|
||||||
q.changes = append(q.changes[:0], stateChange{state: state})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, rc := range q.transfers {
|
|
||||||
if rc != nil {
|
|
||||||
rc.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
q.transfers = nil
|
|
||||||
q.deferring = false
|
|
||||||
for _, h := range q.handlers {
|
|
||||||
h.filter, h.nextFilter = h.nextFilter, h.filter
|
|
||||||
h.nextFilter.Reset()
|
|
||||||
h.processedFilter.Reset()
|
|
||||||
h.pointer.Reset()
|
|
||||||
h.key.Reset()
|
|
||||||
}
|
|
||||||
q.key.filter, q.key.nextFilter = q.key.nextFilter, q.key.filter
|
|
||||||
q.key.nextFilter = q.key.nextFilter[:0]
|
|
||||||
var ops *ops.Ops
|
|
||||||
if frame != nil {
|
|
||||||
ops = &frame.Internal
|
|
||||||
}
|
|
||||||
q.reader.Reset(ops)
|
|
||||||
q.collect()
|
|
||||||
for k, h := range q.handlers {
|
|
||||||
if !h.active {
|
|
||||||
delete(q.handlers, k)
|
|
||||||
} else {
|
|
||||||
h.active = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
q.executeCommands()
|
|
||||||
q.Queue(remaining...)
|
|
||||||
st := q.lastState()
|
|
||||||
pst, evts := q.pointer.queue.Frame(q.handlers, st.pointerState)
|
|
||||||
st.pointerState = pst
|
|
||||||
st.keyState = q.key.queue.Frame(q.handlers, q.lastState().keyState)
|
|
||||||
q.changeState(nil, st, evts)
|
|
||||||
|
|
||||||
// Collapse state and events.
|
|
||||||
q.collapseState(len(q.changes) - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Queue events to be routed.
|
|
||||||
func (q *Router) Queue(events ...event.Event) {
|
|
||||||
for _, e := range events {
|
|
||||||
se, system := e.(SystemEvent)
|
|
||||||
if system {
|
|
||||||
e = se.Event
|
|
||||||
}
|
|
||||||
q.processEvent(e, system)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filter) Add(flt event.Filter) {
|
|
||||||
switch flt := flt.(type) {
|
|
||||||
case key.FocusFilter:
|
|
||||||
f.focusable = true
|
|
||||||
case pointer.Filter:
|
|
||||||
f.pointer.Add(flt)
|
|
||||||
case transfer.SourceFilter, transfer.TargetFilter:
|
|
||||||
f.pointer.Add(flt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge f2 into f.
|
|
||||||
func (f *filter) Merge(f2 filter) {
|
|
||||||
f.focusable = f.focusable || f2.focusable
|
|
||||||
f.pointer.Merge(f2.pointer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filter) Matches(e event.Event) bool {
|
|
||||||
switch e.(type) {
|
|
||||||
case key.FocusEvent, key.SnippetEvent, key.EditEvent, key.SelectionEvent:
|
|
||||||
return f.focusable
|
|
||||||
default:
|
|
||||||
return f.pointer.Matches(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filter) Reset() {
|
|
||||||
*f = filter{
|
|
||||||
pointer: pointerFilter{
|
|
||||||
sourceMimes: f.pointer.sourceMimes[:0],
|
|
||||||
targetMimes: f.pointer.targetMimes[:0],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Router) processEvent(e event.Event, system bool) {
|
|
||||||
state := q.lastState()
|
|
||||||
switch e := e.(type) {
|
|
||||||
case pointer.Event:
|
|
||||||
pstate, evts := q.pointer.queue.Push(q.handlers, state.pointerState, e)
|
|
||||||
state.pointerState = pstate
|
|
||||||
q.changeState(e, state, evts)
|
|
||||||
case key.Event:
|
|
||||||
var evts []taggedEvent
|
|
||||||
if q.key.filter.Matches(state.keyState.focus, e, system) {
|
|
||||||
evts = append(evts, taggedEvent{event: e})
|
|
||||||
}
|
|
||||||
q.changeState(e, state, evts)
|
|
||||||
case key.SnippetEvent:
|
|
||||||
// Expand existing, overlapping snippet.
|
|
||||||
if r := state.content.Snippet.Range; rangeOverlaps(r, key.Range(e)) {
|
|
||||||
if e.Start > r.Start {
|
|
||||||
e.Start = r.Start
|
|
||||||
}
|
|
||||||
if e.End < r.End {
|
|
||||||
e.End = r.End
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var evts []taggedEvent
|
|
||||||
if f := state.focus; f != nil {
|
|
||||||
evts = append(evts, taggedEvent{tag: f, event: e})
|
|
||||||
}
|
|
||||||
q.changeState(e, state, evts)
|
|
||||||
case key.EditEvent, key.FocusEvent, key.SelectionEvent:
|
|
||||||
var evts []taggedEvent
|
|
||||||
if f := state.focus; f != nil {
|
|
||||||
evts = append(evts, taggedEvent{tag: f, event: e})
|
|
||||||
}
|
|
||||||
q.changeState(e, state, evts)
|
|
||||||
case transfer.DataEvent:
|
|
||||||
cstate, evts := q.cqueue.Push(state.clipboardState, e)
|
|
||||||
state.clipboardState = cstate
|
|
||||||
q.changeState(e, state, evts)
|
|
||||||
default:
|
|
||||||
panic("unknown event type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Router) execute(c Command) {
|
|
||||||
// The command can be executed immediately if event delivery is not frozen, and
|
|
||||||
// no event receiver has completed their event handling.
|
|
||||||
if !q.deferring {
|
|
||||||
ch := q.executeCommand(c)
|
|
||||||
immediate := true
|
|
||||||
for _, e := range ch.events {
|
|
||||||
h, ok := q.handlers[e.tag]
|
|
||||||
immediate = immediate && (!ok || !h.processedFilter.Matches(e.event))
|
|
||||||
}
|
|
||||||
if immediate {
|
|
||||||
// Hold on to the remaining events for state replay.
|
|
||||||
var evts []event.Event
|
|
||||||
for _, ch := range q.changes {
|
|
||||||
if ch.event != nil {
|
|
||||||
evts = append(evts, ch.event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(q.changes) > 1 {
|
|
||||||
q.changes = q.changes[:1]
|
|
||||||
}
|
|
||||||
q.changeState(nil, ch.state, ch.events)
|
|
||||||
q.Queue(evts...)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
q.deferring = true
|
|
||||||
q.commands = append(q.commands, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Router) state() inputState {
|
|
||||||
if len(q.changes) > 0 {
|
|
||||||
return q.changes[0].state
|
|
||||||
}
|
|
||||||
return inputState{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Router) lastState() inputState {
|
|
||||||
if n := len(q.changes); n > 0 {
|
|
||||||
return q.changes[n-1].state
|
|
||||||
}
|
|
||||||
return inputState{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Router) executeCommands() {
|
|
||||||
for _, c := range q.commands {
|
|
||||||
ch := q.executeCommand(c)
|
|
||||||
q.changeState(nil, ch.state, ch.events)
|
|
||||||
}
|
|
||||||
q.commands = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// executeCommand the command and return the resulting state change along with the
|
|
||||||
// tag the state change depended on, if any.
|
|
||||||
func (q *Router) executeCommand(c Command) stateChange {
|
|
||||||
state := q.state()
|
|
||||||
var evts []taggedEvent
|
|
||||||
switch req := c.(type) {
|
|
||||||
case key.SelectionCmd:
|
|
||||||
state.keyState = q.key.queue.setSelection(state.keyState, req)
|
|
||||||
case key.FocusCmd:
|
|
||||||
state.keyState, evts = q.key.queue.Focus(q.handlers, state.keyState, req.Tag)
|
|
||||||
case key.SoftKeyboardCmd:
|
|
||||||
state.keyState = state.keyState.softKeyboard(req.Show)
|
|
||||||
case key.SnippetCmd:
|
|
||||||
state.keyState = q.key.queue.setSnippet(state.keyState, req)
|
|
||||||
case transfer.OfferCmd:
|
|
||||||
state.pointerState, evts = q.pointer.queue.offerData(q.handlers, state.pointerState, req)
|
|
||||||
case clipboard.WriteCmd:
|
|
||||||
q.cqueue.ProcessWriteClipboard(req)
|
|
||||||
case clipboard.ReadCmd:
|
|
||||||
state.clipboardState = q.cqueue.ProcessReadClipboard(state.clipboardState, req.Tag)
|
|
||||||
case pointer.GrabCmd:
|
|
||||||
state.pointerState, evts = q.pointer.queue.grab(state.pointerState, req)
|
|
||||||
case op.InvalidateCmd:
|
|
||||||
if !q.wakeup || req.At.Before(q.wakeupTime) {
|
|
||||||
q.wakeup = true
|
|
||||||
q.wakeupTime = req.At
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return stateChange{state: state, events: evts}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Router) changeState(e event.Event, state inputState, evts []taggedEvent) {
|
|
||||||
// Wrap pointer.DataEvent.Open functions to detect them not being called.
|
|
||||||
for i := range evts {
|
|
||||||
e := &evts[i]
|
|
||||||
if de, ok := e.event.(transfer.DataEvent); ok {
|
|
||||||
transferIdx := len(q.transfers)
|
|
||||||
data := de.Open()
|
|
||||||
q.transfers = append(q.transfers, data)
|
|
||||||
de.Open = func() io.ReadCloser {
|
|
||||||
q.transfers[transferIdx] = nil
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
e.event = de
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Initialize the first change to contain the current state
|
|
||||||
// and events that are bound for the current frame.
|
|
||||||
if len(q.changes) == 0 {
|
|
||||||
q.changes = append(q.changes, stateChange{})
|
|
||||||
}
|
|
||||||
if e != nil && len(evts) > 0 {
|
|
||||||
// An event triggered events bound for user receivers. Add a state change to be
|
|
||||||
// able to redo the change in case of a command execution.
|
|
||||||
q.changes = append(q.changes, stateChange{event: e, state: state, events: evts})
|
|
||||||
} else {
|
|
||||||
// Otherwise, merge with previous change.
|
|
||||||
prev := &q.changes[len(q.changes)-1]
|
|
||||||
prev.state = state
|
|
||||||
prev.events = append(prev.events, evts...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func rangeOverlaps(r1, r2 key.Range) bool {
|
|
||||||
r1 = rangeNorm(r1)
|
|
||||||
r2 = rangeNorm(r2)
|
|
||||||
return r1.Start <= r2.Start && r2.Start < r1.End ||
|
|
||||||
r1.Start <= r2.End && r2.End < r1.End
|
|
||||||
}
|
|
||||||
|
|
||||||
func rangeNorm(r key.Range) key.Range {
|
|
||||||
if r.End < r.Start {
|
|
||||||
r.End, r.Start = r.Start, r.End
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Router) MoveFocus(dir key.FocusDirection) {
|
|
||||||
state := q.lastState()
|
|
||||||
kstate, evts := q.key.queue.MoveFocus(q.handlers, state.keyState, dir)
|
|
||||||
state.keyState = kstate
|
|
||||||
q.changeState(nil, state, evts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RevealFocus scrolls the current focus (if any) into viewport
|
|
||||||
// if there are scrollable parent handlers.
|
|
||||||
func (q *Router) RevealFocus(viewport image.Rectangle) {
|
|
||||||
state := q.lastState()
|
|
||||||
focus := state.focus
|
|
||||||
if focus == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
kh := &q.handlers[focus].key
|
|
||||||
bounds := q.key.queue.BoundsFor(kh)
|
|
||||||
area := q.key.queue.AreaFor(kh)
|
|
||||||
viewport = q.pointer.queue.ClipFor(area, viewport)
|
|
||||||
|
|
||||||
topleft := bounds.Min.Sub(viewport.Min)
|
|
||||||
topleft = maxPoint(topleft, bounds.Max.Sub(viewport.Max))
|
|
||||||
topleft = minPoint(image.Pt(0, 0), topleft)
|
|
||||||
bottomright := bounds.Max.Sub(viewport.Max)
|
|
||||||
bottomright = minPoint(bottomright, bounds.Min.Sub(viewport.Min))
|
|
||||||
bottomright = maxPoint(image.Pt(0, 0), bottomright)
|
|
||||||
s := topleft
|
|
||||||
if s.X == 0 {
|
|
||||||
s.X = bottomright.X
|
|
||||||
}
|
|
||||||
if s.Y == 0 {
|
|
||||||
s.Y = bottomright.Y
|
|
||||||
}
|
|
||||||
q.ScrollFocus(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ScrollFocus scrolls the focused widget, if any, by dist.
|
|
||||||
func (q *Router) ScrollFocus(dist image.Point) {
|
|
||||||
state := q.lastState()
|
|
||||||
focus := state.focus
|
|
||||||
if focus == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
kh := &q.handlers[focus].key
|
|
||||||
area := q.key.queue.AreaFor(kh)
|
|
||||||
q.changeState(nil, q.lastState(), q.pointer.queue.Deliver(q.handlers, area, pointer.Event{
|
|
||||||
Kind: pointer.Scroll,
|
|
||||||
Source: pointer.Touch,
|
|
||||||
Scroll: f32internal.FPt(dist),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
func maxPoint(p1, p2 image.Point) image.Point {
|
|
||||||
m := p1
|
|
||||||
if p2.X > m.X {
|
|
||||||
m.X = p2.X
|
|
||||||
}
|
|
||||||
if p2.Y > m.Y {
|
|
||||||
m.Y = p2.Y
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func minPoint(p1, p2 image.Point) image.Point {
|
|
||||||
m := p1
|
|
||||||
if p2.X < m.X {
|
|
||||||
m.X = p2.X
|
|
||||||
}
|
|
||||||
if p2.Y < m.Y {
|
|
||||||
m.Y = p2.Y
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Router) ActionAt(p f32.Point) (system.Action, bool) {
|
|
||||||
return q.pointer.queue.ActionAt(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Router) ClickFocus() {
|
|
||||||
focus := q.lastState().focus
|
|
||||||
if focus == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
kh := &q.handlers[focus].key
|
|
||||||
bounds := q.key.queue.BoundsFor(kh)
|
|
||||||
center := bounds.Max.Add(bounds.Min).Div(2)
|
|
||||||
e := pointer.Event{
|
|
||||||
Position: f32.Pt(float32(center.X), float32(center.Y)),
|
|
||||||
Source: pointer.Touch,
|
|
||||||
}
|
|
||||||
area := q.key.queue.AreaFor(kh)
|
|
||||||
e.Kind = pointer.Press
|
|
||||||
state := q.lastState()
|
|
||||||
q.changeState(nil, state, q.pointer.queue.Deliver(q.handlers, area, e))
|
|
||||||
e.Kind = pointer.Release
|
|
||||||
q.changeState(nil, state, q.pointer.queue.Deliver(q.handlers, area, e))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TextInputState returns the input state from the most recent
|
|
||||||
// call to Frame.
|
|
||||||
func (q *Router) TextInputState() TextInputState {
|
|
||||||
state := q.state()
|
|
||||||
kstate, s := state.InputState()
|
|
||||||
state.keyState = kstate
|
|
||||||
q.changeState(nil, state, nil)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// TextInputHint returns the input mode from the most recent key.InputOp.
|
|
||||||
func (q *Router) TextInputHint() (key.InputHint, bool) {
|
|
||||||
return q.key.queue.InputHint(q.handlers, q.state().keyState)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteClipboard returns the most recent content to be copied
|
|
||||||
// to the clipboard, if any.
|
|
||||||
func (q *Router) WriteClipboard() (mime string, content []byte, ok bool) {
|
|
||||||
return q.cqueue.WriteClipboard()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClipboardRequested reports if any new handler is waiting
|
|
||||||
// to read the clipboard.
|
|
||||||
func (q *Router) ClipboardRequested() bool {
|
|
||||||
return q.cqueue.ClipboardRequested(q.lastState().clipboardState)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cursor returns the last cursor set.
|
|
||||||
func (q *Router) Cursor() pointer.Cursor {
|
|
||||||
return q.state().cursor
|
|
||||||
}
|
|
||||||
|
|
||||||
// SemanticAt returns the first semantic description under pos, if any.
|
|
||||||
func (q *Router) SemanticAt(pos f32.Point) (SemanticID, bool) {
|
|
||||||
return q.pointer.queue.SemanticAt(pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppendSemantics appends the semantic tree to nodes, and returns the result.
|
|
||||||
// The root node is the first added.
|
|
||||||
func (q *Router) AppendSemantics(nodes []SemanticNode) []SemanticNode {
|
|
||||||
q.pointer.collector.q = &q.pointer.queue
|
|
||||||
q.pointer.collector.ensureRoot()
|
|
||||||
return q.pointer.queue.AppendSemantics(nodes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditorState returns the editor state for the focused handler, or the
|
|
||||||
// zero value if there is none.
|
|
||||||
func (q *Router) EditorState() EditorState {
|
|
||||||
return q.key.queue.editorState(q.handlers, q.state().keyState)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Router) stateFor(tag event.Tag) *handler {
|
|
||||||
if tag == nil {
|
|
||||||
panic("internal error: nil tag")
|
|
||||||
}
|
|
||||||
s, ok := q.handlers[tag]
|
|
||||||
if !ok {
|
|
||||||
s = new(handler)
|
|
||||||
if q.handlers == nil {
|
|
||||||
q.handlers = make(map[event.Tag]*handler)
|
|
||||||
}
|
|
||||||
q.handlers[tag] = s
|
|
||||||
}
|
|
||||||
s.active = true
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Router) collect() {
|
|
||||||
q.transStack = q.transStack[:0]
|
|
||||||
pc := &q.pointer.collector
|
|
||||||
pc.q = &q.pointer.queue
|
|
||||||
pc.Reset()
|
|
||||||
kq := &q.key.queue
|
|
||||||
q.key.queue.Reset()
|
|
||||||
t := f32.AffineId()
|
|
||||||
for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() {
|
|
||||||
switch ops.OpType(encOp.Data[0]) {
|
|
||||||
case ops.TypeSave:
|
|
||||||
id := ops.DecodeSave(encOp.Data)
|
|
||||||
if extra := id - len(q.savedTrans) + 1; extra > 0 {
|
|
||||||
for range extra {
|
|
||||||
q.savedTrans = append(q.savedTrans, f32.AffineId())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
q.savedTrans[id] = t
|
|
||||||
case ops.TypeLoad:
|
|
||||||
id := ops.DecodeLoad(encOp.Data)
|
|
||||||
t = q.savedTrans[id]
|
|
||||||
pc.resetState()
|
|
||||||
pc.setTrans(t)
|
|
||||||
|
|
||||||
case ops.TypeClip:
|
|
||||||
var op ops.ClipOp
|
|
||||||
op.Decode(encOp.Data)
|
|
||||||
pc.clip(op)
|
|
||||||
case ops.TypePopClip:
|
|
||||||
pc.popArea()
|
|
||||||
case ops.TypeTransform:
|
|
||||||
t2, push := ops.DecodeTransform(encOp.Data)
|
|
||||||
if push {
|
|
||||||
q.transStack = append(q.transStack, t)
|
|
||||||
}
|
|
||||||
t = t.Mul(t2)
|
|
||||||
pc.setTrans(t)
|
|
||||||
case ops.TypePopTransform:
|
|
||||||
n := len(q.transStack)
|
|
||||||
t = q.transStack[n-1]
|
|
||||||
q.transStack = q.transStack[:n-1]
|
|
||||||
pc.setTrans(t)
|
|
||||||
|
|
||||||
case ops.TypeInput:
|
|
||||||
tag := encOp.Refs[0].(event.Tag)
|
|
||||||
s := q.stateFor(tag)
|
|
||||||
pc.inputOp(tag, &s.pointer)
|
|
||||||
a := pc.currentArea()
|
|
||||||
b := pc.currentAreaBounds()
|
|
||||||
if s.filter.focusable {
|
|
||||||
kq.inputOp(tag, &s.key, t, a, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pointer ops.
|
|
||||||
case ops.TypePass:
|
|
||||||
pc.pass()
|
|
||||||
case ops.TypePopPass:
|
|
||||||
pc.popPass()
|
|
||||||
case ops.TypeCursor:
|
|
||||||
name := pointer.Cursor(encOp.Data[1])
|
|
||||||
pc.cursor(name)
|
|
||||||
case ops.TypeActionInput:
|
|
||||||
act := system.Action(encOp.Data[1])
|
|
||||||
pc.actionInputOp(act)
|
|
||||||
case ops.TypeKeyInputHint:
|
|
||||||
op := key.InputHintOp{
|
|
||||||
Tag: encOp.Refs[0].(event.Tag),
|
|
||||||
Hint: key.InputHint(encOp.Data[1]),
|
|
||||||
}
|
|
||||||
s := q.stateFor(op.Tag)
|
|
||||||
s.key.inputHint(op.Hint)
|
|
||||||
|
|
||||||
// Semantic ops.
|
|
||||||
case ops.TypeSemanticLabel:
|
|
||||||
lbl := *encOp.Refs[0].(*string)
|
|
||||||
pc.semanticLabel(lbl)
|
|
||||||
case ops.TypeSemanticDesc:
|
|
||||||
desc := *encOp.Refs[0].(*string)
|
|
||||||
pc.semanticDesc(desc)
|
|
||||||
case ops.TypeSemanticClass:
|
|
||||||
class := semantic.ClassOp(encOp.Data[1])
|
|
||||||
pc.semanticClass(class)
|
|
||||||
case ops.TypeSemanticSelected:
|
|
||||||
if encOp.Data[1] != 0 {
|
|
||||||
pc.semanticSelected(true)
|
|
||||||
} else {
|
|
||||||
pc.semanticSelected(false)
|
|
||||||
}
|
|
||||||
case ops.TypeSemanticEnabled:
|
|
||||||
if encOp.Data[1] != 0 {
|
|
||||||
pc.semanticEnabled(true)
|
|
||||||
} else {
|
|
||||||
pc.semanticEnabled(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WakeupTime returns the most recent time for doing another frame,
|
|
||||||
// as determined from the last call to Frame.
|
|
||||||
func (q *Router) WakeupTime() (time.Time, bool) {
|
|
||||||
t, w := q.wakeupTime, q.wakeup
|
|
||||||
q.wakeup = false
|
|
||||||
// Pending events always trigger wakeups.
|
|
||||||
if len(q.changes) > 1 || len(q.changes) == 1 && len(q.changes[0].events) > 0 {
|
|
||||||
t, w = time.Time{}, true
|
|
||||||
}
|
|
||||||
return t, w
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SemanticGestures) String() string {
|
|
||||||
var gestures []string
|
|
||||||
if s&ClickGesture != 0 {
|
|
||||||
gestures = append(gestures, "Click")
|
|
||||||
}
|
|
||||||
return strings.Join(gestures, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (SystemEvent) ImplementsEvent() {}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
package input
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"gioui.org/io/pointer"
|
|
||||||
"gioui.org/op"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNoFilterAllocs(t *testing.T) {
|
|
||||||
b := testing.Benchmark(func(b *testing.B) {
|
|
||||||
var r Router
|
|
||||||
s := r.Source()
|
|
||||||
b.ReportAllocs()
|
|
||||||
b.ResetTimer()
|
|
||||||
for b.Loop() {
|
|
||||||
s.Event(pointer.Filter{})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if allocs := b.AllocsPerOp(); allocs != 0 {
|
|
||||||
t.Fatalf("expected 0 AllocsPerOp, got %d", allocs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRouterWakeup(t *testing.T) {
|
|
||||||
r := new(Router)
|
|
||||||
r.Source().Execute(op.InvalidateCmd{})
|
|
||||||
r.Frame(new(op.Ops))
|
|
||||||
if _, wake := r.WakeupTime(); !wake {
|
|
||||||
t.Errorf("InvalidateCmd did not trigger a redraw")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+219
-105
@@ -1,9 +1,18 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
// Package key implements key and text events and operations.
|
/*
|
||||||
|
Package key implements key and text events and operations.
|
||||||
|
|
||||||
|
The InputOp operations is used for declaring key input handlers. Use
|
||||||
|
an implementation of the Queue interface from package ui to receive
|
||||||
|
events.
|
||||||
|
*/
|
||||||
package key
|
package key
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
@@ -12,40 +21,59 @@ import (
|
|||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Filter matches any [Event] that matches the parameters.
|
// InputOp declares a handler ready for key events.
|
||||||
type Filter struct {
|
// Key events are in general only delivered to the
|
||||||
// Focus is the tag that must be focused for the filter to match. It has no effect
|
// focused key handler.
|
||||||
// if it is nil.
|
type InputOp struct {
|
||||||
Focus event.Tag
|
Tag event.Tag
|
||||||
// Required is the set of modifiers that must be included in events matched.
|
// Hint describes the type of text expected by Tag.
|
||||||
Required Modifiers
|
|
||||||
// Optional is the set of modifiers that may be included in events matched.
|
|
||||||
Optional Modifiers
|
|
||||||
// Name of the key to be matched. As a special case, the empty
|
|
||||||
// Name matches every key not matched by any other filter.
|
|
||||||
Name Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// InputHintOp describes the type of text expected by a tag.
|
|
||||||
type InputHintOp struct {
|
|
||||||
Tag event.Tag
|
|
||||||
Hint InputHint
|
Hint InputHint
|
||||||
|
// Keys is the set of keys Tag can handle. That is, Tag will only
|
||||||
|
// receive an Event if its key and modifiers are accepted by Keys.Contains.
|
||||||
|
// As a special case, the topmost (first added) InputOp handler receives all
|
||||||
|
// unhandled events.
|
||||||
|
Keys Set
|
||||||
}
|
}
|
||||||
|
|
||||||
// SoftKeyboardCmd shows or hides the on-screen keyboard, if available.
|
// Set is an expression that describes a set of key combinations, in the form
|
||||||
type SoftKeyboardCmd struct {
|
// "<modifiers>-<keyset>|...". Modifiers are separated by dashes, optional
|
||||||
|
// modifiers are enclosed by parentheses. A key set is either a literal key
|
||||||
|
// name or a list of key names separated by commas and enclosed in brackets.
|
||||||
|
//
|
||||||
|
// The "Short" modifier matches the shortcut modifier (ModShortcut) and
|
||||||
|
// "ShortAlt" matches the alternative modifier (ModShortcutAlt).
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
//
|
||||||
|
// - A|B matches the A and B keys
|
||||||
|
// - [A,B] also matches the A and B keys
|
||||||
|
// - Shift-A matches A key if shift is pressed, and no other modifier.
|
||||||
|
// - Shift-(Ctrl)-A matches A if shift is pressed, and optionally ctrl.
|
||||||
|
type Set string
|
||||||
|
|
||||||
|
// SoftKeyboardOp shows or hide the on-screen keyboard, if available.
|
||||||
|
// It replaces any previous SoftKeyboardOp.
|
||||||
|
type SoftKeyboardOp struct {
|
||||||
Show bool
|
Show bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// SelectionCmd updates the selection for an input handler.
|
// FocusOp sets or clears the keyboard focus. It replaces any previous
|
||||||
type SelectionCmd struct {
|
// FocusOp in the same frame.
|
||||||
|
type FocusOp struct {
|
||||||
|
// Tag is the new focus. The focus is cleared if Tag is nil, or if Tag
|
||||||
|
// has no InputOp in the same frame.
|
||||||
|
Tag event.Tag
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectionOp updates the selection for an input handler.
|
||||||
|
type SelectionOp struct {
|
||||||
Tag event.Tag
|
Tag event.Tag
|
||||||
Range
|
Range
|
||||||
Caret
|
Caret
|
||||||
}
|
}
|
||||||
|
|
||||||
// SnippetCmd updates the content snippet for an input handler.
|
// SnippetOp updates the content snippet for an input handler.
|
||||||
type SnippetCmd struct {
|
type SnippetOp struct {
|
||||||
Tag event.Tag
|
Tag event.Tag
|
||||||
Snippet
|
Snippet
|
||||||
}
|
}
|
||||||
@@ -90,8 +118,11 @@ type FocusEvent struct {
|
|||||||
// An Event is generated when a key is pressed. For text input
|
// An Event is generated when a key is pressed. For text input
|
||||||
// use EditEvent.
|
// use EditEvent.
|
||||||
type Event struct {
|
type Event struct {
|
||||||
// Name of the key.
|
// Name of the key. For letters, the upper case form is used, via
|
||||||
Name Name
|
// unicode.ToUpper. The shift modifier is taken into account, all other
|
||||||
|
// modifiers are ignored. For example, the "shift-1" and "ctrl-shift-1"
|
||||||
|
// combinations both give the Name "!" with the US keyboard layout.
|
||||||
|
Name string
|
||||||
// Modifiers is the set of active modifiers when the key was pressed.
|
// Modifiers is the set of active modifiers when the key was pressed.
|
||||||
Modifiers Modifiers
|
Modifiers Modifiers
|
||||||
// State is the state of the key when the event was fired.
|
// State is the state of the key when the event was fired.
|
||||||
@@ -105,13 +136,6 @@ type EditEvent struct {
|
|||||||
Text string
|
Text string
|
||||||
}
|
}
|
||||||
|
|
||||||
// FocusFilter matches any [FocusEvent], [EditEvent], [SnippetEvent],
|
|
||||||
// or [SelectionEvent] with the specified target.
|
|
||||||
type FocusFilter struct {
|
|
||||||
// Target is a tag specified in a previous event.Op.
|
|
||||||
Target event.Tag
|
|
||||||
}
|
|
||||||
|
|
||||||
// InputHint changes the on-screen-keyboard type. That hints the
|
// InputHint changes the on-screen-keyboard type. That hints the
|
||||||
// type of data that might be entered by the user.
|
// type of data that might be entered by the user.
|
||||||
type InputHint uint8
|
type InputHint uint8
|
||||||
@@ -165,60 +189,41 @@ const (
|
|||||||
ModSuper
|
ModSuper
|
||||||
)
|
)
|
||||||
|
|
||||||
// Name is the identifier for a keyboard key.
|
|
||||||
//
|
|
||||||
// For letters, the upper case form is used, via unicode.ToUpper.
|
|
||||||
// The shift modifier is taken into account, all other
|
|
||||||
// modifiers are ignored. For example, the "shift-1" and "ctrl-shift-1"
|
|
||||||
// combinations both give the Name "!" with the US keyboard layout.
|
|
||||||
type Name string
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Names for special keys.
|
// Names for special keys.
|
||||||
NameLeftArrow Name = "←"
|
NameLeftArrow = "←"
|
||||||
NameRightArrow Name = "→"
|
NameRightArrow = "→"
|
||||||
NameUpArrow Name = "↑"
|
NameUpArrow = "↑"
|
||||||
NameDownArrow Name = "↓"
|
NameDownArrow = "↓"
|
||||||
NameReturn Name = "⏎"
|
NameReturn = "⏎"
|
||||||
NameEnter Name = "⌤"
|
NameEnter = "⌤"
|
||||||
NameEscape Name = "⎋"
|
NameEscape = "⎋"
|
||||||
NameHome Name = "⇱"
|
NameHome = "⇱"
|
||||||
NameEnd Name = "⇲"
|
NameEnd = "⇲"
|
||||||
NameDeleteBackward Name = "⌫"
|
NameDeleteBackward = "⌫"
|
||||||
NameDeleteForward Name = "⌦"
|
NameDeleteForward = "⌦"
|
||||||
NamePageUp Name = "⇞"
|
NamePageUp = "⇞"
|
||||||
NamePageDown Name = "⇟"
|
NamePageDown = "⇟"
|
||||||
NameTab Name = "Tab"
|
NameTab = "Tab"
|
||||||
NameSpace Name = "Space"
|
NameSpace = "Space"
|
||||||
NameCtrl Name = "Ctrl"
|
NameCtrl = "Ctrl"
|
||||||
NameShift Name = "Shift"
|
NameShift = "Shift"
|
||||||
NameAlt Name = "Alt"
|
NameAlt = "Alt"
|
||||||
NameSuper Name = "Super"
|
NameSuper = "Super"
|
||||||
NameCommand Name = "⌘"
|
NameCommand = "⌘"
|
||||||
NameF1 Name = "F1"
|
NameF1 = "F1"
|
||||||
NameF2 Name = "F2"
|
NameF2 = "F2"
|
||||||
NameF3 Name = "F3"
|
NameF3 = "F3"
|
||||||
NameF4 Name = "F4"
|
NameF4 = "F4"
|
||||||
NameF5 Name = "F5"
|
NameF5 = "F5"
|
||||||
NameF6 Name = "F6"
|
NameF6 = "F6"
|
||||||
NameF7 Name = "F7"
|
NameF7 = "F7"
|
||||||
NameF8 Name = "F8"
|
NameF8 = "F8"
|
||||||
NameF9 Name = "F9"
|
NameF9 = "F9"
|
||||||
NameF10 Name = "F10"
|
NameF10 = "F10"
|
||||||
NameF11 Name = "F11"
|
NameF11 = "F11"
|
||||||
NameF12 Name = "F12"
|
NameF12 = "F12"
|
||||||
NameBack Name = "Back"
|
NameBack = "Back"
|
||||||
)
|
|
||||||
|
|
||||||
type FocusDirection int
|
|
||||||
|
|
||||||
const (
|
|
||||||
FocusRight FocusDirection = iota
|
|
||||||
FocusLeft
|
|
||||||
FocusUp
|
|
||||||
FocusDown
|
|
||||||
FocusForward
|
|
||||||
FocusBackward
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Contain reports whether m contains all modifiers
|
// Contain reports whether m contains all modifiers
|
||||||
@@ -227,52 +232,161 @@ func (m Modifiers) Contain(m2 Modifiers) bool {
|
|||||||
return m&m2 == m2
|
return m&m2 == m2
|
||||||
}
|
}
|
||||||
|
|
||||||
// FocusCmd requests to set or clear the keyboard focus.
|
func (k Set) Contains(name string, mods Modifiers) bool {
|
||||||
type FocusCmd struct {
|
ks := string(k)
|
||||||
// Tag is the new focus. The focus is cleared if Tag is nil, or if Tag
|
for len(ks) > 0 {
|
||||||
// has no [event.Op] references.
|
// Cut next key expression.
|
||||||
Tag event.Tag
|
chord, rest, _ := cut(ks, "|")
|
||||||
|
ks = rest
|
||||||
|
// Separate key set and modifier set.
|
||||||
|
var modSet, keySet string
|
||||||
|
sep := strings.LastIndex(chord, "-")
|
||||||
|
if sep != -1 {
|
||||||
|
modSet, keySet = chord[:sep], chord[sep+1:]
|
||||||
|
} else {
|
||||||
|
modSet, keySet = "", chord
|
||||||
|
}
|
||||||
|
if !keySetContains(keySet, name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if modSetContains(modSet, mods) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h InputHintOp) Add(o *op.Ops) {
|
func keySetContains(keySet, name string) bool {
|
||||||
|
// Check for single key match.
|
||||||
|
if keySet == name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Check for set match.
|
||||||
|
if len(keySet) < 2 || keySet[0] != '[' || keySet[len(keySet)-1] != ']' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
keySet = keySet[1 : len(keySet)-1]
|
||||||
|
for len(keySet) > 0 {
|
||||||
|
key, rest, _ := cut(keySet, ",")
|
||||||
|
keySet = rest
|
||||||
|
if key == name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func modSetContains(modSet string, mods Modifiers) bool {
|
||||||
|
var smods Modifiers
|
||||||
|
for len(modSet) > 0 {
|
||||||
|
mod, rest, _ := cut(modSet, "-")
|
||||||
|
modSet = rest
|
||||||
|
if len(mod) >= 2 && mod[0] == '(' && mod[len(mod)-1] == ')' {
|
||||||
|
mods &^= modFor(mod[1 : len(mod)-1])
|
||||||
|
} else {
|
||||||
|
smods |= modFor(mod)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mods == smods
|
||||||
|
}
|
||||||
|
|
||||||
|
// cut is a copy of the standard library strings.Cut.
|
||||||
|
// TODO: remove when Go 1.18 is our minimum.
|
||||||
|
func cut(s, sep string) (before, after string, found bool) {
|
||||||
|
if i := strings.Index(s, sep); i >= 0 {
|
||||||
|
return s[:i], s[i+len(sep):], true
|
||||||
|
}
|
||||||
|
return s, "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func modFor(name string) Modifiers {
|
||||||
|
switch name {
|
||||||
|
case NameCtrl:
|
||||||
|
return ModCtrl
|
||||||
|
case NameShift:
|
||||||
|
return ModShift
|
||||||
|
case NameAlt:
|
||||||
|
return ModAlt
|
||||||
|
case NameSuper:
|
||||||
|
return ModSuper
|
||||||
|
case NameCommand:
|
||||||
|
return ModCommand
|
||||||
|
case "Short":
|
||||||
|
return ModShortcut
|
||||||
|
case "ShortAlt":
|
||||||
|
return ModShortcutAlt
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h InputOp) Add(o *op.Ops) {
|
||||||
if h.Tag == nil {
|
if h.Tag == nil {
|
||||||
panic("Tag must be non-nil")
|
panic("Tag must be non-nil")
|
||||||
}
|
}
|
||||||
data := ops.Write1(&o.Internal, ops.TypeKeyInputHintLen, h.Tag)
|
data := ops.Write2String(&o.Internal, ops.TypeKeyInputLen, h.Tag, string(h.Keys))
|
||||||
data[0] = byte(ops.TypeKeyInputHint)
|
data[0] = byte(ops.TypeKeyInput)
|
||||||
data[1] = byte(h.Hint)
|
data[1] = byte(h.Hint)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h SoftKeyboardOp) Add(o *op.Ops) {
|
||||||
|
data := ops.Write(&o.Internal, ops.TypeKeySoftKeyboardLen)
|
||||||
|
data[0] = byte(ops.TypeKeySoftKeyboard)
|
||||||
|
if h.Show {
|
||||||
|
data[1] = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h FocusOp) Add(o *op.Ops) {
|
||||||
|
data := ops.Write1(&o.Internal, ops.TypeKeyFocusLen, h.Tag)
|
||||||
|
data[0] = byte(ops.TypeKeyFocus)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SnippetOp) Add(o *op.Ops) {
|
||||||
|
data := ops.Write2String(&o.Internal, ops.TypeSnippetLen, s.Tag, s.Text)
|
||||||
|
data[0] = byte(ops.TypeSnippet)
|
||||||
|
bo := binary.LittleEndian
|
||||||
|
bo.PutUint32(data[1:], uint32(s.Range.Start))
|
||||||
|
bo.PutUint32(data[5:], uint32(s.Range.End))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SelectionOp) Add(o *op.Ops) {
|
||||||
|
data := ops.Write1(&o.Internal, ops.TypeSelectionLen, s.Tag)
|
||||||
|
data[0] = byte(ops.TypeSelection)
|
||||||
|
bo := binary.LittleEndian
|
||||||
|
bo.PutUint32(data[1:], uint32(s.Start))
|
||||||
|
bo.PutUint32(data[5:], uint32(s.End))
|
||||||
|
bo.PutUint32(data[9:], math.Float32bits(s.Pos.X))
|
||||||
|
bo.PutUint32(data[13:], math.Float32bits(s.Pos.Y))
|
||||||
|
bo.PutUint32(data[17:], math.Float32bits(s.Ascent))
|
||||||
|
bo.PutUint32(data[21:], math.Float32bits(s.Descent))
|
||||||
|
}
|
||||||
|
|
||||||
func (EditEvent) ImplementsEvent() {}
|
func (EditEvent) ImplementsEvent() {}
|
||||||
func (Event) ImplementsEvent() {}
|
func (Event) ImplementsEvent() {}
|
||||||
func (FocusEvent) ImplementsEvent() {}
|
func (FocusEvent) ImplementsEvent() {}
|
||||||
func (SnippetEvent) ImplementsEvent() {}
|
func (SnippetEvent) ImplementsEvent() {}
|
||||||
func (SelectionEvent) ImplementsEvent() {}
|
func (SelectionEvent) ImplementsEvent() {}
|
||||||
|
|
||||||
func (FocusCmd) ImplementsCommand() {}
|
func (e Event) String() string {
|
||||||
func (SoftKeyboardCmd) ImplementsCommand() {}
|
return fmt.Sprintf("%v %v %v}", e.Name, e.Modifiers, e.State)
|
||||||
func (SelectionCmd) ImplementsCommand() {}
|
}
|
||||||
func (SnippetCmd) ImplementsCommand() {}
|
|
||||||
|
|
||||||
func (Filter) ImplementsFilter() {}
|
|
||||||
func (FocusFilter) ImplementsFilter() {}
|
|
||||||
|
|
||||||
func (m Modifiers) String() string {
|
func (m Modifiers) String() string {
|
||||||
var strs []string
|
var strs []string
|
||||||
if m.Contain(ModCtrl) {
|
if m.Contain(ModCtrl) {
|
||||||
strs = append(strs, string(NameCtrl))
|
strs = append(strs, NameCtrl)
|
||||||
}
|
}
|
||||||
if m.Contain(ModCommand) {
|
if m.Contain(ModCommand) {
|
||||||
strs = append(strs, string(NameCommand))
|
strs = append(strs, NameCommand)
|
||||||
}
|
}
|
||||||
if m.Contain(ModShift) {
|
if m.Contain(ModShift) {
|
||||||
strs = append(strs, string(NameShift))
|
strs = append(strs, NameShift)
|
||||||
}
|
}
|
||||||
if m.Contain(ModAlt) {
|
if m.Contain(ModAlt) {
|
||||||
strs = append(strs, string(NameAlt))
|
strs = append(strs, NameAlt)
|
||||||
}
|
}
|
||||||
if m.Contain(ModSuper) {
|
if m.Contain(ModSuper) {
|
||||||
strs = append(strs, string(NameSuper))
|
strs = append(strs, NameSuper)
|
||||||
}
|
}
|
||||||
return strings.Join(strs, "-")
|
return strings.Join(strs, "-")
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user