forked from joejulian/gio
Compare commits
230 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3f4f8ba7c1 | |||
| 6553915e59 | |||
| 4c0e526c0b | |||
| f45039734f | |||
| 30f8ac10b7 | |||
| 176570527d | |||
| 36a2fa37c7 | |||
| bbb6d05f09 | |||
| a274f6fe0f | |||
| 0c145b3815 | |||
| ba82ae46d0 | |||
| 4e5a344cc2 | |||
| 78b54615cc | |||
| 31564b98c9 | |||
| 3b1effb7f5 | |||
| d76b4272aa | |||
| 3e601e73c4 | |||
| b2b12d6288 | |||
| b2f6707ad1 | |||
| 420f4c32f4 | |||
| ea979b436d | |||
| d50ef687b8 | |||
| 6ce7ffa4ca | |||
| c3ce484b5e | |||
| 8104d527c7 | |||
| 809a6d0dc7 | |||
| 0eac4f2c6a | |||
| 1a17e9ea37 | |||
| 0a209f7d39 | |||
| 0225334124 | |||
| f73287be87 | |||
| 86668e8b45 | |||
| e18db64991 | |||
| efd31ad621 | |||
| 35ec76e516 | |||
| cc6048bc25 | |||
| 016714a668 | |||
| a3117d3823 | |||
| fff2375470 | |||
| 72a72a2bc2 | |||
| 14a9fbcc0d | |||
| 0d23240556 | |||
| 77709d1771 | |||
| 4f720af6f2 | |||
| af446e8b87 | |||
| 95354d80bd | |||
| a5068a1996 | |||
| 593c5fbf4a | |||
| adaace864d | |||
| f200f0e9a3 | |||
| 1ae2b9b8fe | |||
| fe4bf00c70 | |||
| 8107ec2206 | |||
| d2db4f6875 | |||
| 971f86ea7e | |||
| e1fbb189e5 | |||
| a206e5e847 | |||
| e025ed1344 | |||
| b576252963 | |||
| 1d0d5f0383 | |||
| 26cddb00b1 | |||
| 0cbbacc45a | |||
| 1d95c7c6b3 | |||
| 7337c06daf | |||
| 94355e5201 | |||
| ea456f42c7 | |||
| 8daff13af6 | |||
| aa158e0c9c | |||
| 520efdfa75 | |||
| d7a1ec7461 | |||
| d4c5e54375 | |||
| 44ac50506d | |||
| 38e4b1c6a4 | |||
| 5d886b4d7f | |||
| c7277581f8 | |||
| a5f7e7b2c7 | |||
| 0781e62b56 | |||
| 95f63c66b6 | |||
| 6efcb65c4b | |||
| f6e9f6861d | |||
| 6c6cc157c4 | |||
| 8cf449034c | |||
| 6722c7960a | |||
| 97044e53b5 | |||
| af6dda67a5 | |||
| 042ed4ab49 | |||
| 6aa027136e | |||
| b1db32ef72 | |||
| 9dceca6c95 | |||
| 7293fa8a41 | |||
| 82cbb7b8da | |||
| 6c19821a6c | |||
| 86349775b7 | |||
| 4a1b4c2642 | |||
| c900d58fb3 | |||
| 74ccc9c2c7 | |||
| 3f671afea8 | |||
| 42357a29e0 | |||
| 8fb6d3da2b | |||
| 706940ff9b | |||
| 5542aac772 | |||
| 026d3f9daa | |||
| 38fca9ae13 | |||
| e878dbc598 | |||
| 1151eac07d | |||
| 56177c55cf | |||
| e6da07a85a | |||
| 175e134478 | |||
| 46cc311d19 | |||
| b8821875ed | |||
| f6e33914d9 | |||
| a394b330e8 | |||
| 24b0c2a4a1 | |||
| 7a9ce51988 | |||
| 8242234274 | |||
| 691adf4e77 | |||
| ba1e34e570 | |||
| 0deb7b3efc | |||
| e8c73bcb37 | |||
| cf9f2bbffe | |||
| ed28861309 | |||
| 971b01d836 | |||
| 5083a23301 | |||
| 61b603d521 | |||
| 3b5148a64e | |||
| ee6cdec60b | |||
| 42ef3476cc | |||
| 98d3a2eb24 | |||
| 109226b7e9 | |||
| 477bd5c744 | |||
| 1802761c93 | |||
| 0558bb3f1c | |||
| 78ce5e3ad5 | |||
| 1be34eec6f | |||
| 44ede4eceb | |||
| 993ec907be | |||
| 35785e9c96 | |||
| 93ac0b03f1 | |||
| d58d386b9b | |||
| f3fc0d62b8 | |||
| 5e5d164929 | |||
| 1527e91a02 | |||
| caba422d9c | |||
| 390242f214 | |||
| fe1df00d02 | |||
| 0d7f00c634 | |||
| d7528a8338 | |||
| 9bca5bfdcf | |||
| a880d6403d | |||
| 6879a30582 | |||
| 5cda660e6e | |||
| 8cb06ffa30 | |||
| 297c03925d | |||
| c645c2ec8e | |||
| 95ca7b5b59 | |||
| 5a843bee61 | |||
| dbc10056f9 | |||
| eae39d8556 | |||
| e59f91dfd0 | |||
| c3f2abebca | |||
| 77ff21605c | |||
| f5aa745038 | |||
| 1fc646a8c2 | |||
| 33f9a850c8 | |||
| 5fcfc40ab8 | |||
| 20c28ef282 | |||
| ed0d5d5767 | |||
| d9a007586c | |||
| 496fc3cc82 | |||
| 8e209fd2eb | |||
| ab9f42c820 | |||
| 6dcebf205f | |||
| 75314fcee2 | |||
| c515b7804e | |||
| 0fab08bd6c | |||
| 88f5ac9cb9 | |||
| bce1dbd654 | |||
| fc208248b7 | |||
| 67b58a6006 | |||
| 4d8caba6c9 | |||
| 9dfada745c | |||
| 651094d692 | |||
| 3ba5fc557c | |||
| d25912678c | |||
| 27ef6dd7a2 | |||
| 73c3849da4 | |||
| 12a0ad7038 | |||
| ef8171b971 | |||
| d2085ab7c5 | |||
| d7636ea273 | |||
| be86450ea5 | |||
| 1bcbaa8137 | |||
| 676b670119 | |||
| d51aea553f | |||
| a3c539b3c2 | |||
| eed93aaffe | |||
| 813d836641 | |||
| 627e028d3c | |||
| 9de80749e1 | |||
| 8334d2abb4 | |||
| 5dd41f74d3 | |||
| be36fc88aa | |||
| a11f35fe0d | |||
| 6027517949 | |||
| c319f3c214 | |||
| 4fcd96ac4b | |||
| d5a0d2cf60 | |||
| 99399184ac | |||
| dd36ec5e07 | |||
| cb1e605203 | |||
| 60bfb9e064 | |||
| 3648bdc02a | |||
| e19a248815 | |||
| 7cfd226b57 | |||
| 05d28ad76a | |||
| 40706d3782 | |||
| adba14c062 | |||
| ab021c4566 | |||
| fe2a164d30 | |||
| 4eca2c7d26 | |||
| 7ea432fa13 | |||
| e666ef35ca | |||
| a8ec3968d9 | |||
| 2128f7adea | |||
| a454d5fa38 | |||
| 7d1ea02267 | |||
| f7aa4b5c81 | |||
| 52987e53f6 | |||
| e32417353a | |||
| c458eb30f0 |
+2
-2
@@ -29,7 +29,7 @@ environment:
|
||||
tasks:
|
||||
- install_go: |
|
||||
mkdir -p /home/build/sdk
|
||||
curl -s https://dl.google.com/go/go1.19.11.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
curl -s https://dl.google.com/go/go1.24.2.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
- prepare_toolchain: |
|
||||
mkdir -p $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 ./...
|
||||
- test_ios: |
|
||||
cd gio
|
||||
CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-ios GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go build -tags ios ./...
|
||||
CGO_CFLAGS=-Wno-deprecated-module-dot-map CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-ios GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go build -tags ios ./...
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: Unlicense OR MIT
|
||||
image: freebsd/13.x
|
||||
image: freebsd/latest
|
||||
packages:
|
||||
- libX11
|
||||
- libxkbcommon
|
||||
@@ -16,7 +16,7 @@ environment:
|
||||
tasks:
|
||||
- install_go: |
|
||||
mkdir -p /home/build/sdk
|
||||
curl https://dl.google.com/go/go1.19.11.freebsd-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
curl https://dl.google.com/go/go1.24.2.freebsd-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
- test_gio: |
|
||||
cd gio
|
||||
go test ./...
|
||||
|
||||
+3
-1
@@ -40,7 +40,7 @@ secrets:
|
||||
tasks:
|
||||
- install_go: |
|
||||
mkdir -p /home/build/sdk
|
||||
curl -s https://dl.google.com/go/go1.19.11.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
curl -s https://dl.google.com/go/go1.24.2.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
- check_gofmt: |
|
||||
cd gio
|
||||
test -z "$(gofmt -s -l .)"
|
||||
@@ -80,6 +80,8 @@ tasks:
|
||||
unzip -q ndk.zip
|
||||
rm ndk.zip
|
||||
mv android-ndk-* ndk-bundle
|
||||
# sdkmanager needs lots of file descriptors
|
||||
ulimit -n 10000
|
||||
yes|sdkmanager --licenses
|
||||
sdkmanager "platforms;android-31" "build-tools;32.0.0"
|
||||
- test_android: |
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ environment:
|
||||
tasks:
|
||||
- install_go: |
|
||||
mkdir -p /home/build/sdk
|
||||
curl https://dl.google.com/go/go1.19.11.src.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
curl https://dl.google.com/go/go1.24.2.src.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
cd /home/build/sdk/go/src
|
||||
./make.bash
|
||||
- test_gio: |
|
||||
|
||||
+5
-1
@@ -65,8 +65,8 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal
|
||||
private final InputMethodManager imm;
|
||||
private final float scrollXScale;
|
||||
private final float scrollYScale;
|
||||
private final AccessibilityManager accessManager;
|
||||
private int keyboardHint;
|
||||
private AccessibilityManager accessManager;
|
||||
|
||||
private long nhandle;
|
||||
|
||||
@@ -259,6 +259,10 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal
|
||||
}
|
||||
|
||||
private void setHighRefreshRate() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
return;
|
||||
}
|
||||
|
||||
Context context = getContext();
|
||||
Display display = context.getDisplay();
|
||||
Display.Mode[] supportedModes = display.getSupportedModes();
|
||||
|
||||
+93
-9
@@ -3,9 +3,16 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"image"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gioui.org/io/input"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
// extraArgs contains extra arguments to append to
|
||||
@@ -20,23 +27,88 @@ var extraArgs string
|
||||
// On Android ID is the package property of AndroidManifest.xml,
|
||||
// on iOS ID is the CFBundleIdentifier of the app Info.plist,
|
||||
// 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 gogio tool or manually with the -X linker flag. For example,
|
||||
// ID is set by the [gioui.org/cmd/gogio] tool or manually with the -X linker flag. For example,
|
||||
//
|
||||
// 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
|
||||
// is not supported. Default value of ID is filepath.Base(os.Args[0]).
|
||||
// is not supported. The default value of ID is filepath.Base(os.Args[0]).
|
||||
var ID = ""
|
||||
|
||||
func init() {
|
||||
if extraArgs != "" {
|
||||
args := strings.Split(extraArgs, "|")
|
||||
os.Args = append(os.Args, args...)
|
||||
// A FrameEvent requests a new frame in the form of a list of
|
||||
// operations that describes the window content.
|
||||
type FrameEvent struct {
|
||||
// Now is the current animation. Use Now instead of time.Now to
|
||||
// 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 == "" {
|
||||
ID = filepath.Base(os.Args[0])
|
||||
|
||||
return layout.Context{
|
||||
Ops: ops,
|
||||
Now: e.Now,
|
||||
Source: e.Source,
|
||||
Metric: e.Metric,
|
||||
Constraints: layout.Exact(size),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,3 +135,15 @@ func DataDir() (string, error) {
|
||||
func Main() {
|
||||
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])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,10 +60,10 @@ func (c *d3d11Context) RenderTarget() (gpu.RenderTarget, error) {
|
||||
}
|
||||
|
||||
func (c *d3d11Context) Present() error {
|
||||
err := c.swchain.Present(1, 0)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return wrapErr(c.swchain.Present(1, 0))
|
||||
}
|
||||
|
||||
func wrapErr(err error) error {
|
||||
if err, ok := err.(d3d11.ErrorCode); ok {
|
||||
switch err.Code {
|
||||
case d3d11.DXGI_STATUS_OCCLUDED:
|
||||
@@ -84,7 +84,7 @@ func (c *d3d11Context) Refresh() error {
|
||||
}
|
||||
c.releaseFBO()
|
||||
if err := c.swchain.ResizeBuffers(0, 0, 0, d3d11.DXGI_FORMAT_UNKNOWN, 0); err != nil {
|
||||
return err
|
||||
return wrapErr(err)
|
||||
}
|
||||
c.width = width
|
||||
c.height = height
|
||||
|
||||
+11
-18
@@ -8,21 +8,20 @@ See https://gioui.org for instructions to set up and run Gio programs.
|
||||
|
||||
# Windows
|
||||
|
||||
Create a new Window by calling NewWindow. On mobile platforms or when Gio
|
||||
is embedded in another project, NewWindow merely connects with a previously
|
||||
created window.
|
||||
A Window is run by calling its Event method in a loop. The first time a
|
||||
method on Window is called, a new GUI window is created and shown. On mobile
|
||||
platforms or when Gio is embedded in another project, Window merely connects
|
||||
with a previously created GUI window.
|
||||
|
||||
A Window is run by calling NextEvent in a loop. The most important event is
|
||||
FrameEvent that prompts an update of the window contents.
|
||||
The most important event is [FrameEvent] that prompts an update of the window
|
||||
contents.
|
||||
|
||||
For example:
|
||||
|
||||
import "gioui.org/unit"
|
||||
|
||||
w := app.NewWindow()
|
||||
w := new(app.Window)
|
||||
for {
|
||||
e := w.NextEvent()
|
||||
if e, ok := e.(system.FrameEvent); ok {
|
||||
e := w.Event()
|
||||
if e, ok := e.(app.FrameEvent); ok {
|
||||
ops.Reset()
|
||||
// Add operations to ops.
|
||||
...
|
||||
@@ -32,7 +31,7 @@ For example:
|
||||
}
|
||||
|
||||
A program must keep receiving events from the event channel until
|
||||
DestroyEvent is received.
|
||||
[DestroyEvent] is received.
|
||||
|
||||
# Main
|
||||
|
||||
@@ -51,18 +50,12 @@ For example, to display a blank but otherwise functional window:
|
||||
go func() {
|
||||
w := app.NewWindow()
|
||||
for {
|
||||
w.NextEvent()
|
||||
w.Event()
|
||||
}
|
||||
}()
|
||||
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
|
||||
|
||||
The packages under gioui.org/app/permission should be imported
|
||||
|
||||
+4
-6
@@ -17,9 +17,8 @@ import (
|
||||
)
|
||||
|
||||
type androidContext struct {
|
||||
win *window
|
||||
eglSurf egl.NativeWindowType
|
||||
width, height int
|
||||
win *window
|
||||
eglSurf egl.NativeWindowType
|
||||
*egl.Context
|
||||
}
|
||||
|
||||
@@ -45,9 +44,8 @@ func (c *androidContext) Refresh() error {
|
||||
if err := c.win.setVisual(c.Context.VisualID()); err != nil {
|
||||
return err
|
||||
}
|
||||
win, width, height := c.win.nativeWindow()
|
||||
win, _, _ := c.win.nativeWindow()
|
||||
c.eglSurf = egl.NativeWindowType(unsafe.Pointer(win))
|
||||
c.width, c.height = width, height
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -55,7 +53,7 @@ func (c *androidContext) Lock() error {
|
||||
// The Android emulator creates a broken surface if it is not
|
||||
// created on the same thread as the context is made current.
|
||||
if c.eglSurf != nil {
|
||||
if err := c.Context.CreateSurface(c.eglSurf, c.width, c.height); err != nil {
|
||||
if err := c.Context.CreateSurface(c.eglSurf); err != nil {
|
||||
return err
|
||||
}
|
||||
c.eglSurf = nil
|
||||
|
||||
+20
-13
@@ -38,7 +38,24 @@ func init() {
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,22 +71,12 @@ func (c *wlContext) Release() {
|
||||
}
|
||||
|
||||
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()
|
||||
if surf == nil {
|
||||
return errors.New("wayland: no surface")
|
||||
}
|
||||
eglWin := C.wl_egl_window_create(surf, C.int(width), C.int(height))
|
||||
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)
|
||||
C.wl_egl_window_resize(c.eglWin, C.int(width), C.int(height), 0, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *wlContext) Lock() error {
|
||||
|
||||
+12
-17
@@ -5,8 +5,6 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"gioui.org/internal/egl"
|
||||
)
|
||||
|
||||
@@ -24,6 +22,18 @@ func init() {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
win, _, _ := w.HWND()
|
||||
eglSurf := egl.NativeWindowType(win)
|
||||
if err := ctx.CreateSurface(eglSurf); err != nil {
|
||||
ctx.Release()
|
||||
return nil, err
|
||||
}
|
||||
if err := ctx.MakeCurrent(); err != nil {
|
||||
ctx.Release()
|
||||
return nil, err
|
||||
}
|
||||
defer ctx.ReleaseCurrent()
|
||||
ctx.EnableVSync(true)
|
||||
return &glContext{win: w, Context: ctx}, nil
|
||||
},
|
||||
})
|
||||
@@ -37,21 +47,6 @@ func (c *glContext) Release() {
|
||||
}
|
||||
|
||||
func (c *glContext) Refresh() error {
|
||||
c.Context.ReleaseSurface()
|
||||
var (
|
||||
win windows.Handle
|
||||
width, height int
|
||||
)
|
||||
win, width, height = c.win.HWND()
|
||||
eglSurf := egl.NativeWindowType(win)
|
||||
if err := c.Context.CreateSurface(eglSurf, width, height); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Context.MakeCurrent(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Context.EnableVSync(true)
|
||||
c.Context.ReleaseCurrent()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+12
-11
@@ -25,6 +25,18 @@ func init() {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
win, _, _ := w.window()
|
||||
eglSurf := egl.NativeWindowType(uintptr(win))
|
||||
if err := ctx.CreateSurface(eglSurf); err != nil {
|
||||
ctx.Release()
|
||||
return nil, err
|
||||
}
|
||||
if err := ctx.MakeCurrent(); err != nil {
|
||||
ctx.Release()
|
||||
return nil, err
|
||||
}
|
||||
defer ctx.ReleaseCurrent()
|
||||
ctx.EnableVSync(true)
|
||||
return &x11Context{win: w, Context: ctx}, nil
|
||||
}
|
||||
}
|
||||
@@ -37,17 +49,6 @@ func (c *x11Context) Release() {
|
||||
}
|
||||
|
||||
func (c *x11Context) Refresh() error {
|
||||
c.Context.ReleaseSurface()
|
||||
win, width, height := c.win.window()
|
||||
eglSurf := egl.NativeWindowType(uintptr(win))
|
||||
if err := c.Context.CreateSurface(eglSurf, width, height); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Context.MakeCurrent(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Context.EnableVSync(true)
|
||||
c.Context.ReleaseCurrent()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ package app
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/gpu"
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
#include <OpenGL/OpenGL.h>
|
||||
#include "_cgo_export.h"
|
||||
|
||||
CALayer *gio_layerFactory(void) {
|
||||
CALayer *gio_layerFactory(BOOL presentWithTrans) {
|
||||
@autoreleasepool {
|
||||
return [CALayer layer];
|
||||
}
|
||||
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
// 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
|
||||
}
|
||||
+7
-4
@@ -6,13 +6,14 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"gioui.org/f32"
|
||||
"testing"
|
||||
"unicode/utf8"
|
||||
|
||||
"gioui.org/font"
|
||||
"gioui.org/font/gofont"
|
||||
"gioui.org/io/input"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/router"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
"gioui.org/text"
|
||||
@@ -31,15 +32,16 @@ func FuzzIME(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, cmds []byte) {
|
||||
cache := text.NewShaper(text.WithCollection(gofont.Collection()))
|
||||
e := new(widget.Editor)
|
||||
e.Focus()
|
||||
|
||||
var r router.Router
|
||||
gtx := layout.Context{Ops: new(op.Ops), Queue: &r}
|
||||
var r input.Router
|
||||
gtx := layout.Context{Ops: new(op.Ops), Source: r.Source()}
|
||||
gtx.Execute(key.FocusCmd{Tag: e})
|
||||
// Layout once to register focus.
|
||||
e.Layout(gtx, cache, font.Font{}, unit.Sp(10), op.CallOp{}, op.CallOp{})
|
||||
r.Frame(gtx.Ops)
|
||||
|
||||
var state editorState
|
||||
state.Selection.Transform = f32.AffineId()
|
||||
const (
|
||||
cmdReplace = iota
|
||||
cmdSelect
|
||||
@@ -139,6 +141,7 @@ func FuzzIME(f *testing.F) {
|
||||
|
||||
func TestEditorIndices(t *testing.T) {
|
||||
var s editorState
|
||||
s.Selection.Transform = f32.AffineId()
|
||||
const str = "Hello, 😀"
|
||||
s.Snippet = key.Snippet{
|
||||
Text: str,
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// Package points standard output, standard error and the standard
|
||||
// library package log to the platform logger.
|
||||
package log
|
||||
|
||||
var appID = "gio"
|
||||
+141
-38
@@ -108,6 +108,74 @@ type MonitorInfo struct {
|
||||
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 (
|
||||
TRUE = 1
|
||||
|
||||
@@ -244,44 +312,51 @@ const (
|
||||
|
||||
UNICODE_NOCHAR = 65535
|
||||
|
||||
WM_CANCELMODE = 0x001F
|
||||
WM_CHAR = 0x0102
|
||||
WM_CLOSE = 0x0010
|
||||
WM_CREATE = 0x0001
|
||||
WM_DPICHANGED = 0x02E0
|
||||
WM_DESTROY = 0x0002
|
||||
WM_ERASEBKGND = 0x0014
|
||||
WM_GETMINMAXINFO = 0x0024
|
||||
WM_IME_COMPOSITION = 0x010F
|
||||
WM_IME_ENDCOMPOSITION = 0x010E
|
||||
WM_IME_STARTCOMPOSITION = 0x010D
|
||||
WM_KEYDOWN = 0x0100
|
||||
WM_KEYUP = 0x0101
|
||||
WM_KILLFOCUS = 0x0008
|
||||
WM_LBUTTONDOWN = 0x0201
|
||||
WM_LBUTTONUP = 0x0202
|
||||
WM_MBUTTONDOWN = 0x0207
|
||||
WM_MBUTTONUP = 0x0208
|
||||
WM_MOUSEMOVE = 0x0200
|
||||
WM_MOUSEWHEEL = 0x020A
|
||||
WM_MOUSEHWHEEL = 0x020E
|
||||
WM_NCACTIVATE = 0x0086
|
||||
WM_NCHITTEST = 0x0084
|
||||
WM_NCCALCSIZE = 0x0083
|
||||
WM_PAINT = 0x000F
|
||||
WM_QUIT = 0x0012
|
||||
WM_SETCURSOR = 0x0020
|
||||
WM_SETFOCUS = 0x0007
|
||||
WM_SHOWWINDOW = 0x0018
|
||||
WM_SIZE = 0x0005
|
||||
WM_SYSKEYDOWN = 0x0104
|
||||
WM_SYSKEYUP = 0x0105
|
||||
WM_RBUTTONDOWN = 0x0204
|
||||
WM_RBUTTONUP = 0x0205
|
||||
WM_TIMER = 0x0113
|
||||
WM_UNICHAR = 0x0109
|
||||
WM_USER = 0x0400
|
||||
WM_WINDOWPOSCHANGED = 0x0047
|
||||
WM_CANCELMODE = 0x001F
|
||||
WM_CHAR = 0x0102
|
||||
WM_CLOSE = 0x0010
|
||||
WM_CREATE = 0x0001
|
||||
WM_DPICHANGED = 0x02E0
|
||||
WM_DESTROY = 0x0002
|
||||
WM_ERASEBKGND = 0x0014
|
||||
WM_GETMINMAXINFO = 0x0024
|
||||
WM_IME_COMPOSITION = 0x010F
|
||||
WM_IME_ENDCOMPOSITION = 0x010E
|
||||
WM_IME_STARTCOMPOSITION = 0x010D
|
||||
WM_KEYDOWN = 0x0100
|
||||
WM_KEYUP = 0x0101
|
||||
WM_KILLFOCUS = 0x0008
|
||||
WM_LBUTTONDOWN = 0x0201
|
||||
WM_LBUTTONUP = 0x0202
|
||||
WM_MBUTTONDOWN = 0x0207
|
||||
WM_MBUTTONUP = 0x0208
|
||||
WM_MOUSEMOVE = 0x0200
|
||||
WM_MOUSEWHEEL = 0x020A
|
||||
WM_MOUSEHWHEEL = 0x020E
|
||||
WM_NCACTIVATE = 0x0086
|
||||
WM_NCHITTEST = 0x0084
|
||||
WM_NCCALCSIZE = 0x0083
|
||||
WM_PAINT = 0x000F
|
||||
WM_POINTERCAPTURECHANGED = 0x024C
|
||||
WM_POINTERDOWN = 0x0246
|
||||
WM_POINTERUP = 0x0247
|
||||
WM_POINTERUPDATE = 0x0245
|
||||
WM_POINTERWHEEL = 0x024E
|
||||
WM_POINTERHWHEEL = 0x024F
|
||||
WM_QUIT = 0x0012
|
||||
WM_RBUTTONDOWN = 0x0204
|
||||
WM_RBUTTONUP = 0x0205
|
||||
WM_SETCURSOR = 0x0020
|
||||
WM_SETFOCUS = 0x0007
|
||||
WM_SHOWWINDOW = 0x0018
|
||||
WM_SIZE = 0x0005
|
||||
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_CLIPSIBLINGS = 0x04000000
|
||||
@@ -345,6 +420,7 @@ var (
|
||||
_DestroyWindow = user32.NewProc("DestroyWindow")
|
||||
_DispatchMessage = user32.NewProc("DispatchMessageW")
|
||||
_EmptyClipboard = user32.NewProc("EmptyClipboard")
|
||||
_EnableMouseInPointer = user32.NewProc("EnableMouseInPointer")
|
||||
_GetWindowRect = user32.NewProc("GetWindowRect")
|
||||
_GetClientRect = user32.NewProc("GetClientRect")
|
||||
_GetClipboardData = user32.NewProc("GetClipboardData")
|
||||
@@ -354,6 +430,7 @@ var (
|
||||
_GetMessage = user32.NewProc("GetMessageW")
|
||||
_GetMessageTime = user32.NewProc("GetMessageTime")
|
||||
_GetMonitorInfo = user32.NewProc("GetMonitorInfoW")
|
||||
_GetPointerInfo = user32.NewProc("GetPointerInfo")
|
||||
_GetSystemMetrics = user32.NewProc("GetSystemMetrics")
|
||||
_GetWindowLong = user32.NewProc("GetWindowLongPtrW")
|
||||
_GetWindowLong32 = user32.NewProc("GetWindowLongW")
|
||||
@@ -371,6 +448,7 @@ var (
|
||||
_PostQuitMessage = user32.NewProc("PostQuitMessage")
|
||||
_ReleaseCapture = user32.NewProc("ReleaseCapture")
|
||||
_RegisterClassExW = user32.NewProc("RegisterClassExW")
|
||||
_RegisterTouchWindow = user32.NewProc("RegisterTouchWindow")
|
||||
_ReleaseDC = user32.NewProc("ReleaseDC")
|
||||
_ScreenToClient = user32.NewProc("ScreenToClient")
|
||||
_ShowWindow = user32.NewProc("ShowWindow")
|
||||
@@ -444,6 +522,31 @@ func CreateWindowEx(dwExStyle uint32, lpClassName uint16, lpWindowName string, d
|
||||
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 {
|
||||
r, _, _ := _DefWindowProc.Call(uintptr(hwnd), uintptr(msg), wparam, lparam)
|
||||
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))
|
||||
}
|
||||
|
||||
func convertKeysym(s C.xkb_keysym_t) (string, bool) {
|
||||
func convertKeysym(s C.xkb_keysym_t) (key.Name, bool) {
|
||||
if 'a' <= s && s <= 'z' {
|
||||
return string(rune(s - 'a' + 'A')), true
|
||||
return key.Name(rune(s - 'a' + 'A')), true
|
||||
}
|
||||
if C.XKB_KEY_KP_0 <= s && s <= C.XKB_KEY_KP_9 {
|
||||
return string(rune(s - C.XKB_KEY_KP_0 + '0')), true
|
||||
return key.Name(rune(s - C.XKB_KEY_KP_0 + '0')), true
|
||||
}
|
||||
if ' ' < s && s <= '~' {
|
||||
return string(rune(s)), true
|
||||
return key.Name(rune(s)), true
|
||||
}
|
||||
var n string
|
||||
var n key.Name
|
||||
switch s {
|
||||
case C.XKB_KEY_Escape:
|
||||
n = key.NameEscape
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package log
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -llog
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
// 1024 is the truncation limit from android/log.h, plus a \n.
|
||||
const logLineLimit = 1024
|
||||
|
||||
var logTag = C.CString(appID)
|
||||
var logTag = C.CString(ID)
|
||||
|
||||
func init() {
|
||||
// Android's logcat already includes timestamps.
|
||||
@@ -3,7 +3,7 @@
|
||||
//go:build darwin && ios
|
||||
// +build darwin,ios
|
||||
|
||||
package log
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -Werror -fmodules -fobjc-arc -x objective-c
|
||||
@@ -1,17 +1,18 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package log
|
||||
package app
|
||||
|
||||
import (
|
||||
"log"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
syscall "golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type logger struct{}
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32")
|
||||
kernel32 = syscall.NewLazySystemDLL("kernel32")
|
||||
outputDebugStringW = kernel32.NewProc("OutputDebugStringW")
|
||||
debugView *logger
|
||||
)
|
||||
+3
-2
@@ -60,8 +60,9 @@ static void presentDrawable(CFTypeRef queueRef, CFTypeRef drawableRef) {
|
||||
id<MTLDrawable> drawable = (__bridge id<MTLDrawable>)drawableRef;
|
||||
id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)queueRef;
|
||||
id<MTLCommandBuffer> cmdBuffer = [queue commandBuffer];
|
||||
[cmdBuffer presentDrawable:drawable];
|
||||
[cmdBuffer commit];
|
||||
[cmdBuffer waitUntilScheduled];
|
||||
[drawable present];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +96,7 @@ func newMtlContext(w *window) (*mtlContext, error) {
|
||||
return nil, errors.New("metal: CAMetalLayer construction failed")
|
||||
}
|
||||
queue := C.newCommandQueue(dev)
|
||||
if layer == 0 {
|
||||
if queue == 0 {
|
||||
C.CFRelease(dev)
|
||||
C.CFRelease(layer)
|
||||
return nil, errors.New("metal: [MTLDevice newCommandQueue] failed")
|
||||
|
||||
+4
-1
@@ -21,7 +21,10 @@ Class gio_layerClass(void) {
|
||||
static CFTypeRef getMetalLayer(CFTypeRef viewRef) {
|
||||
@autoreleasepool {
|
||||
UIView *view = (__bridge UIView *)viewRef;
|
||||
return CFBridgingRetain(view.layer);
|
||||
CAMetalLayer *l = (CAMetalLayer *)view.layer;
|
||||
l.needsDisplayOnBoundsChange = YES;
|
||||
l.presentsWithTransaction = YES;
|
||||
return CFBridgingRetain(l);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-2
@@ -12,9 +12,13 @@ package app
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
CALayer *gio_layerFactory(void) {
|
||||
CALayer *gio_layerFactory(BOOL presentWithTrans) {
|
||||
@autoreleasepool {
|
||||
return [CAMetalLayer layer];
|
||||
CAMetalLayer *l = [CAMetalLayer layer];
|
||||
l.autoresizingMask = kCALayerHeightSizable|kCALayerWidthSizable;
|
||||
l.needsDisplayOnBoundsChange = YES;
|
||||
l.presentsWithTransaction = presentWithTrans;
|
||||
return l;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@ import (
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/op"
|
||||
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/io/pointer"
|
||||
@@ -43,6 +45,8 @@ type Config struct {
|
||||
CustomRenderer bool
|
||||
// Decorated reports whether window decorations are provided automatically.
|
||||
Decorated bool
|
||||
// Focused reports whether has the keyboard focus.
|
||||
Focused bool
|
||||
// decoHeight is the height of the fallback decoration for platforms such
|
||||
// as Wayland that may need fallback client-side decorations.
|
||||
decoHeight unit.Dp
|
||||
@@ -131,8 +135,30 @@ func (o Orientation) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// eventLoop implements the functionality required for drivers where
|
||||
// window event loops must run on a separate thread.
|
||||
type eventLoop struct {
|
||||
win *callbacks
|
||||
// wakeup is the callback to wake up the event loop.
|
||||
wakeup func()
|
||||
// driverFuncs is a channel of functions to run the next
|
||||
// time the window loop waits for events.
|
||||
driverFuncs chan func()
|
||||
// invalidates is notified when an invalidate is requested by the client.
|
||||
invalidates chan struct{}
|
||||
// immediateInvalidates is an optimistic invalidates that doesn't require a wakeup.
|
||||
immediateInvalidates chan struct{}
|
||||
// events is where the platform backend delivers events bound for the
|
||||
// user program.
|
||||
events chan event.Event
|
||||
frames chan *op.Ops
|
||||
frameAck chan struct{}
|
||||
// delivering avoids re-entrant event delivery.
|
||||
delivering bool
|
||||
}
|
||||
|
||||
type frameEvent struct {
|
||||
system.FrameEvent
|
||||
FrameEvent
|
||||
|
||||
Sync bool
|
||||
}
|
||||
@@ -147,9 +173,13 @@ type context interface {
|
||||
Unlock()
|
||||
}
|
||||
|
||||
// Driver is the interface for the platform implementation
|
||||
// driver is the interface for the platform implementation
|
||||
// of a window.
|
||||
type driver interface {
|
||||
// Event blocks until an event is available and returns it.
|
||||
Event() event.Event
|
||||
// Invalidate requests a FrameEvent.
|
||||
Invalidate()
|
||||
// SetAnimating sets the animation flag. When the window is animating,
|
||||
// FrameEvents are delivered as fast as the display can handle them.
|
||||
SetAnimating(anim bool)
|
||||
@@ -160,23 +190,27 @@ type driver interface {
|
||||
// ReadClipboard requests the clipboard content.
|
||||
ReadClipboard()
|
||||
// WriteClipboard requests a clipboard write.
|
||||
WriteClipboard(s string)
|
||||
WriteClipboard(mime string, s []byte)
|
||||
// Configure the window.
|
||||
Configure([]Option)
|
||||
// SetCursor updates the current cursor to name.
|
||||
SetCursor(cursor pointer.Cursor)
|
||||
// Wakeup wakes up the event loop and sends a WakeupEvent.
|
||||
Wakeup()
|
||||
// Perform actions on the window.
|
||||
Perform(system.Action)
|
||||
// EditorStateChanged notifies the driver that the editor state changed.
|
||||
EditorStateChanged(old, new editorState)
|
||||
// Run a function on the window thread.
|
||||
Run(f func())
|
||||
// Frame receives a frame.
|
||||
Frame(frame *op.Ops)
|
||||
// ProcessEvent processes an event.
|
||||
ProcessEvent(e event.Event)
|
||||
}
|
||||
|
||||
type windowRendezvous struct {
|
||||
in chan windowAndConfig
|
||||
out chan windowAndConfig
|
||||
errs chan error
|
||||
in chan windowAndConfig
|
||||
out chan windowAndConfig
|
||||
windows chan struct{}
|
||||
}
|
||||
|
||||
type windowAndConfig struct {
|
||||
@@ -186,32 +220,137 @@ type windowAndConfig struct {
|
||||
|
||||
func newWindowRendezvous() *windowRendezvous {
|
||||
wr := &windowRendezvous{
|
||||
in: make(chan windowAndConfig),
|
||||
out: make(chan windowAndConfig),
|
||||
errs: make(chan error),
|
||||
in: make(chan windowAndConfig),
|
||||
out: make(chan windowAndConfig),
|
||||
windows: make(chan struct{}),
|
||||
}
|
||||
go func() {
|
||||
var main windowAndConfig
|
||||
in := wr.in
|
||||
var window windowAndConfig
|
||||
var out chan windowAndConfig
|
||||
for {
|
||||
select {
|
||||
case w := <-wr.in:
|
||||
var err error
|
||||
if main.window != nil {
|
||||
err = errors.New("multiple windows are not supported")
|
||||
}
|
||||
wr.errs <- err
|
||||
main = w
|
||||
case w := <-in:
|
||||
window = w
|
||||
out = wr.out
|
||||
case out <- main:
|
||||
case out <- window:
|
||||
}
|
||||
}
|
||||
}()
|
||||
return wr
|
||||
}
|
||||
|
||||
func (wakeupEvent) ImplementsEvent() {}
|
||||
func (ConfigEvent) ImplementsEvent() {}
|
||||
func newEventLoop(w *callbacks, wakeup func()) *eventLoop {
|
||||
return &eventLoop{
|
||||
win: w,
|
||||
wakeup: wakeup,
|
||||
events: make(chan event.Event),
|
||||
invalidates: make(chan struct{}, 1),
|
||||
immediateInvalidates: make(chan struct{}),
|
||||
frames: make(chan *op.Ops),
|
||||
frameAck: make(chan struct{}),
|
||||
driverFuncs: make(chan func(), 1),
|
||||
}
|
||||
}
|
||||
|
||||
// Frame receives a frame and waits for its processing. It is called by
|
||||
// the client goroutine.
|
||||
func (e *eventLoop) Frame(frame *op.Ops) {
|
||||
e.frames <- frame
|
||||
<-e.frameAck
|
||||
}
|
||||
|
||||
// Event returns the next available event. It is called by the client
|
||||
// goroutine.
|
||||
func (e *eventLoop) Event() event.Event {
|
||||
for {
|
||||
evt := <-e.events
|
||||
// Receiving a flushEvent indicates to the platform backend that
|
||||
// all previous events have been processed by the user program.
|
||||
if _, ok := evt.(flushEvent); ok {
|
||||
continue
|
||||
}
|
||||
return evt
|
||||
}
|
||||
}
|
||||
|
||||
// Invalidate requests invalidation of the window. It is called by the client
|
||||
// goroutine.
|
||||
func (e *eventLoop) Invalidate() {
|
||||
select {
|
||||
case e.immediateInvalidates <- struct{}{}:
|
||||
// The event loop was waiting, no need for a wakeup.
|
||||
case e.invalidates <- struct{}{}:
|
||||
// The event loop is sleeping, wake it up.
|
||||
e.wakeup()
|
||||
default:
|
||||
// A redraw is pending.
|
||||
}
|
||||
}
|
||||
|
||||
// Run f in the window loop thread. It is called by the client goroutine.
|
||||
func (e *eventLoop) Run(f func()) {
|
||||
e.driverFuncs <- f
|
||||
e.wakeup()
|
||||
}
|
||||
|
||||
// FlushEvents delivers pending events to the client.
|
||||
func (e *eventLoop) FlushEvents() {
|
||||
if e.delivering {
|
||||
return
|
||||
}
|
||||
e.delivering = true
|
||||
defer func() { e.delivering = false }()
|
||||
for {
|
||||
evt, ok := e.win.nextEvent()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
e.deliverEvent(evt)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *eventLoop) deliverEvent(evt event.Event) {
|
||||
var frames <-chan *op.Ops
|
||||
for {
|
||||
select {
|
||||
case f := <-e.driverFuncs:
|
||||
f()
|
||||
case frame := <-frames:
|
||||
// The client called FrameEvent.Frame.
|
||||
frames = nil
|
||||
e.win.ProcessFrame(frame, e.frameAck)
|
||||
case e.events <- evt:
|
||||
switch evt.(type) {
|
||||
case flushEvent, DestroyEvent:
|
||||
// DestroyEvents are not flushed.
|
||||
return
|
||||
case FrameEvent:
|
||||
frames = e.frames
|
||||
}
|
||||
evt = theFlushEvent
|
||||
case <-e.invalidates:
|
||||
e.win.Invalidate()
|
||||
case <-e.immediateInvalidates:
|
||||
e.win.Invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *eventLoop) Wakeup() {
|
||||
for {
|
||||
select {
|
||||
case f := <-e.driverFuncs:
|
||||
f()
|
||||
case <-e.invalidates:
|
||||
e.win.Invalidate()
|
||||
case <-e.immediateInvalidates:
|
||||
e.win.Invalidate()
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func walkActions(actions system.Action, do func(system.Action)) {
|
||||
for a := system.Action(1); actions != 0; a <<= 1 {
|
||||
@@ -221,3 +360,6 @@ func walkActions(actions system.Action, do func(system.Action)) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wakeupEvent) ImplementsEvent() {}
|
||||
func (ConfigEvent) ImplementsEvent() {}
|
||||
|
||||
+141
-98
@@ -123,31 +123,36 @@ import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/cgo"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/internal/f32color"
|
||||
"gioui.org/op"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/clipboard"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/input"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/router"
|
||||
"gioui.org/io/semantic"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/io/transfer"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
type window struct {
|
||||
callbacks *callbacks
|
||||
loop *eventLoop
|
||||
|
||||
view C.jobject
|
||||
handle cgo.Handle
|
||||
@@ -156,18 +161,19 @@ type window struct {
|
||||
fontScale float32
|
||||
insets pixelInsets
|
||||
|
||||
stage system.Stage
|
||||
visible bool
|
||||
started bool
|
||||
animating bool
|
||||
|
||||
win *C.ANativeWindow
|
||||
config Config
|
||||
win *C.ANativeWindow
|
||||
config Config
|
||||
inputHint key.InputHint
|
||||
|
||||
semantic struct {
|
||||
hoverID router.SemanticID
|
||||
rootID router.SemanticID
|
||||
focusID router.SemanticID
|
||||
diffs []router.SemanticID
|
||||
hoverID input.SemanticID
|
||||
rootID input.SemanticID
|
||||
focusID input.SemanticID
|
||||
diffs []input.SemanticID
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,9 +205,9 @@ type pixelInsets struct {
|
||||
top, bottom, left, right int
|
||||
}
|
||||
|
||||
// ViewEvent is sent whenever the Window's underlying Android view
|
||||
// AndroidViewEvent is sent whenever the Window's underlying Android view
|
||||
// changes.
|
||||
type ViewEvent struct {
|
||||
type AndroidViewEvent struct {
|
||||
// View is a JNI global reference to the android.view.View
|
||||
// instance backing the Window. The reference is valid until
|
||||
// the next ViewEvent is received.
|
||||
@@ -485,24 +491,30 @@ func Java_org_gioui_GioView_onCreateView(env *C.JNIEnv, class C.jclass, view C.j
|
||||
})
|
||||
view = C.jni_NewGlobalRef(env, view)
|
||||
wopts := <-mainWindow.out
|
||||
var cnf Config
|
||||
w, ok := windows[wopts.window]
|
||||
if !ok {
|
||||
w = &window{
|
||||
callbacks: wopts.window,
|
||||
}
|
||||
w.loop = newEventLoop(w.callbacks, w.wakeup)
|
||||
w.callbacks.SetDriver(w)
|
||||
cnf.apply(unit.Metric{}, wopts.options)
|
||||
windows[wopts.window] = w
|
||||
} else {
|
||||
cnf = w.config
|
||||
}
|
||||
mainWindow.windows <- struct{}{}
|
||||
if w.view != 0 {
|
||||
w.detach(env)
|
||||
}
|
||||
w.view = view
|
||||
w.visible = false
|
||||
w.handle = cgo.NewHandle(w)
|
||||
w.callbacks.SetDriver(w)
|
||||
w.loadConfig(env, class)
|
||||
w.Configure(wopts.options)
|
||||
w.SetInputHint(key.HintAny)
|
||||
w.setStage(system.StagePaused)
|
||||
w.callbacks.Event(ViewEvent{View: uintptr(view)})
|
||||
w.setConfig(env, cnf)
|
||||
w.SetInputHint(w.inputHint)
|
||||
w.processEvent(AndroidViewEvent{View: uintptr(view)})
|
||||
return C.jlong(w.handle)
|
||||
}
|
||||
|
||||
@@ -516,7 +528,7 @@ func Java_org_gioui_GioView_onDestroyView(env *C.JNIEnv, class C.jclass, handle
|
||||
func Java_org_gioui_GioView_onStopView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
||||
w := cgo.Handle(handle).Value().(*window)
|
||||
w.started = false
|
||||
w.setStage(system.StagePaused)
|
||||
w.visible = false
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onStartView
|
||||
@@ -532,7 +544,7 @@ func Java_org_gioui_GioView_onStartView(env *C.JNIEnv, class C.jclass, handle C.
|
||||
func Java_org_gioui_GioView_onSurfaceDestroyed(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
||||
w := cgo.Handle(handle).Value().(*window)
|
||||
w.win = nil
|
||||
w.setStage(system.StagePaused)
|
||||
w.visible = false
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onSurfaceChanged
|
||||
@@ -554,9 +566,7 @@ func Java_org_gioui_GioView_onLowMemory(env *C.JNIEnv, class C.jclass) {
|
||||
func Java_org_gioui_GioView_onConfigurationChanged(env *C.JNIEnv, class C.jclass, view C.jlong) {
|
||||
w := cgo.Handle(view).Value().(*window)
|
||||
w.loadConfig(env, class)
|
||||
if w.stage >= system.StageInactive {
|
||||
w.draw(env, true)
|
||||
}
|
||||
w.draw(env, true)
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onFrameCallback
|
||||
@@ -565,19 +575,13 @@ func Java_org_gioui_GioView_onFrameCallback(env *C.JNIEnv, class C.jclass, view
|
||||
if !exist {
|
||||
return
|
||||
}
|
||||
if w.stage < system.StageInactive {
|
||||
return
|
||||
}
|
||||
if w.animating {
|
||||
w.draw(env, false)
|
||||
callVoidMethod(env, w.view, gioView.postFrameCallback)
|
||||
}
|
||||
w.draw(env, false)
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onBack
|
||||
func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong) C.jboolean {
|
||||
w := cgo.Handle(view).Value().(*window)
|
||||
if w.callbacks.Event(key.Event{Name: key.NameBack}) {
|
||||
if w.processEvent(key.Event{Name: key.NameBack}) {
|
||||
return C.JNI_TRUE
|
||||
}
|
||||
return C.JNI_FALSE
|
||||
@@ -586,7 +590,8 @@ func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong)
|
||||
//export Java_org_gioui_GioView_onFocusChange
|
||||
func Java_org_gioui_GioView_onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) {
|
||||
w := cgo.Handle(view).Value().(*window)
|
||||
w.callbacks.Event(key.FocusEvent{Focus: focus == C.JNI_TRUE})
|
||||
w.config.Focused = focus == C.JNI_TRUE
|
||||
w.processEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onWindowInsets
|
||||
@@ -598,9 +603,7 @@ func Java_org_gioui_GioView_onWindowInsets(env *C.JNIEnv, class C.jclass, view C
|
||||
left: int(left),
|
||||
right: int(right),
|
||||
}
|
||||
if w.stage >= system.StageInactive {
|
||||
w.draw(env, true)
|
||||
}
|
||||
w.draw(env, true)
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_initializeAccessibilityNodeInfo
|
||||
@@ -661,7 +664,35 @@ func Java_org_gioui_GioView_onClearA11yFocus(env *C.JNIEnv, class C.jclass, view
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) initAccessibilityNodeInfo(env *C.JNIEnv, sem router.SemanticNode, off image.Point, info C.jobject) error {
|
||||
func (w *window) ProcessEvent(e event.Event) {
|
||||
w.processEvent(e)
|
||||
}
|
||||
|
||||
func (w *window) processEvent(e event.Event) bool {
|
||||
if !w.callbacks.ProcessEvent(e) {
|
||||
return false
|
||||
}
|
||||
w.loop.FlushEvents()
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *window) Event() event.Event {
|
||||
return w.loop.Event()
|
||||
}
|
||||
|
||||
func (w *window) Invalidate() {
|
||||
w.loop.Invalidate()
|
||||
}
|
||||
|
||||
func (w *window) Run(f func()) {
|
||||
w.loop.Run(f)
|
||||
}
|
||||
|
||||
func (w *window) Frame(frame *op.Ops) {
|
||||
w.loop.Frame(frame)
|
||||
}
|
||||
|
||||
func (w *window) initAccessibilityNodeInfo(env *C.JNIEnv, sem input.SemanticNode, off image.Point, info C.jobject) error {
|
||||
for _, ch := range sem.Children {
|
||||
err := callVoidMethod(env, info, android.accessibilityNodeInfo.addChild, jvalue(w.view), jvalue(w.virtualIDFor(ch.ID)))
|
||||
if err != nil {
|
||||
@@ -704,7 +735,7 @@ func (w *window) initAccessibilityNodeInfo(env *C.JNIEnv, sem router.SemanticNod
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
if d.Gestures&router.ClickGesture != 0 {
|
||||
if d.Gestures&input.ClickGesture != 0 {
|
||||
addAction(ACTION_CLICK)
|
||||
}
|
||||
clsName := android.strings.androidViewView
|
||||
@@ -749,25 +780,23 @@ func (w *window) initAccessibilityNodeInfo(env *C.JNIEnv, sem router.SemanticNod
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) virtualIDFor(id router.SemanticID) C.jint {
|
||||
// TODO: Android virtual IDs are 32-bit Java integers, but childID is a int64.
|
||||
func (w *window) virtualIDFor(id input.SemanticID) C.jint {
|
||||
if id == w.semantic.rootID {
|
||||
return HOST_VIEW_ID
|
||||
}
|
||||
return C.jint(id)
|
||||
}
|
||||
|
||||
func (w *window) semIDFor(virtID C.jint) router.SemanticID {
|
||||
func (w *window) semIDFor(virtID C.jint) input.SemanticID {
|
||||
if virtID == HOST_VIEW_ID {
|
||||
return w.semantic.rootID
|
||||
}
|
||||
return router.SemanticID(virtID)
|
||||
return input.SemanticID(virtID)
|
||||
}
|
||||
|
||||
func (w *window) detach(env *C.JNIEnv) {
|
||||
callVoidMethod(env, w.view, gioView.unregister)
|
||||
w.callbacks.Event(ViewEvent{})
|
||||
w.callbacks.SetDriver(nil)
|
||||
w.processEvent(AndroidViewEvent{})
|
||||
w.handle.Delete()
|
||||
C.jni_DeleteGlobalRef(env, w.view)
|
||||
w.view = 0
|
||||
@@ -778,18 +807,10 @@ func (w *window) setVisible(env *C.JNIEnv) {
|
||||
if width == 0 || height == 0 {
|
||||
return
|
||||
}
|
||||
w.setStage(system.StageRunning)
|
||||
w.visible = 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 {
|
||||
if C.ANativeWindow_setBuffersGeometry(w.win, 0, 0, C.int32_t(visID)) != 0 {
|
||||
return errors.New("ANativeWindow_setBuffersGeometry failed")
|
||||
@@ -826,10 +847,13 @@ func (w *window) SetAnimating(anim bool) {
|
||||
}
|
||||
|
||||
func (w *window) draw(env *C.JNIEnv, sync bool) {
|
||||
if !w.visible {
|
||||
return
|
||||
}
|
||||
size := image.Pt(int(C.ANativeWindow_getWidth(w.win)), int(C.ANativeWindow_getHeight(w.win)))
|
||||
if size != w.config.Size {
|
||||
w.config.Size = size
|
||||
w.callbacks.Event(ConfigEvent{Config: w.config})
|
||||
w.processEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
if size.X == 0 || size.Y == 0 {
|
||||
return
|
||||
@@ -837,14 +861,14 @@ func (w *window) draw(env *C.JNIEnv, sync bool) {
|
||||
const inchPrDp = 1.0 / 160
|
||||
ppdp := float32(w.dpi) * inchPrDp
|
||||
dppp := unit.Dp(1.0 / ppdp)
|
||||
insets := system.Insets{
|
||||
insets := Insets{
|
||||
Top: unit.Dp(w.insets.top) * dppp,
|
||||
Bottom: unit.Dp(w.insets.bottom) * dppp,
|
||||
Left: unit.Dp(w.insets.left) * dppp,
|
||||
Right: unit.Dp(w.insets.right) * dppp,
|
||||
}
|
||||
w.callbacks.Event(frameEvent{
|
||||
FrameEvent: system.FrameEvent{
|
||||
w.processEvent(frameEvent{
|
||||
FrameEvent: FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: w.config.Size,
|
||||
Insets: insets,
|
||||
@@ -855,6 +879,9 @@ func (w *window) draw(env *C.JNIEnv, sync bool) {
|
||||
},
|
||||
Sync: sync,
|
||||
})
|
||||
if w.animating {
|
||||
callVoidMethod(env, w.view, gioView.postFrameCallback)
|
||||
}
|
||||
a11yActive, err := callBooleanMethod(env, w.view, gioView.isA11yActive)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -898,8 +925,8 @@ func runInJVM(jvm *C.JavaVM, f func(env *C.JNIEnv)) {
|
||||
f(env)
|
||||
}
|
||||
|
||||
func convertKeyCode(code C.jint) (string, bool) {
|
||||
var n string
|
||||
func convertKeyCode(code C.jint) (key.Name, bool) {
|
||||
var n key.Name
|
||||
switch code {
|
||||
case C.AKEYCODE_FORWARD_DEL:
|
||||
n = key.NameDeleteForward
|
||||
@@ -943,7 +970,7 @@ func Java_org_gioui_GioView_onKeyEvent(env *C.JNIEnv, class C.jclass, handle C.j
|
||||
if pressed == C.JNI_TRUE {
|
||||
state = key.Press
|
||||
}
|
||||
w.callbacks.Event(key.Event{Name: n, State: state})
|
||||
w.processEvent(key.Event{Name: n, State: state})
|
||||
}
|
||||
if pressed == C.JNI_TRUE && r != 0 && r != '\n' { // Checking for "\n" to prevent duplication with key.NameEnter (gio#224).
|
||||
w.callbacks.EditorInsert(string(rune(r)))
|
||||
@@ -993,7 +1020,7 @@ func Java_org_gioui_GioView_onTouchEvent(env *C.JNIEnv, class C.jclass, handle C
|
||||
default:
|
||||
return
|
||||
}
|
||||
w.callbacks.Event(pointer.Event{
|
||||
w.processEvent(pointer.Event{
|
||||
Kind: kind,
|
||||
Source: src,
|
||||
Buttons: btns,
|
||||
@@ -1145,6 +1172,8 @@ func (w *window) ShowTextInput(show bool) {
|
||||
}
|
||||
|
||||
func (w *window) SetInputHint(mode key.InputHint) {
|
||||
w.inputHint = mode
|
||||
|
||||
// Constants defined at https://developer.android.com/reference/android/text/InputType.
|
||||
const (
|
||||
TYPE_NULL = 0
|
||||
@@ -1291,14 +1320,14 @@ func findClass(env *C.JNIEnv, name string) C.jclass {
|
||||
func osMain() {
|
||||
}
|
||||
|
||||
func newWindow(window *callbacks, options []Option) error {
|
||||
func newWindow(window *callbacks, options []Option) {
|
||||
mainWindow.in <- windowAndConfig{window, options}
|
||||
return <-mainWindow.errs
|
||||
<-mainWindow.windows
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(s string) {
|
||||
func (w *window) WriteClipboard(mime string, s []byte) {
|
||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||
jstr := javaString(env, s)
|
||||
jstr := javaString(env, string(s))
|
||||
callStaticVoidMethod(env, android.gioCls, android.mwriteClipboard,
|
||||
jvalue(android.appCtx), jvalue(jstr))
|
||||
})
|
||||
@@ -1312,47 +1341,56 @@ func (w *window) ReadClipboard() {
|
||||
return
|
||||
}
|
||||
content := goString(env, C.jstring(c))
|
||||
w.callbacks.Event(clipboard.Event{Text: content})
|
||||
w.processEvent(transfer.DataEvent{
|
||||
Type: "application/text",
|
||||
Open: func() io.ReadCloser {
|
||||
return io.NopCloser(strings.NewReader(content))
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) Configure(options []Option) {
|
||||
cnf := w.config
|
||||
cnf.apply(unit.Metric{}, options)
|
||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||
prev := w.config
|
||||
cnf := w.config
|
||||
cnf.apply(unit.Metric{}, options)
|
||||
// Decorations are never disabled.
|
||||
cnf.Decorated = true
|
||||
|
||||
if prev.Orientation != cnf.Orientation {
|
||||
w.config.Orientation = cnf.Orientation
|
||||
setOrientation(env, w.view, cnf.Orientation)
|
||||
}
|
||||
if prev.NavigationColor != cnf.NavigationColor {
|
||||
w.config.NavigationColor = cnf.NavigationColor
|
||||
setNavigationColor(env, w.view, cnf.NavigationColor)
|
||||
}
|
||||
if prev.StatusColor != cnf.StatusColor {
|
||||
w.config.StatusColor = cnf.StatusColor
|
||||
setStatusColor(env, w.view, cnf.StatusColor)
|
||||
}
|
||||
if prev.Mode != cnf.Mode {
|
||||
switch cnf.Mode {
|
||||
case Fullscreen:
|
||||
callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_TRUE)
|
||||
w.config.Mode = Fullscreen
|
||||
case Windowed:
|
||||
callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_FALSE)
|
||||
w.config.Mode = Windowed
|
||||
}
|
||||
}
|
||||
if cnf.Decorated != prev.Decorated {
|
||||
w.config.Decorated = cnf.Decorated
|
||||
}
|
||||
w.callbacks.Event(ConfigEvent{Config: w.config})
|
||||
w.setConfig(env, cnf)
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) setConfig(env *C.JNIEnv, cnf Config) {
|
||||
prev := w.config
|
||||
// Decorations are never disabled.
|
||||
cnf.Decorated = true
|
||||
|
||||
if prev.Orientation != cnf.Orientation {
|
||||
w.config.Orientation = cnf.Orientation
|
||||
setOrientation(env, w.view, cnf.Orientation)
|
||||
}
|
||||
if prev.NavigationColor != cnf.NavigationColor {
|
||||
w.config.NavigationColor = cnf.NavigationColor
|
||||
setNavigationColor(env, w.view, cnf.NavigationColor)
|
||||
}
|
||||
if prev.StatusColor != cnf.StatusColor {
|
||||
w.config.StatusColor = cnf.StatusColor
|
||||
setStatusColor(env, w.view, cnf.StatusColor)
|
||||
}
|
||||
if prev.Mode != cnf.Mode {
|
||||
switch cnf.Mode {
|
||||
case Fullscreen:
|
||||
callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_TRUE)
|
||||
w.config.Mode = Fullscreen
|
||||
case Windowed:
|
||||
callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_FALSE)
|
||||
w.config.Mode = Windowed
|
||||
}
|
||||
}
|
||||
if cnf.Decorated != prev.Decorated {
|
||||
w.config.Decorated = cnf.Decorated
|
||||
}
|
||||
w.processEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
func (w *window) Perform(system.Action) {}
|
||||
|
||||
func (w *window) SetCursor(cursor pointer.Cursor) {
|
||||
@@ -1361,9 +1399,10 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) Wakeup() {
|
||||
func (w *window) wakeup() {
|
||||
runOnMain(func(env *C.JNIEnv) {
|
||||
w.callbacks.Event(wakeupEvent{})
|
||||
w.loop.Wakeup()
|
||||
w.loop.FlushEvents()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1454,4 +1493,8 @@ func Java_org_gioui_Gio_scheduleMainFuncs(env *C.JNIEnv, cls C.jclass) {
|
||||
}
|
||||
}
|
||||
|
||||
func (_ ViewEvent) ImplementsEvent() {}
|
||||
func (AndroidViewEvent) implementsViewEvent() {}
|
||||
func (AndroidViewEvent) ImplementsEvent() {}
|
||||
func (a AndroidViewEvent) Valid() bool {
|
||||
return a != (AndroidViewEvent{})
|
||||
}
|
||||
|
||||
+17
-19
@@ -5,7 +5,7 @@ package app
|
||||
/*
|
||||
#include <Foundation/Foundation.h>
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) void gio_wakeupMainThread(void);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_runOnMain(uintptr_t h);
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createDisplayLink(void);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_releaseDisplayLink(CFTypeRef dl);
|
||||
__attribute__ ((visibility ("hidden"))) int gio_startDisplayLink(CFTypeRef dl);
|
||||
@@ -40,8 +40,10 @@ static CFTypeRef newNSString(unichar *chars, NSUInteger length) {
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime/cgo"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -73,30 +75,25 @@ type displayLink struct {
|
||||
// displayLinks maps CFTypeRefs to *displayLinks.
|
||||
var displayLinks sync.Map
|
||||
|
||||
var mainFuncs = make(chan func(), 1)
|
||||
func isMainThread() bool {
|
||||
return bool(C.isMainThread())
|
||||
}
|
||||
|
||||
// runOnMain runs the function on the main thread.
|
||||
func runOnMain(f func()) {
|
||||
if C.isMainThread() {
|
||||
if isMainThread() {
|
||||
f()
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
mainFuncs <- f
|
||||
C.gio_wakeupMainThread()
|
||||
}()
|
||||
C.gio_runOnMain(C.uintptr_t(cgo.NewHandle(f)))
|
||||
}
|
||||
|
||||
//export gio_dispatchMainFuncs
|
||||
func gio_dispatchMainFuncs() {
|
||||
for {
|
||||
select {
|
||||
case f := <-mainFuncs:
|
||||
f()
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
//export gio_runFunc
|
||||
func gio_runFunc(h C.uintptr_t) {
|
||||
handle := cgo.Handle(h)
|
||||
defer handle.Delete()
|
||||
f := handle.Value().(func())
|
||||
f()
|
||||
}
|
||||
|
||||
// nsstringToString converts a NSString to a Go string.
|
||||
@@ -260,8 +257,9 @@ func windowSetCursor(from, to pointer.Cursor) pointer.Cursor {
|
||||
return to
|
||||
}
|
||||
|
||||
func (w *window) Wakeup() {
|
||||
func (w *window) wakeup() {
|
||||
runOnMain(func() {
|
||||
w.w.Event(wakeupEvent{})
|
||||
w.loop.Wakeup()
|
||||
w.loop.FlushEvents()
|
||||
})
|
||||
}
|
||||
|
||||
+2
-2
@@ -4,8 +4,8 @@
|
||||
|
||||
#include "_cgo_export.h"
|
||||
|
||||
void gio_wakeupMainThread(void) {
|
||||
void gio_runOnMain(uintptr_t h) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
gio_dispatchMainFuncs();
|
||||
gio_runFunc(h);
|
||||
});
|
||||
}
|
||||
|
||||
+153
-66
@@ -12,6 +12,9 @@ package app
|
||||
#include <UIKit/UIKit.h>
|
||||
#include <stdint.h>
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) int gio_applicationMain(int argc, char *argv[]);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle);
|
||||
|
||||
struct drawParams {
|
||||
CGFloat dpi, sdpi;
|
||||
CGFloat width, height;
|
||||
@@ -19,6 +22,7 @@ struct drawParams {
|
||||
};
|
||||
|
||||
static void writeClipboard(unichar *chars, NSUInteger length) {
|
||||
#if !TARGET_OS_TV
|
||||
@autoreleasepool {
|
||||
NSString *s = [NSString string];
|
||||
if (length > 0) {
|
||||
@@ -27,13 +31,18 @@ static void writeClipboard(unichar *chars, NSUInteger length) {
|
||||
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
||||
p.string = s;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static CFTypeRef readClipboard(void) {
|
||||
#if !TARGET_OS_TV
|
||||
@autoreleasepool {
|
||||
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
||||
return (__bridge_retained CFTypeRef)p.string;
|
||||
}
|
||||
#else
|
||||
return nil;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void showTextInput(CFTypeRef viewRef) {
|
||||
@@ -72,21 +81,27 @@ import "C"
|
||||
|
||||
import (
|
||||
"image"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/cgo"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/clipboard"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/io/transfer"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
type ViewEvent struct {
|
||||
type UIKitViewEvent struct {
|
||||
// ViewController is a CFTypeRef for the UIViewController backing a Window.
|
||||
ViewController uintptr
|
||||
}
|
||||
@@ -95,18 +110,17 @@ type window struct {
|
||||
view C.CFTypeRef
|
||||
w *callbacks
|
||||
displayLink *displayLink
|
||||
loop *eventLoop
|
||||
|
||||
visible bool
|
||||
cursor pointer.Cursor
|
||||
config Config
|
||||
hidden bool
|
||||
cursor pointer.Cursor
|
||||
config Config
|
||||
|
||||
pointerMap []C.CFTypeRef
|
||||
}
|
||||
|
||||
var mainWindow = newWindowRendezvous()
|
||||
|
||||
var views = make(map[C.CFTypeRef]*window)
|
||||
|
||||
func init() {
|
||||
// Darwin requires UI operations happen on the main thread only.
|
||||
runtime.LockOSThread()
|
||||
@@ -114,55 +128,59 @@ func init() {
|
||||
|
||||
//export onCreate
|
||||
func onCreate(view, controller C.CFTypeRef) {
|
||||
wopts := <-mainWindow.out
|
||||
w := &window{
|
||||
view: view,
|
||||
w: wopts.window,
|
||||
}
|
||||
w.loop = newEventLoop(w.w, w.wakeup)
|
||||
w.w.SetDriver(w)
|
||||
mainWindow.windows <- struct{}{}
|
||||
dl, err := newDisplayLink(func() {
|
||||
w.draw(false)
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
w.w.ProcessEvent(DestroyEvent{Err: err})
|
||||
return
|
||||
}
|
||||
w.displayLink = dl
|
||||
wopts := <-mainWindow.out
|
||||
w.w = wopts.window
|
||||
w.w.SetDriver(w)
|
||||
views[view] = w
|
||||
C.gio_viewSetHandle(view, C.uintptr_t(cgo.NewHandle(w)))
|
||||
w.Configure(wopts.options)
|
||||
w.w.Event(system.StageEvent{Stage: system.StagePaused})
|
||||
w.w.Event(ViewEvent{ViewController: uintptr(controller)})
|
||||
w.ProcessEvent(UIKitViewEvent{ViewController: uintptr(controller)})
|
||||
}
|
||||
|
||||
func viewFor(h C.uintptr_t) *window {
|
||||
return cgo.Handle(h).Value().(*window)
|
||||
}
|
||||
|
||||
//export gio_onDraw
|
||||
func gio_onDraw(view C.CFTypeRef) {
|
||||
w := views[view]
|
||||
func gio_onDraw(h C.uintptr_t) {
|
||||
w := viewFor(h)
|
||||
w.draw(true)
|
||||
}
|
||||
|
||||
func (w *window) draw(sync bool) {
|
||||
if w.hidden {
|
||||
return
|
||||
}
|
||||
params := C.viewDrawParams(w.view)
|
||||
if params.width == 0 || params.height == 0 {
|
||||
return
|
||||
}
|
||||
wasVisible := w.visible
|
||||
w.visible = true
|
||||
if !wasVisible {
|
||||
w.w.Event(system.StageEvent{Stage: system.StageRunning})
|
||||
}
|
||||
const inchPrDp = 1.0 / 163
|
||||
m := unit.Metric{
|
||||
PxPerDp: float32(params.dpi) * inchPrDp,
|
||||
PxPerSp: float32(params.sdpi) * inchPrDp,
|
||||
}
|
||||
dppp := unit.Dp(1. / m.PxPerDp)
|
||||
w.w.Event(frameEvent{
|
||||
FrameEvent: system.FrameEvent{
|
||||
w.ProcessEvent(frameEvent{
|
||||
FrameEvent: FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: image.Point{
|
||||
X: int(params.width + .5),
|
||||
Y: int(params.height + .5),
|
||||
},
|
||||
Insets: system.Insets{
|
||||
Insets: Insets{
|
||||
Top: unit.Dp(params.top) * dppp,
|
||||
Bottom: unit.Dp(params.bottom) * dppp,
|
||||
Left: unit.Dp(params.left) * dppp,
|
||||
@@ -175,26 +193,34 @@ func (w *window) draw(sync bool) {
|
||||
}
|
||||
|
||||
//export onStop
|
||||
func onStop(view C.CFTypeRef) {
|
||||
w := views[view]
|
||||
w.visible = false
|
||||
w.w.Event(system.StageEvent{Stage: system.StagePaused})
|
||||
func onStop(h C.uintptr_t) {
|
||||
w := viewFor(h)
|
||||
w.hidden = true
|
||||
}
|
||||
|
||||
//export onStart
|
||||
func onStart(h C.uintptr_t) {
|
||||
w := viewFor(h)
|
||||
w.hidden = false
|
||||
w.draw(true)
|
||||
}
|
||||
|
||||
//export onDestroy
|
||||
func onDestroy(view C.CFTypeRef) {
|
||||
w := views[view]
|
||||
delete(views, view)
|
||||
w.w.Event(ViewEvent{})
|
||||
w.w.Event(system.DestroyEvent{})
|
||||
func onDestroy(h C.uintptr_t) {
|
||||
w := viewFor(h)
|
||||
w.ProcessEvent(UIKitViewEvent{})
|
||||
w.ProcessEvent(DestroyEvent{})
|
||||
w.displayLink.Close()
|
||||
w.displayLink = nil
|
||||
cgo.Handle(h).Delete()
|
||||
w.view = 0
|
||||
}
|
||||
|
||||
//export onFocus
|
||||
func onFocus(view C.CFTypeRef, focus int) {
|
||||
w := views[view]
|
||||
w.w.Event(key.FocusEvent{Focus: focus != 0})
|
||||
func onFocus(h C.uintptr_t, focus int) {
|
||||
w := viewFor(h)
|
||||
w.config.Focused = focus != 0
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
//export onLowMemory
|
||||
@@ -204,38 +230,38 @@ func onLowMemory() {
|
||||
}
|
||||
|
||||
//export onUpArrow
|
||||
func onUpArrow(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameUpArrow)
|
||||
func onUpArrow(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameUpArrow)
|
||||
}
|
||||
|
||||
//export onDownArrow
|
||||
func onDownArrow(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameDownArrow)
|
||||
func onDownArrow(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameDownArrow)
|
||||
}
|
||||
|
||||
//export onLeftArrow
|
||||
func onLeftArrow(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameLeftArrow)
|
||||
func onLeftArrow(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameLeftArrow)
|
||||
}
|
||||
|
||||
//export onRightArrow
|
||||
func onRightArrow(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameRightArrow)
|
||||
func onRightArrow(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameRightArrow)
|
||||
}
|
||||
|
||||
//export onDeleteBackward
|
||||
func onDeleteBackward(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameDeleteBackward)
|
||||
func onDeleteBackward(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameDeleteBackward)
|
||||
}
|
||||
|
||||
//export onText
|
||||
func onText(view, str C.CFTypeRef) {
|
||||
w := views[view]
|
||||
func onText(h C.uintptr_t, str C.CFTypeRef) {
|
||||
w := viewFor(h)
|
||||
w.w.EditorInsert(nsstringToString(str))
|
||||
}
|
||||
|
||||
//export onTouch
|
||||
func onTouch(last C.int, view, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) {
|
||||
func onTouch(h C.uintptr_t, last C.int, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) {
|
||||
var kind pointer.Kind
|
||||
switch phase {
|
||||
case C.UITouchPhaseBegan:
|
||||
@@ -249,10 +275,10 @@ func onTouch(last C.int, view, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.C
|
||||
default:
|
||||
return
|
||||
}
|
||||
w := views[view]
|
||||
w := viewFor(h)
|
||||
t := time.Duration(float64(ti) * float64(time.Second))
|
||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: kind,
|
||||
Source: pointer.Touch,
|
||||
PointerID: w.lookupTouch(last != 0, touchRef),
|
||||
@@ -265,11 +291,16 @@ func (w *window) ReadClipboard() {
|
||||
cstr := C.readClipboard()
|
||||
defer C.CFRelease(cstr)
|
||||
content := nsstringToString(cstr)
|
||||
w.w.Event(clipboard.Event{Text: content})
|
||||
w.ProcessEvent(transfer.DataEvent{
|
||||
Type: "application/text",
|
||||
Open: func() io.ReadCloser {
|
||||
return io.NopCloser(strings.NewReader(content))
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(s string) {
|
||||
u16 := utf16.Encode([]rune(s))
|
||||
func (w *window) WriteClipboard(mime string, s []byte) {
|
||||
u16 := utf16.Encode([]rune(string(s)))
|
||||
var chars *C.unichar
|
||||
if len(u16) > 0 {
|
||||
chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
|
||||
@@ -280,7 +311,7 @@ func (w *window) WriteClipboard(s string) {
|
||||
func (w *window) Configure([]Option) {
|
||||
// Decorations are never disabled.
|
||||
w.config.Decorated = true
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
func (w *window) EditorStateChanged(old, new editorState) {}
|
||||
@@ -288,10 +319,6 @@ func (w *window) EditorStateChanged(old, new editorState) {}
|
||||
func (w *window) Perform(system.Action) {}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
v := w.view
|
||||
if v == 0 {
|
||||
return
|
||||
}
|
||||
if anim {
|
||||
w.displayLink.Start()
|
||||
} else {
|
||||
@@ -303,8 +330,8 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
||||
w.cursor = windowSetCursor(w.cursor, cursor)
|
||||
}
|
||||
|
||||
func (w *window) onKeyCommand(name string) {
|
||||
w.w.Event(key.Event{
|
||||
func (w *window) onKeyCommand(name key.Name) {
|
||||
w.ProcessEvent(key.Event{
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
@@ -343,17 +370,77 @@ func (w *window) ShowTextInput(show bool) {
|
||||
|
||||
func (w *window) SetInputHint(_ key.InputHint) {}
|
||||
|
||||
func newWindow(win *callbacks, options []Option) error {
|
||||
mainWindow.in <- windowAndConfig{win, options}
|
||||
return <-mainWindow.errs
|
||||
func (w *window) ProcessEvent(e event.Event) {
|
||||
w.w.ProcessEvent(e)
|
||||
w.loop.FlushEvents()
|
||||
}
|
||||
|
||||
func (w *window) Event() event.Event {
|
||||
return w.loop.Event()
|
||||
}
|
||||
|
||||
func (w *window) Invalidate() {
|
||||
w.loop.Invalidate()
|
||||
}
|
||||
|
||||
func (w *window) Run(f func()) {
|
||||
w.loop.Run(f)
|
||||
}
|
||||
|
||||
func (w *window) Frame(frame *op.Ops) {
|
||||
w.loop.Frame(frame)
|
||||
}
|
||||
|
||||
func newWindow(win *callbacks, options []Option) {
|
||||
mainWindow.in <- windowAndConfig{win, options}
|
||||
<-mainWindow.windows
|
||||
}
|
||||
|
||||
var mainMode = mainModeUndefined
|
||||
|
||||
const (
|
||||
mainModeUndefined = iota
|
||||
mainModeExe
|
||||
mainModeLibrary
|
||||
)
|
||||
|
||||
func osMain() {
|
||||
if !isMainThread() {
|
||||
panic("app.Main must be run on the main goroutine")
|
||||
}
|
||||
switch mainMode {
|
||||
case mainModeUndefined:
|
||||
mainMode = mainModeExe
|
||||
var argv []*C.char
|
||||
for _, arg := range os.Args {
|
||||
a := C.CString(arg)
|
||||
defer C.free(unsafe.Pointer(a))
|
||||
argv = append(argv, a)
|
||||
}
|
||||
C.gio_applicationMain(C.int(len(argv)), unsafe.SliceData(argv))
|
||||
case mainModeExe:
|
||||
panic("app.Main may be called only once")
|
||||
case mainModeLibrary:
|
||||
// Do nothing, we're embedded as a library.
|
||||
}
|
||||
}
|
||||
|
||||
//export gio_runMain
|
||||
func gio_runMain() {
|
||||
runMain()
|
||||
if !isMainThread() {
|
||||
panic("app.Main must be run on the main goroutine")
|
||||
}
|
||||
switch mainMode {
|
||||
case mainModeUndefined:
|
||||
mainMode = mainModeLibrary
|
||||
runMain()
|
||||
case mainModeExe:
|
||||
// Do nothing, main has already been called.
|
||||
}
|
||||
}
|
||||
|
||||
func (_ ViewEvent) ImplementsEvent() {}
|
||||
func (UIKitViewEvent) implementsViewEvent() {}
|
||||
func (UIKitViewEvent) ImplementsEvent() {}
|
||||
func (u UIKitViewEvent) Valid() bool {
|
||||
return u != (UIKitViewEvent{})
|
||||
}
|
||||
|
||||
+51
-22
@@ -11,6 +11,7 @@
|
||||
__attribute__ ((visibility ("hidden"))) Class gio_layerClass(void);
|
||||
|
||||
@interface GioView: UIView <UIKeyInput>
|
||||
@property uintptr_t handle;
|
||||
@end
|
||||
|
||||
@implementation GioViewController
|
||||
@@ -25,12 +26,13 @@ CGFloat _keyboardHeight;
|
||||
self.view.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
|
||||
UIView *drawView = [[GioView alloc] initWithFrame:zeroFrame];
|
||||
[self.view addSubview: drawView];
|
||||
#ifndef TARGET_OS_TV
|
||||
#if !TARGET_OS_TV
|
||||
drawView.multipleTouchEnabled = YES;
|
||||
#endif
|
||||
drawView.preservesSuperviewLayoutMargins = YES;
|
||||
drawView.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
|
||||
onCreate((__bridge CFTypeRef)drawView, (__bridge CFTypeRef)self);
|
||||
#if !TARGET_OS_TV
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(keyboardWillChange:)
|
||||
name:UIKeyboardWillShowNotification
|
||||
@@ -43,6 +45,7 @@ CGFloat _keyboardHeight;
|
||||
selector:@selector(keyboardWillHide:)
|
||||
name:UIKeyboardWillHideNotification
|
||||
object:nil];
|
||||
#endif
|
||||
[[NSNotificationCenter defaultCenter] addObserver: self
|
||||
selector: @selector(applicationDidEnterBackground:)
|
||||
name: UIApplicationDidEnterBackgroundNotification
|
||||
@@ -54,33 +57,33 @@ CGFloat _keyboardHeight;
|
||||
}
|
||||
|
||||
- (void)applicationWillEnterForeground:(UIApplication *)application {
|
||||
UIView *drawView = self.view.subviews[0];
|
||||
if (drawView != nil) {
|
||||
gio_onDraw((__bridge CFTypeRef)drawView);
|
||||
GioView *view = (GioView *)self.view.subviews[0];
|
||||
if (view != nil) {
|
||||
onStart(view.handle);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
||||
UIView *drawView = self.view.subviews[0];
|
||||
if (drawView != nil) {
|
||||
onStop((__bridge CFTypeRef)drawView);
|
||||
GioView *view = (GioView *)self.view.subviews[0];
|
||||
if (view != nil) {
|
||||
onStop(view.handle);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewDidDisappear:(BOOL)animated {
|
||||
[super viewDidDisappear:animated];
|
||||
CFTypeRef viewRef = (__bridge CFTypeRef)self.view.subviews[0];
|
||||
onDestroy(viewRef);
|
||||
GioView *view = (GioView *)self.view.subviews[0];
|
||||
onDestroy(view.handle);
|
||||
}
|
||||
|
||||
- (void)viewDidLayoutSubviews {
|
||||
[super viewDidLayoutSubviews];
|
||||
UIView *view = self.view.subviews[0];
|
||||
GioView *view = (GioView *)self.view.subviews[0];
|
||||
CGRect frame = self.view.bounds;
|
||||
// Adjust view bounds to make room for the keyboard.
|
||||
frame.size.height -= _keyboardHeight;
|
||||
view.frame = frame;
|
||||
gio_onDraw((__bridge CFTypeRef)view);
|
||||
gio_onDraw(view.handle);
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning {
|
||||
@@ -88,6 +91,7 @@ CGFloat _keyboardHeight;
|
||||
[super didReceiveMemoryWarning];
|
||||
}
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
- (void)keyboardWillChange:(NSNotification *)note {
|
||||
NSDictionary *userInfo = note.userInfo;
|
||||
CGRect f = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
||||
@@ -99,13 +103,13 @@ CGFloat _keyboardHeight;
|
||||
_keyboardHeight = 0.0;
|
||||
[self.view setNeedsLayout];
|
||||
}
|
||||
#endif
|
||||
@end
|
||||
|
||||
static void handleTouches(int last, UIView *view, NSSet<UITouch *> *touches, UIEvent *event) {
|
||||
static void handleTouches(int last, GioView *view, NSSet<UITouch *> *touches, UIEvent *event) {
|
||||
CGFloat scale = view.contentScaleFactor;
|
||||
NSUInteger i = 0;
|
||||
NSUInteger n = [touches count];
|
||||
CFTypeRef viewRef = (__bridge CFTypeRef)view;
|
||||
for (UITouch *touch in touches) {
|
||||
CFTypeRef touchRef = (__bridge CFTypeRef)touch;
|
||||
i++;
|
||||
@@ -116,7 +120,7 @@ static void handleTouches(int last, UIView *view, NSSet<UITouch *> *touches, UIE
|
||||
CGPoint loc = [coalescedTouch locationInView:view];
|
||||
j++;
|
||||
int lastTouch = last && i == n && j == m;
|
||||
onTouch(lastTouch, viewRef, touchRef, touch.phase, loc.x*scale, loc.y*scale, [coalescedTouch timestamp]);
|
||||
onTouch(view.handle, lastTouch, touchRef, touch.phase, loc.x*scale, loc.y*scale, [coalescedTouch timestamp]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,13 +155,13 @@ NSArray<UIKeyCommand *> *_keyCommands;
|
||||
|
||||
- (void)onWindowDidBecomeKey:(NSNotification *)note {
|
||||
if (self.isFirstResponder) {
|
||||
onFocus((__bridge CFTypeRef)self, YES);
|
||||
onFocus(self.handle, YES);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onWindowDidResignKey:(NSNotification *)note {
|
||||
if (self.isFirstResponder) {
|
||||
onFocus((__bridge CFTypeRef)self, NO);
|
||||
onFocus(self.handle, NO);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +182,7 @@ NSArray<UIKeyCommand *> *_keyCommands;
|
||||
}
|
||||
|
||||
- (void)insertText:(NSString *)text {
|
||||
onText((__bridge CFTypeRef)self, (__bridge CFTypeRef)text);
|
||||
onText(self.handle, (__bridge CFTypeRef)text);
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder {
|
||||
@@ -190,23 +194,23 @@ NSArray<UIKeyCommand *> *_keyCommands;
|
||||
}
|
||||
|
||||
- (void)deleteBackward {
|
||||
onDeleteBackward((__bridge CFTypeRef)self);
|
||||
onDeleteBackward(self.handle);
|
||||
}
|
||||
|
||||
- (void)onUpArrow {
|
||||
onUpArrow((__bridge CFTypeRef)self);
|
||||
onUpArrow(self.handle);
|
||||
}
|
||||
|
||||
- (void)onDownArrow {
|
||||
onDownArrow((__bridge CFTypeRef)self);
|
||||
onDownArrow(self.handle);
|
||||
}
|
||||
|
||||
- (void)onLeftArrow {
|
||||
onLeftArrow((__bridge CFTypeRef)self);
|
||||
onLeftArrow(self.handle);
|
||||
}
|
||||
|
||||
- (void)onRightArrow {
|
||||
onRightArrow((__bridge CFTypeRef)self);
|
||||
onRightArrow(self.handle);
|
||||
}
|
||||
|
||||
- (NSArray<UIKeyCommand *> *)keyCommands {
|
||||
@@ -271,3 +275,28 @@ void gio_showCursor() {
|
||||
void gio_setCursor(NSUInteger curID) {
|
||||
// Not supported.
|
||||
}
|
||||
|
||||
void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
|
||||
GioView *v = (__bridge GioView *)viewRef;
|
||||
v.handle = handle;
|
||||
}
|
||||
|
||||
@interface _gioAppDelegate : UIResponder <UIApplicationDelegate>
|
||||
@property (strong, nonatomic) UIWindow *window;
|
||||
@end
|
||||
|
||||
@implementation _gioAppDelegate
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||
GioViewController *controller = [[GioViewController alloc] initWithNibName:nil bundle:nil];
|
||||
self.window.rootViewController = controller;
|
||||
[self.window makeKeyAndVisible];
|
||||
return YES;
|
||||
}
|
||||
@end
|
||||
|
||||
int gio_applicationMain(int argc, char *argv[]) {
|
||||
@autoreleasepool {
|
||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass([_gioAppDelegate class]));
|
||||
}
|
||||
}
|
||||
|
||||
+99
-96
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"io"
|
||||
"strings"
|
||||
"syscall/js"
|
||||
"time"
|
||||
@@ -13,16 +14,18 @@ import (
|
||||
"unicode/utf8"
|
||||
|
||||
"gioui.org/internal/f32color"
|
||||
"gioui.org/op"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/clipboard"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/io/transfer"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
type ViewEvent struct {
|
||||
type JSViewEvent struct {
|
||||
Element js.Value
|
||||
}
|
||||
|
||||
@@ -53,9 +56,6 @@ type window struct {
|
||||
composing bool
|
||||
requestFocus bool
|
||||
|
||||
chanAnimation chan struct{}
|
||||
chanRedraw chan struct{}
|
||||
|
||||
config Config
|
||||
inset f32.Point
|
||||
scale float32
|
||||
@@ -68,7 +68,7 @@ type window struct {
|
||||
contextStatus contextStatus
|
||||
}
|
||||
|
||||
func newWindow(win *callbacks, options []Option) error {
|
||||
func newWindow(win *callbacks, options []Option) {
|
||||
doc := js.Global().Get("document")
|
||||
cont := getContainer(doc)
|
||||
cnv := createCanvas(doc)
|
||||
@@ -83,7 +83,9 @@ func newWindow(win *callbacks, options []Option) error {
|
||||
head: doc.Get("head"),
|
||||
clipboard: js.Global().Get("navigator").Get("clipboard"),
|
||||
wakeups: make(chan struct{}, 1),
|
||||
w: win,
|
||||
}
|
||||
w.w.SetDriver(w)
|
||||
w.requestAnimationFrame = w.window.Get("requestAnimationFrame")
|
||||
w.browserHistory = w.window.Get("history")
|
||||
w.visualViewport = w.window.Get("visualViewport")
|
||||
@@ -93,42 +95,28 @@ func newWindow(win *callbacks, options []Option) error {
|
||||
if screen := w.window.Get("screen"); screen.Truthy() {
|
||||
w.screenOrientation = screen.Get("orientation")
|
||||
}
|
||||
w.chanAnimation = make(chan struct{}, 1)
|
||||
w.chanRedraw = make(chan struct{}, 1)
|
||||
w.redraw = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
||||
w.chanAnimation <- struct{}{}
|
||||
w.draw(false)
|
||||
return nil
|
||||
})
|
||||
w.clipboardCallback = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
||||
content := args[0].String()
|
||||
go win.Event(clipboard.Event{Text: content})
|
||||
w.processEvent(transfer.DataEvent{
|
||||
Type: "application/text",
|
||||
Open: func() io.ReadCloser {
|
||||
return io.NopCloser(strings.NewReader(content))
|
||||
},
|
||||
})
|
||||
return nil
|
||||
})
|
||||
w.addEventListeners()
|
||||
w.addHistory()
|
||||
w.w = win
|
||||
|
||||
go func() {
|
||||
defer w.cleanup()
|
||||
w.w.SetDriver(w)
|
||||
w.Configure(options)
|
||||
w.blur()
|
||||
w.w.Event(ViewEvent{Element: cont})
|
||||
w.w.Event(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
|
||||
w.Configure(options)
|
||||
w.blur()
|
||||
w.processEvent(JSViewEvent{Element: cont})
|
||||
w.resize()
|
||||
w.draw(true)
|
||||
}
|
||||
|
||||
func getContainer(doc js.Value) js.Value {
|
||||
@@ -188,12 +176,12 @@ func (w *window) addEventListeners() {
|
||||
w.cnv.Set("width", 0)
|
||||
w.cnv.Set("height", 0)
|
||||
w.resize()
|
||||
w.requestRedraw()
|
||||
w.draw(true)
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.visualViewport, "resize", func(this js.Value, args []js.Value) interface{} {
|
||||
w.resize()
|
||||
w.requestRedraw()
|
||||
w.draw(true)
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.window, "contextmenu", func(this js.Value, args []js.Value) interface{} {
|
||||
@@ -201,22 +189,11 @@ func (w *window) addEventListeners() {
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.window, "popstate", func(this js.Value, args []js.Value) interface{} {
|
||||
if w.w.Event(key.Event{Name: key.NameBack}) {
|
||||
if w.processEvent(key.Event{Name: key.NameBack}) {
|
||||
return w.browserHistory.Call("forward")
|
||||
}
|
||||
return w.browserHistory.Call("back")
|
||||
})
|
||||
w.addEventListener(w.document, "visibilitychange", func(this js.Value, args []js.Value) interface{} {
|
||||
ev := 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.pointerEvent(pointer.Move, 0, 0, args[0])
|
||||
return nil
|
||||
@@ -274,18 +251,20 @@ func (w *window) addEventListeners() {
|
||||
w.touches[i] = js.Null()
|
||||
}
|
||||
w.touches = w.touches[:0]
|
||||
w.w.Event(pointer.Event{
|
||||
w.processEvent(pointer.Event{
|
||||
Kind: pointer.Cancel,
|
||||
Source: pointer.Touch,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.tarea, "focus", func(this js.Value, args []js.Value) interface{} {
|
||||
w.w.Event(key.FocusEvent{Focus: true})
|
||||
w.config.Focused = true
|
||||
w.processEvent(ConfigEvent{Config: w.config})
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.tarea, "blur", func(this js.Value, args []js.Value) interface{} {
|
||||
w.w.Event(key.FocusEvent{Focus: false})
|
||||
w.config.Focused = false
|
||||
w.processEvent(ConfigEvent{Config: w.config})
|
||||
w.blur()
|
||||
return nil
|
||||
})
|
||||
@@ -374,10 +353,50 @@ func (w *window) keyEvent(e js.Value, ks key.State) {
|
||||
Modifiers: modifiersFor(e),
|
||||
State: ks,
|
||||
}
|
||||
w.w.Event(cmd)
|
||||
w.processEvent(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) ProcessEvent(e event.Event) {
|
||||
w.processEvent(e)
|
||||
}
|
||||
|
||||
func (w *window) processEvent(e event.Event) bool {
|
||||
if !w.w.ProcessEvent(e) {
|
||||
return false
|
||||
}
|
||||
select {
|
||||
case w.wakeups <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *window) Event() event.Event {
|
||||
for {
|
||||
evt, ok := w.w.nextEvent()
|
||||
if ok {
|
||||
if _, destroy := evt.(DestroyEvent); destroy {
|
||||
w.cleanup()
|
||||
}
|
||||
return evt
|
||||
}
|
||||
<-w.wakeups
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) Invalidate() {
|
||||
w.w.Invalidate()
|
||||
}
|
||||
|
||||
func (w *window) Run(f func()) {
|
||||
f()
|
||||
}
|
||||
|
||||
func (w *window) Frame(frame *op.Ops) {
|
||||
w.w.ProcessFrame(frame, nil)
|
||||
}
|
||||
|
||||
// modifiersFor returns the modifier set for a DOM MouseEvent or
|
||||
// KeyEvent.
|
||||
func modifiersFor(e js.Value) key.Modifiers {
|
||||
@@ -425,7 +444,7 @@ func (w *window) touchEvent(kind pointer.Kind, e js.Value) {
|
||||
X: float32(x) * scale,
|
||||
Y: float32(y) * scale,
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
w.processEvent(pointer.Event{
|
||||
Kind: kind,
|
||||
Source: pointer.Touch,
|
||||
Position: pos,
|
||||
@@ -475,7 +494,7 @@ func (w *window) pointerEvent(kind pointer.Kind, dx, dy float32, e js.Value) {
|
||||
if jbtns&4 != 0 {
|
||||
btns |= pointer.ButtonTertiary
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
w.processEvent(pointer.Event{
|
||||
Kind: kind,
|
||||
Source: pointer.Mouse,
|
||||
Buttons: btns,
|
||||
@@ -502,17 +521,6 @@ func (w *window) funcOf(f func(this js.Value, args []js.Value) interface{}) js.F
|
||||
return jsf
|
||||
}
|
||||
|
||||
func (w *window) animCallback() {
|
||||
anim := w.animating
|
||||
w.animRequested = anim
|
||||
if anim {
|
||||
w.requestAnimationFrame.Invoke(w.redraw)
|
||||
}
|
||||
if anim {
|
||||
w.draw(false)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) EditorStateChanged(old, new editorState) {}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
@@ -533,14 +541,14 @@ func (w *window) ReadClipboard() {
|
||||
w.clipboard.Call("readText", w.clipboard).Call("then", w.clipboardCallback)
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(s string) {
|
||||
func (w *window) WriteClipboard(mime string, s []byte) {
|
||||
if w.clipboard.IsUndefined() {
|
||||
return
|
||||
}
|
||||
if w.clipboard.Get("writeText").IsUndefined() {
|
||||
return
|
||||
}
|
||||
w.clipboard.Call("writeText", s)
|
||||
w.clipboard.Call("writeText", string(s))
|
||||
}
|
||||
|
||||
func (w *window) Configure(options []Option) {
|
||||
@@ -568,7 +576,7 @@ func (w *window) Configure(options []Option) {
|
||||
if cnf.Decorated != prev.Decorated {
|
||||
w.config.Decorated = cnf.Decorated
|
||||
}
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.processEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
func (w *window) Perform(system.Action) {}
|
||||
@@ -607,23 +615,14 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
||||
style.Set("cursor", webCursor[cursor])
|
||||
}
|
||||
|
||||
func (w *window) Wakeup() {
|
||||
select {
|
||||
case w.wakeups <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) ShowTextInput(show bool) {
|
||||
// Run in a goroutine to avoid a deadlock if the
|
||||
// focus change result in an event.
|
||||
go func() {
|
||||
if show {
|
||||
w.focus()
|
||||
} else {
|
||||
w.blur()
|
||||
}
|
||||
}()
|
||||
if show {
|
||||
w.focus()
|
||||
} else {
|
||||
w.blur()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) SetInputHint(mode key.InputHint) {
|
||||
@@ -640,7 +639,7 @@ func (w *window) resize() {
|
||||
}
|
||||
if size != w.config.Size {
|
||||
w.config.Size = size
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.processEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
if vx, vy := w.visualViewport.Get("width"), w.visualViewport.Get("height"); !vx.IsUndefined() && !vy.IsUndefined() {
|
||||
@@ -660,13 +659,20 @@ func (w *window) draw(sync bool) {
|
||||
if w.contextStatus == contextStatusLost {
|
||||
return
|
||||
}
|
||||
anim := w.animating
|
||||
w.animRequested = anim
|
||||
if anim {
|
||||
w.requestAnimationFrame.Invoke(w.redraw)
|
||||
} else if !sync {
|
||||
return
|
||||
}
|
||||
size, insets, metric := w.getConfig()
|
||||
if metric == (unit.Metric{}) || size.X == 0 || size.Y == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
w.w.Event(frameEvent{
|
||||
FrameEvent: system.FrameEvent{
|
||||
w.processEvent(frameEvent{
|
||||
FrameEvent: FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: size,
|
||||
Insets: insets,
|
||||
@@ -676,10 +682,10 @@ func (w *window) draw(sync bool) {
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) getConfig() (image.Point, system.Insets, unit.Metric) {
|
||||
func (w *window) getConfig() (image.Point, Insets, unit.Metric) {
|
||||
invscale := unit.Dp(1. / w.scale)
|
||||
return image.Pt(w.config.Size.X, w.config.Size.Y),
|
||||
system.Insets{
|
||||
Insets{
|
||||
Bottom: unit.Dp(w.inset.Y) * invscale,
|
||||
Right: unit.Dp(w.inset.X) * invscale,
|
||||
}, unit.Metric{
|
||||
@@ -735,19 +741,12 @@ func (w *window) navigationColor(c color.NRGBA) {
|
||||
theme.Set("content", fmt.Sprintf("#%06X", []uint8{rgba.R, rgba.G, rgba.B}))
|
||||
}
|
||||
|
||||
func (w *window) requestRedraw() {
|
||||
select {
|
||||
case w.chanRedraw <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func osMain() {
|
||||
select {}
|
||||
}
|
||||
|
||||
func translateKey(k string) (string, bool) {
|
||||
var n string
|
||||
func translateKey(k string) (key.Name, bool) {
|
||||
var n key.Name
|
||||
|
||||
switch k {
|
||||
case "ArrowUp":
|
||||
@@ -814,11 +813,15 @@ func translateKey(k string) (string, bool) {
|
||||
r, s := utf8.DecodeRuneInString(k)
|
||||
// If there is exactly one printable character, return that.
|
||||
if s == len(k) && unicode.IsPrint(r) {
|
||||
return strings.ToUpper(k), true
|
||||
return key.Name(strings.ToUpper(k)), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
return n, true
|
||||
}
|
||||
|
||||
func (_ ViewEvent) ImplementsEvent() {}
|
||||
func (JSViewEvent) implementsViewEvent() {}
|
||||
func (JSViewEvent) ImplementsEvent() {}
|
||||
func (j JSViewEvent) Valid() bool {
|
||||
return !(j.Element.IsNull() || j.Element.IsUndefined())
|
||||
}
|
||||
|
||||
+494
-322
File diff suppressed because it is too large
Load Diff
+120
-67
@@ -6,7 +6,7 @@
|
||||
|
||||
#include "_cgo_export.h"
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(void);
|
||||
__attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(BOOL presentWithTrans);
|
||||
|
||||
@interface GioAppDelegate : NSObject<NSApplicationDelegate>
|
||||
@end
|
||||
@@ -14,40 +14,55 @@ __attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(void);
|
||||
@interface GioWindowDelegate : NSObject<NSWindowDelegate>
|
||||
@end
|
||||
|
||||
@interface GioView : NSView <CALayerDelegate,NSTextInputClient>
|
||||
@property uintptr_t handle;
|
||||
@property BOOL presentWithTrans;
|
||||
@end
|
||||
|
||||
@implementation GioWindowDelegate
|
||||
- (void)windowWillMiniaturize:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onHide((__bridge CFTypeRef)window.contentView);
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onDraw(view.handle);
|
||||
}
|
||||
- (void)windowDidDeminiaturize:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onShow((__bridge CFTypeRef)window.contentView);
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onDraw(view.handle);
|
||||
}
|
||||
- (void)windowWillEnterFullScreen:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onFullscreen((__bridge CFTypeRef)window.contentView);
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onDraw(view.handle);
|
||||
}
|
||||
- (void)windowWillExitFullScreen:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onWindowed((__bridge CFTypeRef)window.contentView);
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onDraw(view.handle);
|
||||
}
|
||||
- (void)windowDidChangeScreen:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
CGDirectDisplayID dispID = [[[window screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue];
|
||||
CFTypeRef view = (__bridge CFTypeRef)window.contentView;
|
||||
gio_onChangeScreen(view, dispID);
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onChangeScreen(view.handle, dispID);
|
||||
}
|
||||
- (void)windowDidBecomeKey:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onFocus((__bridge CFTypeRef)window.contentView, 1);
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
if ([window firstResponder] == view) {
|
||||
gio_onFocus(view.handle, 1);
|
||||
}
|
||||
}
|
||||
- (void)windowDidResignKey:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onFocus((__bridge CFTypeRef)window.contentView, 0);
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
if ([window firstResponder] == view) {
|
||||
gio_onFocus(view.handle, 0);
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) {
|
||||
static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) {
|
||||
NSPoint p = [view convertPoint:[event locationInWindow] fromView:nil];
|
||||
if (!event.hasPreciseScrollingDeltas) {
|
||||
// dx and dy are in rows and columns.
|
||||
@@ -56,12 +71,9 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
||||
}
|
||||
// Origin is in the lower left corner. Convert to upper left.
|
||||
CGFloat height = view.bounds.size.height;
|
||||
gio_onMouse((__bridge CFTypeRef)view, (__bridge CFTypeRef)event, typ, event.buttonNumber, p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]);
|
||||
gio_onMouse(view.handle, (__bridge CFTypeRef)event, typ, event.buttonNumber, p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]);
|
||||
}
|
||||
|
||||
@interface GioView : NSView <CALayerDelegate,NSTextInputClient>
|
||||
@end
|
||||
|
||||
@implementation GioView
|
||||
- (void)setFrameSize:(NSSize)newSize {
|
||||
[super setFrameSize:newSize];
|
||||
@@ -70,21 +82,19 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
||||
// drawRect is called when OpenGL is used, displayLayer otherwise.
|
||||
// Don't know why.
|
||||
- (void)drawRect:(NSRect)r {
|
||||
gio_onDraw((__bridge CFTypeRef)self);
|
||||
gio_onDraw(self.handle);
|
||||
}
|
||||
- (void)displayLayer:(CALayer *)layer {
|
||||
layer.contentsScale = self.window.backingScaleFactor;
|
||||
gio_onDraw((__bridge CFTypeRef)self);
|
||||
gio_onDraw(self.handle);
|
||||
}
|
||||
- (CALayer *)makeBackingLayer {
|
||||
CALayer *layer = gio_layerFactory();
|
||||
CALayer *layer = gio_layerFactory(self.presentWithTrans);
|
||||
layer.delegate = self;
|
||||
return layer;
|
||||
}
|
||||
- (void)viewDidMoveToWindow {
|
||||
if (self.window == nil) {
|
||||
gio_onClose((__bridge CFTypeRef)self);
|
||||
}
|
||||
gio_onAttached(self.handle, self.window != nil ? 1 : 0);
|
||||
}
|
||||
- (void)mouseDown:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_DOWN, 0, 0);
|
||||
@@ -122,34 +132,37 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
||||
handleMouse(self, event, MOUSE_SCROLL, dx, dy);
|
||||
}
|
||||
- (void)keyDown:(NSEvent *)event {
|
||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
||||
NSString *keys = [event charactersIgnoringModifiers];
|
||||
gio_onKeys((__bridge CFTypeRef)self, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true);
|
||||
gio_onKeys(self.handle, (__bridge CFTypeRef)event, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true);
|
||||
}
|
||||
- (void)flagsChanged:(NSEvent *)event {
|
||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
||||
gio_onFlagsChanged(self.handle, [event modifierFlags]);
|
||||
}
|
||||
- (void)keyUp:(NSEvent *)event {
|
||||
NSString *keys = [event charactersIgnoringModifiers];
|
||||
gio_onKeys((__bridge CFTypeRef)self, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], false);
|
||||
gio_onKeys(self.handle, (__bridge CFTypeRef)event, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], false);
|
||||
}
|
||||
- (void)insertText:(id)string {
|
||||
gio_onText((__bridge CFTypeRef)self, (__bridge CFTypeRef)string);
|
||||
gio_onText(self.handle, (__bridge CFTypeRef)string);
|
||||
}
|
||||
- (void)doCommandBySelector:(SEL)sel {
|
||||
// Don't pass commands up the responder chain.
|
||||
// They will end up in a beep.
|
||||
- (void)doCommandBySelector:(SEL)action {
|
||||
if (!gio_onCommandBySelector(self.handle)) {
|
||||
[super doCommandBySelector:action];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)hasMarkedText {
|
||||
int res = gio_hasMarkedText((__bridge CFTypeRef)self);
|
||||
int res = gio_hasMarkedText(self.handle);
|
||||
return res ? YES : NO;
|
||||
}
|
||||
- (NSRange)markedRange {
|
||||
return gio_markedRange((__bridge CFTypeRef)self);
|
||||
return gio_markedRange(self.handle);
|
||||
}
|
||||
- (NSRange)selectedRange {
|
||||
return gio_selectedRange((__bridge CFTypeRef)self);
|
||||
return gio_selectedRange(self.handle);
|
||||
}
|
||||
- (void)unmarkText {
|
||||
gio_unmarkText((__bridge CFTypeRef)self);
|
||||
gio_unmarkText(self.handle);
|
||||
}
|
||||
- (void)setMarkedText:(id)string
|
||||
selectedRange:(NSRange)selRange
|
||||
@@ -161,14 +174,14 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
||||
} else {
|
||||
str = string;
|
||||
}
|
||||
gio_setMarkedText((__bridge CFTypeRef)self, (__bridge CFTypeRef)str, selRange, replaceRange);
|
||||
gio_setMarkedText(self.handle, (__bridge CFTypeRef)str, selRange, replaceRange);
|
||||
}
|
||||
- (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText {
|
||||
return nil;
|
||||
}
|
||||
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range
|
||||
actualRange:(NSRangePointer)actualRange {
|
||||
NSString *str = CFBridgingRelease(gio_substringForProposedRange((__bridge CFTypeRef)self, range, actualRange));
|
||||
NSString *str = CFBridgingRelease(gio_substringForProposedRange(self.handle, range, actualRange));
|
||||
return [[NSAttributedString alloc] initWithString:str attributes:nil];
|
||||
}
|
||||
- (void)insertText:(id)string
|
||||
@@ -180,17 +193,34 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
||||
} else {
|
||||
str = string;
|
||||
}
|
||||
gio_insertText((__bridge CFTypeRef)self, (__bridge CFTypeRef)str, replaceRange);
|
||||
gio_insertText(self.handle, (__bridge CFTypeRef)str, replaceRange);
|
||||
}
|
||||
- (NSUInteger)characterIndexForPoint:(NSPoint)p {
|
||||
return gio_characterIndexForPoint((__bridge CFTypeRef)self, p);
|
||||
return gio_characterIndexForPoint(self.handle, p);
|
||||
}
|
||||
- (NSRect)firstRectForCharacterRange:(NSRange)rng
|
||||
actualRange:(NSRangePointer)actual {
|
||||
NSRect r = gio_firstRectForCharacterRange((__bridge CFTypeRef)self, rng, actual);
|
||||
NSRect r = gio_firstRectForCharacterRange(self.handle, rng, actual);
|
||||
r = [self convertRect:r toView:nil];
|
||||
return [[self window] convertRectToScreen:r];
|
||||
}
|
||||
- (void)applicationWillUnhide:(NSNotification *)notification {
|
||||
gio_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
|
||||
|
||||
// Delegates are weakly referenced from their peers. Nothing
|
||||
@@ -240,7 +270,7 @@ void gio_showCursor() {
|
||||
|
||||
// some cursors are not public, this tries to use a private cursor
|
||||
// and uses fallback when the use of private cursor fails.
|
||||
void gio_trySetPrivateCursor(SEL cursorName, NSCursor* fallback) {
|
||||
static void trySetPrivateCursor(SEL cursorName, NSCursor* fallback) {
|
||||
if ([NSCursor respondsToSelector:cursorName]) {
|
||||
id object = [NSCursor performSelector:cursorName];
|
||||
if ([object isKindOfClass:[NSCursor class]]) {
|
||||
@@ -272,7 +302,7 @@ void gio_setCursor(NSUInteger curID) {
|
||||
break;
|
||||
case 6: // pointer.CursorAllScroll
|
||||
// For some reason, using _moveCursor fails on Monterey.
|
||||
// gio_trySetPrivateCursor(@selector(_moveCursor), NSCursor.arrowCursor);
|
||||
// trySetPrivateCursor(@selector(_moveCursor), NSCursor.arrowCursor);
|
||||
[NSCursor.arrowCursor set];
|
||||
break;
|
||||
case 7: // pointer.CursorColResize
|
||||
@@ -282,33 +312,31 @@ void gio_setCursor(NSUInteger curID) {
|
||||
[NSCursor.resizeUpDownCursor set];
|
||||
break;
|
||||
case 9: // pointer.CursorGrab
|
||||
// [NSCursor.openHandCursor set];
|
||||
gio_trySetPrivateCursor(@selector(openHandCursor), NSCursor.arrowCursor);
|
||||
[NSCursor.openHandCursor set];
|
||||
break;
|
||||
case 10: // pointer.CursorGrabbing
|
||||
// [NSCursor.closedHandCursor set];
|
||||
gio_trySetPrivateCursor(@selector(closedHandCursor), NSCursor.arrowCursor);
|
||||
[NSCursor.closedHandCursor set];
|
||||
break;
|
||||
case 11: // pointer.CursorNotAllowed
|
||||
[NSCursor.operationNotAllowedCursor set];
|
||||
break;
|
||||
case 12: // pointer.CursorWait
|
||||
gio_trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
||||
trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
||||
break;
|
||||
case 13: // pointer.CursorProgress
|
||||
gio_trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
||||
trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
||||
break;
|
||||
case 14: // pointer.CursorNorthWestResize
|
||||
gio_trySetPrivateCursor(@selector(_windowResizeNorthWestCursor), NSCursor.resizeUpDownCursor);
|
||||
trySetPrivateCursor(@selector(_windowResizeNorthWestCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 15: // pointer.CursorNorthEastResize
|
||||
gio_trySetPrivateCursor(@selector(_windowResizeNorthEastCursor), NSCursor.resizeUpDownCursor);
|
||||
trySetPrivateCursor(@selector(_windowResizeNorthEastCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 16: // pointer.CursorSouthWestResize
|
||||
gio_trySetPrivateCursor(@selector(_windowResizeSouthWestCursor), NSCursor.resizeUpDownCursor);
|
||||
trySetPrivateCursor(@selector(_windowResizeSouthWestCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 17: // pointer.CursorSouthEastResize
|
||||
gio_trySetPrivateCursor(@selector(_windowResizeSouthEastCursor), NSCursor.resizeUpDownCursor);
|
||||
trySetPrivateCursor(@selector(_windowResizeSouthEastCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 18: // pointer.CursorNorthSouthResize
|
||||
[NSCursor.resizeUpDownCursor set];
|
||||
@@ -329,10 +357,10 @@ void gio_setCursor(NSUInteger curID) {
|
||||
[NSCursor.resizeDownCursor set];
|
||||
break;
|
||||
case 24: // pointer.CursorNorthEastSouthWestResize
|
||||
gio_trySetPrivateCursor(@selector(_windowResizeNorthEastSouthWestCursor), NSCursor.resizeUpDownCursor);
|
||||
trySetPrivateCursor(@selector(_windowResizeNorthEastSouthWestCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 25: // pointer.CursorNorthWestSouthEastResize
|
||||
gio_trySetPrivateCursor(@selector(_windowResizeNorthWestSouthEastCursor), NSCursor.resizeUpDownCursor);
|
||||
trySetPrivateCursor(@selector(_windowResizeNorthWestSouthEastCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
default:
|
||||
[NSCursor.arrowCursor set];
|
||||
@@ -341,7 +369,7 @@ void gio_setCursor(NSUInteger curID) {
|
||||
}
|
||||
}
|
||||
|
||||
CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight) {
|
||||
CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height) {
|
||||
@autoreleasepool {
|
||||
NSRect rect = NSMakeRect(0, 0, width, height);
|
||||
NSUInteger styleMask = NSTitledWindowMask |
|
||||
@@ -353,42 +381,45 @@ CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGF
|
||||
styleMask:styleMask
|
||||
backing:NSBackingStoreBuffered
|
||||
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];
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
[window setContentView:view];
|
||||
[window makeFirstResponder:view];
|
||||
window.delegate = globalWindowDel;
|
||||
return (__bridge_retained CFTypeRef)window;
|
||||
}
|
||||
}
|
||||
|
||||
CFTypeRef gio_createView(void) {
|
||||
CFTypeRef gio_createView(int presentWithTrans) {
|
||||
@autoreleasepool {
|
||||
NSRect frame = NSMakeRect(0, 0, 0, 0);
|
||||
GioView* view = [[GioView alloc] initWithFrame:frame];
|
||||
view.presentWithTrans = presentWithTrans ? YES : NO;
|
||||
view.wantsLayer = YES;
|
||||
view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:view
|
||||
selector:@selector(applicationWillUnhide:)
|
||||
name:NSApplicationWillUnhideNotification
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:view
|
||||
selector:@selector(applicationDidHide:)
|
||||
name:NSApplicationDidHideNotification
|
||||
object:nil];
|
||||
return CFBridgingRetain(view);
|
||||
}
|
||||
}
|
||||
|
||||
void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
|
||||
@autoreleasepool {
|
||||
GioView *v = (__bridge GioView *)viewRef;
|
||||
v.handle = handle;
|
||||
}
|
||||
}
|
||||
|
||||
@implementation GioAppDelegate
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
gio_onFinishLaunching();
|
||||
}
|
||||
- (void)applicationDidHide:(NSNotification *)aNotification {
|
||||
gio_onAppHide();
|
||||
}
|
||||
- (void)applicationWillUnhide:(NSNotification *)notification {
|
||||
gio_onAppShow();
|
||||
}
|
||||
@end
|
||||
|
||||
@@ -419,3 +450,25 @@ void gio_main() {
|
||||
[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];
|
||||
}
|
||||
}
|
||||
|
||||
+11
-12
@@ -12,13 +12,6 @@ import (
|
||||
"gioui.org/io/pointer"
|
||||
)
|
||||
|
||||
// ViewEvent provides handles to the underlying window objects for the
|
||||
// current display protocol.
|
||||
type ViewEvent interface {
|
||||
implementsViewEvent()
|
||||
ImplementsEvent()
|
||||
}
|
||||
|
||||
type X11ViewEvent struct {
|
||||
// Display is a pointer to the X11 Display created by XOpenDisplay.
|
||||
Display unsafe.Pointer
|
||||
@@ -28,6 +21,9 @@ type X11ViewEvent struct {
|
||||
|
||||
func (X11ViewEvent) implementsViewEvent() {}
|
||||
func (X11ViewEvent) ImplementsEvent() {}
|
||||
func (x X11ViewEvent) Valid() bool {
|
||||
return x != (X11ViewEvent{})
|
||||
}
|
||||
|
||||
type WaylandViewEvent struct {
|
||||
// Display is the *wl_display returned by wl_display_connect.
|
||||
@@ -38,6 +34,9 @@ type WaylandViewEvent struct {
|
||||
|
||||
func (WaylandViewEvent) implementsViewEvent() {}
|
||||
func (WaylandViewEvent) ImplementsEvent() {}
|
||||
func (w WaylandViewEvent) Valid() bool {
|
||||
return w != (WaylandViewEvent{})
|
||||
}
|
||||
|
||||
func osMain() {
|
||||
select {}
|
||||
@@ -49,7 +48,7 @@ type windowDriver func(*callbacks, []Option) error
|
||||
// let each driver initialize these variables with their own version of createWindow.
|
||||
var wlDriver, x11Driver windowDriver
|
||||
|
||||
func newWindow(window *callbacks, options []Option) error {
|
||||
func newWindow(window *callbacks, options []Option) {
|
||||
var errFirst error
|
||||
for _, d := range []windowDriver{wlDriver, x11Driver} {
|
||||
if d == nil {
|
||||
@@ -57,16 +56,16 @@ func newWindow(window *callbacks, options []Option) error {
|
||||
}
|
||||
err := d(window, options)
|
||||
if err == nil {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
if errFirst == nil {
|
||||
errFirst = err
|
||||
}
|
||||
}
|
||||
if errFirst != nil {
|
||||
return errFirst
|
||||
if errFirst == nil {
|
||||
errFirst = errors.New("app: no window driver available")
|
||||
}
|
||||
return errors.New("app: no window driver available")
|
||||
window.ProcessEvent(DestroyEvent{Err: errFirst})
|
||||
}
|
||||
|
||||
// xCursor contains mapping from pointer.Cursor to XCursor.
|
||||
|
||||
+204
-133
@@ -15,6 +15,7 @@ import (
|
||||
"math"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -25,10 +26,12 @@ import (
|
||||
"gioui.org/app/internal/xkb"
|
||||
"gioui.org/f32"
|
||||
"gioui.org/internal/fling"
|
||||
"gioui.org/io/clipboard"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/io/transfer"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
@@ -97,7 +100,9 @@ type wlDisplay struct {
|
||||
read, write int
|
||||
}
|
||||
|
||||
repeat repeatState
|
||||
repeat repeatState
|
||||
poller poller
|
||||
readClipClose chan struct{}
|
||||
}
|
||||
|
||||
type wlSeat struct {
|
||||
@@ -111,6 +116,8 @@ type wlSeat struct {
|
||||
|
||||
// The most recent input serial.
|
||||
serial C.uint32_t
|
||||
// The most recent pointer enter serial.
|
||||
pointerSerial C.uint32_t
|
||||
|
||||
pointerFocus *window
|
||||
keyboardFocus *window
|
||||
@@ -137,7 +144,7 @@ type repeatState struct {
|
||||
delay time.Duration
|
||||
|
||||
key uint32
|
||||
win *callbacks
|
||||
win *window
|
||||
stopC chan struct{}
|
||||
|
||||
start time.Duration
|
||||
@@ -149,7 +156,6 @@ type repeatState struct {
|
||||
type window struct {
|
||||
w *callbacks
|
||||
disp *wlDisplay
|
||||
seat *wlSeat
|
||||
surf *C.struct_wl_surface
|
||||
wmSurf *C.struct_xdg_surface
|
||||
topLvl *C.struct_xdg_toplevel
|
||||
@@ -194,12 +200,10 @@ type window struct {
|
||||
dir f32.Point
|
||||
}
|
||||
|
||||
stage system.Stage
|
||||
dead bool
|
||||
configured bool
|
||||
lastFrameCallback *C.struct_wl_callback
|
||||
|
||||
animating bool
|
||||
redraw bool
|
||||
// The most recent configure serial waiting to be ack'ed.
|
||||
serial C.uint32_t
|
||||
scale int
|
||||
@@ -209,9 +213,11 @@ type window struct {
|
||||
wsize image.Point // window config size before going fullscreen or maximized
|
||||
inCompositor bool // window is moving or being resized
|
||||
|
||||
clipReads chan clipboard.Event
|
||||
clipReads chan transfer.DataEvent
|
||||
|
||||
wakeups chan struct{}
|
||||
|
||||
closing bool
|
||||
}
|
||||
|
||||
type poller struct {
|
||||
@@ -260,25 +266,17 @@ func newWLWindow(callbacks *callbacks, options []Option) error {
|
||||
return err
|
||||
}
|
||||
w.w = callbacks
|
||||
go func() {
|
||||
defer d.destroy()
|
||||
defer w.destroy()
|
||||
w.w.SetDriver(w)
|
||||
|
||||
w.w.SetDriver(w)
|
||||
// Finish and commit setup from createNativeWindow.
|
||||
w.Configure(options)
|
||||
w.draw(true)
|
||||
C.wl_surface_commit(w.surf)
|
||||
|
||||
// Finish and commit setup from createNativeWindow.
|
||||
w.Configure(options)
|
||||
C.wl_surface_commit(w.surf)
|
||||
|
||||
w.w.Event(WaylandViewEvent{
|
||||
Display: unsafe.Pointer(w.display()),
|
||||
Surface: unsafe.Pointer(w.surf),
|
||||
})
|
||||
|
||||
err := w.loop()
|
||||
w.w.Event(WaylandViewEvent{})
|
||||
w.w.Event(system.DestroyEvent{Err: err})
|
||||
}()
|
||||
w.ProcessEvent(WaylandViewEvent{
|
||||
Display: unsafe.Pointer(w.display()),
|
||||
Surface: unsafe.Pointer(w.surf),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -354,7 +352,7 @@ func (d *wlDisplay) createNativeWindow(options []Option) (*window, error) {
|
||||
ppdp: ppdp,
|
||||
ppsp: ppdp,
|
||||
wakeups: make(chan struct{}, 1),
|
||||
clipReads: make(chan clipboard.Event, 1),
|
||||
clipReads: make(chan transfer.DataEvent, 1),
|
||||
}
|
||||
w.surf = C.wl_compositor_create_surface(d.compositor)
|
||||
if w.surf == nil {
|
||||
@@ -549,15 +547,15 @@ func gio_onSeatName(data unsafe.Pointer, seat *C.struct_wl_seat, name *C.char) {
|
||||
func gio_onXdgSurfaceConfigure(data unsafe.Pointer, wmSurf *C.struct_xdg_surface, serial C.uint32_t) {
|
||||
w := callbackLoad(data).(*window)
|
||||
w.serial = serial
|
||||
w.redraw = true
|
||||
C.xdg_surface_ack_configure(wmSurf, serial)
|
||||
w.setStage(system.StageRunning)
|
||||
w.configured = true
|
||||
w.draw(true)
|
||||
}
|
||||
|
||||
//export gio_onToplevelClose
|
||||
func gio_onToplevelClose(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel) {
|
||||
w := callbackLoad(data).(*window)
|
||||
w.dead = true
|
||||
w.closing = true
|
||||
}
|
||||
|
||||
//export gio_onToplevelConfigure
|
||||
@@ -586,8 +584,8 @@ func gio_onToplevelDecorationConfigure(data unsafe.Pointer, deco *C.struct_zxdg_
|
||||
} else {
|
||||
w.size.Y += int(w.config.decoHeight)
|
||||
}
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.redraw = true
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
w.draw(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -645,7 +643,7 @@ func gio_onSurfaceEnter(data unsafe.Pointer, surf *C.struct_wl_surface, output *
|
||||
if w.config.Mode == Minimized {
|
||||
// Minimized window got brought back up: it is no longer so.
|
||||
w.config.Mode = Windowed
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -790,7 +788,7 @@ func gio_onTouchDown(data unsafe.Pointer, touch *C.struct_wl_touch, serial, t C.
|
||||
X: fromFixed(x) * float32(w.scale),
|
||||
Y: fromFixed(y) * float32(w.scale),
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Press,
|
||||
Source: pointer.Touch,
|
||||
Position: w.lastTouch,
|
||||
@@ -806,7 +804,7 @@ func gio_onTouchUp(data unsafe.Pointer, touch *C.struct_wl_touch, serial, t C.ui
|
||||
s.serial = serial
|
||||
w := s.touchFoci[id]
|
||||
delete(s.touchFoci, id)
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Release,
|
||||
Source: pointer.Touch,
|
||||
Position: w.lastTouch,
|
||||
@@ -824,7 +822,7 @@ func gio_onTouchMotion(data unsafe.Pointer, touch *C.struct_wl_touch, t C.uint32
|
||||
X: fromFixed(x) * float32(w.scale),
|
||||
Y: fromFixed(y) * float32(w.scale),
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Move,
|
||||
Position: w.lastTouch,
|
||||
Source: pointer.Touch,
|
||||
@@ -843,7 +841,7 @@ func gio_onTouchCancel(data unsafe.Pointer, touch *C.struct_wl_touch) {
|
||||
s := callbackLoad(data).(*wlSeat)
|
||||
for id, w := range s.touchFoci {
|
||||
delete(s.touchFoci, id)
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Cancel,
|
||||
Source: pointer.Touch,
|
||||
})
|
||||
@@ -854,8 +852,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) {
|
||||
s := callbackLoad(data).(*wlSeat)
|
||||
s.serial = serial
|
||||
s.pointerSerial = serial
|
||||
w := callbackLoad(unsafe.Pointer(surf)).(*window)
|
||||
w.seat = s
|
||||
s.pointerFocus = w
|
||||
w.setCursor(pointer, serial)
|
||||
w.lastPos = f32.Point{X: fromFixed(x), Y: fromFixed(y)}
|
||||
@@ -864,12 +862,12 @@ func gio_onPointerEnter(data unsafe.Pointer, pointer *C.struct_wl_pointer, seria
|
||||
//export gio_onPointerLeave
|
||||
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.seat = nil
|
||||
s := callbackLoad(data).(*wlSeat)
|
||||
s.serial = serial
|
||||
s.pointerFocus = nil
|
||||
if w.inCompositor {
|
||||
w.inCompositor = false
|
||||
w.w.Event(pointer.Event{Kind: pointer.Cancel})
|
||||
w.ProcessEvent(pointer.Event{Kind: pointer.Cancel})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -886,11 +884,13 @@ func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t,
|
||||
s := callbackLoad(data).(*wlSeat)
|
||||
s.serial = serial
|
||||
w := s.pointerFocus
|
||||
// From linux-event-codes.h.
|
||||
// From Linux: include/uapi/linux/input-event-codes.h
|
||||
const (
|
||||
BTN_LEFT = 0x110
|
||||
BTN_RIGHT = 0x111
|
||||
BTN_MIDDLE = 0x112
|
||||
BTN_SIDE = 0x113
|
||||
BTN_EXTRA = 0x114
|
||||
)
|
||||
var btn pointer.Buttons
|
||||
switch wbtn {
|
||||
@@ -900,6 +900,10 @@ func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t,
|
||||
btn = pointer.ButtonSecondary
|
||||
case BTN_MIDDLE:
|
||||
btn = pointer.ButtonTertiary
|
||||
case BTN_SIDE:
|
||||
btn = pointer.ButtonQuaternary
|
||||
case BTN_EXTRA:
|
||||
btn = pointer.ButtonQuinary
|
||||
default:
|
||||
return
|
||||
}
|
||||
@@ -930,7 +934,7 @@ func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t,
|
||||
}
|
||||
w.flushScroll()
|
||||
w.resetFling()
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: kind,
|
||||
Source: pointer.Mouse,
|
||||
Buttons: w.pointerBtns,
|
||||
@@ -966,6 +970,9 @@ 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) {
|
||||
s := callbackLoad(data).(*wlSeat)
|
||||
w := s.pointerFocus
|
||||
if w == nil {
|
||||
return
|
||||
}
|
||||
w.flushScroll()
|
||||
w.flushFling()
|
||||
}
|
||||
@@ -1018,23 +1025,34 @@ func gio_onPointerAxisDiscrete(data unsafe.Pointer, p *C.struct_wl_pointer, axis
|
||||
}
|
||||
|
||||
func (w *window) ReadClipboard() {
|
||||
if w.disp.readClipClose != nil {
|
||||
return
|
||||
}
|
||||
w.disp.readClipClose = make(chan struct{})
|
||||
r, err := w.disp.readClipboard()
|
||||
// Send empty responses on unavailable clipboards or errors.
|
||||
if r == nil || err != nil {
|
||||
w.w.Event(clipboard.Event{})
|
||||
return
|
||||
}
|
||||
// Don't let slow clipboard transfers block event loop.
|
||||
go func() {
|
||||
defer r.Close()
|
||||
data, _ := io.ReadAll(r)
|
||||
w.clipReads <- clipboard.Event{Text: string(data)}
|
||||
w.Wakeup()
|
||||
e := transfer.DataEvent{
|
||||
Type: "application/text",
|
||||
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(s string) {
|
||||
w.disp.writeClipboard([]byte(s))
|
||||
func (w *window) WriteClipboard(mime string, s []byte) {
|
||||
w.disp.writeClipboard(s)
|
||||
}
|
||||
|
||||
func (w *window) Configure(options []Option) {
|
||||
@@ -1092,8 +1110,7 @@ func (w *window) Configure(options []Option) {
|
||||
w.config.MaxSize = cnf.MaxSize
|
||||
w.setWindowConstraints()
|
||||
}
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.redraw = true
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
func (w *window) setWindowConstraints() {
|
||||
@@ -1130,22 +1147,23 @@ func (w *window) Perform(actions system.Action) {
|
||||
walkActions(actions, func(action system.Action) {
|
||||
switch action {
|
||||
case system.ActionClose:
|
||||
w.dead = true
|
||||
w.closing = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) move(serial C.uint32_t) {
|
||||
s := w.seat
|
||||
if !w.inCompositor && s != nil {
|
||||
w.inCompositor = true
|
||||
C.xdg_toplevel_move(w.topLvl, s.seat, serial)
|
||||
s := w.disp.seat
|
||||
if w.inCompositor || s.pointerFocus != w {
|
||||
return
|
||||
}
|
||||
w.inCompositor = true
|
||||
C.xdg_toplevel_move(w.topLvl, s.seat, serial)
|
||||
}
|
||||
|
||||
func (w *window) resize(serial, edge C.uint32_t) {
|
||||
s := w.seat
|
||||
if w.inCompositor || s == nil {
|
||||
s := w.disp.seat
|
||||
if w.inCompositor || s.pointerFocus != w {
|
||||
return
|
||||
}
|
||||
w.inCompositor = true
|
||||
@@ -1158,11 +1176,12 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
||||
}
|
||||
|
||||
func (w *window) updateCursor() {
|
||||
ptr := w.disp.seat.pointer
|
||||
if ptr == nil {
|
||||
s := w.disp.seat
|
||||
ptr := s.pointer
|
||||
if ptr == nil || s.pointerFocus != w {
|
||||
return
|
||||
}
|
||||
w.setCursor(ptr, w.serial)
|
||||
w.setCursor(ptr, s.pointerSerial)
|
||||
}
|
||||
|
||||
func (w *window) setCursor(pointer *C.struct_wl_pointer, serial C.uint32_t) {
|
||||
@@ -1171,7 +1190,7 @@ func (w *window) setCursor(pointer *C.struct_wl_pointer, serial C.uint32_t) {
|
||||
c = w.cursor.cursor
|
||||
}
|
||||
if c == nil {
|
||||
C.wl_pointer_set_cursor(pointer, w.serial, nil, 0, 0)
|
||||
C.wl_pointer_set_cursor(pointer, serial, nil, 0, 0)
|
||||
return
|
||||
}
|
||||
// Get images[0].
|
||||
@@ -1213,7 +1232,8 @@ func gio_onKeyboardEnter(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, se
|
||||
w := callbackLoad(unsafe.Pointer(surf)).(*window)
|
||||
s.keyboardFocus = w
|
||||
s.disp.repeat.Stop(0)
|
||||
w.w.Event(key.FocusEvent{Focus: true})
|
||||
w.config.Focused = true
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
//export gio_onKeyboardLeave
|
||||
@@ -1222,7 +1242,8 @@ func gio_onKeyboardLeave(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, se
|
||||
s.serial = serial
|
||||
s.disp.repeat.Stop(0)
|
||||
w := s.keyboardFocus
|
||||
w.w.Event(key.FocusEvent{Focus: false})
|
||||
w.config.Focused = false
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
//export gio_onKeyboardKey
|
||||
@@ -1240,7 +1261,7 @@ func gio_onKeyboardKey(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, seri
|
||||
// There's no support for IME yet.
|
||||
w.w.EditorInsert(ee.Text)
|
||||
} else {
|
||||
w.w.Event(e)
|
||||
w.ProcessEvent(e)
|
||||
}
|
||||
}
|
||||
if state != C.WL_KEYBOARD_KEY_STATE_PRESSED {
|
||||
@@ -1275,7 +1296,7 @@ func (r *repeatState) Start(w *window, keyCode uint32, t time.Duration) {
|
||||
r.now = 0
|
||||
r.stopC = stopC
|
||||
r.key = keyCode
|
||||
r.win = w.w
|
||||
r.win = w
|
||||
rate, delay := r.rate, r.delay
|
||||
go func() {
|
||||
timer := time.NewTimer(delay)
|
||||
@@ -1333,9 +1354,9 @@ func (r *repeatState) Repeat(d *wlDisplay) {
|
||||
for _, e := range d.xkb.DispatchKey(r.key, key.Press) {
|
||||
if ee, ok := e.(key.EditEvent); ok {
|
||||
// There's no support for IME yet.
|
||||
r.win.EditorInsert(ee.Text)
|
||||
r.win.w.EditorInsert(ee.Text)
|
||||
} else {
|
||||
r.win.Event(e)
|
||||
r.win.ProcessEvent(e)
|
||||
}
|
||||
}
|
||||
r.last += delay
|
||||
@@ -1348,28 +1369,68 @@ func gio_onFrameDone(data unsafe.Pointer, callback *C.struct_wl_callback, t C.ui
|
||||
w := callbackLoad(data).(*window)
|
||||
if w.lastFrameCallback == callback {
|
||||
w.lastFrameCallback = nil
|
||||
w.draw(false)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) loop() error {
|
||||
var p poller
|
||||
for {
|
||||
if err := w.disp.dispatch(&p); err != nil {
|
||||
return err
|
||||
}
|
||||
select {
|
||||
case e := <-w.clipReads:
|
||||
w.w.Event(e)
|
||||
case <-w.wakeups:
|
||||
w.w.Event(wakeupEvent{})
|
||||
default:
|
||||
}
|
||||
if w.dead {
|
||||
break
|
||||
}
|
||||
w.draw()
|
||||
func (w *window) close(err error) {
|
||||
w.ProcessEvent(WaylandViewEvent{})
|
||||
w.ProcessEvent(DestroyEvent{Err: err})
|
||||
w.destroy()
|
||||
w.disp.destroy()
|
||||
w.disp = nil
|
||||
}
|
||||
|
||||
func (w *window) dispatch() {
|
||||
if w.disp == nil {
|
||||
<-w.wakeups
|
||||
w.w.Invalidate()
|
||||
return
|
||||
}
|
||||
return nil
|
||||
if err := w.disp.dispatch(); err != nil || w.closing {
|
||||
w.close(err)
|
||||
return
|
||||
}
|
||||
select {
|
||||
case e := <-w.clipReads:
|
||||
w.disp.readClipClose = nil
|
||||
w.ProcessEvent(e)
|
||||
case <-w.wakeups:
|
||||
w.w.Invalidate()
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) ProcessEvent(e event.Event) {
|
||||
w.w.ProcessEvent(e)
|
||||
}
|
||||
|
||||
func (w *window) Event() event.Event {
|
||||
for {
|
||||
evt, ok := w.w.nextEvent()
|
||||
if !ok {
|
||||
w.dispatch()
|
||||
continue
|
||||
}
|
||||
return evt
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) Invalidate() {
|
||||
select {
|
||||
case w.wakeups <- struct{}{}:
|
||||
default:
|
||||
return
|
||||
}
|
||||
w.disp.wakeup()
|
||||
}
|
||||
|
||||
func (w *window) Run(f func()) {
|
||||
f()
|
||||
}
|
||||
|
||||
func (w *window) Frame(frame *op.Ops) {
|
||||
w.w.ProcessFrame(frame, nil)
|
||||
}
|
||||
|
||||
// bindDataDevice initializes the dataDev field if and only if both
|
||||
@@ -1385,13 +1446,21 @@ func (d *wlDisplay) bindDataDevice() {
|
||||
}
|
||||
}
|
||||
|
||||
func (d *wlDisplay) dispatch(p *poller) error {
|
||||
func (d *wlDisplay) dispatch() error {
|
||||
// wl_display_prepare_read records the current thread for
|
||||
// use in wl_display_read_events or wl_display_cancel_events.
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
dispfd := C.wl_display_get_fd(d.disp)
|
||||
// Poll for events and notifications.
|
||||
pollfds := append(p.pollfds[:0],
|
||||
pollfds := append(d.poller.pollfds[:0],
|
||||
syscall.PollFd{Fd: int32(dispfd), Events: syscall.POLLIN | syscall.POLLERR},
|
||||
syscall.PollFd{Fd: int32(d.notify.read), Events: syscall.POLLIN | syscall.POLLERR},
|
||||
)
|
||||
for C.wl_display_prepare_read(d.disp) != 0 {
|
||||
C.wl_display_dispatch_pending(d.disp)
|
||||
}
|
||||
dispFd := &pollfds[0]
|
||||
if ret, err := C.wl_display_flush(d.disp); ret < 0 {
|
||||
if err != syscall.EAGAIN {
|
||||
@@ -1402,11 +1471,25 @@ func (d *wlDisplay) dispatch(p *poller) error {
|
||||
dispFd.Events |= syscall.POLLOUT
|
||||
}
|
||||
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
||||
C.wl_display_cancel_read(d.disp)
|
||||
return fmt.Errorf("wayland: poll failed: %v", err)
|
||||
}
|
||||
if dispFd.Revents&(syscall.POLLERR|syscall.POLLHUP) != 0 {
|
||||
C.wl_display_cancel_read(d.disp)
|
||||
return errors.New("wayland: display file descriptor gone")
|
||||
}
|
||||
// Handle events.
|
||||
if dispFd.Revents&syscall.POLLIN != 0 {
|
||||
if ret, err := C.wl_display_read_events(d.disp); ret < 0 {
|
||||
return fmt.Errorf("wayland: wl_display_read_events failed: %v", err)
|
||||
}
|
||||
C.wl_display_dispatch_pending(d.disp)
|
||||
} else {
|
||||
C.wl_display_cancel_read(d.disp)
|
||||
}
|
||||
// Clear notifications.
|
||||
for {
|
||||
_, err := syscall.Read(d.notify.read, p.buf[:])
|
||||
_, err := syscall.Read(d.notify.read, d.poller.buf[:])
|
||||
if err == syscall.EAGAIN {
|
||||
break
|
||||
}
|
||||
@@ -1414,29 +1497,15 @@ func (d *wlDisplay) dispatch(p *poller) error {
|
||||
return fmt.Errorf("wayland: read from notify pipe failed: %v", err)
|
||||
}
|
||||
}
|
||||
// Handle events
|
||||
switch {
|
||||
case dispFd.Revents&syscall.POLLIN != 0:
|
||||
if ret, err := C.wl_display_dispatch(d.disp); ret < 0 {
|
||||
return fmt.Errorf("wayland: wl_display_dispatch failed: %v", err)
|
||||
}
|
||||
case dispFd.Revents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
||||
return errors.New("wayland: display file descriptor gone")
|
||||
}
|
||||
d.repeat.Repeat(d)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) Wakeup() {
|
||||
select {
|
||||
case w.wakeups <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
w.disp.wakeup()
|
||||
}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
w.animating = anim
|
||||
if anim {
|
||||
w.draw(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Wakeup wakes up the event loop through the notification pipe.
|
||||
@@ -1448,6 +1517,10 @@ func (d *wlDisplay) wakeup() {
|
||||
}
|
||||
|
||||
func (w *window) destroy() {
|
||||
if w.lastFrameCallback != nil {
|
||||
C.wl_callback_destroy(w.lastFrameCallback)
|
||||
w.lastFrameCallback = nil
|
||||
}
|
||||
if w.cursor.surf != nil {
|
||||
C.wl_surface_destroy(w.cursor.surf)
|
||||
}
|
||||
@@ -1572,7 +1645,15 @@ func (w *window) flushScroll() {
|
||||
if total == (f32.Point{}) {
|
||||
return
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
if w.scroll.steps == (image.Point{}) {
|
||||
w.fling.xExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.X)
|
||||
w.fling.yExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.Y)
|
||||
}
|
||||
// Zero scroll distance prior to calling ProcessEvent, otherwise we may recursively
|
||||
// re-process the scroll distance.
|
||||
w.scroll.dist = f32.Point{}
|
||||
w.scroll.steps = image.Point{}
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Scroll,
|
||||
Source: pointer.Mouse,
|
||||
Buttons: w.pointerBtns,
|
||||
@@ -1581,12 +1662,6 @@ func (w *window) flushScroll() {
|
||||
Time: w.scroll.time,
|
||||
Modifiers: w.disp.xkb.Modifiers(),
|
||||
})
|
||||
if w.scroll.steps == (image.Point{}) {
|
||||
w.fling.xExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.X)
|
||||
w.fling.yExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.Y)
|
||||
}
|
||||
w.scroll.dist = f32.Point{}
|
||||
w.scroll.steps = image.Point{}
|
||||
}
|
||||
|
||||
func (w *window) onPointerMotion(x, y C.wl_fixed_t, t C.uint32_t) {
|
||||
@@ -1595,7 +1670,7 @@ func (w *window) onPointerMotion(x, y C.wl_fixed_t, t C.uint32_t) {
|
||||
X: fromFixed(x) * float32(w.scale),
|
||||
Y: fromFixed(y) * float32(w.scale),
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Move,
|
||||
Position: w.lastPos,
|
||||
Buttons: w.pointerBtns,
|
||||
@@ -1616,7 +1691,8 @@ func (w *window) systemGesture() (*C.struct_wl_cursor, C.uint32_t) {
|
||||
if w.config.Mode != Windowed || w.config.Decorated {
|
||||
return nil, 0
|
||||
}
|
||||
border := w.w.w.metric.Dp(3)
|
||||
_, cfg := w.getConfig()
|
||||
border := cfg.Dp(3)
|
||||
x, y, size := int(w.lastPos.X), int(w.lastPos.Y), w.config.Size
|
||||
north := y <= border
|
||||
south := y >= size.Y-border
|
||||
@@ -1670,13 +1746,10 @@ func (w *window) updateOutputs() {
|
||||
if found && scale != w.scale {
|
||||
w.scale = scale
|
||||
C.wl_surface_set_buffer_scale(w.surf, C.int32_t(w.scale))
|
||||
w.redraw = true
|
||||
w.draw(true)
|
||||
}
|
||||
if !found {
|
||||
w.setStage(system.StagePaused)
|
||||
} else {
|
||||
w.setStage(system.StageRunning)
|
||||
w.redraw = true
|
||||
if found {
|
||||
w.draw(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1688,7 +1761,10 @@ func (w *window) getConfig() (image.Point, unit.Metric) {
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) draw() {
|
||||
func (w *window) draw(sync bool) {
|
||||
if !w.configured {
|
||||
return
|
||||
}
|
||||
w.flushScroll()
|
||||
size, cfg := w.getConfig()
|
||||
if cfg == (unit.Metric{}) {
|
||||
@@ -1696,11 +1772,9 @@ func (w *window) draw() {
|
||||
}
|
||||
if size != w.config.Size {
|
||||
w.config.Size = size
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
anim := w.animating || w.fling.anim.Active()
|
||||
sync := w.redraw
|
||||
w.redraw = false
|
||||
// Draw animation only when not waiting for frame callback.
|
||||
redrawAnim := anim && w.lastFrameCallback == nil
|
||||
if !redrawAnim && !sync {
|
||||
@@ -1711,8 +1785,8 @@ func (w *window) draw() {
|
||||
// Use the surface as listener data for gio_onFrameDone.
|
||||
C.wl_callback_add_listener(w.lastFrameCallback, &C.gio_callback_listener, unsafe.Pointer(w.surf))
|
||||
}
|
||||
w.w.Event(frameEvent{
|
||||
FrameEvent: system.FrameEvent{
|
||||
w.ProcessEvent(frameEvent{
|
||||
FrameEvent: FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: w.config.Size,
|
||||
Metric: cfg,
|
||||
@@ -1721,14 +1795,6 @@ func (w *window) draw() {
|
||||
})
|
||||
}
|
||||
|
||||
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 {
|
||||
return w.disp.disp
|
||||
}
|
||||
@@ -1820,6 +1886,10 @@ func newWLDisplay() (*wlDisplay, error) {
|
||||
}
|
||||
|
||||
func (d *wlDisplay) destroy() {
|
||||
if d.readClipClose != nil {
|
||||
close(d.readClipClose)
|
||||
d.readClipClose = nil
|
||||
}
|
||||
if d.notify.write != 0 {
|
||||
syscall.Close(d.notify.write)
|
||||
d.notify.write = 0
|
||||
@@ -1861,6 +1931,7 @@ func (d *wlDisplay) destroy() {
|
||||
if d.disp != nil {
|
||||
C.wl_display_disconnect(d.disp)
|
||||
callbackDelete(unsafe.Pointer(d.disp))
|
||||
d.disp = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+249
-165
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"io"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -18,40 +19,39 @@ import (
|
||||
syscall "golang.org/x/sys/windows"
|
||||
|
||||
"gioui.org/app/internal/windows"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
gowindows "golang.org/x/sys/windows"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/clipboard"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/io/transfer"
|
||||
)
|
||||
|
||||
type ViewEvent struct {
|
||||
type Win32ViewEvent struct {
|
||||
HWND uintptr
|
||||
}
|
||||
|
||||
type window struct {
|
||||
hwnd syscall.Handle
|
||||
hdc syscall.Handle
|
||||
w *callbacks
|
||||
stage system.Stage
|
||||
pointerBtns pointer.Buttons
|
||||
hwnd syscall.Handle
|
||||
hdc syscall.Handle
|
||||
w *callbacks
|
||||
|
||||
// cursorIn tracks whether the cursor was inside the window according
|
||||
// to the most recent WM_SETCURSOR.
|
||||
cursorIn bool
|
||||
cursor syscall.Handle
|
||||
|
||||
// placement saves the previous window position when in full screen mode.
|
||||
placement *windows.WindowPlacement
|
||||
|
||||
animating bool
|
||||
focused bool
|
||||
|
||||
borderSize image.Point
|
||||
config Config
|
||||
// frameDims stores the last seen window frame width and height.
|
||||
frameDims image.Point
|
||||
loop *eventLoop
|
||||
}
|
||||
|
||||
const _WM_WAKEUP = windows.WM_USER + iota
|
||||
@@ -84,36 +84,38 @@ func osMain() {
|
||||
select {}
|
||||
}
|
||||
|
||||
func newWindow(window *callbacks, options []Option) error {
|
||||
cerr := make(chan error)
|
||||
func newWindow(win *callbacks, options []Option) {
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
// GetMessage and PeekMessage can filter on a window HWND, but
|
||||
// then thread-specific messages such as WM_QUIT are ignored.
|
||||
// Instead lock the thread so window messages arrive through
|
||||
// unfiltered GetMessage calls.
|
||||
runtime.LockOSThread()
|
||||
w, err := createNativeWindow()
|
||||
|
||||
w := &window{
|
||||
w: win,
|
||||
}
|
||||
w.loop = newEventLoop(w.w, w.wakeup)
|
||||
w.w.SetDriver(w)
|
||||
err := w.init()
|
||||
done <- struct{}{}
|
||||
if err != nil {
|
||||
cerr <- err
|
||||
w.ProcessEvent(DestroyEvent{Err: err})
|
||||
return
|
||||
}
|
||||
cerr <- nil
|
||||
winMap.Store(w.hwnd, w)
|
||||
defer winMap.Delete(w.hwnd)
|
||||
w.w = window
|
||||
w.w.SetDriver(w)
|
||||
w.w.Event(ViewEvent{HWND: uintptr(w.hwnd)})
|
||||
w.Configure(options)
|
||||
w.ProcessEvent(Win32ViewEvent{HWND: uintptr(w.hwnd)})
|
||||
windows.SetForegroundWindow(w.hwnd)
|
||||
windows.SetFocus(w.hwnd)
|
||||
// Since the window class for the cursor is null,
|
||||
// set it here to show the cursor.
|
||||
w.SetCursor(pointer.CursorDefault)
|
||||
if err := w.loop(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
w.runLoop()
|
||||
}()
|
||||
return <-cerr
|
||||
<-done
|
||||
}
|
||||
|
||||
// initResources initializes the resources global.
|
||||
@@ -148,13 +150,13 @@ func initResources() error {
|
||||
|
||||
const dwExStyle = windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE
|
||||
|
||||
func createNativeWindow() (*window, error) {
|
||||
func (w *window) init() error {
|
||||
var resErr error
|
||||
resources.once.Do(func() {
|
||||
resErr = initResources()
|
||||
})
|
||||
if resErr != nil {
|
||||
return nil, resErr
|
||||
return resErr
|
||||
}
|
||||
const dwStyle = windows.WS_OVERLAPPEDWINDOW
|
||||
|
||||
@@ -170,33 +172,56 @@ func createNativeWindow() (*window, error) {
|
||||
resources.handle,
|
||||
0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
w := &window{
|
||||
hwnd: hwnd,
|
||||
if err := windows.RegisterTouchWindow(hwnd, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := windows.EnableMouseInPointer(1); err != nil {
|
||||
return err
|
||||
}
|
||||
w.hdc, err = windows.GetDC(hwnd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
windows.DestroyWindow(hwnd)
|
||||
return err
|
||||
}
|
||||
return w, nil
|
||||
w.hwnd = hwnd
|
||||
return nil
|
||||
}
|
||||
|
||||
// update() handles changes done by the user, and updates the configuration.
|
||||
// update handles changes done by the user, and updates the configuration.
|
||||
// It reads the window style and size/position and updates w.config.
|
||||
// If anything has changed it emits a ConfigEvent to notify the application.
|
||||
func (w *window) update() {
|
||||
cr := windows.GetClientRect(w.hwnd)
|
||||
w.config.Size = image.Point{
|
||||
X: int(cr.Right - cr.Left),
|
||||
Y: int(cr.Bottom - cr.Top),
|
||||
p := windows.GetWindowPlacement(w.hwnd)
|
||||
if !p.IsMinimized() {
|
||||
r := windows.GetWindowRect(w.hwnd)
|
||||
cr := windows.GetClientRect(w.hwnd)
|
||||
w.config.Size = image.Point{
|
||||
X: int(cr.Right - cr.Left),
|
||||
Y: int(cr.Bottom - cr.Top),
|
||||
}
|
||||
w.frameDims = image.Point{
|
||||
X: int(r.Right - r.Left),
|
||||
Y: int(r.Bottom - r.Top),
|
||||
}.Sub(w.config.Size)
|
||||
}
|
||||
|
||||
w.borderSize = image.Pt(
|
||||
windows.GetSystemMetrics(windows.SM_CXSIZEFRAME),
|
||||
windows.GetSystemMetrics(windows.SM_CYSIZEFRAME),
|
||||
)
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE)
|
||||
switch {
|
||||
case p.IsMaximized() && style&windows.WS_OVERLAPPEDWINDOW != 0:
|
||||
w.config.Mode = Maximized
|
||||
case p.IsMaximized():
|
||||
w.config.Mode = Fullscreen
|
||||
default:
|
||||
w.config.Mode = Windowed
|
||||
}
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
w.draw(true)
|
||||
}
|
||||
|
||||
func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
|
||||
@@ -237,7 +262,7 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
||||
e.State = key.Release
|
||||
}
|
||||
|
||||
w.w.Event(e)
|
||||
w.ProcessEvent(e)
|
||||
|
||||
if (wParam == windows.VK_F10) && (msg == windows.WM_SYSKEYDOWN || msg == windows.WM_SYSKEYUP) {
|
||||
// Reserve F10 for ourselves, and don't let it open the system menu. Other Windows programs
|
||||
@@ -245,36 +270,42 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
||||
return 0
|
||||
}
|
||||
}
|
||||
case windows.WM_LBUTTONDOWN:
|
||||
w.pointerButton(pointer.ButtonPrimary, true, lParam, getModifiers())
|
||||
case windows.WM_LBUTTONUP:
|
||||
w.pointerButton(pointer.ButtonPrimary, false, lParam, getModifiers())
|
||||
case windows.WM_RBUTTONDOWN:
|
||||
w.pointerButton(pointer.ButtonSecondary, true, lParam, getModifiers())
|
||||
case windows.WM_RBUTTONUP:
|
||||
w.pointerButton(pointer.ButtonSecondary, false, lParam, getModifiers())
|
||||
case windows.WM_MBUTTONDOWN:
|
||||
w.pointerButton(pointer.ButtonTertiary, true, lParam, getModifiers())
|
||||
case windows.WM_MBUTTONUP:
|
||||
w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers())
|
||||
case windows.WM_POINTERDOWN, windows.WM_POINTERUP, windows.WM_POINTERUPDATE, windows.WM_POINTERCAPTURECHANGED:
|
||||
pid := getPointerIDwParam(wParam)
|
||||
pi, err := windows.GetPointerInfo(uint32(pid))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
switch msg {
|
||||
case windows.WM_POINTERDOWN:
|
||||
windows.SetCapture(w.hwnd)
|
||||
case windows.WM_POINTERUP:
|
||||
windows.ReleaseCapture()
|
||||
}
|
||||
|
||||
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:
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Cancel,
|
||||
})
|
||||
case windows.WM_SETFOCUS:
|
||||
w.focused = true
|
||||
w.w.Event(key.FocusEvent{Focus: true})
|
||||
w.config.Focused = true
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
case windows.WM_KILLFOCUS:
|
||||
w.focused = false
|
||||
w.w.Event(key.FocusEvent{Focus: false})
|
||||
case windows.WM_NCACTIVATE:
|
||||
if w.stage >= system.StageInactive {
|
||||
if wParam == windows.TRUE {
|
||||
w.setStage(system.StageRunning)
|
||||
} else {
|
||||
w.setStage(system.StageInactive)
|
||||
}
|
||||
}
|
||||
w.config.Focused = false
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
case windows.WM_NCHITTEST:
|
||||
if w.config.Decorated {
|
||||
// Let the system handle it.
|
||||
@@ -284,24 +315,14 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
||||
np := windows.Point{X: int32(x), Y: int32(y)}
|
||||
windows.ScreenToClient(w.hwnd, &np)
|
||||
return w.hitTest(int(np.X), int(np.Y))
|
||||
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:
|
||||
case windows.WM_POINTERWHEEL:
|
||||
w.scrollEvent(wParam, lParam, false, getModifiers())
|
||||
case windows.WM_MOUSEHWHEEL:
|
||||
case windows.WM_POINTERHWHEEL:
|
||||
w.scrollEvent(wParam, lParam, true, getModifiers())
|
||||
case windows.WM_DESTROY:
|
||||
w.w.Event(ViewEvent{})
|
||||
w.w.Event(system.DestroyEvent{})
|
||||
w.ProcessEvent(Win32ViewEvent{})
|
||||
w.ProcessEvent(DestroyEvent{})
|
||||
w.w = nil
|
||||
if w.hdc != 0 {
|
||||
windows.ReleaseDC(w.hdc)
|
||||
w.hdc = 0
|
||||
@@ -309,6 +330,7 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
||||
// The system destroys the HWND for us.
|
||||
w.hwnd = 0
|
||||
windows.PostQuitMessage(0)
|
||||
return 0
|
||||
case windows.WM_NCCALCSIZE:
|
||||
if w.config.Decorated {
|
||||
// Let Windows handle decorations.
|
||||
@@ -327,46 +349,37 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
||||
// Adjust window position to avoid the extra padding in maximized
|
||||
// state. See https://devblogs.microsoft.com/oldnewthing/20150304-00/?p=44543.
|
||||
// Note that trying to do the adjustment in WM_GETMINMAXINFO is ignored by Windows.
|
||||
szp := (*windows.NCCalcSizeParams)(unsafe.Pointer(uintptr(lParam)))
|
||||
szp := (*windows.NCCalcSizeParams)(unsafe.Pointer(lParam))
|
||||
mi := windows.GetMonitorInfo(w.hwnd)
|
||||
szp.Rgrc[0] = mi.WorkArea
|
||||
return 0
|
||||
case windows.WM_PAINT:
|
||||
w.draw(true)
|
||||
case windows.WM_STYLECHANGED:
|
||||
w.update()
|
||||
case windows.WM_WINDOWPOSCHANGED:
|
||||
w.update()
|
||||
case windows.WM_SIZE:
|
||||
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:
|
||||
mm := (*windows.MinMaxInfo)(unsafe.Pointer(uintptr(lParam)))
|
||||
var bw, bh int32
|
||||
mm := (*windows.MinMaxInfo)(unsafe.Pointer(lParam))
|
||||
|
||||
var frameDims image.Point
|
||||
if w.config.Decorated {
|
||||
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)
|
||||
frameDims = w.frameDims
|
||||
}
|
||||
if p := w.config.MinSize; p.X > 0 || p.Y > 0 {
|
||||
p = p.Add(frameDims)
|
||||
mm.PtMinTrackSize = windows.Point{
|
||||
X: int32(p.X) + bw,
|
||||
Y: int32(p.Y) + bh,
|
||||
X: int32(p.X),
|
||||
Y: int32(p.Y),
|
||||
}
|
||||
}
|
||||
if p := w.config.MaxSize; p.X > 0 || p.Y > 0 {
|
||||
p = p.Add(frameDims)
|
||||
mm.PtMaxTrackSize = windows.Point{
|
||||
X: int32(p.X) + bw,
|
||||
Y: int32(p.Y) + bh,
|
||||
X: int32(p.X),
|
||||
Y: int32(p.Y),
|
||||
}
|
||||
}
|
||||
return 0
|
||||
@@ -377,7 +390,8 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
||||
return windows.TRUE
|
||||
}
|
||||
case _WM_WAKEUP:
|
||||
w.w.Event(wakeupEvent{})
|
||||
w.loop.Wakeup()
|
||||
w.loop.FlushEvents()
|
||||
case windows.WM_IME_STARTCOMPOSITION:
|
||||
imc := windows.ImmGetContext(w.hwnd)
|
||||
if imc == 0 {
|
||||
@@ -457,9 +471,6 @@ func getModifiers() key.Modifiers {
|
||||
// hitTest returns the non-client area hit by the point, needed to
|
||||
// process WM_NCHITTEST.
|
||||
func (w *window) hitTest(x, y int) uintptr {
|
||||
if w.config.Mode == Fullscreen {
|
||||
return windows.HTCLIENT
|
||||
}
|
||||
if w.config.Mode != Windowed {
|
||||
// Only windowed mode should allow resizing.
|
||||
return windows.HTCLIENT
|
||||
@@ -496,34 +507,28 @@ func (w *window) hitTest(x, y int) uintptr {
|
||||
return windows.HTCLIENT
|
||||
}
|
||||
|
||||
func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) {
|
||||
if !w.focused {
|
||||
func (w *window) pointerUpdate(pi windows.PointerInfo, pid pointer.ID, kind pointer.Kind, lParam uintptr) {
|
||||
if !w.config.Focused {
|
||||
windows.SetFocus(w.hwnd)
|
||||
}
|
||||
|
||||
var kind pointer.Kind
|
||||
if press {
|
||||
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()
|
||||
}
|
||||
src := pointer.Touch
|
||||
if pi.PointerType == windows.PT_MOUSE {
|
||||
src = pointer.Mouse
|
||||
}
|
||||
|
||||
x, y := coordsFromlParam(lParam)
|
||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||
w.w.Event(pointer.Event{
|
||||
np := windows.Point{X: int32(x), Y: int32(y)}
|
||||
windows.ScreenToClient(w.hwnd, &np)
|
||||
p := f32.Point{X: float32(np.X), Y: float32(np.Y)}
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: kind,
|
||||
Source: pointer.Mouse,
|
||||
Source: src,
|
||||
Position: p,
|
||||
Buttons: w.pointerBtns,
|
||||
PointerID: pid,
|
||||
Buttons: getPointerButtons(pi),
|
||||
Time: windows.GetMessageTime(),
|
||||
Modifiers: kmods,
|
||||
Modifiers: getModifiers(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -534,6 +539,12 @@ func coordsFromlParam(lParam uintptr) (int, int) {
|
||||
}
|
||||
|
||||
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)
|
||||
// The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast
|
||||
// to other mouse events.
|
||||
@@ -552,11 +563,11 @@ func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.
|
||||
sp.Y = -dist
|
||||
}
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Scroll,
|
||||
Source: pointer.Mouse,
|
||||
Position: p,
|
||||
Buttons: w.pointerBtns,
|
||||
Buttons: getPointerButtons(pi),
|
||||
Scroll: sp,
|
||||
Modifiers: kmods,
|
||||
Time: windows.GetMessageTime(),
|
||||
@@ -564,18 +575,19 @@ func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.
|
||||
}
|
||||
|
||||
// Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/
|
||||
func (w *window) loop() error {
|
||||
func (w *window) runLoop() {
|
||||
msg := new(windows.Msg)
|
||||
loop:
|
||||
for {
|
||||
anim := w.animating
|
||||
if anim && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) {
|
||||
p := windows.GetWindowPlacement(w.hwnd)
|
||||
if anim && !p.IsMinimized() && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) {
|
||||
w.draw(false)
|
||||
continue
|
||||
}
|
||||
switch ret := windows.GetMessage(msg, 0, 0, 0); ret {
|
||||
case -1:
|
||||
return errors.New("GetMessage failed")
|
||||
panic(errors.New("GetMessage failed"))
|
||||
case 0:
|
||||
// WM_QUIT received.
|
||||
break loop
|
||||
@@ -583,7 +595,6 @@ loop:
|
||||
windows.TranslateMessage(msg)
|
||||
windows.DispatchMessage(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) EditorStateChanged(old, new editorState) {
|
||||
@@ -601,16 +612,30 @@ func (w *window) SetAnimating(anim bool) {
|
||||
w.animating = anim
|
||||
}
|
||||
|
||||
func (w *window) Wakeup() {
|
||||
if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
func (w *window) ProcessEvent(e event.Event) {
|
||||
w.w.ProcessEvent(e)
|
||||
w.loop.FlushEvents()
|
||||
}
|
||||
|
||||
func (w *window) setStage(s system.Stage) {
|
||||
if s != w.stage {
|
||||
w.stage = s
|
||||
w.w.Event(system.StageEvent{Stage: s})
|
||||
func (w *window) Event() event.Event {
|
||||
return w.loop.Event()
|
||||
}
|
||||
|
||||
func (w *window) Invalidate() {
|
||||
w.loop.Invalidate()
|
||||
}
|
||||
|
||||
func (w *window) Run(f func()) {
|
||||
w.loop.Run(f)
|
||||
}
|
||||
|
||||
func (w *window) Frame(frame *op.Ops) {
|
||||
w.loop.Frame(frame)
|
||||
}
|
||||
|
||||
func (w *window) wakeup() {
|
||||
if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -620,8 +645,8 @@ func (w *window) draw(sync bool) {
|
||||
}
|
||||
dpi := windows.GetWindowDPI(w.hwnd)
|
||||
cfg := configForDPI(dpi)
|
||||
w.w.Event(frameEvent{
|
||||
FrameEvent: system.FrameEvent{
|
||||
w.ProcessEvent(frameEvent{
|
||||
FrameEvent: FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: w.config.Size,
|
||||
Metric: cfg,
|
||||
@@ -667,15 +692,25 @@ func (w *window) readClipboard() error {
|
||||
}
|
||||
defer windows.GlobalUnlock(mem)
|
||||
content := gowindows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr)))
|
||||
w.w.Event(clipboard.Event{Text: content})
|
||||
w.ProcessEvent(transfer.DataEvent{
|
||||
Type: "application/text",
|
||||
Open: func() io.ReadCloser {
|
||||
return io.NopCloser(strings.NewReader(content))
|
||||
},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) Configure(options []Option) {
|
||||
dpi := windows.GetSystemDPI()
|
||||
metric := configForDPI(dpi)
|
||||
w.config.apply(metric, options)
|
||||
windows.SetWindowText(w.hwnd, w.config.Title)
|
||||
cnf := w.config
|
||||
cnf.apply(metric, options)
|
||||
w.config.Title = cnf.Title
|
||||
w.config.Decorated = cnf.Decorated
|
||||
w.config.MinSize = cnf.MinSize
|
||||
w.config.MaxSize = cnf.MaxSize
|
||||
windows.SetWindowText(w.hwnd, cnf.Title)
|
||||
|
||||
style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE)
|
||||
var showMode int32
|
||||
@@ -683,7 +718,7 @@ func (w *window) Configure(options []Option) {
|
||||
swpStyle := uintptr(windows.SWP_NOZORDER | windows.SWP_FRAMECHANGED)
|
||||
winStyle := uintptr(windows.WS_OVERLAPPEDWINDOW)
|
||||
style &^= winStyle
|
||||
switch w.config.Mode {
|
||||
switch cnf.Mode {
|
||||
case Minimized:
|
||||
style |= winStyle
|
||||
swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
|
||||
@@ -698,13 +733,13 @@ func (w *window) Configure(options []Option) {
|
||||
style |= winStyle
|
||||
showMode = windows.SW_SHOWNORMAL
|
||||
// Get target for client area size.
|
||||
width = int32(w.config.Size.X)
|
||||
height = int32(w.config.Size.Y)
|
||||
width = int32(cnf.Size.X)
|
||||
height = int32(cnf.Size.Y)
|
||||
// Get the current window size and position.
|
||||
wr := windows.GetWindowRect(w.hwnd)
|
||||
x = wr.Left
|
||||
y = wr.Top
|
||||
if w.config.Decorated {
|
||||
if cnf.Decorated {
|
||||
// Compute client size and position. Note that the client size is
|
||||
// equal to the window size when we are in control of decorations.
|
||||
r := windows.Rect{
|
||||
@@ -714,28 +749,31 @@ func (w *window) Configure(options []Option) {
|
||||
windows.AdjustWindowRectEx(&r, uint32(style), 0, dwExStyle)
|
||||
width = r.Right - r.Left
|
||||
height = r.Bottom - r.Top
|
||||
}
|
||||
if !w.config.Decorated {
|
||||
} else {
|
||||
// Enable drop shadows when we draw decorations.
|
||||
windows.DwmExtendFrameIntoClientArea(w.hwnd, windows.Margins{-1, -1, -1, -1})
|
||||
}
|
||||
|
||||
case Fullscreen:
|
||||
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_SHOW
|
||||
swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
|
||||
showMode = windows.SW_SHOWMAXIMIZED
|
||||
}
|
||||
windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style)
|
||||
windows.SetWindowPos(w.hwnd, 0, x, y, width, height, swpStyle)
|
||||
windows.ShowWindow(w.hwnd, showMode)
|
||||
|
||||
w.update()
|
||||
// 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.ShowWindow(w.hwnd, showMode)
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(s string) {
|
||||
w.writeClipboard(s)
|
||||
func (w *window) WriteClipboard(mime string, s []byte) {
|
||||
w.writeClipboard(string(s))
|
||||
}
|
||||
|
||||
func (w *window) writeClipboard(s string) error {
|
||||
@@ -863,11 +901,11 @@ func (w *window) raise() {
|
||||
windows.SWP_NOMOVE|windows.SWP_NOSIZE|windows.SWP_SHOWWINDOW)
|
||||
}
|
||||
|
||||
func convertKeyCode(code uintptr) (string, bool) {
|
||||
func convertKeyCode(code uintptr) (key.Name, bool) {
|
||||
if '0' <= code && code <= '9' || 'A' <= code && code <= 'Z' {
|
||||
return string(rune(code)), true
|
||||
return key.Name(rune(code)), true
|
||||
}
|
||||
var r string
|
||||
var r key.Name
|
||||
|
||||
switch code {
|
||||
case windows.VK_ESCAPE:
|
||||
@@ -967,4 +1005,50 @@ func configForDPI(dpi int) unit.Metric {
|
||||
}
|
||||
}
|
||||
|
||||
func (_ ViewEvent) ImplementsEvent() {}
|
||||
func (Win32ViewEvent) implementsViewEvent() {}
|
||||
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
|
||||
}
|
||||
|
||||
+117
-86
@@ -26,20 +26,25 @@ package app
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/clipboard"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/io/transfer"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
|
||||
syscall "golang.org/x/sys/unix"
|
||||
@@ -91,12 +96,10 @@ type x11Window struct {
|
||||
// _NET_WM_STATE_MAXIMIZED_VERT
|
||||
wmStateMaximizedVert C.Atom
|
||||
}
|
||||
stage system.Stage
|
||||
metric unit.Metric
|
||||
notify struct {
|
||||
read, write int
|
||||
}
|
||||
dead bool
|
||||
|
||||
animating bool
|
||||
|
||||
@@ -109,6 +112,8 @@ type x11Window struct {
|
||||
config Config
|
||||
|
||||
wakeups chan struct{}
|
||||
handler x11EventHandler
|
||||
buf [100]byte
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -151,8 +156,8 @@ func (w *x11Window) ReadClipboard() {
|
||||
C.XConvertSelection(w.x, w.atoms.clipboard, w.atoms.utf8string, w.atoms.clipboardContent, w.xw, C.CurrentTime)
|
||||
}
|
||||
|
||||
func (w *x11Window) WriteClipboard(s string) {
|
||||
w.clipboard.content = []byte(s)
|
||||
func (w *x11Window) WriteClipboard(mime string, s []byte) {
|
||||
w.clipboard.content = s
|
||||
C.XSetSelectionOwner(w.x, w.atoms.clipboard, w.xw, C.CurrentTime)
|
||||
C.XSetSelectionOwner(w.x, w.atoms.primary, w.xw, C.CurrentTime)
|
||||
}
|
||||
@@ -232,7 +237,7 @@ func (w *x11Window) Configure(options []Option) {
|
||||
if cnf.Decorated != prev.Decorated {
|
||||
w.config.Decorated = cnf.Decorated
|
||||
}
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
func (w *x11Window) setTitle(prev, cnf Config) {
|
||||
@@ -375,7 +380,36 @@ func (w *x11Window) sendWMStateEvent(action C.long, atom1, atom2 C.ulong) {
|
||||
|
||||
var x11OneByte = make([]byte, 1)
|
||||
|
||||
func (w *x11Window) Wakeup() {
|
||||
func (w *x11Window) ProcessEvent(e event.Event) {
|
||||
w.w.ProcessEvent(e)
|
||||
}
|
||||
|
||||
func (w *x11Window) shutdown(err error) {
|
||||
w.ProcessEvent(X11ViewEvent{})
|
||||
w.ProcessEvent(DestroyEvent{Err: err})
|
||||
w.destroy()
|
||||
}
|
||||
|
||||
func (w *x11Window) Event() event.Event {
|
||||
for {
|
||||
evt, ok := w.w.nextEvent()
|
||||
if !ok {
|
||||
w.dispatch()
|
||||
continue
|
||||
}
|
||||
return evt
|
||||
}
|
||||
}
|
||||
|
||||
func (w *x11Window) Run(f func()) {
|
||||
f()
|
||||
}
|
||||
|
||||
func (w *x11Window) Frame(frame *op.Ops) {
|
||||
w.w.ProcessFrame(frame, nil)
|
||||
}
|
||||
|
||||
func (w *x11Window) Invalidate() {
|
||||
select {
|
||||
case w.wakeups <- struct{}{}:
|
||||
default:
|
||||
@@ -393,16 +427,20 @@ func (w *x11Window) window() (C.Window, int, int) {
|
||||
return w.xw, w.config.Size.X, w.config.Size.Y
|
||||
}
|
||||
|
||||
func (w *x11Window) setStage(s system.Stage) {
|
||||
if s == w.stage {
|
||||
func (w *x11Window) dispatch() {
|
||||
if w.x == nil {
|
||||
// Only Invalidate can wake us up.
|
||||
<-w.wakeups
|
||||
w.w.Invalidate()
|
||||
return
|
||||
}
|
||||
w.stage = s
|
||||
w.w.Event(system.StageEvent{Stage: s})
|
||||
}
|
||||
|
||||
func (w *x11Window) loop() {
|
||||
h := x11EventHandler{w: w, xev: new(C.XEvent), text: make([]byte, 4)}
|
||||
select {
|
||||
case <-w.wakeups:
|
||||
w.w.Invalidate()
|
||||
default:
|
||||
}
|
||||
|
||||
xfd := C.XConnectionNumber(w.x)
|
||||
|
||||
// Poll for events and notifications.
|
||||
@@ -412,61 +450,55 @@ func (w *x11Window) loop() {
|
||||
}
|
||||
xEvents := &pollfds[0].Revents
|
||||
// Plenty of room for a backlog of notifications.
|
||||
buf := make([]byte, 100)
|
||||
|
||||
loop:
|
||||
for !w.dead {
|
||||
var syn, anim bool
|
||||
// Check for pending draw events before checking animation or blocking.
|
||||
// This fixes an issue on Xephyr where on startup XPending() > 0 but
|
||||
// poll will still block. This also prevents no-op calls to poll.
|
||||
if syn = h.handleEvents(); !syn {
|
||||
anim = w.animating
|
||||
if !anim {
|
||||
// Clear poll events.
|
||||
*xEvents = 0
|
||||
// Wait for X event or gio notification.
|
||||
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
||||
panic(fmt.Errorf("x11 loop: poll failed: %w", err))
|
||||
}
|
||||
switch {
|
||||
case *xEvents&syscall.POLLIN != 0:
|
||||
syn = h.handleEvents()
|
||||
if w.dead {
|
||||
break loop
|
||||
}
|
||||
case *xEvents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
||||
break loop
|
||||
var syn, anim bool
|
||||
// Check for pending draw events before checking animation or blocking.
|
||||
// This fixes an issue on Xephyr where on startup XPending() > 0 but
|
||||
// poll will still block. This also prevents no-op calls to poll.
|
||||
syn = w.handler.handleEvents()
|
||||
if w.x == nil {
|
||||
// handleEvents received a close request and destroyed the window.
|
||||
return
|
||||
}
|
||||
if !syn {
|
||||
anim = w.animating
|
||||
if !anim {
|
||||
// Clear poll events.
|
||||
*xEvents = 0
|
||||
// Wait for X event or gio notification.
|
||||
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
||||
panic(fmt.Errorf("x11 loop: poll failed: %w", err))
|
||||
}
|
||||
switch {
|
||||
case *xEvents&syscall.POLLIN != 0:
|
||||
syn = w.handler.handleEvents()
|
||||
if w.x == nil {
|
||||
return
|
||||
}
|
||||
case *xEvents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
||||
}
|
||||
}
|
||||
// Clear notifications.
|
||||
for {
|
||||
_, err := syscall.Read(w.notify.read, buf)
|
||||
if err == syscall.EAGAIN {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("x11 loop: read from notify pipe failed: %w", err))
|
||||
}
|
||||
}
|
||||
// Clear notifications.
|
||||
for {
|
||||
_, err := syscall.Read(w.notify.read, w.buf[:])
|
||||
if err == syscall.EAGAIN {
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-w.wakeups:
|
||||
w.w.Event(wakeupEvent{})
|
||||
default:
|
||||
}
|
||||
|
||||
if (anim || syn) && w.config.Size.X != 0 && w.config.Size.Y != 0 {
|
||||
w.w.Event(frameEvent{
|
||||
FrameEvent: system.FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: w.config.Size,
|
||||
Metric: w.metric,
|
||||
},
|
||||
Sync: syn,
|
||||
})
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("x11 loop: read from notify pipe failed: %w", err))
|
||||
}
|
||||
}
|
||||
if (anim || syn) && w.config.Size.X != 0 && w.config.Size.Y != 0 {
|
||||
w.ProcessEvent(frameEvent{
|
||||
FrameEvent: FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: w.config.Size,
|
||||
Metric: w.metric,
|
||||
},
|
||||
Sync: syn,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (w *x11Window) destroy() {
|
||||
@@ -484,6 +516,7 @@ func (w *x11Window) destroy() {
|
||||
}
|
||||
C.XDestroyWindow(w.x, w.xw)
|
||||
C.XCloseDisplay(w.x)
|
||||
w.x = nil
|
||||
}
|
||||
|
||||
// atom is a wrapper around XInternAtom. Callers should cache the result
|
||||
@@ -541,7 +574,7 @@ func (h *x11EventHandler) handleEvents() bool {
|
||||
// There's no support for IME yet.
|
||||
w.w.EditorInsert(ee.Text)
|
||||
} else {
|
||||
w.w.Event(e)
|
||||
w.ProcessEvent(e)
|
||||
}
|
||||
}
|
||||
case C.ButtonPress, C.ButtonRelease:
|
||||
@@ -603,10 +636,10 @@ func (h *x11EventHandler) handleEvents() bool {
|
||||
w.pointerBtns &^= btn
|
||||
}
|
||||
ev.Buttons = w.pointerBtns
|
||||
w.w.Event(ev)
|
||||
w.ProcessEvent(ev)
|
||||
case C.MotionNotify:
|
||||
mevt := (*C.XMotionEvent)(unsafe.Pointer(xev))
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Move,
|
||||
Source: pointer.Mouse,
|
||||
Buttons: w.pointerBtns,
|
||||
@@ -621,14 +654,16 @@ func (h *x11EventHandler) handleEvents() bool {
|
||||
// redraw only on the last expose event
|
||||
redraw = (*C.XExposeEvent)(unsafe.Pointer(xev)).count == 0
|
||||
case C.FocusIn:
|
||||
w.w.Event(key.FocusEvent{Focus: true})
|
||||
w.config.Focused = true
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
case C.FocusOut:
|
||||
w.w.Event(key.FocusEvent{Focus: false})
|
||||
w.config.Focused = false
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
case C.ConfigureNotify: // window configuration change
|
||||
cevt := (*C.XConfigureEvent)(unsafe.Pointer(xev))
|
||||
if sz := image.Pt(int(cevt.width), int(cevt.height)); sz != w.config.Size {
|
||||
w.config.Size = sz
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
// redraw will be done by a later expose event
|
||||
case C.SelectionNotify:
|
||||
@@ -650,7 +685,12 @@ func (h *x11EventHandler) handleEvents() bool {
|
||||
break
|
||||
}
|
||||
str := C.GoStringN((*C.char)(unsafe.Pointer(text.value)), C.int(text.nitems))
|
||||
w.w.Event(clipboard.Event{Text: str})
|
||||
w.ProcessEvent(transfer.DataEvent{
|
||||
Type: "application/text",
|
||||
Open: func() io.ReadCloser {
|
||||
return io.NopCloser(strings.NewReader(str))
|
||||
},
|
||||
})
|
||||
case C.SelectionRequest:
|
||||
cevt := (*C.XSelectionRequestEvent)(unsafe.Pointer(xev))
|
||||
if (cevt.selection != w.atoms.clipboard && cevt.selection != w.atoms.primary) || cevt.property == C.None {
|
||||
@@ -704,7 +744,7 @@ func (h *x11EventHandler) handleEvents() bool {
|
||||
cevt := (*C.XClientMessageEvent)(unsafe.Pointer(xev))
|
||||
switch *(*C.long)(unsafe.Pointer(&cevt.data)) {
|
||||
case C.long(w.atoms.evDelWindow):
|
||||
w.dead = true
|
||||
w.shutdown(nil)
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -712,9 +752,7 @@ func (h *x11EventHandler) handleEvents() bool {
|
||||
return redraw
|
||||
}
|
||||
|
||||
var (
|
||||
x11Threads sync.Once
|
||||
)
|
||||
var x11Threads sync.Once
|
||||
|
||||
func init() {
|
||||
x11Driver = newX11Window
|
||||
@@ -786,8 +824,10 @@ func newX11Window(gioWin *callbacks, options []Option) error {
|
||||
wakeups: make(chan struct{}, 1),
|
||||
config: Config{Size: cnf.Size},
|
||||
}
|
||||
w.handler = x11EventHandler{w: w, xev: new(C.XEvent), text: make([]byte, 4)}
|
||||
w.notify.read = pipe[0]
|
||||
w.notify.write = pipe[1]
|
||||
w.w.SetDriver(w)
|
||||
|
||||
if err := w.updateXkbKeymap(); err != nil {
|
||||
w.destroy()
|
||||
@@ -823,19 +863,10 @@ func newX11Window(gioWin *callbacks, options []Option) error {
|
||||
// extensions
|
||||
C.XSetWMProtocols(dpy, win, &w.atoms.evDelWindow, 1)
|
||||
|
||||
go func() {
|
||||
w.w.SetDriver(w)
|
||||
|
||||
// make the window visible on the screen
|
||||
C.XMapWindow(dpy, win)
|
||||
w.Configure(options)
|
||||
w.w.Event(X11ViewEvent{Display: unsafe.Pointer(dpy), Window: uintptr(win)})
|
||||
w.setStage(system.StageRunning)
|
||||
w.loop()
|
||||
w.w.Event(X11ViewEvent{})
|
||||
w.w.Event(system.DestroyEvent{Err: nil})
|
||||
w.destroy()
|
||||
}()
|
||||
// make the window visible on the screen
|
||||
C.XMapWindow(dpy, win)
|
||||
w.Configure(options)
|
||||
w.ProcessEvent(X11ViewEvent{Display: unsafe.Pointer(dpy), Window: uintptr(win)})
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
minExt, maxExt := caps.MinExtent(), caps.MaxExtent()
|
||||
minExt, maxExt := vk.SurfaceCapabilitiesMinExtent(caps), vk.SurfaceCapabilitiesMaxExtent(caps)
|
||||
if width < minExt.X || maxExt.X < width || height < minExt.Y || maxExt.Y < height {
|
||||
return errOutOfDate
|
||||
}
|
||||
|
||||
+404
-618
File diff suppressed because it is too large
Load Diff
+10
-1
@@ -30,6 +30,15 @@ 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.
|
||||
func (a Affine2D) Offset(offset Point) Affine2D {
|
||||
return Affine2D{
|
||||
@@ -114,7 +123,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
|
||||
// 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{
|
||||
a: a.a, b: a.b, c: 0,
|
||||
d: a.d, e: a.e, f: 0,
|
||||
|
||||
+160
-30
@@ -27,11 +27,11 @@ func TestTransformOffset(t *testing.T) {
|
||||
p := Point{X: 1, Y: 2}
|
||||
o := Point{X: 2, Y: -3}
|
||||
|
||||
r := Affine2D{}.Offset(o).Transform(p)
|
||||
r := AffineId().Offset(o).Transform(p)
|
||||
if !eq(r, Pt(3, -1)) {
|
||||
t.Errorf("offset transformation mismatch: have %v, want {3 -1}", r)
|
||||
}
|
||||
i := Affine2D{}.Offset(o).Invert().Transform(r)
|
||||
i := AffineId().Offset(o).Invert().Transform(r)
|
||||
if !eq(i, p) {
|
||||
t.Errorf("offset transformation inverse mismatch: have %v, want %v", i, p)
|
||||
}
|
||||
@@ -51,6 +51,9 @@ func TestString(t *testing.T) {
|
||||
}, {
|
||||
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]]",
|
||||
}, {
|
||||
in: AffineId(),
|
||||
exp: "[[1 0 0] [0 1 0]]",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
@@ -64,11 +67,11 @@ func TestTransformScale(t *testing.T) {
|
||||
p := Point{X: 1, Y: 2}
|
||||
s := Point{X: -1, Y: 2}
|
||||
|
||||
r := Affine2D{}.Scale(Point{}, s).Transform(p)
|
||||
r := AffineId().Scale(Point{}, s).Transform(p)
|
||||
if !eq(r, Pt(-1, 4)) {
|
||||
t.Errorf("scale transformation mismatch: have %v, want {-1 4}", r)
|
||||
}
|
||||
i := Affine2D{}.Scale(Point{}, s).Invert().Transform(r)
|
||||
i := AffineId().Scale(Point{}, s).Invert().Transform(r)
|
||||
if !eq(i, p) {
|
||||
t.Errorf("scale transformation inverse mismatch: have %v, want %v", i, p)
|
||||
}
|
||||
@@ -78,11 +81,11 @@ func TestTransformRotate(t *testing.T) {
|
||||
p := Point{X: 1, Y: 0}
|
||||
a := float32(math.Pi / 2)
|
||||
|
||||
r := Affine2D{}.Rotate(Point{}, a).Transform(p)
|
||||
r := AffineId().Rotate(Point{}, a).Transform(p)
|
||||
if !eq(r, Pt(0, 1)) {
|
||||
t.Errorf("rotate transformation mismatch: have %v, want {0 1}", r)
|
||||
}
|
||||
i := Affine2D{}.Rotate(Point{}, a).Invert().Transform(r)
|
||||
i := AffineId().Rotate(Point{}, a).Invert().Transform(r)
|
||||
if !eq(i, p) {
|
||||
t.Errorf("rotate transformation inverse mismatch: have %v, want %v", i, p)
|
||||
}
|
||||
@@ -91,11 +94,11 @@ func TestTransformRotate(t *testing.T) {
|
||||
func TestTransformShear(t *testing.T) {
|
||||
p := Point{X: 1, Y: 1}
|
||||
|
||||
r := Affine2D{}.Shear(Point{}, math.Pi/4, 0).Transform(p)
|
||||
r := AffineId().Shear(Point{}, math.Pi/4, 0).Transform(p)
|
||||
if !eq(r, Pt(2, 1)) {
|
||||
t.Errorf("shear transformation mismatch: have %v, want {2 1}", r)
|
||||
}
|
||||
i := Affine2D{}.Shear(Point{}, math.Pi/4, 0).Invert().Transform(r)
|
||||
i := AffineId().Shear(Point{}, math.Pi/4, 0).Invert().Transform(r)
|
||||
if !eq(i, p) {
|
||||
t.Errorf("shear transformation inverse mismatch: have %v, want %v", i, p)
|
||||
}
|
||||
@@ -107,11 +110,11 @@ func TestTransformMultiply(t *testing.T) {
|
||||
s := Point{X: -1, Y: 2}
|
||||
a := float32(-math.Pi / 2)
|
||||
|
||||
r := Affine2D{}.Offset(o).Scale(Point{}, s).Rotate(Point{}, a).Shear(Point{}, math.Pi/4, 0).Transform(p)
|
||||
r := AffineId().Offset(o).Scale(Point{}, s).Rotate(Point{}, a).Shear(Point{}, math.Pi/4, 0).Transform(p)
|
||||
if !eq(r, Pt(1, 3)) {
|
||||
t.Errorf("complex transformation mismatch: have %v, want {1 3}", r)
|
||||
}
|
||||
i := Affine2D{}.Offset(o).Scale(Point{}, s).Rotate(Point{}, a).Shear(Point{}, math.Pi/4, 0).Invert().Transform(r)
|
||||
i := AffineId().Offset(o).Scale(Point{}, s).Rotate(Point{}, a).Shear(Point{}, math.Pi/4, 0).Invert().Transform(r)
|
||||
if !eq(i, p) {
|
||||
t.Errorf("complex transformation inverse mismatch: have %v, want %v", i, p)
|
||||
}
|
||||
@@ -163,7 +166,7 @@ func TestPrimes(t *testing.T) {
|
||||
func TestTransformScaleAround(t *testing.T) {
|
||||
p := Pt(-1, -1)
|
||||
target := Pt(-6, -13)
|
||||
pt := Affine2D{}.Scale(Pt(4, 5), Pt(2, 3)).Transform(p)
|
||||
pt := AffineId().Scale(Pt(4, 5), Pt(2, 3)).Transform(p)
|
||||
if !eq(pt, target) {
|
||||
t.Log(pt, "!=", target)
|
||||
t.Error("Scale not as expected")
|
||||
@@ -172,7 +175,7 @@ func TestTransformScaleAround(t *testing.T) {
|
||||
|
||||
func TestTransformRotateAround(t *testing.T) {
|
||||
p := Pt(-1, -1)
|
||||
pt := Affine2D{}.Rotate(Pt(1, 1), -math.Pi/2).Transform(p)
|
||||
pt := AffineId().Rotate(Pt(1, 1), -math.Pi/2).Transform(p)
|
||||
target := Pt(-1, 3)
|
||||
if !eq(pt, target) {
|
||||
t.Log(pt, "!=", target)
|
||||
@@ -181,12 +184,12 @@ func TestTransformRotateAround(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMulOrder(t *testing.T) {
|
||||
A := Affine2D{}.Offset(Pt(100, 100))
|
||||
B := Affine2D{}.Scale(Point{}, Pt(2, 2))
|
||||
A := AffineId().Offset(Pt(100, 100))
|
||||
B := AffineId().Scale(Point{}, Pt(2, 2))
|
||||
_ = A
|
||||
_ = B
|
||||
|
||||
T1 := Affine2D{}.Offset(Pt(100, 100)).Scale(Point{}, Pt(2, 2))
|
||||
T1 := AffineId().Offset(Pt(100, 100)).Scale(Point{}, Pt(2, 2))
|
||||
T2 := B.Mul(A)
|
||||
|
||||
if T1 != T2 {
|
||||
@@ -199,9 +202,9 @@ func TestMulOrder(t *testing.T) {
|
||||
func BenchmarkTransformOffset(b *testing.B) {
|
||||
p := Point{X: 1, Y: 2}
|
||||
o := Point{X: 0.5, Y: 0.5}
|
||||
aff := Affine2D{}.Offset(o)
|
||||
aff := AffineId().Offset(o)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
p = aff.Transform(p)
|
||||
}
|
||||
_ = p
|
||||
@@ -210,8 +213,8 @@ func BenchmarkTransformOffset(b *testing.B) {
|
||||
func BenchmarkTransformScale(b *testing.B) {
|
||||
p := Point{X: 1, Y: 2}
|
||||
s := Point{X: 0.5, Y: 0.5}
|
||||
aff := Affine2D{}.Scale(Point{}, s)
|
||||
for i := 0; i < b.N; i++ {
|
||||
aff := AffineId().Scale(Point{}, s)
|
||||
for b.Loop() {
|
||||
p = aff.Transform(p)
|
||||
}
|
||||
_ = p
|
||||
@@ -220,36 +223,163 @@ func BenchmarkTransformScale(b *testing.B) {
|
||||
func BenchmarkTransformRotate(b *testing.B) {
|
||||
p := Point{X: 1, Y: 2}
|
||||
a := float32(math.Pi / 2)
|
||||
aff := Affine2D{}.Rotate(Point{}, a)
|
||||
for i := 0; i < b.N; i++ {
|
||||
aff := AffineId().Rotate(Point{}, a)
|
||||
for b.Loop() {
|
||||
p = aff.Transform(p)
|
||||
}
|
||||
_ = p
|
||||
}
|
||||
|
||||
func BenchmarkTransformTranslateMultiply(b *testing.B) {
|
||||
a := Affine2D{}.Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
|
||||
t := Affine2D{}.Offset(Point{X: 0.5, Y: 0.5})
|
||||
a := AffineId().Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
|
||||
t := AffineId().Offset(Point{X: 0.5, Y: 0.5})
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
a = a.Mul(t)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTransformScaleMultiply(b *testing.B) {
|
||||
a := Affine2D{}.Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
|
||||
t := Affine2D{}.Offset(Point{X: 0.5, Y: 0.5}).Scale(Point{}, Point{X: 0.4, Y: -0.5})
|
||||
a := AffineId().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})
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
a = a.Mul(t)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTransformMultiply(b *testing.B) {
|
||||
a := Affine2D{}.Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
|
||||
t := Affine2D{}.Offset(Point{X: 0.5, Y: 0.5}).Rotate(Point{}, math.Pi/7)
|
||||
a := AffineId().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)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
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
+39
-67
@@ -1,86 +1,58 @@
|
||||
{
|
||||
"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": {
|
||||
"locked": {
|
||||
"lastModified": 1659305579,
|
||||
"narHash": "sha256-SFeQTmh7hc9Y2fSkooHaoS8mDfPa04sfmUCtQ8MA6Pg=",
|
||||
"lastModified": 1747953325,
|
||||
"narHash": "sha256-y2ZtlIlNTuVJUZCqzZAhIw5rrKP4DOSklev6c8PyCkQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "5857574d45925585baffde730369414319228a84",
|
||||
"rev": "55d1f923c480dadce40f5231feb472e81b0bab48",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-25.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"android": "android",
|
||||
"nixpkgs": "nixpkgs"
|
||||
"nixpkgs": "nixpkgs",
|
||||
"utils": "utils"
|
||||
}
|
||||
},
|
||||
"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,42 +3,38 @@
|
||||
description = "Gio build environment";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs";
|
||||
android.url = "github:tadfisher/android-nixpkgs";
|
||||
android.inputs.nixpkgs.follows = "nixpkgs";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
|
||||
utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, android }:
|
||||
let
|
||||
supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-darwin" ];
|
||||
forAllSystems = f: nixpkgs.lib.genAttrs supportedSystems (system: f system);
|
||||
in
|
||||
{
|
||||
devShells = forAllSystems
|
||||
(system:
|
||||
let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
outputs = { self, nixpkgs, utils }:
|
||||
utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
|
||||
# allow unfree Android packages.
|
||||
config.allowUnfree = true;
|
||||
# accept the Android SDK license.
|
||||
config.android_sdk.accept_license = true;
|
||||
};
|
||||
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;
|
||||
};
|
||||
android-sdk = android.sdk.${system} (sdkPkgs: with sdkPkgs;
|
||||
[
|
||||
build-tools-31-0-0
|
||||
cmdline-tools-latest
|
||||
platform-tools
|
||||
platforms-android-31
|
||||
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 [
|
||||
in androidComposition.androidsdk;
|
||||
in {
|
||||
default = with pkgs;
|
||||
mkShell (rec {
|
||||
ANDROID_HOME = "${android-sdk}/libexec/android-sdk";
|
||||
packages = [ android-sdk jdk clang ]
|
||||
++ (if stdenv.isLinux then [
|
||||
vulkan-headers
|
||||
libxkbcommon
|
||||
wayland
|
||||
@@ -46,18 +42,13 @@
|
||||
xorg.libXcursor
|
||||
xorg.libXfixes
|
||||
libGL
|
||||
pkgconfig
|
||||
] else if stdenv.isDarwin then [
|
||||
darwin.apple_sdk_11_0.frameworks.Foundation
|
||||
darwin.apple_sdk_11_0.frameworks.Metal
|
||||
darwin.apple_sdk_11_0.frameworks.QuartzCore
|
||||
darwin.apple_sdk_11_0.frameworks.AppKit
|
||||
darwin.apple_sdk_11_0.MacOSX-SDK
|
||||
] else [ ]);
|
||||
} // (if stdenv.isLinux then {
|
||||
LD_LIBRARY_PATH = "${vulkan-loader}/lib";
|
||||
} else { }));
|
||||
}
|
||||
);
|
||||
};
|
||||
pkg-config
|
||||
] 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
|
||||
// upon the kind of font and shaper in use.
|
||||
type Face interface {
|
||||
Face() font.Face
|
||||
Face() *font.Face
|
||||
}
|
||||
|
||||
// Typeface identifies a list of font families to attempt to use for displaying
|
||||
|
||||
+41
-43
@@ -16,23 +16,21 @@ import (
|
||||
_ "image/png"
|
||||
|
||||
giofont "gioui.org/font"
|
||||
"github.com/go-text/typesetting/font"
|
||||
fontapi "github.com/go-text/typesetting/opentype/api/font"
|
||||
"github.com/go-text/typesetting/opentype/api/metadata"
|
||||
"github.com/go-text/typesetting/opentype/loader"
|
||||
fontapi "github.com/go-text/typesetting/font"
|
||||
"github.com/go-text/typesetting/font/opentype"
|
||||
)
|
||||
|
||||
// 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
|
||||
// text shapers.
|
||||
type Face struct {
|
||||
face font.Font
|
||||
face *fontapi.Font
|
||||
font giofont.Font
|
||||
}
|
||||
|
||||
// Parse constructs a Face from source bytes.
|
||||
func Parse(src []byte) (Face, error) {
|
||||
ld, err := loader.NewLoader(bytes.NewReader(src))
|
||||
ld, err := opentype.NewLoader(bytes.NewReader(src))
|
||||
if err != nil {
|
||||
return Face{}, err
|
||||
}
|
||||
@@ -49,11 +47,11 @@ func Parse(src []byte) (Face, error) {
|
||||
// ParseCollection parse an Opentype font file, with support for collections.
|
||||
// Single font files are supported, returning a slice with length 1.
|
||||
// The returned fonts are automatically wrapped in a text.FontFace with
|
||||
// inferred font metadata.
|
||||
// inferred font font.
|
||||
// BUG(whereswaldon): the only Variant that can be detected automatically is
|
||||
// "Mono".
|
||||
func ParseCollection(src []byte) ([]giofont.FontFace, error) {
|
||||
lds, err := loader.NewLoaders(bytes.NewReader(src))
|
||||
lds, err := opentype.NewLoaders(bytes.NewReader(src))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -76,7 +74,7 @@ func ParseCollection(src []byte) ([]giofont.FontFace, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func DescriptionToFont(md metadata.Description) giofont.Font {
|
||||
func DescriptionToFont(md fontapi.Description) giofont.Font {
|
||||
return giofont.Font{
|
||||
Typeface: giofont.Typeface(md.Family),
|
||||
Style: gioStyle(md.Aspect.Style),
|
||||
@@ -84,30 +82,30 @@ func DescriptionToFont(md metadata.Description) giofont.Font {
|
||||
}
|
||||
}
|
||||
|
||||
func FontToDescription(font giofont.Font) metadata.Description {
|
||||
return metadata.Description{
|
||||
func FontToDescription(font giofont.Font) fontapi.Description {
|
||||
return fontapi.Description{
|
||||
Family: string(font.Typeface),
|
||||
Aspect: metadata.Aspect{
|
||||
Aspect: fontapi.Aspect{
|
||||
Style: mdStyle(font.Style),
|
||||
Weight: mdWeight(font.Weight),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// parseLoader parses the contents of the loader into a face and its metadata.
|
||||
func parseLoader(ld *loader.Loader) (font.Font, giofont.Font, error) {
|
||||
// parseLoader parses the contents of the loader into a face and its font.
|
||||
func parseLoader(ld *opentype.Loader) (*fontapi.Font, giofont.Font, error) {
|
||||
ft, err := fontapi.NewFont(ld)
|
||||
if err != nil {
|
||||
return nil, giofont.Font{}, err
|
||||
}
|
||||
data := DescriptionToFont(metadata.Metadata(ld))
|
||||
data := DescriptionToFont(ft.Describe())
|
||||
return ft, data, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
// only used by one goroutine.
|
||||
func (f Face) Face() font.Face {
|
||||
func (f Face) Face() *fontapi.Face {
|
||||
return &fontapi.Face{Font: f.face}
|
||||
}
|
||||
|
||||
@@ -119,74 +117,74 @@ func (f Face) Font() giofont.Font {
|
||||
return f.font
|
||||
}
|
||||
|
||||
func gioStyle(s metadata.Style) giofont.Style {
|
||||
func gioStyle(s fontapi.Style) giofont.Style {
|
||||
switch s {
|
||||
case metadata.StyleItalic:
|
||||
case fontapi.StyleItalic:
|
||||
return giofont.Italic
|
||||
case metadata.StyleNormal:
|
||||
case fontapi.StyleNormal:
|
||||
fallthrough
|
||||
default:
|
||||
return giofont.Regular
|
||||
}
|
||||
}
|
||||
|
||||
func mdStyle(g giofont.Style) metadata.Style {
|
||||
func mdStyle(g giofont.Style) fontapi.Style {
|
||||
switch g {
|
||||
case giofont.Italic:
|
||||
return metadata.StyleItalic
|
||||
return fontapi.StyleItalic
|
||||
case giofont.Regular:
|
||||
fallthrough
|
||||
default:
|
||||
return metadata.StyleNormal
|
||||
return fontapi.StyleNormal
|
||||
}
|
||||
}
|
||||
|
||||
func gioWeight(w metadata.Weight) giofont.Weight {
|
||||
func gioWeight(w fontapi.Weight) giofont.Weight {
|
||||
switch w {
|
||||
case metadata.WeightThin:
|
||||
case fontapi.WeightThin:
|
||||
return giofont.Thin
|
||||
case metadata.WeightExtraLight:
|
||||
case fontapi.WeightExtraLight:
|
||||
return giofont.ExtraLight
|
||||
case metadata.WeightLight:
|
||||
case fontapi.WeightLight:
|
||||
return giofont.Light
|
||||
case metadata.WeightNormal:
|
||||
case fontapi.WeightNormal:
|
||||
return giofont.Normal
|
||||
case metadata.WeightMedium:
|
||||
case fontapi.WeightMedium:
|
||||
return giofont.Medium
|
||||
case metadata.WeightSemibold:
|
||||
case fontapi.WeightSemibold:
|
||||
return giofont.SemiBold
|
||||
case metadata.WeightBold:
|
||||
case fontapi.WeightBold:
|
||||
return giofont.Bold
|
||||
case metadata.WeightExtraBold:
|
||||
case fontapi.WeightExtraBold:
|
||||
return giofont.ExtraBold
|
||||
case metadata.WeightBlack:
|
||||
case fontapi.WeightBlack:
|
||||
return giofont.Black
|
||||
default:
|
||||
return giofont.Normal
|
||||
}
|
||||
}
|
||||
|
||||
func mdWeight(g giofont.Weight) metadata.Weight {
|
||||
func mdWeight(g giofont.Weight) fontapi.Weight {
|
||||
switch g {
|
||||
case giofont.Thin:
|
||||
return metadata.WeightThin
|
||||
return fontapi.WeightThin
|
||||
case giofont.ExtraLight:
|
||||
return metadata.WeightExtraLight
|
||||
return fontapi.WeightExtraLight
|
||||
case giofont.Light:
|
||||
return metadata.WeightLight
|
||||
return fontapi.WeightLight
|
||||
case giofont.Normal:
|
||||
return metadata.WeightNormal
|
||||
return fontapi.WeightNormal
|
||||
case giofont.Medium:
|
||||
return metadata.WeightMedium
|
||||
return fontapi.WeightMedium
|
||||
case giofont.SemiBold:
|
||||
return metadata.WeightSemibold
|
||||
return fontapi.WeightSemibold
|
||||
case giofont.Bold:
|
||||
return metadata.WeightBold
|
||||
return fontapi.WeightBold
|
||||
case giofont.ExtraBold:
|
||||
return metadata.WeightExtraBold
|
||||
return fontapi.WeightExtraBold
|
||||
case giofont.Black:
|
||||
return metadata.WeightBlack
|
||||
return fontapi.WeightBlack
|
||||
default:
|
||||
return metadata.WeightNormal
|
||||
return fontapi.WeightNormal
|
||||
}
|
||||
}
|
||||
|
||||
+84
-66
@@ -18,6 +18,7 @@ import (
|
||||
"gioui.org/f32"
|
||||
"gioui.org/internal/fling"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/input"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/op"
|
||||
@@ -37,15 +38,19 @@ type Hover struct {
|
||||
|
||||
// Add the gesture to detect hovering over the current pointer area.
|
||||
func (h *Hover) Add(ops *op.Ops) {
|
||||
pointer.InputOp{
|
||||
Tag: h,
|
||||
Kinds: pointer.Enter | pointer.Leave,
|
||||
}.Add(ops)
|
||||
event.Op(ops, h)
|
||||
}
|
||||
|
||||
// Update state and report whether a pointer is inside the area.
|
||||
func (h *Hover) Update(q event.Queue) bool {
|
||||
for _, ev := range q.Events(h) {
|
||||
func (h *Hover) Update(q input.Source) bool {
|
||||
for {
|
||||
ev, ok := q.Event(pointer.Filter{
|
||||
Target: h,
|
||||
Kinds: pointer.Enter | pointer.Leave | pointer.Cancel,
|
||||
})
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
e, ok := ev.(pointer.Event)
|
||||
if !ok {
|
||||
continue
|
||||
@@ -107,7 +112,6 @@ type Drag struct {
|
||||
pressed bool
|
||||
pid pointer.ID
|
||||
start f32.Point
|
||||
grab bool
|
||||
}
|
||||
|
||||
// Scroll detects scroll gestures and reduces them to
|
||||
@@ -115,11 +119,9 @@ type Drag struct {
|
||||
// movements as well as drag and fling touch gestures.
|
||||
type Scroll struct {
|
||||
dragging bool
|
||||
axis Axis
|
||||
estimator fling.Extrapolation
|
||||
flinger fling.Animation
|
||||
pid pointer.ID
|
||||
grab bool
|
||||
last int
|
||||
// Leftover scroll.
|
||||
scroll float32
|
||||
@@ -161,10 +163,7 @@ const touchSlop = unit.Dp(3)
|
||||
|
||||
// Add the handler to the operation list to receive click events.
|
||||
func (c *Click) Add(ops *op.Ops) {
|
||||
pointer.InputOp{
|
||||
Tag: c,
|
||||
Kinds: pointer.Press | pointer.Release | pointer.Enter | pointer.Leave,
|
||||
}.Add(ops)
|
||||
event.Op(ops, c)
|
||||
}
|
||||
|
||||
// Hovered returns whether a pointer is inside the area.
|
||||
@@ -177,10 +176,16 @@ func (c *Click) Pressed() bool {
|
||||
return c.pressed
|
||||
}
|
||||
|
||||
// Update state and return the click events.
|
||||
func (c *Click) Update(q event.Queue) []ClickEvent {
|
||||
var events []ClickEvent
|
||||
for _, evt := range q.Events(c) {
|
||||
// Update state and return the next click events, if any.
|
||||
func (c *Click) Update(q input.Source) (ClickEvent, bool) {
|
||||
for {
|
||||
evt, ok := q.Event(pointer.Filter{
|
||||
Target: c,
|
||||
Kinds: pointer.Press | pointer.Release | pointer.Enter | pointer.Leave | pointer.Cancel,
|
||||
})
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
e, ok := evt.(pointer.Event)
|
||||
if !ok {
|
||||
continue
|
||||
@@ -192,9 +197,15 @@ func (c *Click) Update(q event.Queue) []ClickEvent {
|
||||
}
|
||||
c.pressed = false
|
||||
if !c.entered || c.hovered {
|
||||
events = append(events, ClickEvent{Kind: KindClick, Position: e.Position.Round(), Source: e.Source, Modifiers: e.Modifiers, NumClicks: c.clicks})
|
||||
return ClickEvent{
|
||||
Kind: KindClick,
|
||||
Position: e.Position.Round(),
|
||||
Source: e.Source,
|
||||
Modifiers: e.Modifiers,
|
||||
NumClicks: c.clicks,
|
||||
}, true
|
||||
} else {
|
||||
events = append(events, ClickEvent{Kind: KindCancel})
|
||||
return ClickEvent{Kind: KindCancel}, true
|
||||
}
|
||||
case pointer.Cancel:
|
||||
wasPressed := c.pressed
|
||||
@@ -202,7 +213,7 @@ func (c *Click) Update(q event.Queue) []ClickEvent {
|
||||
c.hovered = false
|
||||
c.entered = false
|
||||
if wasPressed {
|
||||
events = append(events, ClickEvent{Kind: KindCancel})
|
||||
return ClickEvent{Kind: KindCancel}, true
|
||||
}
|
||||
case pointer.Press:
|
||||
if c.pressed {
|
||||
@@ -224,7 +235,7 @@ func (c *Click) Update(q event.Queue) []ClickEvent {
|
||||
c.clicks = 1
|
||||
}
|
||||
c.clickedAt = e.Time
|
||||
events = append(events, ClickEvent{Kind: KindPress, Position: e.Position.Round(), Source: e.Source, Modifiers: e.Modifiers, NumClicks: c.clicks})
|
||||
return ClickEvent{Kind: KindPress, Position: e.Position.Round(), Source: e.Source, Modifiers: e.Modifiers, NumClicks: c.clicks}, true
|
||||
case pointer.Leave:
|
||||
if !c.pressed {
|
||||
c.pid = e.PointerID
|
||||
@@ -242,25 +253,16 @@ func (c *Click) Update(q event.Queue) []ClickEvent {
|
||||
}
|
||||
}
|
||||
}
|
||||
return events
|
||||
return ClickEvent{}, false
|
||||
}
|
||||
|
||||
func (ClickEvent) ImplementsEvent() {}
|
||||
|
||||
// Add the handler to the operation list to receive scroll events.
|
||||
// The bounds variable refers to the scrolling boundaries
|
||||
// as defined in io/pointer.InputOp.
|
||||
func (s *Scroll) Add(ops *op.Ops, bounds image.Rectangle) {
|
||||
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)
|
||||
}
|
||||
// as defined in [pointer.Filter].
|
||||
func (s *Scroll) Add(ops *op.Ops) {
|
||||
event.Op(ops, s)
|
||||
}
|
||||
|
||||
// Stop any remaining fling movement.
|
||||
@@ -269,13 +271,19 @@ func (s *Scroll) Stop() {
|
||||
}
|
||||
|
||||
// Update state and report the scroll distance along axis.
|
||||
func (s *Scroll) Update(cfg unit.Metric, q event.Queue, t time.Time, axis Axis) int {
|
||||
if s.axis != axis {
|
||||
s.axis = axis
|
||||
return 0
|
||||
}
|
||||
func (s *Scroll) Update(cfg unit.Metric, q input.Source, t time.Time, axis Axis, scrollx, scrolly pointer.ScrollRange) int {
|
||||
total := 0
|
||||
for _, evt := range q.Events(s) {
|
||||
f := pointer.Filter{
|
||||
Target: s,
|
||||
Kinds: pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll | pointer.Cancel,
|
||||
ScrollX: scrollx,
|
||||
ScrollY: scrolly,
|
||||
}
|
||||
for {
|
||||
evt, ok := q.Event(f)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
e, ok := evt.(pointer.Event)
|
||||
if !ok {
|
||||
continue
|
||||
@@ -292,7 +300,7 @@ func (s *Scroll) Update(cfg unit.Metric, q event.Queue, t time.Time, axis Axis)
|
||||
}
|
||||
s.Stop()
|
||||
s.estimator = fling.Extrapolation{}
|
||||
v := s.val(e.Position)
|
||||
v := s.val(axis, e.Position)
|
||||
s.last = int(math.Round(float64(v)))
|
||||
s.estimator.Sample(e.Time, v)
|
||||
s.dragging = true
|
||||
@@ -308,13 +316,14 @@ func (s *Scroll) Update(cfg unit.Metric, q event.Queue, t time.Time, axis Axis)
|
||||
fallthrough
|
||||
case pointer.Cancel:
|
||||
s.dragging = false
|
||||
s.grab = false
|
||||
case pointer.Scroll:
|
||||
switch s.axis {
|
||||
switch axis {
|
||||
case Horizontal:
|
||||
s.scroll += e.Scroll.X
|
||||
case Vertical:
|
||||
s.scroll += e.Scroll.Y
|
||||
case Both:
|
||||
s.scroll += e.Scroll.X + e.Scroll.Y
|
||||
}
|
||||
iscroll := int(s.scroll)
|
||||
s.scroll -= float32(iscroll)
|
||||
@@ -323,14 +332,14 @@ func (s *Scroll) Update(cfg unit.Metric, q event.Queue, t time.Time, axis Axis)
|
||||
if !s.dragging || s.pid != e.PointerID {
|
||||
continue
|
||||
}
|
||||
val := s.val(e.Position)
|
||||
val := s.val(axis, e.Position)
|
||||
s.estimator.Sample(e.Time, val)
|
||||
v := int(math.Round(float64(val)))
|
||||
dist := s.last - v
|
||||
if e.Priority < pointer.Grabbed {
|
||||
slop := cfg.Dp(touchSlop)
|
||||
if dist := dist; dist >= slop || -slop >= dist {
|
||||
s.grab = true
|
||||
q.Execute(pointer.GrabCmd{Tag: s, ID: e.PointerID})
|
||||
}
|
||||
} else {
|
||||
s.last = v
|
||||
@@ -339,14 +348,22 @@ func (s *Scroll) Update(cfg unit.Metric, q event.Queue, t time.Time, axis Axis)
|
||||
}
|
||||
}
|
||||
total += s.flinger.Tick(t)
|
||||
if s.flinger.Active() {
|
||||
q.Execute(op.InvalidateCmd{})
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
func (s *Scroll) val(p f32.Point) float32 {
|
||||
if s.axis == Horizontal {
|
||||
func (s *Scroll) val(axis Axis, p f32.Point) float32 {
|
||||
switch axis {
|
||||
case Horizontal:
|
||||
return p.X
|
||||
} else {
|
||||
case Vertical:
|
||||
return p.Y
|
||||
case Both:
|
||||
return p.X + p.Y
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,18 +381,20 @@ func (s *Scroll) State() ScrollState {
|
||||
|
||||
// Add the handler to the operation list to receive drag events.
|
||||
func (d *Drag) Add(ops *op.Ops) {
|
||||
pointer.InputOp{
|
||||
Tag: d,
|
||||
Grab: d.grab,
|
||||
Kinds: pointer.Press | pointer.Drag | pointer.Release,
|
||||
}.Add(ops)
|
||||
event.Op(ops, d)
|
||||
}
|
||||
|
||||
// Update state and return the drag events.
|
||||
func (d *Drag) Update(cfg unit.Metric, q event.Queue, axis Axis) []pointer.Event {
|
||||
var events []pointer.Event
|
||||
for _, e := range q.Events(d) {
|
||||
e, ok := e.(pointer.Event)
|
||||
// Update state and return the next drag event, if any.
|
||||
func (d *Drag) Update(cfg unit.Metric, q input.Source, axis Axis) (pointer.Event, bool) {
|
||||
for {
|
||||
ev, ok := q.Event(pointer.Filter{
|
||||
Target: d,
|
||||
Kinds: pointer.Press | pointer.Drag | pointer.Release | pointer.Cancel,
|
||||
})
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
e, ok := ev.(pointer.Event)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
@@ -408,7 +427,7 @@ func (d *Drag) Update(cfg unit.Metric, q event.Queue, axis Axis) []pointer.Event
|
||||
diff := e.Position.Sub(d.start)
|
||||
slop := cfg.Dp(touchSlop)
|
||||
if diff.X*diff.X+diff.Y*diff.Y > float32(slop*slop) {
|
||||
d.grab = true
|
||||
q.Execute(pointer.GrabCmd{Tag: d, ID: e.PointerID})
|
||||
}
|
||||
}
|
||||
case pointer.Release, pointer.Cancel:
|
||||
@@ -417,13 +436,12 @@ func (d *Drag) Update(cfg unit.Metric, q event.Queue, axis Axis) []pointer.Event
|
||||
continue
|
||||
}
|
||||
d.dragging = false
|
||||
d.grab = false
|
||||
}
|
||||
|
||||
events = append(events, e)
|
||||
return e, true
|
||||
}
|
||||
|
||||
return events
|
||||
return pointer.Event{}, false
|
||||
}
|
||||
|
||||
// Dragging reports whether it is currently in use.
|
||||
@@ -446,13 +464,13 @@ func (a Axis) String() string {
|
||||
func (ct ClickKind) String() string {
|
||||
switch ct {
|
||||
case KindPress:
|
||||
return "TypePress"
|
||||
return "KindPress"
|
||||
case KindClick:
|
||||
return "TypeClick"
|
||||
return "KindClick"
|
||||
case KindCancel:
|
||||
return "TypeCancel"
|
||||
return "KindCancel"
|
||||
default:
|
||||
panic("invalid ClickType")
|
||||
panic("invalid ClickKind")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
-17
@@ -9,8 +9,8 @@ import (
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/input"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/router"
|
||||
"gioui.org/op"
|
||||
"gioui.org/op/clip"
|
||||
)
|
||||
@@ -22,20 +22,21 @@ func TestHover(t *testing.T) {
|
||||
stack := clip.Rect(rect).Push(ops)
|
||||
h.Add(ops)
|
||||
stack.Pop()
|
||||
r := new(router.Router)
|
||||
r := new(input.Router)
|
||||
h.Update(r.Source())
|
||||
r.Frame(ops)
|
||||
|
||||
r.Queue(
|
||||
pointer.Event{Kind: pointer.Move, Position: f32.Pt(30, 30)},
|
||||
)
|
||||
if !h.Update(r) {
|
||||
if !h.Update(r.Source()) {
|
||||
t.Fatal("expected hovered")
|
||||
}
|
||||
|
||||
r.Queue(
|
||||
pointer.Event{Kind: pointer.Move, Position: f32.Pt(50, 50)},
|
||||
)
|
||||
if h.Update(r) {
|
||||
if h.Update(r.Source()) {
|
||||
t.Fatal("expected not hovered")
|
||||
}
|
||||
}
|
||||
@@ -71,12 +72,21 @@ func TestMouseClicks(t *testing.T) {
|
||||
var ops op.Ops
|
||||
click.Add(&ops)
|
||||
|
||||
var r router.Router
|
||||
var r input.Router
|
||||
click.Update(r.Source())
|
||||
r.Frame(&ops)
|
||||
r.Queue(tc.events...)
|
||||
|
||||
events := click.Update(&r)
|
||||
clicks := filterMouseClicks(events)
|
||||
var clicks []ClickEvent
|
||||
for {
|
||||
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 {
|
||||
t.Fatalf("got %d mouse clicks, expected %d", got, want)
|
||||
}
|
||||
@@ -106,13 +116,3 @@ func mouseClickEvents(times ...time.Duration) []event.Event {
|
||||
}
|
||||
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,16 +1,14 @@
|
||||
module gioui.org
|
||||
|
||||
go 1.19
|
||||
go 1.23.8
|
||||
|
||||
require (
|
||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d
|
||||
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2
|
||||
gioui.org/shader v1.0.8
|
||||
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372
|
||||
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95
|
||||
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91
|
||||
golang.org/x/image v0.5.0
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64
|
||||
github.com/go-text/typesetting v0.3.0
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
|
||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0
|
||||
golang.org/x/image v0.26.0
|
||||
golang.org/x/sys v0.33.0
|
||||
golang.org/x/text v0.24.0
|
||||
)
|
||||
|
||||
require golang.org/x/text v0.7.0
|
||||
|
||||
@@ -1,43 +1,19 @@
|
||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
|
||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
|
||||
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/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
|
||||
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 h1:FQivqchis6bE2/9uF70M2gmmLpe82esEm2QadL0TEJo=
|
||||
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 h1:sBdrWpxhGDdTAYNqbgBLAR+ULAPPhfgncLr1X0lyWtg=
|
||||
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 h1:ryT6Nf0R83ZgD8WnFFdfI8wCeyqgdXWN4+CkFVNPAT0=
|
||||
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8=
|
||||
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
|
||||
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
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=
|
||||
github.com/go-text/typesetting v0.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML4iF4=
|
||||
github.com/go-text/typesetting v0.3.0/go.mod h1:qjZLkhRgOEYMhU9eHBr3AR4sfnGJvOXNLt8yRAySFuY=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 h1:tMSqXTK+AQdW3LpCbfatHSRPHeW6+2WuxaVQuHftn80=
|
||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8=
|
||||
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
|
||||
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
|
||||
+17
-13
@@ -4,12 +4,16 @@ package gpu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gioui.org/internal/f32"
|
||||
"image"
|
||||
)
|
||||
|
||||
type resourceCache struct {
|
||||
res map[interface{}]resourceCacheValue
|
||||
type textureCacheKey struct {
|
||||
filter byte
|
||||
handle any
|
||||
}
|
||||
|
||||
type textureCache struct {
|
||||
res map[textureCacheKey]resourceCacheValue
|
||||
}
|
||||
|
||||
type resourceCacheValue struct {
|
||||
@@ -31,19 +35,19 @@ type opCache struct {
|
||||
type opCacheValue struct {
|
||||
data pathData
|
||||
|
||||
bounds f32.Rectangle
|
||||
bounds image.Rectangle
|
||||
// the fields below are handled by opCache
|
||||
key opKey
|
||||
keep bool
|
||||
}
|
||||
|
||||
func newResourceCache() *resourceCache {
|
||||
return &resourceCache{
|
||||
res: make(map[interface{}]resourceCacheValue),
|
||||
func newTextureCache() *textureCache {
|
||||
return &textureCache{
|
||||
res: make(map[textureCacheKey]resourceCacheValue),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *resourceCache) get(key interface{}) (resource, bool) {
|
||||
func (r *textureCache) get(key textureCacheKey) (resource, bool) {
|
||||
v, exists := r.res[key]
|
||||
if !exists {
|
||||
return nil, false
|
||||
@@ -55,17 +59,17 @@ func (r *resourceCache) get(key interface{}) (resource, bool) {
|
||||
return v.resource, exists
|
||||
}
|
||||
|
||||
func (r *resourceCache) put(key interface{}, val resource) {
|
||||
func (r *textureCache) put(key textureCacheKey, val resource) {
|
||||
v, exists := r.res[key]
|
||||
if exists && v.used {
|
||||
panic(fmt.Errorf("key exists, %p", key))
|
||||
panic(fmt.Errorf("key exists, %v", key))
|
||||
}
|
||||
v.used = true
|
||||
v.resource = val
|
||||
r.res[key] = v
|
||||
}
|
||||
|
||||
func (r *resourceCache) frame() {
|
||||
func (r *textureCache) frame() {
|
||||
for k, v := range r.res {
|
||||
if v.used {
|
||||
v.used = false
|
||||
@@ -77,7 +81,7 @@ func (r *resourceCache) frame() {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *resourceCache) release() {
|
||||
func (r *textureCache) release() {
|
||||
for _, v := range r.res {
|
||||
v.resource.release()
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
// 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) {
|
||||
// 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*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`.
|
||||
|
||||
bo := binary.LittleEndian
|
||||
@@ -48,10 +48,10 @@ func encodeQuadTo(data []byte, meta uint32, from, ctrl, to f32.Point) {
|
||||
}
|
||||
|
||||
const (
|
||||
nwCorner = 1*0.25 + 0*0.5
|
||||
neCorner = 1*0.25 + 1*0.5
|
||||
swCorner = 0*0.25 + 0*0.5
|
||||
seCorner = 0*0.25 + 1*0.5
|
||||
nwCorner = 1*0.5 + 0*0.25
|
||||
neCorner = 1*0.5 + 1*0.25
|
||||
swCorner = 0*0.5 + 0*0.25
|
||||
seCorner = 0*0.5 + 1*0.25
|
||||
)
|
||||
|
||||
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) {
|
||||
var data [vertStride * 4]byte
|
||||
for i := 0; i < b.N; i++ {
|
||||
for i := 0; b.Loop(); i++ {
|
||||
v := float32(i)
|
||||
encodeQuadTo(data[:], 123,
|
||||
f32.Point{X: v, Y: v},
|
||||
|
||||
-2202
File diff suppressed because it is too large
Load Diff
-129
@@ -1,129 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package gpu
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/cpu"
|
||||
)
|
||||
|
||||
// This file contains code specific to running compute shaders on the CPU.
|
||||
|
||||
// dispatcher dispatches CPU compute programs across multiple goroutines.
|
||||
type dispatcher struct {
|
||||
// done is notified when a worker completes its work slice.
|
||||
done chan struct{}
|
||||
// work receives work slice indices. It is closed when the dispatcher is released.
|
||||
work chan work
|
||||
// dispatch receives compute jobs, which is then split among workers.
|
||||
dispatch chan dispatch
|
||||
// sync receives notification when a Sync completes.
|
||||
sync chan struct{}
|
||||
}
|
||||
|
||||
type work struct {
|
||||
ctx *cpu.DispatchContext
|
||||
index int
|
||||
}
|
||||
|
||||
type dispatch struct {
|
||||
_type jobType
|
||||
program *cpu.ProgramInfo
|
||||
descSet unsafe.Pointer
|
||||
x, y, z int
|
||||
}
|
||||
|
||||
type jobType uint8
|
||||
|
||||
const (
|
||||
jobDispatch jobType = iota
|
||||
jobBarrier
|
||||
jobSync
|
||||
)
|
||||
|
||||
func newDispatcher(workers int) *dispatcher {
|
||||
d := &dispatcher{
|
||||
work: make(chan work, workers),
|
||||
done: make(chan struct{}, workers),
|
||||
// Leave some room to avoid blocking calls to Dispatch.
|
||||
dispatch: make(chan dispatch, 20),
|
||||
sync: make(chan struct{}),
|
||||
}
|
||||
for i := 0; i < workers; i++ {
|
||||
go d.worker()
|
||||
}
|
||||
go d.dispatcher()
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *dispatcher) dispatcher() {
|
||||
defer close(d.work)
|
||||
var free []*cpu.DispatchContext
|
||||
defer func() {
|
||||
for _, ctx := range free {
|
||||
ctx.Free()
|
||||
}
|
||||
}()
|
||||
var used []*cpu.DispatchContext
|
||||
for job := range d.dispatch {
|
||||
switch job._type {
|
||||
case jobDispatch:
|
||||
if len(free) == 0 {
|
||||
free = append(free, cpu.NewDispatchContext())
|
||||
}
|
||||
ctx := free[len(free)-1]
|
||||
free = free[:len(free)-1]
|
||||
used = append(used, ctx)
|
||||
ctx.Prepare(cap(d.work), job.program, job.descSet, job.x, job.y, job.z)
|
||||
for i := 0; i < cap(d.work); i++ {
|
||||
d.work <- work{
|
||||
ctx: ctx,
|
||||
index: i,
|
||||
}
|
||||
}
|
||||
case jobBarrier:
|
||||
// Wait for all outstanding dispatches to complete.
|
||||
for i := 0; i < len(used)*cap(d.work); i++ {
|
||||
<-d.done
|
||||
}
|
||||
free = append(free, used...)
|
||||
used = used[:0]
|
||||
case jobSync:
|
||||
d.sync <- struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dispatcher) worker() {
|
||||
thread := cpu.NewThreadContext()
|
||||
defer thread.Free()
|
||||
for w := range d.work {
|
||||
w.ctx.Dispatch(w.index, thread)
|
||||
d.done <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dispatcher) Barrier() {
|
||||
d.dispatch <- dispatch{_type: jobBarrier}
|
||||
}
|
||||
|
||||
func (d *dispatcher) Sync() {
|
||||
d.dispatch <- dispatch{_type: jobSync}
|
||||
<-d.sync
|
||||
}
|
||||
|
||||
func (d *dispatcher) Dispatch(program *cpu.ProgramInfo, descSet unsafe.Pointer, x, y, z int) {
|
||||
d.dispatch <- dispatch{
|
||||
_type: jobDispatch,
|
||||
program: program,
|
||||
descSet: descSet,
|
||||
x: x,
|
||||
y: y,
|
||||
z: z,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dispatcher) Stop() {
|
||||
close(d.dispatch)
|
||||
}
|
||||
+212
-192
@@ -9,12 +9,13 @@ package gpu
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
"os"
|
||||
"reflect"
|
||||
"slices"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
@@ -44,14 +45,10 @@ type GPU interface {
|
||||
Clear(color color.NRGBA)
|
||||
// Frame draws the graphics operations from op into a viewport of target.
|
||||
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 {
|
||||
cache *resourceCache
|
||||
cache *textureCache
|
||||
|
||||
profile string
|
||||
timers *timers
|
||||
@@ -73,7 +70,6 @@ type renderer struct {
|
||||
}
|
||||
|
||||
type drawOps struct {
|
||||
profile bool
|
||||
reader ops.Reader
|
||||
states []f32.Affine2D
|
||||
transStack []f32.Affine2D
|
||||
@@ -122,17 +118,17 @@ type drawState struct {
|
||||
}
|
||||
|
||||
type pathOp struct {
|
||||
off f32.Point
|
||||
off image.Point
|
||||
// rect tracks whether the clip stack can be represented by a
|
||||
// pixel-aligned rectangle.
|
||||
rect bool
|
||||
// clip is the union of all
|
||||
// later clip rectangles.
|
||||
clip image.Rectangle
|
||||
bounds f32.Rectangle
|
||||
bounds image.Rectangle
|
||||
// intersect is the intersection of bounds and all
|
||||
// previous clip bounds.
|
||||
intersect f32.Rectangle
|
||||
intersect image.Rectangle
|
||||
pathKey opKey
|
||||
path bool
|
||||
pathVerts []byte
|
||||
@@ -194,7 +190,7 @@ const (
|
||||
// imageOpData is the shadow of paint.ImageOp.
|
||||
type imageOpData struct {
|
||||
src *image.RGBA
|
||||
handle interface{}
|
||||
handle any
|
||||
filter byte
|
||||
}
|
||||
|
||||
@@ -205,7 +201,7 @@ type linearGradientOpData struct {
|
||||
color2 color.NRGBA
|
||||
}
|
||||
|
||||
func decodeImageOp(data []byte, refs []interface{}) imageOpData {
|
||||
func decodeImageOp(data []byte, refs []any) imageOpData {
|
||||
handle := refs[1]
|
||||
if handle == nil {
|
||||
return imageOpData{}
|
||||
@@ -266,7 +262,7 @@ type texture struct {
|
||||
type blitter struct {
|
||||
ctx driver.Device
|
||||
viewport image.Point
|
||||
pipelines [3]*pipeline
|
||||
pipelines [2][3]*pipeline
|
||||
colUniforms *blitColUniforms
|
||||
texUniforms *blitTexUniforms
|
||||
linearGradientUniforms *blitLinearGradientUniforms
|
||||
@@ -348,18 +344,17 @@ func New(api API) (GPU, error) {
|
||||
func NewWithDevice(d driver.Device) (GPU, error) {
|
||||
d.BeginFrame(nil, false, image.Point{})
|
||||
defer d.EndFrame()
|
||||
forceCompute := os.Getenv("GIORENDERER") == "forcecompute"
|
||||
feats := d.Caps().Features
|
||||
switch {
|
||||
case !forceCompute && feats.Has(driver.FeatureFloatRenderTargets) && feats.Has(driver.FeatureSRGB):
|
||||
case feats.Has(driver.FeatureFloatRenderTargets) && feats.Has(driver.FeatureSRGB):
|
||||
return newGPU(d)
|
||||
}
|
||||
return newCompute(d)
|
||||
return nil, errors.New("no available GPU driver")
|
||||
}
|
||||
|
||||
func newGPU(ctx driver.Device) (*gpu, error) {
|
||||
g := &gpu{
|
||||
cache: newResourceCache(),
|
||||
cache: newTextureCache(),
|
||||
}
|
||||
g.drawOps.pathCache = newOpCache()
|
||||
if err := g.init(ctx); err != nil {
|
||||
@@ -399,7 +394,7 @@ func (g *gpu) collect(viewport image.Point, frameOps *op.Ops) {
|
||||
g.renderer.pather.viewport = viewport
|
||||
g.drawOps.reset(viewport)
|
||||
g.drawOps.collect(frameOps, viewport)
|
||||
if g.drawOps.profile && g.timers == nil && g.ctx.Caps().Features.Has(driver.FeatureTimers) {
|
||||
if false && g.timers == nil && g.ctx.Caps().Features.Has(driver.FeatureTimers) {
|
||||
g.frameStart = time.Now()
|
||||
g.timers = newTimers(g.ctx)
|
||||
g.stencilTimer = g.timers.newTimer()
|
||||
@@ -425,9 +420,9 @@ func (g *gpu) frame(target RenderTarget) error {
|
||||
g.stencilTimer.end()
|
||||
g.coverTimer.begin()
|
||||
g.renderer.uploadImages(g.cache, g.drawOps.imageOps)
|
||||
g.renderer.prepareDrawOps(g.cache, g.drawOps.imageOps)
|
||||
g.renderer.prepareDrawOps(g.drawOps.imageOps)
|
||||
g.drawOps.layers = g.renderer.packLayers(g.drawOps.layers)
|
||||
g.renderer.drawLayers(g.cache, g.drawOps.layers, g.drawOps.imageOps)
|
||||
g.renderer.drawLayers(g.drawOps.layers, g.drawOps.imageOps)
|
||||
d := driver.LoadDesc{
|
||||
ClearColor: g.drawOps.clearColor,
|
||||
}
|
||||
@@ -437,14 +432,14 @@ func (g *gpu) frame(target RenderTarget) error {
|
||||
}
|
||||
g.ctx.BeginRenderPass(defFBO, d)
|
||||
g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
|
||||
g.renderer.drawOps(g.cache, false, image.Point{}, g.renderer.blitter.viewport, g.drawOps.imageOps)
|
||||
g.renderer.drawOps(false, image.Point{}, g.renderer.blitter.viewport, g.drawOps.imageOps)
|
||||
g.coverTimer.end()
|
||||
g.ctx.EndRenderPass()
|
||||
g.cleanupTimer.begin()
|
||||
g.cache.frame()
|
||||
g.drawOps.pathCache.frame()
|
||||
g.cleanupTimer.end()
|
||||
if g.drawOps.profile && g.timers.ready() {
|
||||
if false && g.timers.ready() {
|
||||
st, covt, cleant := g.stencilTimer.Elapsed, g.coverTimer.Elapsed, g.cleanupTimer.Elapsed
|
||||
ft := st + covt + cleant
|
||||
q := 100 * time.Microsecond
|
||||
@@ -460,12 +455,8 @@ func (g *gpu) Profile() string {
|
||||
return g.profile
|
||||
}
|
||||
|
||||
func (r *renderer) texHandle(cache *resourceCache, data imageOpData) driver.Texture {
|
||||
type cachekey struct {
|
||||
filter byte
|
||||
handle any
|
||||
}
|
||||
key := cachekey{
|
||||
func (r *renderer) texHandle(cache *textureCache, data imageOpData) driver.Texture {
|
||||
key := textureCacheKey{
|
||||
filter: data.filter,
|
||||
handle: data.handle,
|
||||
}
|
||||
@@ -557,7 +548,7 @@ func newBlitter(ctx driver.Device) *blitter {
|
||||
b.texUniforms = new(blitTexUniforms)
|
||||
b.linearGradientUniforms = new(blitLinearGradientUniforms)
|
||||
pipelines, err := createColorPrograms(ctx, gio.Shader_blit_vert, gio.Shader_blit_frag,
|
||||
[3]interface{}{b.colUniforms, b.linearGradientUniforms, b.texUniforms},
|
||||
[3]any{b.colUniforms, b.linearGradientUniforms, b.texUniforms},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -569,12 +560,24 @@ func newBlitter(ctx driver.Device) *blitter {
|
||||
func (b *blitter) release() {
|
||||
b.quadVerts.Release()
|
||||
for _, p := range b.pipelines {
|
||||
p.Release()
|
||||
for _, p := range p {
|
||||
p.Release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.Sources, uniforms [3]interface{}) ([3]*pipeline, error) {
|
||||
var pipelines [3]*pipeline
|
||||
func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.Sources, uniforms [3]any) (pipelines [2][3]*pipeline, err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
for _, p := range pipelines {
|
||||
for _, p := range p {
|
||||
if p != nil {
|
||||
p.Release()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
blend := driver.BlendDesc{
|
||||
Enable: true,
|
||||
SrcFactor: driver.BlendFactorOne,
|
||||
@@ -592,86 +595,76 @@ func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.
|
||||
return pipelines, err
|
||||
}
|
||||
defer vsh.Release()
|
||||
{
|
||||
fsh, err := b.NewFragmentShader(fsSrc[materialTexture])
|
||||
if err != nil {
|
||||
return pipelines, err
|
||||
for i, format := range []driver.TextureFormat{driver.TextureFormatOutput, driver.TextureFormatSRGBA} {
|
||||
{
|
||||
fsh, err := b.NewFragmentShader(fsSrc[materialTexture])
|
||||
if err != nil {
|
||||
return pipelines, err
|
||||
}
|
||||
defer fsh.Release()
|
||||
pipe, err := b.NewPipeline(driver.PipelineDesc{
|
||||
VertexShader: vsh,
|
||||
FragmentShader: fsh,
|
||||
BlendDesc: blend,
|
||||
VertexLayout: layout,
|
||||
PixelFormat: format,
|
||||
Topology: driver.TopologyTriangleStrip,
|
||||
})
|
||||
if err != nil {
|
||||
return pipelines, err
|
||||
}
|
||||
var vertBuffer *uniformBuffer
|
||||
if u := uniforms[materialTexture]; u != nil {
|
||||
vertBuffer = newUniformBuffer(b, u)
|
||||
}
|
||||
pipelines[i][materialTexture] = &pipeline{pipe, vertBuffer}
|
||||
}
|
||||
defer fsh.Release()
|
||||
pipe, err := b.NewPipeline(driver.PipelineDesc{
|
||||
VertexShader: vsh,
|
||||
FragmentShader: fsh,
|
||||
BlendDesc: blend,
|
||||
VertexLayout: layout,
|
||||
PixelFormat: driver.TextureFormatOutput,
|
||||
Topology: driver.TopologyTriangleStrip,
|
||||
})
|
||||
if err != nil {
|
||||
return pipelines, err
|
||||
{
|
||||
var vertBuffer *uniformBuffer
|
||||
fsh, err := b.NewFragmentShader(fsSrc[materialColor])
|
||||
if err != nil {
|
||||
return pipelines, err
|
||||
}
|
||||
defer fsh.Release()
|
||||
pipe, err := b.NewPipeline(driver.PipelineDesc{
|
||||
VertexShader: vsh,
|
||||
FragmentShader: fsh,
|
||||
BlendDesc: blend,
|
||||
VertexLayout: layout,
|
||||
PixelFormat: format,
|
||||
Topology: driver.TopologyTriangleStrip,
|
||||
})
|
||||
if err != nil {
|
||||
return pipelines, err
|
||||
}
|
||||
if u := uniforms[materialColor]; u != nil {
|
||||
vertBuffer = newUniformBuffer(b, u)
|
||||
}
|
||||
pipelines[i][materialColor] = &pipeline{pipe, vertBuffer}
|
||||
}
|
||||
var vertBuffer *uniformBuffer
|
||||
if u := uniforms[materialTexture]; u != nil {
|
||||
vertBuffer = newUniformBuffer(b, u)
|
||||
{
|
||||
var vertBuffer *uniformBuffer
|
||||
fsh, err := b.NewFragmentShader(fsSrc[materialLinearGradient])
|
||||
if err != nil {
|
||||
return pipelines, err
|
||||
}
|
||||
defer fsh.Release()
|
||||
pipe, err := b.NewPipeline(driver.PipelineDesc{
|
||||
VertexShader: vsh,
|
||||
FragmentShader: fsh,
|
||||
BlendDesc: blend,
|
||||
VertexLayout: layout,
|
||||
PixelFormat: format,
|
||||
Topology: driver.TopologyTriangleStrip,
|
||||
})
|
||||
if err != nil {
|
||||
return pipelines, err
|
||||
}
|
||||
if u := uniforms[materialLinearGradient]; u != nil {
|
||||
vertBuffer = newUniformBuffer(b, u)
|
||||
}
|
||||
pipelines[i][materialLinearGradient] = &pipeline{pipe, vertBuffer}
|
||||
}
|
||||
pipelines[materialTexture] = &pipeline{pipe, vertBuffer}
|
||||
}
|
||||
{
|
||||
var vertBuffer *uniformBuffer
|
||||
fsh, err := b.NewFragmentShader(fsSrc[materialColor])
|
||||
if err != nil {
|
||||
pipelines[materialTexture].Release()
|
||||
return pipelines, err
|
||||
}
|
||||
defer fsh.Release()
|
||||
pipe, err := b.NewPipeline(driver.PipelineDesc{
|
||||
VertexShader: vsh,
|
||||
FragmentShader: fsh,
|
||||
BlendDesc: blend,
|
||||
VertexLayout: layout,
|
||||
PixelFormat: driver.TextureFormatOutput,
|
||||
Topology: driver.TopologyTriangleStrip,
|
||||
})
|
||||
if err != nil {
|
||||
pipelines[materialTexture].Release()
|
||||
return pipelines, err
|
||||
}
|
||||
if u := uniforms[materialColor]; u != nil {
|
||||
vertBuffer = newUniformBuffer(b, u)
|
||||
}
|
||||
pipelines[materialColor] = &pipeline{pipe, vertBuffer}
|
||||
}
|
||||
{
|
||||
var vertBuffer *uniformBuffer
|
||||
fsh, err := b.NewFragmentShader(fsSrc[materialLinearGradient])
|
||||
if err != nil {
|
||||
pipelines[materialTexture].Release()
|
||||
pipelines[materialColor].Release()
|
||||
return pipelines, err
|
||||
}
|
||||
defer fsh.Release()
|
||||
pipe, err := b.NewPipeline(driver.PipelineDesc{
|
||||
VertexShader: vsh,
|
||||
FragmentShader: fsh,
|
||||
BlendDesc: blend,
|
||||
VertexLayout: layout,
|
||||
PixelFormat: driver.TextureFormatOutput,
|
||||
Topology: driver.TopologyTriangleStrip,
|
||||
})
|
||||
if err != nil {
|
||||
pipelines[materialTexture].Release()
|
||||
pipelines[materialColor].Release()
|
||||
return pipelines, err
|
||||
}
|
||||
if u := uniforms[materialLinearGradient]; u != nil {
|
||||
vertBuffer = newUniformBuffer(b, u)
|
||||
}
|
||||
pipelines[materialLinearGradient] = &pipeline{pipe, vertBuffer}
|
||||
}
|
||||
if err != nil {
|
||||
for _, p := range pipelines {
|
||||
p.Release()
|
||||
}
|
||||
return pipelines, err
|
||||
}
|
||||
return pipelines, nil
|
||||
}
|
||||
@@ -830,7 +823,7 @@ func (r *renderer) packLayers(layers []opacityLayer) []opacityLayer {
|
||||
layers[l.parent].clip = b.Union(l.clip)
|
||||
}
|
||||
if l.clip.Empty() {
|
||||
layers = append(layers[:i], layers[i+1:]...)
|
||||
layers = slices.Delete(layers, i, i+1)
|
||||
}
|
||||
}
|
||||
// Pack layers.
|
||||
@@ -853,7 +846,7 @@ func (r *renderer) packLayers(layers []opacityLayer) []opacityLayer {
|
||||
return layers
|
||||
}
|
||||
|
||||
func (r *renderer) drawLayers(cache *resourceCache, layers []opacityLayer, ops []imageOp) {
|
||||
func (r *renderer) drawLayers(layers []opacityLayer, ops []imageOp) {
|
||||
if len(r.layers.sizes) == 0 {
|
||||
return
|
||||
}
|
||||
@@ -874,12 +867,12 @@ func (r *renderer) drawLayers(cache *resourceCache, layers []opacityLayer, ops [
|
||||
Min: l.place.Pos,
|
||||
Max: l.place.Pos.Add(l.clip.Size()),
|
||||
}
|
||||
r.ctx.Viewport(v.Min.X, v.Min.Y, v.Max.X, v.Max.Y)
|
||||
r.ctx.Viewport(v.Min.X, v.Min.Y, v.Dx(), v.Dy())
|
||||
f := r.layerFBOs.fbos[fbo]
|
||||
r.drawOps(cache, true, l.clip.Min.Mul(-1), l.clip.Size(), ops[l.opStart:l.opEnd])
|
||||
r.drawOps(true, l.clip.Min.Mul(-1), l.clip.Size(), ops[l.opStart:l.opEnd])
|
||||
sr := f32.FRect(v)
|
||||
uvScale, uvOffset := texSpaceTransform(sr, f.size)
|
||||
uvTrans := f32.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset)
|
||||
uvTrans := f32.AffineId().Scale(f32.Point{}, uvScale).Offset(uvOffset)
|
||||
// Replace layer ops with one textured op.
|
||||
ops[l.opStart] = imageOp{
|
||||
clip: l.clip,
|
||||
@@ -899,7 +892,6 @@ func (r *renderer) drawLayers(cache *resourceCache, layers []opacityLayer, ops [
|
||||
}
|
||||
|
||||
func (d *drawOps) reset(viewport image.Point) {
|
||||
d.profile = false
|
||||
d.viewport = viewport
|
||||
d.imageOps = d.imageOps[:0]
|
||||
d.pathOps = d.pathOps[:0]
|
||||
@@ -910,16 +902,14 @@ func (d *drawOps) reset(viewport image.Point) {
|
||||
d.opacityStack = d.opacityStack[:0]
|
||||
}
|
||||
|
||||
func (d *drawOps) collect(root *op.Ops, viewport image.Point) {
|
||||
viewf := f32.Rectangle{
|
||||
Max: f32.Point{X: float32(viewport.X), Y: float32(viewport.Y)},
|
||||
}
|
||||
func (d *drawOps) collect(root *op.Ops, viewportSize image.Point) {
|
||||
viewport := image.Rectangle{Max: viewportSize}
|
||||
var ops *ops.Ops
|
||||
if root != nil {
|
||||
ops = &root.Internal
|
||||
}
|
||||
d.reader.Reset(ops)
|
||||
d.collectOps(&d.reader, viewf)
|
||||
d.collectOps(&d.reader, viewport)
|
||||
}
|
||||
|
||||
func (d *drawOps) buildPaths(ctx driver.Device) {
|
||||
@@ -940,7 +930,7 @@ func (d *drawOps) newPathOp() *pathOp {
|
||||
return &d.pathOpCache[len(d.pathOpCache)-1]
|
||||
}
|
||||
|
||||
func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey opKey, bounds f32.Rectangle, off f32.Point, push bool) {
|
||||
func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey opKey, bounds image.Rectangle, off image.Point) {
|
||||
npath := d.newPathOp()
|
||||
*npath = pathOp{
|
||||
parent: state.cpath,
|
||||
@@ -965,7 +955,9 @@ func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey opKey, bounds
|
||||
|
||||
func (d *drawOps) save(id int, state f32.Affine2D) {
|
||||
if extra := id - len(d.states) + 1; extra > 0 {
|
||||
d.states = append(d.states, make([]f32.Affine2D, extra)...)
|
||||
for range extra {
|
||||
d.states = append(d.states, f32.AffineId())
|
||||
}
|
||||
}
|
||||
d.states[id] = state
|
||||
}
|
||||
@@ -979,13 +971,14 @@ func (k opKey) SetTransform(t f32.Affine2D) opKey {
|
||||
return k
|
||||
}
|
||||
|
||||
func (d *drawOps) collectOps(r *ops.Reader, viewport f32.Rectangle) {
|
||||
var (
|
||||
quads quadsOp
|
||||
state drawState
|
||||
)
|
||||
func (d *drawOps) collectOps(r *ops.Reader, viewport image.Rectangle) {
|
||||
var quads quadsOp
|
||||
state := drawState{
|
||||
t: f32.AffineId(),
|
||||
}
|
||||
reset := func() {
|
||||
state = drawState{
|
||||
t: f32.AffineId(),
|
||||
color: color.NRGBA{A: 0xff},
|
||||
}
|
||||
}
|
||||
@@ -993,8 +986,6 @@ func (d *drawOps) collectOps(r *ops.Reader, viewport f32.Rectangle) {
|
||||
loop:
|
||||
for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
|
||||
switch ops.OpType(encOp.Data[0]) {
|
||||
case ops.TypeProfile:
|
||||
d.profile = true
|
||||
case ops.TypeTransform:
|
||||
dop, push := ops.DecodeTransform(encOp.Data)
|
||||
if push {
|
||||
@@ -1042,8 +1033,8 @@ loop:
|
||||
var op ops.ClipOp
|
||||
op.Decode(encOp.Data)
|
||||
quads.key.outline = op.Outline
|
||||
bounds := f32.FRect(op.Bounds)
|
||||
trans, off := state.t.Split()
|
||||
bounds := op.Bounds
|
||||
trans, off := transformOffset(state.t)
|
||||
if len(quads.aux) > 0 {
|
||||
// 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
|
||||
@@ -1054,11 +1045,11 @@ loop:
|
||||
// Why is this not used for the offset shapes?
|
||||
bounds = v.bounds
|
||||
} else {
|
||||
var pathData []byte
|
||||
pathData, bounds = d.buildVerts(
|
||||
newPathData, newBounds := d.buildVerts(
|
||||
quads.aux, trans, quads.key.outline, quads.key.strokeWidth,
|
||||
)
|
||||
quads.aux = pathData
|
||||
quads.aux = newPathData
|
||||
bounds = newBounds.Round()
|
||||
// add it to the cache, without GPU data, so the transform can be
|
||||
// reused.
|
||||
d.pathCache.put(quads.key, opCacheValue{bounds: bounds})
|
||||
@@ -1066,8 +1057,9 @@ loop:
|
||||
} else {
|
||||
quads.aux, bounds, _ = d.boundsForTransformedRect(bounds, trans)
|
||||
quads.key = opKey{Key: encOp.Key}
|
||||
quads.key = quads.key.SetTransform(trans)
|
||||
}
|
||||
d.addClipPath(&state, quads.aux, quads.key, bounds, off, true)
|
||||
d.addClipPath(&state, quads.aux, quads.key, bounds, off)
|
||||
quads = quadsOp{}
|
||||
case ops.TypePopClip:
|
||||
state.cpath = state.cpath.parent
|
||||
@@ -1089,21 +1081,21 @@ loop:
|
||||
// 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
|
||||
// the new bounding rectangle and the transformed original paint rectangle.
|
||||
t, off := state.t.Split()
|
||||
t, off := transformOffset(state.t)
|
||||
// Fill the clip area, unless the material is a (bounded) image.
|
||||
// TODO: Find a tighter bound.
|
||||
inf := float32(1e6)
|
||||
dst := f32.Rect(-inf, -inf, inf, inf)
|
||||
inf := int(1e6)
|
||||
dst := image.Rect(-inf, -inf, inf, inf)
|
||||
if state.matType == materialTexture {
|
||||
sz := state.image.src.Rect.Size()
|
||||
dst = f32.Rectangle{Max: layout.FPt(sz)}
|
||||
dst = image.Rectangle{Max: sz}
|
||||
}
|
||||
clipData, bnd, partialTrans := d.boundsForTransformedRect(dst, t)
|
||||
cl := viewport.Intersect(bnd.Add(off))
|
||||
bounds := viewport.Intersect(bnd.Add(off))
|
||||
if state.cpath != nil {
|
||||
cl = state.cpath.intersect.Intersect(cl)
|
||||
bounds = state.cpath.intersect.Intersect(bounds)
|
||||
}
|
||||
if cl.Empty() {
|
||||
if bounds.Empty() {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1111,11 +1103,10 @@ loop:
|
||||
// The paint operation is sheared or rotated, add a clip path representing
|
||||
// this transformed rectangle.
|
||||
k := opKey{Key: encOp.Key}
|
||||
k.SetTransform(t) // TODO: This call has no effect.
|
||||
d.addClipPath(&state, clipData, k, bnd, off, false)
|
||||
k = k.SetTransform(t)
|
||||
d.addClipPath(&state, clipData, k, bnd, off)
|
||||
}
|
||||
|
||||
bounds := cl.Round()
|
||||
mat := state.materialFor(bnd, off, partialTrans, bounds)
|
||||
|
||||
rect := state.cpath == nil || state.cpath.rect
|
||||
@@ -1169,9 +1160,10 @@ func expandPathOp(p *pathOp, clip image.Rectangle) {
|
||||
}
|
||||
}
|
||||
|
||||
func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32.Affine2D, clip image.Rectangle) material {
|
||||
func (d *drawState) materialFor(rect image.Rectangle, off image.Point, partTrans f32.Affine2D, clip image.Rectangle) material {
|
||||
m := material{
|
||||
opacity: 1.,
|
||||
uvTrans: f32.AffineId(),
|
||||
}
|
||||
switch d.matType {
|
||||
case materialColor:
|
||||
@@ -1188,7 +1180,7 @@ func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32
|
||||
m.uvTrans = partTrans.Mul(gradientSpaceTransform(clip, off, d.stop1, d.stop2))
|
||||
case materialTexture:
|
||||
m.material = materialTexture
|
||||
dr := rect.Add(off).Round()
|
||||
dr := rect.Add(off)
|
||||
sz := d.image.src.Bounds().Size()
|
||||
sr := f32.Rectangle{
|
||||
Max: f32.Point{
|
||||
@@ -1205,13 +1197,13 @@ func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32
|
||||
sr.Min.Y += float32(clip.Min.Y-dr.Min.Y) * sdy / dy
|
||||
sr.Max.Y -= float32(dr.Max.Y-clip.Max.Y) * sdy / dy
|
||||
uvScale, uvOffset := texSpaceTransform(sr, sz)
|
||||
m.uvTrans = partTrans.Mul(f32.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset))
|
||||
m.uvTrans = partTrans.Mul(f32.AffineId().Scale(f32.Point{}, uvScale).Offset(uvOffset))
|
||||
m.data = d.image
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (r *renderer) uploadImages(cache *resourceCache, ops []imageOp) {
|
||||
func (r *renderer) uploadImages(cache *textureCache, ops []imageOp) {
|
||||
for i := range ops {
|
||||
img := &ops[i]
|
||||
m := img.material
|
||||
@@ -1221,7 +1213,7 @@ func (r *renderer) uploadImages(cache *resourceCache, ops []imageOp) {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *renderer) prepareDrawOps(cache *resourceCache, ops []imageOp) {
|
||||
func (r *renderer) prepareDrawOps(ops []imageOp) {
|
||||
for _, img := range ops {
|
||||
m := img.material
|
||||
switch m.material {
|
||||
@@ -1242,7 +1234,7 @@ func (r *renderer) prepareDrawOps(cache *resourceCache, ops []imageOp) {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *renderer) drawOps(cache *resourceCache, isFBO bool, opOff image.Point, viewport image.Point, ops []imageOp) {
|
||||
func (r *renderer) drawOps(isFBO bool, opOff, viewport image.Point, ops []imageOp) {
|
||||
var coverTex driver.Texture
|
||||
for i := 0; i < len(ops); i++ {
|
||||
img := ops[i]
|
||||
@@ -1256,9 +1248,13 @@ func (r *renderer) drawOps(cache *resourceCache, isFBO bool, opOff image.Point,
|
||||
|
||||
scale, off := clipSpaceTransform(drc, viewport)
|
||||
var fbo FBO
|
||||
fboIdx := 0
|
||||
if isFBO {
|
||||
fboIdx = 1
|
||||
}
|
||||
switch img.clipType {
|
||||
case clipTypeNone:
|
||||
p := r.blitter.pipelines[m.material]
|
||||
p := r.blitter.pipelines[fboIdx][m.material]
|
||||
r.ctx.BindPipeline(p.pipeline)
|
||||
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
|
||||
r.blitter.blit(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.opacity, m.uvTrans)
|
||||
@@ -1277,7 +1273,7 @@ func (r *renderer) drawOps(cache *resourceCache, isFBO bool, opOff image.Point,
|
||||
Max: img.place.Pos.Add(drc.Size()),
|
||||
}
|
||||
coverScale, coverOff := texSpaceTransform(f32.FRect(uv), fbo.size)
|
||||
p := r.pather.coverer.pipelines[m.material]
|
||||
p := r.pather.coverer.pipelines[fboIdx][m.material]
|
||||
r.ctx.BindPipeline(p.pipeline)
|
||||
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
|
||||
r.pather.cover(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.uvTrans, coverScale, coverOff)
|
||||
@@ -1285,7 +1281,11 @@ func (r *renderer) drawOps(cache *resourceCache, isFBO bool, opOff image.Point,
|
||||
}
|
||||
|
||||
func (b *blitter) blit(mat materialType, fbo bool, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, opacity float32, uvTrans f32.Affine2D) {
|
||||
p := b.pipelines[mat]
|
||||
fboIdx := 0
|
||||
if fbo {
|
||||
fboIdx = 1
|
||||
}
|
||||
p := b.pipelines[fboIdx][mat]
|
||||
b.ctx.BindPipeline(p.pipeline)
|
||||
var uniforms *blitUniforms
|
||||
switch mat {
|
||||
@@ -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
|
||||
// structure uniformBlock points to.
|
||||
func newUniformBuffer(b driver.Device, uniformBlock interface{}) *uniformBuffer {
|
||||
func newUniformBuffer(b driver.Device, uniformBlock any) *uniformBuffer {
|
||||
ref := reflect.ValueOf(uniformBlock)
|
||||
// Determine the size of the uniforms structure, *uniforms.
|
||||
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)].
|
||||
func gradientSpaceTransform(clip image.Rectangle, off f32.Point, stop1, stop2 f32.Point) f32.Affine2D {
|
||||
func gradientSpaceTransform(clip image.Rectangle, off image.Point, stop1, stop2 f32.Point) f32.Affine2D {
|
||||
d := stop2.Sub(stop1)
|
||||
l := float32(math.Sqrt(float64(d.X*d.X + d.Y*d.Y)))
|
||||
a := float32(math.Atan2(float64(-d.Y), float64(d.X)))
|
||||
|
||||
// TODO: optimize
|
||||
zp := f32.Point{}
|
||||
return f32.Affine2D{}.
|
||||
Scale(zp, layout.FPt(clip.Size())). // scale to pixel space
|
||||
Offset(zp.Sub(off).Add(layout.FPt(clip.Min))). // offset to clip space
|
||||
Offset(zp.Sub(stop1)). // offset to first stop point
|
||||
Rotate(zp, a). // rotate to align gradient
|
||||
Scale(zp, f32.Pt(1/l, 1/l)) // scale gradient to right size
|
||||
return f32.AffineId().
|
||||
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(stop1)). // offset to first stop point
|
||||
Rotate(zp, a). // rotate to align gradient
|
||||
Scale(zp, f32.Pt(1/l, 1/l)) // scale gradient to right size
|
||||
}
|
||||
|
||||
// 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.
|
||||
func decodeToOutlineQuads(qs *quadSplitter, tr f32.Affine2D, pathData []byte) {
|
||||
for len(pathData) >= scene.CommandSize+4 {
|
||||
qs.contour = bo.Uint32(pathData)
|
||||
qs.contour = binary.LittleEndian.Uint32(pathData)
|
||||
cmd := ops.DecodeCommand(pathData[4:])
|
||||
switch cmd.Op() {
|
||||
case scene.OpLine:
|
||||
@@ -1521,37 +1521,38 @@ func decodeToOutlineQuads(qs *quadSplitter, tr f32.Affine2D, pathData []byte) {
|
||||
}
|
||||
|
||||
// create GPU vertices for transformed r, find the bounds and establish texture transform.
|
||||
func (d *drawOps) boundsForTransformedRect(r f32.Rectangle, tr f32.Affine2D) (aux []byte, bnd f32.Rectangle, ptr f32.Affine2D) {
|
||||
if isPureOffset(tr) {
|
||||
// fast-path to allow blitting of pure rectangles
|
||||
_, _, ox, _, _, oy := tr.Elems()
|
||||
off := f32.Pt(ox, oy)
|
||||
bnd.Min = r.Min.Add(off)
|
||||
bnd.Max = r.Max.Add(off)
|
||||
func (d *drawOps) boundsForTransformedRect(r image.Rectangle, tr f32.Affine2D) (aux []byte, bnd image.Rectangle, ptr f32.Affine2D) {
|
||||
ptr = f32.AffineId()
|
||||
if tr == f32.AffineId() {
|
||||
// fast-path to allow blitting of pure rectangles.
|
||||
bnd = r
|
||||
return
|
||||
}
|
||||
|
||||
// transform all corners, find new bounds
|
||||
corners := [4]f32.Point{
|
||||
tr.Transform(r.Min), tr.Transform(f32.Pt(r.Max.X, r.Min.Y)),
|
||||
tr.Transform(r.Max), tr.Transform(f32.Pt(r.Min.X, r.Max.Y)),
|
||||
tr.Transform(f32.FPt(r.Min)), tr.Transform(f32.Pt(float32(r.Max.X), float32(r.Min.Y))),
|
||||
tr.Transform(f32.FPt(r.Max)), tr.Transform(f32.Pt(float32(r.Min.X), float32(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 {
|
||||
if c.X < bnd.Min.X {
|
||||
bnd.Min.X = c.X
|
||||
if c.X < fBounds.Min.X {
|
||||
fBounds.Min.X = c.X
|
||||
}
|
||||
if c.Y < bnd.Min.Y {
|
||||
bnd.Min.Y = c.Y
|
||||
if c.Y < fBounds.Min.Y {
|
||||
fBounds.Min.Y = c.Y
|
||||
}
|
||||
if c.X > bnd.Max.X {
|
||||
bnd.Max.X = c.X
|
||||
if c.X > fBounds.Max.X {
|
||||
fBounds.Max.X = c.X
|
||||
}
|
||||
if c.Y > bnd.Max.Y {
|
||||
bnd.Max.Y = c.Y
|
||||
if c.Y > fBounds.Max.Y {
|
||||
fBounds.Max.Y = c.Y
|
||||
}
|
||||
}
|
||||
bnd = fBounds.Round()
|
||||
|
||||
// build the GPU vertices
|
||||
l := len(d.vertCache)
|
||||
@@ -1565,19 +1566,38 @@ func (d *drawOps) boundsForTransformedRect(r f32.Rectangle, tr f32.Affine2D) (au
|
||||
|
||||
// establish the transform mapping from bounds rectangle to transformed corners
|
||||
var P1, P2, P3 f32.Point
|
||||
P1.X = (corners[1].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X)
|
||||
P1.Y = (corners[1].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y)
|
||||
P2.X = (corners[2].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X)
|
||||
P2.Y = (corners[2].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y)
|
||||
P3.X = (corners[3].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X)
|
||||
P3.Y = (corners[3].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y)
|
||||
P1.X = (corners[1].X - fBounds.Min.X) / (fBounds.Max.X - fBounds.Min.X)
|
||||
P1.Y = (corners[1].Y - fBounds.Min.Y) / (fBounds.Max.Y - fBounds.Min.Y)
|
||||
P2.X = (corners[2].X - fBounds.Min.X) / (fBounds.Max.X - fBounds.Min.X)
|
||||
P2.Y = (corners[2].Y - fBounds.Min.Y) / (fBounds.Max.Y - fBounds.Min.Y)
|
||||
P3.X = (corners[3].X - fBounds.Min.X) / (fBounds.Max.X - fBounds.Min.X)
|
||||
P3.Y = (corners[3].Y - fBounds.Min.Y) / (fBounds.Max.Y - fBounds.Min.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()
|
||||
|
||||
return
|
||||
return aux, bnd, ptr
|
||||
}
|
||||
|
||||
func isPureOffset(t f32.Affine2D) bool {
|
||||
a, b, _, d, e, _ := t.Elems()
|
||||
return a == 1 && b == 0 && d == 0 && e == 1
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -21,8 +21,10 @@ import (
|
||||
|
||||
var dumpImages = flag.Bool("saveimages", false, "save test images")
|
||||
|
||||
var clearCol = color.NRGBA{A: 0xff, R: 0xde, G: 0xad, B: 0xbe}
|
||||
var clearColExpect = f32color.NRGBAToRGBA(clearCol)
|
||||
var (
|
||||
clearCol = color.NRGBA{A: 0xff, R: 0xde, G: 0xad, B: 0xbe}
|
||||
clearColExpect = f32color.NRGBAToRGBA(clearCol)
|
||||
)
|
||||
|
||||
func TestFramebufferClear(t *testing.T) {
|
||||
b := newDriver(t)
|
||||
@@ -202,5 +204,5 @@ func saveImage(file string, img image.Image) error {
|
||||
if err := png.Encode(&buf, img); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(file, buf.Bytes(), 0666)
|
||||
return os.WriteFile(file, buf.Bytes(), 0o666)
|
||||
}
|
||||
|
||||
@@ -152,9 +152,7 @@ func newDirect3D11Device(api driver.Direct3D11) (driver.Device, error) {
|
||||
}
|
||||
|
||||
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 {
|
||||
switch t := target.(type) {
|
||||
case driver.Direct3D11RenderTarget:
|
||||
@@ -229,10 +227,7 @@ func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, min
|
||||
// Flags required by ID3D11DeviceContext::GenerateMips.
|
||||
bindFlags |= d3d11.BIND_SHADER_RESOURCE | d3d11.BIND_RENDER_TARGET
|
||||
miscFlags |= d3d11.RESOURCE_MISC_GENERATE_MIPS
|
||||
dim := width
|
||||
if height > dim {
|
||||
dim = height
|
||||
}
|
||||
dim := max(height, width)
|
||||
log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1
|
||||
nmipmaps = log2 + 1
|
||||
}
|
||||
@@ -802,7 +797,7 @@ func (t *Texture) ReadPixels(src image.Rectangle, pixels []byte, stride int) err
|
||||
mapSize := dstPitch * h
|
||||
data := sliceOf(resMap.PData, mapSize)
|
||||
width := w * 4
|
||||
for r := 0; r < h; r++ {
|
||||
for r := range h {
|
||||
pixels := pixels[r*srcPitch:]
|
||||
copy(pixels[:width], data[r*dstPitch:])
|
||||
}
|
||||
|
||||
@@ -96,8 +96,10 @@ type BlendFactor uint8
|
||||
|
||||
type Topology uint8
|
||||
|
||||
type TextureFilter uint8
|
||||
type TextureFormat uint8
|
||||
type (
|
||||
TextureFilter uint8
|
||||
TextureFormat uint8
|
||||
)
|
||||
|
||||
type BufferBinding uint8
|
||||
|
||||
@@ -217,7 +219,7 @@ func flipImageY(stride, height int, pixels []byte) {
|
||||
// Flip image in y-direction. OpenGL's origin is in the lower
|
||||
// left corner.
|
||||
row := make([]uint8, stride)
|
||||
for y := 0; y < height/2; y++ {
|
||||
for y := range height / 2 {
|
||||
y1 := height - y - 1
|
||||
dest := y1 * stride
|
||||
src := y * stride
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"image"
|
||||
"math/bits"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
@@ -717,10 +718,7 @@ func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, min
|
||||
if mipmap {
|
||||
nmipmaps := 1
|
||||
if mipmap {
|
||||
dim := width
|
||||
if height > dim {
|
||||
dim = height
|
||||
}
|
||||
dim := max(height, width)
|
||||
log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1
|
||||
nmipmaps = log2 + 1
|
||||
}
|
||||
@@ -1132,7 +1130,7 @@ func (b *Backend) setupVertexArrays() {
|
||||
enabled[inp.Location] = true
|
||||
b.glstate.vertexAttribPointer(b.funcs, buf.obj, inp.Location, l.Size, gltyp, false, p.layout.Stride, buf.offset+l.Offset)
|
||||
}
|
||||
for i := 0; i < max; i++ {
|
||||
for i := range max {
|
||||
b.glstate.setVertexAttribArray(b.funcs, i, enabled[i])
|
||||
}
|
||||
}
|
||||
@@ -1175,7 +1173,7 @@ func (t *texture) ReadPixels(src image.Rectangle, pixels []byte, stride int) err
|
||||
} else {
|
||||
tmp := make([]byte, w*h*4)
|
||||
t.backend.funcs.ReadPixels(src.Min.X, src.Min.Y, w, h, gl.RGBA, gl.UNSIGNED_BYTE, tmp)
|
||||
for y := 0; y < h; y++ {
|
||||
for y := range h {
|
||||
copy(pixels[y*stride:], tmp[y*w*4:])
|
||||
}
|
||||
}
|
||||
@@ -1357,12 +1355,7 @@ func alphaTripleFor(ver [2]int) textureTriple {
|
||||
}
|
||||
|
||||
func hasExtension(exts []string, ext string) bool {
|
||||
for _, e := range exts {
|
||||
if ext == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.Contains(exts, ext)
|
||||
}
|
||||
|
||||
func firstBufferType(typ driver.BufferBinding) gl.Enum {
|
||||
|
||||
@@ -66,8 +66,8 @@ func BenchmarkDrawUICached(b *testing.B) {
|
||||
defer w.Release()
|
||||
drawCore(gtx, th)
|
||||
w.Frame(gtx.Ops)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
||||
for b.Loop() {
|
||||
w.Frame(gtx.Ops)
|
||||
}
|
||||
finishBenchmark(b, w)
|
||||
@@ -83,12 +83,12 @@ func BenchmarkDrawUI(b *testing.B) {
|
||||
drawCore(gtx, th)
|
||||
w.Frame(gtx.Ops)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
||||
for i := 0; b.Loop(); i++ {
|
||||
resetOps(gtx)
|
||||
|
||||
off := float32(math.Mod(float64(i)/10, 10))
|
||||
t := op.Affine(f32.Affine2D{}.Offset(f32.Pt(off, off))).Push(gtx.Ops)
|
||||
t := op.Affine(f32.AffineId().Offset(f32.Pt(off, off))).Push(gtx.Ops)
|
||||
|
||||
drawCore(gtx, th)
|
||||
|
||||
@@ -105,12 +105,12 @@ func BenchmarkDrawUITransformed(b *testing.B) {
|
||||
drawCore(gtx, th)
|
||||
w.Frame(gtx.Ops)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
||||
for i := 0; b.Loop(); i++ {
|
||||
resetOps(gtx)
|
||||
|
||||
angle := float32(math.Mod(float64(i)/1000, 0.05))
|
||||
a := f32.Affine2D{}.Shear(f32.Point{}, angle, angle).Rotate(f32.Point{}, angle)
|
||||
a := f32.AffineId().Shear(f32.Point{}, angle, angle).Rotate(f32.Point{}, angle)
|
||||
t := op.Affine(a).Push(gtx.Ops)
|
||||
|
||||
drawCore(gtx, th)
|
||||
@@ -130,8 +130,8 @@ func Benchmark1000Circles(b *testing.B) {
|
||||
draw1000Circles(gtx)
|
||||
w.Frame(gtx.Ops)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
||||
for b.Loop() {
|
||||
resetOps(gtx)
|
||||
draw1000Circles(gtx)
|
||||
w.Frame(gtx.Ops)
|
||||
@@ -147,8 +147,8 @@ func Benchmark1000CirclesInstanced(b *testing.B) {
|
||||
draw1000CirclesInstanced(gtx)
|
||||
w.Frame(gtx.Ops)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
||||
for b.Loop() {
|
||||
resetOps(gtx)
|
||||
draw1000CirclesInstanced(gtx)
|
||||
w.Frame(gtx.Ops)
|
||||
@@ -158,9 +158,9 @@ func Benchmark1000CirclesInstanced(b *testing.B) {
|
||||
|
||||
func draw1000Circles(gtx layout.Context) {
|
||||
ops := gtx.Ops
|
||||
for x := 0; x < 100; x++ {
|
||||
for x := range 100 {
|
||||
op.Offset(image.Pt(x*10, 0)).Add(ops)
|
||||
for y := 0; y < 10; y++ {
|
||||
for y := range 10 {
|
||||
paint.FillShape(ops,
|
||||
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),
|
||||
@@ -179,9 +179,9 @@ func draw1000CirclesInstanced(gtx layout.Context) {
|
||||
cl.Pop()
|
||||
c := r.Stop()
|
||||
|
||||
for x := 0; x < 100; x++ {
|
||||
for x := range 100 {
|
||||
op.Offset(image.Pt(x*10, 0)).Add(ops)
|
||||
for y := 0; y < 10; y++ {
|
||||
for y := range 10 {
|
||||
paint.ColorOp{Color: color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120}}.Add(ops)
|
||||
c.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() {
|
||||
ops := &op1
|
||||
c := op.Record(ops)
|
||||
for x := 0; x < 9; x++ {
|
||||
for x := range 9 {
|
||||
op.Offset(image.Pt(x*50, 0)).Add(ops)
|
||||
for y := 0; y < 9; y++ {
|
||||
for y := range 9 {
|
||||
paint.FillShape(ops,
|
||||
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),
|
||||
@@ -233,8 +233,8 @@ func drawShapeInstances(gtx layout.Context, th *material.Theme) chan op.CallOp {
|
||||
|
||||
squares.Add(ops)
|
||||
rad := float32(0)
|
||||
for x := 0; x < 20; x++ {
|
||||
for y := 0; y < 20; y++ {
|
||||
for x := range 20 {
|
||||
for y := range 20 {
|
||||
t := op.Offset(image.Pt(x*50+25, y*50+25)).Push(ops)
|
||||
c.Add(ops)
|
||||
t.Pop()
|
||||
@@ -253,7 +253,7 @@ func drawText(gtx layout.Context, th *material.Theme) chan op.CallOp {
|
||||
c := op.Record(ops)
|
||||
|
||||
txt := material.H6(th, "")
|
||||
for x := 0; x < 40; x++ {
|
||||
for x := range 40 {
|
||||
txt.Text = textRows[x]
|
||||
t := op.Offset(image.Pt(0, 24*x)).Push(ops)
|
||||
gtx.Ops = ops
|
||||
|
||||
@@ -40,6 +40,25 @@ 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) {
|
||||
run(t, func(o *op.Ops) {
|
||||
const r = 10
|
||||
@@ -132,8 +151,7 @@ func TestTexturedStrokeClipped(t *testing.T) {
|
||||
defer clip.RRect{Rect: image.Rect(-30, -30, 60, 60)}.Push(o).Pop()
|
||||
defer op.Offset(image.Pt(-10, -10)).Push(o).Pop()
|
||||
paint.PaintOp{}.Add(o)
|
||||
}, func(r result) {
|
||||
})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func TestTexturedStroke(t *testing.T) {
|
||||
@@ -146,8 +164,7 @@ func TestTexturedStroke(t *testing.T) {
|
||||
}.Op().Push(o).Pop()
|
||||
defer op.Offset(image.Pt(-10, -10)).Push(o).Pop()
|
||||
paint.PaintOp{}.Add(o)
|
||||
}, func(r result) {
|
||||
})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func TestPaintClippedTexture(t *testing.T) {
|
||||
@@ -178,7 +195,6 @@ func TestStrokedPathZeroWidth(t *testing.T) {
|
||||
paint.Fill(o, red)
|
||||
cl.Pop()
|
||||
}
|
||||
|
||||
}, func(r result) {
|
||||
r.expect(0, 0, transparent)
|
||||
r.expect(10, 50, colornames.Black)
|
||||
@@ -252,8 +268,7 @@ func TestPathReuse(t *testing.T) {
|
||||
stroke := clip.Stroke{Path: spec, Width: 3}.Op().Push(o)
|
||||
paint.Fill(o, color.NRGBA{B: 0xFF, A: 0xFF})
|
||||
stroke.Pop()
|
||||
}, func(r result) {
|
||||
})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func TestPathInterleave(t *testing.T) {
|
||||
@@ -290,6 +305,22 @@ func TestStrokedRect(t *testing.T) {
|
||||
Width: 5,
|
||||
}.Op(),
|
||||
)
|
||||
}, func(r result) {
|
||||
})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
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.
|
After Width: | Height: | Size: 787 B |
Binary file not shown.
|
Before Width: | Height: | Size: 334 B After Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 435 B |
@@ -24,7 +24,6 @@ func TestTransformMacro(t *testing.T) {
|
||||
c := constSqPath()
|
||||
|
||||
run(t, func(o *op.Ops) {
|
||||
|
||||
// render the first Stacked item
|
||||
m1 := op.Record(o)
|
||||
dr := image.Rect(0, 0, 128, 50)
|
||||
@@ -88,10 +87,10 @@ func TestNoClipFromPaint(t *testing.T) {
|
||||
// ensure that a paint operation does not pollute the state
|
||||
// by leaving any clip paths in place.
|
||||
run(t, func(o *op.Ops) {
|
||||
a := f32.Affine2D{}.Rotate(f32.Pt(20, 20), math.Pi/4)
|
||||
a := f32.AffineId().Rotate(f32.Pt(20, 20), math.Pi/4)
|
||||
defer op.Affine(a).Push(o).Pop()
|
||||
paint.FillShape(o, red, clip.Rect(image.Rect(10, 10, 30, 30)).Op())
|
||||
a = f32.Affine2D{}.Rotate(f32.Pt(20, 20), -math.Pi/4)
|
||||
a = f32.AffineId().Rotate(f32.Pt(20, 20), -math.Pi/4)
|
||||
defer op.Affine(a).Push(o).Pop()
|
||||
|
||||
paint.FillShape(o, black, clip.Rect(image.Rect(0, 0, 50, 50)).Op())
|
||||
@@ -110,7 +109,7 @@ func TestDeferredPaint(t *testing.T) {
|
||||
paint.PaintOp{}.Add(o)
|
||||
cl.Pop()
|
||||
|
||||
t := op.Affine(f32.Affine2D{}.Offset(f32.Pt(20, 20))).Push(o)
|
||||
t := op.Affine(f32.AffineId().Offset(f32.Pt(20, 20))).Push(o)
|
||||
m := op.Record(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)
|
||||
@@ -120,12 +119,11 @@ func TestDeferredPaint(t *testing.T) {
|
||||
op.Defer(o, paintMacro)
|
||||
t.Pop()
|
||||
|
||||
defer op.Affine(f32.Affine2D{}.Offset(f32.Pt(10, 10))).Push(o).Pop()
|
||||
defer op.Affine(f32.AffineId().Offset(f32.Pt(10, 10))).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.PaintOp{}.Add(o)
|
||||
}, func(r result) {
|
||||
})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func constSqPath() clip.Op {
|
||||
@@ -143,8 +141,10 @@ func constSqPath() clip.Op {
|
||||
|
||||
func constSqCirc() clip.Op {
|
||||
innerOps := new(op.Ops)
|
||||
return clip.RRect{Rect: image.Rect(0, 0, 40, 40),
|
||||
NW: 20, NE: 20, SW: 20, SE: 20}.Op(innerOps)
|
||||
return clip.RRect{
|
||||
Rect: image.Rect(0, 0, 40, 40),
|
||||
NW: 20, NE: 20, SW: 20, SE: 20,
|
||||
}.Op(innerOps)
|
||||
}
|
||||
|
||||
func drawChild(ops *op.Ops, text clip.Op) op.CallOp {
|
||||
@@ -260,7 +260,7 @@ func TestLinearGradient(t *testing.T) {
|
||||
Color2: g.To,
|
||||
}.Add(ops)
|
||||
cl := clip.RRect{Rect: gr.Round()}.Push(ops)
|
||||
t1 := op.Affine(f32.Affine2D{}.Offset(pixelAligned.Min)).Push(ops)
|
||||
t1 := op.Affine(f32.AffineId().Offset(pixelAligned.Min)).Push(ops)
|
||||
t2 := scale(pixelAligned.Dx()/128, 1).Push(ops)
|
||||
paint.PaintOp{}.Add(ops)
|
||||
t2.Pop()
|
||||
@@ -323,7 +323,7 @@ func TestLinearGradientAngled(t *testing.T) {
|
||||
cl = clip.Rect(image.Rect(0, 64, 64, 128)).Push(ops)
|
||||
paint.PaintOp{}.Add(ops)
|
||||
cl.Pop()
|
||||
}, func(r result) {})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func TestZeroImage(t *testing.T) {
|
||||
@@ -363,7 +363,7 @@ func TestImageRGBA_ScaleLinear(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
w := newWindow(t, 128, 128)
|
||||
defer clip.Rect{Max: image.Pt(128, 128)}.Push(o).Pop()
|
||||
op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(64, 64))).Add(o)
|
||||
op.Affine(f32.AffineId().Scale(f32.Point{}, f32.Pt(64, 64))).Add(o)
|
||||
|
||||
im := image.NewRGBA(image.Rect(0, 0, 2, 2))
|
||||
im.Set(0, 0, colornames.Red)
|
||||
@@ -397,7 +397,7 @@ func TestImageRGBA_ScaleLinear(t *testing.T) {
|
||||
func TestImageRGBA_ScaleNearest(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
w := newWindow(t, 128, 128)
|
||||
op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(64, 64))).Add(o)
|
||||
op.Affine(f32.AffineId().Scale(f32.Point{}, f32.Pt(64, 64))).Add(o)
|
||||
|
||||
im := image.NewRGBA(image.Rect(0, 0, 2, 2))
|
||||
im.Set(0, 0, colornames.Red)
|
||||
@@ -483,17 +483,16 @@ func TestGapsInPath(t *testing.T) {
|
||||
func TestOpacity(t *testing.T) {
|
||||
run(t, func(ops *op.Ops) {
|
||||
opc1 := paint.PushOpacity(ops, .3)
|
||||
// Fill screen to exercize the glClear optimization.
|
||||
// Fill screen to exercise the glClear optimization.
|
||||
paint.FillShape(ops, color.NRGBA{R: 255, A: 255}, clip.Rect{Max: image.Pt(1024, 1024)}.Op())
|
||||
opc2 := paint.PushOpacity(ops, .6)
|
||||
paint.FillShape(ops, color.NRGBA{G: 255, A: 255}, clip.Rect{Min: image.Pt(20, 10), Max: image.Pt(64, 128)}.Op())
|
||||
opc2.Pop()
|
||||
opc1.Pop()
|
||||
opc3 := paint.PushOpacity(ops, .6)
|
||||
paint.FillShape(ops, color.NRGBA{G: 255, A: 255}, clip.Rect{Min: image.Pt(50+20, 10), Max: image.Pt(50+64, 128)}.Op())
|
||||
paint.FillShape(ops, color.NRGBA{B: 255, A: 255}, clip.Ellipse(image.Rectangle{Min: image.Pt(20+20, 10), Max: image.Pt(50+64, 128)}).Op(ops))
|
||||
opc3.Pop()
|
||||
}, func(r result) {
|
||||
})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
// lerp calculates linear interpolation with color b and p.
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestPaintOffset(t *testing.T) {
|
||||
|
||||
func TestPaintRotate(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
a := f32.Affine2D{}.Rotate(f32.Pt(40, 40), -math.Pi/8)
|
||||
a := f32.AffineId().Rotate(f32.Pt(40, 40), -math.Pi/8)
|
||||
defer op.Affine(a).Push(o).Pop()
|
||||
paint.FillShape(o, red, clip.Rect(image.Rect(20, 20, 60, 60)).Op())
|
||||
}, func(r result) {
|
||||
@@ -42,7 +42,7 @@ func TestPaintRotate(t *testing.T) {
|
||||
|
||||
func TestPaintShear(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
a := f32.Affine2D{}.Shear(f32.Point{}, math.Pi/4, 0)
|
||||
a := f32.AffineId().Shear(f32.Point{}, math.Pi/4, 0)
|
||||
defer op.Affine(a).Push(o).Pop()
|
||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 40, 40)).Op())
|
||||
}, func(r result) {
|
||||
@@ -79,7 +79,7 @@ func TestClipOffset(t *testing.T) {
|
||||
|
||||
func TestClipScale(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
a := f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(2, 2)).Offset(f32.Pt(10, 10))
|
||||
a := f32.AffineId().Scale(f32.Point{}, f32.Pt(2, 2)).Offset(f32.Pt(10, 10))
|
||||
defer op.Affine(a).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())
|
||||
@@ -93,7 +93,7 @@ func TestClipScale(t *testing.T) {
|
||||
|
||||
func TestClipRotate(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
defer op.Affine(f32.Affine2D{}.Rotate(f32.Pt(40, 40), -math.Pi/4)).Push(o).Pop()
|
||||
defer op.Affine(f32.AffineId().Rotate(f32.Pt(40, 40), -math.Pi/4)).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())
|
||||
}, func(r result) {
|
||||
@@ -121,7 +121,7 @@ func TestOffsetScaleTexture(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
defer op.Offset(image.Pt(15, 15)).Push(o).Pop()
|
||||
squares.Add(o)
|
||||
defer op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(2, 1))).Push(o).Pop()
|
||||
defer op.Affine(f32.AffineId().Scale(f32.Point{}, f32.Pt(2, 1))).Push(o).Pop()
|
||||
defer scale(50.0/512, 50.0/512).Push(o).Pop()
|
||||
paint.PaintOp{}.Add(o)
|
||||
}, func(r result) {
|
||||
@@ -133,7 +133,7 @@ func TestOffsetScaleTexture(t *testing.T) {
|
||||
func TestRotateTexture(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
squares.Add(o)
|
||||
a := f32.Affine2D{}.Offset(f32.Pt(30, 30)).Rotate(f32.Pt(40, 40), math.Pi/4)
|
||||
a := f32.AffineId().Offset(f32.Pt(30, 30)).Rotate(f32.Pt(40, 40), math.Pi/4)
|
||||
defer op.Affine(a).Push(o).Pop()
|
||||
defer scale(20.0/512, 20.0/512).Push(o).Pop()
|
||||
paint.PaintOp{}.Add(o)
|
||||
@@ -146,10 +146,10 @@ func TestRotateTexture(t *testing.T) {
|
||||
func TestRotateClipTexture(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
squares.Add(o)
|
||||
a := f32.Affine2D{}.Rotate(f32.Pt(40, 40), math.Pi/8)
|
||||
a := f32.AffineId().Rotate(f32.Pt(40, 40), math.Pi/8)
|
||||
defer op.Affine(a).Push(o).Pop()
|
||||
defer clip.RRect{Rect: image.Rect(30, 30, 50, 50)}.Push(o).Pop()
|
||||
defer op.Affine(f32.Affine2D{}.Offset(f32.Pt(10, 10))).Push(o).Pop()
|
||||
defer op.Affine(f32.AffineId().Offset(f32.Pt(10, 10))).Push(o).Pop()
|
||||
defer scale(60.0/512, 60.0/512).Push(o).Pop()
|
||||
paint.PaintOp{}.Add(o)
|
||||
}, 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()
|
||||
|
||||
a := f32.Affine2D{}.Shear(f32.Point{}, math.Pi/4, 0)
|
||||
a := f32.AffineId().Shear(f32.Point{}, math.Pi/4, 0)
|
||||
defer op.Affine(a).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) {
|
||||
// check the ordering of operations bot in affine and in gpu stack.
|
||||
run(t, func(o *op.Ops) {
|
||||
a := f32.Affine2D{}.Offset(f32.Pt(64, 64))
|
||||
a := f32.AffineId().Offset(f32.Pt(64, 64))
|
||||
defer op.Affine(a).Push(o).Pop()
|
||||
|
||||
b := f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(8, 8))
|
||||
b := f32.AffineId().Scale(f32.Point{}, f32.Pt(8, 8))
|
||||
defer op.Affine(b).Push(o).Pop()
|
||||
|
||||
c := f32.Affine2D{}.Offset(f32.Pt(-10, -10)).Scale(f32.Point{}, f32.Pt(0.5, 0.5))
|
||||
c := f32.AffineId().Offset(f32.Pt(-10, -10)).Scale(f32.Point{}, f32.Pt(0.5, 0.5))
|
||||
defer op.Affine(c).Push(o).Pop()
|
||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 20, 20)).Op())
|
||||
}, func(r result) {
|
||||
|
||||
@@ -49,8 +49,8 @@ func buildSquares(size int) paint.ImageOp {
|
||||
sub := size / 4
|
||||
im := image.NewNRGBA(image.Rect(0, 0, size, size))
|
||||
c1, c2 := image.NewUniform(colornames.Green), image.NewUniform(colornames.Blue)
|
||||
for r := 0; r < 4; r++ {
|
||||
for c := 0; c < 4; c++ {
|
||||
for r := range 4 {
|
||||
for c := range 4 {
|
||||
c1, c2 = c2, c1
|
||||
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 err error
|
||||
ops := new(op.Ops)
|
||||
for i := 0; i < 3; i++ {
|
||||
for i := range 3 {
|
||||
ops.Reset()
|
||||
img, err = drawImage(t, 128, ops, f)
|
||||
if err != nil {
|
||||
@@ -90,7 +90,9 @@ func run(t *testing.T, f func(o *op.Ops), c func(r result)) {
|
||||
name := fmt.Sprintf("%s-%d-bad.png", t.Name(), i)
|
||||
saveImage(t, name, img)
|
||||
}
|
||||
c(result{t: t, img: img})
|
||||
if c != nil {
|
||||
c(result{t: t, img: img})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +153,7 @@ func verifyRef(t *testing.T, img *image.RGBA, frame int) (ok bool) {
|
||||
}
|
||||
path = filepath.Join("refs", path+".png")
|
||||
if *dumpImages {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0766); err != nil {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o766); err != nil {
|
||||
if !os.IsExist(err) {
|
||||
t.Error(err)
|
||||
return
|
||||
@@ -285,7 +287,7 @@ func saveImage(t testing.TB, file string, img *image.RGBA) {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if err := os.WriteFile(file, buf.Bytes(), 0666); err != nil {
|
||||
if err := os.WriteFile(file, buf.Bytes(), 0o666); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
@@ -300,5 +302,5 @@ func newWindow(t testing.TB, width, height int) *headless.Window {
|
||||
}
|
||||
|
||||
func scale(sx, sy float32) op.TransformOp {
|
||||
return op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(sx, sy)))
|
||||
return op.Affine(f32.AffineId().Scale(f32.Point{}, f32.Pt(sx, sy)))
|
||||
}
|
||||
|
||||
@@ -862,8 +862,8 @@ func (b *Backend) BindUniforms(buffer driver.Buffer) {
|
||||
buf := buffer.(*Buffer)
|
||||
cmdBuf := b.currentCmdBuf()
|
||||
for _, s := range b.pipe.pushRanges {
|
||||
off := s.Offset()
|
||||
vk.CmdPushConstants(cmdBuf, b.pipe.desc.layout, s.StageFlags(), off, buf.store[off:off+s.Size()])
|
||||
off := vk.PushConstantRangeOffset(s)
|
||||
vk.CmdPushConstants(cmdBuf, b.pipe.desc.layout, vk.PushConstantRangeStageFlags(s), off, buf.store[off:off+vk.PushConstantRangeSize(s)])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -10,10 +10,10 @@ import (
|
||||
func BenchmarkPacker(b *testing.B) {
|
||||
var p packer
|
||||
p.maxDims = image.Point{X: 4096, Y: 4096}
|
||||
for i := 0; i < b.N; i++ {
|
||||
for i := 0; b.Loop(); i++ {
|
||||
p.clear()
|
||||
p.newPage()
|
||||
for k := 0; k < 500; k++ {
|
||||
for k := range 500 {
|
||||
_, ok := p.tryAdd(xy(k))
|
||||
if !ok {
|
||||
b.Fatal("add failed", i, k, xy(k))
|
||||
|
||||
+14
-8
@@ -30,7 +30,7 @@ type pather struct {
|
||||
|
||||
type coverer struct {
|
||||
ctx driver.Device
|
||||
pipelines [3]*pipeline
|
||||
pipelines [2][3]*pipeline
|
||||
texUniforms *coverTexUniforms
|
||||
colUniforms *coverColUniforms
|
||||
linearGradientUniforms *coverLinearGradientUniforms
|
||||
@@ -150,7 +150,7 @@ func newCoverer(ctx driver.Device) *coverer {
|
||||
c.texUniforms = new(coverTexUniforms)
|
||||
c.linearGradientUniforms = new(coverLinearGradientUniforms)
|
||||
pipelines, err := createColorPrograms(ctx, gio.Shader_cover_vert, gio.Shader_cover_frag,
|
||||
[3]interface{}{c.colUniforms, c.linearGradientUniforms, c.texUniforms},
|
||||
[3]any{c.colUniforms, c.linearGradientUniforms, c.texUniforms},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -162,7 +162,7 @@ func newCoverer(ctx driver.Device) *coverer {
|
||||
func newStenciler(ctx driver.Device) *stenciler {
|
||||
// Allocate a suitably large index buffer for drawing paths.
|
||||
indices := make([]uint16, pathBatchSize*6)
|
||||
for i := 0; i < pathBatchSize; i++ {
|
||||
for i := range pathBatchSize {
|
||||
i := uint16(i)
|
||||
indices[i*6+0] = i*4 + 0
|
||||
indices[i*6+1] = i*4 + 1
|
||||
@@ -309,7 +309,9 @@ func (p *pather) release() {
|
||||
|
||||
func (c *coverer) release() {
|
||||
for _, p := range c.pipelines {
|
||||
p.Release()
|
||||
for _, p := range p {
|
||||
p.Release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,7 +334,7 @@ func (p *pather) begin(sizes []image.Point) {
|
||||
p.stenciler.begin(sizes)
|
||||
}
|
||||
|
||||
func (p *pather) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) {
|
||||
func (p *pather) stencilPath(bounds image.Rectangle, offset image.Point, uv image.Point, data pathData) {
|
||||
p.stenciler.stencilPath(bounds, offset, uv, data)
|
||||
}
|
||||
|
||||
@@ -351,14 +353,14 @@ func (s *stenciler) begin(sizes []image.Point) {
|
||||
s.fbos.resize(s.ctx, driver.TextureFormatFloat, sizes)
|
||||
}
|
||||
|
||||
func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) {
|
||||
func (s *stenciler) stencilPath(bounds image.Rectangle, offset image.Point, uv image.Point, data pathData) {
|
||||
s.ctx.Viewport(uv.X, uv.Y, bounds.Dx(), bounds.Dy())
|
||||
// Transform UI coordinates to OpenGL coordinates.
|
||||
texSize := f32.Point{X: float32(bounds.Dx()), Y: float32(bounds.Dy())}
|
||||
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}
|
||||
s.pipeline.uniforms.transform = [4]float32{scale.X, scale.Y, orig.X, orig.Y}
|
||||
s.pipeline.uniforms.pathOffset = [2]float32{offset.X, offset.Y}
|
||||
s.pipeline.uniforms.pathOffset = [2]float32{float32(offset.X), float32(offset.Y)}
|
||||
s.pipeline.pipeline.UploadUniforms(s.ctx)
|
||||
// Draw in batches that fit in uint16 indices.
|
||||
start := 0
|
||||
@@ -405,7 +407,11 @@ func (c *coverer) cover(mat materialType, isFBO bool, col f32color.RGBA, col1, c
|
||||
}
|
||||
uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
|
||||
uniforms.uvCoverTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y}
|
||||
c.pipelines[mat].UploadUniforms(c.ctx)
|
||||
fboIdx := 0
|
||||
if isFBO {
|
||||
fboIdx = 1
|
||||
}
|
||||
c.pipelines[fboIdx][mat].UploadUniforms(c.ctx)
|
||||
c.ctx.DrawArrays(0, 4)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
// Struct returns a byte slice view of a struct.
|
||||
func Struct(s interface{}) []byte {
|
||||
func Struct(s any) []byte {
|
||||
v := reflect.ValueOf(s)
|
||||
sz := int(v.Elem().Type().Size())
|
||||
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.
|
||||
func Slice(s interface{}) []byte {
|
||||
func Slice(s any) []byte {
|
||||
v := reflect.ValueOf(s)
|
||||
first := v.Index(0)
|
||||
sz := int(first.Type().Size())
|
||||
|
||||
+6
-13
@@ -9,16 +9,16 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"gioui.org/gpu"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
disp _EGLDisplay
|
||||
eglCtx *eglContext
|
||||
eglSurf _EGLSurface
|
||||
width, height int
|
||||
disp _EGLDisplay
|
||||
eglCtx *eglContext
|
||||
eglSurf _EGLSurface
|
||||
}
|
||||
|
||||
type eglContext struct {
|
||||
@@ -121,11 +121,9 @@ func (c *Context) VisualID() int {
|
||||
return c.eglCtx.visualID
|
||||
}
|
||||
|
||||
func (c *Context) CreateSurface(win NativeWindowType, width, height int) error {
|
||||
func (c *Context) CreateSurface(win NativeWindowType) error {
|
||||
eglSurf, err := createSurface(c.disp, c.eglCtx, win)
|
||||
c.eglSurf = eglSurf
|
||||
c.width = width
|
||||
c.height = height
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -157,12 +155,7 @@ func (c *Context) EnableVSync(enable bool) {
|
||||
}
|
||||
|
||||
func hasExtension(exts []string, ext string) bool {
|
||||
for _, e := range exts {
|
||||
if ext == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.Contains(exts, ext)
|
||||
}
|
||||
|
||||
func createContext(disp _EGLDisplay) (*eglContext, error) {
|
||||
|
||||
+49
-27
@@ -9,8 +9,6 @@ import (
|
||||
"unsafe"
|
||||
|
||||
syscall "golang.org/x/sys/windows"
|
||||
|
||||
"gioui.org/internal/gl"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -24,23 +22,23 @@ type (
|
||||
)
|
||||
|
||||
var (
|
||||
libEGL = syscall.NewLazyDLL("libEGL.dll")
|
||||
_eglChooseConfig = libEGL.NewProc("eglChooseConfig")
|
||||
_eglCreateContext = libEGL.NewProc("eglCreateContext")
|
||||
_eglCreateWindowSurface = libEGL.NewProc("eglCreateWindowSurface")
|
||||
_eglDestroyContext = libEGL.NewProc("eglDestroyContext")
|
||||
_eglDestroySurface = libEGL.NewProc("eglDestroySurface")
|
||||
_eglGetConfigAttrib = libEGL.NewProc("eglGetConfigAttrib")
|
||||
_eglGetDisplay = libEGL.NewProc("eglGetDisplay")
|
||||
_eglGetError = libEGL.NewProc("eglGetError")
|
||||
_eglInitialize = libEGL.NewProc("eglInitialize")
|
||||
_eglMakeCurrent = libEGL.NewProc("eglMakeCurrent")
|
||||
_eglReleaseThread = libEGL.NewProc("eglReleaseThread")
|
||||
_eglSwapInterval = libEGL.NewProc("eglSwapInterval")
|
||||
_eglSwapBuffers = libEGL.NewProc("eglSwapBuffers")
|
||||
_eglTerminate = libEGL.NewProc("eglTerminate")
|
||||
_eglQueryString = libEGL.NewProc("eglQueryString")
|
||||
_eglWaitClient = libEGL.NewProc("eglWaitClient")
|
||||
libEGL = syscall.DLL{}
|
||||
_eglChooseConfig *syscall.Proc
|
||||
_eglCreateContext *syscall.Proc
|
||||
_eglCreateWindowSurface *syscall.Proc
|
||||
_eglDestroyContext *syscall.Proc
|
||||
_eglDestroySurface *syscall.Proc
|
||||
_eglGetConfigAttrib *syscall.Proc
|
||||
_eglGetDisplay *syscall.Proc
|
||||
_eglGetError *syscall.Proc
|
||||
_eglInitialize *syscall.Proc
|
||||
_eglMakeCurrent *syscall.Proc
|
||||
_eglReleaseThread *syscall.Proc
|
||||
_eglSwapInterval *syscall.Proc
|
||||
_eglSwapBuffers *syscall.Proc
|
||||
_eglTerminate *syscall.Proc
|
||||
_eglQueryString *syscall.Proc
|
||||
_eglWaitClient *syscall.Proc
|
||||
)
|
||||
|
||||
var loadOnce sync.Once
|
||||
@@ -54,21 +52,45 @@ func loadEGL() error {
|
||||
}
|
||||
|
||||
func loadDLLs() error {
|
||||
if err := loadDLL(libEGL, "libEGL.dll"); err != nil {
|
||||
if err := loadDLL(&libEGL, "libEGL.dll"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := loadDLL(gl.LibGLESv2, "libGLESv2.dll"); err != nil {
|
||||
return err
|
||||
|
||||
procs := map[string]**syscall.Proc{
|
||||
"eglChooseConfig": &_eglChooseConfig,
|
||||
"eglCreateContext": &_eglCreateContext,
|
||||
"eglCreateWindowSurface": &_eglCreateWindowSurface,
|
||||
"eglDestroyContext": &_eglDestroyContext,
|
||||
"eglDestroySurface": &_eglDestroySurface,
|
||||
"eglGetConfigAttrib": &_eglGetConfigAttrib,
|
||||
"eglGetDisplay": &_eglGetDisplay,
|
||||
"eglGetError": &_eglGetError,
|
||||
"eglInitialize": &_eglInitialize,
|
||||
"eglMakeCurrent": &_eglMakeCurrent,
|
||||
"eglReleaseThread": &_eglReleaseThread,
|
||||
"eglSwapInterval": &_eglSwapInterval,
|
||||
"eglSwapBuffers": &_eglSwapBuffers,
|
||||
"eglTerminate": &_eglTerminate,
|
||||
"eglQueryString": &_eglQueryString,
|
||||
"eglWaitClient": &_eglWaitClient,
|
||||
}
|
||||
// d3dcompiler_47.dll is needed internally for shader compilation to function.
|
||||
return loadDLL(syscall.NewLazyDLL("d3dcompiler_47.dll"), "d3dcompiler_47.dll")
|
||||
for name, proc := range procs {
|
||||
p, err := libEGL.FindProc(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to locate %s in %s: %w", name, libEGL.Name, err)
|
||||
}
|
||||
*proc = p
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadDLL(dll *syscall.LazyDLL, name string) error {
|
||||
err := dll.Load()
|
||||
func loadDLL(dll *syscall.DLL, name string) error {
|
||||
handle, err := syscall.LoadLibraryEx(name, 0, syscall.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)
|
||||
if err != nil {
|
||||
return fmt.Errorf("egl: failed to load %s: %v", name, err)
|
||||
}
|
||||
dll.Handle = handle
|
||||
dll.Name = name
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -164,6 +186,6 @@ func eglWaitClient() bool {
|
||||
|
||||
// issue34474KeepAlive calls runtime.KeepAlive as a
|
||||
// workaround for golang.org/issue/34474.
|
||||
func issue34474KeepAlive(v interface{}) {
|
||||
func issue34474KeepAlive(v any) {
|
||||
runtime.KeepAlive(v)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ type Affine2D = f32.Affine2D
|
||||
|
||||
var NewAffine2D = f32.NewAffine2D
|
||||
|
||||
var AffineId = f32.AffineId
|
||||
|
||||
// A Rectangle contains the points (X, Y) where Min.X <= X < Max.X,
|
||||
// Min.Y <= Y < Max.Y.
|
||||
type Rectangle struct {
|
||||
|
||||
@@ -16,7 +16,7 @@ func main() {
|
||||
flag.Parse()
|
||||
|
||||
var b bytes.Buffer
|
||||
printf := func(content string, args ...interface{}) {
|
||||
printf := func(content string, args ...any) {
|
||||
fmt.Fprintf(&b, content, args...)
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func main() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(*out, data, 0755)
|
||||
err = os.WriteFile(*out, data, 0o755)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -41,17 +41,17 @@ var sink RGBA
|
||||
|
||||
func BenchmarkLinearFromSRGB(b *testing.B) {
|
||||
b.Run("opaque", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for i := 0; b.Loop(); i++ {
|
||||
sink = LinearFromSRGB(color.NRGBA{R: byte(i), G: byte(i >> 8), B: byte(i >> 16), A: 0xFF})
|
||||
}
|
||||
})
|
||||
b.Run("translucent", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for i := 0; b.Loop(); i++ {
|
||||
sink = LinearFromSRGB(color.NRGBA{R: byte(i), G: byte(i >> 8), B: byte(i >> 16), A: 0x50})
|
||||
}
|
||||
})
|
||||
b.Run("transparent", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for i := 0; b.Loop(); i++ {
|
||||
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)
|
||||
t := first.t
|
||||
// Walk backwards collecting samples.
|
||||
for i := 0; i < len(e.samples); i++ {
|
||||
for i := range e.samples {
|
||||
p := e.get(-i)
|
||||
age := first.t - p.t
|
||||
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
|
||||
Q := newMatrix(A.rows, A.cols) // Column-major.
|
||||
Rt := newMatrix(A.rows, A.rows) // R transposed, row-major.
|
||||
for i := 0; i < Q.rows; i++ {
|
||||
for i := range Q.rows {
|
||||
// Copy A column.
|
||||
for j := 0; j < Q.cols; j++ {
|
||||
for j := range Q.cols {
|
||||
Q.set(i, j, A.get(i, j))
|
||||
}
|
||||
// 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:
|
||||
//
|
||||
// proje a = <e, a>/<e, e> e = <e, a> e
|
||||
for j := 0; j < i; j++ {
|
||||
for j := range i {
|
||||
d := dot(Q.col(j), Q.col(i))
|
||||
for k := 0; k < Q.cols; k++ {
|
||||
for k := range Q.cols {
|
||||
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
|
||||
}
|
||||
invNorm := 1 / n
|
||||
for j := 0; j < Q.cols; j++ {
|
||||
for j := range Q.cols {
|
||||
Q.set(i, j, Q.get(i, j)*invNorm)
|
||||
}
|
||||
// Update Rt.
|
||||
@@ -261,8 +261,8 @@ func (m *matrix) approxEqual(m2 *matrix) bool {
|
||||
return false
|
||||
}
|
||||
const epsilon = 0.00001
|
||||
for row := 0; row < m.rows; row++ {
|
||||
for col := 0; col < m.cols; col++ {
|
||||
for row := range m.rows {
|
||||
for col := range m.cols {
|
||||
d := m2.get(row, col) - m.get(row, col)
|
||||
if d < -epsilon || d > epsilon {
|
||||
return false
|
||||
@@ -278,8 +278,8 @@ func (m *matrix) transpose() *matrix {
|
||||
cols: m.rows,
|
||||
data: make([]float32, len(m.data)),
|
||||
}
|
||||
for i := 0; i < m.rows; i++ {
|
||||
for j := 0; j < m.cols; j++ {
|
||||
for i := range m.rows {
|
||||
for j := range m.cols {
|
||||
t.set(j, i, m.get(i, j))
|
||||
}
|
||||
}
|
||||
@@ -295,10 +295,10 @@ func (m *matrix) mul(m2 *matrix) *matrix {
|
||||
cols: m2.cols,
|
||||
data: make([]float32, m.rows*m2.cols),
|
||||
}
|
||||
for i := 0; i < mm.rows; i++ {
|
||||
for j := 0; j < mm.cols; j++ {
|
||||
for i := range mm.rows {
|
||||
for j := range mm.cols {
|
||||
var v float32
|
||||
for k := 0; k < m.rows; k++ {
|
||||
for k := range m.rows {
|
||||
v += m.get(k, j) * m2.get(i, k)
|
||||
}
|
||||
mm.set(i, j, v)
|
||||
@@ -309,8 +309,8 @@ func (m *matrix) mul(m2 *matrix) *matrix {
|
||||
|
||||
func (m *matrix) String() string {
|
||||
var b strings.Builder
|
||||
for i := 0; i < m.rows; i++ {
|
||||
for j := 0; j < m.cols; j++ {
|
||||
for i := range m.rows {
|
||||
for j := range m.cols {
|
||||
v := m.get(i, j)
|
||||
b.WriteString(strconv.FormatFloat(float64(v), 'g', -1, 32))
|
||||
b.WriteString(", ")
|
||||
|
||||
@@ -247,9 +247,11 @@ func (f *Functions) getExtension(name string) js.Value {
|
||||
func (f *Functions) ActiveTexture(t Enum) {
|
||||
f._activeTexture.Invoke(int(t))
|
||||
}
|
||||
|
||||
func (f *Functions) AttachShader(p Program, s Shader) {
|
||||
f._attachShader.Invoke(js.Value(p), js.Value(s))
|
||||
}
|
||||
|
||||
func (f *Functions) BeginQuery(target Enum, query Query) {
|
||||
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
||||
f._beginQuery.Invoke(int(target), js.Value(query))
|
||||
@@ -257,36 +259,47 @@ func (f *Functions) BeginQuery(target Enum, query Query) {
|
||||
f.EXT_disjoint_timer_query.Call("beginQueryEXT", int(target), js.Value(query))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Functions) BindAttribLocation(p Program, a Attrib, name string) {
|
||||
f._bindAttribLocation.Invoke(js.Value(p), int(a), name)
|
||||
}
|
||||
|
||||
func (f *Functions) BindBuffer(target Enum, b Buffer) {
|
||||
f._bindBuffer.Invoke(int(target), js.Value(b))
|
||||
}
|
||||
|
||||
func (f *Functions) BindBufferBase(target Enum, index int, b Buffer) {
|
||||
f._bindBufferBase.Invoke(int(target), index, js.Value(b))
|
||||
}
|
||||
|
||||
func (f *Functions) BindFramebuffer(target Enum, fb Framebuffer) {
|
||||
f._bindFramebuffer.Invoke(int(target), js.Value(fb))
|
||||
}
|
||||
|
||||
func (f *Functions) BindRenderbuffer(target Enum, rb Renderbuffer) {
|
||||
f._bindRenderbuffer.Invoke(int(target), js.Value(rb))
|
||||
}
|
||||
|
||||
func (f *Functions) BindTexture(target Enum, t Texture) {
|
||||
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) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (f *Functions) BindVertexArray(a VertexArray) {
|
||||
panic("not supported")
|
||||
}
|
||||
|
||||
func (f *Functions) BlendEquation(mode Enum) {
|
||||
f._blendEquation.Invoke(int(mode))
|
||||
}
|
||||
|
||||
func (f *Functions) BlendFuncSeparate(srcRGB, dstRGB, srcA, dstA Enum) {
|
||||
f._blendFunc.Invoke(int(srcRGB), int(dstRGB), int(srcA), int(dstA))
|
||||
}
|
||||
|
||||
func (f *Functions) BufferData(target Enum, size int, usage Enum, data []byte) {
|
||||
if data == nil {
|
||||
f._bufferData.Invoke(int(target), size, int(usage))
|
||||
@@ -297,9 +310,11 @@ func (f *Functions) BufferData(target Enum, size int, usage Enum, data []byte) {
|
||||
f._bufferData.Invoke(int(target), f.byteArrayOf(data), int(usage))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
|
||||
f._bufferSubData.Invoke(int(target), offset, f.byteArrayOf(src))
|
||||
}
|
||||
|
||||
func (f *Functions) CheckFramebufferStatus(target Enum) Enum {
|
||||
status := Enum(f._checkFramebufferStatus.Invoke(int(target)).Int())
|
||||
if status != FRAMEBUFFER_COMPLETE && f.Ctx.Call("isContextLost").Bool() {
|
||||
@@ -308,54 +323,71 @@ func (f *Functions) CheckFramebufferStatus(target Enum) Enum {
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
func (f *Functions) Clear(mask Enum) {
|
||||
f._clear.Invoke(int(mask))
|
||||
}
|
||||
|
||||
func (f *Functions) ClearColor(red, green, blue, alpha float32) {
|
||||
f._clearColor.Invoke(red, green, blue, alpha)
|
||||
}
|
||||
|
||||
func (f *Functions) ClearDepthf(d float32) {
|
||||
f._clearDepth.Invoke(d)
|
||||
}
|
||||
|
||||
func (f *Functions) CompileShader(s Shader) {
|
||||
f._compileShader.Invoke(js.Value(s))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (f *Functions) CreateBuffer() Buffer {
|
||||
return Buffer(f._createBuffer.Invoke())
|
||||
}
|
||||
|
||||
func (f *Functions) CreateFramebuffer() Framebuffer {
|
||||
return Framebuffer(f._createFramebuffer.Invoke())
|
||||
}
|
||||
|
||||
func (f *Functions) CreateProgram() Program {
|
||||
return Program(f._createProgram.Invoke())
|
||||
}
|
||||
|
||||
func (f *Functions) CreateQuery() Query {
|
||||
return Query(f._createQuery.Invoke())
|
||||
}
|
||||
|
||||
func (f *Functions) CreateRenderbuffer() Renderbuffer {
|
||||
return Renderbuffer(f._createRenderbuffer.Invoke())
|
||||
}
|
||||
|
||||
func (f *Functions) CreateShader(ty Enum) Shader {
|
||||
return Shader(f._createShader.Invoke(int(ty)))
|
||||
}
|
||||
|
||||
func (f *Functions) CreateTexture() Texture {
|
||||
return Texture(f._createTexture.Invoke())
|
||||
}
|
||||
|
||||
func (f *Functions) CreateVertexArray() VertexArray {
|
||||
panic("not supported")
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteBuffer(v Buffer) {
|
||||
f._deleteBuffer.Invoke(js.Value(v))
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteFramebuffer(v Framebuffer) {
|
||||
f._deleteFramebuffer.Invoke(js.Value(v))
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteProgram(p Program) {
|
||||
f._deleteProgram.Invoke(js.Value(p))
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteQuery(query Query) {
|
||||
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
||||
f._deleteQuery.Invoke(js.Value(query))
|
||||
@@ -363,45 +395,59 @@ func (f *Functions) DeleteQuery(query Query) {
|
||||
f.EXT_disjoint_timer_query.Call("deleteQueryEXT", js.Value(query))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteShader(s Shader) {
|
||||
f._deleteShader.Invoke(js.Value(s))
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteRenderbuffer(v Renderbuffer) {
|
||||
f._deleteRenderbuffer.Invoke(js.Value(v))
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteTexture(v Texture) {
|
||||
f._deleteTexture.Invoke(js.Value(v))
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteVertexArray(a VertexArray) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (f *Functions) DepthFunc(fn Enum) {
|
||||
f._depthFunc.Invoke(int(fn))
|
||||
}
|
||||
|
||||
func (f *Functions) DepthMask(mask bool) {
|
||||
f._depthMask.Invoke(mask)
|
||||
}
|
||||
|
||||
func (f *Functions) DisableVertexAttribArray(a Attrib) {
|
||||
f._disableVertexAttribArray.Invoke(int(a))
|
||||
}
|
||||
|
||||
func (f *Functions) Disable(cap Enum) {
|
||||
f._disable.Invoke(int(cap))
|
||||
}
|
||||
|
||||
func (f *Functions) DrawArrays(mode Enum, first, count int) {
|
||||
f._drawArrays.Invoke(int(mode), first, count)
|
||||
}
|
||||
|
||||
func (f *Functions) DrawElements(mode Enum, count int, ty Enum, offset int) {
|
||||
f._drawElements.Invoke(int(mode), count, int(ty), offset)
|
||||
}
|
||||
|
||||
func (f *Functions) DispatchCompute(x, y, z int) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (f *Functions) Enable(cap Enum) {
|
||||
f._enable.Invoke(int(cap))
|
||||
}
|
||||
|
||||
func (f *Functions) EnableVertexAttribArray(a Attrib) {
|
||||
f._enableVertexAttribArray.Invoke(int(a))
|
||||
}
|
||||
|
||||
func (f *Functions) EndQuery(target Enum) {
|
||||
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
||||
f._endQuery.Invoke(int(target))
|
||||
@@ -409,28 +455,36 @@ func (f *Functions) EndQuery(target Enum) {
|
||||
f.EXT_disjoint_timer_query.Call("endQueryEXT", int(target))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Functions) Finish() {
|
||||
f._finish.Invoke()
|
||||
}
|
||||
|
||||
func (f *Functions) Flush() {
|
||||
f._flush.Invoke()
|
||||
}
|
||||
|
||||
func (f *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer 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) {
|
||||
f._framebufferTexture2D.Invoke(int(target), int(attachment), int(texTarget), js.Value(t), level)
|
||||
}
|
||||
|
||||
func (f *Functions) GenerateMipmap(target Enum) {
|
||||
f._generateMipmap.Invoke(int(target))
|
||||
}
|
||||
|
||||
func (f *Functions) GetError() Enum {
|
||||
// Avoid slow getError calls. See gio#179.
|
||||
return 0
|
||||
}
|
||||
|
||||
func (f *Functions) GetRenderbufferParameteri(target, pname Enum) int {
|
||||
return paramVal(f._getRenderbufferParameteri.Invoke(int(pname)))
|
||||
}
|
||||
|
||||
func (f *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int {
|
||||
if !f.isWebGL2 && pname == FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING {
|
||||
// FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING is only available on WebGL 2
|
||||
@@ -438,6 +492,7 @@ func (f *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname
|
||||
}
|
||||
return paramVal(f._getFramebufferAttachmentParameter.Invoke(int(target), int(attachment), int(pname)))
|
||||
}
|
||||
|
||||
func (f *Functions) GetBinding(pname Enum) Object {
|
||||
obj := f._getParameter.Invoke(int(pname))
|
||||
if !obj.Truthy() {
|
||||
@@ -445,6 +500,7 @@ func (f *Functions) GetBinding(pname Enum) Object {
|
||||
}
|
||||
return Object(obj)
|
||||
}
|
||||
|
||||
func (f *Functions) GetBindingi(pname Enum, idx int) Object {
|
||||
obj := f._getIndexedParameter.Invoke(int(pname), idx)
|
||||
if !obj.Truthy() {
|
||||
@@ -452,6 +508,7 @@ func (f *Functions) GetBindingi(pname Enum, idx int) Object {
|
||||
}
|
||||
return Object(obj)
|
||||
}
|
||||
|
||||
func (f *Functions) GetInteger(pname Enum) int {
|
||||
if !f.isWebGL2 {
|
||||
switch pname {
|
||||
@@ -461,9 +518,11 @@ func (f *Functions) GetInteger(pname Enum) int {
|
||||
}
|
||||
return paramVal(f._getParameter.Invoke(int(pname)))
|
||||
}
|
||||
|
||||
func (f *Functions) GetFloat(pname Enum) float32 {
|
||||
return float32(f._getParameter.Invoke(int(pname)).Float())
|
||||
}
|
||||
|
||||
func (f *Functions) GetInteger4(pname Enum) [4]int {
|
||||
arr := f._getParameter.Invoke(int(pname))
|
||||
var res [4]int
|
||||
@@ -472,6 +531,7 @@ func (f *Functions) GetInteger4(pname Enum) [4]int {
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (f *Functions) GetFloat4(pname Enum) [4]float32 {
|
||||
arr := f._getParameter.Invoke(int(pname))
|
||||
var res [4]float32
|
||||
@@ -480,12 +540,15 @@ func (f *Functions) GetFloat4(pname Enum) [4]float32 {
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (f *Functions) GetProgrami(p Program, pname Enum) int {
|
||||
return paramVal(f._getProgramParameter.Invoke(js.Value(p), int(pname)))
|
||||
}
|
||||
|
||||
func (f *Functions) GetProgramInfoLog(p Program) string {
|
||||
return f._getProgramInfoLog.Invoke(js.Value(p)).String()
|
||||
}
|
||||
|
||||
func (f *Functions) GetQueryObjectuiv(query Query, pname Enum) uint {
|
||||
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
||||
return uint(paramVal(f._getQueryParameter.Invoke(js.Value(query), int(pname))))
|
||||
@@ -493,12 +556,15 @@ func (f *Functions) GetQueryObjectuiv(query Query, pname Enum) uint {
|
||||
return uint(paramVal(f.EXT_disjoint_timer_query.Call("getQueryObjectEXT", js.Value(query), int(pname))))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Functions) GetShaderi(s Shader, pname Enum) int {
|
||||
return paramVal(f._getShaderParameter.Invoke(js.Value(s), int(pname)))
|
||||
}
|
||||
|
||||
func (f *Functions) GetShaderInfoLog(s Shader) string {
|
||||
return f._getShaderInfoLog.Invoke(js.Value(s)).String()
|
||||
}
|
||||
|
||||
func (f *Functions) GetString(pname Enum) string {
|
||||
switch pname {
|
||||
case EXTENSIONS:
|
||||
@@ -512,15 +578,19 @@ func (f *Functions) GetString(pname Enum) string {
|
||||
return f._getParameter.Invoke(int(pname)).String()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Functions) GetUniformBlockIndex(p Program, name string) uint {
|
||||
return uint(paramVal(f._getUniformBlockIndex.Invoke(js.Value(p), name)))
|
||||
}
|
||||
|
||||
func (f *Functions) GetUniformLocation(p Program, name string) Uniform {
|
||||
return Uniform(f._getUniformLocation.Invoke(js.Value(p), name))
|
||||
}
|
||||
|
||||
func (f *Functions) GetVertexAttrib(index int, pname Enum) int {
|
||||
return paramVal(f._getVertexAttrib.Invoke(index, int(pname)))
|
||||
}
|
||||
|
||||
func (f *Functions) GetVertexAttribBinding(index int, pname Enum) Object {
|
||||
obj := f._getVertexAttrib.Invoke(index, int(pname))
|
||||
if !obj.Truthy() {
|
||||
@@ -528,9 +598,11 @@ func (f *Functions) GetVertexAttribBinding(index int, pname Enum) Object {
|
||||
}
|
||||
return Object(obj)
|
||||
}
|
||||
|
||||
func (f *Functions) GetVertexAttribPointer(index int, pname Enum) uintptr {
|
||||
return uintptr(f._getVertexAttribOffset.Invoke(index, int(pname)).Int())
|
||||
}
|
||||
|
||||
func (f *Functions) InvalidateFramebuffer(target, attachment Enum) {
|
||||
fn := f.Ctx.Get("invalidateFramebuffer")
|
||||
if !fn.IsUndefined() {
|
||||
@@ -541,74 +613,97 @@ func (f *Functions) InvalidateFramebuffer(target, attachment Enum) {
|
||||
f._invalidateFramebuffer.Invoke(int(target), f.int32Buf)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Functions) IsEnabled(cap Enum) bool {
|
||||
return f._isEnabled.Invoke(int(cap)).Truthy()
|
||||
}
|
||||
|
||||
func (f *Functions) LinkProgram(p Program) {
|
||||
f._linkProgram.Invoke(js.Value(p))
|
||||
}
|
||||
|
||||
func (f *Functions) PixelStorei(pname Enum, param int) {
|
||||
f._pixelStorei.Invoke(int(pname), param)
|
||||
}
|
||||
|
||||
func (f *Functions) MemoryBarrier(barriers Enum) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (f *Functions) MapBufferRange(target Enum, offset, length int, access Enum) []byte {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (f *Functions) RenderbufferStorage(target, internalformat Enum, width, height int) {
|
||||
f._renderbufferStorage.Invoke(int(target), int(internalformat), width, height)
|
||||
}
|
||||
|
||||
func (f *Functions) ReadPixels(x, y, width, height int, format, ty Enum, data []byte) {
|
||||
ba := f.byteArrayOf(data)
|
||||
f._readPixels.Invoke(x, y, width, height, int(format), int(ty), ba)
|
||||
js.CopyBytesToGo(data, ba)
|
||||
}
|
||||
|
||||
func (f *Functions) Scissor(x, y, width, height int32) {
|
||||
f._scissor.Invoke(x, y, width, height)
|
||||
}
|
||||
|
||||
func (f *Functions) ShaderSource(s Shader, src string) {
|
||||
f._shaderSource.Invoke(js.Value(s), src)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (f *Functions) TexStorage2D(target Enum, levels int, internalFormat Enum, width, height int) {
|
||||
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) {
|
||||
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) {
|
||||
f._texParameteri.Invoke(int(target), int(pname), int(param))
|
||||
}
|
||||
|
||||
func (f *Functions) UniformBlockBinding(p Program, uniformBlockIndex uint, uniformBlockBinding uint) {
|
||||
f._uniformBlockBinding.Invoke(js.Value(p), int(uniformBlockIndex), int(uniformBlockBinding))
|
||||
}
|
||||
|
||||
func (f *Functions) Uniform1f(dst Uniform, v float32) {
|
||||
f._uniform1f.Invoke(js.Value(dst), v)
|
||||
}
|
||||
|
||||
func (f *Functions) Uniform1i(dst Uniform, v int) {
|
||||
f._uniform1i.Invoke(js.Value(dst), v)
|
||||
}
|
||||
|
||||
func (f *Functions) Uniform2f(dst Uniform, v0, v1 float32) {
|
||||
f._uniform2f.Invoke(js.Value(dst), v0, v1)
|
||||
}
|
||||
|
||||
func (f *Functions) Uniform3f(dst Uniform, v0, v1, v2 float32) {
|
||||
f._uniform3f.Invoke(js.Value(dst), v0, v1, v2)
|
||||
}
|
||||
|
||||
func (f *Functions) Uniform4f(dst Uniform, v0, v1, v2, v3 float32) {
|
||||
f._uniform4f.Invoke(js.Value(dst), v0, v1, v2, v3)
|
||||
}
|
||||
|
||||
func (f *Functions) UseProgram(p Program) {
|
||||
f._useProgram.Invoke(js.Value(p))
|
||||
}
|
||||
|
||||
func (f *Functions) UnmapBuffer(target Enum) bool {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (f *Functions) Viewport(x, y, width, height int) {
|
||||
f._viewport.Invoke(x, y, width, height)
|
||||
}
|
||||
|
||||
@@ -665,7 +665,7 @@ func (f *Functions) load(forceES bool) error {
|
||||
case runtime.GOOS == "android":
|
||||
libNames = []string{"libGLESv2.so", "libGLESv3.so"}
|
||||
default:
|
||||
libNames = []string{"libGLESv2.so.2"}
|
||||
libNames = []string{"libGLESv2.so.2", "libGLESv2.so.3.0"}
|
||||
}
|
||||
for _, lib := range libNames {
|
||||
if h := dlopen(lib); h != nil {
|
||||
|
||||
+306
-91
@@ -3,103 +3,217 @@
|
||||
package gl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"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 (
|
||||
LibGLESv2 = windows.NewLazyDLL("libGLESv2.dll")
|
||||
_glActiveTexture = LibGLESv2.NewProc("glActiveTexture")
|
||||
_glAttachShader = LibGLESv2.NewProc("glAttachShader")
|
||||
_glBeginQuery = LibGLESv2.NewProc("glBeginQuery")
|
||||
_glBindAttribLocation = LibGLESv2.NewProc("glBindAttribLocation")
|
||||
_glBindBuffer = LibGLESv2.NewProc("glBindBuffer")
|
||||
_glBindBufferBase = LibGLESv2.NewProc("glBindBufferBase")
|
||||
_glBindFramebuffer = LibGLESv2.NewProc("glBindFramebuffer")
|
||||
_glBindRenderbuffer = LibGLESv2.NewProc("glBindRenderbuffer")
|
||||
_glBindTexture = LibGLESv2.NewProc("glBindTexture")
|
||||
_glBindVertexArray = LibGLESv2.NewProc("glBindVertexArray")
|
||||
_glBlendEquation = LibGLESv2.NewProc("glBlendEquation")
|
||||
_glBlendFuncSeparate = LibGLESv2.NewProc("glBlendFuncSeparate")
|
||||
_glBufferData = LibGLESv2.NewProc("glBufferData")
|
||||
_glBufferSubData = LibGLESv2.NewProc("glBufferSubData")
|
||||
_glCheckFramebufferStatus = LibGLESv2.NewProc("glCheckFramebufferStatus")
|
||||
_glClear = LibGLESv2.NewProc("glClear")
|
||||
_glClearColor = LibGLESv2.NewProc("glClearColor")
|
||||
_glClearDepthf = LibGLESv2.NewProc("glClearDepthf")
|
||||
_glDeleteQueries = LibGLESv2.NewProc("glDeleteQueries")
|
||||
_glDeleteVertexArrays = LibGLESv2.NewProc("glDeleteVertexArrays")
|
||||
_glCompileShader = LibGLESv2.NewProc("glCompileShader")
|
||||
_glCopyTexSubImage2D = LibGLESv2.NewProc("glCopyTexSubImage2D")
|
||||
_glGenerateMipmap = LibGLESv2.NewProc("glGenerateMipmap")
|
||||
_glGenBuffers = LibGLESv2.NewProc("glGenBuffers")
|
||||
_glGenFramebuffers = LibGLESv2.NewProc("glGenFramebuffers")
|
||||
_glGenVertexArrays = LibGLESv2.NewProc("glGenVertexArrays")
|
||||
_glGetUniformBlockIndex = LibGLESv2.NewProc("glGetUniformBlockIndex")
|
||||
_glCreateProgram = LibGLESv2.NewProc("glCreateProgram")
|
||||
_glGenRenderbuffers = LibGLESv2.NewProc("glGenRenderbuffers")
|
||||
_glCreateShader = LibGLESv2.NewProc("glCreateShader")
|
||||
_glGenTextures = LibGLESv2.NewProc("glGenTextures")
|
||||
_glDeleteBuffers = LibGLESv2.NewProc("glDeleteBuffers")
|
||||
_glDeleteFramebuffers = LibGLESv2.NewProc("glDeleteFramebuffers")
|
||||
_glDeleteProgram = LibGLESv2.NewProc("glDeleteProgram")
|
||||
_glDeleteShader = LibGLESv2.NewProc("glDeleteShader")
|
||||
_glDeleteRenderbuffers = LibGLESv2.NewProc("glDeleteRenderbuffers")
|
||||
_glDeleteTextures = LibGLESv2.NewProc("glDeleteTextures")
|
||||
_glDepthFunc = LibGLESv2.NewProc("glDepthFunc")
|
||||
_glDepthMask = LibGLESv2.NewProc("glDepthMask")
|
||||
_glDisableVertexAttribArray = LibGLESv2.NewProc("glDisableVertexAttribArray")
|
||||
_glDisable = LibGLESv2.NewProc("glDisable")
|
||||
_glDrawArrays = LibGLESv2.NewProc("glDrawArrays")
|
||||
_glDrawElements = LibGLESv2.NewProc("glDrawElements")
|
||||
_glEnable = LibGLESv2.NewProc("glEnable")
|
||||
_glEnableVertexAttribArray = LibGLESv2.NewProc("glEnableVertexAttribArray")
|
||||
_glEndQuery = LibGLESv2.NewProc("glEndQuery")
|
||||
_glFinish = LibGLESv2.NewProc("glFinish")
|
||||
_glFlush = LibGLESv2.NewProc("glFlush")
|
||||
_glFramebufferRenderbuffer = LibGLESv2.NewProc("glFramebufferRenderbuffer")
|
||||
_glFramebufferTexture2D = LibGLESv2.NewProc("glFramebufferTexture2D")
|
||||
_glGenQueries = LibGLESv2.NewProc("glGenQueries")
|
||||
_glGetError = LibGLESv2.NewProc("glGetError")
|
||||
_glGetRenderbufferParameteriv = LibGLESv2.NewProc("glGetRenderbufferParameteriv")
|
||||
_glGetFloatv = LibGLESv2.NewProc("glGetFloatv")
|
||||
_glGetFramebufferAttachmentParameteriv = LibGLESv2.NewProc("glGetFramebufferAttachmentParameteriv")
|
||||
_glGetIntegerv = LibGLESv2.NewProc("glGetIntegerv")
|
||||
_glGetIntegeri_v = LibGLESv2.NewProc("glGetIntegeri_v")
|
||||
_glGetProgramiv = LibGLESv2.NewProc("glGetProgramiv")
|
||||
_glGetProgramInfoLog = LibGLESv2.NewProc("glGetProgramInfoLog")
|
||||
_glGetQueryObjectuiv = LibGLESv2.NewProc("glGetQueryObjectuiv")
|
||||
_glGetShaderiv = LibGLESv2.NewProc("glGetShaderiv")
|
||||
_glGetShaderInfoLog = LibGLESv2.NewProc("glGetShaderInfoLog")
|
||||
_glGetString = LibGLESv2.NewProc("glGetString")
|
||||
_glGetUniformLocation = LibGLESv2.NewProc("glGetUniformLocation")
|
||||
_glGetVertexAttribiv = LibGLESv2.NewProc("glGetVertexAttribiv")
|
||||
_glGetVertexAttribPointerv = LibGLESv2.NewProc("glGetVertexAttribPointerv")
|
||||
_glInvalidateFramebuffer = LibGLESv2.NewProc("glInvalidateFramebuffer")
|
||||
_glIsEnabled = LibGLESv2.NewProc("glIsEnabled")
|
||||
_glLinkProgram = LibGLESv2.NewProc("glLinkProgram")
|
||||
_glPixelStorei = LibGLESv2.NewProc("glPixelStorei")
|
||||
_glReadPixels = LibGLESv2.NewProc("glReadPixels")
|
||||
_glRenderbufferStorage = LibGLESv2.NewProc("glRenderbufferStorage")
|
||||
_glScissor = LibGLESv2.NewProc("glScissor")
|
||||
_glShaderSource = LibGLESv2.NewProc("glShaderSource")
|
||||
_glTexImage2D = LibGLESv2.NewProc("glTexImage2D")
|
||||
_glTexStorage2D = LibGLESv2.NewProc("glTexStorage2D")
|
||||
_glTexSubImage2D = LibGLESv2.NewProc("glTexSubImage2D")
|
||||
_glTexParameteri = LibGLESv2.NewProc("glTexParameteri")
|
||||
_glUniformBlockBinding = LibGLESv2.NewProc("glUniformBlockBinding")
|
||||
_glUniform1f = LibGLESv2.NewProc("glUniform1f")
|
||||
_glUniform1i = LibGLESv2.NewProc("glUniform1i")
|
||||
_glUniform2f = LibGLESv2.NewProc("glUniform2f")
|
||||
_glUniform3f = LibGLESv2.NewProc("glUniform3f")
|
||||
_glUniform4f = LibGLESv2.NewProc("glUniform4f")
|
||||
_glUseProgram = LibGLESv2.NewProc("glUseProgram")
|
||||
_glVertexAttribPointer = LibGLESv2.NewProc("glVertexAttribPointer")
|
||||
_glViewport = LibGLESv2.NewProc("glViewport")
|
||||
glInitOnce sync.Once
|
||||
_glActiveTexture *windows.Proc
|
||||
_glAttachShader *windows.Proc
|
||||
_glBeginQuery *windows.Proc
|
||||
_glBindAttribLocation *windows.Proc
|
||||
_glBindBuffer *windows.Proc
|
||||
_glBindBufferBase *windows.Proc
|
||||
_glBindFramebuffer *windows.Proc
|
||||
_glBindRenderbuffer *windows.Proc
|
||||
_glBindTexture *windows.Proc
|
||||
_glBindVertexArray *windows.Proc
|
||||
_glBlendEquation *windows.Proc
|
||||
_glBlendFuncSeparate *windows.Proc
|
||||
_glBufferData *windows.Proc
|
||||
_glBufferSubData *windows.Proc
|
||||
_glCheckFramebufferStatus *windows.Proc
|
||||
_glClear *windows.Proc
|
||||
_glClearColor *windows.Proc
|
||||
_glClearDepthf *windows.Proc
|
||||
_glDeleteQueries *windows.Proc
|
||||
_glDeleteVertexArrays *windows.Proc
|
||||
_glCompileShader *windows.Proc
|
||||
_glCopyTexSubImage2D *windows.Proc
|
||||
_glGenerateMipmap *windows.Proc
|
||||
_glGenBuffers *windows.Proc
|
||||
_glGenFramebuffers *windows.Proc
|
||||
_glGenVertexArrays *windows.Proc
|
||||
_glGetUniformBlockIndex *windows.Proc
|
||||
_glCreateProgram *windows.Proc
|
||||
_glGenRenderbuffers *windows.Proc
|
||||
_glCreateShader *windows.Proc
|
||||
_glGenTextures *windows.Proc
|
||||
_glDeleteBuffers *windows.Proc
|
||||
_glDeleteFramebuffers *windows.Proc
|
||||
_glDeleteProgram *windows.Proc
|
||||
_glDeleteShader *windows.Proc
|
||||
_glDeleteRenderbuffers *windows.Proc
|
||||
_glDeleteTextures *windows.Proc
|
||||
_glDepthFunc *windows.Proc
|
||||
_glDepthMask *windows.Proc
|
||||
_glDisableVertexAttribArray *windows.Proc
|
||||
_glDisable *windows.Proc
|
||||
_glDrawArrays *windows.Proc
|
||||
_glDrawElements *windows.Proc
|
||||
_glEnable *windows.Proc
|
||||
_glEnableVertexAttribArray *windows.Proc
|
||||
_glEndQuery *windows.Proc
|
||||
_glFinish *windows.Proc
|
||||
_glFlush *windows.Proc
|
||||
_glFramebufferRenderbuffer *windows.Proc
|
||||
_glFramebufferTexture2D *windows.Proc
|
||||
_glGenQueries *windows.Proc
|
||||
_glGetError *windows.Proc
|
||||
_glGetRenderbufferParameteriv *windows.Proc
|
||||
_glGetFloatv *windows.Proc
|
||||
_glGetFramebufferAttachmentParameteriv *windows.Proc
|
||||
_glGetIntegerv *windows.Proc
|
||||
_glGetIntegeri_v *windows.Proc
|
||||
_glGetProgramiv *windows.Proc
|
||||
_glGetProgramInfoLog *windows.Proc
|
||||
_glGetQueryObjectuiv *windows.Proc
|
||||
_glGetShaderiv *windows.Proc
|
||||
_glGetShaderInfoLog *windows.Proc
|
||||
_glGetString *windows.Proc
|
||||
_glGetUniformLocation *windows.Proc
|
||||
_glGetVertexAttribiv *windows.Proc
|
||||
_glGetVertexAttribPointerv *windows.Proc
|
||||
_glInvalidateFramebuffer *windows.Proc
|
||||
_glIsEnabled *windows.Proc
|
||||
_glLinkProgram *windows.Proc
|
||||
_glPixelStorei *windows.Proc
|
||||
_glReadPixels *windows.Proc
|
||||
_glRenderbufferStorage *windows.Proc
|
||||
_glScissor *windows.Proc
|
||||
_glShaderSource *windows.Proc
|
||||
_glTexImage2D *windows.Proc
|
||||
_glTexStorage2D *windows.Proc
|
||||
_glTexSubImage2D *windows.Proc
|
||||
_glTexParameteri *windows.Proc
|
||||
_glUniformBlockBinding *windows.Proc
|
||||
_glUniform1f *windows.Proc
|
||||
_glUniform1i *windows.Proc
|
||||
_glUniform2f *windows.Proc
|
||||
_glUniform3f *windows.Proc
|
||||
_glUniform4f *windows.Proc
|
||||
_glUseProgram *windows.Proc
|
||||
_glVertexAttribPointer *windows.Proc
|
||||
_glViewport *windows.Proc
|
||||
)
|
||||
|
||||
type Functions struct {
|
||||
@@ -109,57 +223,74 @@ type Functions struct {
|
||||
uintptrs [100]uintptr
|
||||
}
|
||||
|
||||
type Context interface{}
|
||||
type Context any
|
||||
|
||||
func NewFunctions(ctx Context, forceES bool) (*Functions, error) {
|
||||
if ctx != nil {
|
||||
panic("non-nil context")
|
||||
}
|
||||
return new(Functions), nil
|
||||
var err error
|
||||
glInitOnce.Do(func() {
|
||||
err = loadGLESv2Procs()
|
||||
})
|
||||
return new(Functions), err
|
||||
}
|
||||
|
||||
func (c *Functions) ActiveTexture(t Enum) {
|
||||
syscall.Syscall(_glActiveTexture.Addr(), 1, uintptr(t), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) AttachShader(p Program, s Shader) {
|
||||
syscall.Syscall(_glAttachShader.Addr(), 2, uintptr(p.V), uintptr(s.V), 0)
|
||||
}
|
||||
|
||||
func (f *Functions) BeginQuery(target Enum, query Query) {
|
||||
syscall.Syscall(_glBeginQuery.Addr(), 2, uintptr(target), uintptr(query.V), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) BindAttribLocation(p Program, a Attrib, name string) {
|
||||
cname := cString(name)
|
||||
c0 := &cname[0]
|
||||
syscall.Syscall(_glBindAttribLocation.Addr(), 3, uintptr(p.V), uintptr(a), uintptr(unsafe.Pointer(c0)))
|
||||
issue34474KeepAlive(c)
|
||||
}
|
||||
|
||||
func (c *Functions) BindBuffer(target Enum, b Buffer) {
|
||||
syscall.Syscall(_glBindBuffer.Addr(), 2, uintptr(target), uintptr(b.V), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) BindBufferBase(target Enum, index int, b Buffer) {
|
||||
syscall.Syscall(_glBindBufferBase.Addr(), 3, uintptr(target), uintptr(index), uintptr(b.V))
|
||||
}
|
||||
|
||||
func (c *Functions) BindFramebuffer(target Enum, fb Framebuffer) {
|
||||
syscall.Syscall(_glBindFramebuffer.Addr(), 2, uintptr(target), uintptr(fb.V), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) BindRenderbuffer(target Enum, rb Renderbuffer) {
|
||||
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) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (c *Functions) BindTexture(target Enum, t Texture) {
|
||||
syscall.Syscall(_glBindTexture.Addr(), 2, uintptr(target), uintptr(t.V), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) BindVertexArray(a VertexArray) {
|
||||
syscall.Syscall(_glBindVertexArray.Addr(), 1, uintptr(a.V), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) BlendEquation(mode Enum) {
|
||||
syscall.Syscall(_glBlendEquation.Addr(), 1, uintptr(mode), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) BlendFuncSeparate(srcRGB, dstRGB, srcA, dstA Enum) {
|
||||
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) {
|
||||
var p unsafe.Pointer
|
||||
if len(data) > 0 {
|
||||
@@ -167,6 +298,7 @@ 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)
|
||||
}
|
||||
|
||||
func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
|
||||
if n := len(src); n > 0 {
|
||||
s0 := &src[0]
|
||||
@@ -174,93 +306,118 @@ func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
|
||||
issue34474KeepAlive(s0)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Functions) CheckFramebufferStatus(target Enum) Enum {
|
||||
s, _, _ := syscall.Syscall(_glCheckFramebufferStatus.Addr(), 1, uintptr(target), 0, 0)
|
||||
return Enum(s)
|
||||
}
|
||||
|
||||
func (c *Functions) Clear(mask Enum) {
|
||||
syscall.Syscall(_glClear.Addr(), 1, uintptr(mask), 0, 0)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (c *Functions) ClearDepthf(d float32) {
|
||||
syscall.Syscall(_glClearDepthf.Addr(), 1, uintptr(math.Float32bits(d)), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) CompileShader(s Shader) {
|
||||
syscall.Syscall(_glCompileShader.Addr(), 1, uintptr(s.V), 0, 0)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (f *Functions) GenerateMipmap(target Enum) {
|
||||
syscall.Syscall(_glGenerateMipmap.Addr(), 1, uintptr(target), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) CreateBuffer() Buffer {
|
||||
var buf uintptr
|
||||
syscall.Syscall(_glGenBuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&buf)), 0)
|
||||
return Buffer{uint(buf)}
|
||||
}
|
||||
|
||||
func (c *Functions) CreateFramebuffer() Framebuffer {
|
||||
var fb uintptr
|
||||
syscall.Syscall(_glGenFramebuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&fb)), 0)
|
||||
return Framebuffer{uint(fb)}
|
||||
}
|
||||
|
||||
func (c *Functions) CreateProgram() Program {
|
||||
p, _, _ := syscall.Syscall(_glCreateProgram.Addr(), 0, 0, 0, 0)
|
||||
return Program{uint(p)}
|
||||
}
|
||||
|
||||
func (f *Functions) CreateQuery() Query {
|
||||
var q uintptr
|
||||
syscall.Syscall(_glGenQueries.Addr(), 2, 1, uintptr(unsafe.Pointer(&q)), 0)
|
||||
return Query{uint(q)}
|
||||
}
|
||||
|
||||
func (c *Functions) CreateRenderbuffer() Renderbuffer {
|
||||
var rb uintptr
|
||||
syscall.Syscall(_glGenRenderbuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&rb)), 0)
|
||||
return Renderbuffer{uint(rb)}
|
||||
}
|
||||
|
||||
func (c *Functions) CreateShader(ty Enum) Shader {
|
||||
s, _, _ := syscall.Syscall(_glCreateShader.Addr(), 1, uintptr(ty), 0, 0)
|
||||
return Shader{uint(s)}
|
||||
}
|
||||
|
||||
func (c *Functions) CreateTexture() Texture {
|
||||
var t uintptr
|
||||
syscall.Syscall(_glGenTextures.Addr(), 2, 1, uintptr(unsafe.Pointer(&t)), 0)
|
||||
return Texture{uint(t)}
|
||||
}
|
||||
|
||||
func (c *Functions) CreateVertexArray() VertexArray {
|
||||
var t uintptr
|
||||
syscall.Syscall(_glGenVertexArrays.Addr(), 2, 1, uintptr(unsafe.Pointer(&t)), 0)
|
||||
return VertexArray{uint(t)}
|
||||
}
|
||||
|
||||
func (c *Functions) DeleteBuffer(v Buffer) {
|
||||
syscall.Syscall(_glDeleteBuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v)), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) DeleteFramebuffer(v Framebuffer) {
|
||||
syscall.Syscall(_glDeleteFramebuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) DeleteProgram(p Program) {
|
||||
syscall.Syscall(_glDeleteProgram.Addr(), 1, uintptr(p.V), 0, 0)
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteQuery(query Query) {
|
||||
syscall.Syscall(_glDeleteQueries.Addr(), 2, 1, uintptr(unsafe.Pointer(&query.V)), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) DeleteShader(s Shader) {
|
||||
syscall.Syscall(_glDeleteShader.Addr(), 1, uintptr(s.V), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) DeleteRenderbuffer(v Renderbuffer) {
|
||||
syscall.Syscall(_glDeleteRenderbuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) DeleteTexture(v Texture) {
|
||||
syscall.Syscall(_glDeleteTextures.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteVertexArray(array VertexArray) {
|
||||
syscall.Syscall(_glDeleteVertexArrays.Addr(), 2, 1, uintptr(unsafe.Pointer(&array.V)), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) DepthFunc(f Enum) {
|
||||
syscall.Syscall(_glDepthFunc.Addr(), 1, uintptr(f), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) DepthMask(mask bool) {
|
||||
var m uintptr
|
||||
if mask {
|
||||
@@ -268,42 +425,55 @@ func (c *Functions) DepthMask(mask bool) {
|
||||
}
|
||||
syscall.Syscall(_glDepthMask.Addr(), 1, m, 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) DisableVertexAttribArray(a Attrib) {
|
||||
syscall.Syscall(_glDisableVertexAttribArray.Addr(), 1, uintptr(a), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) Disable(cap Enum) {
|
||||
syscall.Syscall(_glDisable.Addr(), 1, uintptr(cap), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) DrawArrays(mode Enum, first, count int) {
|
||||
syscall.Syscall(_glDrawArrays.Addr(), 3, uintptr(mode), uintptr(first), uintptr(count))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (f *Functions) DispatchCompute(x, y, z int) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (c *Functions) Enable(cap Enum) {
|
||||
syscall.Syscall(_glEnable.Addr(), 1, uintptr(cap), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) EnableVertexAttribArray(a Attrib) {
|
||||
syscall.Syscall(_glEnableVertexAttribArray.Addr(), 1, uintptr(a), 0, 0)
|
||||
}
|
||||
|
||||
func (f *Functions) EndQuery(target Enum) {
|
||||
syscall.Syscall(_glEndQuery.Addr(), 1, uintptr(target), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) Finish() {
|
||||
syscall.Syscall(_glFinish.Addr(), 0, 0, 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) Flush() {
|
||||
syscall.Syscall(_glFlush.Addr(), 0, 0, 0, 0)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (f *Functions) GetUniformBlockIndex(p Program, name string) uint {
|
||||
cname := cString(name)
|
||||
c0 := &cname[0]
|
||||
@@ -311,24 +481,30 @@ func (f *Functions) GetUniformBlockIndex(p Program, name string) uint {
|
||||
issue34474KeepAlive(c0)
|
||||
return uint(u)
|
||||
}
|
||||
|
||||
func (c *Functions) GetBinding(pname Enum) Object {
|
||||
return Object{uint(c.GetInteger(pname))}
|
||||
}
|
||||
|
||||
func (c *Functions) GetBindingi(pname Enum, idx int) Object {
|
||||
return Object{uint(c.GetIntegeri(pname, idx))}
|
||||
}
|
||||
|
||||
func (c *Functions) GetError() Enum {
|
||||
e, _, _ := syscall.Syscall(_glGetError.Addr(), 0, 0, 0, 0)
|
||||
return Enum(e)
|
||||
}
|
||||
|
||||
func (c *Functions) GetRenderbufferParameteri(target, pname Enum) int {
|
||||
syscall.Syscall(_glGetRenderbufferParameteriv.Addr(), 3, uintptr(target), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
||||
return int(c.int32s[0])
|
||||
}
|
||||
|
||||
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)
|
||||
return int(c.int32s[0])
|
||||
}
|
||||
|
||||
func (c *Functions) GetInteger4(pname Enum) [4]int {
|
||||
syscall.Syscall(_glGetIntegerv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0)
|
||||
var r [4]int
|
||||
@@ -337,52 +513,66 @@ func (c *Functions) GetInteger4(pname Enum) [4]int {
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (c *Functions) GetInteger(pname Enum) int {
|
||||
syscall.Syscall(_glGetIntegerv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0)
|
||||
return int(c.int32s[0])
|
||||
}
|
||||
|
||||
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])))
|
||||
return int(c.int32s[0])
|
||||
}
|
||||
|
||||
func (c *Functions) GetFloat(pname Enum) float32 {
|
||||
syscall.Syscall(_glGetFloatv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.float32s[0])), 0)
|
||||
return c.float32s[0]
|
||||
}
|
||||
|
||||
func (c *Functions) GetFloat4(pname Enum) [4]float32 {
|
||||
syscall.Syscall(_glGetFloatv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.float32s[0])), 0)
|
||||
var r [4]float32
|
||||
copy(r[:], c.float32s[:])
|
||||
return r
|
||||
}
|
||||
|
||||
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])))
|
||||
return int(c.int32s[0])
|
||||
}
|
||||
|
||||
func (c *Functions) GetProgramInfoLog(p Program) string {
|
||||
n := c.GetProgrami(p, INFO_LOG_LENGTH)
|
||||
if n == 0 {
|
||||
return ""
|
||||
}
|
||||
buf := make([]byte, n)
|
||||
syscall.Syscall6(_glGetProgramInfoLog.Addr(), 4, uintptr(p.V), uintptr(len(buf)), 0, uintptr(unsafe.Pointer(&buf[0])), 0, 0)
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
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])))
|
||||
return uint(c.int32s[0])
|
||||
}
|
||||
|
||||
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])))
|
||||
return int(c.int32s[0])
|
||||
}
|
||||
|
||||
func (c *Functions) GetShaderInfoLog(s Shader) string {
|
||||
n := c.GetShaderi(s, INFO_LOG_LENGTH)
|
||||
buf := make([]byte, n)
|
||||
syscall.Syscall6(_glGetShaderInfoLog.Addr(), 4, uintptr(s.V), uintptr(len(buf)), 0, uintptr(unsafe.Pointer(&buf[0])), 0, 0)
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
func (c *Functions) GetString(pname Enum) string {
|
||||
s, _, _ := syscall.Syscall(_glGetString.Addr(), 1, uintptr(pname), 0, 0)
|
||||
return windows.BytePtrToString((*byte)(unsafe.Pointer(s)))
|
||||
}
|
||||
|
||||
func (c *Functions) GetUniformLocation(p Program, name string) Uniform {
|
||||
cname := cString(name)
|
||||
c0 := &cname[0]
|
||||
@@ -390,6 +580,7 @@ func (c *Functions) GetUniformLocation(p Program, name string) Uniform {
|
||||
issue34474KeepAlive(c0)
|
||||
return Uniform{int(u)}
|
||||
}
|
||||
|
||||
func (c *Functions) GetVertexAttrib(index int, pname Enum) int {
|
||||
syscall.Syscall(_glGetVertexAttribiv.Addr(), 3, uintptr(index), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
||||
return int(c.int32s[0])
|
||||
@@ -403,6 +594,7 @@ func (c *Functions) GetVertexAttribPointer(index int, pname Enum) uintptr {
|
||||
syscall.Syscall(_glGetVertexAttribPointerv.Addr(), 3, uintptr(index), uintptr(pname), uintptr(unsafe.Pointer(&c.uintptrs[0])))
|
||||
return c.uintptrs[0]
|
||||
}
|
||||
|
||||
func (c *Functions) InvalidateFramebuffer(target, attachment Enum) {
|
||||
addr := _glInvalidateFramebuffer.Addr()
|
||||
if addr == 0 {
|
||||
@@ -411,77 +603,99 @@ func (c *Functions) InvalidateFramebuffer(target, attachment Enum) {
|
||||
}
|
||||
syscall.Syscall(addr, 3, uintptr(target), 1, uintptr(unsafe.Pointer(&attachment)))
|
||||
}
|
||||
|
||||
func (f *Functions) IsEnabled(cap Enum) bool {
|
||||
u, _, _ := syscall.Syscall(_glIsEnabled.Addr(), 1, uintptr(cap), 0, 0)
|
||||
return u == TRUE
|
||||
}
|
||||
|
||||
func (c *Functions) LinkProgram(p Program) {
|
||||
syscall.Syscall(_glLinkProgram.Addr(), 1, uintptr(p.V), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) PixelStorei(pname Enum, param int) {
|
||||
syscall.Syscall(_glPixelStorei.Addr(), 2, uintptr(pname), uintptr(param), 0)
|
||||
}
|
||||
|
||||
func (f *Functions) MemoryBarrier(barriers Enum) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (f *Functions) MapBufferRange(target Enum, offset, length int, access Enum) []byte {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (f *Functions) ReadPixels(x, y, width, height int, format, ty Enum, data []byte) {
|
||||
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)
|
||||
issue34474KeepAlive(d0)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (c *Functions) Scissor(x, y, width, height int32) {
|
||||
syscall.Syscall6(_glScissor.Addr(), 4, uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) ShaderSource(s Shader, src string) {
|
||||
var n uintptr = uintptr(len(src))
|
||||
psrc := &src
|
||||
syscall.Syscall6(_glShaderSource.Addr(), 4, uintptr(s.V), 1, uintptr(unsafe.Pointer(psrc)), uintptr(unsafe.Pointer(&n)), 0, 0)
|
||||
issue34474KeepAlive(psrc)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (c *Functions) TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte) {
|
||||
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)))
|
||||
issue34474KeepAlive(d0)
|
||||
}
|
||||
|
||||
func (c *Functions) TexParameteri(target, pname Enum, param int) {
|
||||
syscall.Syscall(_glTexParameteri.Addr(), 3, uintptr(target), uintptr(pname), uintptr(param))
|
||||
}
|
||||
|
||||
func (f *Functions) UniformBlockBinding(p Program, uniformBlockIndex uint, uniformBlockBinding uint) {
|
||||
syscall.Syscall(_glUniformBlockBinding.Addr(), 3, uintptr(p.V), uintptr(uniformBlockIndex), uintptr(uniformBlockBinding))
|
||||
}
|
||||
|
||||
func (c *Functions) Uniform1f(dst Uniform, v float32) {
|
||||
syscall.Syscall(_glUniform1f.Addr(), 2, uintptr(dst.V), uintptr(math.Float32bits(v)), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) Uniform1i(dst Uniform, v int) {
|
||||
syscall.Syscall(_glUniform1i.Addr(), 2, uintptr(dst.V), uintptr(v), 0)
|
||||
}
|
||||
|
||||
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)))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (c *Functions) UseProgram(p Program) {
|
||||
syscall.Syscall(_glUseProgram.Addr(), 1, uintptr(p.V), 0, 0)
|
||||
}
|
||||
|
||||
func (f *Functions) UnmapBuffer(target Enum) bool {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (c *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int) {
|
||||
var norm uintptr
|
||||
if normalized {
|
||||
@@ -489,6 +703,7 @@ 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))
|
||||
}
|
||||
|
||||
func (c *Functions) Viewport(x, y, width, height int) {
|
||||
syscall.Syscall6(_glViewport.Addr(), 4, uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0, 0)
|
||||
}
|
||||
@@ -501,6 +716,6 @@ func cString(s string) []byte {
|
||||
|
||||
// issue34474KeepAlive calls runtime.KeepAlive as a
|
||||
// workaround for golang.org/issue/34474.
|
||||
func issue34474KeepAlive(v interface{}) {
|
||||
func issue34474KeepAlive(v any) {
|
||||
runtime.KeepAlive(v)
|
||||
}
|
||||
|
||||
+15
-65
@@ -18,7 +18,7 @@ type Ops struct {
|
||||
// data contains the serialized operations.
|
||||
data []byte
|
||||
// refs hold external references for operations.
|
||||
refs []interface{}
|
||||
refs []any
|
||||
// stringRefs provides space for string references, pointers to which will
|
||||
// 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
|
||||
@@ -55,28 +55,19 @@ const (
|
||||
TypePopTransform
|
||||
TypePushOpacity
|
||||
TypePopOpacity
|
||||
TypeInvalidate
|
||||
TypeImage
|
||||
TypePaint
|
||||
TypeColor
|
||||
TypeLinearGradient
|
||||
TypePass
|
||||
TypePopPass
|
||||
TypePointerInput
|
||||
TypeClipboardRead
|
||||
TypeClipboardWrite
|
||||
TypeSource
|
||||
TypeTarget
|
||||
TypeOffer
|
||||
TypeKeyInput
|
||||
TypeKeyFocus
|
||||
TypeKeySoftKeyboard
|
||||
TypeInput
|
||||
TypeKeyInputHint
|
||||
TypeSave
|
||||
TypeLoad
|
||||
TypeAux
|
||||
TypeClip
|
||||
TypePopClip
|
||||
TypeProfile
|
||||
TypeCursor
|
||||
TypePath
|
||||
TypeStroke
|
||||
@@ -85,8 +76,6 @@ const (
|
||||
TypeSemanticClass
|
||||
TypeSemanticSelected
|
||||
TypeSemanticEnabled
|
||||
TypeSnippet
|
||||
TypeSelection
|
||||
TypeActionInput
|
||||
)
|
||||
|
||||
@@ -148,21 +137,13 @@ const (
|
||||
TypeLinearGradientLen = 1 + 8*2 + 4*2
|
||||
TypePassLen = 1
|
||||
TypePopPassLen = 1
|
||||
TypePointerInputLen = 1 + 1 + 1*2 + 2*4 + 2*4
|
||||
TypeClipboardReadLen = 1
|
||||
TypeClipboardWriteLen = 1
|
||||
TypeSourceLen = 1
|
||||
TypeTargetLen = 1
|
||||
TypeOfferLen = 1
|
||||
TypeKeyInputLen = 1 + 1
|
||||
TypeKeyFocusLen = 1 + 1
|
||||
TypeKeySoftKeyboardLen = 1 + 1
|
||||
TypeInputLen = 1
|
||||
TypeKeyInputHintLen = 1 + 1
|
||||
TypeSaveLen = 1 + 4
|
||||
TypeLoadLen = 1 + 4
|
||||
TypeAuxLen = 1
|
||||
TypeClipLen = 1 + 4*4 + 1 + 1
|
||||
TypePopClipLen = 1
|
||||
TypeProfileLen = 1
|
||||
TypeCursorLen = 2
|
||||
TypePathLen = 8 + 1
|
||||
TypeStrokeLen = 1 + 4
|
||||
@@ -171,8 +152,6 @@ const (
|
||||
TypeSemanticClassLen = 2
|
||||
TypeSemanticSelectedLen = 2
|
||||
TypeSemanticEnabledLen = 2
|
||||
TypeSnippetLen = 1 + 4 + 4
|
||||
TypeSelectionLen = 1 + 2*4 + 2*4 + 4 + 4
|
||||
TypeActionInputLen = 1 + 1
|
||||
)
|
||||
|
||||
@@ -277,7 +256,7 @@ func PopOp(o *Ops, kind StackKind, sid StackID, macroID uint32) {
|
||||
o.stacks[kind].pop(sid)
|
||||
}
|
||||
|
||||
func Write1(o *Ops, n int, ref1 interface{}) []byte {
|
||||
func Write1(o *Ops, n int, ref1 any) []byte {
|
||||
o.data = append(o.data, make([]byte, n)...)
|
||||
o.refs = append(o.refs, ref1)
|
||||
return o.data[len(o.data)-n:]
|
||||
@@ -290,20 +269,20 @@ func Write1String(o *Ops, n int, ref1 string) []byte {
|
||||
return o.data[len(o.data)-n:]
|
||||
}
|
||||
|
||||
func Write2(o *Ops, n int, ref1, ref2 interface{}) []byte {
|
||||
func Write2(o *Ops, n int, ref1, ref2 any) []byte {
|
||||
o.data = append(o.data, make([]byte, n)...)
|
||||
o.refs = append(o.refs, ref1, ref2)
|
||||
return o.data[len(o.data)-n:]
|
||||
}
|
||||
|
||||
func Write2String(o *Ops, n int, ref1 interface{}, ref2 string) []byte {
|
||||
func Write2String(o *Ops, n int, ref1 any, ref2 string) []byte {
|
||||
o.data = append(o.data, make([]byte, n)...)
|
||||
o.stringRefs = append(o.stringRefs, ref2)
|
||||
o.refs = append(o.refs, ref1, &o.stringRefs[len(o.stringRefs)-1])
|
||||
return o.data[len(o.data)-n:]
|
||||
}
|
||||
|
||||
func Write3(o *Ops, n int, ref1, ref2, ref3 interface{}) []byte {
|
||||
func Write3(o *Ops, n int, ref1, ref2, ref3 any) []byte {
|
||||
o.data = append(o.data, make([]byte, n)...)
|
||||
o.refs = append(o.refs, ref1, ref2, ref3)
|
||||
return o.data[len(o.data)-n:]
|
||||
@@ -425,28 +404,19 @@ var opProps = [0x100]opProp{
|
||||
TypePopTransform: {Size: TypePopTransformLen, NumRefs: 0},
|
||||
TypePushOpacity: {Size: TypePushOpacityLen, NumRefs: 0},
|
||||
TypePopOpacity: {Size: TypePopOpacityLen, NumRefs: 0},
|
||||
TypeInvalidate: {Size: TypeRedrawLen, NumRefs: 0},
|
||||
TypeImage: {Size: TypeImageLen, NumRefs: 2},
|
||||
TypePaint: {Size: TypePaintLen, NumRefs: 0},
|
||||
TypeColor: {Size: TypeColorLen, NumRefs: 0},
|
||||
TypeLinearGradient: {Size: TypeLinearGradientLen, NumRefs: 0},
|
||||
TypePass: {Size: TypePassLen, NumRefs: 0},
|
||||
TypePopPass: {Size: TypePopPassLen, NumRefs: 0},
|
||||
TypePointerInput: {Size: TypePointerInputLen, 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},
|
||||
TypeInput: {Size: TypeInputLen, NumRefs: 1},
|
||||
TypeKeyInputHint: {Size: TypeKeyInputHintLen, NumRefs: 1},
|
||||
TypeSave: {Size: TypeSaveLen, NumRefs: 0},
|
||||
TypeLoad: {Size: TypeLoadLen, NumRefs: 0},
|
||||
TypeAux: {Size: TypeAuxLen, NumRefs: 0},
|
||||
TypeClip: {Size: TypeClipLen, NumRefs: 0},
|
||||
TypePopClip: {Size: TypePopClipLen, NumRefs: 0},
|
||||
TypeProfile: {Size: TypeProfileLen, NumRefs: 1},
|
||||
TypeCursor: {Size: TypeCursorLen, NumRefs: 0},
|
||||
TypePath: {Size: TypePathLen, NumRefs: 0},
|
||||
TypeStroke: {Size: TypeStrokeLen, NumRefs: 0},
|
||||
@@ -455,8 +425,6 @@ var opProps = [0x100]opProp{
|
||||
TypeSemanticClass: {Size: TypeSemanticClassLen, NumRefs: 0},
|
||||
TypeSemanticSelected: {Size: TypeSemanticSelectedLen, NumRefs: 0},
|
||||
TypeSemanticEnabled: {Size: TypeSemanticEnabledLen, NumRefs: 0},
|
||||
TypeSnippet: {Size: TypeSnippetLen, NumRefs: 2},
|
||||
TypeSelection: {Size: TypeSelectionLen, NumRefs: 1},
|
||||
TypeActionInput: {Size: TypeActionInputLen, NumRefs: 0},
|
||||
}
|
||||
|
||||
@@ -489,8 +457,6 @@ func (t OpType) String() string {
|
||||
return "PushOpacity"
|
||||
case TypePopOpacity:
|
||||
return "PopOpacity"
|
||||
case TypeInvalidate:
|
||||
return "Invalidate"
|
||||
case TypeImage:
|
||||
return "Image"
|
||||
case TypePaint:
|
||||
@@ -503,24 +469,10 @@ func (t OpType) String() string {
|
||||
return "Pass"
|
||||
case TypePopPass:
|
||||
return "PopPass"
|
||||
case TypePointerInput:
|
||||
return "PointerInput"
|
||||
case TypeClipboardRead:
|
||||
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 TypeInput:
|
||||
return "Input"
|
||||
case TypeKeyInputHint:
|
||||
return "KeyInputHint"
|
||||
case TypeSave:
|
||||
return "Save"
|
||||
case TypeLoad:
|
||||
@@ -531,8 +483,6 @@ func (t OpType) String() string {
|
||||
return "Clip"
|
||||
case TypePopClip:
|
||||
return "PopClip"
|
||||
case TypeProfile:
|
||||
return "Profile"
|
||||
case TypeCursor:
|
||||
return "Cursor"
|
||||
case TypePath:
|
||||
|
||||
@@ -20,7 +20,7 @@ type Reader struct {
|
||||
type EncodedOp struct {
|
||||
Key Key
|
||||
Data []byte
|
||||
Refs []interface{}
|
||||
Refs []any
|
||||
}
|
||||
|
||||
// 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:])
|
||||
}
|
||||
|
||||
func (m *macroOp) decode(data []byte, refs []interface{}) {
|
||||
func (m *macroOp) decode(data []byte, refs []any) {
|
||||
if len(data) < TypeCallLen || len(refs) < 1 || OpType(data[0]) != TypeCall {
|
||||
panic("invalid op")
|
||||
}
|
||||
|
||||
+22
-10
@@ -87,7 +87,7 @@ func (qs *StrokeQuads) lineTo(pt f32.Point) {
|
||||
func (qs *StrokeQuads) arc(f1, f2 f32.Point, angle float32) {
|
||||
pen := qs.pen()
|
||||
m, segments := ArcTransform(pen, f1.Add(pen), f2.Add(pen), angle)
|
||||
for i := 0; i < segments; i++ {
|
||||
for range segments {
|
||||
p0 := qs.pen()
|
||||
p1 := m.Transform(p0)
|
||||
p2 := m.Transform(p1)
|
||||
@@ -327,10 +327,26 @@ func strokePathNorm(p0, p1, p2 f32.Point, t, d float32) f32.Point {
|
||||
func rot90CW(p f32.Point) f32.Point { return f32.Pt(+p.Y, -p.X) }
|
||||
|
||||
func normPt(p f32.Point, l float32) f32.Point {
|
||||
if (p.X == 0 && p.Y == 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))
|
||||
l64 := float64(l)
|
||||
if math.Abs(d-l64) < 1e-10 {
|
||||
return f32.Point{}
|
||||
if math.Signbit(float64(l)) {
|
||||
return f32.Point{X: -p.X, Y: -p.Y}
|
||||
} else {
|
||||
return f32.Point{X: p.X, Y: p.Y}
|
||||
}
|
||||
}
|
||||
n := float32(l64 / d)
|
||||
return f32.Point{X: p.X * n, Y: p.Y * n}
|
||||
@@ -437,7 +453,6 @@ func flattenQuadBezier(qs StrokeQuads, p0, p1, p2 f32.Point, d, flatness float32
|
||||
}
|
||||
|
||||
func (qs *StrokeQuads) addLine(p0, ctrl, p1 f32.Point, t, d float32) {
|
||||
|
||||
switch i := len(*qs); i {
|
||||
case 0:
|
||||
p0 = p0.Add(strokePathNorm(p0, ctrl, p1, 0, d))
|
||||
@@ -470,7 +485,6 @@ func quadInterp(p, q f32.Point, t float32) f32.Point {
|
||||
// quadBezierSplit returns the pair of triplets (from,ctrl,to) Bézier curve,
|
||||
// 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) {
|
||||
|
||||
var (
|
||||
b0 = p0
|
||||
b1 = quadInterp(p0, p1, t)
|
||||
@@ -574,12 +588,10 @@ func ArcTransform(p, f1, f2 f32.Point, angle float32) (transform f32.Affine2D, s
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
θ = angle / float32(segments)
|
||||
ref f32.Affine2D // transform from absolute frame to ellipse-based one
|
||||
rot f32.Affine2D // rotation matrix for each segment
|
||||
inv f32.Affine2D // transform from ellipse-based frame to absolute one
|
||||
)
|
||||
θ := angle / float32(segments)
|
||||
ref := f32.AffineId() // transform from absolute frame to ellipse-based one
|
||||
rot := f32.AffineId() // rotation matrix for each segment
|
||||
inv := f32.AffineId() // transform from ellipse-based frame to absolute one
|
||||
center := f32.Point{
|
||||
X: 0.5 * (f1.X + f2.X),
|
||||
Y: 0.5 * (f1.Y + f2.Y),
|
||||
|
||||
@@ -9,6 +9,111 @@ import (
|
||||
"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) {
|
||||
type scenario struct {
|
||||
segments int
|
||||
@@ -52,7 +157,7 @@ func BenchmarkSplitCubic(b *testing.B) {
|
||||
from, ctrl0, ctrl1, to := s.from, s.ctrl0, s.ctrl1, s.to
|
||||
quads := make([]QuadSegment, s.segments)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
quads = SplitCubic(from, ctrl0, ctrl1, to, quads[:0])
|
||||
}
|
||||
if len(quads) != s.segments {
|
||||
|
||||
@@ -385,6 +385,7 @@ static VkResult vkQueuePresentKHR(PFN_vkQueuePresentKHR f, VkQueue queue, const
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -1167,7 +1168,6 @@ func CreateFramebuffer(d Device, rp RenderPass, view ImageView, width, height in
|
||||
return nilFramebuffer, fmt.Errorf("vulkan: vkCreateFramebuffer: %w", err)
|
||||
}
|
||||
return fbo, nil
|
||||
|
||||
}
|
||||
|
||||
func DestroyFramebuffer(d Device, f Framebuffer) {
|
||||
@@ -1893,27 +1893,27 @@ func BuildWriteDescriptorSetBuffer(set DescriptorSet, binding int, typ Descripto
|
||||
}
|
||||
}
|
||||
|
||||
func (r PushConstantRange) StageFlags() ShaderStageFlags {
|
||||
func PushConstantRangeStageFlags(r PushConstantRange) ShaderStageFlags {
|
||||
return r.stageFlags
|
||||
}
|
||||
|
||||
func (r PushConstantRange) Offset() int {
|
||||
func PushConstantRangeOffset(r PushConstantRange) int {
|
||||
return int(r.offset)
|
||||
}
|
||||
|
||||
func (r PushConstantRange) Size() int {
|
||||
func PushConstantRangeSize(r PushConstantRange) int {
|
||||
return int(r.size)
|
||||
}
|
||||
|
||||
func (p QueueFamilyProperties) Flags() QueueFlags {
|
||||
func QueueFamilyPropertiesFlags(p QueueFamilyProperties) QueueFlags {
|
||||
return p.queueFlags
|
||||
}
|
||||
|
||||
func (c SurfaceCapabilities) MinExtent() image.Point {
|
||||
func SurfaceCapabilitiesMinExtent(c SurfaceCapabilities) image.Point {
|
||||
return image.Pt(int(c.minImageExtent.width), int(c.minImageExtent.height))
|
||||
}
|
||||
|
||||
func (c SurfaceCapabilities) MaxExtent() image.Point {
|
||||
func SurfaceCapabilitiesMaxExtent(c SurfaceCapabilities) image.Point {
|
||||
return image.Pt(int(c.maxImageExtent.width), int(c.maxImageExtent.height))
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ static VkResult vkCreateAndroidSurfaceKHR(PFN_vkCreateAndroidSurfaceKHR f, VkIns
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
@@ -19,6 +19,7 @@ static VkResult vkCreateWaylandSurfaceKHR(PFN_vkCreateWaylandSurfaceKHR f, VkIns
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
@@ -17,6 +17,7 @@ static VkResult vkCreateXlibSurfaceKHR(PFN_vkCreateXlibSurfaceKHR f, VkInstance
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
+11
-24
@@ -3,35 +3,22 @@
|
||||
package clipboard
|
||||
|
||||
import (
|
||||
"gioui.org/internal/ops"
|
||||
"io"
|
||||
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/op"
|
||||
)
|
||||
|
||||
// Event is generated when the clipboard content is requested.
|
||||
type Event struct {
|
||||
Text string
|
||||
// WriteCmd copies Text to the clipboard.
|
||||
type WriteCmd struct {
|
||||
Type string
|
||||
Data io.ReadCloser
|
||||
}
|
||||
|
||||
// ReadOp requests the text of the clipboard, delivered to
|
||||
// the current handler through an Event.
|
||||
type ReadOp struct {
|
||||
// ReadCmd requests the text of the clipboard, delivered to
|
||||
// the handler through an [io/transfer.DataEvent].
|
||||
type ReadCmd struct {
|
||||
Tag event.Tag
|
||||
}
|
||||
|
||||
// WriteOp copies Text to the clipboard.
|
||||
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() {}
|
||||
func (WriteCmd) ImplementsCommand() {}
|
||||
func (ReadCmd) ImplementsCommand() {}
|
||||
|
||||
+21
-35
@@ -1,47 +1,33 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
/*
|
||||
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 contains types for event handling.
|
||||
package event
|
||||
|
||||
// Queue maps an event handler key to the events
|
||||
// available to the handler.
|
||||
type Queue interface {
|
||||
// Events returns the available events for an
|
||||
// event handler tag.
|
||||
Events(t Tag) []Event
|
||||
}
|
||||
import (
|
||||
"gioui.org/internal/ops"
|
||||
"gioui.org/op"
|
||||
)
|
||||
|
||||
// Tag is the stable identifier for an event handler.
|
||||
// For a handler h, the tag is typically &h.
|
||||
type Tag interface{}
|
||||
type Tag any
|
||||
|
||||
// Event is the marker interface for events.
|
||||
type Event interface {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
// 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
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// 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
@@ -0,0 +1,364 @@
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
// 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,16 +1,16 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package router
|
||||
package input
|
||||
|
||||
import (
|
||||
"image"
|
||||
"io"
|
||||
"slices"
|
||||
|
||||
"gioui.org/f32"
|
||||
f32internal "gioui.org/internal/f32"
|
||||
"gioui.org/internal/ops"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/semantic"
|
||||
"gioui.org/io/system"
|
||||
@@ -18,14 +18,8 @@ import (
|
||||
)
|
||||
|
||||
type pointerQueue struct {
|
||||
hitTree []hitNode
|
||||
areas []areaNode
|
||||
cursor pointer.Cursor
|
||||
handlers map[event.Tag]*pointerHandler
|
||||
pointers []pointerInfo
|
||||
transfers []io.ReadCloser // pending data transfers
|
||||
|
||||
scratch []event.Tag
|
||||
hitTree []hitNode
|
||||
areas []areaNode
|
||||
|
||||
semantic struct {
|
||||
idsAssigned bool
|
||||
@@ -43,10 +37,15 @@ type hitNode struct {
|
||||
|
||||
// For handler nodes.
|
||||
tag event.Tag
|
||||
ktag event.Tag
|
||||
pass bool
|
||||
}
|
||||
|
||||
// pointerState is the input state related to pointer events.
|
||||
type pointerState struct {
|
||||
cursor pointer.Cursor
|
||||
pointers []pointerInfo
|
||||
}
|
||||
|
||||
type pointerInfo struct {
|
||||
id pointer.ID
|
||||
pressed bool
|
||||
@@ -63,17 +62,21 @@ type pointerInfo struct {
|
||||
}
|
||||
|
||||
type pointerHandler struct {
|
||||
area int
|
||||
active bool
|
||||
wantsGrab bool
|
||||
types pointer.Kind
|
||||
// areaPlusOne is the index into the list of pointerQueue.areas, plus 1.
|
||||
areaPlusOne int
|
||||
// setup tracks whether the handler has received
|
||||
// the pointer.Cancel event that resets its state.
|
||||
setup bool
|
||||
}
|
||||
|
||||
// pointerFilter represents the union of a set of pointer filters.
|
||||
type pointerFilter struct {
|
||||
kinds pointer.Kind
|
||||
// min and max horizontal/vertical scroll
|
||||
scrollRange image.Rectangle
|
||||
scrollX, scrollY pointer.ScrollRange
|
||||
|
||||
sourceMimes []string
|
||||
targetMimes []string
|
||||
offeredMime string
|
||||
data io.ReadCloser
|
||||
}
|
||||
|
||||
type areaOp struct {
|
||||
@@ -141,7 +144,9 @@ const (
|
||||
)
|
||||
|
||||
func (c *pointerCollector) resetState() {
|
||||
c.state = collectState{}
|
||||
c.state = collectState{
|
||||
t: f32.AffineId(),
|
||||
}
|
||||
c.nodeStack = c.nodeStack[:0]
|
||||
// Pop every node except the root.
|
||||
if len(c.q.hitTree) > 0 {
|
||||
@@ -229,33 +234,18 @@ func (c *pointerCollector) addHitNode(n hitNode) {
|
||||
}
|
||||
|
||||
// newHandler returns the current handler or a new one for tag.
|
||||
func (c *pointerCollector) newHandler(tag event.Tag, events *handlerEvents) *pointerHandler {
|
||||
func (c *pointerCollector) newHandler(tag event.Tag, state *pointerHandler) {
|
||||
areaID := c.currentArea()
|
||||
c.addHitNode(hitNode{
|
||||
area: areaID,
|
||||
tag: tag,
|
||||
pass: c.state.pass > 0,
|
||||
})
|
||||
h, ok := c.q.handlers[tag]
|
||||
if !ok {
|
||||
h = new(pointerHandler)
|
||||
c.q.handlers[tag] = h
|
||||
// Cancel handlers on (each) first appearance, but don't
|
||||
// trigger redraw.
|
||||
events.AddNoRedraw(tag, pointer.Event{Kind: pointer.Cancel})
|
||||
}
|
||||
h.active = true
|
||||
h.area = areaID
|
||||
return h
|
||||
state.areaPlusOne = areaID + 1
|
||||
}
|
||||
|
||||
func (c *pointerCollector) keyInputOp(op key.InputOp) {
|
||||
areaID := c.currentArea()
|
||||
c.addHitNode(hitNode{
|
||||
area: areaID,
|
||||
ktag: op.Tag,
|
||||
pass: true,
|
||||
})
|
||||
func (s *pointerHandler) Reset() {
|
||||
s.areaPlusOne = 0
|
||||
}
|
||||
|
||||
func (c *pointerCollector) actionInputOp(act system.Action) {
|
||||
@@ -264,21 +254,108 @@ func (c *pointerCollector) actionInputOp(act system.Action) {
|
||||
area.action = act
|
||||
}
|
||||
|
||||
func (c *pointerCollector) inputOp(op pointer.InputOp, events *handlerEvents) {
|
||||
func (q *pointerQueue) grab(state pointerState, req pointer.GrabCmd) (pointerState, []taggedEvent) {
|
||||
var evts []taggedEvent
|
||||
for _, p := range state.pointers {
|
||||
if !p.pressed || p.id != req.ID {
|
||||
continue
|
||||
}
|
||||
// Verify that the grabber is among the handlers.
|
||||
found := slices.Contains(p.handlers, req.Tag)
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
// Drop other handlers that lost their grab.
|
||||
for i := len(p.handlers) - 1; i >= 0; i-- {
|
||||
if tag := p.handlers[i]; tag != req.Tag {
|
||||
evts = append(evts, taggedEvent{
|
||||
tag: tag,
|
||||
event: pointer.Event{Kind: pointer.Cancel},
|
||||
})
|
||||
state = dropHandler(state, tag)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
return state, evts
|
||||
}
|
||||
|
||||
func (c *pointerCollector) inputOp(tag event.Tag, state *pointerHandler) {
|
||||
areaID := c.currentArea()
|
||||
area := &c.q.areas[areaID]
|
||||
area.semantic.content.tag = op.Tag
|
||||
if op.Kinds&(pointer.Press|pointer.Release) != 0 {
|
||||
area.semantic.content.gestures |= ClickGesture
|
||||
area.semantic.content.tag = tag
|
||||
c.newHandler(tag, state)
|
||||
}
|
||||
|
||||
func (p *pointerFilter) Add(f event.Filter) {
|
||||
switch f := f.(type) {
|
||||
case transfer.SourceFilter:
|
||||
if slices.Contains(p.sourceMimes, f.Type) {
|
||||
return
|
||||
}
|
||||
p.sourceMimes = append(p.sourceMimes, f.Type)
|
||||
case transfer.TargetFilter:
|
||||
if slices.Contains(p.targetMimes, f.Type) {
|
||||
return
|
||||
}
|
||||
p.targetMimes = append(p.targetMimes, f.Type)
|
||||
case pointer.Filter:
|
||||
p.kinds = p.kinds | f.Kinds
|
||||
p.scrollX = p.scrollX.Union(f.ScrollX)
|
||||
p.scrollY = p.scrollY.Union(f.ScrollY)
|
||||
}
|
||||
if op.Kinds&pointer.Scroll != 0 {
|
||||
area.semantic.content.gestures |= ScrollGesture
|
||||
}
|
||||
|
||||
func (p *pointerFilter) Matches(e event.Event) bool {
|
||||
switch e := e.(type) {
|
||||
case pointer.Event:
|
||||
return e.Kind&p.kinds == e.Kind
|
||||
case transfer.CancelEvent, transfer.InitiateEvent:
|
||||
return len(p.sourceMimes) > 0 || len(p.targetMimes) > 0
|
||||
case transfer.RequestEvent:
|
||||
if slices.Contains(p.sourceMimes, e.Type) {
|
||||
return true
|
||||
}
|
||||
case transfer.DataEvent:
|
||||
if slices.Contains(p.targetMimes, e.Type) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
area.semantic.valid = area.semantic.content.gestures != 0
|
||||
h := c.newHandler(op.Tag, events)
|
||||
h.wantsGrab = h.wantsGrab || op.Grab
|
||||
h.types = h.types | op.Kinds
|
||||
h.scrollRange = op.ScrollBounds
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *pointerFilter) Merge(p2 pointerFilter) {
|
||||
p.kinds = p.kinds | p2.kinds
|
||||
p.scrollX = p.scrollX.Union(p2.scrollX)
|
||||
p.scrollY = p.scrollY.Union(p2.scrollY)
|
||||
p.sourceMimes = append(p.sourceMimes, p2.sourceMimes...)
|
||||
p.targetMimes = append(p.targetMimes, p2.targetMimes...)
|
||||
}
|
||||
|
||||
// clampScroll splits a scroll distance in the remaining scroll and the
|
||||
// scroll accepted by the filter.
|
||||
func (p *pointerFilter) clampScroll(scroll f32.Point) (left, scrolled f32.Point) {
|
||||
left.X, scrolled.X = clampSplit(scroll.X, p.scrollX.Min, p.scrollX.Max)
|
||||
left.Y, scrolled.Y = clampSplit(scroll.Y, p.scrollY.Min, p.scrollY.Max)
|
||||
return
|
||||
}
|
||||
|
||||
func clampSplit(v float32, min, max int) (float32, float32) {
|
||||
if m := float32(max); v > m {
|
||||
return v - m, m
|
||||
}
|
||||
if m := float32(min); v < m {
|
||||
return v - m, m
|
||||
}
|
||||
return 0, v
|
||||
}
|
||||
|
||||
func (s *pointerHandler) ResetEvent() (event.Event, bool) {
|
||||
if s.setup {
|
||||
return nil, false
|
||||
}
|
||||
s.setup = true
|
||||
return pointer.Event{Kind: pointer.Cancel}, true
|
||||
}
|
||||
|
||||
func (c *pointerCollector) semanticLabel(lbl string) {
|
||||
@@ -322,23 +399,28 @@ func (c *pointerCollector) cursor(cursor pointer.Cursor) {
|
||||
area.cursor = cursor
|
||||
}
|
||||
|
||||
func (c *pointerCollector) sourceOp(op transfer.SourceOp, events *handlerEvents) {
|
||||
h := c.newHandler(op.Tag, events)
|
||||
h.sourceMimes = append(h.sourceMimes, op.Type)
|
||||
func (q *pointerQueue) offerData(handlers map[event.Tag]*handler, state pointerState, req transfer.OfferCmd) (pointerState, []taggedEvent) {
|
||||
var evts []taggedEvent
|
||||
for i, p := range state.pointers {
|
||||
if p.dataSource != req.Tag {
|
||||
continue
|
||||
}
|
||||
if p.dataTarget != nil {
|
||||
evts = append(evts, taggedEvent{tag: p.dataTarget, event: transfer.DataEvent{
|
||||
Type: req.Type,
|
||||
Open: func() io.ReadCloser {
|
||||
return req.Data
|
||||
},
|
||||
}})
|
||||
}
|
||||
state.pointers = slices.Clone(state.pointers)
|
||||
state.pointers[i], evts = q.deliverTransferCancelEvent(handlers, p, evts)
|
||||
break
|
||||
}
|
||||
return state, evts
|
||||
}
|
||||
|
||||
func (c *pointerCollector) targetOp(op transfer.TargetOp, events *handlerEvents) {
|
||||
h := c.newHandler(op.Tag, events)
|
||||
h.targetMimes = append(h.targetMimes, op.Type)
|
||||
}
|
||||
|
||||
func (c *pointerCollector) offerOp(op transfer.OfferOp, events *handlerEvents) {
|
||||
h := c.newHandler(op.Tag, events)
|
||||
h.offeredMime = op.Type
|
||||
h.data = op.Data
|
||||
}
|
||||
|
||||
func (c *pointerCollector) reset() {
|
||||
func (c *pointerCollector) Reset() {
|
||||
c.q.reset()
|
||||
c.resetState()
|
||||
c.ensureRoot()
|
||||
@@ -493,20 +575,6 @@ func (q *pointerQueue) hitTest(pos f32.Point, onNode func(*hitNode) bool) pointe
|
||||
return cursor
|
||||
}
|
||||
|
||||
func (q *pointerQueue) opHit(pos f32.Point) ([]event.Tag, pointer.Cursor) {
|
||||
hits := q.scratch[:0]
|
||||
cursor := q.hitTest(pos, func(n *hitNode) bool {
|
||||
if n.tag != nil {
|
||||
if _, exists := q.handlers[n.tag]; exists {
|
||||
hits = addHandler(hits, n.tag)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
q.scratch = hits[:0]
|
||||
return hits, cursor
|
||||
}
|
||||
|
||||
func (q *pointerQueue) invTransform(areaIdx int, p f32.Point) f32.Point {
|
||||
if areaIdx == -1 {
|
||||
return p
|
||||
@@ -531,24 +599,13 @@ func (q *pointerQueue) hit(areaIdx int, p f32.Point) (bool, pointer.Cursor) {
|
||||
}
|
||||
|
||||
func (q *pointerQueue) reset() {
|
||||
if q.handlers == nil {
|
||||
q.handlers = make(map[event.Tag]*pointerHandler)
|
||||
}
|
||||
for _, h := range q.handlers {
|
||||
// Reset handler.
|
||||
h.active = false
|
||||
h.wantsGrab = false
|
||||
h.types = 0
|
||||
h.sourceMimes = h.sourceMimes[:0]
|
||||
h.targetMimes = h.targetMimes[:0]
|
||||
}
|
||||
q.hitTree = q.hitTree[:0]
|
||||
q.areas = q.areas[:0]
|
||||
q.semantic.idsAssigned = false
|
||||
for k, ids := range q.semantic.contentIDs {
|
||||
for i := len(ids) - 1; i >= 0; i-- {
|
||||
if !ids[i].used {
|
||||
ids = append(ids[:i], ids[i+1:]...)
|
||||
ids = slices.Delete(ids, i, i+1)
|
||||
} else {
|
||||
ids[i].used = false
|
||||
}
|
||||
@@ -559,80 +616,71 @@ func (q *pointerQueue) reset() {
|
||||
delete(q.semantic.contentIDs, k)
|
||||
}
|
||||
}
|
||||
for _, rc := range q.transfers {
|
||||
if rc != nil {
|
||||
rc.Close()
|
||||
}
|
||||
}
|
||||
q.transfers = nil
|
||||
}
|
||||
|
||||
func (q *pointerQueue) Frame(events *handlerEvents) {
|
||||
for k, h := range q.handlers {
|
||||
if !h.active {
|
||||
q.dropHandler(nil, k)
|
||||
delete(q.handlers, k)
|
||||
}
|
||||
if h.wantsGrab {
|
||||
for _, p := range q.pointers {
|
||||
if !p.pressed {
|
||||
continue
|
||||
}
|
||||
for i, k2 := range p.handlers {
|
||||
if k2 == k {
|
||||
// Drop other handlers that lost their grab.
|
||||
dropped := q.scratch[:0]
|
||||
dropped = append(dropped, p.handlers[:i]...)
|
||||
dropped = append(dropped, p.handlers[i+1:]...)
|
||||
for _, tag := range dropped {
|
||||
q.dropHandler(events, tag)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
func (q *pointerQueue) Frame(handlers map[event.Tag]*handler, state pointerState) (pointerState, []taggedEvent) {
|
||||
for _, h := range handlers {
|
||||
if h.pointer.areaPlusOne != 0 {
|
||||
area := &q.areas[h.pointer.areaPlusOne-1]
|
||||
if h.filter.pointer.kinds&(pointer.Press|pointer.Release) != 0 {
|
||||
area.semantic.content.gestures |= ClickGesture
|
||||
}
|
||||
if h.filter.pointer.kinds&pointer.Scroll != 0 {
|
||||
area.semantic.content.gestures |= ScrollGesture
|
||||
}
|
||||
area.semantic.valid = area.semantic.content.gestures != 0
|
||||
}
|
||||
}
|
||||
for i := range q.pointers {
|
||||
p := &q.pointers[i]
|
||||
q.deliverEnterLeaveEvents(p, events, p.last)
|
||||
q.deliverTransferDataEvent(p, events)
|
||||
var evts []taggedEvent
|
||||
for i, p := range state.pointers {
|
||||
changed := false
|
||||
p, evts, state.cursor, changed = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, p.last)
|
||||
if changed {
|
||||
state.pointers = slices.Clone(state.pointers)
|
||||
state.pointers[i] = p
|
||||
}
|
||||
}
|
||||
return state, evts
|
||||
}
|
||||
|
||||
func (q *pointerQueue) dropHandler(events *handlerEvents, tag event.Tag) {
|
||||
if events != nil {
|
||||
events.Add(tag, pointer.Event{Kind: pointer.Cancel})
|
||||
}
|
||||
for i := range q.pointers {
|
||||
p := &q.pointers[i]
|
||||
for i := len(p.handlers) - 1; i >= 0; i-- {
|
||||
if p.handlers[i] == tag {
|
||||
p.handlers = append(p.handlers[:i], p.handlers[i+1:]...)
|
||||
func dropHandler(state pointerState, tag event.Tag) pointerState {
|
||||
pointers := state.pointers
|
||||
state.pointers = nil
|
||||
for _, p := range pointers {
|
||||
handlers := p.handlers
|
||||
p.handlers = nil
|
||||
for _, h := range handlers {
|
||||
if h != tag {
|
||||
p.handlers = append(p.handlers, h)
|
||||
}
|
||||
}
|
||||
for i := len(p.entered) - 1; i >= 0; i-- {
|
||||
if p.entered[i] == tag {
|
||||
p.entered = append(p.entered[:i], p.entered[i+1:]...)
|
||||
entered := p.entered
|
||||
p.entered = nil
|
||||
for _, h := range entered {
|
||||
if h != tag {
|
||||
p.entered = append(p.entered, h)
|
||||
}
|
||||
}
|
||||
state.pointers = append(state.pointers, p)
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
// pointerOf returns the pointerInfo index corresponding to the pointer in e.
|
||||
func (q *pointerQueue) pointerOf(e pointer.Event) int {
|
||||
for i, p := range q.pointers {
|
||||
func (s pointerState) pointerOf(e pointer.Event) (pointerState, int) {
|
||||
for i, p := range s.pointers {
|
||||
if p.id == e.PointerID {
|
||||
return i
|
||||
return s, i
|
||||
}
|
||||
}
|
||||
q.pointers = append(q.pointers, pointerInfo{id: e.PointerID})
|
||||
return len(q.pointers) - 1
|
||||
n := len(s.pointers)
|
||||
s.pointers = append(s.pointers[:n:n], pointerInfo{id: e.PointerID})
|
||||
return s, len(s.pointers) - 1
|
||||
}
|
||||
|
||||
// Deliver is like Push, but delivers an event to a particular area.
|
||||
func (q *pointerQueue) Deliver(areaIdx int, e pointer.Event, events *handlerEvents) {
|
||||
var sx, sy = e.Scroll.X, e.Scroll.Y
|
||||
func (q *pointerQueue) Deliver(handlers map[event.Tag]*handler, areaIdx int, e pointer.Event) []taggedEvent {
|
||||
scroll := e.Scroll
|
||||
idx := len(q.hitTree) - 1
|
||||
// Locate first potential receiver.
|
||||
for idx != -1 {
|
||||
@@ -642,31 +690,28 @@ func (q *pointerQueue) Deliver(areaIdx int, e pointer.Event, events *handlerEven
|
||||
}
|
||||
idx--
|
||||
}
|
||||
var evts []taggedEvent
|
||||
for idx != -1 {
|
||||
n := &q.hitTree[idx]
|
||||
idx = n.next
|
||||
if n.tag == nil {
|
||||
continue
|
||||
}
|
||||
h := q.handlers[n.tag]
|
||||
if e.Kind&h.types == 0 {
|
||||
h, ok := handlers[n.tag]
|
||||
if !ok || !h.filter.pointer.Matches(e) {
|
||||
continue
|
||||
}
|
||||
e := e
|
||||
if e.Kind == pointer.Scroll {
|
||||
if sx == 0 && sy == 0 {
|
||||
if scroll == (f32.Point{}) {
|
||||
break
|
||||
}
|
||||
// Distribute the scroll to the handler based on its ScrollRange.
|
||||
sx, e.Scroll.X = setScrollEvent(sx, h.scrollRange.Min.X, h.scrollRange.Max.X)
|
||||
sy, e.Scroll.Y = setScrollEvent(sy, h.scrollRange.Min.Y, h.scrollRange.Max.Y)
|
||||
scroll, e.Scroll = h.filter.pointer.clampScroll(scroll)
|
||||
}
|
||||
e.Position = q.invTransform(h.area, e.Position)
|
||||
events.Add(n.tag, e)
|
||||
e.Position = q.invTransform(h.pointer.areaPlusOne-1, e.Position)
|
||||
evts = append(evts, taggedEvent{tag: n.tag, event: e})
|
||||
if e.Kind != pointer.Scroll {
|
||||
break
|
||||
}
|
||||
}
|
||||
return evts
|
||||
}
|
||||
|
||||
// SemanticArea returns the sematic content for area, and its parent area.
|
||||
@@ -682,106 +727,123 @@ func (q *pointerQueue) SemanticArea(areaIdx int) (semanticContent, int) {
|
||||
return semanticContent{}, -1
|
||||
}
|
||||
|
||||
func (q *pointerQueue) Push(e pointer.Event, events *handlerEvents) {
|
||||
func (q *pointerQueue) Push(handlers map[event.Tag]*handler, state pointerState, e pointer.Event) (pointerState, []taggedEvent) {
|
||||
var evts []taggedEvent
|
||||
if e.Kind == pointer.Cancel {
|
||||
q.pointers = q.pointers[:0]
|
||||
for k := range q.handlers {
|
||||
q.dropHandler(events, k)
|
||||
for k := range handlers {
|
||||
evts = append(evts, taggedEvent{
|
||||
event: pointer.Event{Kind: pointer.Cancel},
|
||||
tag: k,
|
||||
})
|
||||
}
|
||||
return
|
||||
state.pointers = nil
|
||||
return state, evts
|
||||
}
|
||||
pidx := q.pointerOf(e)
|
||||
p := &q.pointers[pidx]
|
||||
p.last = e
|
||||
state, pidx := state.pointerOf(e)
|
||||
p := state.pointers[pidx]
|
||||
|
||||
switch e.Kind {
|
||||
case pointer.Press:
|
||||
q.deliverEnterLeaveEvents(p, events, e)
|
||||
p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e)
|
||||
p.pressed = true
|
||||
q.deliverEvent(p, events, e)
|
||||
evts = q.deliverEvent(handlers, p, evts, e)
|
||||
case pointer.Move:
|
||||
if p.pressed {
|
||||
e.Kind = pointer.Drag
|
||||
}
|
||||
q.deliverEnterLeaveEvents(p, events, e)
|
||||
q.deliverEvent(p, events, e)
|
||||
p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e)
|
||||
evts = q.deliverEvent(handlers, p, evts, e)
|
||||
if p.pressed {
|
||||
q.deliverDragEvent(p, events)
|
||||
p, evts = q.deliverDragEvent(handlers, p, evts)
|
||||
}
|
||||
case pointer.Release:
|
||||
q.deliverEvent(p, events, e)
|
||||
evts = q.deliverEvent(handlers, p, evts, e)
|
||||
p.pressed = false
|
||||
q.deliverEnterLeaveEvents(p, events, e)
|
||||
q.deliverDropEvent(p, events)
|
||||
p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e)
|
||||
p, evts = q.deliverDropEvent(handlers, p, evts)
|
||||
case pointer.Scroll:
|
||||
q.deliverEnterLeaveEvents(p, events, e)
|
||||
q.deliverEvent(p, events, e)
|
||||
p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e)
|
||||
evts = q.deliverEvent(handlers, p, evts, e)
|
||||
default:
|
||||
panic("unsupported pointer event type")
|
||||
}
|
||||
|
||||
p.last = e
|
||||
|
||||
if !p.pressed && len(p.entered) == 0 {
|
||||
// No longer need to track pointer.
|
||||
q.pointers = append(q.pointers[:pidx], q.pointers[pidx+1:]...)
|
||||
state.pointers = slices.Concat(state.pointers[:pidx:pidx], state.pointers[pidx+1:])
|
||||
} else {
|
||||
state.pointers = slices.Clone(state.pointers)
|
||||
state.pointers[pidx] = p
|
||||
}
|
||||
return state, evts
|
||||
}
|
||||
|
||||
func (q *pointerQueue) deliverEvent(p *pointerInfo, events *handlerEvents, e pointer.Event) {
|
||||
foremost := true
|
||||
func (q *pointerQueue) deliverEvent(handlers map[event.Tag]*handler, p pointerInfo, evts []taggedEvent, e pointer.Event) []taggedEvent {
|
||||
if p.pressed && len(p.handlers) == 1 {
|
||||
e.Priority = pointer.Grabbed
|
||||
foremost = false
|
||||
}
|
||||
var sx, sy = e.Scroll.X, e.Scroll.Y
|
||||
scroll := e.Scroll
|
||||
for _, k := range p.handlers {
|
||||
h := q.handlers[k]
|
||||
if e.Kind == pointer.Scroll {
|
||||
if sx == 0 && sy == 0 {
|
||||
return
|
||||
}
|
||||
// Distribute the scroll to the handler based on its ScrollRange.
|
||||
sx, e.Scroll.X = setScrollEvent(sx, h.scrollRange.Min.X, h.scrollRange.Max.X)
|
||||
sy, e.Scroll.Y = setScrollEvent(sy, h.scrollRange.Min.Y, h.scrollRange.Max.Y)
|
||||
}
|
||||
if e.Kind&h.types == 0 {
|
||||
h, ok := handlers[k]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
e := e
|
||||
if foremost {
|
||||
foremost = false
|
||||
e.Priority = pointer.Foremost
|
||||
f := h.filter.pointer
|
||||
if !f.Matches(e) {
|
||||
continue
|
||||
}
|
||||
e.Position = q.invTransform(h.area, e.Position)
|
||||
events.Add(k, e)
|
||||
if e.Kind == pointer.Scroll {
|
||||
if scroll == (f32.Point{}) {
|
||||
return evts
|
||||
}
|
||||
scroll, e.Scroll = f.clampScroll(scroll)
|
||||
}
|
||||
e := e
|
||||
e.Position = q.invTransform(h.pointer.areaPlusOne-1, e.Position)
|
||||
evts = append(evts, taggedEvent{event: e, tag: k})
|
||||
}
|
||||
return evts
|
||||
}
|
||||
|
||||
func (q *pointerQueue) deliverEnterLeaveEvents(p *pointerInfo, events *handlerEvents, e pointer.Event) {
|
||||
func (q *pointerQueue) deliverEnterLeaveEvents(handlers map[event.Tag]*handler, cursor pointer.Cursor, p pointerInfo, evts []taggedEvent, e pointer.Event) (pointerInfo, []taggedEvent, pointer.Cursor, bool) {
|
||||
changed := false
|
||||
var hits []event.Tag
|
||||
if e.Source != pointer.Mouse && !p.pressed && e.Kind != pointer.Press {
|
||||
// Consider non-mouse pointers leaving when they're released.
|
||||
} else {
|
||||
hits, q.cursor = q.opHit(e.Position)
|
||||
if p.pressed {
|
||||
// Filter out non-participating handlers,
|
||||
// except potential transfer targets when a transfer has been initiated.
|
||||
var hitsHaveTarget bool
|
||||
if p.dataSource != nil {
|
||||
transferSource := q.handlers[p.dataSource]
|
||||
for _, hit := range hits {
|
||||
if _, ok := firstMimeMatch(transferSource, q.handlers[hit]); ok {
|
||||
hitsHaveTarget = true
|
||||
break
|
||||
var transSrc *pointerFilter
|
||||
if p.dataSource != nil {
|
||||
transSrc = &handlers[p.dataSource].filter.pointer
|
||||
}
|
||||
cursor = q.hitTest(e.Position, func(n *hitNode) bool {
|
||||
h, ok := handlers[n.tag]
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
add := true
|
||||
if p.pressed {
|
||||
add = false
|
||||
// Filter out non-participating handlers,
|
||||
// except potential transfer targets when a transfer has been initiated.
|
||||
if _, found := searchTag(p.handlers, n.tag); found {
|
||||
add = true
|
||||
}
|
||||
if transSrc != nil {
|
||||
if _, ok := firstMimeMatch(transSrc, &h.filter.pointer); ok {
|
||||
add = true
|
||||
}
|
||||
}
|
||||
}
|
||||
for i := len(hits) - 1; i >= 0; i-- {
|
||||
if _, found := searchTag(p.handlers, hits[i]); !found && !hitsHaveTarget {
|
||||
hits = append(hits[:i], hits[i+1:]...)
|
||||
}
|
||||
if add {
|
||||
hits = addHandler(hits, n.tag)
|
||||
}
|
||||
} else {
|
||||
p.handlers = append(p.handlers[:0], hits...)
|
||||
return true
|
||||
})
|
||||
if !p.pressed {
|
||||
changed = true
|
||||
p.handlers = hits
|
||||
}
|
||||
}
|
||||
// Deliver Leave events.
|
||||
@@ -789,111 +851,94 @@ func (q *pointerQueue) deliverEnterLeaveEvents(p *pointerInfo, events *handlerEv
|
||||
if _, found := searchTag(hits, k); found {
|
||||
continue
|
||||
}
|
||||
h := q.handlers[k]
|
||||
h, ok := handlers[k]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
changed = true
|
||||
e := e
|
||||
e.Kind = pointer.Leave
|
||||
|
||||
if e.Kind&h.types != 0 {
|
||||
e := e
|
||||
e.Position = q.invTransform(h.area, e.Position)
|
||||
events.Add(k, e)
|
||||
if h.filter.pointer.Matches(e) {
|
||||
e.Position = q.invTransform(h.pointer.areaPlusOne-1, e.Position)
|
||||
evts = append(evts, taggedEvent{tag: k, event: e})
|
||||
}
|
||||
}
|
||||
// Deliver Enter events.
|
||||
for _, k := range hits {
|
||||
h := q.handlers[k]
|
||||
if _, found := searchTag(p.entered, k); found {
|
||||
continue
|
||||
}
|
||||
h, ok := handlers[k]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
changed = true
|
||||
e := e
|
||||
e.Kind = pointer.Enter
|
||||
|
||||
if e.Kind&h.types != 0 {
|
||||
e := e
|
||||
e.Position = q.invTransform(h.area, e.Position)
|
||||
events.Add(k, e)
|
||||
if h.filter.pointer.Matches(e) {
|
||||
e.Position = q.invTransform(h.pointer.areaPlusOne-1, e.Position)
|
||||
evts = append(evts, taggedEvent{tag: k, event: e})
|
||||
}
|
||||
}
|
||||
p.entered = append(p.entered[:0], hits...)
|
||||
p.entered = hits
|
||||
return p, evts, cursor, changed
|
||||
}
|
||||
|
||||
func (q *pointerQueue) deliverDragEvent(p *pointerInfo, events *handlerEvents) {
|
||||
func (q *pointerQueue) deliverDragEvent(handlers map[event.Tag]*handler, p pointerInfo, evts []taggedEvent) (pointerInfo, []taggedEvent) {
|
||||
if p.dataSource != nil {
|
||||
return
|
||||
return p, evts
|
||||
}
|
||||
// Identify the data source.
|
||||
for _, k := range p.entered {
|
||||
src := q.handlers[k]
|
||||
src := &handlers[k].filter.pointer
|
||||
if len(src.sourceMimes) == 0 {
|
||||
continue
|
||||
}
|
||||
// One data source handler per pointer.
|
||||
p.dataSource = k
|
||||
// Notify all potential targets.
|
||||
for k, tgt := range q.handlers {
|
||||
if _, ok := firstMimeMatch(src, tgt); ok {
|
||||
events.Add(k, transfer.InitiateEvent{})
|
||||
for k, tgt := range handlers {
|
||||
if _, ok := firstMimeMatch(src, &tgt.filter.pointer); ok {
|
||||
evts = append(evts, taggedEvent{tag: k, event: transfer.InitiateEvent{}})
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
return p, evts
|
||||
}
|
||||
|
||||
func (q *pointerQueue) deliverDropEvent(p *pointerInfo, events *handlerEvents) {
|
||||
func (q *pointerQueue) deliverDropEvent(handlers map[event.Tag]*handler, p pointerInfo, evts []taggedEvent) (pointerInfo, []taggedEvent) {
|
||||
if p.dataSource == nil {
|
||||
return
|
||||
return p, evts
|
||||
}
|
||||
// Request data from the source.
|
||||
src := q.handlers[p.dataSource]
|
||||
src := &handlers[p.dataSource].filter.pointer
|
||||
for _, k := range p.entered {
|
||||
h := q.handlers[k]
|
||||
if m, ok := firstMimeMatch(src, h); ok {
|
||||
h := handlers[k]
|
||||
if m, ok := firstMimeMatch(src, &h.filter.pointer); ok {
|
||||
p.dataTarget = k
|
||||
events.Add(p.dataSource, transfer.RequestEvent{Type: m})
|
||||
return
|
||||
evts = append(evts, taggedEvent{tag: p.dataSource, event: transfer.RequestEvent{Type: m}})
|
||||
return p, evts
|
||||
}
|
||||
}
|
||||
// No valid target found, abort.
|
||||
q.deliverTransferCancelEvent(p, events)
|
||||
return q.deliverTransferCancelEvent(handlers, p, evts)
|
||||
}
|
||||
|
||||
func (q *pointerQueue) deliverTransferDataEvent(p *pointerInfo, events *handlerEvents) {
|
||||
if p.dataSource == nil {
|
||||
return
|
||||
}
|
||||
src := q.handlers[p.dataSource]
|
||||
if src.data == nil {
|
||||
// Data not received yet.
|
||||
return
|
||||
}
|
||||
if p.dataTarget == nil {
|
||||
q.deliverTransferCancelEvent(p, events)
|
||||
return
|
||||
}
|
||||
// Send the offered data to the target.
|
||||
transferIdx := len(q.transfers)
|
||||
events.Add(p.dataTarget, transfer.DataEvent{
|
||||
Type: src.offeredMime,
|
||||
Open: func() io.ReadCloser {
|
||||
q.transfers[transferIdx] = nil
|
||||
return src.data
|
||||
},
|
||||
})
|
||||
q.transfers = append(q.transfers, src.data)
|
||||
p.dataTarget = nil
|
||||
}
|
||||
|
||||
func (q *pointerQueue) deliverTransferCancelEvent(p *pointerInfo, events *handlerEvents) {
|
||||
events.Add(p.dataSource, transfer.CancelEvent{})
|
||||
func (q *pointerQueue) deliverTransferCancelEvent(handlers map[event.Tag]*handler, p pointerInfo, evts []taggedEvent) (pointerInfo, []taggedEvent) {
|
||||
evts = append(evts, taggedEvent{tag: p.dataSource, event: transfer.CancelEvent{}})
|
||||
// Cancel all potential targets.
|
||||
src := q.handlers[p.dataSource]
|
||||
for k, h := range q.handlers {
|
||||
if _, ok := firstMimeMatch(src, h); ok {
|
||||
events.Add(k, transfer.CancelEvent{})
|
||||
src := &handlers[p.dataSource].filter.pointer
|
||||
for k, h := range handlers {
|
||||
if _, ok := firstMimeMatch(src, &h.filter.pointer); ok {
|
||||
evts = append(evts, taggedEvent{tag: k, event: transfer.CancelEvent{}})
|
||||
}
|
||||
}
|
||||
src.offeredMime = ""
|
||||
src.data = nil
|
||||
p.dataSource = nil
|
||||
p.dataTarget = nil
|
||||
return p, evts
|
||||
}
|
||||
|
||||
// ClipFor clips r to the parents of area.
|
||||
@@ -919,21 +964,17 @@ func searchTag(tags []event.Tag, tag event.Tag) (int, bool) {
|
||||
|
||||
// addHandler adds tag to the slice if not present.
|
||||
func addHandler(tags []event.Tag, tag event.Tag) []event.Tag {
|
||||
for _, t := range tags {
|
||||
if t == tag {
|
||||
return tags
|
||||
}
|
||||
if slices.Contains(tags, tag) {
|
||||
return tags
|
||||
}
|
||||
return append(tags, tag)
|
||||
}
|
||||
|
||||
// firstMimeMatch returns the first type match between src and tgt.
|
||||
func firstMimeMatch(src, tgt *pointerHandler) (first string, matched bool) {
|
||||
func firstMimeMatch(src, tgt *pointerFilter) (first string, matched bool) {
|
||||
for _, m1 := range tgt.targetMimes {
|
||||
for _, m2 := range src.sourceMimes {
|
||||
if m1 == m2 {
|
||||
return m1, true
|
||||
}
|
||||
if slices.Contains(src.sourceMimes, m1) {
|
||||
return m1, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
@@ -965,13 +1006,3 @@ func (a *areaNode) bounds() image.Rectangle {
|
||||
Max: a.trans.Transform(f32internal.FPt(a.area.rect.Max)),
|
||||
}.Round()
|
||||
}
|
||||
|
||||
func setScrollEvent(scroll float32, min, max int) (left, scrolled float32) {
|
||||
if v := float32(max); scroll > v {
|
||||
return scroll - v, v
|
||||
}
|
||||
if v := float32(min); scroll < v {
|
||||
return scroll - v, v
|
||||
}
|
||||
return 0, scroll
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user