mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-03 16:35:36 +00:00
Compare commits
229 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 |
+2
-2
@@ -29,7 +29,7 @@ environment:
|
|||||||
tasks:
|
tasks:
|
||||||
- install_go: |
|
- install_go: |
|
||||||
mkdir -p /home/build/sdk
|
mkdir -p /home/build/sdk
|
||||||
curl -s https://dl.google.com/go/go1.19.11.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
curl -s https://dl.google.com/go/go1.24.2.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||||
- prepare_toolchain: |
|
- prepare_toolchain: |
|
||||||
mkdir -p $APPLE_TOOLCHAIN_ROOT
|
mkdir -p $APPLE_TOOLCHAIN_ROOT
|
||||||
cd $APPLE_TOOLCHAIN_ROOT
|
cd $APPLE_TOOLCHAIN_ROOT
|
||||||
@@ -71,4 +71,4 @@ tasks:
|
|||||||
CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-macos GOOS=darwin CGO_ENABLED=1 go build ./...
|
CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-macos GOOS=darwin CGO_ENABLED=1 go build ./...
|
||||||
- test_ios: |
|
- test_ios: |
|
||||||
cd gio
|
cd gio
|
||||||
CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-ios GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go build -tags ios ./...
|
CGO_CFLAGS=-Wno-deprecated-module-dot-map CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-ios GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go build -tags ios ./...
|
||||||
|
|||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
# SPDX-License-Identifier: Unlicense OR MIT
|
# SPDX-License-Identifier: Unlicense OR MIT
|
||||||
image: freebsd/13.x
|
image: freebsd/latest
|
||||||
packages:
|
packages:
|
||||||
- libX11
|
- libX11
|
||||||
- libxkbcommon
|
- libxkbcommon
|
||||||
@@ -16,7 +16,7 @@ environment:
|
|||||||
tasks:
|
tasks:
|
||||||
- install_go: |
|
- install_go: |
|
||||||
mkdir -p /home/build/sdk
|
mkdir -p /home/build/sdk
|
||||||
curl https://dl.google.com/go/go1.19.11.freebsd-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
curl https://dl.google.com/go/go1.24.2.freebsd-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||||
- test_gio: |
|
- test_gio: |
|
||||||
cd gio
|
cd gio
|
||||||
go test ./...
|
go test ./...
|
||||||
|
|||||||
+3
-1
@@ -40,7 +40,7 @@ secrets:
|
|||||||
tasks:
|
tasks:
|
||||||
- install_go: |
|
- install_go: |
|
||||||
mkdir -p /home/build/sdk
|
mkdir -p /home/build/sdk
|
||||||
curl -s https://dl.google.com/go/go1.19.11.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
curl -s https://dl.google.com/go/go1.24.2.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||||
- check_gofmt: |
|
- check_gofmt: |
|
||||||
cd gio
|
cd gio
|
||||||
test -z "$(gofmt -s -l .)"
|
test -z "$(gofmt -s -l .)"
|
||||||
@@ -80,6 +80,8 @@ tasks:
|
|||||||
unzip -q ndk.zip
|
unzip -q ndk.zip
|
||||||
rm ndk.zip
|
rm ndk.zip
|
||||||
mv android-ndk-* ndk-bundle
|
mv android-ndk-* ndk-bundle
|
||||||
|
# sdkmanager needs lots of file descriptors
|
||||||
|
ulimit -n 10000
|
||||||
yes|sdkmanager --licenses
|
yes|sdkmanager --licenses
|
||||||
sdkmanager "platforms;android-31" "build-tools;32.0.0"
|
sdkmanager "platforms;android-31" "build-tools;32.0.0"
|
||||||
- test_android: |
|
- test_android: |
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ environment:
|
|||||||
tasks:
|
tasks:
|
||||||
- install_go: |
|
- install_go: |
|
||||||
mkdir -p /home/build/sdk
|
mkdir -p /home/build/sdk
|
||||||
curl https://dl.google.com/go/go1.19.11.src.tar.gz | tar -C /home/build/sdk -xzf -
|
curl https://dl.google.com/go/go1.24.2.src.tar.gz | tar -C /home/build/sdk -xzf -
|
||||||
cd /home/build/sdk/go/src
|
cd /home/build/sdk/go/src
|
||||||
./make.bash
|
./make.bash
|
||||||
- test_gio: |
|
- test_gio: |
|
||||||
|
|||||||
+5
-1
@@ -65,8 +65,8 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal
|
|||||||
private final InputMethodManager imm;
|
private final InputMethodManager imm;
|
||||||
private final float scrollXScale;
|
private final float scrollXScale;
|
||||||
private final float scrollYScale;
|
private final float scrollYScale;
|
||||||
|
private final AccessibilityManager accessManager;
|
||||||
private int keyboardHint;
|
private int keyboardHint;
|
||||||
private AccessibilityManager accessManager;
|
|
||||||
|
|
||||||
private long nhandle;
|
private long nhandle;
|
||||||
|
|
||||||
@@ -259,6 +259,10 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setHighRefreshRate() {
|
private void setHighRefreshRate() {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Context context = getContext();
|
Context context = getContext();
|
||||||
Display display = context.getDisplay();
|
Display display = context.getDisplay();
|
||||||
Display.Mode[] supportedModes = display.getSupportedModes();
|
Display.Mode[] supportedModes = display.getSupportedModes();
|
||||||
|
|||||||
+93
-9
@@ -3,9 +3,16 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"image"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gioui.org/io/input"
|
||||||
|
"gioui.org/layout"
|
||||||
|
"gioui.org/op"
|
||||||
|
"gioui.org/unit"
|
||||||
)
|
)
|
||||||
|
|
||||||
// extraArgs contains extra arguments to append to
|
// extraArgs contains extra arguments to append to
|
||||||
@@ -20,23 +27,88 @@ var extraArgs string
|
|||||||
// On Android ID is the package property of AndroidManifest.xml,
|
// On Android ID is the package property of AndroidManifest.xml,
|
||||||
// on iOS ID is the CFBundleIdentifier of the app Info.plist,
|
// on iOS ID is the CFBundleIdentifier of the app Info.plist,
|
||||||
// on Wayland it is the toplevel app_id,
|
// on Wayland it is the toplevel app_id,
|
||||||
// on X11 it is the X11 XClassHint
|
// on X11 it is the X11 XClassHint.
|
||||||
//
|
//
|
||||||
// ID is set by the 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'" .
|
// go build -ldflags="-X 'gioui.org/app.ID=org.gioui.example.Kitchen'" .
|
||||||
//
|
//
|
||||||
// Note that ID is treated as a constant, and that changing it at runtime
|
// Note that ID is treated as a constant, and that changing it at runtime
|
||||||
// is not supported. 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 = ""
|
var ID = ""
|
||||||
|
|
||||||
func init() {
|
// A FrameEvent requests a new frame in the form of a list of
|
||||||
if extraArgs != "" {
|
// operations that describes the window content.
|
||||||
args := strings.Split(extraArgs, "|")
|
type FrameEvent struct {
|
||||||
os.Args = append(os.Args, args...)
|
// 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() {
|
func Main() {
|
||||||
osMain()
|
osMain()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (FrameEvent) ImplementsEvent() {}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if extraArgs != "" {
|
||||||
|
args := strings.Split(extraArgs, "|")
|
||||||
|
os.Args = append(os.Args, args...)
|
||||||
|
}
|
||||||
|
if ID == "" {
|
||||||
|
ID = filepath.Base(os.Args[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -60,10 +60,10 @@ func (c *d3d11Context) RenderTarget() (gpu.RenderTarget, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *d3d11Context) Present() error {
|
func (c *d3d11Context) Present() error {
|
||||||
err := c.swchain.Present(1, 0)
|
return wrapErr(c.swchain.Present(1, 0))
|
||||||
if err == nil {
|
}
|
||||||
return nil
|
|
||||||
}
|
func wrapErr(err error) error {
|
||||||
if err, ok := err.(d3d11.ErrorCode); ok {
|
if err, ok := err.(d3d11.ErrorCode); ok {
|
||||||
switch err.Code {
|
switch err.Code {
|
||||||
case d3d11.DXGI_STATUS_OCCLUDED:
|
case d3d11.DXGI_STATUS_OCCLUDED:
|
||||||
@@ -84,7 +84,7 @@ func (c *d3d11Context) Refresh() error {
|
|||||||
}
|
}
|
||||||
c.releaseFBO()
|
c.releaseFBO()
|
||||||
if err := c.swchain.ResizeBuffers(0, 0, 0, d3d11.DXGI_FORMAT_UNKNOWN, 0); err != nil {
|
if err := c.swchain.ResizeBuffers(0, 0, 0, d3d11.DXGI_FORMAT_UNKNOWN, 0); err != nil {
|
||||||
return err
|
return wrapErr(err)
|
||||||
}
|
}
|
||||||
c.width = width
|
c.width = width
|
||||||
c.height = height
|
c.height = height
|
||||||
|
|||||||
+11
-18
@@ -8,21 +8,20 @@ See https://gioui.org for instructions to set up and run Gio programs.
|
|||||||
|
|
||||||
# Windows
|
# Windows
|
||||||
|
|
||||||
Create a new Window by calling NewWindow. On mobile platforms or when Gio
|
A Window is run by calling its Event method in a loop. The first time a
|
||||||
is embedded in another project, NewWindow merely connects with a previously
|
method on Window is called, a new GUI window is created and shown. On mobile
|
||||||
created window.
|
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
|
The most important event is [FrameEvent] that prompts an update of the window
|
||||||
FrameEvent that prompts an update of the window contents.
|
contents.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
import "gioui.org/unit"
|
w := new(app.Window)
|
||||||
|
|
||||||
w := app.NewWindow()
|
|
||||||
for {
|
for {
|
||||||
e := w.NextEvent()
|
e := w.Event()
|
||||||
if e, ok := e.(system.FrameEvent); ok {
|
if e, ok := e.(app.FrameEvent); ok {
|
||||||
ops.Reset()
|
ops.Reset()
|
||||||
// Add operations to ops.
|
// Add operations to ops.
|
||||||
...
|
...
|
||||||
@@ -32,7 +31,7 @@ For example:
|
|||||||
}
|
}
|
||||||
|
|
||||||
A program must keep receiving events from the event channel until
|
A program must keep receiving events from the event channel until
|
||||||
DestroyEvent is received.
|
[DestroyEvent] is received.
|
||||||
|
|
||||||
# Main
|
# Main
|
||||||
|
|
||||||
@@ -51,18 +50,12 @@ For example, to display a blank but otherwise functional window:
|
|||||||
go func() {
|
go func() {
|
||||||
w := app.NewWindow()
|
w := app.NewWindow()
|
||||||
for {
|
for {
|
||||||
w.NextEvent()
|
w.Event()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
app.Main()
|
app.Main()
|
||||||
}
|
}
|
||||||
|
|
||||||
# Event queue
|
|
||||||
|
|
||||||
A FrameEvent's Queue method returns an event.Queue implementation that distributes
|
|
||||||
incoming events to the event handlers declared in the last frame.
|
|
||||||
See the gioui.org/io/event package for more information about event handlers.
|
|
||||||
|
|
||||||
# Permissions
|
# Permissions
|
||||||
|
|
||||||
The packages under gioui.org/app/permission should be imported
|
The packages under gioui.org/app/permission should be imported
|
||||||
|
|||||||
+4
-6
@@ -17,9 +17,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type androidContext struct {
|
type androidContext struct {
|
||||||
win *window
|
win *window
|
||||||
eglSurf egl.NativeWindowType
|
eglSurf egl.NativeWindowType
|
||||||
width, height int
|
|
||||||
*egl.Context
|
*egl.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,9 +44,8 @@ func (c *androidContext) Refresh() error {
|
|||||||
if err := c.win.setVisual(c.Context.VisualID()); err != nil {
|
if err := c.win.setVisual(c.Context.VisualID()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
win, width, height := c.win.nativeWindow()
|
win, _, _ := c.win.nativeWindow()
|
||||||
c.eglSurf = egl.NativeWindowType(unsafe.Pointer(win))
|
c.eglSurf = egl.NativeWindowType(unsafe.Pointer(win))
|
||||||
c.width, c.height = width, height
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +53,7 @@ func (c *androidContext) Lock() error {
|
|||||||
// The Android emulator creates a broken surface if it is not
|
// The Android emulator creates a broken surface if it is not
|
||||||
// created on the same thread as the context is made current.
|
// created on the same thread as the context is made current.
|
||||||
if c.eglSurf != nil {
|
if c.eglSurf != nil {
|
||||||
if err := c.Context.CreateSurface(c.eglSurf, c.width, c.height); err != nil {
|
if err := c.Context.CreateSurface(c.eglSurf); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.eglSurf = nil
|
c.eglSurf = nil
|
||||||
|
|||||||
+20
-13
@@ -38,7 +38,24 @@ func init() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &wlContext{Context: ctx, win: w}, nil
|
|
||||||
|
surf, width, height := w.surface()
|
||||||
|
if surf == nil {
|
||||||
|
return nil, errors.New("wayland: no surface")
|
||||||
|
}
|
||||||
|
eglWin := C.wl_egl_window_create(surf, C.int(width), C.int(height))
|
||||||
|
if eglWin == nil {
|
||||||
|
return nil, errors.New("wayland: wl_egl_window_create failed")
|
||||||
|
}
|
||||||
|
eglSurf := egl.NativeWindowType(uintptr(unsafe.Pointer(eglWin)))
|
||||||
|
if err := ctx.CreateSurface(eglSurf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// We're in charge of the frame callbacks, don't let eglSwapBuffers
|
||||||
|
// wait for callbacks that may never arrive.
|
||||||
|
ctx.EnableVSync(false)
|
||||||
|
|
||||||
|
return &wlContext{Context: ctx, win: w, eglWin: eglWin}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,22 +71,12 @@ func (c *wlContext) Release() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *wlContext) Refresh() error {
|
func (c *wlContext) Refresh() error {
|
||||||
c.Context.ReleaseSurface()
|
|
||||||
if c.eglWin != nil {
|
|
||||||
C.wl_egl_window_destroy(c.eglWin)
|
|
||||||
c.eglWin = nil
|
|
||||||
}
|
|
||||||
surf, width, height := c.win.surface()
|
surf, width, height := c.win.surface()
|
||||||
if surf == nil {
|
if surf == nil {
|
||||||
return errors.New("wayland: no surface")
|
return errors.New("wayland: no surface")
|
||||||
}
|
}
|
||||||
eglWin := C.wl_egl_window_create(surf, C.int(width), C.int(height))
|
C.wl_egl_window_resize(c.eglWin, C.int(width), C.int(height), 0, 0)
|
||||||
if eglWin == nil {
|
return nil
|
||||||
return errors.New("wayland: wl_egl_window_create failed")
|
|
||||||
}
|
|
||||||
c.eglWin = eglWin
|
|
||||||
eglSurf := egl.NativeWindowType(uintptr(unsafe.Pointer(eglWin)))
|
|
||||||
return c.Context.CreateSurface(eglSurf, width, height)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *wlContext) Lock() error {
|
func (c *wlContext) Lock() error {
|
||||||
|
|||||||
+12
-17
@@ -5,8 +5,6 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
|
|
||||||
"gioui.org/internal/egl"
|
"gioui.org/internal/egl"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,6 +22,18 @@ func init() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
win, _, _ := w.HWND()
|
||||||
|
eglSurf := egl.NativeWindowType(win)
|
||||||
|
if err := ctx.CreateSurface(eglSurf); err != nil {
|
||||||
|
ctx.Release()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := ctx.MakeCurrent(); err != nil {
|
||||||
|
ctx.Release()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer ctx.ReleaseCurrent()
|
||||||
|
ctx.EnableVSync(true)
|
||||||
return &glContext{win: w, Context: ctx}, nil
|
return &glContext{win: w, Context: ctx}, nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -37,21 +47,6 @@ func (c *glContext) Release() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *glContext) Refresh() error {
|
func (c *glContext) Refresh() error {
|
||||||
c.Context.ReleaseSurface()
|
|
||||||
var (
|
|
||||||
win windows.Handle
|
|
||||||
width, height int
|
|
||||||
)
|
|
||||||
win, width, height = c.win.HWND()
|
|
||||||
eglSurf := egl.NativeWindowType(win)
|
|
||||||
if err := c.Context.CreateSurface(eglSurf, width, height); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := c.Context.MakeCurrent(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.Context.EnableVSync(true)
|
|
||||||
c.Context.ReleaseCurrent()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+12
-11
@@ -25,6 +25,18 @@ func init() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
win, _, _ := w.window()
|
||||||
|
eglSurf := egl.NativeWindowType(uintptr(win))
|
||||||
|
if err := ctx.CreateSurface(eglSurf); err != nil {
|
||||||
|
ctx.Release()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := ctx.MakeCurrent(); err != nil {
|
||||||
|
ctx.Release()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer ctx.ReleaseCurrent()
|
||||||
|
ctx.EnableVSync(true)
|
||||||
return &x11Context{win: w, Context: ctx}, nil
|
return &x11Context{win: w, Context: ctx}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,17 +49,6 @@ func (c *x11Context) Release() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *x11Context) Refresh() error {
|
func (c *x11Context) Refresh() error {
|
||||||
c.Context.ReleaseSurface()
|
|
||||||
win, width, height := c.win.window()
|
|
||||||
eglSurf := egl.NativeWindowType(uintptr(win))
|
|
||||||
if err := c.Context.CreateSurface(eglSurf, width, height); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := c.Context.MakeCurrent(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.Context.EnableVSync(true)
|
|
||||||
c.Context.ReleaseCurrent()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ package app
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"gioui.org/gpu"
|
"gioui.org/gpu"
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
#include <OpenGL/OpenGL.h>
|
#include <OpenGL/OpenGL.h>
|
||||||
#include "_cgo_export.h"
|
#include "_cgo_export.h"
|
||||||
|
|
||||||
CALayer *gio_layerFactory(void) {
|
CALayer *gio_layerFactory(BOOL presentWithTrans) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
return [CALayer layer];
|
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
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"gioui.org/f32"
|
||||||
"testing"
|
"testing"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"gioui.org/font"
|
"gioui.org/font"
|
||||||
"gioui.org/font/gofont"
|
"gioui.org/font/gofont"
|
||||||
|
"gioui.org/io/input"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/router"
|
|
||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
"gioui.org/text"
|
"gioui.org/text"
|
||||||
@@ -31,15 +32,16 @@ func FuzzIME(f *testing.F) {
|
|||||||
f.Fuzz(func(t *testing.T, cmds []byte) {
|
f.Fuzz(func(t *testing.T, cmds []byte) {
|
||||||
cache := text.NewShaper(text.WithCollection(gofont.Collection()))
|
cache := text.NewShaper(text.WithCollection(gofont.Collection()))
|
||||||
e := new(widget.Editor)
|
e := new(widget.Editor)
|
||||||
e.Focus()
|
|
||||||
|
|
||||||
var r router.Router
|
var r input.Router
|
||||||
gtx := layout.Context{Ops: new(op.Ops), Queue: &r}
|
gtx := layout.Context{Ops: new(op.Ops), Source: r.Source()}
|
||||||
|
gtx.Execute(key.FocusCmd{Tag: e})
|
||||||
// Layout once to register focus.
|
// Layout once to register focus.
|
||||||
e.Layout(gtx, cache, font.Font{}, unit.Sp(10), op.CallOp{}, op.CallOp{})
|
e.Layout(gtx, cache, font.Font{}, unit.Sp(10), op.CallOp{}, op.CallOp{})
|
||||||
r.Frame(gtx.Ops)
|
r.Frame(gtx.Ops)
|
||||||
|
|
||||||
var state editorState
|
var state editorState
|
||||||
|
state.Selection.Transform = f32.AffineId()
|
||||||
const (
|
const (
|
||||||
cmdReplace = iota
|
cmdReplace = iota
|
||||||
cmdSelect
|
cmdSelect
|
||||||
@@ -139,6 +141,7 @@ func FuzzIME(f *testing.F) {
|
|||||||
|
|
||||||
func TestEditorIndices(t *testing.T) {
|
func TestEditorIndices(t *testing.T) {
|
||||||
var s editorState
|
var s editorState
|
||||||
|
s.Selection.Transform = f32.AffineId()
|
||||||
const str = "Hello, 😀"
|
const str = "Hello, 😀"
|
||||||
s.Snippet = key.Snippet{
|
s.Snippet = key.Snippet{
|
||||||
Text: str,
|
Text: str,
|
||||||
|
|||||||
@@ -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
|
Flags uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type POINTER_INPUT_TYPE int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
PT_POINTER POINTER_INPUT_TYPE = 1
|
||||||
|
PT_TOUCH POINTER_INPUT_TYPE = 2
|
||||||
|
PT_PEN POINTER_INPUT_TYPE = 3
|
||||||
|
PT_MOUSE POINTER_INPUT_TYPE = 4
|
||||||
|
PT_TOUCHPAD POINTER_INPUT_TYPE = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
type POINTER_INFO_POINTER_FLAGS int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
POINTER_FLAG_NEW POINTER_INFO_POINTER_FLAGS = 0x00000001
|
||||||
|
POINTER_FLAG_INRANGE POINTER_INFO_POINTER_FLAGS = 0x00000002
|
||||||
|
POINTER_FLAG_INCONTACT POINTER_INFO_POINTER_FLAGS = 0x00000004
|
||||||
|
POINTER_FLAG_FIRSTBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000010
|
||||||
|
POINTER_FLAG_SECONDBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000020
|
||||||
|
POINTER_FLAG_THIRDBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000040
|
||||||
|
POINTER_FLAG_FOURTHBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000080
|
||||||
|
POINTER_FLAG_FIFTHBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000100
|
||||||
|
POINTER_FLAG_PRIMARY POINTER_INFO_POINTER_FLAGS = 0x00002000
|
||||||
|
POINTER_FLAG_CONFIDENCE POINTER_INFO_POINTER_FLAGS = 0x00004000
|
||||||
|
POINTER_FLAG_CANCELED POINTER_INFO_POINTER_FLAGS = 0x00008000
|
||||||
|
POINTER_FLAG_DOWN POINTER_INFO_POINTER_FLAGS = 0x00010000
|
||||||
|
POINTER_FLAG_UPDATE POINTER_INFO_POINTER_FLAGS = 0x00020000
|
||||||
|
POINTER_FLAG_UP POINTER_INFO_POINTER_FLAGS = 0x00040000
|
||||||
|
POINTER_FLAG_WHEEL POINTER_INFO_POINTER_FLAGS = 0x00080000
|
||||||
|
POINTER_FLAG_HWHEEL POINTER_INFO_POINTER_FLAGS = 0x00100000
|
||||||
|
POINTER_FLAG_CAPTURECHANGED POINTER_INFO_POINTER_FLAGS = 0x00200000
|
||||||
|
POINTER_FLAG_HASTRANSFORM POINTER_INFO_POINTER_FLAGS = 0x00400000
|
||||||
|
)
|
||||||
|
|
||||||
|
type POINTER_BUTTON_CHANGE_TYPE int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
POINTER_CHANGE_NONE POINTER_BUTTON_CHANGE_TYPE = 0
|
||||||
|
POINTER_CHANGE_FIRSTBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 1
|
||||||
|
POINTER_CHANGE_FIRSTBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 2
|
||||||
|
POINTER_CHANGE_SECONDBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 3
|
||||||
|
POINTER_CHANGE_SECONDBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 4
|
||||||
|
POINTER_CHANGE_THIRDBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 5
|
||||||
|
POINTER_CHANGE_THIRDBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 6
|
||||||
|
POINTER_CHANGE_FOURTHBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 7
|
||||||
|
POINTER_CHANGE_FOURTHBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 8
|
||||||
|
POINTER_CHANGE_FIFTHBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 9
|
||||||
|
POINTER_CHANGE_FIFTHBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
type PointerInfo struct {
|
||||||
|
PointerType POINTER_INPUT_TYPE
|
||||||
|
PointerId uint32
|
||||||
|
FrameId uint32
|
||||||
|
PointerFlags POINTER_INFO_POINTER_FLAGS
|
||||||
|
SourceDevice syscall.Handle
|
||||||
|
HwndTarget syscall.Handle
|
||||||
|
PtPixelLocation Point
|
||||||
|
PtHimetricLocation Point
|
||||||
|
PtPixelLocationRaw Point
|
||||||
|
PtHimetricLocationRaw Point
|
||||||
|
DwTime uint32
|
||||||
|
HistoryCount uint32
|
||||||
|
InputData int32
|
||||||
|
DwKeyStates uint32
|
||||||
|
PerformanceCount uint64
|
||||||
|
ButtonChangeType POINTER_BUTTON_CHANGE_TYPE
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TRUE = 1
|
TRUE = 1
|
||||||
|
|
||||||
@@ -244,44 +312,51 @@ const (
|
|||||||
|
|
||||||
UNICODE_NOCHAR = 65535
|
UNICODE_NOCHAR = 65535
|
||||||
|
|
||||||
WM_CANCELMODE = 0x001F
|
WM_CANCELMODE = 0x001F
|
||||||
WM_CHAR = 0x0102
|
WM_CHAR = 0x0102
|
||||||
WM_CLOSE = 0x0010
|
WM_CLOSE = 0x0010
|
||||||
WM_CREATE = 0x0001
|
WM_CREATE = 0x0001
|
||||||
WM_DPICHANGED = 0x02E0
|
WM_DPICHANGED = 0x02E0
|
||||||
WM_DESTROY = 0x0002
|
WM_DESTROY = 0x0002
|
||||||
WM_ERASEBKGND = 0x0014
|
WM_ERASEBKGND = 0x0014
|
||||||
WM_GETMINMAXINFO = 0x0024
|
WM_GETMINMAXINFO = 0x0024
|
||||||
WM_IME_COMPOSITION = 0x010F
|
WM_IME_COMPOSITION = 0x010F
|
||||||
WM_IME_ENDCOMPOSITION = 0x010E
|
WM_IME_ENDCOMPOSITION = 0x010E
|
||||||
WM_IME_STARTCOMPOSITION = 0x010D
|
WM_IME_STARTCOMPOSITION = 0x010D
|
||||||
WM_KEYDOWN = 0x0100
|
WM_KEYDOWN = 0x0100
|
||||||
WM_KEYUP = 0x0101
|
WM_KEYUP = 0x0101
|
||||||
WM_KILLFOCUS = 0x0008
|
WM_KILLFOCUS = 0x0008
|
||||||
WM_LBUTTONDOWN = 0x0201
|
WM_LBUTTONDOWN = 0x0201
|
||||||
WM_LBUTTONUP = 0x0202
|
WM_LBUTTONUP = 0x0202
|
||||||
WM_MBUTTONDOWN = 0x0207
|
WM_MBUTTONDOWN = 0x0207
|
||||||
WM_MBUTTONUP = 0x0208
|
WM_MBUTTONUP = 0x0208
|
||||||
WM_MOUSEMOVE = 0x0200
|
WM_MOUSEMOVE = 0x0200
|
||||||
WM_MOUSEWHEEL = 0x020A
|
WM_MOUSEWHEEL = 0x020A
|
||||||
WM_MOUSEHWHEEL = 0x020E
|
WM_MOUSEHWHEEL = 0x020E
|
||||||
WM_NCACTIVATE = 0x0086
|
WM_NCACTIVATE = 0x0086
|
||||||
WM_NCHITTEST = 0x0084
|
WM_NCHITTEST = 0x0084
|
||||||
WM_NCCALCSIZE = 0x0083
|
WM_NCCALCSIZE = 0x0083
|
||||||
WM_PAINT = 0x000F
|
WM_PAINT = 0x000F
|
||||||
WM_QUIT = 0x0012
|
WM_POINTERCAPTURECHANGED = 0x024C
|
||||||
WM_SETCURSOR = 0x0020
|
WM_POINTERDOWN = 0x0246
|
||||||
WM_SETFOCUS = 0x0007
|
WM_POINTERUP = 0x0247
|
||||||
WM_SHOWWINDOW = 0x0018
|
WM_POINTERUPDATE = 0x0245
|
||||||
WM_SIZE = 0x0005
|
WM_POINTERWHEEL = 0x024E
|
||||||
WM_SYSKEYDOWN = 0x0104
|
WM_POINTERHWHEEL = 0x024F
|
||||||
WM_SYSKEYUP = 0x0105
|
WM_QUIT = 0x0012
|
||||||
WM_RBUTTONDOWN = 0x0204
|
WM_RBUTTONDOWN = 0x0204
|
||||||
WM_RBUTTONUP = 0x0205
|
WM_RBUTTONUP = 0x0205
|
||||||
WM_TIMER = 0x0113
|
WM_SETCURSOR = 0x0020
|
||||||
WM_UNICHAR = 0x0109
|
WM_SETFOCUS = 0x0007
|
||||||
WM_USER = 0x0400
|
WM_SHOWWINDOW = 0x0018
|
||||||
WM_WINDOWPOSCHANGED = 0x0047
|
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_CLIPCHILDREN = 0x02000000
|
||||||
WS_CLIPSIBLINGS = 0x04000000
|
WS_CLIPSIBLINGS = 0x04000000
|
||||||
@@ -345,6 +420,7 @@ var (
|
|||||||
_DestroyWindow = user32.NewProc("DestroyWindow")
|
_DestroyWindow = user32.NewProc("DestroyWindow")
|
||||||
_DispatchMessage = user32.NewProc("DispatchMessageW")
|
_DispatchMessage = user32.NewProc("DispatchMessageW")
|
||||||
_EmptyClipboard = user32.NewProc("EmptyClipboard")
|
_EmptyClipboard = user32.NewProc("EmptyClipboard")
|
||||||
|
_EnableMouseInPointer = user32.NewProc("EnableMouseInPointer")
|
||||||
_GetWindowRect = user32.NewProc("GetWindowRect")
|
_GetWindowRect = user32.NewProc("GetWindowRect")
|
||||||
_GetClientRect = user32.NewProc("GetClientRect")
|
_GetClientRect = user32.NewProc("GetClientRect")
|
||||||
_GetClipboardData = user32.NewProc("GetClipboardData")
|
_GetClipboardData = user32.NewProc("GetClipboardData")
|
||||||
@@ -354,6 +430,7 @@ var (
|
|||||||
_GetMessage = user32.NewProc("GetMessageW")
|
_GetMessage = user32.NewProc("GetMessageW")
|
||||||
_GetMessageTime = user32.NewProc("GetMessageTime")
|
_GetMessageTime = user32.NewProc("GetMessageTime")
|
||||||
_GetMonitorInfo = user32.NewProc("GetMonitorInfoW")
|
_GetMonitorInfo = user32.NewProc("GetMonitorInfoW")
|
||||||
|
_GetPointerInfo = user32.NewProc("GetPointerInfo")
|
||||||
_GetSystemMetrics = user32.NewProc("GetSystemMetrics")
|
_GetSystemMetrics = user32.NewProc("GetSystemMetrics")
|
||||||
_GetWindowLong = user32.NewProc("GetWindowLongPtrW")
|
_GetWindowLong = user32.NewProc("GetWindowLongPtrW")
|
||||||
_GetWindowLong32 = user32.NewProc("GetWindowLongW")
|
_GetWindowLong32 = user32.NewProc("GetWindowLongW")
|
||||||
@@ -371,6 +448,7 @@ var (
|
|||||||
_PostQuitMessage = user32.NewProc("PostQuitMessage")
|
_PostQuitMessage = user32.NewProc("PostQuitMessage")
|
||||||
_ReleaseCapture = user32.NewProc("ReleaseCapture")
|
_ReleaseCapture = user32.NewProc("ReleaseCapture")
|
||||||
_RegisterClassExW = user32.NewProc("RegisterClassExW")
|
_RegisterClassExW = user32.NewProc("RegisterClassExW")
|
||||||
|
_RegisterTouchWindow = user32.NewProc("RegisterTouchWindow")
|
||||||
_ReleaseDC = user32.NewProc("ReleaseDC")
|
_ReleaseDC = user32.NewProc("ReleaseDC")
|
||||||
_ScreenToClient = user32.NewProc("ScreenToClient")
|
_ScreenToClient = user32.NewProc("ScreenToClient")
|
||||||
_ShowWindow = user32.NewProc("ShowWindow")
|
_ShowWindow = user32.NewProc("ShowWindow")
|
||||||
@@ -444,6 +522,31 @@ func CreateWindowEx(dwExStyle uint32, lpClassName uint16, lpWindowName string, d
|
|||||||
return syscall.Handle(hwnd), nil
|
return syscall.Handle(hwnd), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetPointerInfo(pointerId uint32) (PointerInfo, error) {
|
||||||
|
var info PointerInfo
|
||||||
|
r1, _, err := _GetPointerInfo.Call(uintptr(pointerId), uintptr(unsafe.Pointer(&info)))
|
||||||
|
if r1 == 0 {
|
||||||
|
return PointerInfo{}, fmt.Errorf("GetPointerInfo failed: %v", err)
|
||||||
|
}
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterTouchWindow(hwnd syscall.Handle, flags uint32) error {
|
||||||
|
r1, _, err := _RegisterTouchWindow.Call(uintptr(hwnd), uintptr(flags))
|
||||||
|
if r1 == 0 {
|
||||||
|
return fmt.Errorf("RegisterTouchWindow failed: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func EnableMouseInPointer(enable uint) error {
|
||||||
|
r1, _, err := _EnableMouseInPointer.Call(uintptr(enable))
|
||||||
|
if r1 == 0 {
|
||||||
|
return fmt.Errorf("EnableMouseInPointer failed: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func DefWindowProc(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
|
func DefWindowProc(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
|
||||||
r, _, _ := _DefWindowProc.Call(uintptr(hwnd), uintptr(msg), wparam, lparam)
|
r, _, _ := _DefWindowProc.Call(uintptr(hwnd), uintptr(msg), wparam, lparam)
|
||||||
return r
|
return r
|
||||||
|
|||||||
@@ -238,17 +238,17 @@ func (x *Context) UpdateMask(depressed, latched, locked, depressedGroup, latched
|
|||||||
C.xkb_layout_index_t(depressedGroup), C.xkb_layout_index_t(latchedGroup), C.xkb_layout_index_t(lockedGroup))
|
C.xkb_layout_index_t(depressedGroup), C.xkb_layout_index_t(latchedGroup), C.xkb_layout_index_t(lockedGroup))
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertKeysym(s C.xkb_keysym_t) (string, bool) {
|
func convertKeysym(s C.xkb_keysym_t) (key.Name, bool) {
|
||||||
if 'a' <= s && s <= 'z' {
|
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 {
|
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 <= '~' {
|
if ' ' < s && s <= '~' {
|
||||||
return string(rune(s)), true
|
return key.Name(rune(s)), true
|
||||||
}
|
}
|
||||||
var n string
|
var n key.Name
|
||||||
switch s {
|
switch s {
|
||||||
case C.XKB_KEY_Escape:
|
case C.XKB_KEY_Escape:
|
||||||
n = key.NameEscape
|
n = key.NameEscape
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
package log
|
package app
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#cgo LDFLAGS: -llog
|
#cgo LDFLAGS: -llog
|
||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
// 1024 is the truncation limit from android/log.h, plus a \n.
|
// 1024 is the truncation limit from android/log.h, plus a \n.
|
||||||
const logLineLimit = 1024
|
const logLineLimit = 1024
|
||||||
|
|
||||||
var logTag = C.CString(appID)
|
var logTag = C.CString(ID)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Android's logcat already includes timestamps.
|
// Android's logcat already includes timestamps.
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
//go:build darwin && ios
|
//go:build darwin && ios
|
||||||
// +build darwin,ios
|
// +build darwin,ios
|
||||||
|
|
||||||
package log
|
package app
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#cgo CFLAGS: -Werror -fmodules -fobjc-arc -x objective-c
|
#cgo CFLAGS: -Werror -fmodules -fobjc-arc -x objective-c
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
package log
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
syscall "golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
|
|
||||||
type logger struct{}
|
type logger struct{}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
kernel32 = syscall.NewLazyDLL("kernel32")
|
kernel32 = syscall.NewLazySystemDLL("kernel32")
|
||||||
outputDebugStringW = kernel32.NewProc("OutputDebugStringW")
|
outputDebugStringW = kernel32.NewProc("OutputDebugStringW")
|
||||||
debugView *logger
|
debugView *logger
|
||||||
)
|
)
|
||||||
+3
-2
@@ -60,8 +60,9 @@ static void presentDrawable(CFTypeRef queueRef, CFTypeRef drawableRef) {
|
|||||||
id<MTLDrawable> drawable = (__bridge id<MTLDrawable>)drawableRef;
|
id<MTLDrawable> drawable = (__bridge id<MTLDrawable>)drawableRef;
|
||||||
id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)queueRef;
|
id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)queueRef;
|
||||||
id<MTLCommandBuffer> cmdBuffer = [queue commandBuffer];
|
id<MTLCommandBuffer> cmdBuffer = [queue commandBuffer];
|
||||||
[cmdBuffer presentDrawable:drawable];
|
|
||||||
[cmdBuffer commit];
|
[cmdBuffer commit];
|
||||||
|
[cmdBuffer waitUntilScheduled];
|
||||||
|
[drawable present];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +96,7 @@ func newMtlContext(w *window) (*mtlContext, error) {
|
|||||||
return nil, errors.New("metal: CAMetalLayer construction failed")
|
return nil, errors.New("metal: CAMetalLayer construction failed")
|
||||||
}
|
}
|
||||||
queue := C.newCommandQueue(dev)
|
queue := C.newCommandQueue(dev)
|
||||||
if layer == 0 {
|
if queue == 0 {
|
||||||
C.CFRelease(dev)
|
C.CFRelease(dev)
|
||||||
C.CFRelease(layer)
|
C.CFRelease(layer)
|
||||||
return nil, errors.New("metal: [MTLDevice newCommandQueue] failed")
|
return nil, errors.New("metal: [MTLDevice newCommandQueue] failed")
|
||||||
|
|||||||
+4
-1
@@ -21,7 +21,10 @@ Class gio_layerClass(void) {
|
|||||||
static CFTypeRef getMetalLayer(CFTypeRef viewRef) {
|
static CFTypeRef getMetalLayer(CFTypeRef viewRef) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
UIView *view = (__bridge UIView *)viewRef;
|
UIView *view = (__bridge UIView *)viewRef;
|
||||||
return CFBridgingRetain(view.layer);
|
CAMetalLayer *l = (CAMetalLayer *)view.layer;
|
||||||
|
l.needsDisplayOnBoundsChange = YES;
|
||||||
|
l.presentsWithTransaction = YES;
|
||||||
|
return CFBridgingRetain(l);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-2
@@ -12,9 +12,13 @@ package app
|
|||||||
#import <QuartzCore/CAMetalLayer.h>
|
#import <QuartzCore/CAMetalLayer.h>
|
||||||
#include <CoreFoundation/CoreFoundation.h>
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
|
|
||||||
CALayer *gio_layerFactory(void) {
|
CALayer *gio_layerFactory(BOOL presentWithTrans) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
return [CAMetalLayer layer];
|
CAMetalLayer *l = [CAMetalLayer layer];
|
||||||
|
l.autoresizingMask = kCALayerHeightSizable|kCALayerWidthSizable;
|
||||||
|
l.needsDisplayOnBoundsChange = YES;
|
||||||
|
l.presentsWithTransaction = presentWithTrans;
|
||||||
|
return l;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
|
"gioui.org/op"
|
||||||
|
|
||||||
"gioui.org/gpu"
|
"gioui.org/gpu"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
@@ -43,6 +45,8 @@ type Config struct {
|
|||||||
CustomRenderer bool
|
CustomRenderer bool
|
||||||
// Decorated reports whether window decorations are provided automatically.
|
// Decorated reports whether window decorations are provided automatically.
|
||||||
Decorated bool
|
Decorated bool
|
||||||
|
// Focused reports whether has the keyboard focus.
|
||||||
|
Focused bool
|
||||||
// decoHeight is the height of the fallback decoration for platforms such
|
// decoHeight is the height of the fallback decoration for platforms such
|
||||||
// as Wayland that may need fallback client-side decorations.
|
// as Wayland that may need fallback client-side decorations.
|
||||||
decoHeight unit.Dp
|
decoHeight unit.Dp
|
||||||
@@ -131,8 +135,30 @@ func (o Orientation) String() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eventLoop implements the functionality required for drivers where
|
||||||
|
// window event loops must run on a separate thread.
|
||||||
|
type eventLoop struct {
|
||||||
|
win *callbacks
|
||||||
|
// wakeup is the callback to wake up the event loop.
|
||||||
|
wakeup func()
|
||||||
|
// driverFuncs is a channel of functions to run the next
|
||||||
|
// time the window loop waits for events.
|
||||||
|
driverFuncs chan func()
|
||||||
|
// invalidates is notified when an invalidate is requested by the client.
|
||||||
|
invalidates chan struct{}
|
||||||
|
// immediateInvalidates is an optimistic invalidates that doesn't require a wakeup.
|
||||||
|
immediateInvalidates chan struct{}
|
||||||
|
// events is where the platform backend delivers events bound for the
|
||||||
|
// user program.
|
||||||
|
events chan event.Event
|
||||||
|
frames chan *op.Ops
|
||||||
|
frameAck chan struct{}
|
||||||
|
// delivering avoids re-entrant event delivery.
|
||||||
|
delivering bool
|
||||||
|
}
|
||||||
|
|
||||||
type frameEvent struct {
|
type frameEvent struct {
|
||||||
system.FrameEvent
|
FrameEvent
|
||||||
|
|
||||||
Sync bool
|
Sync bool
|
||||||
}
|
}
|
||||||
@@ -147,9 +173,13 @@ type context interface {
|
|||||||
Unlock()
|
Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Driver is the interface for the platform implementation
|
// driver is the interface for the platform implementation
|
||||||
// of a window.
|
// of a window.
|
||||||
type driver interface {
|
type driver interface {
|
||||||
|
// Event blocks until an event is available and returns it.
|
||||||
|
Event() event.Event
|
||||||
|
// Invalidate requests a FrameEvent.
|
||||||
|
Invalidate()
|
||||||
// SetAnimating sets the animation flag. When the window is animating,
|
// SetAnimating sets the animation flag. When the window is animating,
|
||||||
// FrameEvents are delivered as fast as the display can handle them.
|
// FrameEvents are delivered as fast as the display can handle them.
|
||||||
SetAnimating(anim bool)
|
SetAnimating(anim bool)
|
||||||
@@ -160,23 +190,27 @@ type driver interface {
|
|||||||
// ReadClipboard requests the clipboard content.
|
// ReadClipboard requests the clipboard content.
|
||||||
ReadClipboard()
|
ReadClipboard()
|
||||||
// WriteClipboard requests a clipboard write.
|
// WriteClipboard requests a clipboard write.
|
||||||
WriteClipboard(s string)
|
WriteClipboard(mime string, s []byte)
|
||||||
// Configure the window.
|
// Configure the window.
|
||||||
Configure([]Option)
|
Configure([]Option)
|
||||||
// SetCursor updates the current cursor to name.
|
// SetCursor updates the current cursor to name.
|
||||||
SetCursor(cursor pointer.Cursor)
|
SetCursor(cursor pointer.Cursor)
|
||||||
// Wakeup wakes up the event loop and sends a WakeupEvent.
|
|
||||||
Wakeup()
|
|
||||||
// Perform actions on the window.
|
// Perform actions on the window.
|
||||||
Perform(system.Action)
|
Perform(system.Action)
|
||||||
// EditorStateChanged notifies the driver that the editor state changed.
|
// EditorStateChanged notifies the driver that the editor state changed.
|
||||||
EditorStateChanged(old, new editorState)
|
EditorStateChanged(old, new editorState)
|
||||||
|
// Run a function on the window thread.
|
||||||
|
Run(f func())
|
||||||
|
// Frame receives a frame.
|
||||||
|
Frame(frame *op.Ops)
|
||||||
|
// ProcessEvent processes an event.
|
||||||
|
ProcessEvent(e event.Event)
|
||||||
}
|
}
|
||||||
|
|
||||||
type windowRendezvous struct {
|
type windowRendezvous struct {
|
||||||
in chan windowAndConfig
|
in chan windowAndConfig
|
||||||
out chan windowAndConfig
|
out chan windowAndConfig
|
||||||
errs chan error
|
windows chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type windowAndConfig struct {
|
type windowAndConfig struct {
|
||||||
@@ -186,32 +220,137 @@ type windowAndConfig struct {
|
|||||||
|
|
||||||
func newWindowRendezvous() *windowRendezvous {
|
func newWindowRendezvous() *windowRendezvous {
|
||||||
wr := &windowRendezvous{
|
wr := &windowRendezvous{
|
||||||
in: make(chan windowAndConfig),
|
in: make(chan windowAndConfig),
|
||||||
out: make(chan windowAndConfig),
|
out: make(chan windowAndConfig),
|
||||||
errs: make(chan error),
|
windows: make(chan struct{}),
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
var main windowAndConfig
|
in := wr.in
|
||||||
|
var window windowAndConfig
|
||||||
var out chan windowAndConfig
|
var out chan windowAndConfig
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case w := <-wr.in:
|
case w := <-in:
|
||||||
var err error
|
window = w
|
||||||
if main.window != nil {
|
|
||||||
err = errors.New("multiple windows are not supported")
|
|
||||||
}
|
|
||||||
wr.errs <- err
|
|
||||||
main = w
|
|
||||||
out = wr.out
|
out = wr.out
|
||||||
case out <- main:
|
case out <- window:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return wr
|
return wr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wakeupEvent) ImplementsEvent() {}
|
func newEventLoop(w *callbacks, wakeup func()) *eventLoop {
|
||||||
func (ConfigEvent) ImplementsEvent() {}
|
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)) {
|
func walkActions(actions system.Action, do func(system.Action)) {
|
||||||
for a := system.Action(1); actions != 0; a <<= 1 {
|
for a := system.Action(1); actions != 0; a <<= 1 {
|
||||||
@@ -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"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/cgo"
|
"runtime/cgo"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf16"
|
"unicode/utf16"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"gioui.org/internal/f32color"
|
"gioui.org/internal/f32color"
|
||||||
|
"gioui.org/op"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/io/clipboard"
|
"gioui.org/io/event"
|
||||||
|
"gioui.org/io/input"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/router"
|
|
||||||
"gioui.org/io/semantic"
|
"gioui.org/io/semantic"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
|
"gioui.org/io/transfer"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
)
|
)
|
||||||
|
|
||||||
type window struct {
|
type window struct {
|
||||||
callbacks *callbacks
|
callbacks *callbacks
|
||||||
|
loop *eventLoop
|
||||||
|
|
||||||
view C.jobject
|
view C.jobject
|
||||||
handle cgo.Handle
|
handle cgo.Handle
|
||||||
@@ -156,18 +161,19 @@ type window struct {
|
|||||||
fontScale float32
|
fontScale float32
|
||||||
insets pixelInsets
|
insets pixelInsets
|
||||||
|
|
||||||
stage system.Stage
|
visible bool
|
||||||
started bool
|
started bool
|
||||||
animating bool
|
animating bool
|
||||||
|
|
||||||
win *C.ANativeWindow
|
win *C.ANativeWindow
|
||||||
config Config
|
config Config
|
||||||
|
inputHint key.InputHint
|
||||||
|
|
||||||
semantic struct {
|
semantic struct {
|
||||||
hoverID router.SemanticID
|
hoverID input.SemanticID
|
||||||
rootID router.SemanticID
|
rootID input.SemanticID
|
||||||
focusID router.SemanticID
|
focusID input.SemanticID
|
||||||
diffs []router.SemanticID
|
diffs []input.SemanticID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,9 +205,9 @@ type pixelInsets struct {
|
|||||||
top, bottom, left, right int
|
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.
|
// changes.
|
||||||
type ViewEvent struct {
|
type AndroidViewEvent struct {
|
||||||
// View is a JNI global reference to the android.view.View
|
// View is a JNI global reference to the android.view.View
|
||||||
// instance backing the Window. The reference is valid until
|
// instance backing the Window. The reference is valid until
|
||||||
// the next ViewEvent is received.
|
// the next ViewEvent is received.
|
||||||
@@ -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)
|
view = C.jni_NewGlobalRef(env, view)
|
||||||
wopts := <-mainWindow.out
|
wopts := <-mainWindow.out
|
||||||
|
var cnf Config
|
||||||
w, ok := windows[wopts.window]
|
w, ok := windows[wopts.window]
|
||||||
if !ok {
|
if !ok {
|
||||||
w = &window{
|
w = &window{
|
||||||
callbacks: wopts.window,
|
callbacks: wopts.window,
|
||||||
}
|
}
|
||||||
|
w.loop = newEventLoop(w.callbacks, w.wakeup)
|
||||||
|
w.callbacks.SetDriver(w)
|
||||||
|
cnf.apply(unit.Metric{}, wopts.options)
|
||||||
windows[wopts.window] = w
|
windows[wopts.window] = w
|
||||||
|
} else {
|
||||||
|
cnf = w.config
|
||||||
}
|
}
|
||||||
|
mainWindow.windows <- struct{}{}
|
||||||
if w.view != 0 {
|
if w.view != 0 {
|
||||||
w.detach(env)
|
w.detach(env)
|
||||||
}
|
}
|
||||||
w.view = view
|
w.view = view
|
||||||
|
w.visible = false
|
||||||
w.handle = cgo.NewHandle(w)
|
w.handle = cgo.NewHandle(w)
|
||||||
w.callbacks.SetDriver(w)
|
|
||||||
w.loadConfig(env, class)
|
w.loadConfig(env, class)
|
||||||
w.Configure(wopts.options)
|
w.setConfig(env, cnf)
|
||||||
w.SetInputHint(key.HintAny)
|
w.SetInputHint(w.inputHint)
|
||||||
w.setStage(system.StagePaused)
|
w.processEvent(AndroidViewEvent{View: uintptr(view)})
|
||||||
w.callbacks.Event(ViewEvent{View: uintptr(view)})
|
|
||||||
return C.jlong(w.handle)
|
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) {
|
func Java_org_gioui_GioView_onStopView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
||||||
w := cgo.Handle(handle).Value().(*window)
|
w := cgo.Handle(handle).Value().(*window)
|
||||||
w.started = false
|
w.started = false
|
||||||
w.setStage(system.StagePaused)
|
w.visible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Java_org_gioui_GioView_onStartView
|
//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) {
|
func Java_org_gioui_GioView_onSurfaceDestroyed(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
||||||
w := cgo.Handle(handle).Value().(*window)
|
w := cgo.Handle(handle).Value().(*window)
|
||||||
w.win = nil
|
w.win = nil
|
||||||
w.setStage(system.StagePaused)
|
w.visible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Java_org_gioui_GioView_onSurfaceChanged
|
//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) {
|
func Java_org_gioui_GioView_onConfigurationChanged(env *C.JNIEnv, class C.jclass, view C.jlong) {
|
||||||
w := cgo.Handle(view).Value().(*window)
|
w := cgo.Handle(view).Value().(*window)
|
||||||
w.loadConfig(env, class)
|
w.loadConfig(env, class)
|
||||||
if w.stage >= system.StageInactive {
|
w.draw(env, true)
|
||||||
w.draw(env, true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Java_org_gioui_GioView_onFrameCallback
|
//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 {
|
if !exist {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if w.stage < system.StageInactive {
|
w.draw(env, false)
|
||||||
return
|
|
||||||
}
|
|
||||||
if w.animating {
|
|
||||||
w.draw(env, false)
|
|
||||||
callVoidMethod(env, w.view, gioView.postFrameCallback)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Java_org_gioui_GioView_onBack
|
//export Java_org_gioui_GioView_onBack
|
||||||
func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong) C.jboolean {
|
func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong) C.jboolean {
|
||||||
w := cgo.Handle(view).Value().(*window)
|
w := cgo.Handle(view).Value().(*window)
|
||||||
if w.callbacks.Event(key.Event{Name: key.NameBack}) {
|
if w.processEvent(key.Event{Name: key.NameBack}) {
|
||||||
return C.JNI_TRUE
|
return C.JNI_TRUE
|
||||||
}
|
}
|
||||||
return C.JNI_FALSE
|
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
|
//export Java_org_gioui_GioView_onFocusChange
|
||||||
func Java_org_gioui_GioView_onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) {
|
func Java_org_gioui_GioView_onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) {
|
||||||
w := cgo.Handle(view).Value().(*window)
|
w := cgo.Handle(view).Value().(*window)
|
||||||
w.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
|
//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),
|
left: int(left),
|
||||||
right: int(right),
|
right: int(right),
|
||||||
}
|
}
|
||||||
if w.stage >= system.StageInactive {
|
w.draw(env, true)
|
||||||
w.draw(env, true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Java_org_gioui_GioView_initializeAccessibilityNodeInfo
|
//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 {
|
for _, ch := range sem.Children {
|
||||||
err := callVoidMethod(env, info, android.accessibilityNodeInfo.addChild, jvalue(w.view), jvalue(w.virtualIDFor(ch.ID)))
|
err := callVoidMethod(env, info, android.accessibilityNodeInfo.addChild, jvalue(w.view), jvalue(w.virtualIDFor(ch.ID)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -704,7 +735,7 @@ func (w *window) initAccessibilityNodeInfo(env *C.JNIEnv, sem router.SemanticNod
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if d.Gestures&router.ClickGesture != 0 {
|
if d.Gestures&input.ClickGesture != 0 {
|
||||||
addAction(ACTION_CLICK)
|
addAction(ACTION_CLICK)
|
||||||
}
|
}
|
||||||
clsName := android.strings.androidViewView
|
clsName := android.strings.androidViewView
|
||||||
@@ -749,25 +780,23 @@ func (w *window) initAccessibilityNodeInfo(env *C.JNIEnv, sem router.SemanticNod
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) virtualIDFor(id router.SemanticID) C.jint {
|
func (w *window) virtualIDFor(id input.SemanticID) C.jint {
|
||||||
// TODO: Android virtual IDs are 32-bit Java integers, but childID is a int64.
|
|
||||||
if id == w.semantic.rootID {
|
if id == w.semantic.rootID {
|
||||||
return HOST_VIEW_ID
|
return HOST_VIEW_ID
|
||||||
}
|
}
|
||||||
return C.jint(id)
|
return C.jint(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) semIDFor(virtID C.jint) router.SemanticID {
|
func (w *window) semIDFor(virtID C.jint) input.SemanticID {
|
||||||
if virtID == HOST_VIEW_ID {
|
if virtID == HOST_VIEW_ID {
|
||||||
return w.semantic.rootID
|
return w.semantic.rootID
|
||||||
}
|
}
|
||||||
return router.SemanticID(virtID)
|
return input.SemanticID(virtID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) detach(env *C.JNIEnv) {
|
func (w *window) detach(env *C.JNIEnv) {
|
||||||
callVoidMethod(env, w.view, gioView.unregister)
|
callVoidMethod(env, w.view, gioView.unregister)
|
||||||
w.callbacks.Event(ViewEvent{})
|
w.processEvent(AndroidViewEvent{})
|
||||||
w.callbacks.SetDriver(nil)
|
|
||||||
w.handle.Delete()
|
w.handle.Delete()
|
||||||
C.jni_DeleteGlobalRef(env, w.view)
|
C.jni_DeleteGlobalRef(env, w.view)
|
||||||
w.view = 0
|
w.view = 0
|
||||||
@@ -778,18 +807,10 @@ func (w *window) setVisible(env *C.JNIEnv) {
|
|||||||
if width == 0 || height == 0 {
|
if width == 0 || height == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.setStage(system.StageRunning)
|
w.visible = true
|
||||||
w.draw(env, true)
|
w.draw(env, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) setStage(stage system.Stage) {
|
|
||||||
if stage == w.stage {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.stage = stage
|
|
||||||
w.callbacks.Event(system.StageEvent{stage})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) setVisual(visID int) error {
|
func (w *window) setVisual(visID int) error {
|
||||||
if C.ANativeWindow_setBuffersGeometry(w.win, 0, 0, C.int32_t(visID)) != 0 {
|
if C.ANativeWindow_setBuffersGeometry(w.win, 0, 0, C.int32_t(visID)) != 0 {
|
||||||
return errors.New("ANativeWindow_setBuffersGeometry failed")
|
return errors.New("ANativeWindow_setBuffersGeometry failed")
|
||||||
@@ -826,10 +847,13 @@ func (w *window) SetAnimating(anim bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) draw(env *C.JNIEnv, sync bool) {
|
func (w *window) draw(env *C.JNIEnv, sync bool) {
|
||||||
|
if !w.visible {
|
||||||
|
return
|
||||||
|
}
|
||||||
size := image.Pt(int(C.ANativeWindow_getWidth(w.win)), int(C.ANativeWindow_getHeight(w.win)))
|
size := image.Pt(int(C.ANativeWindow_getWidth(w.win)), int(C.ANativeWindow_getHeight(w.win)))
|
||||||
if size != w.config.Size {
|
if size != w.config.Size {
|
||||||
w.config.Size = size
|
w.config.Size = size
|
||||||
w.callbacks.Event(ConfigEvent{Config: w.config})
|
w.processEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
if size.X == 0 || size.Y == 0 {
|
if size.X == 0 || size.Y == 0 {
|
||||||
return
|
return
|
||||||
@@ -837,14 +861,14 @@ func (w *window) draw(env *C.JNIEnv, sync bool) {
|
|||||||
const inchPrDp = 1.0 / 160
|
const inchPrDp = 1.0 / 160
|
||||||
ppdp := float32(w.dpi) * inchPrDp
|
ppdp := float32(w.dpi) * inchPrDp
|
||||||
dppp := unit.Dp(1.0 / ppdp)
|
dppp := unit.Dp(1.0 / ppdp)
|
||||||
insets := system.Insets{
|
insets := Insets{
|
||||||
Top: unit.Dp(w.insets.top) * dppp,
|
Top: unit.Dp(w.insets.top) * dppp,
|
||||||
Bottom: unit.Dp(w.insets.bottom) * dppp,
|
Bottom: unit.Dp(w.insets.bottom) * dppp,
|
||||||
Left: unit.Dp(w.insets.left) * dppp,
|
Left: unit.Dp(w.insets.left) * dppp,
|
||||||
Right: unit.Dp(w.insets.right) * dppp,
|
Right: unit.Dp(w.insets.right) * dppp,
|
||||||
}
|
}
|
||||||
w.callbacks.Event(frameEvent{
|
w.processEvent(frameEvent{
|
||||||
FrameEvent: system.FrameEvent{
|
FrameEvent: FrameEvent{
|
||||||
Now: time.Now(),
|
Now: time.Now(),
|
||||||
Size: w.config.Size,
|
Size: w.config.Size,
|
||||||
Insets: insets,
|
Insets: insets,
|
||||||
@@ -855,6 +879,9 @@ func (w *window) draw(env *C.JNIEnv, sync bool) {
|
|||||||
},
|
},
|
||||||
Sync: sync,
|
Sync: sync,
|
||||||
})
|
})
|
||||||
|
if w.animating {
|
||||||
|
callVoidMethod(env, w.view, gioView.postFrameCallback)
|
||||||
|
}
|
||||||
a11yActive, err := callBooleanMethod(env, w.view, gioView.isA11yActive)
|
a11yActive, err := callBooleanMethod(env, w.view, gioView.isA11yActive)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -898,8 +925,8 @@ func runInJVM(jvm *C.JavaVM, f func(env *C.JNIEnv)) {
|
|||||||
f(env)
|
f(env)
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertKeyCode(code C.jint) (string, bool) {
|
func convertKeyCode(code C.jint) (key.Name, bool) {
|
||||||
var n string
|
var n key.Name
|
||||||
switch code {
|
switch code {
|
||||||
case C.AKEYCODE_FORWARD_DEL:
|
case C.AKEYCODE_FORWARD_DEL:
|
||||||
n = key.NameDeleteForward
|
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 {
|
if pressed == C.JNI_TRUE {
|
||||||
state = key.Press
|
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).
|
if pressed == C.JNI_TRUE && r != 0 && r != '\n' { // Checking for "\n" to prevent duplication with key.NameEnter (gio#224).
|
||||||
w.callbacks.EditorInsert(string(rune(r)))
|
w.callbacks.EditorInsert(string(rune(r)))
|
||||||
@@ -993,7 +1020,7 @@ func Java_org_gioui_GioView_onTouchEvent(env *C.JNIEnv, class C.jclass, handle C
|
|||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.callbacks.Event(pointer.Event{
|
w.processEvent(pointer.Event{
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Source: src,
|
Source: src,
|
||||||
Buttons: btns,
|
Buttons: btns,
|
||||||
@@ -1145,6 +1172,8 @@ func (w *window) ShowTextInput(show bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) SetInputHint(mode key.InputHint) {
|
func (w *window) SetInputHint(mode key.InputHint) {
|
||||||
|
w.inputHint = mode
|
||||||
|
|
||||||
// Constants defined at https://developer.android.com/reference/android/text/InputType.
|
// Constants defined at https://developer.android.com/reference/android/text/InputType.
|
||||||
const (
|
const (
|
||||||
TYPE_NULL = 0
|
TYPE_NULL = 0
|
||||||
@@ -1291,14 +1320,14 @@ func findClass(env *C.JNIEnv, name string) C.jclass {
|
|||||||
func osMain() {
|
func osMain() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWindow(window *callbacks, options []Option) error {
|
func newWindow(window *callbacks, options []Option) {
|
||||||
mainWindow.in <- windowAndConfig{window, options}
|
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) {
|
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||||
jstr := javaString(env, s)
|
jstr := javaString(env, string(s))
|
||||||
callStaticVoidMethod(env, android.gioCls, android.mwriteClipboard,
|
callStaticVoidMethod(env, android.gioCls, android.mwriteClipboard,
|
||||||
jvalue(android.appCtx), jvalue(jstr))
|
jvalue(android.appCtx), jvalue(jstr))
|
||||||
})
|
})
|
||||||
@@ -1312,47 +1341,56 @@ func (w *window) ReadClipboard() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
content := goString(env, C.jstring(c))
|
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) {
|
func (w *window) Configure(options []Option) {
|
||||||
|
cnf := w.config
|
||||||
|
cnf.apply(unit.Metric{}, options)
|
||||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||||
prev := w.config
|
w.setConfig(env, cnf)
|
||||||
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})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) Perform(system.Action) {}
|
||||||
|
|
||||||
func (w *window) SetCursor(cursor pointer.Cursor) {
|
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) {
|
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>
|
#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"))) CFTypeRef gio_createDisplayLink(void);
|
||||||
__attribute__ ((visibility ("hidden"))) void gio_releaseDisplayLink(CFTypeRef dl);
|
__attribute__ ((visibility ("hidden"))) void gio_releaseDisplayLink(CFTypeRef dl);
|
||||||
__attribute__ ((visibility ("hidden"))) int gio_startDisplayLink(CFTypeRef dl);
|
__attribute__ ((visibility ("hidden"))) int gio_startDisplayLink(CFTypeRef dl);
|
||||||
@@ -40,8 +40,10 @@ static CFTypeRef newNSString(unichar *chars, NSUInteger length) {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"runtime/cgo"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@@ -73,30 +75,25 @@ type displayLink struct {
|
|||||||
// displayLinks maps CFTypeRefs to *displayLinks.
|
// displayLinks maps CFTypeRefs to *displayLinks.
|
||||||
var displayLinks sync.Map
|
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.
|
// runOnMain runs the function on the main thread.
|
||||||
func runOnMain(f func()) {
|
func runOnMain(f func()) {
|
||||||
if C.isMainThread() {
|
if isMainThread() {
|
||||||
f()
|
f()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go func() {
|
C.gio_runOnMain(C.uintptr_t(cgo.NewHandle(f)))
|
||||||
mainFuncs <- f
|
|
||||||
C.gio_wakeupMainThread()
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_dispatchMainFuncs
|
//export gio_runFunc
|
||||||
func gio_dispatchMainFuncs() {
|
func gio_runFunc(h C.uintptr_t) {
|
||||||
for {
|
handle := cgo.Handle(h)
|
||||||
select {
|
defer handle.Delete()
|
||||||
case f := <-mainFuncs:
|
f := handle.Value().(func())
|
||||||
f()
|
f()
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// nsstringToString converts a NSString to a Go string.
|
// nsstringToString converts a NSString to a Go string.
|
||||||
@@ -260,8 +257,9 @@ func windowSetCursor(from, to pointer.Cursor) pointer.Cursor {
|
|||||||
return to
|
return to
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Wakeup() {
|
func (w *window) wakeup() {
|
||||||
runOnMain(func() {
|
runOnMain(func() {
|
||||||
w.w.Event(wakeupEvent{})
|
w.loop.Wakeup()
|
||||||
|
w.loop.FlushEvents()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
#include "_cgo_export.h"
|
#include "_cgo_export.h"
|
||||||
|
|
||||||
void gio_wakeupMainThread(void) {
|
void gio_runOnMain(uintptr_t h) {
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
gio_dispatchMainFuncs();
|
gio_runFunc(h);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+153
-66
@@ -12,6 +12,9 @@ package app
|
|||||||
#include <UIKit/UIKit.h>
|
#include <UIKit/UIKit.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
__attribute__ ((visibility ("hidden"))) int gio_applicationMain(int argc, char *argv[]);
|
||||||
|
__attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle);
|
||||||
|
|
||||||
struct drawParams {
|
struct drawParams {
|
||||||
CGFloat dpi, sdpi;
|
CGFloat dpi, sdpi;
|
||||||
CGFloat width, height;
|
CGFloat width, height;
|
||||||
@@ -19,6 +22,7 @@ struct drawParams {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static void writeClipboard(unichar *chars, NSUInteger length) {
|
static void writeClipboard(unichar *chars, NSUInteger length) {
|
||||||
|
#if !TARGET_OS_TV
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
NSString *s = [NSString string];
|
NSString *s = [NSString string];
|
||||||
if (length > 0) {
|
if (length > 0) {
|
||||||
@@ -27,13 +31,18 @@ static void writeClipboard(unichar *chars, NSUInteger length) {
|
|||||||
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
||||||
p.string = s;
|
p.string = s;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static CFTypeRef readClipboard(void) {
|
static CFTypeRef readClipboard(void) {
|
||||||
|
#if !TARGET_OS_TV
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
||||||
return (__bridge_retained CFTypeRef)p.string;
|
return (__bridge_retained CFTypeRef)p.string;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
return nil;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static void showTextInput(CFTypeRef viewRef) {
|
static void showTextInput(CFTypeRef viewRef) {
|
||||||
@@ -72,21 +81,27 @@ import "C"
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"image"
|
"image"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"runtime/cgo"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf16"
|
"unicode/utf16"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/io/clipboard"
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
|
"gioui.org/io/transfer"
|
||||||
|
"gioui.org/op"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ViewEvent struct {
|
type UIKitViewEvent struct {
|
||||||
// ViewController is a CFTypeRef for the UIViewController backing a Window.
|
// ViewController is a CFTypeRef for the UIViewController backing a Window.
|
||||||
ViewController uintptr
|
ViewController uintptr
|
||||||
}
|
}
|
||||||
@@ -95,18 +110,17 @@ type window struct {
|
|||||||
view C.CFTypeRef
|
view C.CFTypeRef
|
||||||
w *callbacks
|
w *callbacks
|
||||||
displayLink *displayLink
|
displayLink *displayLink
|
||||||
|
loop *eventLoop
|
||||||
|
|
||||||
visible bool
|
hidden bool
|
||||||
cursor pointer.Cursor
|
cursor pointer.Cursor
|
||||||
config Config
|
config Config
|
||||||
|
|
||||||
pointerMap []C.CFTypeRef
|
pointerMap []C.CFTypeRef
|
||||||
}
|
}
|
||||||
|
|
||||||
var mainWindow = newWindowRendezvous()
|
var mainWindow = newWindowRendezvous()
|
||||||
|
|
||||||
var views = make(map[C.CFTypeRef]*window)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Darwin requires UI operations happen on the main thread only.
|
// Darwin requires UI operations happen on the main thread only.
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
@@ -114,55 +128,59 @@ func init() {
|
|||||||
|
|
||||||
//export onCreate
|
//export onCreate
|
||||||
func onCreate(view, controller C.CFTypeRef) {
|
func onCreate(view, controller C.CFTypeRef) {
|
||||||
|
wopts := <-mainWindow.out
|
||||||
w := &window{
|
w := &window{
|
||||||
view: view,
|
view: view,
|
||||||
|
w: wopts.window,
|
||||||
}
|
}
|
||||||
|
w.loop = newEventLoop(w.w, w.wakeup)
|
||||||
|
w.w.SetDriver(w)
|
||||||
|
mainWindow.windows <- struct{}{}
|
||||||
dl, err := newDisplayLink(func() {
|
dl, err := newDisplayLink(func() {
|
||||||
w.draw(false)
|
w.draw(false)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
w.w.ProcessEvent(DestroyEvent{Err: err})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
w.displayLink = dl
|
w.displayLink = dl
|
||||||
wopts := <-mainWindow.out
|
C.gio_viewSetHandle(view, C.uintptr_t(cgo.NewHandle(w)))
|
||||||
w.w = wopts.window
|
|
||||||
w.w.SetDriver(w)
|
|
||||||
views[view] = w
|
|
||||||
w.Configure(wopts.options)
|
w.Configure(wopts.options)
|
||||||
w.w.Event(system.StageEvent{Stage: system.StagePaused})
|
w.ProcessEvent(UIKitViewEvent{ViewController: uintptr(controller)})
|
||||||
w.w.Event(ViewEvent{ViewController: uintptr(controller)})
|
}
|
||||||
|
|
||||||
|
func viewFor(h C.uintptr_t) *window {
|
||||||
|
return cgo.Handle(h).Value().(*window)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onDraw
|
//export gio_onDraw
|
||||||
func gio_onDraw(view C.CFTypeRef) {
|
func gio_onDraw(h C.uintptr_t) {
|
||||||
w := views[view]
|
w := viewFor(h)
|
||||||
w.draw(true)
|
w.draw(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) draw(sync bool) {
|
func (w *window) draw(sync bool) {
|
||||||
|
if w.hidden {
|
||||||
|
return
|
||||||
|
}
|
||||||
params := C.viewDrawParams(w.view)
|
params := C.viewDrawParams(w.view)
|
||||||
if params.width == 0 || params.height == 0 {
|
if params.width == 0 || params.height == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
wasVisible := w.visible
|
|
||||||
w.visible = true
|
|
||||||
if !wasVisible {
|
|
||||||
w.w.Event(system.StageEvent{Stage: system.StageRunning})
|
|
||||||
}
|
|
||||||
const inchPrDp = 1.0 / 163
|
const inchPrDp = 1.0 / 163
|
||||||
m := unit.Metric{
|
m := unit.Metric{
|
||||||
PxPerDp: float32(params.dpi) * inchPrDp,
|
PxPerDp: float32(params.dpi) * inchPrDp,
|
||||||
PxPerSp: float32(params.sdpi) * inchPrDp,
|
PxPerSp: float32(params.sdpi) * inchPrDp,
|
||||||
}
|
}
|
||||||
dppp := unit.Dp(1. / m.PxPerDp)
|
dppp := unit.Dp(1. / m.PxPerDp)
|
||||||
w.w.Event(frameEvent{
|
w.ProcessEvent(frameEvent{
|
||||||
FrameEvent: system.FrameEvent{
|
FrameEvent: FrameEvent{
|
||||||
Now: time.Now(),
|
Now: time.Now(),
|
||||||
Size: image.Point{
|
Size: image.Point{
|
||||||
X: int(params.width + .5),
|
X: int(params.width + .5),
|
||||||
Y: int(params.height + .5),
|
Y: int(params.height + .5),
|
||||||
},
|
},
|
||||||
Insets: system.Insets{
|
Insets: Insets{
|
||||||
Top: unit.Dp(params.top) * dppp,
|
Top: unit.Dp(params.top) * dppp,
|
||||||
Bottom: unit.Dp(params.bottom) * dppp,
|
Bottom: unit.Dp(params.bottom) * dppp,
|
||||||
Left: unit.Dp(params.left) * dppp,
|
Left: unit.Dp(params.left) * dppp,
|
||||||
@@ -175,26 +193,34 @@ func (w *window) draw(sync bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//export onStop
|
//export onStop
|
||||||
func onStop(view C.CFTypeRef) {
|
func onStop(h C.uintptr_t) {
|
||||||
w := views[view]
|
w := viewFor(h)
|
||||||
w.visible = false
|
w.hidden = true
|
||||||
w.w.Event(system.StageEvent{Stage: system.StagePaused})
|
}
|
||||||
|
|
||||||
|
//export onStart
|
||||||
|
func onStart(h C.uintptr_t) {
|
||||||
|
w := viewFor(h)
|
||||||
|
w.hidden = false
|
||||||
|
w.draw(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onDestroy
|
//export onDestroy
|
||||||
func onDestroy(view C.CFTypeRef) {
|
func onDestroy(h C.uintptr_t) {
|
||||||
w := views[view]
|
w := viewFor(h)
|
||||||
delete(views, view)
|
w.ProcessEvent(UIKitViewEvent{})
|
||||||
w.w.Event(ViewEvent{})
|
w.ProcessEvent(DestroyEvent{})
|
||||||
w.w.Event(system.DestroyEvent{})
|
|
||||||
w.displayLink.Close()
|
w.displayLink.Close()
|
||||||
|
w.displayLink = nil
|
||||||
|
cgo.Handle(h).Delete()
|
||||||
w.view = 0
|
w.view = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onFocus
|
//export onFocus
|
||||||
func onFocus(view C.CFTypeRef, focus int) {
|
func onFocus(h C.uintptr_t, focus int) {
|
||||||
w := views[view]
|
w := viewFor(h)
|
||||||
w.w.Event(key.FocusEvent{Focus: focus != 0})
|
w.config.Focused = focus != 0
|
||||||
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onLowMemory
|
//export onLowMemory
|
||||||
@@ -204,38 +230,38 @@ func onLowMemory() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//export onUpArrow
|
//export onUpArrow
|
||||||
func onUpArrow(view C.CFTypeRef) {
|
func onUpArrow(h C.uintptr_t) {
|
||||||
views[view].onKeyCommand(key.NameUpArrow)
|
viewFor(h).onKeyCommand(key.NameUpArrow)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onDownArrow
|
//export onDownArrow
|
||||||
func onDownArrow(view C.CFTypeRef) {
|
func onDownArrow(h C.uintptr_t) {
|
||||||
views[view].onKeyCommand(key.NameDownArrow)
|
viewFor(h).onKeyCommand(key.NameDownArrow)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onLeftArrow
|
//export onLeftArrow
|
||||||
func onLeftArrow(view C.CFTypeRef) {
|
func onLeftArrow(h C.uintptr_t) {
|
||||||
views[view].onKeyCommand(key.NameLeftArrow)
|
viewFor(h).onKeyCommand(key.NameLeftArrow)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onRightArrow
|
//export onRightArrow
|
||||||
func onRightArrow(view C.CFTypeRef) {
|
func onRightArrow(h C.uintptr_t) {
|
||||||
views[view].onKeyCommand(key.NameRightArrow)
|
viewFor(h).onKeyCommand(key.NameRightArrow)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onDeleteBackward
|
//export onDeleteBackward
|
||||||
func onDeleteBackward(view C.CFTypeRef) {
|
func onDeleteBackward(h C.uintptr_t) {
|
||||||
views[view].onKeyCommand(key.NameDeleteBackward)
|
viewFor(h).onKeyCommand(key.NameDeleteBackward)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onText
|
//export onText
|
||||||
func onText(view, str C.CFTypeRef) {
|
func onText(h C.uintptr_t, str C.CFTypeRef) {
|
||||||
w := views[view]
|
w := viewFor(h)
|
||||||
w.w.EditorInsert(nsstringToString(str))
|
w.w.EditorInsert(nsstringToString(str))
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onTouch
|
//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
|
var kind pointer.Kind
|
||||||
switch phase {
|
switch phase {
|
||||||
case C.UITouchPhaseBegan:
|
case C.UITouchPhaseBegan:
|
||||||
@@ -249,10 +275,10 @@ func onTouch(last C.int, view, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.C
|
|||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w := views[view]
|
w := viewFor(h)
|
||||||
t := time.Duration(float64(ti) * float64(time.Second))
|
t := time.Duration(float64(ti) * float64(time.Second))
|
||||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
PointerID: w.lookupTouch(last != 0, touchRef),
|
PointerID: w.lookupTouch(last != 0, touchRef),
|
||||||
@@ -265,11 +291,16 @@ func (w *window) ReadClipboard() {
|
|||||||
cstr := C.readClipboard()
|
cstr := C.readClipboard()
|
||||||
defer C.CFRelease(cstr)
|
defer C.CFRelease(cstr)
|
||||||
content := nsstringToString(cstr)
|
content := nsstringToString(cstr)
|
||||||
w.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) {
|
func (w *window) WriteClipboard(mime string, s []byte) {
|
||||||
u16 := utf16.Encode([]rune(s))
|
u16 := utf16.Encode([]rune(string(s)))
|
||||||
var chars *C.unichar
|
var chars *C.unichar
|
||||||
if len(u16) > 0 {
|
if len(u16) > 0 {
|
||||||
chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
|
chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
|
||||||
@@ -280,7 +311,7 @@ func (w *window) WriteClipboard(s string) {
|
|||||||
func (w *window) Configure([]Option) {
|
func (w *window) Configure([]Option) {
|
||||||
// Decorations are never disabled.
|
// Decorations are never disabled.
|
||||||
w.config.Decorated = true
|
w.config.Decorated = true
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) EditorStateChanged(old, new editorState) {}
|
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) Perform(system.Action) {}
|
||||||
|
|
||||||
func (w *window) SetAnimating(anim bool) {
|
func (w *window) SetAnimating(anim bool) {
|
||||||
v := w.view
|
|
||||||
if v == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if anim {
|
if anim {
|
||||||
w.displayLink.Start()
|
w.displayLink.Start()
|
||||||
} else {
|
} else {
|
||||||
@@ -303,8 +330,8 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
|||||||
w.cursor = windowSetCursor(w.cursor, cursor)
|
w.cursor = windowSetCursor(w.cursor, cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) onKeyCommand(name string) {
|
func (w *window) onKeyCommand(name key.Name) {
|
||||||
w.w.Event(key.Event{
|
w.ProcessEvent(key.Event{
|
||||||
Name: name,
|
Name: name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -343,17 +370,77 @@ func (w *window) ShowTextInput(show bool) {
|
|||||||
|
|
||||||
func (w *window) SetInputHint(_ key.InputHint) {}
|
func (w *window) SetInputHint(_ key.InputHint) {}
|
||||||
|
|
||||||
func newWindow(win *callbacks, options []Option) error {
|
func (w *window) ProcessEvent(e event.Event) {
|
||||||
mainWindow.in <- windowAndConfig{win, options}
|
w.w.ProcessEvent(e)
|
||||||
return <-mainWindow.errs
|
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() {
|
func osMain() {
|
||||||
|
if !isMainThread() {
|
||||||
|
panic("app.Main must be run on the main goroutine")
|
||||||
|
}
|
||||||
|
switch mainMode {
|
||||||
|
case mainModeUndefined:
|
||||||
|
mainMode = mainModeExe
|
||||||
|
var argv []*C.char
|
||||||
|
for _, arg := range os.Args {
|
||||||
|
a := C.CString(arg)
|
||||||
|
defer C.free(unsafe.Pointer(a))
|
||||||
|
argv = append(argv, a)
|
||||||
|
}
|
||||||
|
C.gio_applicationMain(C.int(len(argv)), unsafe.SliceData(argv))
|
||||||
|
case mainModeExe:
|
||||||
|
panic("app.Main may be called only once")
|
||||||
|
case mainModeLibrary:
|
||||||
|
// Do nothing, we're embedded as a library.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_runMain
|
//export gio_runMain
|
||||||
func gio_runMain() {
|
func gio_runMain() {
|
||||||
runMain()
|
if !isMainThread() {
|
||||||
|
panic("app.Main must be run on the main goroutine")
|
||||||
|
}
|
||||||
|
switch mainMode {
|
||||||
|
case mainModeUndefined:
|
||||||
|
mainMode = mainModeLibrary
|
||||||
|
runMain()
|
||||||
|
case mainModeExe:
|
||||||
|
// Do nothing, main has already been called.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_ 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);
|
__attribute__ ((visibility ("hidden"))) Class gio_layerClass(void);
|
||||||
|
|
||||||
@interface GioView: UIView <UIKeyInput>
|
@interface GioView: UIView <UIKeyInput>
|
||||||
|
@property uintptr_t handle;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation GioViewController
|
@implementation GioViewController
|
||||||
@@ -25,12 +26,13 @@ CGFloat _keyboardHeight;
|
|||||||
self.view.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
|
self.view.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
|
||||||
UIView *drawView = [[GioView alloc] initWithFrame:zeroFrame];
|
UIView *drawView = [[GioView alloc] initWithFrame:zeroFrame];
|
||||||
[self.view addSubview: drawView];
|
[self.view addSubview: drawView];
|
||||||
#ifndef TARGET_OS_TV
|
#if !TARGET_OS_TV
|
||||||
drawView.multipleTouchEnabled = YES;
|
drawView.multipleTouchEnabled = YES;
|
||||||
#endif
|
#endif
|
||||||
drawView.preservesSuperviewLayoutMargins = YES;
|
drawView.preservesSuperviewLayoutMargins = YES;
|
||||||
drawView.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
|
drawView.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
|
||||||
onCreate((__bridge CFTypeRef)drawView, (__bridge CFTypeRef)self);
|
onCreate((__bridge CFTypeRef)drawView, (__bridge CFTypeRef)self);
|
||||||
|
#if !TARGET_OS_TV
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
selector:@selector(keyboardWillChange:)
|
selector:@selector(keyboardWillChange:)
|
||||||
name:UIKeyboardWillShowNotification
|
name:UIKeyboardWillShowNotification
|
||||||
@@ -43,6 +45,7 @@ CGFloat _keyboardHeight;
|
|||||||
selector:@selector(keyboardWillHide:)
|
selector:@selector(keyboardWillHide:)
|
||||||
name:UIKeyboardWillHideNotification
|
name:UIKeyboardWillHideNotification
|
||||||
object:nil];
|
object:nil];
|
||||||
|
#endif
|
||||||
[[NSNotificationCenter defaultCenter] addObserver: self
|
[[NSNotificationCenter defaultCenter] addObserver: self
|
||||||
selector: @selector(applicationDidEnterBackground:)
|
selector: @selector(applicationDidEnterBackground:)
|
||||||
name: UIApplicationDidEnterBackgroundNotification
|
name: UIApplicationDidEnterBackgroundNotification
|
||||||
@@ -54,33 +57,33 @@ CGFloat _keyboardHeight;
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)applicationWillEnterForeground:(UIApplication *)application {
|
- (void)applicationWillEnterForeground:(UIApplication *)application {
|
||||||
UIView *drawView = self.view.subviews[0];
|
GioView *view = (GioView *)self.view.subviews[0];
|
||||||
if (drawView != nil) {
|
if (view != nil) {
|
||||||
gio_onDraw((__bridge CFTypeRef)drawView);
|
onStart(view.handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
||||||
UIView *drawView = self.view.subviews[0];
|
GioView *view = (GioView *)self.view.subviews[0];
|
||||||
if (drawView != nil) {
|
if (view != nil) {
|
||||||
onStop((__bridge CFTypeRef)drawView);
|
onStop(view.handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewDidDisappear:(BOOL)animated {
|
- (void)viewDidDisappear:(BOOL)animated {
|
||||||
[super viewDidDisappear:animated];
|
[super viewDidDisappear:animated];
|
||||||
CFTypeRef viewRef = (__bridge CFTypeRef)self.view.subviews[0];
|
GioView *view = (GioView *)self.view.subviews[0];
|
||||||
onDestroy(viewRef);
|
onDestroy(view.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewDidLayoutSubviews {
|
- (void)viewDidLayoutSubviews {
|
||||||
[super viewDidLayoutSubviews];
|
[super viewDidLayoutSubviews];
|
||||||
UIView *view = self.view.subviews[0];
|
GioView *view = (GioView *)self.view.subviews[0];
|
||||||
CGRect frame = self.view.bounds;
|
CGRect frame = self.view.bounds;
|
||||||
// Adjust view bounds to make room for the keyboard.
|
// Adjust view bounds to make room for the keyboard.
|
||||||
frame.size.height -= _keyboardHeight;
|
frame.size.height -= _keyboardHeight;
|
||||||
view.frame = frame;
|
view.frame = frame;
|
||||||
gio_onDraw((__bridge CFTypeRef)view);
|
gio_onDraw(view.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)didReceiveMemoryWarning {
|
- (void)didReceiveMemoryWarning {
|
||||||
@@ -88,6 +91,7 @@ CGFloat _keyboardHeight;
|
|||||||
[super didReceiveMemoryWarning];
|
[super didReceiveMemoryWarning];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !TARGET_OS_TV
|
||||||
- (void)keyboardWillChange:(NSNotification *)note {
|
- (void)keyboardWillChange:(NSNotification *)note {
|
||||||
NSDictionary *userInfo = note.userInfo;
|
NSDictionary *userInfo = note.userInfo;
|
||||||
CGRect f = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
CGRect f = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
||||||
@@ -99,13 +103,13 @@ CGFloat _keyboardHeight;
|
|||||||
_keyboardHeight = 0.0;
|
_keyboardHeight = 0.0;
|
||||||
[self.view setNeedsLayout];
|
[self.view setNeedsLayout];
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
@end
|
@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;
|
CGFloat scale = view.contentScaleFactor;
|
||||||
NSUInteger i = 0;
|
NSUInteger i = 0;
|
||||||
NSUInteger n = [touches count];
|
NSUInteger n = [touches count];
|
||||||
CFTypeRef viewRef = (__bridge CFTypeRef)view;
|
|
||||||
for (UITouch *touch in touches) {
|
for (UITouch *touch in touches) {
|
||||||
CFTypeRef touchRef = (__bridge CFTypeRef)touch;
|
CFTypeRef touchRef = (__bridge CFTypeRef)touch;
|
||||||
i++;
|
i++;
|
||||||
@@ -116,7 +120,7 @@ static void handleTouches(int last, UIView *view, NSSet<UITouch *> *touches, UIE
|
|||||||
CGPoint loc = [coalescedTouch locationInView:view];
|
CGPoint loc = [coalescedTouch locationInView:view];
|
||||||
j++;
|
j++;
|
||||||
int lastTouch = last && i == n && j == m;
|
int lastTouch = last && i == n && j == m;
|
||||||
onTouch(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 {
|
- (void)onWindowDidBecomeKey:(NSNotification *)note {
|
||||||
if (self.isFirstResponder) {
|
if (self.isFirstResponder) {
|
||||||
onFocus((__bridge CFTypeRef)self, YES);
|
onFocus(self.handle, YES);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)onWindowDidResignKey:(NSNotification *)note {
|
- (void)onWindowDidResignKey:(NSNotification *)note {
|
||||||
if (self.isFirstResponder) {
|
if (self.isFirstResponder) {
|
||||||
onFocus((__bridge CFTypeRef)self, NO);
|
onFocus(self.handle, NO);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +182,7 @@ NSArray<UIKeyCommand *> *_keyCommands;
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)insertText:(NSString *)text {
|
- (void)insertText:(NSString *)text {
|
||||||
onText((__bridge CFTypeRef)self, (__bridge CFTypeRef)text);
|
onText(self.handle, (__bridge CFTypeRef)text);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)canBecomeFirstResponder {
|
- (BOOL)canBecomeFirstResponder {
|
||||||
@@ -190,23 +194,23 @@ NSArray<UIKeyCommand *> *_keyCommands;
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)deleteBackward {
|
- (void)deleteBackward {
|
||||||
onDeleteBackward((__bridge CFTypeRef)self);
|
onDeleteBackward(self.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)onUpArrow {
|
- (void)onUpArrow {
|
||||||
onUpArrow((__bridge CFTypeRef)self);
|
onUpArrow(self.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)onDownArrow {
|
- (void)onDownArrow {
|
||||||
onDownArrow((__bridge CFTypeRef)self);
|
onDownArrow(self.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)onLeftArrow {
|
- (void)onLeftArrow {
|
||||||
onLeftArrow((__bridge CFTypeRef)self);
|
onLeftArrow(self.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)onRightArrow {
|
- (void)onRightArrow {
|
||||||
onRightArrow((__bridge CFTypeRef)self);
|
onRightArrow(self.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray<UIKeyCommand *> *)keyCommands {
|
- (NSArray<UIKeyCommand *> *)keyCommands {
|
||||||
@@ -271,3 +275,28 @@ void gio_showCursor() {
|
|||||||
void gio_setCursor(NSUInteger curID) {
|
void gio_setCursor(NSUInteger curID) {
|
||||||
// Not supported.
|
// Not supported.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
|
||||||
|
GioView *v = (__bridge GioView *)viewRef;
|
||||||
|
v.handle = handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@interface _gioAppDelegate : UIResponder <UIApplicationDelegate>
|
||||||
|
@property (strong, nonatomic) UIWindow *window;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation _gioAppDelegate
|
||||||
|
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||||
|
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||||
|
GioViewController *controller = [[GioViewController alloc] initWithNibName:nil bundle:nil];
|
||||||
|
self.window.rootViewController = controller;
|
||||||
|
[self.window makeKeyAndVisible];
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
int gio_applicationMain(int argc, char *argv[]) {
|
||||||
|
@autoreleasepool {
|
||||||
|
return UIApplicationMain(argc, argv, nil, NSStringFromClass([_gioAppDelegate class]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+99
-96
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall/js"
|
"syscall/js"
|
||||||
"time"
|
"time"
|
||||||
@@ -13,16 +14,18 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"gioui.org/internal/f32color"
|
"gioui.org/internal/f32color"
|
||||||
|
"gioui.org/op"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/io/clipboard"
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
|
"gioui.org/io/transfer"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ViewEvent struct {
|
type JSViewEvent struct {
|
||||||
Element js.Value
|
Element js.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,9 +56,6 @@ type window struct {
|
|||||||
composing bool
|
composing bool
|
||||||
requestFocus bool
|
requestFocus bool
|
||||||
|
|
||||||
chanAnimation chan struct{}
|
|
||||||
chanRedraw chan struct{}
|
|
||||||
|
|
||||||
config Config
|
config Config
|
||||||
inset f32.Point
|
inset f32.Point
|
||||||
scale float32
|
scale float32
|
||||||
@@ -68,7 +68,7 @@ type window struct {
|
|||||||
contextStatus contextStatus
|
contextStatus contextStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWindow(win *callbacks, options []Option) error {
|
func newWindow(win *callbacks, options []Option) {
|
||||||
doc := js.Global().Get("document")
|
doc := js.Global().Get("document")
|
||||||
cont := getContainer(doc)
|
cont := getContainer(doc)
|
||||||
cnv := createCanvas(doc)
|
cnv := createCanvas(doc)
|
||||||
@@ -83,7 +83,9 @@ func newWindow(win *callbacks, options []Option) error {
|
|||||||
head: doc.Get("head"),
|
head: doc.Get("head"),
|
||||||
clipboard: js.Global().Get("navigator").Get("clipboard"),
|
clipboard: js.Global().Get("navigator").Get("clipboard"),
|
||||||
wakeups: make(chan struct{}, 1),
|
wakeups: make(chan struct{}, 1),
|
||||||
|
w: win,
|
||||||
}
|
}
|
||||||
|
w.w.SetDriver(w)
|
||||||
w.requestAnimationFrame = w.window.Get("requestAnimationFrame")
|
w.requestAnimationFrame = w.window.Get("requestAnimationFrame")
|
||||||
w.browserHistory = w.window.Get("history")
|
w.browserHistory = w.window.Get("history")
|
||||||
w.visualViewport = w.window.Get("visualViewport")
|
w.visualViewport = w.window.Get("visualViewport")
|
||||||
@@ -93,42 +95,28 @@ func newWindow(win *callbacks, options []Option) error {
|
|||||||
if screen := w.window.Get("screen"); screen.Truthy() {
|
if screen := w.window.Get("screen"); screen.Truthy() {
|
||||||
w.screenOrientation = screen.Get("orientation")
|
w.screenOrientation = screen.Get("orientation")
|
||||||
}
|
}
|
||||||
w.chanAnimation = make(chan struct{}, 1)
|
|
||||||
w.chanRedraw = make(chan struct{}, 1)
|
|
||||||
w.redraw = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
w.redraw = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
||||||
w.chanAnimation <- struct{}{}
|
w.draw(false)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.clipboardCallback = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
w.clipboardCallback = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
||||||
content := args[0].String()
|
content := args[0].String()
|
||||||
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
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListeners()
|
w.addEventListeners()
|
||||||
w.addHistory()
|
w.addHistory()
|
||||||
w.w = win
|
|
||||||
|
|
||||||
go func() {
|
w.Configure(options)
|
||||||
defer w.cleanup()
|
w.blur()
|
||||||
w.w.SetDriver(w)
|
w.processEvent(JSViewEvent{Element: cont})
|
||||||
w.Configure(options)
|
w.resize()
|
||||||
w.blur()
|
w.draw(true)
|
||||||
w.w.Event(ViewEvent{Element: cont})
|
|
||||||
w.w.Event(system.StageEvent{Stage: system.StageRunning})
|
|
||||||
w.resize()
|
|
||||||
w.draw(true)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-w.wakeups:
|
|
||||||
w.w.Event(wakeupEvent{})
|
|
||||||
case <-w.chanAnimation:
|
|
||||||
w.animCallback()
|
|
||||||
case <-w.chanRedraw:
|
|
||||||
w.draw(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getContainer(doc js.Value) js.Value {
|
func getContainer(doc js.Value) js.Value {
|
||||||
@@ -188,12 +176,12 @@ func (w *window) addEventListeners() {
|
|||||||
w.cnv.Set("width", 0)
|
w.cnv.Set("width", 0)
|
||||||
w.cnv.Set("height", 0)
|
w.cnv.Set("height", 0)
|
||||||
w.resize()
|
w.resize()
|
||||||
w.requestRedraw()
|
w.draw(true)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.visualViewport, "resize", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.visualViewport, "resize", func(this js.Value, args []js.Value) interface{} {
|
||||||
w.resize()
|
w.resize()
|
||||||
w.requestRedraw()
|
w.draw(true)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.window, "contextmenu", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.window, "contextmenu", func(this js.Value, args []js.Value) interface{} {
|
||||||
@@ -201,22 +189,11 @@ func (w *window) addEventListeners() {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.window, "popstate", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.window, "popstate", func(this js.Value, args []js.Value) interface{} {
|
||||||
if w.w.Event(key.Event{Name: key.NameBack}) {
|
if w.processEvent(key.Event{Name: key.NameBack}) {
|
||||||
return w.browserHistory.Call("forward")
|
return w.browserHistory.Call("forward")
|
||||||
}
|
}
|
||||||
return w.browserHistory.Call("back")
|
return w.browserHistory.Call("back")
|
||||||
})
|
})
|
||||||
w.addEventListener(w.document, "visibilitychange", func(this js.Value, args []js.Value) interface{} {
|
|
||||||
ev := system.StageEvent{}
|
|
||||||
switch w.document.Get("visibilityState").String() {
|
|
||||||
case "hidden", "prerender", "unloaded":
|
|
||||||
ev.Stage = system.StagePaused
|
|
||||||
default:
|
|
||||||
ev.Stage = system.StageRunning
|
|
||||||
}
|
|
||||||
w.w.Event(ev)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
w.addEventListener(w.cnv, "mousemove", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.cnv, "mousemove", func(this js.Value, args []js.Value) interface{} {
|
||||||
w.pointerEvent(pointer.Move, 0, 0, args[0])
|
w.pointerEvent(pointer.Move, 0, 0, args[0])
|
||||||
return nil
|
return nil
|
||||||
@@ -274,18 +251,20 @@ func (w *window) addEventListeners() {
|
|||||||
w.touches[i] = js.Null()
|
w.touches[i] = js.Null()
|
||||||
}
|
}
|
||||||
w.touches = w.touches[:0]
|
w.touches = w.touches[:0]
|
||||||
w.w.Event(pointer.Event{
|
w.processEvent(pointer.Event{
|
||||||
Kind: pointer.Cancel,
|
Kind: pointer.Cancel,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.tarea, "focus", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.tarea, "focus", func(this js.Value, args []js.Value) interface{} {
|
||||||
w.w.Event(key.FocusEvent{Focus: true})
|
w.config.Focused = true
|
||||||
|
w.processEvent(ConfigEvent{Config: w.config})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.tarea, "blur", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.tarea, "blur", func(this js.Value, args []js.Value) interface{} {
|
||||||
w.w.Event(key.FocusEvent{Focus: false})
|
w.config.Focused = false
|
||||||
|
w.processEvent(ConfigEvent{Config: w.config})
|
||||||
w.blur()
|
w.blur()
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -374,10 +353,50 @@ func (w *window) keyEvent(e js.Value, ks key.State) {
|
|||||||
Modifiers: modifiersFor(e),
|
Modifiers: modifiersFor(e),
|
||||||
State: ks,
|
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
|
// modifiersFor returns the modifier set for a DOM MouseEvent or
|
||||||
// KeyEvent.
|
// KeyEvent.
|
||||||
func modifiersFor(e js.Value) key.Modifiers {
|
func modifiersFor(e js.Value) key.Modifiers {
|
||||||
@@ -425,7 +444,7 @@ func (w *window) touchEvent(kind pointer.Kind, e js.Value) {
|
|||||||
X: float32(x) * scale,
|
X: float32(x) * scale,
|
||||||
Y: float32(y) * scale,
|
Y: float32(y) * scale,
|
||||||
}
|
}
|
||||||
w.w.Event(pointer.Event{
|
w.processEvent(pointer.Event{
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
Position: pos,
|
Position: pos,
|
||||||
@@ -475,7 +494,7 @@ func (w *window) pointerEvent(kind pointer.Kind, dx, dy float32, e js.Value) {
|
|||||||
if jbtns&4 != 0 {
|
if jbtns&4 != 0 {
|
||||||
btns |= pointer.ButtonTertiary
|
btns |= pointer.ButtonTertiary
|
||||||
}
|
}
|
||||||
w.w.Event(pointer.Event{
|
w.processEvent(pointer.Event{
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Buttons: btns,
|
Buttons: btns,
|
||||||
@@ -502,17 +521,6 @@ func (w *window) funcOf(f func(this js.Value, args []js.Value) interface{}) js.F
|
|||||||
return jsf
|
return jsf
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) animCallback() {
|
|
||||||
anim := w.animating
|
|
||||||
w.animRequested = anim
|
|
||||||
if anim {
|
|
||||||
w.requestAnimationFrame.Invoke(w.redraw)
|
|
||||||
}
|
|
||||||
if anim {
|
|
||||||
w.draw(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) EditorStateChanged(old, new editorState) {}
|
func (w *window) EditorStateChanged(old, new editorState) {}
|
||||||
|
|
||||||
func (w *window) SetAnimating(anim bool) {
|
func (w *window) SetAnimating(anim bool) {
|
||||||
@@ -533,14 +541,14 @@ func (w *window) ReadClipboard() {
|
|||||||
w.clipboard.Call("readText", w.clipboard).Call("then", w.clipboardCallback)
|
w.clipboard.Call("readText", w.clipboard).Call("then", w.clipboardCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) WriteClipboard(s string) {
|
func (w *window) WriteClipboard(mime string, s []byte) {
|
||||||
if w.clipboard.IsUndefined() {
|
if w.clipboard.IsUndefined() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if w.clipboard.Get("writeText").IsUndefined() {
|
if w.clipboard.Get("writeText").IsUndefined() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.clipboard.Call("writeText", s)
|
w.clipboard.Call("writeText", string(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Configure(options []Option) {
|
func (w *window) Configure(options []Option) {
|
||||||
@@ -568,7 +576,7 @@ func (w *window) Configure(options []Option) {
|
|||||||
if cnf.Decorated != prev.Decorated {
|
if cnf.Decorated != prev.Decorated {
|
||||||
w.config.Decorated = cnf.Decorated
|
w.config.Decorated = cnf.Decorated
|
||||||
}
|
}
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.processEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Perform(system.Action) {}
|
func (w *window) Perform(system.Action) {}
|
||||||
@@ -607,23 +615,14 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
|||||||
style.Set("cursor", webCursor[cursor])
|
style.Set("cursor", webCursor[cursor])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Wakeup() {
|
|
||||||
select {
|
|
||||||
case w.wakeups <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) ShowTextInput(show bool) {
|
func (w *window) ShowTextInput(show bool) {
|
||||||
// Run in a goroutine to avoid a deadlock if the
|
// Run in a goroutine to avoid a deadlock if the
|
||||||
// focus change result in an event.
|
// focus change result in an event.
|
||||||
go func() {
|
if show {
|
||||||
if show {
|
w.focus()
|
||||||
w.focus()
|
} else {
|
||||||
} else {
|
w.blur()
|
||||||
w.blur()
|
}
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) SetInputHint(mode key.InputHint) {
|
func (w *window) SetInputHint(mode key.InputHint) {
|
||||||
@@ -640,7 +639,7 @@ func (w *window) resize() {
|
|||||||
}
|
}
|
||||||
if size != w.config.Size {
|
if size != w.config.Size {
|
||||||
w.config.Size = 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() {
|
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 {
|
if w.contextStatus == contextStatusLost {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
anim := w.animating
|
||||||
|
w.animRequested = anim
|
||||||
|
if anim {
|
||||||
|
w.requestAnimationFrame.Invoke(w.redraw)
|
||||||
|
} else if !sync {
|
||||||
|
return
|
||||||
|
}
|
||||||
size, insets, metric := w.getConfig()
|
size, insets, metric := w.getConfig()
|
||||||
if metric == (unit.Metric{}) || size.X == 0 || size.Y == 0 {
|
if metric == (unit.Metric{}) || size.X == 0 || size.Y == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.w.Event(frameEvent{
|
w.processEvent(frameEvent{
|
||||||
FrameEvent: system.FrameEvent{
|
FrameEvent: FrameEvent{
|
||||||
Now: time.Now(),
|
Now: time.Now(),
|
||||||
Size: size,
|
Size: size,
|
||||||
Insets: insets,
|
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)
|
invscale := unit.Dp(1. / w.scale)
|
||||||
return image.Pt(w.config.Size.X, w.config.Size.Y),
|
return image.Pt(w.config.Size.X, w.config.Size.Y),
|
||||||
system.Insets{
|
Insets{
|
||||||
Bottom: unit.Dp(w.inset.Y) * invscale,
|
Bottom: unit.Dp(w.inset.Y) * invscale,
|
||||||
Right: unit.Dp(w.inset.X) * invscale,
|
Right: unit.Dp(w.inset.X) * invscale,
|
||||||
}, unit.Metric{
|
}, unit.Metric{
|
||||||
@@ -735,19 +741,12 @@ func (w *window) navigationColor(c color.NRGBA) {
|
|||||||
theme.Set("content", fmt.Sprintf("#%06X", []uint8{rgba.R, rgba.G, rgba.B}))
|
theme.Set("content", fmt.Sprintf("#%06X", []uint8{rgba.R, rgba.G, rgba.B}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) requestRedraw() {
|
|
||||||
select {
|
|
||||||
case w.chanRedraw <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func osMain() {
|
func osMain() {
|
||||||
select {}
|
select {}
|
||||||
}
|
}
|
||||||
|
|
||||||
func translateKey(k string) (string, bool) {
|
func translateKey(k string) (key.Name, bool) {
|
||||||
var n string
|
var n key.Name
|
||||||
|
|
||||||
switch k {
|
switch k {
|
||||||
case "ArrowUp":
|
case "ArrowUp":
|
||||||
@@ -814,11 +813,15 @@ func translateKey(k string) (string, bool) {
|
|||||||
r, s := utf8.DecodeRuneInString(k)
|
r, s := utf8.DecodeRuneInString(k)
|
||||||
// If there is exactly one printable character, return that.
|
// If there is exactly one printable character, return that.
|
||||||
if s == len(k) && unicode.IsPrint(r) {
|
if s == len(k) && unicode.IsPrint(r) {
|
||||||
return strings.ToUpper(k), true
|
return key.Name(strings.ToUpper(k)), true
|
||||||
}
|
}
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
return n, true
|
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"
|
#include "_cgo_export.h"
|
||||||
|
|
||||||
__attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(void);
|
__attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(BOOL presentWithTrans);
|
||||||
|
|
||||||
@interface GioAppDelegate : NSObject<NSApplicationDelegate>
|
@interface GioAppDelegate : NSObject<NSApplicationDelegate>
|
||||||
@end
|
@end
|
||||||
@@ -14,40 +14,55 @@ __attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(void);
|
|||||||
@interface GioWindowDelegate : NSObject<NSWindowDelegate>
|
@interface GioWindowDelegate : NSObject<NSWindowDelegate>
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@interface GioView : NSView <CALayerDelegate,NSTextInputClient>
|
||||||
|
@property uintptr_t handle;
|
||||||
|
@property BOOL presentWithTrans;
|
||||||
|
@end
|
||||||
|
|
||||||
@implementation GioWindowDelegate
|
@implementation GioWindowDelegate
|
||||||
- (void)windowWillMiniaturize:(NSNotification *)notification {
|
- (void)windowWillMiniaturize:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
gio_onHide((__bridge CFTypeRef)window.contentView);
|
GioView *view = (GioView *)window.contentView;
|
||||||
|
gio_onDraw(view.handle);
|
||||||
}
|
}
|
||||||
- (void)windowDidDeminiaturize:(NSNotification *)notification {
|
- (void)windowDidDeminiaturize:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
gio_onShow((__bridge CFTypeRef)window.contentView);
|
GioView *view = (GioView *)window.contentView;
|
||||||
|
gio_onDraw(view.handle);
|
||||||
}
|
}
|
||||||
- (void)windowWillEnterFullScreen:(NSNotification *)notification {
|
- (void)windowWillEnterFullScreen:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
gio_onFullscreen((__bridge CFTypeRef)window.contentView);
|
GioView *view = (GioView *)window.contentView;
|
||||||
|
gio_onDraw(view.handle);
|
||||||
}
|
}
|
||||||
- (void)windowWillExitFullScreen:(NSNotification *)notification {
|
- (void)windowWillExitFullScreen:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
gio_onWindowed((__bridge CFTypeRef)window.contentView);
|
GioView *view = (GioView *)window.contentView;
|
||||||
|
gio_onDraw(view.handle);
|
||||||
}
|
}
|
||||||
- (void)windowDidChangeScreen:(NSNotification *)notification {
|
- (void)windowDidChangeScreen:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
CGDirectDisplayID dispID = [[[window screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue];
|
CGDirectDisplayID dispID = [[[window screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue];
|
||||||
CFTypeRef view = (__bridge CFTypeRef)window.contentView;
|
GioView *view = (GioView *)window.contentView;
|
||||||
gio_onChangeScreen(view, dispID);
|
gio_onChangeScreen(view.handle, dispID);
|
||||||
}
|
}
|
||||||
- (void)windowDidBecomeKey:(NSNotification *)notification {
|
- (void)windowDidBecomeKey:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
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 {
|
- (void)windowDidResignKey:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
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
|
@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];
|
NSPoint p = [view convertPoint:[event locationInWindow] fromView:nil];
|
||||||
if (!event.hasPreciseScrollingDeltas) {
|
if (!event.hasPreciseScrollingDeltas) {
|
||||||
// dx and dy are in rows and columns.
|
// dx and dy are in rows and columns.
|
||||||
@@ -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.
|
// Origin is in the lower left corner. Convert to upper left.
|
||||||
CGFloat height = view.bounds.size.height;
|
CGFloat height = view.bounds.size.height;
|
||||||
gio_onMouse((__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
|
@implementation GioView
|
||||||
- (void)setFrameSize:(NSSize)newSize {
|
- (void)setFrameSize:(NSSize)newSize {
|
||||||
[super setFrameSize: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.
|
// drawRect is called when OpenGL is used, displayLayer otherwise.
|
||||||
// Don't know why.
|
// Don't know why.
|
||||||
- (void)drawRect:(NSRect)r {
|
- (void)drawRect:(NSRect)r {
|
||||||
gio_onDraw((__bridge CFTypeRef)self);
|
gio_onDraw(self.handle);
|
||||||
}
|
}
|
||||||
- (void)displayLayer:(CALayer *)layer {
|
- (void)displayLayer:(CALayer *)layer {
|
||||||
layer.contentsScale = self.window.backingScaleFactor;
|
layer.contentsScale = self.window.backingScaleFactor;
|
||||||
gio_onDraw((__bridge CFTypeRef)self);
|
gio_onDraw(self.handle);
|
||||||
}
|
}
|
||||||
- (CALayer *)makeBackingLayer {
|
- (CALayer *)makeBackingLayer {
|
||||||
CALayer *layer = gio_layerFactory();
|
CALayer *layer = gio_layerFactory(self.presentWithTrans);
|
||||||
layer.delegate = self;
|
layer.delegate = self;
|
||||||
return layer;
|
return layer;
|
||||||
}
|
}
|
||||||
- (void)viewDidMoveToWindow {
|
- (void)viewDidMoveToWindow {
|
||||||
if (self.window == nil) {
|
gio_onAttached(self.handle, self.window != nil ? 1 : 0);
|
||||||
gio_onClose((__bridge CFTypeRef)self);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
- (void)mouseDown:(NSEvent *)event {
|
- (void)mouseDown:(NSEvent *)event {
|
||||||
handleMouse(self, event, MOUSE_DOWN, 0, 0);
|
handleMouse(self, event, MOUSE_DOWN, 0, 0);
|
||||||
@@ -122,34 +132,37 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
|||||||
handleMouse(self, event, MOUSE_SCROLL, dx, dy);
|
handleMouse(self, event, MOUSE_SCROLL, dx, dy);
|
||||||
}
|
}
|
||||||
- (void)keyDown:(NSEvent *)event {
|
- (void)keyDown:(NSEvent *)event {
|
||||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
|
||||||
NSString *keys = [event charactersIgnoringModifiers];
|
NSString *keys = [event charactersIgnoringModifiers];
|
||||||
gio_onKeys((__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 {
|
- (void)keyUp:(NSEvent *)event {
|
||||||
NSString *keys = [event charactersIgnoringModifiers];
|
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 {
|
- (void)insertText:(id)string {
|
||||||
gio_onText((__bridge CFTypeRef)self, (__bridge CFTypeRef)string);
|
gio_onText(self.handle, (__bridge CFTypeRef)string);
|
||||||
}
|
}
|
||||||
- (void)doCommandBySelector:(SEL)sel {
|
- (void)doCommandBySelector:(SEL)action {
|
||||||
// Don't pass commands up the responder chain.
|
if (!gio_onCommandBySelector(self.handle)) {
|
||||||
// They will end up in a beep.
|
[super doCommandBySelector:action];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)hasMarkedText {
|
- (BOOL)hasMarkedText {
|
||||||
int res = gio_hasMarkedText((__bridge CFTypeRef)self);
|
int res = gio_hasMarkedText(self.handle);
|
||||||
return res ? YES : NO;
|
return res ? YES : NO;
|
||||||
}
|
}
|
||||||
- (NSRange)markedRange {
|
- (NSRange)markedRange {
|
||||||
return gio_markedRange((__bridge CFTypeRef)self);
|
return gio_markedRange(self.handle);
|
||||||
}
|
}
|
||||||
- (NSRange)selectedRange {
|
- (NSRange)selectedRange {
|
||||||
return gio_selectedRange((__bridge CFTypeRef)self);
|
return gio_selectedRange(self.handle);
|
||||||
}
|
}
|
||||||
- (void)unmarkText {
|
- (void)unmarkText {
|
||||||
gio_unmarkText((__bridge CFTypeRef)self);
|
gio_unmarkText(self.handle);
|
||||||
}
|
}
|
||||||
- (void)setMarkedText:(id)string
|
- (void)setMarkedText:(id)string
|
||||||
selectedRange:(NSRange)selRange
|
selectedRange:(NSRange)selRange
|
||||||
@@ -161,14 +174,14 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
|||||||
} else {
|
} else {
|
||||||
str = string;
|
str = string;
|
||||||
}
|
}
|
||||||
gio_setMarkedText((__bridge CFTypeRef)self, (__bridge CFTypeRef)str, selRange, replaceRange);
|
gio_setMarkedText(self.handle, (__bridge CFTypeRef)str, selRange, replaceRange);
|
||||||
}
|
}
|
||||||
- (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText {
|
- (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range
|
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range
|
||||||
actualRange:(NSRangePointer)actualRange {
|
actualRange:(NSRangePointer)actualRange {
|
||||||
NSString *str = CFBridgingRelease(gio_substringForProposedRange((__bridge CFTypeRef)self, range, actualRange));
|
NSString *str = CFBridgingRelease(gio_substringForProposedRange(self.handle, range, actualRange));
|
||||||
return [[NSAttributedString alloc] initWithString:str attributes:nil];
|
return [[NSAttributedString alloc] initWithString:str attributes:nil];
|
||||||
}
|
}
|
||||||
- (void)insertText:(id)string
|
- (void)insertText:(id)string
|
||||||
@@ -180,17 +193,34 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
|||||||
} else {
|
} else {
|
||||||
str = string;
|
str = string;
|
||||||
}
|
}
|
||||||
gio_insertText((__bridge CFTypeRef)self, (__bridge CFTypeRef)str, replaceRange);
|
gio_insertText(self.handle, (__bridge CFTypeRef)str, replaceRange);
|
||||||
}
|
}
|
||||||
- (NSUInteger)characterIndexForPoint:(NSPoint)p {
|
- (NSUInteger)characterIndexForPoint:(NSPoint)p {
|
||||||
return gio_characterIndexForPoint((__bridge CFTypeRef)self, p);
|
return gio_characterIndexForPoint(self.handle, p);
|
||||||
}
|
}
|
||||||
- (NSRect)firstRectForCharacterRange:(NSRange)rng
|
- (NSRect)firstRectForCharacterRange:(NSRange)rng
|
||||||
actualRange:(NSRangePointer)actual {
|
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];
|
r = [self convertRect:r toView:nil];
|
||||||
return [[self window] convertRectToScreen:r];
|
return [[self window] convertRectToScreen:r];
|
||||||
}
|
}
|
||||||
|
- (void)applicationWillUnhide:(NSNotification *)notification {
|
||||||
|
gio_onDraw(self.handle);
|
||||||
|
}
|
||||||
|
- (void)applicationDidHide:(NSNotification *)notification {
|
||||||
|
gio_onDraw(self.handle);
|
||||||
|
}
|
||||||
|
- (void)dealloc {
|
||||||
|
gio_onDestroy(self.handle);
|
||||||
|
}
|
||||||
|
- (BOOL) becomeFirstResponder {
|
||||||
|
gio_onFocus(self.handle, 1);
|
||||||
|
return [super becomeFirstResponder];
|
||||||
|
}
|
||||||
|
- (BOOL) resignFirstResponder {
|
||||||
|
gio_onFocus(self.handle, 0);
|
||||||
|
return [super resignFirstResponder];
|
||||||
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
// Delegates are weakly referenced from their peers. Nothing
|
// Delegates are weakly referenced from their peers. Nothing
|
||||||
@@ -240,7 +270,7 @@ void gio_showCursor() {
|
|||||||
|
|
||||||
// some cursors are not public, this tries to use a private cursor
|
// some cursors are not public, this tries to use a private cursor
|
||||||
// and uses fallback when the use of private cursor fails.
|
// and uses fallback when the use of private cursor fails.
|
||||||
void gio_trySetPrivateCursor(SEL cursorName, NSCursor* fallback) {
|
static void trySetPrivateCursor(SEL cursorName, NSCursor* fallback) {
|
||||||
if ([NSCursor respondsToSelector:cursorName]) {
|
if ([NSCursor respondsToSelector:cursorName]) {
|
||||||
id object = [NSCursor performSelector:cursorName];
|
id object = [NSCursor performSelector:cursorName];
|
||||||
if ([object isKindOfClass:[NSCursor class]]) {
|
if ([object isKindOfClass:[NSCursor class]]) {
|
||||||
@@ -272,7 +302,7 @@ void gio_setCursor(NSUInteger curID) {
|
|||||||
break;
|
break;
|
||||||
case 6: // pointer.CursorAllScroll
|
case 6: // pointer.CursorAllScroll
|
||||||
// For some reason, using _moveCursor fails on Monterey.
|
// For some reason, using _moveCursor fails on Monterey.
|
||||||
// gio_trySetPrivateCursor(@selector(_moveCursor), NSCursor.arrowCursor);
|
// trySetPrivateCursor(@selector(_moveCursor), NSCursor.arrowCursor);
|
||||||
[NSCursor.arrowCursor set];
|
[NSCursor.arrowCursor set];
|
||||||
break;
|
break;
|
||||||
case 7: // pointer.CursorColResize
|
case 7: // pointer.CursorColResize
|
||||||
@@ -282,33 +312,31 @@ void gio_setCursor(NSUInteger curID) {
|
|||||||
[NSCursor.resizeUpDownCursor set];
|
[NSCursor.resizeUpDownCursor set];
|
||||||
break;
|
break;
|
||||||
case 9: // pointer.CursorGrab
|
case 9: // pointer.CursorGrab
|
||||||
// [NSCursor.openHandCursor set];
|
[NSCursor.openHandCursor set];
|
||||||
gio_trySetPrivateCursor(@selector(openHandCursor), NSCursor.arrowCursor);
|
|
||||||
break;
|
break;
|
||||||
case 10: // pointer.CursorGrabbing
|
case 10: // pointer.CursorGrabbing
|
||||||
// [NSCursor.closedHandCursor set];
|
[NSCursor.closedHandCursor set];
|
||||||
gio_trySetPrivateCursor(@selector(closedHandCursor), NSCursor.arrowCursor);
|
|
||||||
break;
|
break;
|
||||||
case 11: // pointer.CursorNotAllowed
|
case 11: // pointer.CursorNotAllowed
|
||||||
[NSCursor.operationNotAllowedCursor set];
|
[NSCursor.operationNotAllowedCursor set];
|
||||||
break;
|
break;
|
||||||
case 12: // pointer.CursorWait
|
case 12: // pointer.CursorWait
|
||||||
gio_trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
||||||
break;
|
break;
|
||||||
case 13: // pointer.CursorProgress
|
case 13: // pointer.CursorProgress
|
||||||
gio_trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
||||||
break;
|
break;
|
||||||
case 14: // pointer.CursorNorthWestResize
|
case 14: // pointer.CursorNorthWestResize
|
||||||
gio_trySetPrivateCursor(@selector(_windowResizeNorthWestCursor), NSCursor.resizeUpDownCursor);
|
trySetPrivateCursor(@selector(_windowResizeNorthWestCursor), NSCursor.resizeUpDownCursor);
|
||||||
break;
|
break;
|
||||||
case 15: // pointer.CursorNorthEastResize
|
case 15: // pointer.CursorNorthEastResize
|
||||||
gio_trySetPrivateCursor(@selector(_windowResizeNorthEastCursor), NSCursor.resizeUpDownCursor);
|
trySetPrivateCursor(@selector(_windowResizeNorthEastCursor), NSCursor.resizeUpDownCursor);
|
||||||
break;
|
break;
|
||||||
case 16: // pointer.CursorSouthWestResize
|
case 16: // pointer.CursorSouthWestResize
|
||||||
gio_trySetPrivateCursor(@selector(_windowResizeSouthWestCursor), NSCursor.resizeUpDownCursor);
|
trySetPrivateCursor(@selector(_windowResizeSouthWestCursor), NSCursor.resizeUpDownCursor);
|
||||||
break;
|
break;
|
||||||
case 17: // pointer.CursorSouthEastResize
|
case 17: // pointer.CursorSouthEastResize
|
||||||
gio_trySetPrivateCursor(@selector(_windowResizeSouthEastCursor), NSCursor.resizeUpDownCursor);
|
trySetPrivateCursor(@selector(_windowResizeSouthEastCursor), NSCursor.resizeUpDownCursor);
|
||||||
break;
|
break;
|
||||||
case 18: // pointer.CursorNorthSouthResize
|
case 18: // pointer.CursorNorthSouthResize
|
||||||
[NSCursor.resizeUpDownCursor set];
|
[NSCursor.resizeUpDownCursor set];
|
||||||
@@ -329,10 +357,10 @@ void gio_setCursor(NSUInteger curID) {
|
|||||||
[NSCursor.resizeDownCursor set];
|
[NSCursor.resizeDownCursor set];
|
||||||
break;
|
break;
|
||||||
case 24: // pointer.CursorNorthEastSouthWestResize
|
case 24: // pointer.CursorNorthEastSouthWestResize
|
||||||
gio_trySetPrivateCursor(@selector(_windowResizeNorthEastSouthWestCursor), NSCursor.resizeUpDownCursor);
|
trySetPrivateCursor(@selector(_windowResizeNorthEastSouthWestCursor), NSCursor.resizeUpDownCursor);
|
||||||
break;
|
break;
|
||||||
case 25: // pointer.CursorNorthWestSouthEastResize
|
case 25: // pointer.CursorNorthWestSouthEastResize
|
||||||
gio_trySetPrivateCursor(@selector(_windowResizeNorthWestSouthEastCursor), NSCursor.resizeUpDownCursor);
|
trySetPrivateCursor(@selector(_windowResizeNorthWestSouthEastCursor), NSCursor.resizeUpDownCursor);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
[NSCursor.arrowCursor set];
|
[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 {
|
@autoreleasepool {
|
||||||
NSRect rect = NSMakeRect(0, 0, width, height);
|
NSRect rect = NSMakeRect(0, 0, width, height);
|
||||||
NSUInteger styleMask = NSTitledWindowMask |
|
NSUInteger styleMask = NSTitledWindowMask |
|
||||||
@@ -353,42 +381,45 @@ CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGF
|
|||||||
styleMask:styleMask
|
styleMask:styleMask
|
||||||
backing:NSBackingStoreBuffered
|
backing:NSBackingStoreBuffered
|
||||||
defer:NO];
|
defer:NO];
|
||||||
if (minWidth > 0 || minHeight > 0) {
|
|
||||||
window.contentMinSize = NSMakeSize(minWidth, minHeight);
|
|
||||||
}
|
|
||||||
if (maxWidth > 0 || maxHeight > 0) {
|
|
||||||
window.contentMaxSize = NSMakeSize(maxWidth, maxHeight);
|
|
||||||
}
|
|
||||||
[window setAcceptsMouseMovedEvents:YES];
|
[window setAcceptsMouseMovedEvents:YES];
|
||||||
NSView *view = (__bridge NSView *)viewRef;
|
NSView *view = (__bridge NSView *)viewRef;
|
||||||
[window setContentView:view];
|
[window setContentView:view];
|
||||||
[window makeFirstResponder:view];
|
|
||||||
window.delegate = globalWindowDel;
|
window.delegate = globalWindowDel;
|
||||||
return (__bridge_retained CFTypeRef)window;
|
return (__bridge_retained CFTypeRef)window;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CFTypeRef gio_createView(void) {
|
CFTypeRef gio_createView(int presentWithTrans) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
NSRect frame = NSMakeRect(0, 0, 0, 0);
|
NSRect frame = NSMakeRect(0, 0, 0, 0);
|
||||||
GioView* view = [[GioView alloc] initWithFrame:frame];
|
GioView* view = [[GioView alloc] initWithFrame:frame];
|
||||||
|
view.presentWithTrans = presentWithTrans ? YES : NO;
|
||||||
view.wantsLayer = YES;
|
view.wantsLayer = YES;
|
||||||
view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
|
view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
|
||||||
|
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:view
|
||||||
|
selector:@selector(applicationWillUnhide:)
|
||||||
|
name:NSApplicationWillUnhideNotification
|
||||||
|
object:nil];
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:view
|
||||||
|
selector:@selector(applicationDidHide:)
|
||||||
|
name:NSApplicationDidHideNotification
|
||||||
|
object:nil];
|
||||||
return CFBridgingRetain(view);
|
return CFBridgingRetain(view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
|
||||||
|
@autoreleasepool {
|
||||||
|
GioView *v = (__bridge GioView *)viewRef;
|
||||||
|
v.handle = handle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@implementation GioAppDelegate
|
@implementation GioAppDelegate
|
||||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||||
[NSApp activateIgnoringOtherApps:YES];
|
[NSApp activateIgnoringOtherApps:YES];
|
||||||
gio_onFinishLaunching();
|
|
||||||
}
|
|
||||||
- (void)applicationDidHide:(NSNotification *)aNotification {
|
|
||||||
gio_onAppHide();
|
|
||||||
}
|
|
||||||
- (void)applicationWillUnhide:(NSNotification *)notification {
|
|
||||||
gio_onAppShow();
|
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -419,3 +450,25 @@ void gio_main() {
|
|||||||
[NSApp run];
|
[NSApp run];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@interface AppListener : NSObject
|
||||||
|
@end
|
||||||
|
|
||||||
|
static AppListener *appListener;
|
||||||
|
|
||||||
|
@implementation AppListener
|
||||||
|
- (void)launchFinished:(NSNotification *)notification {
|
||||||
|
appListener = nil;
|
||||||
|
gio_onFinishLaunching();
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
void gio_init() {
|
||||||
|
@autoreleasepool {
|
||||||
|
appListener = [[AppListener alloc] init];
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:appListener
|
||||||
|
selector:@selector(launchFinished:)
|
||||||
|
name:NSApplicationDidFinishLaunchingNotification
|
||||||
|
object:nil];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+11
-12
@@ -12,13 +12,6 @@ import (
|
|||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ViewEvent provides handles to the underlying window objects for the
|
|
||||||
// current display protocol.
|
|
||||||
type ViewEvent interface {
|
|
||||||
implementsViewEvent()
|
|
||||||
ImplementsEvent()
|
|
||||||
}
|
|
||||||
|
|
||||||
type X11ViewEvent struct {
|
type X11ViewEvent struct {
|
||||||
// Display is a pointer to the X11 Display created by XOpenDisplay.
|
// Display is a pointer to the X11 Display created by XOpenDisplay.
|
||||||
Display unsafe.Pointer
|
Display unsafe.Pointer
|
||||||
@@ -28,6 +21,9 @@ type X11ViewEvent struct {
|
|||||||
|
|
||||||
func (X11ViewEvent) implementsViewEvent() {}
|
func (X11ViewEvent) implementsViewEvent() {}
|
||||||
func (X11ViewEvent) ImplementsEvent() {}
|
func (X11ViewEvent) ImplementsEvent() {}
|
||||||
|
func (x X11ViewEvent) Valid() bool {
|
||||||
|
return x != (X11ViewEvent{})
|
||||||
|
}
|
||||||
|
|
||||||
type WaylandViewEvent struct {
|
type WaylandViewEvent struct {
|
||||||
// Display is the *wl_display returned by wl_display_connect.
|
// Display is the *wl_display returned by wl_display_connect.
|
||||||
@@ -38,6 +34,9 @@ type WaylandViewEvent struct {
|
|||||||
|
|
||||||
func (WaylandViewEvent) implementsViewEvent() {}
|
func (WaylandViewEvent) implementsViewEvent() {}
|
||||||
func (WaylandViewEvent) ImplementsEvent() {}
|
func (WaylandViewEvent) ImplementsEvent() {}
|
||||||
|
func (w WaylandViewEvent) Valid() bool {
|
||||||
|
return w != (WaylandViewEvent{})
|
||||||
|
}
|
||||||
|
|
||||||
func osMain() {
|
func osMain() {
|
||||||
select {}
|
select {}
|
||||||
@@ -49,7 +48,7 @@ type windowDriver func(*callbacks, []Option) error
|
|||||||
// let each driver initialize these variables with their own version of createWindow.
|
// let each driver initialize these variables with their own version of createWindow.
|
||||||
var wlDriver, x11Driver windowDriver
|
var wlDriver, x11Driver windowDriver
|
||||||
|
|
||||||
func newWindow(window *callbacks, options []Option) error {
|
func newWindow(window *callbacks, options []Option) {
|
||||||
var errFirst error
|
var errFirst error
|
||||||
for _, d := range []windowDriver{wlDriver, x11Driver} {
|
for _, d := range []windowDriver{wlDriver, x11Driver} {
|
||||||
if d == nil {
|
if d == nil {
|
||||||
@@ -57,16 +56,16 @@ func newWindow(window *callbacks, options []Option) error {
|
|||||||
}
|
}
|
||||||
err := d(window, options)
|
err := d(window, options)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
if errFirst == nil {
|
if errFirst == nil {
|
||||||
errFirst = err
|
errFirst = err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if errFirst != nil {
|
if errFirst == nil {
|
||||||
return errFirst
|
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.
|
// xCursor contains mapping from pointer.Cursor to XCursor.
|
||||||
|
|||||||
+204
-133
@@ -15,6 +15,7 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -25,10 +26,12 @@ import (
|
|||||||
"gioui.org/app/internal/xkb"
|
"gioui.org/app/internal/xkb"
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/internal/fling"
|
"gioui.org/internal/fling"
|
||||||
"gioui.org/io/clipboard"
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
|
"gioui.org/io/transfer"
|
||||||
|
"gioui.org/op"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -97,7 +100,9 @@ type wlDisplay struct {
|
|||||||
read, write int
|
read, write int
|
||||||
}
|
}
|
||||||
|
|
||||||
repeat repeatState
|
repeat repeatState
|
||||||
|
poller poller
|
||||||
|
readClipClose chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type wlSeat struct {
|
type wlSeat struct {
|
||||||
@@ -111,6 +116,8 @@ type wlSeat struct {
|
|||||||
|
|
||||||
// The most recent input serial.
|
// The most recent input serial.
|
||||||
serial C.uint32_t
|
serial C.uint32_t
|
||||||
|
// The most recent pointer enter serial.
|
||||||
|
pointerSerial C.uint32_t
|
||||||
|
|
||||||
pointerFocus *window
|
pointerFocus *window
|
||||||
keyboardFocus *window
|
keyboardFocus *window
|
||||||
@@ -137,7 +144,7 @@ type repeatState struct {
|
|||||||
delay time.Duration
|
delay time.Duration
|
||||||
|
|
||||||
key uint32
|
key uint32
|
||||||
win *callbacks
|
win *window
|
||||||
stopC chan struct{}
|
stopC chan struct{}
|
||||||
|
|
||||||
start time.Duration
|
start time.Duration
|
||||||
@@ -149,7 +156,6 @@ type repeatState struct {
|
|||||||
type window struct {
|
type window struct {
|
||||||
w *callbacks
|
w *callbacks
|
||||||
disp *wlDisplay
|
disp *wlDisplay
|
||||||
seat *wlSeat
|
|
||||||
surf *C.struct_wl_surface
|
surf *C.struct_wl_surface
|
||||||
wmSurf *C.struct_xdg_surface
|
wmSurf *C.struct_xdg_surface
|
||||||
topLvl *C.struct_xdg_toplevel
|
topLvl *C.struct_xdg_toplevel
|
||||||
@@ -194,12 +200,10 @@ type window struct {
|
|||||||
dir f32.Point
|
dir f32.Point
|
||||||
}
|
}
|
||||||
|
|
||||||
stage system.Stage
|
configured bool
|
||||||
dead bool
|
|
||||||
lastFrameCallback *C.struct_wl_callback
|
lastFrameCallback *C.struct_wl_callback
|
||||||
|
|
||||||
animating bool
|
animating bool
|
||||||
redraw bool
|
|
||||||
// The most recent configure serial waiting to be ack'ed.
|
// The most recent configure serial waiting to be ack'ed.
|
||||||
serial C.uint32_t
|
serial C.uint32_t
|
||||||
scale int
|
scale int
|
||||||
@@ -209,9 +213,11 @@ type window struct {
|
|||||||
wsize image.Point // window config size before going fullscreen or maximized
|
wsize image.Point // window config size before going fullscreen or maximized
|
||||||
inCompositor bool // window is moving or being resized
|
inCompositor bool // window is moving or being resized
|
||||||
|
|
||||||
clipReads chan clipboard.Event
|
clipReads chan transfer.DataEvent
|
||||||
|
|
||||||
wakeups chan struct{}
|
wakeups chan struct{}
|
||||||
|
|
||||||
|
closing bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type poller struct {
|
type poller struct {
|
||||||
@@ -260,25 +266,17 @@ func newWLWindow(callbacks *callbacks, options []Option) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
w.w = callbacks
|
w.w = callbacks
|
||||||
go func() {
|
w.w.SetDriver(w)
|
||||||
defer d.destroy()
|
|
||||||
defer w.destroy()
|
|
||||||
|
|
||||||
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.ProcessEvent(WaylandViewEvent{
|
||||||
w.Configure(options)
|
Display: unsafe.Pointer(w.display()),
|
||||||
C.wl_surface_commit(w.surf)
|
Surface: unsafe.Pointer(w.surf),
|
||||||
|
})
|
||||||
w.w.Event(WaylandViewEvent{
|
|
||||||
Display: unsafe.Pointer(w.display()),
|
|
||||||
Surface: unsafe.Pointer(w.surf),
|
|
||||||
})
|
|
||||||
|
|
||||||
err := w.loop()
|
|
||||||
w.w.Event(WaylandViewEvent{})
|
|
||||||
w.w.Event(system.DestroyEvent{Err: err})
|
|
||||||
}()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,7 +352,7 @@ func (d *wlDisplay) createNativeWindow(options []Option) (*window, error) {
|
|||||||
ppdp: ppdp,
|
ppdp: ppdp,
|
||||||
ppsp: ppdp,
|
ppsp: ppdp,
|
||||||
wakeups: make(chan struct{}, 1),
|
wakeups: make(chan struct{}, 1),
|
||||||
clipReads: make(chan clipboard.Event, 1),
|
clipReads: make(chan transfer.DataEvent, 1),
|
||||||
}
|
}
|
||||||
w.surf = C.wl_compositor_create_surface(d.compositor)
|
w.surf = C.wl_compositor_create_surface(d.compositor)
|
||||||
if w.surf == nil {
|
if w.surf == nil {
|
||||||
@@ -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) {
|
func gio_onXdgSurfaceConfigure(data unsafe.Pointer, wmSurf *C.struct_xdg_surface, serial C.uint32_t) {
|
||||||
w := callbackLoad(data).(*window)
|
w := callbackLoad(data).(*window)
|
||||||
w.serial = serial
|
w.serial = serial
|
||||||
w.redraw = true
|
|
||||||
C.xdg_surface_ack_configure(wmSurf, serial)
|
C.xdg_surface_ack_configure(wmSurf, serial)
|
||||||
w.setStage(system.StageRunning)
|
w.configured = true
|
||||||
|
w.draw(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onToplevelClose
|
//export gio_onToplevelClose
|
||||||
func gio_onToplevelClose(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel) {
|
func gio_onToplevelClose(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel) {
|
||||||
w := callbackLoad(data).(*window)
|
w := callbackLoad(data).(*window)
|
||||||
w.dead = true
|
w.closing = true
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onToplevelConfigure
|
//export gio_onToplevelConfigure
|
||||||
@@ -586,8 +584,8 @@ func gio_onToplevelDecorationConfigure(data unsafe.Pointer, deco *C.struct_zxdg_
|
|||||||
} else {
|
} else {
|
||||||
w.size.Y += int(w.config.decoHeight)
|
w.size.Y += int(w.config.decoHeight)
|
||||||
}
|
}
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
w.redraw = true
|
w.draw(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -645,7 +643,7 @@ func gio_onSurfaceEnter(data unsafe.Pointer, surf *C.struct_wl_surface, output *
|
|||||||
if w.config.Mode == Minimized {
|
if w.config.Mode == Minimized {
|
||||||
// Minimized window got brought back up: it is no longer so.
|
// Minimized window got brought back up: it is no longer so.
|
||||||
w.config.Mode = Windowed
|
w.config.Mode = Windowed
|
||||||
w.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),
|
X: fromFixed(x) * float32(w.scale),
|
||||||
Y: fromFixed(y) * float32(w.scale),
|
Y: fromFixed(y) * float32(w.scale),
|
||||||
}
|
}
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Press,
|
Kind: pointer.Press,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
Position: w.lastTouch,
|
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
|
s.serial = serial
|
||||||
w := s.touchFoci[id]
|
w := s.touchFoci[id]
|
||||||
delete(s.touchFoci, id)
|
delete(s.touchFoci, id)
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Release,
|
Kind: pointer.Release,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
Position: w.lastTouch,
|
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),
|
X: fromFixed(x) * float32(w.scale),
|
||||||
Y: fromFixed(y) * float32(w.scale),
|
Y: fromFixed(y) * float32(w.scale),
|
||||||
}
|
}
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Move,
|
Kind: pointer.Move,
|
||||||
Position: w.lastTouch,
|
Position: w.lastTouch,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
@@ -843,7 +841,7 @@ func gio_onTouchCancel(data unsafe.Pointer, touch *C.struct_wl_touch) {
|
|||||||
s := callbackLoad(data).(*wlSeat)
|
s := callbackLoad(data).(*wlSeat)
|
||||||
for id, w := range s.touchFoci {
|
for id, w := range s.touchFoci {
|
||||||
delete(s.touchFoci, id)
|
delete(s.touchFoci, id)
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Cancel,
|
Kind: pointer.Cancel,
|
||||||
Source: pointer.Touch,
|
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) {
|
func gio_onPointerEnter(data unsafe.Pointer, pointer *C.struct_wl_pointer, serial C.uint32_t, surf *C.struct_wl_surface, x, y C.wl_fixed_t) {
|
||||||
s := callbackLoad(data).(*wlSeat)
|
s := callbackLoad(data).(*wlSeat)
|
||||||
s.serial = serial
|
s.serial = serial
|
||||||
|
s.pointerSerial = serial
|
||||||
w := callbackLoad(unsafe.Pointer(surf)).(*window)
|
w := callbackLoad(unsafe.Pointer(surf)).(*window)
|
||||||
w.seat = s
|
|
||||||
s.pointerFocus = w
|
s.pointerFocus = w
|
||||||
w.setCursor(pointer, serial)
|
w.setCursor(pointer, serial)
|
||||||
w.lastPos = f32.Point{X: fromFixed(x), Y: fromFixed(y)}
|
w.lastPos = f32.Point{X: fromFixed(x), Y: fromFixed(y)}
|
||||||
@@ -864,12 +862,12 @@ func gio_onPointerEnter(data unsafe.Pointer, pointer *C.struct_wl_pointer, seria
|
|||||||
//export gio_onPointerLeave
|
//export gio_onPointerLeave
|
||||||
func gio_onPointerLeave(data unsafe.Pointer, p *C.struct_wl_pointer, serial C.uint32_t, surf *C.struct_wl_surface) {
|
func gio_onPointerLeave(data unsafe.Pointer, p *C.struct_wl_pointer, serial C.uint32_t, surf *C.struct_wl_surface) {
|
||||||
w := callbackLoad(unsafe.Pointer(surf)).(*window)
|
w := callbackLoad(unsafe.Pointer(surf)).(*window)
|
||||||
w.seat = nil
|
|
||||||
s := callbackLoad(data).(*wlSeat)
|
s := callbackLoad(data).(*wlSeat)
|
||||||
s.serial = serial
|
s.serial = serial
|
||||||
|
s.pointerFocus = nil
|
||||||
if w.inCompositor {
|
if w.inCompositor {
|
||||||
w.inCompositor = false
|
w.inCompositor = false
|
||||||
w.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 := callbackLoad(data).(*wlSeat)
|
||||||
s.serial = serial
|
s.serial = serial
|
||||||
w := s.pointerFocus
|
w := s.pointerFocus
|
||||||
// From linux-event-codes.h.
|
// From Linux: include/uapi/linux/input-event-codes.h
|
||||||
const (
|
const (
|
||||||
BTN_LEFT = 0x110
|
BTN_LEFT = 0x110
|
||||||
BTN_RIGHT = 0x111
|
BTN_RIGHT = 0x111
|
||||||
BTN_MIDDLE = 0x112
|
BTN_MIDDLE = 0x112
|
||||||
|
BTN_SIDE = 0x113
|
||||||
|
BTN_EXTRA = 0x114
|
||||||
)
|
)
|
||||||
var btn pointer.Buttons
|
var btn pointer.Buttons
|
||||||
switch wbtn {
|
switch wbtn {
|
||||||
@@ -900,6 +900,10 @@ func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t,
|
|||||||
btn = pointer.ButtonSecondary
|
btn = pointer.ButtonSecondary
|
||||||
case BTN_MIDDLE:
|
case BTN_MIDDLE:
|
||||||
btn = pointer.ButtonTertiary
|
btn = pointer.ButtonTertiary
|
||||||
|
case BTN_SIDE:
|
||||||
|
btn = pointer.ButtonQuaternary
|
||||||
|
case BTN_EXTRA:
|
||||||
|
btn = pointer.ButtonQuinary
|
||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -930,7 +934,7 @@ func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t,
|
|||||||
}
|
}
|
||||||
w.flushScroll()
|
w.flushScroll()
|
||||||
w.resetFling()
|
w.resetFling()
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Buttons: w.pointerBtns,
|
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) {
|
func gio_onPointerFrame(data unsafe.Pointer, p *C.struct_wl_pointer) {
|
||||||
s := callbackLoad(data).(*wlSeat)
|
s := callbackLoad(data).(*wlSeat)
|
||||||
w := s.pointerFocus
|
w := s.pointerFocus
|
||||||
|
if w == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
w.flushScroll()
|
w.flushScroll()
|
||||||
w.flushFling()
|
w.flushFling()
|
||||||
}
|
}
|
||||||
@@ -1018,23 +1025,34 @@ func gio_onPointerAxisDiscrete(data unsafe.Pointer, p *C.struct_wl_pointer, axis
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) ReadClipboard() {
|
func (w *window) ReadClipboard() {
|
||||||
|
if w.disp.readClipClose != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.disp.readClipClose = make(chan struct{})
|
||||||
r, err := w.disp.readClipboard()
|
r, err := w.disp.readClipboard()
|
||||||
// Send empty responses on unavailable clipboards or errors.
|
|
||||||
if r == nil || err != nil {
|
if r == nil || err != nil {
|
||||||
w.w.Event(clipboard.Event{})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Don't let slow clipboard transfers block event loop.
|
// Don't let slow clipboard transfers block event loop.
|
||||||
go func() {
|
go func() {
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
data, _ := io.ReadAll(r)
|
data, _ := io.ReadAll(r)
|
||||||
w.clipReads <- clipboard.Event{Text: string(data)}
|
e := transfer.DataEvent{
|
||||||
w.Wakeup()
|
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) {
|
func (w *window) WriteClipboard(mime string, s []byte) {
|
||||||
w.disp.writeClipboard([]byte(s))
|
w.disp.writeClipboard(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Configure(options []Option) {
|
func (w *window) Configure(options []Option) {
|
||||||
@@ -1092,8 +1110,7 @@ func (w *window) Configure(options []Option) {
|
|||||||
w.config.MaxSize = cnf.MaxSize
|
w.config.MaxSize = cnf.MaxSize
|
||||||
w.setWindowConstraints()
|
w.setWindowConstraints()
|
||||||
}
|
}
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
w.redraw = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) setWindowConstraints() {
|
func (w *window) setWindowConstraints() {
|
||||||
@@ -1130,22 +1147,23 @@ func (w *window) Perform(actions system.Action) {
|
|||||||
walkActions(actions, func(action system.Action) {
|
walkActions(actions, func(action system.Action) {
|
||||||
switch action {
|
switch action {
|
||||||
case system.ActionClose:
|
case system.ActionClose:
|
||||||
w.dead = true
|
w.closing = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) move(serial C.uint32_t) {
|
func (w *window) move(serial C.uint32_t) {
|
||||||
s := w.seat
|
s := w.disp.seat
|
||||||
if !w.inCompositor && s != nil {
|
if w.inCompositor || s.pointerFocus != w {
|
||||||
w.inCompositor = true
|
return
|
||||||
C.xdg_toplevel_move(w.topLvl, s.seat, serial)
|
|
||||||
}
|
}
|
||||||
|
w.inCompositor = true
|
||||||
|
C.xdg_toplevel_move(w.topLvl, s.seat, serial)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) resize(serial, edge C.uint32_t) {
|
func (w *window) resize(serial, edge C.uint32_t) {
|
||||||
s := w.seat
|
s := w.disp.seat
|
||||||
if w.inCompositor || s == nil {
|
if w.inCompositor || s.pointerFocus != w {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.inCompositor = true
|
w.inCompositor = true
|
||||||
@@ -1158,11 +1176,12 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) updateCursor() {
|
func (w *window) updateCursor() {
|
||||||
ptr := w.disp.seat.pointer
|
s := w.disp.seat
|
||||||
if ptr == nil {
|
ptr := s.pointer
|
||||||
|
if ptr == nil || s.pointerFocus != w {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.setCursor(ptr, w.serial)
|
w.setCursor(ptr, s.pointerSerial)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) setCursor(pointer *C.struct_wl_pointer, serial C.uint32_t) {
|
func (w *window) setCursor(pointer *C.struct_wl_pointer, serial C.uint32_t) {
|
||||||
@@ -1171,7 +1190,7 @@ func (w *window) setCursor(pointer *C.struct_wl_pointer, serial C.uint32_t) {
|
|||||||
c = w.cursor.cursor
|
c = w.cursor.cursor
|
||||||
}
|
}
|
||||||
if c == nil {
|
if c == nil {
|
||||||
C.wl_pointer_set_cursor(pointer, w.serial, nil, 0, 0)
|
C.wl_pointer_set_cursor(pointer, serial, nil, 0, 0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Get images[0].
|
// 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)
|
w := callbackLoad(unsafe.Pointer(surf)).(*window)
|
||||||
s.keyboardFocus = w
|
s.keyboardFocus = w
|
||||||
s.disp.repeat.Stop(0)
|
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
|
//export gio_onKeyboardLeave
|
||||||
@@ -1222,7 +1242,8 @@ func gio_onKeyboardLeave(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, se
|
|||||||
s.serial = serial
|
s.serial = serial
|
||||||
s.disp.repeat.Stop(0)
|
s.disp.repeat.Stop(0)
|
||||||
w := s.keyboardFocus
|
w := s.keyboardFocus
|
||||||
w.w.Event(key.FocusEvent{Focus: false})
|
w.config.Focused = false
|
||||||
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onKeyboardKey
|
//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.
|
// There's no support for IME yet.
|
||||||
w.w.EditorInsert(ee.Text)
|
w.w.EditorInsert(ee.Text)
|
||||||
} else {
|
} else {
|
||||||
w.w.Event(e)
|
w.ProcessEvent(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if state != C.WL_KEYBOARD_KEY_STATE_PRESSED {
|
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.now = 0
|
||||||
r.stopC = stopC
|
r.stopC = stopC
|
||||||
r.key = keyCode
|
r.key = keyCode
|
||||||
r.win = w.w
|
r.win = w
|
||||||
rate, delay := r.rate, r.delay
|
rate, delay := r.rate, r.delay
|
||||||
go func() {
|
go func() {
|
||||||
timer := time.NewTimer(delay)
|
timer := time.NewTimer(delay)
|
||||||
@@ -1333,9 +1354,9 @@ func (r *repeatState) Repeat(d *wlDisplay) {
|
|||||||
for _, e := range d.xkb.DispatchKey(r.key, key.Press) {
|
for _, e := range d.xkb.DispatchKey(r.key, key.Press) {
|
||||||
if ee, ok := e.(key.EditEvent); ok {
|
if ee, ok := e.(key.EditEvent); ok {
|
||||||
// There's no support for IME yet.
|
// There's no support for IME yet.
|
||||||
r.win.EditorInsert(ee.Text)
|
r.win.w.EditorInsert(ee.Text)
|
||||||
} else {
|
} else {
|
||||||
r.win.Event(e)
|
r.win.ProcessEvent(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
r.last += delay
|
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)
|
w := callbackLoad(data).(*window)
|
||||||
if w.lastFrameCallback == callback {
|
if w.lastFrameCallback == callback {
|
||||||
w.lastFrameCallback = nil
|
w.lastFrameCallback = nil
|
||||||
|
w.draw(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) loop() error {
|
func (w *window) close(err error) {
|
||||||
var p poller
|
w.ProcessEvent(WaylandViewEvent{})
|
||||||
for {
|
w.ProcessEvent(DestroyEvent{Err: err})
|
||||||
if err := w.disp.dispatch(&p); err != nil {
|
w.destroy()
|
||||||
return err
|
w.disp.destroy()
|
||||||
}
|
w.disp = nil
|
||||||
select {
|
}
|
||||||
case e := <-w.clipReads:
|
|
||||||
w.w.Event(e)
|
func (w *window) dispatch() {
|
||||||
case <-w.wakeups:
|
if w.disp == nil {
|
||||||
w.w.Event(wakeupEvent{})
|
<-w.wakeups
|
||||||
default:
|
w.w.Invalidate()
|
||||||
}
|
return
|
||||||
if w.dead {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
w.draw()
|
|
||||||
}
|
}
|
||||||
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
|
// 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)
|
dispfd := C.wl_display_get_fd(d.disp)
|
||||||
// Poll for events and notifications.
|
// 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(dispfd), Events: syscall.POLLIN | syscall.POLLERR},
|
||||||
syscall.PollFd{Fd: int32(d.notify.read), Events: syscall.POLLIN | syscall.POLLERR},
|
syscall.PollFd{Fd: int32(d.notify.read), Events: syscall.POLLIN | syscall.POLLERR},
|
||||||
)
|
)
|
||||||
|
for C.wl_display_prepare_read(d.disp) != 0 {
|
||||||
|
C.wl_display_dispatch_pending(d.disp)
|
||||||
|
}
|
||||||
dispFd := &pollfds[0]
|
dispFd := &pollfds[0]
|
||||||
if ret, err := C.wl_display_flush(d.disp); ret < 0 {
|
if ret, err := C.wl_display_flush(d.disp); ret < 0 {
|
||||||
if err != syscall.EAGAIN {
|
if err != syscall.EAGAIN {
|
||||||
@@ -1402,11 +1471,25 @@ func (d *wlDisplay) dispatch(p *poller) error {
|
|||||||
dispFd.Events |= syscall.POLLOUT
|
dispFd.Events |= syscall.POLLOUT
|
||||||
}
|
}
|
||||||
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
||||||
|
C.wl_display_cancel_read(d.disp)
|
||||||
return fmt.Errorf("wayland: poll failed: %v", err)
|
return fmt.Errorf("wayland: poll failed: %v", err)
|
||||||
}
|
}
|
||||||
|
if dispFd.Revents&(syscall.POLLERR|syscall.POLLHUP) != 0 {
|
||||||
|
C.wl_display_cancel_read(d.disp)
|
||||||
|
return errors.New("wayland: display file descriptor gone")
|
||||||
|
}
|
||||||
|
// Handle events.
|
||||||
|
if dispFd.Revents&syscall.POLLIN != 0 {
|
||||||
|
if ret, err := C.wl_display_read_events(d.disp); ret < 0 {
|
||||||
|
return fmt.Errorf("wayland: wl_display_read_events failed: %v", err)
|
||||||
|
}
|
||||||
|
C.wl_display_dispatch_pending(d.disp)
|
||||||
|
} else {
|
||||||
|
C.wl_display_cancel_read(d.disp)
|
||||||
|
}
|
||||||
// Clear notifications.
|
// Clear notifications.
|
||||||
for {
|
for {
|
||||||
_, err := syscall.Read(d.notify.read, p.buf[:])
|
_, err := syscall.Read(d.notify.read, d.poller.buf[:])
|
||||||
if err == syscall.EAGAIN {
|
if err == syscall.EAGAIN {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -1414,29 +1497,15 @@ func (d *wlDisplay) dispatch(p *poller) error {
|
|||||||
return fmt.Errorf("wayland: read from notify pipe failed: %v", err)
|
return fmt.Errorf("wayland: read from notify pipe failed: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Handle events
|
|
||||||
switch {
|
|
||||||
case dispFd.Revents&syscall.POLLIN != 0:
|
|
||||||
if ret, err := C.wl_display_dispatch(d.disp); ret < 0 {
|
|
||||||
return fmt.Errorf("wayland: wl_display_dispatch failed: %v", err)
|
|
||||||
}
|
|
||||||
case dispFd.Revents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
|
||||||
return errors.New("wayland: display file descriptor gone")
|
|
||||||
}
|
|
||||||
d.repeat.Repeat(d)
|
d.repeat.Repeat(d)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Wakeup() {
|
|
||||||
select {
|
|
||||||
case w.wakeups <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
w.disp.wakeup()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) SetAnimating(anim bool) {
|
func (w *window) SetAnimating(anim bool) {
|
||||||
w.animating = anim
|
w.animating = anim
|
||||||
|
if anim {
|
||||||
|
w.draw(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wakeup wakes up the event loop through the notification pipe.
|
// Wakeup wakes up the event loop through the notification pipe.
|
||||||
@@ -1448,6 +1517,10 @@ func (d *wlDisplay) wakeup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) destroy() {
|
func (w *window) destroy() {
|
||||||
|
if w.lastFrameCallback != nil {
|
||||||
|
C.wl_callback_destroy(w.lastFrameCallback)
|
||||||
|
w.lastFrameCallback = nil
|
||||||
|
}
|
||||||
if w.cursor.surf != nil {
|
if w.cursor.surf != nil {
|
||||||
C.wl_surface_destroy(w.cursor.surf)
|
C.wl_surface_destroy(w.cursor.surf)
|
||||||
}
|
}
|
||||||
@@ -1572,7 +1645,15 @@ func (w *window) flushScroll() {
|
|||||||
if total == (f32.Point{}) {
|
if total == (f32.Point{}) {
|
||||||
return
|
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,
|
Kind: pointer.Scroll,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Buttons: w.pointerBtns,
|
Buttons: w.pointerBtns,
|
||||||
@@ -1581,12 +1662,6 @@ func (w *window) flushScroll() {
|
|||||||
Time: w.scroll.time,
|
Time: w.scroll.time,
|
||||||
Modifiers: w.disp.xkb.Modifiers(),
|
Modifiers: w.disp.xkb.Modifiers(),
|
||||||
})
|
})
|
||||||
if w.scroll.steps == (image.Point{}) {
|
|
||||||
w.fling.xExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.X)
|
|
||||||
w.fling.yExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.Y)
|
|
||||||
}
|
|
||||||
w.scroll.dist = f32.Point{}
|
|
||||||
w.scroll.steps = image.Point{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) onPointerMotion(x, y C.wl_fixed_t, t C.uint32_t) {
|
func (w *window) onPointerMotion(x, y C.wl_fixed_t, t C.uint32_t) {
|
||||||
@@ -1595,7 +1670,7 @@ func (w *window) onPointerMotion(x, y C.wl_fixed_t, t C.uint32_t) {
|
|||||||
X: fromFixed(x) * float32(w.scale),
|
X: fromFixed(x) * float32(w.scale),
|
||||||
Y: fromFixed(y) * float32(w.scale),
|
Y: fromFixed(y) * float32(w.scale),
|
||||||
}
|
}
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Move,
|
Kind: pointer.Move,
|
||||||
Position: w.lastPos,
|
Position: w.lastPos,
|
||||||
Buttons: w.pointerBtns,
|
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 {
|
if w.config.Mode != Windowed || w.config.Decorated {
|
||||||
return nil, 0
|
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
|
x, y, size := int(w.lastPos.X), int(w.lastPos.Y), w.config.Size
|
||||||
north := y <= border
|
north := y <= border
|
||||||
south := y >= size.Y-border
|
south := y >= size.Y-border
|
||||||
@@ -1670,13 +1746,10 @@ func (w *window) updateOutputs() {
|
|||||||
if found && scale != w.scale {
|
if found && scale != w.scale {
|
||||||
w.scale = scale
|
w.scale = scale
|
||||||
C.wl_surface_set_buffer_scale(w.surf, C.int32_t(w.scale))
|
C.wl_surface_set_buffer_scale(w.surf, C.int32_t(w.scale))
|
||||||
w.redraw = true
|
w.draw(true)
|
||||||
}
|
}
|
||||||
if !found {
|
if found {
|
||||||
w.setStage(system.StagePaused)
|
w.draw(true)
|
||||||
} else {
|
|
||||||
w.setStage(system.StageRunning)
|
|
||||||
w.redraw = 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()
|
w.flushScroll()
|
||||||
size, cfg := w.getConfig()
|
size, cfg := w.getConfig()
|
||||||
if cfg == (unit.Metric{}) {
|
if cfg == (unit.Metric{}) {
|
||||||
@@ -1696,11 +1772,9 @@ func (w *window) draw() {
|
|||||||
}
|
}
|
||||||
if size != w.config.Size {
|
if size != w.config.Size {
|
||||||
w.config.Size = 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()
|
anim := w.animating || w.fling.anim.Active()
|
||||||
sync := w.redraw
|
|
||||||
w.redraw = false
|
|
||||||
// Draw animation only when not waiting for frame callback.
|
// Draw animation only when not waiting for frame callback.
|
||||||
redrawAnim := anim && w.lastFrameCallback == nil
|
redrawAnim := anim && w.lastFrameCallback == nil
|
||||||
if !redrawAnim && !sync {
|
if !redrawAnim && !sync {
|
||||||
@@ -1711,8 +1785,8 @@ func (w *window) draw() {
|
|||||||
// Use the surface as listener data for gio_onFrameDone.
|
// Use the surface as listener data for gio_onFrameDone.
|
||||||
C.wl_callback_add_listener(w.lastFrameCallback, &C.gio_callback_listener, unsafe.Pointer(w.surf))
|
C.wl_callback_add_listener(w.lastFrameCallback, &C.gio_callback_listener, unsafe.Pointer(w.surf))
|
||||||
}
|
}
|
||||||
w.w.Event(frameEvent{
|
w.ProcessEvent(frameEvent{
|
||||||
FrameEvent: system.FrameEvent{
|
FrameEvent: FrameEvent{
|
||||||
Now: time.Now(),
|
Now: time.Now(),
|
||||||
Size: w.config.Size,
|
Size: w.config.Size,
|
||||||
Metric: cfg,
|
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 {
|
func (w *window) display() *C.struct_wl_display {
|
||||||
return w.disp.disp
|
return w.disp.disp
|
||||||
}
|
}
|
||||||
@@ -1820,6 +1886,10 @@ func newWLDisplay() (*wlDisplay, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *wlDisplay) destroy() {
|
func (d *wlDisplay) destroy() {
|
||||||
|
if d.readClipClose != nil {
|
||||||
|
close(d.readClipClose)
|
||||||
|
d.readClipClose = nil
|
||||||
|
}
|
||||||
if d.notify.write != 0 {
|
if d.notify.write != 0 {
|
||||||
syscall.Close(d.notify.write)
|
syscall.Close(d.notify.write)
|
||||||
d.notify.write = 0
|
d.notify.write = 0
|
||||||
@@ -1861,6 +1931,7 @@ func (d *wlDisplay) destroy() {
|
|||||||
if d.disp != nil {
|
if d.disp != nil {
|
||||||
C.wl_display_disconnect(d.disp)
|
C.wl_display_disconnect(d.disp)
|
||||||
callbackDelete(unsafe.Pointer(d.disp))
|
callbackDelete(unsafe.Pointer(d.disp))
|
||||||
|
d.disp = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+249
-165
@@ -6,6 +6,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
|
"io"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -18,40 +19,39 @@ import (
|
|||||||
syscall "golang.org/x/sys/windows"
|
syscall "golang.org/x/sys/windows"
|
||||||
|
|
||||||
"gioui.org/app/internal/windows"
|
"gioui.org/app/internal/windows"
|
||||||
|
"gioui.org/op"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
gowindows "golang.org/x/sys/windows"
|
gowindows "golang.org/x/sys/windows"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/io/clipboard"
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
|
"gioui.org/io/transfer"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ViewEvent struct {
|
type Win32ViewEvent struct {
|
||||||
HWND uintptr
|
HWND uintptr
|
||||||
}
|
}
|
||||||
|
|
||||||
type window struct {
|
type window struct {
|
||||||
hwnd syscall.Handle
|
hwnd syscall.Handle
|
||||||
hdc syscall.Handle
|
hdc syscall.Handle
|
||||||
w *callbacks
|
w *callbacks
|
||||||
stage system.Stage
|
|
||||||
pointerBtns pointer.Buttons
|
|
||||||
|
|
||||||
// cursorIn tracks whether the cursor was inside the window according
|
// cursorIn tracks whether the cursor was inside the window according
|
||||||
// to the most recent WM_SETCURSOR.
|
// to the most recent WM_SETCURSOR.
|
||||||
cursorIn bool
|
cursorIn bool
|
||||||
cursor syscall.Handle
|
cursor syscall.Handle
|
||||||
|
|
||||||
// placement saves the previous window position when in full screen mode.
|
|
||||||
placement *windows.WindowPlacement
|
|
||||||
|
|
||||||
animating bool
|
animating bool
|
||||||
focused bool
|
|
||||||
|
|
||||||
borderSize image.Point
|
borderSize image.Point
|
||||||
config Config
|
config Config
|
||||||
|
// frameDims stores the last seen window frame width and height.
|
||||||
|
frameDims image.Point
|
||||||
|
loop *eventLoop
|
||||||
}
|
}
|
||||||
|
|
||||||
const _WM_WAKEUP = windows.WM_USER + iota
|
const _WM_WAKEUP = windows.WM_USER + iota
|
||||||
@@ -84,36 +84,38 @@ func osMain() {
|
|||||||
select {}
|
select {}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWindow(window *callbacks, options []Option) error {
|
func newWindow(win *callbacks, options []Option) {
|
||||||
cerr := make(chan error)
|
done := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
// GetMessage and PeekMessage can filter on a window HWND, but
|
// GetMessage and PeekMessage can filter on a window HWND, but
|
||||||
// then thread-specific messages such as WM_QUIT are ignored.
|
// then thread-specific messages such as WM_QUIT are ignored.
|
||||||
// Instead lock the thread so window messages arrive through
|
// Instead lock the thread so window messages arrive through
|
||||||
// unfiltered GetMessage calls.
|
// unfiltered GetMessage calls.
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
w, err := createNativeWindow()
|
|
||||||
|
w := &window{
|
||||||
|
w: win,
|
||||||
|
}
|
||||||
|
w.loop = newEventLoop(w.w, w.wakeup)
|
||||||
|
w.w.SetDriver(w)
|
||||||
|
err := w.init()
|
||||||
|
done <- struct{}{}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cerr <- err
|
w.ProcessEvent(DestroyEvent{Err: err})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cerr <- nil
|
|
||||||
winMap.Store(w.hwnd, w)
|
winMap.Store(w.hwnd, w)
|
||||||
defer winMap.Delete(w.hwnd)
|
defer winMap.Delete(w.hwnd)
|
||||||
w.w = window
|
|
||||||
w.w.SetDriver(w)
|
|
||||||
w.w.Event(ViewEvent{HWND: uintptr(w.hwnd)})
|
|
||||||
w.Configure(options)
|
w.Configure(options)
|
||||||
|
w.ProcessEvent(Win32ViewEvent{HWND: uintptr(w.hwnd)})
|
||||||
windows.SetForegroundWindow(w.hwnd)
|
windows.SetForegroundWindow(w.hwnd)
|
||||||
windows.SetFocus(w.hwnd)
|
windows.SetFocus(w.hwnd)
|
||||||
// Since the window class for the cursor is null,
|
// Since the window class for the cursor is null,
|
||||||
// set it here to show the cursor.
|
// set it here to show the cursor.
|
||||||
w.SetCursor(pointer.CursorDefault)
|
w.SetCursor(pointer.CursorDefault)
|
||||||
if err := w.loop(); err != nil {
|
w.runLoop()
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
return <-cerr
|
<-done
|
||||||
}
|
}
|
||||||
|
|
||||||
// initResources initializes the resources global.
|
// initResources initializes the resources global.
|
||||||
@@ -148,13 +150,13 @@ func initResources() error {
|
|||||||
|
|
||||||
const dwExStyle = windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE
|
const dwExStyle = windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE
|
||||||
|
|
||||||
func createNativeWindow() (*window, error) {
|
func (w *window) init() error {
|
||||||
var resErr error
|
var resErr error
|
||||||
resources.once.Do(func() {
|
resources.once.Do(func() {
|
||||||
resErr = initResources()
|
resErr = initResources()
|
||||||
})
|
})
|
||||||
if resErr != nil {
|
if resErr != nil {
|
||||||
return nil, resErr
|
return resErr
|
||||||
}
|
}
|
||||||
const dwStyle = windows.WS_OVERLAPPEDWINDOW
|
const dwStyle = windows.WS_OVERLAPPEDWINDOW
|
||||||
|
|
||||||
@@ -170,33 +172,56 @@ func createNativeWindow() (*window, error) {
|
|||||||
resources.handle,
|
resources.handle,
|
||||||
0)
|
0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
w := &window{
|
if err := windows.RegisterTouchWindow(hwnd, 0); err != nil {
|
||||||
hwnd: hwnd,
|
return err
|
||||||
|
}
|
||||||
|
if err := windows.EnableMouseInPointer(1); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
w.hdc, err = windows.GetDC(hwnd)
|
w.hdc, err = windows.GetDC(hwnd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
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.
|
// It reads the window style and size/position and updates w.config.
|
||||||
// If anything has changed it emits a ConfigEvent to notify the application.
|
// If anything has changed it emits a ConfigEvent to notify the application.
|
||||||
func (w *window) update() {
|
func (w *window) update() {
|
||||||
cr := windows.GetClientRect(w.hwnd)
|
p := windows.GetWindowPlacement(w.hwnd)
|
||||||
w.config.Size = image.Point{
|
if !p.IsMinimized() {
|
||||||
X: int(cr.Right - cr.Left),
|
r := windows.GetWindowRect(w.hwnd)
|
||||||
Y: int(cr.Bottom - cr.Top),
|
cr := windows.GetClientRect(w.hwnd)
|
||||||
|
w.config.Size = image.Point{
|
||||||
|
X: int(cr.Right - cr.Left),
|
||||||
|
Y: int(cr.Bottom - cr.Top),
|
||||||
|
}
|
||||||
|
w.frameDims = image.Point{
|
||||||
|
X: int(r.Right - r.Left),
|
||||||
|
Y: int(r.Bottom - r.Top),
|
||||||
|
}.Sub(w.config.Size)
|
||||||
}
|
}
|
||||||
|
|
||||||
w.borderSize = image.Pt(
|
w.borderSize = image.Pt(
|
||||||
windows.GetSystemMetrics(windows.SM_CXSIZEFRAME),
|
windows.GetSystemMetrics(windows.SM_CXSIZEFRAME),
|
||||||
windows.GetSystemMetrics(windows.SM_CYSIZEFRAME),
|
windows.GetSystemMetrics(windows.SM_CYSIZEFRAME),
|
||||||
)
|
)
|
||||||
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 {
|
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
|
e.State = key.Release
|
||||||
}
|
}
|
||||||
|
|
||||||
w.w.Event(e)
|
w.ProcessEvent(e)
|
||||||
|
|
||||||
if (wParam == windows.VK_F10) && (msg == windows.WM_SYSKEYDOWN || msg == windows.WM_SYSKEYUP) {
|
if (wParam == windows.VK_F10) && (msg == windows.WM_SYSKEYDOWN || msg == windows.WM_SYSKEYUP) {
|
||||||
// Reserve F10 for ourselves, and don't let it open the system menu. Other Windows programs
|
// Reserve F10 for ourselves, and don't let it open the system menu. Other Windows programs
|
||||||
@@ -245,36 +270,42 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case windows.WM_LBUTTONDOWN:
|
case windows.WM_POINTERDOWN, windows.WM_POINTERUP, windows.WM_POINTERUPDATE, windows.WM_POINTERCAPTURECHANGED:
|
||||||
w.pointerButton(pointer.ButtonPrimary, true, lParam, getModifiers())
|
pid := getPointerIDwParam(wParam)
|
||||||
case windows.WM_LBUTTONUP:
|
pi, err := windows.GetPointerInfo(uint32(pid))
|
||||||
w.pointerButton(pointer.ButtonPrimary, false, lParam, getModifiers())
|
if err != nil {
|
||||||
case windows.WM_RBUTTONDOWN:
|
panic(err)
|
||||||
w.pointerButton(pointer.ButtonSecondary, true, lParam, getModifiers())
|
}
|
||||||
case windows.WM_RBUTTONUP:
|
switch msg {
|
||||||
w.pointerButton(pointer.ButtonSecondary, false, lParam, getModifiers())
|
case windows.WM_POINTERDOWN:
|
||||||
case windows.WM_MBUTTONDOWN:
|
windows.SetCapture(w.hwnd)
|
||||||
w.pointerButton(pointer.ButtonTertiary, true, lParam, getModifiers())
|
case windows.WM_POINTERUP:
|
||||||
case windows.WM_MBUTTONUP:
|
windows.ReleaseCapture()
|
||||||
w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers())
|
}
|
||||||
|
|
||||||
|
kind := pointer.Move
|
||||||
|
switch pi.ButtonChangeType {
|
||||||
|
case windows.POINTER_CHANGE_FIRSTBUTTON_DOWN, windows.POINTER_CHANGE_SECONDBUTTON_DOWN, windows.POINTER_CHANGE_THIRDBUTTON_DOWN, windows.POINTER_CHANGE_FOURTHBUTTON_DOWN, windows.POINTER_CHANGE_FIFTHBUTTON_DOWN:
|
||||||
|
kind = pointer.Press
|
||||||
|
case windows.POINTER_CHANGE_FIRSTBUTTON_UP, windows.POINTER_CHANGE_SECONDBUTTON_UP, windows.POINTER_CHANGE_THIRDBUTTON_UP, windows.POINTER_CHANGE_FOURTHBUTTON_UP, windows.POINTER_CHANGE_FIFTHBUTTON_UP:
|
||||||
|
kind = pointer.Release
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pi.PointerFlags&windows.POINTER_FLAG_CANCELED != 0) || (msg == windows.WM_POINTERCAPTURECHANGED) {
|
||||||
|
kind = pointer.Cancel
|
||||||
|
}
|
||||||
|
|
||||||
|
w.pointerUpdate(pi, pid, kind, lParam)
|
||||||
case windows.WM_CANCELMODE:
|
case windows.WM_CANCELMODE:
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Cancel,
|
Kind: pointer.Cancel,
|
||||||
})
|
})
|
||||||
case windows.WM_SETFOCUS:
|
case windows.WM_SETFOCUS:
|
||||||
w.focused = true
|
w.config.Focused = true
|
||||||
w.w.Event(key.FocusEvent{Focus: true})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
case windows.WM_KILLFOCUS:
|
case windows.WM_KILLFOCUS:
|
||||||
w.focused = false
|
w.config.Focused = false
|
||||||
w.w.Event(key.FocusEvent{Focus: false})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
case windows.WM_NCACTIVATE:
|
|
||||||
if w.stage >= system.StageInactive {
|
|
||||||
if wParam == windows.TRUE {
|
|
||||||
w.setStage(system.StageRunning)
|
|
||||||
} else {
|
|
||||||
w.setStage(system.StageInactive)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case windows.WM_NCHITTEST:
|
case windows.WM_NCHITTEST:
|
||||||
if w.config.Decorated {
|
if w.config.Decorated {
|
||||||
// Let the system handle it.
|
// Let the system handle it.
|
||||||
@@ -284,24 +315,14 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
np := windows.Point{X: int32(x), Y: int32(y)}
|
np := windows.Point{X: int32(x), Y: int32(y)}
|
||||||
windows.ScreenToClient(w.hwnd, &np)
|
windows.ScreenToClient(w.hwnd, &np)
|
||||||
return w.hitTest(int(np.X), int(np.Y))
|
return w.hitTest(int(np.X), int(np.Y))
|
||||||
case windows.WM_MOUSEMOVE:
|
case windows.WM_POINTERWHEEL:
|
||||||
x, y := coordsFromlParam(lParam)
|
|
||||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
|
||||||
w.w.Event(pointer.Event{
|
|
||||||
Kind: pointer.Move,
|
|
||||||
Source: pointer.Mouse,
|
|
||||||
Position: p,
|
|
||||||
Buttons: w.pointerBtns,
|
|
||||||
Time: windows.GetMessageTime(),
|
|
||||||
Modifiers: getModifiers(),
|
|
||||||
})
|
|
||||||
case windows.WM_MOUSEWHEEL:
|
|
||||||
w.scrollEvent(wParam, lParam, false, getModifiers())
|
w.scrollEvent(wParam, lParam, false, getModifiers())
|
||||||
case windows.WM_MOUSEHWHEEL:
|
case windows.WM_POINTERHWHEEL:
|
||||||
w.scrollEvent(wParam, lParam, true, getModifiers())
|
w.scrollEvent(wParam, lParam, true, getModifiers())
|
||||||
case windows.WM_DESTROY:
|
case windows.WM_DESTROY:
|
||||||
w.w.Event(ViewEvent{})
|
w.ProcessEvent(Win32ViewEvent{})
|
||||||
w.w.Event(system.DestroyEvent{})
|
w.ProcessEvent(DestroyEvent{})
|
||||||
|
w.w = nil
|
||||||
if w.hdc != 0 {
|
if w.hdc != 0 {
|
||||||
windows.ReleaseDC(w.hdc)
|
windows.ReleaseDC(w.hdc)
|
||||||
w.hdc = 0
|
w.hdc = 0
|
||||||
@@ -309,6 +330,7 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
// The system destroys the HWND for us.
|
// The system destroys the HWND for us.
|
||||||
w.hwnd = 0
|
w.hwnd = 0
|
||||||
windows.PostQuitMessage(0)
|
windows.PostQuitMessage(0)
|
||||||
|
return 0
|
||||||
case windows.WM_NCCALCSIZE:
|
case windows.WM_NCCALCSIZE:
|
||||||
if w.config.Decorated {
|
if w.config.Decorated {
|
||||||
// Let Windows handle decorations.
|
// Let Windows handle decorations.
|
||||||
@@ -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
|
// Adjust window position to avoid the extra padding in maximized
|
||||||
// state. See https://devblogs.microsoft.com/oldnewthing/20150304-00/?p=44543.
|
// state. See https://devblogs.microsoft.com/oldnewthing/20150304-00/?p=44543.
|
||||||
// Note that trying to do the adjustment in WM_GETMINMAXINFO is ignored by Windows.
|
// Note that trying to do the adjustment in WM_GETMINMAXINFO is ignored by Windows.
|
||||||
szp := (*windows.NCCalcSizeParams)(unsafe.Pointer(uintptr(lParam)))
|
szp := (*windows.NCCalcSizeParams)(unsafe.Pointer(lParam))
|
||||||
mi := windows.GetMonitorInfo(w.hwnd)
|
mi := windows.GetMonitorInfo(w.hwnd)
|
||||||
szp.Rgrc[0] = mi.WorkArea
|
szp.Rgrc[0] = mi.WorkArea
|
||||||
return 0
|
return 0
|
||||||
case windows.WM_PAINT:
|
case windows.WM_PAINT:
|
||||||
w.draw(true)
|
w.draw(true)
|
||||||
|
case windows.WM_STYLECHANGED:
|
||||||
|
w.update()
|
||||||
|
case windows.WM_WINDOWPOSCHANGED:
|
||||||
|
w.update()
|
||||||
case windows.WM_SIZE:
|
case windows.WM_SIZE:
|
||||||
w.update()
|
w.update()
|
||||||
switch wParam {
|
|
||||||
case windows.SIZE_MINIMIZED:
|
|
||||||
w.config.Mode = Minimized
|
|
||||||
w.setStage(system.StagePaused)
|
|
||||||
case windows.SIZE_MAXIMIZED:
|
|
||||||
w.config.Mode = Maximized
|
|
||||||
w.setStage(system.StageRunning)
|
|
||||||
case windows.SIZE_RESTORED:
|
|
||||||
if w.config.Mode != Fullscreen {
|
|
||||||
w.config.Mode = Windowed
|
|
||||||
}
|
|
||||||
w.setStage(system.StageRunning)
|
|
||||||
}
|
|
||||||
case windows.WM_GETMINMAXINFO:
|
case windows.WM_GETMINMAXINFO:
|
||||||
mm := (*windows.MinMaxInfo)(unsafe.Pointer(uintptr(lParam)))
|
mm := (*windows.MinMaxInfo)(unsafe.Pointer(lParam))
|
||||||
var bw, bh int32
|
|
||||||
|
var frameDims image.Point
|
||||||
if w.config.Decorated {
|
if w.config.Decorated {
|
||||||
r := windows.GetWindowRect(w.hwnd)
|
frameDims = w.frameDims
|
||||||
cr := windows.GetClientRect(w.hwnd)
|
|
||||||
bw = r.Right - r.Left - (cr.Right - cr.Left)
|
|
||||||
bh = r.Bottom - r.Top - (cr.Bottom - cr.Top)
|
|
||||||
}
|
}
|
||||||
if p := w.config.MinSize; p.X > 0 || p.Y > 0 {
|
if p := w.config.MinSize; p.X > 0 || p.Y > 0 {
|
||||||
|
p = p.Add(frameDims)
|
||||||
mm.PtMinTrackSize = windows.Point{
|
mm.PtMinTrackSize = windows.Point{
|
||||||
X: int32(p.X) + bw,
|
X: int32(p.X),
|
||||||
Y: int32(p.Y) + bh,
|
Y: int32(p.Y),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if p := w.config.MaxSize; p.X > 0 || p.Y > 0 {
|
if p := w.config.MaxSize; p.X > 0 || p.Y > 0 {
|
||||||
|
p = p.Add(frameDims)
|
||||||
mm.PtMaxTrackSize = windows.Point{
|
mm.PtMaxTrackSize = windows.Point{
|
||||||
X: int32(p.X) + bw,
|
X: int32(p.X),
|
||||||
Y: int32(p.Y) + bh,
|
Y: int32(p.Y),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
@@ -377,7 +390,8 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
return windows.TRUE
|
return windows.TRUE
|
||||||
}
|
}
|
||||||
case _WM_WAKEUP:
|
case _WM_WAKEUP:
|
||||||
w.w.Event(wakeupEvent{})
|
w.loop.Wakeup()
|
||||||
|
w.loop.FlushEvents()
|
||||||
case windows.WM_IME_STARTCOMPOSITION:
|
case windows.WM_IME_STARTCOMPOSITION:
|
||||||
imc := windows.ImmGetContext(w.hwnd)
|
imc := windows.ImmGetContext(w.hwnd)
|
||||||
if imc == 0 {
|
if imc == 0 {
|
||||||
@@ -457,9 +471,6 @@ func getModifiers() key.Modifiers {
|
|||||||
// hitTest returns the non-client area hit by the point, needed to
|
// hitTest returns the non-client area hit by the point, needed to
|
||||||
// process WM_NCHITTEST.
|
// process WM_NCHITTEST.
|
||||||
func (w *window) hitTest(x, y int) uintptr {
|
func (w *window) hitTest(x, y int) uintptr {
|
||||||
if w.config.Mode == Fullscreen {
|
|
||||||
return windows.HTCLIENT
|
|
||||||
}
|
|
||||||
if w.config.Mode != Windowed {
|
if w.config.Mode != Windowed {
|
||||||
// Only windowed mode should allow resizing.
|
// Only windowed mode should allow resizing.
|
||||||
return windows.HTCLIENT
|
return windows.HTCLIENT
|
||||||
@@ -496,34 +507,28 @@ func (w *window) hitTest(x, y int) uintptr {
|
|||||||
return windows.HTCLIENT
|
return windows.HTCLIENT
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) {
|
func (w *window) pointerUpdate(pi windows.PointerInfo, pid pointer.ID, kind pointer.Kind, lParam uintptr) {
|
||||||
if !w.focused {
|
if !w.config.Focused {
|
||||||
windows.SetFocus(w.hwnd)
|
windows.SetFocus(w.hwnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
var kind pointer.Kind
|
src := pointer.Touch
|
||||||
if press {
|
if pi.PointerType == windows.PT_MOUSE {
|
||||||
kind = pointer.Press
|
src = pointer.Mouse
|
||||||
if w.pointerBtns == 0 {
|
|
||||||
windows.SetCapture(w.hwnd)
|
|
||||||
}
|
|
||||||
w.pointerBtns |= btn
|
|
||||||
} else {
|
|
||||||
kind = pointer.Release
|
|
||||||
w.pointerBtns &^= btn
|
|
||||||
if w.pointerBtns == 0 {
|
|
||||||
windows.ReleaseCapture()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
x, y := coordsFromlParam(lParam)
|
x, y := coordsFromlParam(lParam)
|
||||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
np := windows.Point{X: int32(x), Y: int32(y)}
|
||||||
w.w.Event(pointer.Event{
|
windows.ScreenToClient(w.hwnd, &np)
|
||||||
|
p := f32.Point{X: float32(np.X), Y: float32(np.Y)}
|
||||||
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Source: pointer.Mouse,
|
Source: src,
|
||||||
Position: p,
|
Position: p,
|
||||||
Buttons: w.pointerBtns,
|
PointerID: pid,
|
||||||
|
Buttons: getPointerButtons(pi),
|
||||||
Time: windows.GetMessageTime(),
|
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) {
|
func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.Modifiers) {
|
||||||
|
pid := getPointerIDwParam(wParam)
|
||||||
|
pi, err := windows.GetPointerInfo(uint32(pid))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
x, y := coordsFromlParam(lParam)
|
x, y := coordsFromlParam(lParam)
|
||||||
// The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast
|
// The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast
|
||||||
// to other mouse events.
|
// to other mouse events.
|
||||||
@@ -552,11 +563,11 @@ func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.
|
|||||||
sp.Y = -dist
|
sp.Y = -dist
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Scroll,
|
Kind: pointer.Scroll,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Position: p,
|
Position: p,
|
||||||
Buttons: w.pointerBtns,
|
Buttons: getPointerButtons(pi),
|
||||||
Scroll: sp,
|
Scroll: sp,
|
||||||
Modifiers: kmods,
|
Modifiers: kmods,
|
||||||
Time: windows.GetMessageTime(),
|
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/
|
// 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)
|
msg := new(windows.Msg)
|
||||||
loop:
|
loop:
|
||||||
for {
|
for {
|
||||||
anim := w.animating
|
anim := w.animating
|
||||||
if anim && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) {
|
p := windows.GetWindowPlacement(w.hwnd)
|
||||||
|
if anim && !p.IsMinimized() && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) {
|
||||||
w.draw(false)
|
w.draw(false)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
switch ret := windows.GetMessage(msg, 0, 0, 0); ret {
|
switch ret := windows.GetMessage(msg, 0, 0, 0); ret {
|
||||||
case -1:
|
case -1:
|
||||||
return errors.New("GetMessage failed")
|
panic(errors.New("GetMessage failed"))
|
||||||
case 0:
|
case 0:
|
||||||
// WM_QUIT received.
|
// WM_QUIT received.
|
||||||
break loop
|
break loop
|
||||||
@@ -583,7 +595,6 @@ loop:
|
|||||||
windows.TranslateMessage(msg)
|
windows.TranslateMessage(msg)
|
||||||
windows.DispatchMessage(msg)
|
windows.DispatchMessage(msg)
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) EditorStateChanged(old, new editorState) {
|
func (w *window) EditorStateChanged(old, new editorState) {
|
||||||
@@ -601,16 +612,30 @@ func (w *window) SetAnimating(anim bool) {
|
|||||||
w.animating = anim
|
w.animating = anim
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Wakeup() {
|
func (w *window) ProcessEvent(e event.Event) {
|
||||||
if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil {
|
w.w.ProcessEvent(e)
|
||||||
panic(err)
|
w.loop.FlushEvents()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) setStage(s system.Stage) {
|
func (w *window) Event() event.Event {
|
||||||
if s != w.stage {
|
return w.loop.Event()
|
||||||
w.stage = s
|
}
|
||||||
w.w.Event(system.StageEvent{Stage: s})
|
|
||||||
|
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)
|
dpi := windows.GetWindowDPI(w.hwnd)
|
||||||
cfg := configForDPI(dpi)
|
cfg := configForDPI(dpi)
|
||||||
w.w.Event(frameEvent{
|
w.ProcessEvent(frameEvent{
|
||||||
FrameEvent: system.FrameEvent{
|
FrameEvent: FrameEvent{
|
||||||
Now: time.Now(),
|
Now: time.Now(),
|
||||||
Size: w.config.Size,
|
Size: w.config.Size,
|
||||||
Metric: cfg,
|
Metric: cfg,
|
||||||
@@ -667,15 +692,25 @@ func (w *window) readClipboard() error {
|
|||||||
}
|
}
|
||||||
defer windows.GlobalUnlock(mem)
|
defer windows.GlobalUnlock(mem)
|
||||||
content := gowindows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr)))
|
content := gowindows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr)))
|
||||||
w.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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Configure(options []Option) {
|
func (w *window) Configure(options []Option) {
|
||||||
dpi := windows.GetSystemDPI()
|
dpi := windows.GetSystemDPI()
|
||||||
metric := configForDPI(dpi)
|
metric := configForDPI(dpi)
|
||||||
w.config.apply(metric, options)
|
cnf := w.config
|
||||||
windows.SetWindowText(w.hwnd, w.config.Title)
|
cnf.apply(metric, options)
|
||||||
|
w.config.Title = cnf.Title
|
||||||
|
w.config.Decorated = cnf.Decorated
|
||||||
|
w.config.MinSize = cnf.MinSize
|
||||||
|
w.config.MaxSize = cnf.MaxSize
|
||||||
|
windows.SetWindowText(w.hwnd, cnf.Title)
|
||||||
|
|
||||||
style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE)
|
style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE)
|
||||||
var showMode int32
|
var showMode int32
|
||||||
@@ -683,7 +718,7 @@ func (w *window) Configure(options []Option) {
|
|||||||
swpStyle := uintptr(windows.SWP_NOZORDER | windows.SWP_FRAMECHANGED)
|
swpStyle := uintptr(windows.SWP_NOZORDER | windows.SWP_FRAMECHANGED)
|
||||||
winStyle := uintptr(windows.WS_OVERLAPPEDWINDOW)
|
winStyle := uintptr(windows.WS_OVERLAPPEDWINDOW)
|
||||||
style &^= winStyle
|
style &^= winStyle
|
||||||
switch w.config.Mode {
|
switch cnf.Mode {
|
||||||
case Minimized:
|
case Minimized:
|
||||||
style |= winStyle
|
style |= winStyle
|
||||||
swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
|
swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
|
||||||
@@ -698,13 +733,13 @@ func (w *window) Configure(options []Option) {
|
|||||||
style |= winStyle
|
style |= winStyle
|
||||||
showMode = windows.SW_SHOWNORMAL
|
showMode = windows.SW_SHOWNORMAL
|
||||||
// Get target for client area size.
|
// Get target for client area size.
|
||||||
width = int32(w.config.Size.X)
|
width = int32(cnf.Size.X)
|
||||||
height = int32(w.config.Size.Y)
|
height = int32(cnf.Size.Y)
|
||||||
// Get the current window size and position.
|
// Get the current window size and position.
|
||||||
wr := windows.GetWindowRect(w.hwnd)
|
wr := windows.GetWindowRect(w.hwnd)
|
||||||
x = wr.Left
|
x = wr.Left
|
||||||
y = wr.Top
|
y = wr.Top
|
||||||
if w.config.Decorated {
|
if cnf.Decorated {
|
||||||
// Compute client size and position. Note that the client size is
|
// Compute client size and position. Note that the client size is
|
||||||
// equal to the window size when we are in control of decorations.
|
// equal to the window size when we are in control of decorations.
|
||||||
r := windows.Rect{
|
r := windows.Rect{
|
||||||
@@ -714,28 +749,31 @@ func (w *window) Configure(options []Option) {
|
|||||||
windows.AdjustWindowRectEx(&r, uint32(style), 0, dwExStyle)
|
windows.AdjustWindowRectEx(&r, uint32(style), 0, dwExStyle)
|
||||||
width = r.Right - r.Left
|
width = r.Right - r.Left
|
||||||
height = r.Bottom - r.Top
|
height = r.Bottom - r.Top
|
||||||
}
|
} else {
|
||||||
if !w.config.Decorated {
|
|
||||||
// Enable drop shadows when we draw decorations.
|
// Enable drop shadows when we draw decorations.
|
||||||
windows.DwmExtendFrameIntoClientArea(w.hwnd, windows.Margins{-1, -1, -1, -1})
|
windows.DwmExtendFrameIntoClientArea(w.hwnd, windows.Margins{-1, -1, -1, -1})
|
||||||
}
|
}
|
||||||
|
|
||||||
case Fullscreen:
|
case Fullscreen:
|
||||||
mi := windows.GetMonitorInfo(w.hwnd)
|
swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
|
||||||
x, y = mi.Monitor.Left, mi.Monitor.Top
|
showMode = windows.SW_SHOWMAXIMIZED
|
||||||
width = mi.Monitor.Right - mi.Monitor.Left
|
|
||||||
height = mi.Monitor.Bottom - mi.Monitor.Top
|
|
||||||
showMode = windows.SW_SHOW
|
|
||||||
}
|
}
|
||||||
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) {
|
func (w *window) WriteClipboard(mime string, s []byte) {
|
||||||
w.writeClipboard(s)
|
w.writeClipboard(string(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) writeClipboard(s string) error {
|
func (w *window) writeClipboard(s string) error {
|
||||||
@@ -863,11 +901,11 @@ func (w *window) raise() {
|
|||||||
windows.SWP_NOMOVE|windows.SWP_NOSIZE|windows.SWP_SHOWWINDOW)
|
windows.SWP_NOMOVE|windows.SWP_NOSIZE|windows.SWP_SHOWWINDOW)
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertKeyCode(code uintptr) (string, bool) {
|
func convertKeyCode(code uintptr) (key.Name, bool) {
|
||||||
if '0' <= code && code <= '9' || 'A' <= code && code <= 'Z' {
|
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 {
|
switch code {
|
||||||
case windows.VK_ESCAPE:
|
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 "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/io/clipboard"
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
|
"gioui.org/io/transfer"
|
||||||
|
"gioui.org/op"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
|
|
||||||
syscall "golang.org/x/sys/unix"
|
syscall "golang.org/x/sys/unix"
|
||||||
@@ -91,12 +96,10 @@ type x11Window struct {
|
|||||||
// _NET_WM_STATE_MAXIMIZED_VERT
|
// _NET_WM_STATE_MAXIMIZED_VERT
|
||||||
wmStateMaximizedVert C.Atom
|
wmStateMaximizedVert C.Atom
|
||||||
}
|
}
|
||||||
stage system.Stage
|
|
||||||
metric unit.Metric
|
metric unit.Metric
|
||||||
notify struct {
|
notify struct {
|
||||||
read, write int
|
read, write int
|
||||||
}
|
}
|
||||||
dead bool
|
|
||||||
|
|
||||||
animating bool
|
animating bool
|
||||||
|
|
||||||
@@ -109,6 +112,8 @@ type x11Window struct {
|
|||||||
config Config
|
config Config
|
||||||
|
|
||||||
wakeups chan struct{}
|
wakeups chan struct{}
|
||||||
|
handler x11EventHandler
|
||||||
|
buf [100]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
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)
|
C.XConvertSelection(w.x, w.atoms.clipboard, w.atoms.utf8string, w.atoms.clipboardContent, w.xw, C.CurrentTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *x11Window) WriteClipboard(s string) {
|
func (w *x11Window) WriteClipboard(mime string, s []byte) {
|
||||||
w.clipboard.content = []byte(s)
|
w.clipboard.content = s
|
||||||
C.XSetSelectionOwner(w.x, w.atoms.clipboard, w.xw, C.CurrentTime)
|
C.XSetSelectionOwner(w.x, w.atoms.clipboard, w.xw, C.CurrentTime)
|
||||||
C.XSetSelectionOwner(w.x, w.atoms.primary, w.xw, C.CurrentTime)
|
C.XSetSelectionOwner(w.x, w.atoms.primary, w.xw, C.CurrentTime)
|
||||||
}
|
}
|
||||||
@@ -232,7 +237,7 @@ func (w *x11Window) Configure(options []Option) {
|
|||||||
if cnf.Decorated != prev.Decorated {
|
if cnf.Decorated != prev.Decorated {
|
||||||
w.config.Decorated = cnf.Decorated
|
w.config.Decorated = cnf.Decorated
|
||||||
}
|
}
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *x11Window) setTitle(prev, cnf 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)
|
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 {
|
select {
|
||||||
case w.wakeups <- struct{}{}:
|
case w.wakeups <- struct{}{}:
|
||||||
default:
|
default:
|
||||||
@@ -393,16 +427,20 @@ func (w *x11Window) window() (C.Window, int, int) {
|
|||||||
return w.xw, w.config.Size.X, w.config.Size.Y
|
return w.xw, w.config.Size.X, w.config.Size.Y
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *x11Window) setStage(s system.Stage) {
|
func (w *x11Window) dispatch() {
|
||||||
if s == w.stage {
|
if w.x == nil {
|
||||||
|
// Only Invalidate can wake us up.
|
||||||
|
<-w.wakeups
|
||||||
|
w.w.Invalidate()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.stage = s
|
|
||||||
w.w.Event(system.StageEvent{Stage: s})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *x11Window) loop() {
|
select {
|
||||||
h := x11EventHandler{w: w, xev: new(C.XEvent), text: make([]byte, 4)}
|
case <-w.wakeups:
|
||||||
|
w.w.Invalidate()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
xfd := C.XConnectionNumber(w.x)
|
xfd := C.XConnectionNumber(w.x)
|
||||||
|
|
||||||
// Poll for events and notifications.
|
// Poll for events and notifications.
|
||||||
@@ -412,61 +450,55 @@ func (w *x11Window) loop() {
|
|||||||
}
|
}
|
||||||
xEvents := &pollfds[0].Revents
|
xEvents := &pollfds[0].Revents
|
||||||
// Plenty of room for a backlog of notifications.
|
// Plenty of room for a backlog of notifications.
|
||||||
buf := make([]byte, 100)
|
|
||||||
|
|
||||||
loop:
|
var syn, anim bool
|
||||||
for !w.dead {
|
// Check for pending draw events before checking animation or blocking.
|
||||||
var syn, anim bool
|
// This fixes an issue on Xephyr where on startup XPending() > 0 but
|
||||||
// Check for pending draw events before checking animation or blocking.
|
// poll will still block. This also prevents no-op calls to poll.
|
||||||
// This fixes an issue on Xephyr where on startup XPending() > 0 but
|
syn = w.handler.handleEvents()
|
||||||
// poll will still block. This also prevents no-op calls to poll.
|
if w.x == nil {
|
||||||
if syn = h.handleEvents(); !syn {
|
// handleEvents received a close request and destroyed the window.
|
||||||
anim = w.animating
|
return
|
||||||
if !anim {
|
}
|
||||||
// Clear poll events.
|
if !syn {
|
||||||
*xEvents = 0
|
anim = w.animating
|
||||||
// Wait for X event or gio notification.
|
if !anim {
|
||||||
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
// Clear poll events.
|
||||||
panic(fmt.Errorf("x11 loop: poll failed: %w", err))
|
*xEvents = 0
|
||||||
}
|
// Wait for X event or gio notification.
|
||||||
switch {
|
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
||||||
case *xEvents&syscall.POLLIN != 0:
|
panic(fmt.Errorf("x11 loop: poll failed: %w", err))
|
||||||
syn = h.handleEvents()
|
}
|
||||||
if w.dead {
|
switch {
|
||||||
break loop
|
case *xEvents&syscall.POLLIN != 0:
|
||||||
}
|
syn = w.handler.handleEvents()
|
||||||
case *xEvents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
if w.x == nil {
|
||||||
break loop
|
return
|
||||||
}
|
}
|
||||||
|
case *xEvents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Clear notifications.
|
}
|
||||||
for {
|
// Clear notifications.
|
||||||
_, err := syscall.Read(w.notify.read, buf)
|
for {
|
||||||
if err == syscall.EAGAIN {
|
_, err := syscall.Read(w.notify.read, w.buf[:])
|
||||||
break
|
if err == syscall.EAGAIN {
|
||||||
}
|
break
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Errorf("x11 loop: read from notify pipe failed: %w", err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
select {
|
if err != nil {
|
||||||
case <-w.wakeups:
|
panic(fmt.Errorf("x11 loop: read from notify pipe failed: %w", err))
|
||||||
w.w.Event(wakeupEvent{})
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
if (anim || syn) && w.config.Size.X != 0 && w.config.Size.Y != 0 {
|
|
||||||
w.w.Event(frameEvent{
|
|
||||||
FrameEvent: system.FrameEvent{
|
|
||||||
Now: time.Now(),
|
|
||||||
Size: w.config.Size,
|
|
||||||
Metric: w.metric,
|
|
||||||
},
|
|
||||||
Sync: syn,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (anim || syn) && w.config.Size.X != 0 && w.config.Size.Y != 0 {
|
||||||
|
w.ProcessEvent(frameEvent{
|
||||||
|
FrameEvent: FrameEvent{
|
||||||
|
Now: time.Now(),
|
||||||
|
Size: w.config.Size,
|
||||||
|
Metric: w.metric,
|
||||||
|
},
|
||||||
|
Sync: syn,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *x11Window) destroy() {
|
func (w *x11Window) destroy() {
|
||||||
@@ -484,6 +516,7 @@ func (w *x11Window) destroy() {
|
|||||||
}
|
}
|
||||||
C.XDestroyWindow(w.x, w.xw)
|
C.XDestroyWindow(w.x, w.xw)
|
||||||
C.XCloseDisplay(w.x)
|
C.XCloseDisplay(w.x)
|
||||||
|
w.x = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// atom is a wrapper around XInternAtom. Callers should cache the result
|
// atom is a wrapper around XInternAtom. Callers should cache the result
|
||||||
@@ -541,7 +574,7 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
// There's no support for IME yet.
|
// There's no support for IME yet.
|
||||||
w.w.EditorInsert(ee.Text)
|
w.w.EditorInsert(ee.Text)
|
||||||
} else {
|
} else {
|
||||||
w.w.Event(e)
|
w.ProcessEvent(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case C.ButtonPress, C.ButtonRelease:
|
case C.ButtonPress, C.ButtonRelease:
|
||||||
@@ -603,10 +636,10 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
w.pointerBtns &^= btn
|
w.pointerBtns &^= btn
|
||||||
}
|
}
|
||||||
ev.Buttons = w.pointerBtns
|
ev.Buttons = w.pointerBtns
|
||||||
w.w.Event(ev)
|
w.ProcessEvent(ev)
|
||||||
case C.MotionNotify:
|
case C.MotionNotify:
|
||||||
mevt := (*C.XMotionEvent)(unsafe.Pointer(xev))
|
mevt := (*C.XMotionEvent)(unsafe.Pointer(xev))
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Move,
|
Kind: pointer.Move,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Buttons: w.pointerBtns,
|
Buttons: w.pointerBtns,
|
||||||
@@ -621,14 +654,16 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
// redraw only on the last expose event
|
// redraw only on the last expose event
|
||||||
redraw = (*C.XExposeEvent)(unsafe.Pointer(xev)).count == 0
|
redraw = (*C.XExposeEvent)(unsafe.Pointer(xev)).count == 0
|
||||||
case C.FocusIn:
|
case C.FocusIn:
|
||||||
w.w.Event(key.FocusEvent{Focus: true})
|
w.config.Focused = true
|
||||||
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
case C.FocusOut:
|
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
|
case C.ConfigureNotify: // window configuration change
|
||||||
cevt := (*C.XConfigureEvent)(unsafe.Pointer(xev))
|
cevt := (*C.XConfigureEvent)(unsafe.Pointer(xev))
|
||||||
if sz := image.Pt(int(cevt.width), int(cevt.height)); sz != w.config.Size {
|
if sz := image.Pt(int(cevt.width), int(cevt.height)); sz != w.config.Size {
|
||||||
w.config.Size = sz
|
w.config.Size = sz
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
// redraw will be done by a later expose event
|
// redraw will be done by a later expose event
|
||||||
case C.SelectionNotify:
|
case C.SelectionNotify:
|
||||||
@@ -650,7 +685,12 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
str := C.GoStringN((*C.char)(unsafe.Pointer(text.value)), C.int(text.nitems))
|
str := C.GoStringN((*C.char)(unsafe.Pointer(text.value)), C.int(text.nitems))
|
||||||
w.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:
|
case C.SelectionRequest:
|
||||||
cevt := (*C.XSelectionRequestEvent)(unsafe.Pointer(xev))
|
cevt := (*C.XSelectionRequestEvent)(unsafe.Pointer(xev))
|
||||||
if (cevt.selection != w.atoms.clipboard && cevt.selection != w.atoms.primary) || cevt.property == C.None {
|
if (cevt.selection != w.atoms.clipboard && cevt.selection != w.atoms.primary) || cevt.property == C.None {
|
||||||
@@ -704,7 +744,7 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
cevt := (*C.XClientMessageEvent)(unsafe.Pointer(xev))
|
cevt := (*C.XClientMessageEvent)(unsafe.Pointer(xev))
|
||||||
switch *(*C.long)(unsafe.Pointer(&cevt.data)) {
|
switch *(*C.long)(unsafe.Pointer(&cevt.data)) {
|
||||||
case C.long(w.atoms.evDelWindow):
|
case C.long(w.atoms.evDelWindow):
|
||||||
w.dead = true
|
w.shutdown(nil)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -712,9 +752,7 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
return redraw
|
return redraw
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var x11Threads sync.Once
|
||||||
x11Threads sync.Once
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
x11Driver = newX11Window
|
x11Driver = newX11Window
|
||||||
@@ -786,8 +824,10 @@ func newX11Window(gioWin *callbacks, options []Option) error {
|
|||||||
wakeups: make(chan struct{}, 1),
|
wakeups: make(chan struct{}, 1),
|
||||||
config: Config{Size: cnf.Size},
|
config: Config{Size: cnf.Size},
|
||||||
}
|
}
|
||||||
|
w.handler = x11EventHandler{w: w, xev: new(C.XEvent), text: make([]byte, 4)}
|
||||||
w.notify.read = pipe[0]
|
w.notify.read = pipe[0]
|
||||||
w.notify.write = pipe[1]
|
w.notify.write = pipe[1]
|
||||||
|
w.w.SetDriver(w)
|
||||||
|
|
||||||
if err := w.updateXkbKeymap(); err != nil {
|
if err := w.updateXkbKeymap(); err != nil {
|
||||||
w.destroy()
|
w.destroy()
|
||||||
@@ -823,19 +863,10 @@ func newX11Window(gioWin *callbacks, options []Option) error {
|
|||||||
// extensions
|
// extensions
|
||||||
C.XSetWMProtocols(dpy, win, &w.atoms.evDelWindow, 1)
|
C.XSetWMProtocols(dpy, win, &w.atoms.evDelWindow, 1)
|
||||||
|
|
||||||
go func() {
|
// make the window visible on the screen
|
||||||
w.w.SetDriver(w)
|
C.XMapWindow(dpy, win)
|
||||||
|
w.Configure(options)
|
||||||
// make the window visible on the screen
|
w.ProcessEvent(X11ViewEvent{Display: unsafe.Pointer(dpy), Window: uintptr(win)})
|
||||||
C.XMapWindow(dpy, win)
|
|
||||||
w.Configure(options)
|
|
||||||
w.w.Event(X11ViewEvent{Display: unsafe.Pointer(dpy), Window: uintptr(win)})
|
|
||||||
w.setStage(system.StageRunning)
|
|
||||||
w.loop()
|
|
||||||
w.w.Event(X11ViewEvent{})
|
|
||||||
w.w.Event(system.DestroyEvent{Err: nil})
|
|
||||||
w.destroy()
|
|
||||||
}()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
minExt, maxExt := caps.MinExtent(), caps.MaxExtent()
|
minExt, maxExt := vk.SurfaceCapabilitiesMinExtent(caps), vk.SurfaceCapabilitiesMaxExtent(caps)
|
||||||
if width < minExt.X || maxExt.X < width || height < minExt.Y || maxExt.Y < height {
|
if width < minExt.X || maxExt.X < width || height < minExt.Y || maxExt.Y < height {
|
||||||
return errOutOfDate
|
return errOutOfDate
|
||||||
}
|
}
|
||||||
|
|||||||
+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.
|
// Offset the transformation.
|
||||||
func (a Affine2D) Offset(offset Point) Affine2D {
|
func (a Affine2D) Offset(offset Point) Affine2D {
|
||||||
return 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
|
// Split a transform into two parts, one which is pure offset and the
|
||||||
// other representing the scaling, shearing and rotation part.
|
// other representing the scaling, shearing and rotation part.
|
||||||
func (a *Affine2D) Split() (srs Affine2D, offset Point) {
|
func (a Affine2D) Split() (srs Affine2D, offset Point) {
|
||||||
return Affine2D{
|
return Affine2D{
|
||||||
a: a.a, b: a.b, c: 0,
|
a: a.a, b: a.b, c: 0,
|
||||||
d: a.d, e: a.e, f: 0,
|
d: a.d, e: a.e, f: 0,
|
||||||
|
|||||||
+160
-30
@@ -27,11 +27,11 @@ func TestTransformOffset(t *testing.T) {
|
|||||||
p := Point{X: 1, Y: 2}
|
p := Point{X: 1, Y: 2}
|
||||||
o := Point{X: 2, Y: -3}
|
o := Point{X: 2, Y: -3}
|
||||||
|
|
||||||
r := Affine2D{}.Offset(o).Transform(p)
|
r := AffineId().Offset(o).Transform(p)
|
||||||
if !eq(r, Pt(3, -1)) {
|
if !eq(r, Pt(3, -1)) {
|
||||||
t.Errorf("offset transformation mismatch: have %v, want {3 -1}", r)
|
t.Errorf("offset transformation mismatch: have %v, want {3 -1}", r)
|
||||||
}
|
}
|
||||||
i := Affine2D{}.Offset(o).Invert().Transform(r)
|
i := AffineId().Offset(o).Invert().Transform(r)
|
||||||
if !eq(i, p) {
|
if !eq(i, p) {
|
||||||
t.Errorf("offset transformation inverse mismatch: have %v, want %v", i, p)
|
t.Errorf("offset transformation inverse mismatch: have %v, want %v", i, p)
|
||||||
}
|
}
|
||||||
@@ -51,6 +51,9 @@ func TestString(t *testing.T) {
|
|||||||
}, {
|
}, {
|
||||||
in: NewAffine2D(29.142342, 31.4123412, 37.53152, 43.51324213, 47.123412, 53.14312342),
|
in: NewAffine2D(29.142342, 31.4123412, 37.53152, 43.51324213, 47.123412, 53.14312342),
|
||||||
exp: "[[29.1423 31.4123 37.5315] [43.5132 47.1234 53.1431]]",
|
exp: "[[29.1423 31.4123 37.5315] [43.5132 47.1234 53.1431]]",
|
||||||
|
}, {
|
||||||
|
in: AffineId(),
|
||||||
|
exp: "[[1 0 0] [0 1 0]]",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
@@ -64,11 +67,11 @@ func TestTransformScale(t *testing.T) {
|
|||||||
p := Point{X: 1, Y: 2}
|
p := Point{X: 1, Y: 2}
|
||||||
s := Point{X: -1, Y: 2}
|
s := Point{X: -1, Y: 2}
|
||||||
|
|
||||||
r := Affine2D{}.Scale(Point{}, s).Transform(p)
|
r := AffineId().Scale(Point{}, s).Transform(p)
|
||||||
if !eq(r, Pt(-1, 4)) {
|
if !eq(r, Pt(-1, 4)) {
|
||||||
t.Errorf("scale transformation mismatch: have %v, want {-1 4}", r)
|
t.Errorf("scale transformation mismatch: have %v, want {-1 4}", r)
|
||||||
}
|
}
|
||||||
i := Affine2D{}.Scale(Point{}, s).Invert().Transform(r)
|
i := AffineId().Scale(Point{}, s).Invert().Transform(r)
|
||||||
if !eq(i, p) {
|
if !eq(i, p) {
|
||||||
t.Errorf("scale transformation inverse mismatch: have %v, want %v", i, p)
|
t.Errorf("scale transformation inverse mismatch: have %v, want %v", i, p)
|
||||||
}
|
}
|
||||||
@@ -78,11 +81,11 @@ func TestTransformRotate(t *testing.T) {
|
|||||||
p := Point{X: 1, Y: 0}
|
p := Point{X: 1, Y: 0}
|
||||||
a := float32(math.Pi / 2)
|
a := float32(math.Pi / 2)
|
||||||
|
|
||||||
r := Affine2D{}.Rotate(Point{}, a).Transform(p)
|
r := AffineId().Rotate(Point{}, a).Transform(p)
|
||||||
if !eq(r, Pt(0, 1)) {
|
if !eq(r, Pt(0, 1)) {
|
||||||
t.Errorf("rotate transformation mismatch: have %v, want {0 1}", r)
|
t.Errorf("rotate transformation mismatch: have %v, want {0 1}", r)
|
||||||
}
|
}
|
||||||
i := Affine2D{}.Rotate(Point{}, a).Invert().Transform(r)
|
i := AffineId().Rotate(Point{}, a).Invert().Transform(r)
|
||||||
if !eq(i, p) {
|
if !eq(i, p) {
|
||||||
t.Errorf("rotate transformation inverse mismatch: have %v, want %v", i, p)
|
t.Errorf("rotate transformation inverse mismatch: have %v, want %v", i, p)
|
||||||
}
|
}
|
||||||
@@ -91,11 +94,11 @@ func TestTransformRotate(t *testing.T) {
|
|||||||
func TestTransformShear(t *testing.T) {
|
func TestTransformShear(t *testing.T) {
|
||||||
p := Point{X: 1, Y: 1}
|
p := Point{X: 1, Y: 1}
|
||||||
|
|
||||||
r := 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)) {
|
if !eq(r, Pt(2, 1)) {
|
||||||
t.Errorf("shear transformation mismatch: have %v, want {2 1}", r)
|
t.Errorf("shear transformation mismatch: have %v, want {2 1}", r)
|
||||||
}
|
}
|
||||||
i := 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) {
|
if !eq(i, p) {
|
||||||
t.Errorf("shear transformation inverse mismatch: have %v, want %v", i, p)
|
t.Errorf("shear transformation inverse mismatch: have %v, want %v", i, p)
|
||||||
}
|
}
|
||||||
@@ -107,11 +110,11 @@ func TestTransformMultiply(t *testing.T) {
|
|||||||
s := Point{X: -1, Y: 2}
|
s := Point{X: -1, Y: 2}
|
||||||
a := float32(-math.Pi / 2)
|
a := float32(-math.Pi / 2)
|
||||||
|
|
||||||
r := 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)) {
|
if !eq(r, Pt(1, 3)) {
|
||||||
t.Errorf("complex transformation mismatch: have %v, want {1 3}", r)
|
t.Errorf("complex transformation mismatch: have %v, want {1 3}", r)
|
||||||
}
|
}
|
||||||
i := 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) {
|
if !eq(i, p) {
|
||||||
t.Errorf("complex transformation inverse mismatch: have %v, want %v", i, p)
|
t.Errorf("complex transformation inverse mismatch: have %v, want %v", i, p)
|
||||||
}
|
}
|
||||||
@@ -163,7 +166,7 @@ func TestPrimes(t *testing.T) {
|
|||||||
func TestTransformScaleAround(t *testing.T) {
|
func TestTransformScaleAround(t *testing.T) {
|
||||||
p := Pt(-1, -1)
|
p := Pt(-1, -1)
|
||||||
target := Pt(-6, -13)
|
target := Pt(-6, -13)
|
||||||
pt := 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) {
|
if !eq(pt, target) {
|
||||||
t.Log(pt, "!=", target)
|
t.Log(pt, "!=", target)
|
||||||
t.Error("Scale not as expected")
|
t.Error("Scale not as expected")
|
||||||
@@ -172,7 +175,7 @@ func TestTransformScaleAround(t *testing.T) {
|
|||||||
|
|
||||||
func TestTransformRotateAround(t *testing.T) {
|
func TestTransformRotateAround(t *testing.T) {
|
||||||
p := Pt(-1, -1)
|
p := Pt(-1, -1)
|
||||||
pt := 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)
|
target := Pt(-1, 3)
|
||||||
if !eq(pt, target) {
|
if !eq(pt, target) {
|
||||||
t.Log(pt, "!=", target)
|
t.Log(pt, "!=", target)
|
||||||
@@ -181,12 +184,12 @@ func TestTransformRotateAround(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMulOrder(t *testing.T) {
|
func TestMulOrder(t *testing.T) {
|
||||||
A := Affine2D{}.Offset(Pt(100, 100))
|
A := AffineId().Offset(Pt(100, 100))
|
||||||
B := Affine2D{}.Scale(Point{}, Pt(2, 2))
|
B := AffineId().Scale(Point{}, Pt(2, 2))
|
||||||
_ = A
|
_ = A
|
||||||
_ = B
|
_ = 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)
|
T2 := B.Mul(A)
|
||||||
|
|
||||||
if T1 != T2 {
|
if T1 != T2 {
|
||||||
@@ -199,9 +202,9 @@ func TestMulOrder(t *testing.T) {
|
|||||||
func BenchmarkTransformOffset(b *testing.B) {
|
func BenchmarkTransformOffset(b *testing.B) {
|
||||||
p := Point{X: 1, Y: 2}
|
p := Point{X: 1, Y: 2}
|
||||||
o := Point{X: 0.5, Y: 0.5}
|
o := Point{X: 0.5, Y: 0.5}
|
||||||
aff := Affine2D{}.Offset(o)
|
aff := AffineId().Offset(o)
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for b.Loop() {
|
||||||
p = aff.Transform(p)
|
p = aff.Transform(p)
|
||||||
}
|
}
|
||||||
_ = p
|
_ = p
|
||||||
@@ -210,8 +213,8 @@ func BenchmarkTransformOffset(b *testing.B) {
|
|||||||
func BenchmarkTransformScale(b *testing.B) {
|
func BenchmarkTransformScale(b *testing.B) {
|
||||||
p := Point{X: 1, Y: 2}
|
p := Point{X: 1, Y: 2}
|
||||||
s := Point{X: 0.5, Y: 0.5}
|
s := Point{X: 0.5, Y: 0.5}
|
||||||
aff := Affine2D{}.Scale(Point{}, s)
|
aff := AffineId().Scale(Point{}, s)
|
||||||
for i := 0; i < b.N; i++ {
|
for b.Loop() {
|
||||||
p = aff.Transform(p)
|
p = aff.Transform(p)
|
||||||
}
|
}
|
||||||
_ = p
|
_ = p
|
||||||
@@ -220,36 +223,163 @@ func BenchmarkTransformScale(b *testing.B) {
|
|||||||
func BenchmarkTransformRotate(b *testing.B) {
|
func BenchmarkTransformRotate(b *testing.B) {
|
||||||
p := Point{X: 1, Y: 2}
|
p := Point{X: 1, Y: 2}
|
||||||
a := float32(math.Pi / 2)
|
a := float32(math.Pi / 2)
|
||||||
aff := Affine2D{}.Rotate(Point{}, a)
|
aff := AffineId().Rotate(Point{}, a)
|
||||||
for i := 0; i < b.N; i++ {
|
for b.Loop() {
|
||||||
p = aff.Transform(p)
|
p = aff.Transform(p)
|
||||||
}
|
}
|
||||||
_ = p
|
_ = p
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkTransformTranslateMultiply(b *testing.B) {
|
func BenchmarkTransformTranslateMultiply(b *testing.B) {
|
||||||
a := Affine2D{}.Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
|
a := AffineId().Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
|
||||||
t := Affine2D{}.Offset(Point{X: 0.5, Y: 0.5})
|
t := AffineId().Offset(Point{X: 0.5, Y: 0.5})
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for b.Loop() {
|
||||||
a = a.Mul(t)
|
a = a.Mul(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkTransformScaleMultiply(b *testing.B) {
|
func BenchmarkTransformScaleMultiply(b *testing.B) {
|
||||||
a := Affine2D{}.Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
|
a := AffineId().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})
|
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)
|
a = a.Mul(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkTransformMultiply(b *testing.B) {
|
func BenchmarkTransformMultiply(b *testing.B) {
|
||||||
a := Affine2D{}.Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
|
a := AffineId().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)
|
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)
|
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": {
|
"nodes": {
|
||||||
"android": {
|
|
||||||
"inputs": {
|
|
||||||
"devshell": "devshell",
|
|
||||||
"flake-utils": "flake-utils",
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1659298920,
|
|
||||||
"narHash": "sha256-LgRMge8BZUG15EN43iDJOlnEMX1dvRprB7SaoNqgibU=",
|
|
||||||
"owner": "tadfisher",
|
|
||||||
"repo": "android-nixpkgs",
|
|
||||||
"rev": "d4f20a3cd4ce961bb23b48447457f6810d69ae5e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "tadfisher",
|
|
||||||
"repo": "android-nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"devshell": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-utils": [
|
|
||||||
"android",
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"nixpkgs": [
|
|
||||||
"android",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1658746384,
|
|
||||||
"narHash": "sha256-CCJcoMOcXyZFrV1ag4XMTpAPjLWb4Anbv+ktXFI1ry0=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "devshell",
|
|
||||||
"rev": "0ffc7937bb5e8141af03d462b468bd071eb18e1b",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "devshell",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-utils": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1656928814,
|
|
||||||
"narHash": "sha256-RIFfgBuKz6Hp89yRr7+NR5tzIAbn52h8vT6vXkYjZoM=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "7e2a3b3dfd9af950a856d66b0a7d01e3c18aa249",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1659305579,
|
"lastModified": 1747953325,
|
||||||
"narHash": "sha256-SFeQTmh7hc9Y2fSkooHaoS8mDfPa04sfmUCtQ8MA6Pg=",
|
"narHash": "sha256-y2ZtlIlNTuVJUZCqzZAhIw5rrKP4DOSklev6c8PyCkQ=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "5857574d45925585baffde730369414319228a84",
|
"rev": "55d1f923c480dadce40f5231feb472e81b0bab48",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-25.05",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"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";
|
description = "Gio build environment";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
|
||||||
android.url = "github:tadfisher/android-nixpkgs";
|
utils.url = "github:numtide/flake-utils";
|
||||||
android.inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, android }:
|
outputs = { self, nixpkgs, utils }:
|
||||||
let
|
utils.lib.eachDefaultSystem (system:
|
||||||
supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-darwin" ];
|
let
|
||||||
forAllSystems = f: nixpkgs.lib.genAttrs supportedSystems (system: f system);
|
pkgs = import nixpkgs {
|
||||||
in
|
inherit system;
|
||||||
{
|
|
||||||
devShells = forAllSystems
|
# allow unfree Android packages.
|
||||||
(system:
|
config.allowUnfree = true;
|
||||||
let
|
# accept the Android SDK license.
|
||||||
pkgs = import nixpkgs {
|
config.android_sdk.accept_license = true;
|
||||||
inherit system;
|
};
|
||||||
|
in {
|
||||||
|
devShells = let
|
||||||
|
android-sdk = let
|
||||||
|
androidComposition = pkgs.androidenv.composeAndroidPackages {
|
||||||
|
platformVersions = [ "latest" ];
|
||||||
|
abiVersions = [ "armeabi-v7a" "arm64-v8a" ];
|
||||||
|
# Omit the deprecated tools package.
|
||||||
|
toolsVersion = null;
|
||||||
|
includeNDK = true;
|
||||||
};
|
};
|
||||||
android-sdk = android.sdk.${system} (sdkPkgs: with sdkPkgs;
|
in androidComposition.androidsdk;
|
||||||
[
|
in {
|
||||||
build-tools-31-0-0
|
default = with pkgs;
|
||||||
cmdline-tools-latest
|
mkShell (rec {
|
||||||
platform-tools
|
ANDROID_HOME = "${android-sdk}/libexec/android-sdk";
|
||||||
platforms-android-31
|
packages = [ android-sdk jdk clang ]
|
||||||
ndk-bundle
|
++ (if stdenv.isLinux then [
|
||||||
]);
|
|
||||||
in
|
|
||||||
{
|
|
||||||
default = with pkgs; mkShell
|
|
||||||
({
|
|
||||||
ANDROID_SDK_ROOT = "${android-sdk}/share/android-sdk";
|
|
||||||
JAVA_HOME = jdk8.home;
|
|
||||||
packages = [
|
|
||||||
android-sdk
|
|
||||||
jdk8
|
|
||||||
clang
|
|
||||||
] ++ (if stdenv.isLinux then [
|
|
||||||
vulkan-headers
|
vulkan-headers
|
||||||
libxkbcommon
|
libxkbcommon
|
||||||
wayland
|
wayland
|
||||||
@@ -46,18 +42,13 @@
|
|||||||
xorg.libXcursor
|
xorg.libXcursor
|
||||||
xorg.libXfixes
|
xorg.libXfixes
|
||||||
libGL
|
libGL
|
||||||
pkgconfig
|
pkg-config
|
||||||
] else if stdenv.isDarwin then [
|
] else
|
||||||
darwin.apple_sdk_11_0.frameworks.Foundation
|
[ ]);
|
||||||
darwin.apple_sdk_11_0.frameworks.Metal
|
} // (if stdenv.isLinux then {
|
||||||
darwin.apple_sdk_11_0.frameworks.QuartzCore
|
LD_LIBRARY_PATH = "${vulkan-loader}/lib";
|
||||||
darwin.apple_sdk_11_0.frameworks.AppKit
|
} else
|
||||||
darwin.apple_sdk_11_0.MacOSX-SDK
|
{ }));
|
||||||
] else [ ]);
|
};
|
||||||
} // (if stdenv.isLinux then {
|
});
|
||||||
LD_LIBRARY_PATH = "${vulkan-loader}/lib";
|
|
||||||
} else { }));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -34,7 +34,7 @@ type Font struct {
|
|||||||
// Face is an opaque handle to a typeface. The concrete implementation depends
|
// Face is an opaque handle to a typeface. The concrete implementation depends
|
||||||
// upon the kind of font and shaper in use.
|
// upon the kind of font and shaper in use.
|
||||||
type Face interface {
|
type Face interface {
|
||||||
Face() font.Face
|
Face() *font.Face
|
||||||
}
|
}
|
||||||
|
|
||||||
// Typeface identifies a list of font families to attempt to use for displaying
|
// Typeface identifies a list of font families to attempt to use for displaying
|
||||||
|
|||||||
+41
-43
@@ -16,23 +16,21 @@ import (
|
|||||||
_ "image/png"
|
_ "image/png"
|
||||||
|
|
||||||
giofont "gioui.org/font"
|
giofont "gioui.org/font"
|
||||||
"github.com/go-text/typesetting/font"
|
fontapi "github.com/go-text/typesetting/font"
|
||||||
fontapi "github.com/go-text/typesetting/opentype/api/font"
|
"github.com/go-text/typesetting/font/opentype"
|
||||||
"github.com/go-text/typesetting/opentype/api/metadata"
|
|
||||||
"github.com/go-text/typesetting/opentype/loader"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Face is a thread-safe representation of a loaded font. For efficiency, applications
|
// Face is a thread-safe representation of a loaded font. For efficiency, applications
|
||||||
// should construct a face for any given font file once, reusing it across different
|
// should construct a face for any given font file once, reusing it across different
|
||||||
// text shapers.
|
// text shapers.
|
||||||
type Face struct {
|
type Face struct {
|
||||||
face font.Font
|
face *fontapi.Font
|
||||||
font giofont.Font
|
font giofont.Font
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse constructs a Face from source bytes.
|
// Parse constructs a Face from source bytes.
|
||||||
func Parse(src []byte) (Face, error) {
|
func Parse(src []byte) (Face, error) {
|
||||||
ld, err := loader.NewLoader(bytes.NewReader(src))
|
ld, err := opentype.NewLoader(bytes.NewReader(src))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Face{}, err
|
return Face{}, err
|
||||||
}
|
}
|
||||||
@@ -49,11 +47,11 @@ func Parse(src []byte) (Face, error) {
|
|||||||
// ParseCollection parse an Opentype font file, with support for collections.
|
// ParseCollection parse an Opentype font file, with support for collections.
|
||||||
// Single font files are supported, returning a slice with length 1.
|
// Single font files are supported, returning a slice with length 1.
|
||||||
// The returned fonts are automatically wrapped in a text.FontFace with
|
// The returned fonts are automatically wrapped in a text.FontFace with
|
||||||
// inferred font metadata.
|
// inferred font font.
|
||||||
// BUG(whereswaldon): the only Variant that can be detected automatically is
|
// BUG(whereswaldon): the only Variant that can be detected automatically is
|
||||||
// "Mono".
|
// "Mono".
|
||||||
func ParseCollection(src []byte) ([]giofont.FontFace, error) {
|
func ParseCollection(src []byte) ([]giofont.FontFace, error) {
|
||||||
lds, err := loader.NewLoaders(bytes.NewReader(src))
|
lds, err := opentype.NewLoaders(bytes.NewReader(src))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -76,7 +74,7 @@ func ParseCollection(src []byte) ([]giofont.FontFace, error) {
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DescriptionToFont(md metadata.Description) giofont.Font {
|
func DescriptionToFont(md fontapi.Description) giofont.Font {
|
||||||
return giofont.Font{
|
return giofont.Font{
|
||||||
Typeface: giofont.Typeface(md.Family),
|
Typeface: giofont.Typeface(md.Family),
|
||||||
Style: gioStyle(md.Aspect.Style),
|
Style: gioStyle(md.Aspect.Style),
|
||||||
@@ -84,30 +82,30 @@ func DescriptionToFont(md metadata.Description) giofont.Font {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func FontToDescription(font giofont.Font) metadata.Description {
|
func FontToDescription(font giofont.Font) fontapi.Description {
|
||||||
return metadata.Description{
|
return fontapi.Description{
|
||||||
Family: string(font.Typeface),
|
Family: string(font.Typeface),
|
||||||
Aspect: metadata.Aspect{
|
Aspect: fontapi.Aspect{
|
||||||
Style: mdStyle(font.Style),
|
Style: mdStyle(font.Style),
|
||||||
Weight: mdWeight(font.Weight),
|
Weight: mdWeight(font.Weight),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseLoader parses the contents of the loader into a face and its metadata.
|
// parseLoader parses the contents of the loader into a face and its font.
|
||||||
func parseLoader(ld *loader.Loader) (font.Font, giofont.Font, error) {
|
func parseLoader(ld *opentype.Loader) (*fontapi.Font, giofont.Font, error) {
|
||||||
ft, err := fontapi.NewFont(ld)
|
ft, err := fontapi.NewFont(ld)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, giofont.Font{}, err
|
return nil, giofont.Font{}, err
|
||||||
}
|
}
|
||||||
data := DescriptionToFont(metadata.Metadata(ld))
|
data := DescriptionToFont(ft.Describe())
|
||||||
return ft, data, nil
|
return ft, data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Face returns a thread-unsafe wrapper for this Face suitable for use by a single shaper.
|
// Face returns a thread-unsafe wrapper for this Face suitable for use by a single shaper.
|
||||||
// Face many be invoked any number of times and is safe so long as each return value is
|
// Face many be invoked any number of times and is safe so long as each return value is
|
||||||
// only used by one goroutine.
|
// only used by one goroutine.
|
||||||
func (f Face) Face() font.Face {
|
func (f Face) Face() *fontapi.Face {
|
||||||
return &fontapi.Face{Font: f.face}
|
return &fontapi.Face{Font: f.face}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,74 +117,74 @@ func (f Face) Font() giofont.Font {
|
|||||||
return f.font
|
return f.font
|
||||||
}
|
}
|
||||||
|
|
||||||
func gioStyle(s metadata.Style) giofont.Style {
|
func gioStyle(s fontapi.Style) giofont.Style {
|
||||||
switch s {
|
switch s {
|
||||||
case metadata.StyleItalic:
|
case fontapi.StyleItalic:
|
||||||
return giofont.Italic
|
return giofont.Italic
|
||||||
case metadata.StyleNormal:
|
case fontapi.StyleNormal:
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
return giofont.Regular
|
return giofont.Regular
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mdStyle(g giofont.Style) metadata.Style {
|
func mdStyle(g giofont.Style) fontapi.Style {
|
||||||
switch g {
|
switch g {
|
||||||
case giofont.Italic:
|
case giofont.Italic:
|
||||||
return metadata.StyleItalic
|
return fontapi.StyleItalic
|
||||||
case giofont.Regular:
|
case giofont.Regular:
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
return metadata.StyleNormal
|
return fontapi.StyleNormal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func gioWeight(w metadata.Weight) giofont.Weight {
|
func gioWeight(w fontapi.Weight) giofont.Weight {
|
||||||
switch w {
|
switch w {
|
||||||
case metadata.WeightThin:
|
case fontapi.WeightThin:
|
||||||
return giofont.Thin
|
return giofont.Thin
|
||||||
case metadata.WeightExtraLight:
|
case fontapi.WeightExtraLight:
|
||||||
return giofont.ExtraLight
|
return giofont.ExtraLight
|
||||||
case metadata.WeightLight:
|
case fontapi.WeightLight:
|
||||||
return giofont.Light
|
return giofont.Light
|
||||||
case metadata.WeightNormal:
|
case fontapi.WeightNormal:
|
||||||
return giofont.Normal
|
return giofont.Normal
|
||||||
case metadata.WeightMedium:
|
case fontapi.WeightMedium:
|
||||||
return giofont.Medium
|
return giofont.Medium
|
||||||
case metadata.WeightSemibold:
|
case fontapi.WeightSemibold:
|
||||||
return giofont.SemiBold
|
return giofont.SemiBold
|
||||||
case metadata.WeightBold:
|
case fontapi.WeightBold:
|
||||||
return giofont.Bold
|
return giofont.Bold
|
||||||
case metadata.WeightExtraBold:
|
case fontapi.WeightExtraBold:
|
||||||
return giofont.ExtraBold
|
return giofont.ExtraBold
|
||||||
case metadata.WeightBlack:
|
case fontapi.WeightBlack:
|
||||||
return giofont.Black
|
return giofont.Black
|
||||||
default:
|
default:
|
||||||
return giofont.Normal
|
return giofont.Normal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mdWeight(g giofont.Weight) metadata.Weight {
|
func mdWeight(g giofont.Weight) fontapi.Weight {
|
||||||
switch g {
|
switch g {
|
||||||
case giofont.Thin:
|
case giofont.Thin:
|
||||||
return metadata.WeightThin
|
return fontapi.WeightThin
|
||||||
case giofont.ExtraLight:
|
case giofont.ExtraLight:
|
||||||
return metadata.WeightExtraLight
|
return fontapi.WeightExtraLight
|
||||||
case giofont.Light:
|
case giofont.Light:
|
||||||
return metadata.WeightLight
|
return fontapi.WeightLight
|
||||||
case giofont.Normal:
|
case giofont.Normal:
|
||||||
return metadata.WeightNormal
|
return fontapi.WeightNormal
|
||||||
case giofont.Medium:
|
case giofont.Medium:
|
||||||
return metadata.WeightMedium
|
return fontapi.WeightMedium
|
||||||
case giofont.SemiBold:
|
case giofont.SemiBold:
|
||||||
return metadata.WeightSemibold
|
return fontapi.WeightSemibold
|
||||||
case giofont.Bold:
|
case giofont.Bold:
|
||||||
return metadata.WeightBold
|
return fontapi.WeightBold
|
||||||
case giofont.ExtraBold:
|
case giofont.ExtraBold:
|
||||||
return metadata.WeightExtraBold
|
return fontapi.WeightExtraBold
|
||||||
case giofont.Black:
|
case giofont.Black:
|
||||||
return metadata.WeightBlack
|
return fontapi.WeightBlack
|
||||||
default:
|
default:
|
||||||
return metadata.WeightNormal
|
return fontapi.WeightNormal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+84
-66
@@ -18,6 +18,7 @@ import (
|
|||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/internal/fling"
|
"gioui.org/internal/fling"
|
||||||
"gioui.org/io/event"
|
"gioui.org/io/event"
|
||||||
|
"gioui.org/io/input"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
@@ -37,15 +38,19 @@ type Hover struct {
|
|||||||
|
|
||||||
// Add the gesture to detect hovering over the current pointer area.
|
// Add the gesture to detect hovering over the current pointer area.
|
||||||
func (h *Hover) Add(ops *op.Ops) {
|
func (h *Hover) Add(ops *op.Ops) {
|
||||||
pointer.InputOp{
|
event.Op(ops, h)
|
||||||
Tag: h,
|
|
||||||
Kinds: pointer.Enter | pointer.Leave,
|
|
||||||
}.Add(ops)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update state and report whether a pointer is inside the area.
|
// Update state and report whether a pointer is inside the area.
|
||||||
func (h *Hover) Update(q event.Queue) bool {
|
func (h *Hover) Update(q input.Source) bool {
|
||||||
for _, ev := range q.Events(h) {
|
for {
|
||||||
|
ev, ok := q.Event(pointer.Filter{
|
||||||
|
Target: h,
|
||||||
|
Kinds: pointer.Enter | pointer.Leave | pointer.Cancel,
|
||||||
|
})
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
e, ok := ev.(pointer.Event)
|
e, ok := ev.(pointer.Event)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
@@ -107,7 +112,6 @@ type Drag struct {
|
|||||||
pressed bool
|
pressed bool
|
||||||
pid pointer.ID
|
pid pointer.ID
|
||||||
start f32.Point
|
start f32.Point
|
||||||
grab bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scroll detects scroll gestures and reduces them to
|
// Scroll detects scroll gestures and reduces them to
|
||||||
@@ -115,11 +119,9 @@ type Drag struct {
|
|||||||
// movements as well as drag and fling touch gestures.
|
// movements as well as drag and fling touch gestures.
|
||||||
type Scroll struct {
|
type Scroll struct {
|
||||||
dragging bool
|
dragging bool
|
||||||
axis Axis
|
|
||||||
estimator fling.Extrapolation
|
estimator fling.Extrapolation
|
||||||
flinger fling.Animation
|
flinger fling.Animation
|
||||||
pid pointer.ID
|
pid pointer.ID
|
||||||
grab bool
|
|
||||||
last int
|
last int
|
||||||
// Leftover scroll.
|
// Leftover scroll.
|
||||||
scroll float32
|
scroll float32
|
||||||
@@ -161,10 +163,7 @@ const touchSlop = unit.Dp(3)
|
|||||||
|
|
||||||
// Add the handler to the operation list to receive click events.
|
// Add the handler to the operation list to receive click events.
|
||||||
func (c *Click) Add(ops *op.Ops) {
|
func (c *Click) Add(ops *op.Ops) {
|
||||||
pointer.InputOp{
|
event.Op(ops, c)
|
||||||
Tag: c,
|
|
||||||
Kinds: pointer.Press | pointer.Release | pointer.Enter | pointer.Leave,
|
|
||||||
}.Add(ops)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hovered returns whether a pointer is inside the area.
|
// Hovered returns whether a pointer is inside the area.
|
||||||
@@ -177,10 +176,16 @@ func (c *Click) Pressed() bool {
|
|||||||
return c.pressed
|
return c.pressed
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update state and return the click events.
|
// Update state and return the next click events, if any.
|
||||||
func (c *Click) Update(q event.Queue) []ClickEvent {
|
func (c *Click) Update(q input.Source) (ClickEvent, bool) {
|
||||||
var events []ClickEvent
|
for {
|
||||||
for _, evt := range q.Events(c) {
|
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)
|
e, ok := evt.(pointer.Event)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
@@ -192,9 +197,15 @@ func (c *Click) Update(q event.Queue) []ClickEvent {
|
|||||||
}
|
}
|
||||||
c.pressed = false
|
c.pressed = false
|
||||||
if !c.entered || c.hovered {
|
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 {
|
} else {
|
||||||
events = append(events, ClickEvent{Kind: KindCancel})
|
return ClickEvent{Kind: KindCancel}, true
|
||||||
}
|
}
|
||||||
case pointer.Cancel:
|
case pointer.Cancel:
|
||||||
wasPressed := c.pressed
|
wasPressed := c.pressed
|
||||||
@@ -202,7 +213,7 @@ func (c *Click) Update(q event.Queue) []ClickEvent {
|
|||||||
c.hovered = false
|
c.hovered = false
|
||||||
c.entered = false
|
c.entered = false
|
||||||
if wasPressed {
|
if wasPressed {
|
||||||
events = append(events, ClickEvent{Kind: KindCancel})
|
return ClickEvent{Kind: KindCancel}, true
|
||||||
}
|
}
|
||||||
case pointer.Press:
|
case pointer.Press:
|
||||||
if c.pressed {
|
if c.pressed {
|
||||||
@@ -224,7 +235,7 @@ func (c *Click) Update(q event.Queue) []ClickEvent {
|
|||||||
c.clicks = 1
|
c.clicks = 1
|
||||||
}
|
}
|
||||||
c.clickedAt = e.Time
|
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:
|
case pointer.Leave:
|
||||||
if !c.pressed {
|
if !c.pressed {
|
||||||
c.pid = e.PointerID
|
c.pid = e.PointerID
|
||||||
@@ -242,25 +253,16 @@ func (c *Click) Update(q event.Queue) []ClickEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return events
|
return ClickEvent{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ClickEvent) ImplementsEvent() {}
|
func (ClickEvent) ImplementsEvent() {}
|
||||||
|
|
||||||
// Add the handler to the operation list to receive scroll events.
|
// Add the handler to the operation list to receive scroll events.
|
||||||
// The bounds variable refers to the scrolling boundaries
|
// The bounds variable refers to the scrolling boundaries
|
||||||
// as defined in io/pointer.InputOp.
|
// as defined in [pointer.Filter].
|
||||||
func (s *Scroll) Add(ops *op.Ops, bounds image.Rectangle) {
|
func (s *Scroll) Add(ops *op.Ops) {
|
||||||
oph := pointer.InputOp{
|
event.Op(ops, s)
|
||||||
Tag: s,
|
|
||||||
Grab: s.grab,
|
|
||||||
Kinds: pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll,
|
|
||||||
ScrollBounds: bounds,
|
|
||||||
}
|
|
||||||
oph.Add(ops)
|
|
||||||
if s.flinger.Active() {
|
|
||||||
op.InvalidateOp{}.Add(ops)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop any remaining fling movement.
|
// Stop any remaining fling movement.
|
||||||
@@ -269,13 +271,19 @@ func (s *Scroll) Stop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update state and report the scroll distance along axis.
|
// Update state and report the scroll distance along axis.
|
||||||
func (s *Scroll) Update(cfg unit.Metric, q event.Queue, t time.Time, axis Axis) int {
|
func (s *Scroll) Update(cfg unit.Metric, q input.Source, t time.Time, axis Axis, scrollx, scrolly pointer.ScrollRange) int {
|
||||||
if s.axis != axis {
|
|
||||||
s.axis = axis
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
total := 0
|
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)
|
e, ok := evt.(pointer.Event)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
@@ -292,7 +300,7 @@ func (s *Scroll) Update(cfg unit.Metric, q event.Queue, t time.Time, axis Axis)
|
|||||||
}
|
}
|
||||||
s.Stop()
|
s.Stop()
|
||||||
s.estimator = fling.Extrapolation{}
|
s.estimator = fling.Extrapolation{}
|
||||||
v := s.val(e.Position)
|
v := s.val(axis, e.Position)
|
||||||
s.last = int(math.Round(float64(v)))
|
s.last = int(math.Round(float64(v)))
|
||||||
s.estimator.Sample(e.Time, v)
|
s.estimator.Sample(e.Time, v)
|
||||||
s.dragging = true
|
s.dragging = true
|
||||||
@@ -308,13 +316,14 @@ func (s *Scroll) Update(cfg unit.Metric, q event.Queue, t time.Time, axis Axis)
|
|||||||
fallthrough
|
fallthrough
|
||||||
case pointer.Cancel:
|
case pointer.Cancel:
|
||||||
s.dragging = false
|
s.dragging = false
|
||||||
s.grab = false
|
|
||||||
case pointer.Scroll:
|
case pointer.Scroll:
|
||||||
switch s.axis {
|
switch axis {
|
||||||
case Horizontal:
|
case Horizontal:
|
||||||
s.scroll += e.Scroll.X
|
s.scroll += e.Scroll.X
|
||||||
case Vertical:
|
case Vertical:
|
||||||
s.scroll += e.Scroll.Y
|
s.scroll += e.Scroll.Y
|
||||||
|
case Both:
|
||||||
|
s.scroll += e.Scroll.X + e.Scroll.Y
|
||||||
}
|
}
|
||||||
iscroll := int(s.scroll)
|
iscroll := int(s.scroll)
|
||||||
s.scroll -= float32(iscroll)
|
s.scroll -= float32(iscroll)
|
||||||
@@ -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 {
|
if !s.dragging || s.pid != e.PointerID {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
val := s.val(e.Position)
|
val := s.val(axis, e.Position)
|
||||||
s.estimator.Sample(e.Time, val)
|
s.estimator.Sample(e.Time, val)
|
||||||
v := int(math.Round(float64(val)))
|
v := int(math.Round(float64(val)))
|
||||||
dist := s.last - v
|
dist := s.last - v
|
||||||
if e.Priority < pointer.Grabbed {
|
if e.Priority < pointer.Grabbed {
|
||||||
slop := cfg.Dp(touchSlop)
|
slop := cfg.Dp(touchSlop)
|
||||||
if dist := dist; dist >= slop || -slop >= dist {
|
if dist := dist; dist >= slop || -slop >= dist {
|
||||||
s.grab = true
|
q.Execute(pointer.GrabCmd{Tag: s, ID: e.PointerID})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
s.last = v
|
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)
|
total += s.flinger.Tick(t)
|
||||||
|
if s.flinger.Active() {
|
||||||
|
q.Execute(op.InvalidateCmd{})
|
||||||
|
}
|
||||||
return total
|
return total
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Scroll) val(p f32.Point) float32 {
|
func (s *Scroll) val(axis Axis, p f32.Point) float32 {
|
||||||
if s.axis == Horizontal {
|
switch axis {
|
||||||
|
case Horizontal:
|
||||||
return p.X
|
return p.X
|
||||||
} else {
|
case Vertical:
|
||||||
return p.Y
|
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.
|
// Add the handler to the operation list to receive drag events.
|
||||||
func (d *Drag) Add(ops *op.Ops) {
|
func (d *Drag) Add(ops *op.Ops) {
|
||||||
pointer.InputOp{
|
event.Op(ops, d)
|
||||||
Tag: d,
|
|
||||||
Grab: d.grab,
|
|
||||||
Kinds: pointer.Press | pointer.Drag | pointer.Release,
|
|
||||||
}.Add(ops)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update state and return the drag events.
|
// Update state and return the next drag event, if any.
|
||||||
func (d *Drag) Update(cfg unit.Metric, q event.Queue, axis Axis) []pointer.Event {
|
func (d *Drag) Update(cfg unit.Metric, q input.Source, axis Axis) (pointer.Event, bool) {
|
||||||
var events []pointer.Event
|
for {
|
||||||
for _, e := range q.Events(d) {
|
ev, ok := q.Event(pointer.Filter{
|
||||||
e, ok := e.(pointer.Event)
|
Target: d,
|
||||||
|
Kinds: pointer.Press | pointer.Drag | pointer.Release | pointer.Cancel,
|
||||||
|
})
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
e, ok := ev.(pointer.Event)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
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)
|
diff := e.Position.Sub(d.start)
|
||||||
slop := cfg.Dp(touchSlop)
|
slop := cfg.Dp(touchSlop)
|
||||||
if diff.X*diff.X+diff.Y*diff.Y > float32(slop*slop) {
|
if diff.X*diff.X+diff.Y*diff.Y > float32(slop*slop) {
|
||||||
d.grab = true
|
q.Execute(pointer.GrabCmd{Tag: d, ID: e.PointerID})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case pointer.Release, pointer.Cancel:
|
case pointer.Release, pointer.Cancel:
|
||||||
@@ -417,13 +436,12 @@ func (d *Drag) Update(cfg unit.Metric, q event.Queue, axis Axis) []pointer.Event
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
d.dragging = false
|
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.
|
// Dragging reports whether it is currently in use.
|
||||||
@@ -446,13 +464,13 @@ func (a Axis) String() string {
|
|||||||
func (ct ClickKind) String() string {
|
func (ct ClickKind) String() string {
|
||||||
switch ct {
|
switch ct {
|
||||||
case KindPress:
|
case KindPress:
|
||||||
return "TypePress"
|
return "KindPress"
|
||||||
case KindClick:
|
case KindClick:
|
||||||
return "TypeClick"
|
return "KindClick"
|
||||||
case KindCancel:
|
case KindCancel:
|
||||||
return "TypeCancel"
|
return "KindCancel"
|
||||||
default:
|
default:
|
||||||
panic("invalid ClickType")
|
panic("invalid ClickKind")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+17
-17
@@ -9,8 +9,8 @@ import (
|
|||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/io/event"
|
"gioui.org/io/event"
|
||||||
|
"gioui.org/io/input"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/router"
|
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
"gioui.org/op/clip"
|
"gioui.org/op/clip"
|
||||||
)
|
)
|
||||||
@@ -22,20 +22,21 @@ func TestHover(t *testing.T) {
|
|||||||
stack := clip.Rect(rect).Push(ops)
|
stack := clip.Rect(rect).Push(ops)
|
||||||
h.Add(ops)
|
h.Add(ops)
|
||||||
stack.Pop()
|
stack.Pop()
|
||||||
r := new(router.Router)
|
r := new(input.Router)
|
||||||
|
h.Update(r.Source())
|
||||||
r.Frame(ops)
|
r.Frame(ops)
|
||||||
|
|
||||||
r.Queue(
|
r.Queue(
|
||||||
pointer.Event{Kind: pointer.Move, Position: f32.Pt(30, 30)},
|
pointer.Event{Kind: pointer.Move, Position: f32.Pt(30, 30)},
|
||||||
)
|
)
|
||||||
if !h.Update(r) {
|
if !h.Update(r.Source()) {
|
||||||
t.Fatal("expected hovered")
|
t.Fatal("expected hovered")
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Queue(
|
r.Queue(
|
||||||
pointer.Event{Kind: pointer.Move, Position: f32.Pt(50, 50)},
|
pointer.Event{Kind: pointer.Move, Position: f32.Pt(50, 50)},
|
||||||
)
|
)
|
||||||
if h.Update(r) {
|
if h.Update(r.Source()) {
|
||||||
t.Fatal("expected not hovered")
|
t.Fatal("expected not hovered")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,12 +72,21 @@ func TestMouseClicks(t *testing.T) {
|
|||||||
var ops op.Ops
|
var ops op.Ops
|
||||||
click.Add(&ops)
|
click.Add(&ops)
|
||||||
|
|
||||||
var r router.Router
|
var r input.Router
|
||||||
|
click.Update(r.Source())
|
||||||
r.Frame(&ops)
|
r.Frame(&ops)
|
||||||
r.Queue(tc.events...)
|
r.Queue(tc.events...)
|
||||||
|
|
||||||
events := click.Update(&r)
|
var clicks []ClickEvent
|
||||||
clicks := filterMouseClicks(events)
|
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 {
|
if got, want := len(clicks), len(tc.clicks); got != want {
|
||||||
t.Fatalf("got %d mouse clicks, expected %d", got, want)
|
t.Fatalf("got %d mouse clicks, expected %d", got, want)
|
||||||
}
|
}
|
||||||
@@ -106,13 +116,3 @@ func mouseClickEvents(times ...time.Duration) []event.Event {
|
|||||||
}
|
}
|
||||||
return events
|
return events
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterMouseClicks(events []ClickEvent) []ClickEvent {
|
|
||||||
var clicks []ClickEvent
|
|
||||||
for _, ev := range events {
|
|
||||||
if ev.Kind == KindClick {
|
|
||||||
clicks = append(clicks, ev)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return clicks
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
module gioui.org
|
module gioui.org
|
||||||
|
|
||||||
go 1.19
|
go 1.23.8
|
||||||
|
|
||||||
require (
|
require (
|
||||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d
|
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d
|
||||||
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2
|
|
||||||
gioui.org/shader v1.0.8
|
gioui.org/shader v1.0.8
|
||||||
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372
|
github.com/go-text/typesetting v0.3.0
|
||||||
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
|
||||||
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91
|
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0
|
||||||
golang.org/x/image v0.5.0
|
golang.org/x/image v0.26.0
|
||||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64
|
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 h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
|
||||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
|
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
|
||||||
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
||||||
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc=
|
|
||||||
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
|
||||||
gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA=
|
gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA=
|
||||||
gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
|
gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
|
||||||
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 h1:FQivqchis6bE2/9uF70M2gmmLpe82esEm2QadL0TEJo=
|
github.com/go-text/typesetting v0.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML4iF4=
|
||||||
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k=
|
github.com/go-text/typesetting v0.3.0/go.mod h1:qjZLkhRgOEYMhU9eHBr3AR4sfnGJvOXNLt8yRAySFuY=
|
||||||
github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI=
|
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
||||||
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 h1:sBdrWpxhGDdTAYNqbgBLAR+ULAPPhfgncLr1X0lyWtg=
|
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 h1:tMSqXTK+AQdW3LpCbfatHSRPHeW6+2WuxaVQuHftn80=
|
||||||
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8=
|
||||||
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 h1:ryT6Nf0R83ZgD8WnFFdfI8wCeyqgdXWN4+CkFVNPAT0=
|
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
|
||||||
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8=
|
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
|
||||||
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
|
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||||
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
|
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||||
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=
|
|
||||||
|
|||||||
+17
-13
@@ -4,12 +4,16 @@ package gpu
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"image"
|
||||||
"gioui.org/internal/f32"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type resourceCache struct {
|
type textureCacheKey struct {
|
||||||
res map[interface{}]resourceCacheValue
|
filter byte
|
||||||
|
handle any
|
||||||
|
}
|
||||||
|
|
||||||
|
type textureCache struct {
|
||||||
|
res map[textureCacheKey]resourceCacheValue
|
||||||
}
|
}
|
||||||
|
|
||||||
type resourceCacheValue struct {
|
type resourceCacheValue struct {
|
||||||
@@ -31,19 +35,19 @@ type opCache struct {
|
|||||||
type opCacheValue struct {
|
type opCacheValue struct {
|
||||||
data pathData
|
data pathData
|
||||||
|
|
||||||
bounds f32.Rectangle
|
bounds image.Rectangle
|
||||||
// the fields below are handled by opCache
|
// the fields below are handled by opCache
|
||||||
key opKey
|
key opKey
|
||||||
keep bool
|
keep bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newResourceCache() *resourceCache {
|
func newTextureCache() *textureCache {
|
||||||
return &resourceCache{
|
return &textureCache{
|
||||||
res: make(map[interface{}]resourceCacheValue),
|
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]
|
v, exists := r.res[key]
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, false
|
return nil, false
|
||||||
@@ -55,17 +59,17 @@ func (r *resourceCache) get(key interface{}) (resource, bool) {
|
|||||||
return v.resource, exists
|
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]
|
v, exists := r.res[key]
|
||||||
if exists && v.used {
|
if exists && v.used {
|
||||||
panic(fmt.Errorf("key exists, %p", key))
|
panic(fmt.Errorf("key exists, %v", key))
|
||||||
}
|
}
|
||||||
v.used = true
|
v.used = true
|
||||||
v.resource = val
|
v.resource = val
|
||||||
r.res[key] = v
|
r.res[key] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *resourceCache) frame() {
|
func (r *textureCache) frame() {
|
||||||
for k, v := range r.res {
|
for k, v := range r.res {
|
||||||
if v.used {
|
if v.used {
|
||||||
v.used = false
|
v.used = false
|
||||||
@@ -77,7 +81,7 @@ func (r *resourceCache) frame() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *resourceCache) release() {
|
func (r *textureCache) release() {
|
||||||
for _, v := range r.res {
|
for _, v := range r.res {
|
||||||
v.resource.release()
|
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) {
|
func encodeQuadTo(data []byte, meta uint32, from, ctrl, to f32.Point) {
|
||||||
// inlined code:
|
// inlined code:
|
||||||
// encodeVertex(data, meta, -1, 1, from, ctrl, to)
|
// encodeVertex(data, meta, 1, -1, from, ctrl, to)
|
||||||
// encodeVertex(data[vertStride:], meta, 1, 1, from, ctrl, to)
|
// encodeVertex(data[vertStride:], meta, 1, 1, from, ctrl, to)
|
||||||
// encodeVertex(data[vertStride*2:], meta, -1, -1, from, ctrl, to)
|
// encodeVertex(data[vertStride*2:], meta, -1, -1, from, ctrl, to)
|
||||||
// encodeVertex(data[vertStride*3:], meta, 1, -1, from, ctrl, to)
|
// encodeVertex(data[vertStride*3:], meta, -1, 1, from, ctrl, to)
|
||||||
// this code needs to stay in sync with `vertex.encode`.
|
// this code needs to stay in sync with `vertex.encode`.
|
||||||
|
|
||||||
bo := binary.LittleEndian
|
bo := binary.LittleEndian
|
||||||
@@ -48,10 +48,10 @@ func encodeQuadTo(data []byte, meta uint32, from, ctrl, to f32.Point) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
nwCorner = 1*0.25 + 0*0.5
|
nwCorner = 1*0.5 + 0*0.25
|
||||||
neCorner = 1*0.25 + 1*0.5
|
neCorner = 1*0.5 + 1*0.25
|
||||||
swCorner = 0*0.25 + 0*0.5
|
swCorner = 0*0.5 + 0*0.25
|
||||||
seCorner = 0*0.25 + 1*0.5
|
seCorner = 0*0.5 + 1*0.25
|
||||||
)
|
)
|
||||||
|
|
||||||
func encodeVertex(data []byte, meta uint32, cornerx, cornery int16, from, ctrl, to f32.Point) {
|
func encodeVertex(data []byte, meta uint32, cornerx, cornery int16, from, ctrl, to f32.Point) {
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
func BenchmarkEncodeQuadTo(b *testing.B) {
|
func BenchmarkEncodeQuadTo(b *testing.B) {
|
||||||
var data [vertStride * 4]byte
|
var data [vertStride * 4]byte
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; b.Loop(); i++ {
|
||||||
v := float32(i)
|
v := float32(i)
|
||||||
encodeQuadTo(data[:], 123,
|
encodeQuadTo(data[:], 123,
|
||||||
f32.Point{X: v, Y: v},
|
f32.Point{X: v, Y: v},
|
||||||
|
|||||||
-2202
File diff suppressed because it is too large
Load Diff
-129
@@ -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 (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
@@ -44,14 +45,10 @@ type GPU interface {
|
|||||||
Clear(color color.NRGBA)
|
Clear(color color.NRGBA)
|
||||||
// Frame draws the graphics operations from op into a viewport of target.
|
// Frame draws the graphics operations from op into a viewport of target.
|
||||||
Frame(frame *op.Ops, target RenderTarget, viewport image.Point) error
|
Frame(frame *op.Ops, target RenderTarget, viewport image.Point) error
|
||||||
// Profile returns the last available profiling information. Profiling
|
|
||||||
// information is requested when Frame sees an io/profile.Op, and the result
|
|
||||||
// is available through Profile at some later time.
|
|
||||||
Profile() string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type gpu struct {
|
type gpu struct {
|
||||||
cache *resourceCache
|
cache *textureCache
|
||||||
|
|
||||||
profile string
|
profile string
|
||||||
timers *timers
|
timers *timers
|
||||||
@@ -73,7 +70,6 @@ type renderer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type drawOps struct {
|
type drawOps struct {
|
||||||
profile bool
|
|
||||||
reader ops.Reader
|
reader ops.Reader
|
||||||
states []f32.Affine2D
|
states []f32.Affine2D
|
||||||
transStack []f32.Affine2D
|
transStack []f32.Affine2D
|
||||||
@@ -122,17 +118,17 @@ type drawState struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type pathOp struct {
|
type pathOp struct {
|
||||||
off f32.Point
|
off image.Point
|
||||||
// rect tracks whether the clip stack can be represented by a
|
// rect tracks whether the clip stack can be represented by a
|
||||||
// pixel-aligned rectangle.
|
// pixel-aligned rectangle.
|
||||||
rect bool
|
rect bool
|
||||||
// clip is the union of all
|
// clip is the union of all
|
||||||
// later clip rectangles.
|
// later clip rectangles.
|
||||||
clip image.Rectangle
|
clip image.Rectangle
|
||||||
bounds f32.Rectangle
|
bounds image.Rectangle
|
||||||
// intersect is the intersection of bounds and all
|
// intersect is the intersection of bounds and all
|
||||||
// previous clip bounds.
|
// previous clip bounds.
|
||||||
intersect f32.Rectangle
|
intersect image.Rectangle
|
||||||
pathKey opKey
|
pathKey opKey
|
||||||
path bool
|
path bool
|
||||||
pathVerts []byte
|
pathVerts []byte
|
||||||
@@ -194,7 +190,7 @@ const (
|
|||||||
// imageOpData is the shadow of paint.ImageOp.
|
// imageOpData is the shadow of paint.ImageOp.
|
||||||
type imageOpData struct {
|
type imageOpData struct {
|
||||||
src *image.RGBA
|
src *image.RGBA
|
||||||
handle interface{}
|
handle any
|
||||||
filter byte
|
filter byte
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,7 +201,7 @@ type linearGradientOpData struct {
|
|||||||
color2 color.NRGBA
|
color2 color.NRGBA
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeImageOp(data []byte, refs []interface{}) imageOpData {
|
func decodeImageOp(data []byte, refs []any) imageOpData {
|
||||||
handle := refs[1]
|
handle := refs[1]
|
||||||
if handle == nil {
|
if handle == nil {
|
||||||
return imageOpData{}
|
return imageOpData{}
|
||||||
@@ -266,7 +262,7 @@ type texture struct {
|
|||||||
type blitter struct {
|
type blitter struct {
|
||||||
ctx driver.Device
|
ctx driver.Device
|
||||||
viewport image.Point
|
viewport image.Point
|
||||||
pipelines [3]*pipeline
|
pipelines [2][3]*pipeline
|
||||||
colUniforms *blitColUniforms
|
colUniforms *blitColUniforms
|
||||||
texUniforms *blitTexUniforms
|
texUniforms *blitTexUniforms
|
||||||
linearGradientUniforms *blitLinearGradientUniforms
|
linearGradientUniforms *blitLinearGradientUniforms
|
||||||
@@ -348,18 +344,17 @@ func New(api API) (GPU, error) {
|
|||||||
func NewWithDevice(d driver.Device) (GPU, error) {
|
func NewWithDevice(d driver.Device) (GPU, error) {
|
||||||
d.BeginFrame(nil, false, image.Point{})
|
d.BeginFrame(nil, false, image.Point{})
|
||||||
defer d.EndFrame()
|
defer d.EndFrame()
|
||||||
forceCompute := os.Getenv("GIORENDERER") == "forcecompute"
|
|
||||||
feats := d.Caps().Features
|
feats := d.Caps().Features
|
||||||
switch {
|
switch {
|
||||||
case !forceCompute && feats.Has(driver.FeatureFloatRenderTargets) && feats.Has(driver.FeatureSRGB):
|
case feats.Has(driver.FeatureFloatRenderTargets) && feats.Has(driver.FeatureSRGB):
|
||||||
return newGPU(d)
|
return newGPU(d)
|
||||||
}
|
}
|
||||||
return newCompute(d)
|
return nil, errors.New("no available GPU driver")
|
||||||
}
|
}
|
||||||
|
|
||||||
func newGPU(ctx driver.Device) (*gpu, error) {
|
func newGPU(ctx driver.Device) (*gpu, error) {
|
||||||
g := &gpu{
|
g := &gpu{
|
||||||
cache: newResourceCache(),
|
cache: newTextureCache(),
|
||||||
}
|
}
|
||||||
g.drawOps.pathCache = newOpCache()
|
g.drawOps.pathCache = newOpCache()
|
||||||
if err := g.init(ctx); err != nil {
|
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.renderer.pather.viewport = viewport
|
||||||
g.drawOps.reset(viewport)
|
g.drawOps.reset(viewport)
|
||||||
g.drawOps.collect(frameOps, 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.frameStart = time.Now()
|
||||||
g.timers = newTimers(g.ctx)
|
g.timers = newTimers(g.ctx)
|
||||||
g.stencilTimer = g.timers.newTimer()
|
g.stencilTimer = g.timers.newTimer()
|
||||||
@@ -425,9 +420,9 @@ func (g *gpu) frame(target RenderTarget) error {
|
|||||||
g.stencilTimer.end()
|
g.stencilTimer.end()
|
||||||
g.coverTimer.begin()
|
g.coverTimer.begin()
|
||||||
g.renderer.uploadImages(g.cache, g.drawOps.imageOps)
|
g.renderer.uploadImages(g.cache, g.drawOps.imageOps)
|
||||||
g.renderer.prepareDrawOps(g.cache, g.drawOps.imageOps)
|
g.renderer.prepareDrawOps(g.drawOps.imageOps)
|
||||||
g.drawOps.layers = g.renderer.packLayers(g.drawOps.layers)
|
g.drawOps.layers = g.renderer.packLayers(g.drawOps.layers)
|
||||||
g.renderer.drawLayers(g.cache, g.drawOps.layers, g.drawOps.imageOps)
|
g.renderer.drawLayers(g.drawOps.layers, g.drawOps.imageOps)
|
||||||
d := driver.LoadDesc{
|
d := driver.LoadDesc{
|
||||||
ClearColor: g.drawOps.clearColor,
|
ClearColor: g.drawOps.clearColor,
|
||||||
}
|
}
|
||||||
@@ -437,14 +432,14 @@ func (g *gpu) frame(target RenderTarget) error {
|
|||||||
}
|
}
|
||||||
g.ctx.BeginRenderPass(defFBO, d)
|
g.ctx.BeginRenderPass(defFBO, d)
|
||||||
g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
|
g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
|
||||||
g.renderer.drawOps(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.coverTimer.end()
|
||||||
g.ctx.EndRenderPass()
|
g.ctx.EndRenderPass()
|
||||||
g.cleanupTimer.begin()
|
g.cleanupTimer.begin()
|
||||||
g.cache.frame()
|
g.cache.frame()
|
||||||
g.drawOps.pathCache.frame()
|
g.drawOps.pathCache.frame()
|
||||||
g.cleanupTimer.end()
|
g.cleanupTimer.end()
|
||||||
if g.drawOps.profile && g.timers.ready() {
|
if false && g.timers.ready() {
|
||||||
st, covt, cleant := g.stencilTimer.Elapsed, g.coverTimer.Elapsed, g.cleanupTimer.Elapsed
|
st, covt, cleant := g.stencilTimer.Elapsed, g.coverTimer.Elapsed, g.cleanupTimer.Elapsed
|
||||||
ft := st + covt + cleant
|
ft := st + covt + cleant
|
||||||
q := 100 * time.Microsecond
|
q := 100 * time.Microsecond
|
||||||
@@ -460,12 +455,8 @@ func (g *gpu) Profile() string {
|
|||||||
return g.profile
|
return g.profile
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *renderer) texHandle(cache *resourceCache, data imageOpData) driver.Texture {
|
func (r *renderer) texHandle(cache *textureCache, data imageOpData) driver.Texture {
|
||||||
type cachekey struct {
|
key := textureCacheKey{
|
||||||
filter byte
|
|
||||||
handle any
|
|
||||||
}
|
|
||||||
key := cachekey{
|
|
||||||
filter: data.filter,
|
filter: data.filter,
|
||||||
handle: data.handle,
|
handle: data.handle,
|
||||||
}
|
}
|
||||||
@@ -557,7 +548,7 @@ func newBlitter(ctx driver.Device) *blitter {
|
|||||||
b.texUniforms = new(blitTexUniforms)
|
b.texUniforms = new(blitTexUniforms)
|
||||||
b.linearGradientUniforms = new(blitLinearGradientUniforms)
|
b.linearGradientUniforms = new(blitLinearGradientUniforms)
|
||||||
pipelines, err := createColorPrograms(ctx, gio.Shader_blit_vert, gio.Shader_blit_frag,
|
pipelines, err := createColorPrograms(ctx, gio.Shader_blit_vert, gio.Shader_blit_frag,
|
||||||
[3]interface{}{b.colUniforms, b.linearGradientUniforms, b.texUniforms},
|
[3]any{b.colUniforms, b.linearGradientUniforms, b.texUniforms},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -569,12 +560,24 @@ func newBlitter(ctx driver.Device) *blitter {
|
|||||||
func (b *blitter) release() {
|
func (b *blitter) release() {
|
||||||
b.quadVerts.Release()
|
b.quadVerts.Release()
|
||||||
for _, p := range b.pipelines {
|
for _, p := range b.pipelines {
|
||||||
p.Release()
|
for _, p := range p {
|
||||||
|
p.Release()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.Sources, uniforms [3]interface{}) ([3]*pipeline, error) {
|
func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.Sources, uniforms [3]any) (pipelines [2][3]*pipeline, err error) {
|
||||||
var pipelines [3]*pipeline
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
for _, p := range pipelines {
|
||||||
|
for _, p := range p {
|
||||||
|
if p != nil {
|
||||||
|
p.Release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
blend := driver.BlendDesc{
|
blend := driver.BlendDesc{
|
||||||
Enable: true,
|
Enable: true,
|
||||||
SrcFactor: driver.BlendFactorOne,
|
SrcFactor: driver.BlendFactorOne,
|
||||||
@@ -592,86 +595,76 @@ func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.
|
|||||||
return pipelines, err
|
return pipelines, err
|
||||||
}
|
}
|
||||||
defer vsh.Release()
|
defer vsh.Release()
|
||||||
{
|
for i, format := range []driver.TextureFormat{driver.TextureFormatOutput, driver.TextureFormatSRGBA} {
|
||||||
fsh, err := b.NewFragmentShader(fsSrc[materialTexture])
|
{
|
||||||
if err != nil {
|
fsh, err := b.NewFragmentShader(fsSrc[materialTexture])
|
||||||
return pipelines, err
|
if err != nil {
|
||||||
|
return pipelines, err
|
||||||
|
}
|
||||||
|
defer fsh.Release()
|
||||||
|
pipe, err := b.NewPipeline(driver.PipelineDesc{
|
||||||
|
VertexShader: vsh,
|
||||||
|
FragmentShader: fsh,
|
||||||
|
BlendDesc: blend,
|
||||||
|
VertexLayout: layout,
|
||||||
|
PixelFormat: format,
|
||||||
|
Topology: driver.TopologyTriangleStrip,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return pipelines, err
|
||||||
|
}
|
||||||
|
var vertBuffer *uniformBuffer
|
||||||
|
if u := uniforms[materialTexture]; u != nil {
|
||||||
|
vertBuffer = newUniformBuffer(b, u)
|
||||||
|
}
|
||||||
|
pipelines[i][materialTexture] = &pipeline{pipe, vertBuffer}
|
||||||
}
|
}
|
||||||
defer fsh.Release()
|
{
|
||||||
pipe, err := b.NewPipeline(driver.PipelineDesc{
|
var vertBuffer *uniformBuffer
|
||||||
VertexShader: vsh,
|
fsh, err := b.NewFragmentShader(fsSrc[materialColor])
|
||||||
FragmentShader: fsh,
|
if err != nil {
|
||||||
BlendDesc: blend,
|
return pipelines, err
|
||||||
VertexLayout: layout,
|
}
|
||||||
PixelFormat: driver.TextureFormatOutput,
|
defer fsh.Release()
|
||||||
Topology: driver.TopologyTriangleStrip,
|
pipe, err := b.NewPipeline(driver.PipelineDesc{
|
||||||
})
|
VertexShader: vsh,
|
||||||
if err != nil {
|
FragmentShader: fsh,
|
||||||
return pipelines, err
|
BlendDesc: blend,
|
||||||
|
VertexLayout: layout,
|
||||||
|
PixelFormat: format,
|
||||||
|
Topology: driver.TopologyTriangleStrip,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return pipelines, err
|
||||||
|
}
|
||||||
|
if u := uniforms[materialColor]; u != nil {
|
||||||
|
vertBuffer = newUniformBuffer(b, u)
|
||||||
|
}
|
||||||
|
pipelines[i][materialColor] = &pipeline{pipe, vertBuffer}
|
||||||
}
|
}
|
||||||
var vertBuffer *uniformBuffer
|
{
|
||||||
if u := uniforms[materialTexture]; u != nil {
|
var vertBuffer *uniformBuffer
|
||||||
vertBuffer = newUniformBuffer(b, u)
|
fsh, err := b.NewFragmentShader(fsSrc[materialLinearGradient])
|
||||||
|
if err != nil {
|
||||||
|
return pipelines, err
|
||||||
|
}
|
||||||
|
defer fsh.Release()
|
||||||
|
pipe, err := b.NewPipeline(driver.PipelineDesc{
|
||||||
|
VertexShader: vsh,
|
||||||
|
FragmentShader: fsh,
|
||||||
|
BlendDesc: blend,
|
||||||
|
VertexLayout: layout,
|
||||||
|
PixelFormat: format,
|
||||||
|
Topology: driver.TopologyTriangleStrip,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return pipelines, err
|
||||||
|
}
|
||||||
|
if u := uniforms[materialLinearGradient]; u != nil {
|
||||||
|
vertBuffer = newUniformBuffer(b, u)
|
||||||
|
}
|
||||||
|
pipelines[i][materialLinearGradient] = &pipeline{pipe, vertBuffer}
|
||||||
}
|
}
|
||||||
pipelines[materialTexture] = &pipeline{pipe, vertBuffer}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
var vertBuffer *uniformBuffer
|
|
||||||
fsh, err := b.NewFragmentShader(fsSrc[materialColor])
|
|
||||||
if err != nil {
|
|
||||||
pipelines[materialTexture].Release()
|
|
||||||
return pipelines, err
|
|
||||||
}
|
|
||||||
defer fsh.Release()
|
|
||||||
pipe, err := b.NewPipeline(driver.PipelineDesc{
|
|
||||||
VertexShader: vsh,
|
|
||||||
FragmentShader: fsh,
|
|
||||||
BlendDesc: blend,
|
|
||||||
VertexLayout: layout,
|
|
||||||
PixelFormat: driver.TextureFormatOutput,
|
|
||||||
Topology: driver.TopologyTriangleStrip,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
pipelines[materialTexture].Release()
|
|
||||||
return pipelines, err
|
|
||||||
}
|
|
||||||
if u := uniforms[materialColor]; u != nil {
|
|
||||||
vertBuffer = newUniformBuffer(b, u)
|
|
||||||
}
|
|
||||||
pipelines[materialColor] = &pipeline{pipe, vertBuffer}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
var vertBuffer *uniformBuffer
|
|
||||||
fsh, err := b.NewFragmentShader(fsSrc[materialLinearGradient])
|
|
||||||
if err != nil {
|
|
||||||
pipelines[materialTexture].Release()
|
|
||||||
pipelines[materialColor].Release()
|
|
||||||
return pipelines, err
|
|
||||||
}
|
|
||||||
defer fsh.Release()
|
|
||||||
pipe, err := b.NewPipeline(driver.PipelineDesc{
|
|
||||||
VertexShader: vsh,
|
|
||||||
FragmentShader: fsh,
|
|
||||||
BlendDesc: blend,
|
|
||||||
VertexLayout: layout,
|
|
||||||
PixelFormat: driver.TextureFormatOutput,
|
|
||||||
Topology: driver.TopologyTriangleStrip,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
pipelines[materialTexture].Release()
|
|
||||||
pipelines[materialColor].Release()
|
|
||||||
return pipelines, err
|
|
||||||
}
|
|
||||||
if u := uniforms[materialLinearGradient]; u != nil {
|
|
||||||
vertBuffer = newUniformBuffer(b, u)
|
|
||||||
}
|
|
||||||
pipelines[materialLinearGradient] = &pipeline{pipe, vertBuffer}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
for _, p := range pipelines {
|
|
||||||
p.Release()
|
|
||||||
}
|
|
||||||
return pipelines, err
|
|
||||||
}
|
}
|
||||||
return pipelines, nil
|
return pipelines, nil
|
||||||
}
|
}
|
||||||
@@ -830,7 +823,7 @@ func (r *renderer) packLayers(layers []opacityLayer) []opacityLayer {
|
|||||||
layers[l.parent].clip = b.Union(l.clip)
|
layers[l.parent].clip = b.Union(l.clip)
|
||||||
}
|
}
|
||||||
if l.clip.Empty() {
|
if l.clip.Empty() {
|
||||||
layers = append(layers[:i], layers[i+1:]...)
|
layers = slices.Delete(layers, i, i+1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Pack layers.
|
// Pack layers.
|
||||||
@@ -853,7 +846,7 @@ func (r *renderer) packLayers(layers []opacityLayer) []opacityLayer {
|
|||||||
return layers
|
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 {
|
if len(r.layers.sizes) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -874,12 +867,12 @@ func (r *renderer) drawLayers(cache *resourceCache, layers []opacityLayer, ops [
|
|||||||
Min: l.place.Pos,
|
Min: l.place.Pos,
|
||||||
Max: l.place.Pos.Add(l.clip.Size()),
|
Max: l.place.Pos.Add(l.clip.Size()),
|
||||||
}
|
}
|
||||||
r.ctx.Viewport(v.Min.X, v.Min.Y, v.Max.X, v.Max.Y)
|
r.ctx.Viewport(v.Min.X, v.Min.Y, v.Dx(), v.Dy())
|
||||||
f := r.layerFBOs.fbos[fbo]
|
f := r.layerFBOs.fbos[fbo]
|
||||||
r.drawOps(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)
|
sr := f32.FRect(v)
|
||||||
uvScale, uvOffset := texSpaceTransform(sr, f.size)
|
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.
|
// Replace layer ops with one textured op.
|
||||||
ops[l.opStart] = imageOp{
|
ops[l.opStart] = imageOp{
|
||||||
clip: l.clip,
|
clip: l.clip,
|
||||||
@@ -899,7 +892,6 @@ func (r *renderer) drawLayers(cache *resourceCache, layers []opacityLayer, ops [
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *drawOps) reset(viewport image.Point) {
|
func (d *drawOps) reset(viewport image.Point) {
|
||||||
d.profile = false
|
|
||||||
d.viewport = viewport
|
d.viewport = viewport
|
||||||
d.imageOps = d.imageOps[:0]
|
d.imageOps = d.imageOps[:0]
|
||||||
d.pathOps = d.pathOps[:0]
|
d.pathOps = d.pathOps[:0]
|
||||||
@@ -910,16 +902,14 @@ func (d *drawOps) reset(viewport image.Point) {
|
|||||||
d.opacityStack = d.opacityStack[:0]
|
d.opacityStack = d.opacityStack[:0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *drawOps) collect(root *op.Ops, viewport image.Point) {
|
func (d *drawOps) collect(root *op.Ops, viewportSize image.Point) {
|
||||||
viewf := f32.Rectangle{
|
viewport := image.Rectangle{Max: viewportSize}
|
||||||
Max: f32.Point{X: float32(viewport.X), Y: float32(viewport.Y)},
|
|
||||||
}
|
|
||||||
var ops *ops.Ops
|
var ops *ops.Ops
|
||||||
if root != nil {
|
if root != nil {
|
||||||
ops = &root.Internal
|
ops = &root.Internal
|
||||||
}
|
}
|
||||||
d.reader.Reset(ops)
|
d.reader.Reset(ops)
|
||||||
d.collectOps(&d.reader, viewf)
|
d.collectOps(&d.reader, viewport)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *drawOps) buildPaths(ctx driver.Device) {
|
func (d *drawOps) buildPaths(ctx driver.Device) {
|
||||||
@@ -940,7 +930,7 @@ func (d *drawOps) newPathOp() *pathOp {
|
|||||||
return &d.pathOpCache[len(d.pathOpCache)-1]
|
return &d.pathOpCache[len(d.pathOpCache)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey opKey, bounds f32.Rectangle, off f32.Point, push bool) {
|
func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey opKey, bounds image.Rectangle, off image.Point) {
|
||||||
npath := d.newPathOp()
|
npath := d.newPathOp()
|
||||||
*npath = pathOp{
|
*npath = pathOp{
|
||||||
parent: state.cpath,
|
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) {
|
func (d *drawOps) save(id int, state f32.Affine2D) {
|
||||||
if extra := id - len(d.states) + 1; extra > 0 {
|
if extra := id - len(d.states) + 1; extra > 0 {
|
||||||
d.states = append(d.states, make([]f32.Affine2D, extra)...)
|
for range extra {
|
||||||
|
d.states = append(d.states, f32.AffineId())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
d.states[id] = state
|
d.states[id] = state
|
||||||
}
|
}
|
||||||
@@ -979,13 +971,14 @@ func (k opKey) SetTransform(t f32.Affine2D) opKey {
|
|||||||
return k
|
return k
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *drawOps) collectOps(r *ops.Reader, viewport f32.Rectangle) {
|
func (d *drawOps) collectOps(r *ops.Reader, viewport image.Rectangle) {
|
||||||
var (
|
var quads quadsOp
|
||||||
quads quadsOp
|
state := drawState{
|
||||||
state drawState
|
t: f32.AffineId(),
|
||||||
)
|
}
|
||||||
reset := func() {
|
reset := func() {
|
||||||
state = drawState{
|
state = drawState{
|
||||||
|
t: f32.AffineId(),
|
||||||
color: color.NRGBA{A: 0xff},
|
color: color.NRGBA{A: 0xff},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -993,8 +986,6 @@ func (d *drawOps) collectOps(r *ops.Reader, viewport f32.Rectangle) {
|
|||||||
loop:
|
loop:
|
||||||
for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
|
for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
|
||||||
switch ops.OpType(encOp.Data[0]) {
|
switch ops.OpType(encOp.Data[0]) {
|
||||||
case ops.TypeProfile:
|
|
||||||
d.profile = true
|
|
||||||
case ops.TypeTransform:
|
case ops.TypeTransform:
|
||||||
dop, push := ops.DecodeTransform(encOp.Data)
|
dop, push := ops.DecodeTransform(encOp.Data)
|
||||||
if push {
|
if push {
|
||||||
@@ -1042,8 +1033,8 @@ loop:
|
|||||||
var op ops.ClipOp
|
var op ops.ClipOp
|
||||||
op.Decode(encOp.Data)
|
op.Decode(encOp.Data)
|
||||||
quads.key.outline = op.Outline
|
quads.key.outline = op.Outline
|
||||||
bounds := f32.FRect(op.Bounds)
|
bounds := op.Bounds
|
||||||
trans, off := state.t.Split()
|
trans, off := transformOffset(state.t)
|
||||||
if len(quads.aux) > 0 {
|
if len(quads.aux) > 0 {
|
||||||
// There is a clipping path, build the gpu data and update the
|
// There is a clipping path, build the gpu data and update the
|
||||||
// cache key such that it will be equal only if the transform is the
|
// cache key such that it will be equal only if the transform is the
|
||||||
@@ -1054,11 +1045,11 @@ loop:
|
|||||||
// Why is this not used for the offset shapes?
|
// Why is this not used for the offset shapes?
|
||||||
bounds = v.bounds
|
bounds = v.bounds
|
||||||
} else {
|
} else {
|
||||||
var pathData []byte
|
newPathData, newBounds := d.buildVerts(
|
||||||
pathData, bounds = d.buildVerts(
|
|
||||||
quads.aux, trans, quads.key.outline, quads.key.strokeWidth,
|
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
|
// add it to the cache, without GPU data, so the transform can be
|
||||||
// reused.
|
// reused.
|
||||||
d.pathCache.put(quads.key, opCacheValue{bounds: bounds})
|
d.pathCache.put(quads.key, opCacheValue{bounds: bounds})
|
||||||
@@ -1066,8 +1057,9 @@ loop:
|
|||||||
} else {
|
} else {
|
||||||
quads.aux, bounds, _ = d.boundsForTransformedRect(bounds, trans)
|
quads.aux, bounds, _ = d.boundsForTransformedRect(bounds, trans)
|
||||||
quads.key = opKey{Key: encOp.Key}
|
quads.key = opKey{Key: encOp.Key}
|
||||||
|
quads.key = quads.key.SetTransform(trans)
|
||||||
}
|
}
|
||||||
d.addClipPath(&state, quads.aux, quads.key, bounds, off, true)
|
d.addClipPath(&state, quads.aux, quads.key, bounds, off)
|
||||||
quads = quadsOp{}
|
quads = quadsOp{}
|
||||||
case ops.TypePopClip:
|
case ops.TypePopClip:
|
||||||
state.cpath = state.cpath.parent
|
state.cpath = state.cpath.parent
|
||||||
@@ -1089,21 +1081,21 @@ loop:
|
|||||||
// Transform (if needed) the painting rectangle and if so generate a clip path,
|
// Transform (if needed) the painting rectangle and if so generate a clip path,
|
||||||
// for those cases also compute a partialTrans that maps texture coordinates between
|
// for those cases also compute a partialTrans that maps texture coordinates between
|
||||||
// the new bounding rectangle and the transformed original paint rectangle.
|
// the new bounding rectangle and the transformed original paint rectangle.
|
||||||
t, off := state.t.Split()
|
t, off := transformOffset(state.t)
|
||||||
// Fill the clip area, unless the material is a (bounded) image.
|
// Fill the clip area, unless the material is a (bounded) image.
|
||||||
// TODO: Find a tighter bound.
|
// TODO: Find a tighter bound.
|
||||||
inf := float32(1e6)
|
inf := int(1e6)
|
||||||
dst := f32.Rect(-inf, -inf, inf, inf)
|
dst := image.Rect(-inf, -inf, inf, inf)
|
||||||
if state.matType == materialTexture {
|
if state.matType == materialTexture {
|
||||||
sz := state.image.src.Rect.Size()
|
sz := state.image.src.Rect.Size()
|
||||||
dst = f32.Rectangle{Max: layout.FPt(sz)}
|
dst = image.Rectangle{Max: sz}
|
||||||
}
|
}
|
||||||
clipData, bnd, partialTrans := d.boundsForTransformedRect(dst, t)
|
clipData, bnd, partialTrans := d.boundsForTransformedRect(dst, t)
|
||||||
cl := viewport.Intersect(bnd.Add(off))
|
bounds := viewport.Intersect(bnd.Add(off))
|
||||||
if state.cpath != nil {
|
if state.cpath != nil {
|
||||||
cl = state.cpath.intersect.Intersect(cl)
|
bounds = state.cpath.intersect.Intersect(bounds)
|
||||||
}
|
}
|
||||||
if cl.Empty() {
|
if bounds.Empty() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1111,11 +1103,10 @@ loop:
|
|||||||
// The paint operation is sheared or rotated, add a clip path representing
|
// The paint operation is sheared or rotated, add a clip path representing
|
||||||
// this transformed rectangle.
|
// this transformed rectangle.
|
||||||
k := opKey{Key: encOp.Key}
|
k := opKey{Key: encOp.Key}
|
||||||
k.SetTransform(t) // TODO: This call has no effect.
|
k = k.SetTransform(t)
|
||||||
d.addClipPath(&state, clipData, k, bnd, off, false)
|
d.addClipPath(&state, clipData, k, bnd, off)
|
||||||
}
|
}
|
||||||
|
|
||||||
bounds := cl.Round()
|
|
||||||
mat := state.materialFor(bnd, off, partialTrans, bounds)
|
mat := state.materialFor(bnd, off, partialTrans, bounds)
|
||||||
|
|
||||||
rect := state.cpath == nil || state.cpath.rect
|
rect := state.cpath == nil || state.cpath.rect
|
||||||
@@ -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{
|
m := material{
|
||||||
opacity: 1.,
|
opacity: 1.,
|
||||||
|
uvTrans: f32.AffineId(),
|
||||||
}
|
}
|
||||||
switch d.matType {
|
switch d.matType {
|
||||||
case materialColor:
|
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))
|
m.uvTrans = partTrans.Mul(gradientSpaceTransform(clip, off, d.stop1, d.stop2))
|
||||||
case materialTexture:
|
case materialTexture:
|
||||||
m.material = materialTexture
|
m.material = materialTexture
|
||||||
dr := rect.Add(off).Round()
|
dr := rect.Add(off)
|
||||||
sz := d.image.src.Bounds().Size()
|
sz := d.image.src.Bounds().Size()
|
||||||
sr := f32.Rectangle{
|
sr := f32.Rectangle{
|
||||||
Max: f32.Point{
|
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.Min.Y += float32(clip.Min.Y-dr.Min.Y) * sdy / dy
|
||||||
sr.Max.Y -= float32(dr.Max.Y-clip.Max.Y) * sdy / dy
|
sr.Max.Y -= float32(dr.Max.Y-clip.Max.Y) * sdy / dy
|
||||||
uvScale, uvOffset := texSpaceTransform(sr, sz)
|
uvScale, uvOffset := texSpaceTransform(sr, sz)
|
||||||
m.uvTrans = partTrans.Mul(f32.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset))
|
m.uvTrans = partTrans.Mul(f32.AffineId().Scale(f32.Point{}, uvScale).Offset(uvOffset))
|
||||||
m.data = d.image
|
m.data = d.image
|
||||||
}
|
}
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *renderer) uploadImages(cache *resourceCache, ops []imageOp) {
|
func (r *renderer) uploadImages(cache *textureCache, ops []imageOp) {
|
||||||
for i := range ops {
|
for i := range ops {
|
||||||
img := &ops[i]
|
img := &ops[i]
|
||||||
m := img.material
|
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 {
|
for _, img := range ops {
|
||||||
m := img.material
|
m := img.material
|
||||||
switch m.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
|
var coverTex driver.Texture
|
||||||
for i := 0; i < len(ops); i++ {
|
for i := 0; i < len(ops); i++ {
|
||||||
img := 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)
|
scale, off := clipSpaceTransform(drc, viewport)
|
||||||
var fbo FBO
|
var fbo FBO
|
||||||
|
fboIdx := 0
|
||||||
|
if isFBO {
|
||||||
|
fboIdx = 1
|
||||||
|
}
|
||||||
switch img.clipType {
|
switch img.clipType {
|
||||||
case clipTypeNone:
|
case clipTypeNone:
|
||||||
p := r.blitter.pipelines[m.material]
|
p := r.blitter.pipelines[fboIdx][m.material]
|
||||||
r.ctx.BindPipeline(p.pipeline)
|
r.ctx.BindPipeline(p.pipeline)
|
||||||
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
|
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
|
||||||
r.blitter.blit(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.opacity, m.uvTrans)
|
r.blitter.blit(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.opacity, m.uvTrans)
|
||||||
@@ -1277,7 +1273,7 @@ func (r *renderer) drawOps(cache *resourceCache, isFBO bool, opOff image.Point,
|
|||||||
Max: img.place.Pos.Add(drc.Size()),
|
Max: img.place.Pos.Add(drc.Size()),
|
||||||
}
|
}
|
||||||
coverScale, coverOff := texSpaceTransform(f32.FRect(uv), fbo.size)
|
coverScale, coverOff := texSpaceTransform(f32.FRect(uv), fbo.size)
|
||||||
p := r.pather.coverer.pipelines[m.material]
|
p := r.pather.coverer.pipelines[fboIdx][m.material]
|
||||||
r.ctx.BindPipeline(p.pipeline)
|
r.ctx.BindPipeline(p.pipeline)
|
||||||
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
|
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
|
||||||
r.pather.cover(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.uvTrans, coverScale, coverOff)
|
r.pather.cover(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.uvTrans, coverScale, coverOff)
|
||||||
@@ -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) {
|
func (b *blitter) blit(mat materialType, fbo bool, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, opacity float32, uvTrans f32.Affine2D) {
|
||||||
p := b.pipelines[mat]
|
fboIdx := 0
|
||||||
|
if fbo {
|
||||||
|
fboIdx = 1
|
||||||
|
}
|
||||||
|
p := b.pipelines[fboIdx][mat]
|
||||||
b.ctx.BindPipeline(p.pipeline)
|
b.ctx.BindPipeline(p.pipeline)
|
||||||
var uniforms *blitUniforms
|
var uniforms *blitUniforms
|
||||||
switch mat {
|
switch mat {
|
||||||
@@ -1318,7 +1318,7 @@ func (b *blitter) blit(mat materialType, fbo bool, col f32color.RGBA, col1, col2
|
|||||||
|
|
||||||
// newUniformBuffer creates a new GPU uniform buffer backed by the
|
// newUniformBuffer creates a new GPU uniform buffer backed by the
|
||||||
// structure uniformBlock points to.
|
// structure uniformBlock points to.
|
||||||
func newUniformBuffer(b driver.Device, uniformBlock interface{}) *uniformBuffer {
|
func newUniformBuffer(b driver.Device, uniformBlock any) *uniformBuffer {
|
||||||
ref := reflect.ValueOf(uniformBlock)
|
ref := reflect.ValueOf(uniformBlock)
|
||||||
// Determine the size of the uniforms structure, *uniforms.
|
// Determine the size of the uniforms structure, *uniforms.
|
||||||
size := ref.Elem().Type().Size()
|
size := ref.Elem().Type().Size()
|
||||||
@@ -1365,19 +1365,19 @@ func texSpaceTransform(r f32.Rectangle, bounds image.Point) (f32.Point, f32.Poin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// gradientSpaceTransform transforms stop1 and stop2 to [(0,0), (1,1)].
|
// gradientSpaceTransform transforms stop1 and stop2 to [(0,0), (1,1)].
|
||||||
func gradientSpaceTransform(clip image.Rectangle, off 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)
|
d := stop2.Sub(stop1)
|
||||||
l := float32(math.Sqrt(float64(d.X*d.X + d.Y*d.Y)))
|
l := float32(math.Sqrt(float64(d.X*d.X + d.Y*d.Y)))
|
||||||
a := float32(math.Atan2(float64(-d.Y), float64(d.X)))
|
a := float32(math.Atan2(float64(-d.Y), float64(d.X)))
|
||||||
|
|
||||||
// TODO: optimize
|
// TODO: optimize
|
||||||
zp := f32.Point{}
|
zp := f32.Point{}
|
||||||
return f32.Affine2D{}.
|
return f32.AffineId().
|
||||||
Scale(zp, layout.FPt(clip.Size())). // scale to pixel space
|
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(f32.FPt(off)).Add(layout.FPt(clip.Min))). // offset to clip space
|
||||||
Offset(zp.Sub(stop1)). // offset to first stop point
|
Offset(zp.Sub(stop1)). // offset to first stop point
|
||||||
Rotate(zp, a). // rotate to align gradient
|
Rotate(zp, a). // rotate to align gradient
|
||||||
Scale(zp, f32.Pt(1/l, 1/l)) // scale gradient to right size
|
Scale(zp, f32.Pt(1/l, 1/l)) // scale gradient to right size
|
||||||
}
|
}
|
||||||
|
|
||||||
// clipSpaceTransform returns the scale and offset that transforms the given
|
// clipSpaceTransform returns the scale and offset that transforms the given
|
||||||
@@ -1486,7 +1486,7 @@ func (d *drawOps) buildVerts(pathData []byte, tr f32.Affine2D, outline bool, str
|
|||||||
// as needed and feeds them to the supplied splitter.
|
// as needed and feeds them to the supplied splitter.
|
||||||
func decodeToOutlineQuads(qs *quadSplitter, tr f32.Affine2D, pathData []byte) {
|
func decodeToOutlineQuads(qs *quadSplitter, tr f32.Affine2D, pathData []byte) {
|
||||||
for len(pathData) >= scene.CommandSize+4 {
|
for len(pathData) >= scene.CommandSize+4 {
|
||||||
qs.contour = bo.Uint32(pathData)
|
qs.contour = binary.LittleEndian.Uint32(pathData)
|
||||||
cmd := ops.DecodeCommand(pathData[4:])
|
cmd := ops.DecodeCommand(pathData[4:])
|
||||||
switch cmd.Op() {
|
switch cmd.Op() {
|
||||||
case scene.OpLine:
|
case scene.OpLine:
|
||||||
@@ -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.
|
// 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) {
|
func (d *drawOps) boundsForTransformedRect(r image.Rectangle, tr f32.Affine2D) (aux []byte, bnd image.Rectangle, ptr f32.Affine2D) {
|
||||||
if isPureOffset(tr) {
|
ptr = f32.AffineId()
|
||||||
// fast-path to allow blitting of pure rectangles
|
if tr == f32.AffineId() {
|
||||||
_, _, ox, _, _, oy := tr.Elems()
|
// fast-path to allow blitting of pure rectangles.
|
||||||
off := f32.Pt(ox, oy)
|
bnd = r
|
||||||
bnd.Min = r.Min.Add(off)
|
|
||||||
bnd.Max = r.Max.Add(off)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// transform all corners, find new bounds
|
// transform all corners, find new bounds
|
||||||
corners := [4]f32.Point{
|
corners := [4]f32.Point{
|
||||||
tr.Transform(r.Min), tr.Transform(f32.Pt(r.Max.X, r.Min.Y)),
|
tr.Transform(f32.FPt(r.Min)), tr.Transform(f32.Pt(float32(r.Max.X), float32(r.Min.Y))),
|
||||||
tr.Transform(r.Max), tr.Transform(f32.Pt(r.Min.X, r.Max.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 {
|
for _, c := range corners {
|
||||||
if c.X < bnd.Min.X {
|
if c.X < fBounds.Min.X {
|
||||||
bnd.Min.X = c.X
|
fBounds.Min.X = c.X
|
||||||
}
|
}
|
||||||
if c.Y < bnd.Min.Y {
|
if c.Y < fBounds.Min.Y {
|
||||||
bnd.Min.Y = c.Y
|
fBounds.Min.Y = c.Y
|
||||||
}
|
}
|
||||||
if c.X > bnd.Max.X {
|
if c.X > fBounds.Max.X {
|
||||||
bnd.Max.X = c.X
|
fBounds.Max.X = c.X
|
||||||
}
|
}
|
||||||
if c.Y > bnd.Max.Y {
|
if c.Y > fBounds.Max.Y {
|
||||||
bnd.Max.Y = c.Y
|
fBounds.Max.Y = c.Y
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
bnd = fBounds.Round()
|
||||||
|
|
||||||
// build the GPU vertices
|
// build the GPU vertices
|
||||||
l := len(d.vertCache)
|
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
|
// establish the transform mapping from bounds rectangle to transformed corners
|
||||||
var P1, P2, P3 f32.Point
|
var P1, P2, P3 f32.Point
|
||||||
P1.X = (corners[1].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X)
|
P1.X = (corners[1].X - fBounds.Min.X) / (fBounds.Max.X - fBounds.Min.X)
|
||||||
P1.Y = (corners[1].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y)
|
P1.Y = (corners[1].Y - fBounds.Min.Y) / (fBounds.Max.Y - fBounds.Min.Y)
|
||||||
P2.X = (corners[2].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X)
|
P2.X = (corners[2].X - fBounds.Min.X) / (fBounds.Max.X - fBounds.Min.X)
|
||||||
P2.Y = (corners[2].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y)
|
P2.Y = (corners[2].Y - fBounds.Min.Y) / (fBounds.Max.Y - fBounds.Min.Y)
|
||||||
P3.X = (corners[3].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X)
|
P3.X = (corners[3].X - fBounds.Min.X) / (fBounds.Max.X - fBounds.Min.X)
|
||||||
P3.Y = (corners[3].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y)
|
P3.Y = (corners[3].Y - fBounds.Min.Y) / (fBounds.Max.Y - fBounds.Min.Y)
|
||||||
sx, sy := P2.X-P3.X, P2.Y-P3.Y
|
sx, sy := P2.X-P3.X, P2.Y-P3.Y
|
||||||
ptr = f32.NewAffine2D(sx, P2.X-P1.X, P1.X-sx, sy, P2.Y-P1.Y, P1.Y-sy).Invert()
|
ptr = f32.NewAffine2D(sx, P2.X-P1.X, P1.X-sx, sy, P2.Y-P1.Y, P1.Y-sy).Invert()
|
||||||
|
|
||||||
return
|
return aux, bnd, ptr
|
||||||
}
|
}
|
||||||
|
|
||||||
func isPureOffset(t f32.Affine2D) bool {
|
// transformOffset a transform into two parts, one which is pure integer offset
|
||||||
a, b, _, d, e, _ := t.Elems()
|
// and the other representing the scaling, shearing and rotation and fractional
|
||||||
return a == 1 && b == 0 && d == 0 && e == 1
|
// 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 dumpImages = flag.Bool("saveimages", false, "save test images")
|
||||||
|
|
||||||
var clearCol = color.NRGBA{A: 0xff, R: 0xde, G: 0xad, B: 0xbe}
|
var (
|
||||||
var clearColExpect = f32color.NRGBAToRGBA(clearCol)
|
clearCol = color.NRGBA{A: 0xff, R: 0xde, G: 0xad, B: 0xbe}
|
||||||
|
clearColExpect = f32color.NRGBAToRGBA(clearCol)
|
||||||
|
)
|
||||||
|
|
||||||
func TestFramebufferClear(t *testing.T) {
|
func TestFramebufferClear(t *testing.T) {
|
||||||
b := newDriver(t)
|
b := newDriver(t)
|
||||||
@@ -202,5 +204,5 @@ func saveImage(file string, img image.Image) error {
|
|||||||
if err := png.Encode(&buf, img); err != nil {
|
if err := png.Encode(&buf, img); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return os.WriteFile(file, buf.Bytes(), 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 {
|
func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport image.Point) driver.Texture {
|
||||||
var (
|
var renderTarget *d3d11.RenderTargetView
|
||||||
renderTarget *d3d11.RenderTargetView
|
|
||||||
)
|
|
||||||
if target != nil {
|
if target != nil {
|
||||||
switch t := target.(type) {
|
switch t := target.(type) {
|
||||||
case driver.Direct3D11RenderTarget:
|
case driver.Direct3D11RenderTarget:
|
||||||
@@ -229,10 +227,7 @@ func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, min
|
|||||||
// Flags required by ID3D11DeviceContext::GenerateMips.
|
// Flags required by ID3D11DeviceContext::GenerateMips.
|
||||||
bindFlags |= d3d11.BIND_SHADER_RESOURCE | d3d11.BIND_RENDER_TARGET
|
bindFlags |= d3d11.BIND_SHADER_RESOURCE | d3d11.BIND_RENDER_TARGET
|
||||||
miscFlags |= d3d11.RESOURCE_MISC_GENERATE_MIPS
|
miscFlags |= d3d11.RESOURCE_MISC_GENERATE_MIPS
|
||||||
dim := width
|
dim := max(height, width)
|
||||||
if height > dim {
|
|
||||||
dim = height
|
|
||||||
}
|
|
||||||
log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1
|
log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1
|
||||||
nmipmaps = log2 + 1
|
nmipmaps = log2 + 1
|
||||||
}
|
}
|
||||||
@@ -802,7 +797,7 @@ func (t *Texture) ReadPixels(src image.Rectangle, pixels []byte, stride int) err
|
|||||||
mapSize := dstPitch * h
|
mapSize := dstPitch * h
|
||||||
data := sliceOf(resMap.PData, mapSize)
|
data := sliceOf(resMap.PData, mapSize)
|
||||||
width := w * 4
|
width := w * 4
|
||||||
for r := 0; r < h; r++ {
|
for r := range h {
|
||||||
pixels := pixels[r*srcPitch:]
|
pixels := pixels[r*srcPitch:]
|
||||||
copy(pixels[:width], data[r*dstPitch:])
|
copy(pixels[:width], data[r*dstPitch:])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,8 +96,10 @@ type BlendFactor uint8
|
|||||||
|
|
||||||
type Topology uint8
|
type Topology uint8
|
||||||
|
|
||||||
type TextureFilter uint8
|
type (
|
||||||
type TextureFormat uint8
|
TextureFilter uint8
|
||||||
|
TextureFormat uint8
|
||||||
|
)
|
||||||
|
|
||||||
type BufferBinding 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
|
// Flip image in y-direction. OpenGL's origin is in the lower
|
||||||
// left corner.
|
// left corner.
|
||||||
row := make([]uint8, stride)
|
row := make([]uint8, stride)
|
||||||
for y := 0; y < height/2; y++ {
|
for y := range height / 2 {
|
||||||
y1 := height - y - 1
|
y1 := height - y - 1
|
||||||
dest := y1 * stride
|
dest := y1 * stride
|
||||||
src := y * stride
|
src := y * stride
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"math/bits"
|
"math/bits"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
@@ -717,10 +718,7 @@ func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, min
|
|||||||
if mipmap {
|
if mipmap {
|
||||||
nmipmaps := 1
|
nmipmaps := 1
|
||||||
if mipmap {
|
if mipmap {
|
||||||
dim := width
|
dim := max(height, width)
|
||||||
if height > dim {
|
|
||||||
dim = height
|
|
||||||
}
|
|
||||||
log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1
|
log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1
|
||||||
nmipmaps = log2 + 1
|
nmipmaps = log2 + 1
|
||||||
}
|
}
|
||||||
@@ -1132,7 +1130,7 @@ func (b *Backend) setupVertexArrays() {
|
|||||||
enabled[inp.Location] = true
|
enabled[inp.Location] = true
|
||||||
b.glstate.vertexAttribPointer(b.funcs, buf.obj, inp.Location, l.Size, gltyp, false, p.layout.Stride, buf.offset+l.Offset)
|
b.glstate.vertexAttribPointer(b.funcs, buf.obj, inp.Location, l.Size, gltyp, false, p.layout.Stride, buf.offset+l.Offset)
|
||||||
}
|
}
|
||||||
for i := 0; i < max; i++ {
|
for i := range max {
|
||||||
b.glstate.setVertexAttribArray(b.funcs, i, enabled[i])
|
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 {
|
} else {
|
||||||
tmp := make([]byte, w*h*4)
|
tmp := make([]byte, w*h*4)
|
||||||
t.backend.funcs.ReadPixels(src.Min.X, src.Min.Y, w, h, gl.RGBA, gl.UNSIGNED_BYTE, tmp)
|
t.backend.funcs.ReadPixels(src.Min.X, src.Min.Y, w, h, gl.RGBA, gl.UNSIGNED_BYTE, tmp)
|
||||||
for y := 0; y < h; y++ {
|
for y := range h {
|
||||||
copy(pixels[y*stride:], tmp[y*w*4:])
|
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 {
|
func hasExtension(exts []string, ext string) bool {
|
||||||
for _, e := range exts {
|
return slices.Contains(exts, ext)
|
||||||
if ext == e {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func firstBufferType(typ driver.BufferBinding) gl.Enum {
|
func firstBufferType(typ driver.BufferBinding) gl.Enum {
|
||||||
|
|||||||
@@ -66,8 +66,8 @@ func BenchmarkDrawUICached(b *testing.B) {
|
|||||||
defer w.Release()
|
defer w.Release()
|
||||||
drawCore(gtx, th)
|
drawCore(gtx, th)
|
||||||
w.Frame(gtx.Ops)
|
w.Frame(gtx.Ops)
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
for b.Loop() {
|
||||||
w.Frame(gtx.Ops)
|
w.Frame(gtx.Ops)
|
||||||
}
|
}
|
||||||
finishBenchmark(b, w)
|
finishBenchmark(b, w)
|
||||||
@@ -83,12 +83,12 @@ func BenchmarkDrawUI(b *testing.B) {
|
|||||||
drawCore(gtx, th)
|
drawCore(gtx, th)
|
||||||
w.Frame(gtx.Ops)
|
w.Frame(gtx.Ops)
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; b.Loop(); i++ {
|
||||||
resetOps(gtx)
|
resetOps(gtx)
|
||||||
|
|
||||||
off := float32(math.Mod(float64(i)/10, 10))
|
off := float32(math.Mod(float64(i)/10, 10))
|
||||||
t := op.Affine(f32.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)
|
drawCore(gtx, th)
|
||||||
|
|
||||||
@@ -105,12 +105,12 @@ func BenchmarkDrawUITransformed(b *testing.B) {
|
|||||||
drawCore(gtx, th)
|
drawCore(gtx, th)
|
||||||
w.Frame(gtx.Ops)
|
w.Frame(gtx.Ops)
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; b.Loop(); i++ {
|
||||||
resetOps(gtx)
|
resetOps(gtx)
|
||||||
|
|
||||||
angle := float32(math.Mod(float64(i)/1000, 0.05))
|
angle := float32(math.Mod(float64(i)/1000, 0.05))
|
||||||
a := f32.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)
|
t := op.Affine(a).Push(gtx.Ops)
|
||||||
|
|
||||||
drawCore(gtx, th)
|
drawCore(gtx, th)
|
||||||
@@ -130,8 +130,8 @@ func Benchmark1000Circles(b *testing.B) {
|
|||||||
draw1000Circles(gtx)
|
draw1000Circles(gtx)
|
||||||
w.Frame(gtx.Ops)
|
w.Frame(gtx.Ops)
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
for b.Loop() {
|
||||||
resetOps(gtx)
|
resetOps(gtx)
|
||||||
draw1000Circles(gtx)
|
draw1000Circles(gtx)
|
||||||
w.Frame(gtx.Ops)
|
w.Frame(gtx.Ops)
|
||||||
@@ -147,8 +147,8 @@ func Benchmark1000CirclesInstanced(b *testing.B) {
|
|||||||
draw1000CirclesInstanced(gtx)
|
draw1000CirclesInstanced(gtx)
|
||||||
w.Frame(gtx.Ops)
|
w.Frame(gtx.Ops)
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
for b.Loop() {
|
||||||
resetOps(gtx)
|
resetOps(gtx)
|
||||||
draw1000CirclesInstanced(gtx)
|
draw1000CirclesInstanced(gtx)
|
||||||
w.Frame(gtx.Ops)
|
w.Frame(gtx.Ops)
|
||||||
@@ -158,9 +158,9 @@ func Benchmark1000CirclesInstanced(b *testing.B) {
|
|||||||
|
|
||||||
func draw1000Circles(gtx layout.Context) {
|
func draw1000Circles(gtx layout.Context) {
|
||||||
ops := gtx.Ops
|
ops := gtx.Ops
|
||||||
for x := 0; x < 100; x++ {
|
for x := range 100 {
|
||||||
op.Offset(image.Pt(x*10, 0)).Add(ops)
|
op.Offset(image.Pt(x*10, 0)).Add(ops)
|
||||||
for y := 0; y < 10; y++ {
|
for y := range 10 {
|
||||||
paint.FillShape(ops,
|
paint.FillShape(ops,
|
||||||
color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120},
|
color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120},
|
||||||
clip.RRect{Rect: image.Rect(0, 0, 10, 10), NE: 5, SE: 5, SW: 5, NW: 5}.Op(ops),
|
clip.RRect{Rect: image.Rect(0, 0, 10, 10), NE: 5, SE: 5, SW: 5, NW: 5}.Op(ops),
|
||||||
@@ -179,9 +179,9 @@ func draw1000CirclesInstanced(gtx layout.Context) {
|
|||||||
cl.Pop()
|
cl.Pop()
|
||||||
c := r.Stop()
|
c := r.Stop()
|
||||||
|
|
||||||
for x := 0; x < 100; x++ {
|
for x := range 100 {
|
||||||
op.Offset(image.Pt(x*10, 0)).Add(ops)
|
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)
|
paint.ColorOp{Color: color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120}}.Add(ops)
|
||||||
c.Add(ops)
|
c.Add(ops)
|
||||||
op.Offset(image.Pt(0, 100)).Add(ops)
|
op.Offset(image.Pt(0, 100)).Add(ops)
|
||||||
@@ -204,9 +204,9 @@ func drawIndividualShapes(gtx layout.Context, th *material.Theme) chan op.CallOp
|
|||||||
go func() {
|
go func() {
|
||||||
ops := &op1
|
ops := &op1
|
||||||
c := op.Record(ops)
|
c := op.Record(ops)
|
||||||
for x := 0; x < 9; x++ {
|
for x := range 9 {
|
||||||
op.Offset(image.Pt(x*50, 0)).Add(ops)
|
op.Offset(image.Pt(x*50, 0)).Add(ops)
|
||||||
for y := 0; y < 9; y++ {
|
for y := range 9 {
|
||||||
paint.FillShape(ops,
|
paint.FillShape(ops,
|
||||||
color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120},
|
color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120},
|
||||||
clip.RRect{Rect: image.Rect(0, 0, 25, 25), NE: 10, SE: 10, SW: 10, NW: 10}.Op(ops),
|
clip.RRect{Rect: image.Rect(0, 0, 25, 25), NE: 10, SE: 10, SW: 10, NW: 10}.Op(ops),
|
||||||
@@ -233,8 +233,8 @@ func drawShapeInstances(gtx layout.Context, th *material.Theme) chan op.CallOp {
|
|||||||
|
|
||||||
squares.Add(ops)
|
squares.Add(ops)
|
||||||
rad := float32(0)
|
rad := float32(0)
|
||||||
for x := 0; x < 20; x++ {
|
for x := range 20 {
|
||||||
for y := 0; y < 20; y++ {
|
for y := range 20 {
|
||||||
t := op.Offset(image.Pt(x*50+25, y*50+25)).Push(ops)
|
t := op.Offset(image.Pt(x*50+25, y*50+25)).Push(ops)
|
||||||
c.Add(ops)
|
c.Add(ops)
|
||||||
t.Pop()
|
t.Pop()
|
||||||
@@ -253,7 +253,7 @@ func drawText(gtx layout.Context, th *material.Theme) chan op.CallOp {
|
|||||||
c := op.Record(ops)
|
c := op.Record(ops)
|
||||||
|
|
||||||
txt := material.H6(th, "")
|
txt := material.H6(th, "")
|
||||||
for x := 0; x < 40; x++ {
|
for x := range 40 {
|
||||||
txt.Text = textRows[x]
|
txt.Text = textRows[x]
|
||||||
t := op.Offset(image.Pt(0, 24*x)).Push(ops)
|
t := op.Offset(image.Pt(0, 24*x)).Push(ops)
|
||||||
gtx.Ops = ops
|
gtx.Ops = ops
|
||||||
|
|||||||
@@ -40,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) {
|
func TestPaintClippedCircle(t *testing.T) {
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
const r = 10
|
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 clip.RRect{Rect: image.Rect(-30, -30, 60, 60)}.Push(o).Pop()
|
||||||
defer op.Offset(image.Pt(-10, -10)).Push(o).Pop()
|
defer op.Offset(image.Pt(-10, -10)).Push(o).Pop()
|
||||||
paint.PaintOp{}.Add(o)
|
paint.PaintOp{}.Add(o)
|
||||||
}, func(r result) {
|
}, nil)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTexturedStroke(t *testing.T) {
|
func TestTexturedStroke(t *testing.T) {
|
||||||
@@ -146,8 +164,7 @@ func TestTexturedStroke(t *testing.T) {
|
|||||||
}.Op().Push(o).Pop()
|
}.Op().Push(o).Pop()
|
||||||
defer op.Offset(image.Pt(-10, -10)).Push(o).Pop()
|
defer op.Offset(image.Pt(-10, -10)).Push(o).Pop()
|
||||||
paint.PaintOp{}.Add(o)
|
paint.PaintOp{}.Add(o)
|
||||||
}, func(r result) {
|
}, nil)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPaintClippedTexture(t *testing.T) {
|
func TestPaintClippedTexture(t *testing.T) {
|
||||||
@@ -178,7 +195,6 @@ func TestStrokedPathZeroWidth(t *testing.T) {
|
|||||||
paint.Fill(o, red)
|
paint.Fill(o, red)
|
||||||
cl.Pop()
|
cl.Pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
}, func(r result) {
|
}, func(r result) {
|
||||||
r.expect(0, 0, transparent)
|
r.expect(0, 0, transparent)
|
||||||
r.expect(10, 50, colornames.Black)
|
r.expect(10, 50, colornames.Black)
|
||||||
@@ -252,8 +268,7 @@ func TestPathReuse(t *testing.T) {
|
|||||||
stroke := clip.Stroke{Path: spec, Width: 3}.Op().Push(o)
|
stroke := clip.Stroke{Path: spec, Width: 3}.Op().Push(o)
|
||||||
paint.Fill(o, color.NRGBA{B: 0xFF, A: 0xFF})
|
paint.Fill(o, color.NRGBA{B: 0xFF, A: 0xFF})
|
||||||
stroke.Pop()
|
stroke.Pop()
|
||||||
}, func(r result) {
|
}, nil)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathInterleave(t *testing.T) {
|
func TestPathInterleave(t *testing.T) {
|
||||||
@@ -290,6 +305,22 @@ func TestStrokedRect(t *testing.T) {
|
|||||||
Width: 5,
|
Width: 5,
|
||||||
}.Op(),
|
}.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()
|
c := constSqPath()
|
||||||
|
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
|
|
||||||
// render the first Stacked item
|
// render the first Stacked item
|
||||||
m1 := op.Record(o)
|
m1 := op.Record(o)
|
||||||
dr := image.Rect(0, 0, 128, 50)
|
dr := image.Rect(0, 0, 128, 50)
|
||||||
@@ -88,10 +87,10 @@ func TestNoClipFromPaint(t *testing.T) {
|
|||||||
// ensure that a paint operation does not pollute the state
|
// ensure that a paint operation does not pollute the state
|
||||||
// by leaving any clip paths in place.
|
// by leaving any clip paths in place.
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
a := f32.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()
|
defer op.Affine(a).Push(o).Pop()
|
||||||
paint.FillShape(o, red, clip.Rect(image.Rect(10, 10, 30, 30)).Op())
|
paint.FillShape(o, red, clip.Rect(image.Rect(10, 10, 30, 30)).Op())
|
||||||
a = f32.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()
|
defer op.Affine(a).Push(o).Pop()
|
||||||
|
|
||||||
paint.FillShape(o, black, clip.Rect(image.Rect(0, 0, 50, 50)).Op())
|
paint.FillShape(o, black, clip.Rect(image.Rect(0, 0, 50, 50)).Op())
|
||||||
@@ -110,7 +109,7 @@ func TestDeferredPaint(t *testing.T) {
|
|||||||
paint.PaintOp{}.Add(o)
|
paint.PaintOp{}.Add(o)
|
||||||
cl.Pop()
|
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)
|
m := op.Record(o)
|
||||||
cl2 := clip.Rect(image.Rect(0, 0, 80, 80)).Op().Push(o)
|
cl2 := clip.Rect(image.Rect(0, 0, 80, 80)).Op().Push(o)
|
||||||
paint.ColorOp{Color: color.NRGBA{A: 0x60, R: 0xff, G: 0xff}}.Add(o)
|
paint.ColorOp{Color: color.NRGBA{A: 0x60, R: 0xff, G: 0xff}}.Add(o)
|
||||||
@@ -120,12 +119,11 @@ func TestDeferredPaint(t *testing.T) {
|
|||||||
op.Defer(o, paintMacro)
|
op.Defer(o, paintMacro)
|
||||||
t.Pop()
|
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()
|
defer clip.Rect(image.Rect(0, 0, 80, 80)).Op().Push(o).Pop()
|
||||||
paint.ColorOp{Color: color.NRGBA{A: 0x60, B: 0xff}}.Add(o)
|
paint.ColorOp{Color: color.NRGBA{A: 0x60, B: 0xff}}.Add(o)
|
||||||
paint.PaintOp{}.Add(o)
|
paint.PaintOp{}.Add(o)
|
||||||
}, func(r result) {
|
}, nil)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func constSqPath() clip.Op {
|
func constSqPath() clip.Op {
|
||||||
@@ -143,8 +141,10 @@ func constSqPath() clip.Op {
|
|||||||
|
|
||||||
func constSqCirc() clip.Op {
|
func constSqCirc() clip.Op {
|
||||||
innerOps := new(op.Ops)
|
innerOps := new(op.Ops)
|
||||||
return clip.RRect{Rect: image.Rect(0, 0, 40, 40),
|
return clip.RRect{
|
||||||
NW: 20, NE: 20, SW: 20, SE: 20}.Op(innerOps)
|
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 {
|
func drawChild(ops *op.Ops, text clip.Op) op.CallOp {
|
||||||
@@ -260,7 +260,7 @@ func TestLinearGradient(t *testing.T) {
|
|||||||
Color2: g.To,
|
Color2: g.To,
|
||||||
}.Add(ops)
|
}.Add(ops)
|
||||||
cl := clip.RRect{Rect: gr.Round()}.Push(ops)
|
cl := clip.RRect{Rect: gr.Round()}.Push(ops)
|
||||||
t1 := op.Affine(f32.Affine2D{}.Offset(pixelAligned.Min)).Push(ops)
|
t1 := op.Affine(f32.AffineId().Offset(pixelAligned.Min)).Push(ops)
|
||||||
t2 := scale(pixelAligned.Dx()/128, 1).Push(ops)
|
t2 := scale(pixelAligned.Dx()/128, 1).Push(ops)
|
||||||
paint.PaintOp{}.Add(ops)
|
paint.PaintOp{}.Add(ops)
|
||||||
t2.Pop()
|
t2.Pop()
|
||||||
@@ -323,7 +323,7 @@ func TestLinearGradientAngled(t *testing.T) {
|
|||||||
cl = clip.Rect(image.Rect(0, 64, 64, 128)).Push(ops)
|
cl = clip.Rect(image.Rect(0, 64, 64, 128)).Push(ops)
|
||||||
paint.PaintOp{}.Add(ops)
|
paint.PaintOp{}.Add(ops)
|
||||||
cl.Pop()
|
cl.Pop()
|
||||||
}, func(r result) {})
|
}, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestZeroImage(t *testing.T) {
|
func TestZeroImage(t *testing.T) {
|
||||||
@@ -363,7 +363,7 @@ func TestImageRGBA_ScaleLinear(t *testing.T) {
|
|||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
w := newWindow(t, 128, 128)
|
w := newWindow(t, 128, 128)
|
||||||
defer clip.Rect{Max: image.Pt(128, 128)}.Push(o).Pop()
|
defer clip.Rect{Max: image.Pt(128, 128)}.Push(o).Pop()
|
||||||
op.Affine(f32.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 := image.NewRGBA(image.Rect(0, 0, 2, 2))
|
||||||
im.Set(0, 0, colornames.Red)
|
im.Set(0, 0, colornames.Red)
|
||||||
@@ -397,7 +397,7 @@ func TestImageRGBA_ScaleLinear(t *testing.T) {
|
|||||||
func TestImageRGBA_ScaleNearest(t *testing.T) {
|
func TestImageRGBA_ScaleNearest(t *testing.T) {
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
w := newWindow(t, 128, 128)
|
w := newWindow(t, 128, 128)
|
||||||
op.Affine(f32.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 := image.NewRGBA(image.Rect(0, 0, 2, 2))
|
||||||
im.Set(0, 0, colornames.Red)
|
im.Set(0, 0, colornames.Red)
|
||||||
@@ -483,17 +483,16 @@ func TestGapsInPath(t *testing.T) {
|
|||||||
func TestOpacity(t *testing.T) {
|
func TestOpacity(t *testing.T) {
|
||||||
run(t, func(ops *op.Ops) {
|
run(t, func(ops *op.Ops) {
|
||||||
opc1 := paint.PushOpacity(ops, .3)
|
opc1 := paint.PushOpacity(ops, .3)
|
||||||
// Fill screen to exercize the glClear optimization.
|
// Fill screen to exercise the glClear optimization.
|
||||||
paint.FillShape(ops, color.NRGBA{R: 255, A: 255}, clip.Rect{Max: image.Pt(1024, 1024)}.Op())
|
paint.FillShape(ops, color.NRGBA{R: 255, A: 255}, clip.Rect{Max: image.Pt(1024, 1024)}.Op())
|
||||||
opc2 := paint.PushOpacity(ops, .6)
|
opc2 := paint.PushOpacity(ops, .6)
|
||||||
paint.FillShape(ops, color.NRGBA{G: 255, A: 255}, clip.Rect{Min: image.Pt(20, 10), Max: image.Pt(64, 128)}.Op())
|
paint.FillShape(ops, color.NRGBA{G: 255, A: 255}, clip.Rect{Min: image.Pt(20, 10), Max: image.Pt(64, 128)}.Op())
|
||||||
opc2.Pop()
|
opc2.Pop()
|
||||||
opc1.Pop()
|
opc1.Pop()
|
||||||
opc3 := paint.PushOpacity(ops, .6)
|
opc3 := paint.PushOpacity(ops, .6)
|
||||||
paint.FillShape(ops, color.NRGBA{G: 255, A: 255}, clip.Rect{Min: image.Pt(50+20, 10), Max: image.Pt(50+64, 128)}.Op())
|
paint.FillShape(ops, color.NRGBA{B: 255, A: 255}, clip.Ellipse(image.Rectangle{Min: image.Pt(20+20, 10), Max: image.Pt(50+64, 128)}).Op(ops))
|
||||||
opc3.Pop()
|
opc3.Pop()
|
||||||
}, func(r result) {
|
}, nil)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// lerp calculates linear interpolation with color b and p.
|
// lerp calculates linear interpolation with color b and p.
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func TestPaintOffset(t *testing.T) {
|
|||||||
|
|
||||||
func TestPaintRotate(t *testing.T) {
|
func TestPaintRotate(t *testing.T) {
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
a := f32.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 op.Affine(a).Push(o).Pop()
|
||||||
paint.FillShape(o, red, clip.Rect(image.Rect(20, 20, 60, 60)).Op())
|
paint.FillShape(o, red, clip.Rect(image.Rect(20, 20, 60, 60)).Op())
|
||||||
}, func(r result) {
|
}, func(r result) {
|
||||||
@@ -42,7 +42,7 @@ func TestPaintRotate(t *testing.T) {
|
|||||||
|
|
||||||
func TestPaintShear(t *testing.T) {
|
func TestPaintShear(t *testing.T) {
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
a := f32.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 op.Affine(a).Push(o).Pop()
|
||||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 40, 40)).Op())
|
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 40, 40)).Op())
|
||||||
}, func(r result) {
|
}, func(r result) {
|
||||||
@@ -79,7 +79,7 @@ func TestClipOffset(t *testing.T) {
|
|||||||
|
|
||||||
func TestClipScale(t *testing.T) {
|
func TestClipScale(t *testing.T) {
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
a := f32.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 op.Affine(a).Push(o).Pop()
|
||||||
defer clip.RRect{Rect: image.Rect(10, 10, 20, 20)}.Push(o).Pop()
|
defer clip.RRect{Rect: image.Rect(10, 10, 20, 20)}.Push(o).Pop()
|
||||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 1000, 1000)).Op())
|
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 1000, 1000)).Op())
|
||||||
@@ -93,7 +93,7 @@ func TestClipScale(t *testing.T) {
|
|||||||
|
|
||||||
func TestClipRotate(t *testing.T) {
|
func TestClipRotate(t *testing.T) {
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
defer op.Affine(f32.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()
|
defer clip.RRect{Rect: image.Rect(30, 30, 50, 50)}.Push(o).Pop()
|
||||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 40, 100, 100)).Op())
|
paint.FillShape(o, red, clip.Rect(image.Rect(0, 40, 100, 100)).Op())
|
||||||
}, func(r result) {
|
}, func(r result) {
|
||||||
@@ -121,7 +121,7 @@ func TestOffsetScaleTexture(t *testing.T) {
|
|||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
defer op.Offset(image.Pt(15, 15)).Push(o).Pop()
|
defer op.Offset(image.Pt(15, 15)).Push(o).Pop()
|
||||||
squares.Add(o)
|
squares.Add(o)
|
||||||
defer op.Affine(f32.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()
|
defer scale(50.0/512, 50.0/512).Push(o).Pop()
|
||||||
paint.PaintOp{}.Add(o)
|
paint.PaintOp{}.Add(o)
|
||||||
}, func(r result) {
|
}, func(r result) {
|
||||||
@@ -133,7 +133,7 @@ func TestOffsetScaleTexture(t *testing.T) {
|
|||||||
func TestRotateTexture(t *testing.T) {
|
func TestRotateTexture(t *testing.T) {
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
squares.Add(o)
|
squares.Add(o)
|
||||||
a := f32.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 op.Affine(a).Push(o).Pop()
|
||||||
defer scale(20.0/512, 20.0/512).Push(o).Pop()
|
defer scale(20.0/512, 20.0/512).Push(o).Pop()
|
||||||
paint.PaintOp{}.Add(o)
|
paint.PaintOp{}.Add(o)
|
||||||
@@ -146,10 +146,10 @@ func TestRotateTexture(t *testing.T) {
|
|||||||
func TestRotateClipTexture(t *testing.T) {
|
func TestRotateClipTexture(t *testing.T) {
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
squares.Add(o)
|
squares.Add(o)
|
||||||
a := f32.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 op.Affine(a).Push(o).Pop()
|
||||||
defer clip.RRect{Rect: image.Rect(30, 30, 50, 50)}.Push(o).Pop()
|
defer clip.RRect{Rect: image.Rect(30, 30, 50, 50)}.Push(o).Pop()
|
||||||
defer op.Affine(f32.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()
|
defer scale(60.0/512, 60.0/512).Push(o).Pop()
|
||||||
paint.PaintOp{}.Add(o)
|
paint.PaintOp{}.Add(o)
|
||||||
}, func(r result) {
|
}, func(r result) {
|
||||||
@@ -168,7 +168,7 @@ func TestComplicatedTransform(t *testing.T) {
|
|||||||
|
|
||||||
defer clip.RRect{Rect: image.Rect(0, 0, 100, 100), SE: 50, SW: 50, NW: 50, NE: 50}.Push(o).Pop()
|
defer clip.RRect{Rect: image.Rect(0, 0, 100, 100), SE: 50, SW: 50, NW: 50, NE: 50}.Push(o).Pop()
|
||||||
|
|
||||||
a := f32.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 op.Affine(a).Push(o).Pop()
|
||||||
defer clip.RRect{Rect: image.Rect(0, 0, 50, 40)}.Push(o).Pop()
|
defer clip.RRect{Rect: image.Rect(0, 0, 50, 40)}.Push(o).Pop()
|
||||||
|
|
||||||
@@ -182,13 +182,13 @@ func TestComplicatedTransform(t *testing.T) {
|
|||||||
func TestTransformOrder(t *testing.T) {
|
func TestTransformOrder(t *testing.T) {
|
||||||
// check the ordering of operations bot in affine and in gpu stack.
|
// check the ordering of operations bot in affine and in gpu stack.
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
a := f32.Affine2D{}.Offset(f32.Pt(64, 64))
|
a := f32.AffineId().Offset(f32.Pt(64, 64))
|
||||||
defer op.Affine(a).Push(o).Pop()
|
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()
|
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()
|
defer op.Affine(c).Push(o).Pop()
|
||||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 20, 20)).Op())
|
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 20, 20)).Op())
|
||||||
}, func(r result) {
|
}, func(r result) {
|
||||||
|
|||||||
@@ -49,8 +49,8 @@ func buildSquares(size int) paint.ImageOp {
|
|||||||
sub := size / 4
|
sub := size / 4
|
||||||
im := image.NewNRGBA(image.Rect(0, 0, size, size))
|
im := image.NewNRGBA(image.Rect(0, 0, size, size))
|
||||||
c1, c2 := image.NewUniform(colornames.Green), image.NewUniform(colornames.Blue)
|
c1, c2 := image.NewUniform(colornames.Green), image.NewUniform(colornames.Blue)
|
||||||
for r := 0; r < 4; r++ {
|
for r := range 4 {
|
||||||
for c := 0; c < 4; c++ {
|
for c := range 4 {
|
||||||
c1, c2 = c2, c1
|
c1, c2 = c2, c1
|
||||||
draw.Draw(im, image.Rect(r*sub, c*sub, r*sub+sub, c*sub+sub), c1, image.Point{}, draw.Over)
|
draw.Draw(im, image.Rect(r*sub, c*sub, r*sub+sub, c*sub+sub), c1, image.Point{}, draw.Over)
|
||||||
}
|
}
|
||||||
@@ -78,7 +78,7 @@ func run(t *testing.T, f func(o *op.Ops), c func(r result)) {
|
|||||||
var img *image.RGBA
|
var img *image.RGBA
|
||||||
var err error
|
var err error
|
||||||
ops := new(op.Ops)
|
ops := new(op.Ops)
|
||||||
for i := 0; i < 3; i++ {
|
for i := range 3 {
|
||||||
ops.Reset()
|
ops.Reset()
|
||||||
img, err = drawImage(t, 128, ops, f)
|
img, err = drawImage(t, 128, ops, f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -90,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)
|
name := fmt.Sprintf("%s-%d-bad.png", t.Name(), i)
|
||||||
saveImage(t, name, img)
|
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")
|
path = filepath.Join("refs", path+".png")
|
||||||
if *dumpImages {
|
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) {
|
if !os.IsExist(err) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@@ -285,7 +287,7 @@ func saveImage(t testing.TB, file string, img *image.RGBA) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := os.WriteFile(file, buf.Bytes(), 0666); err != nil {
|
if err := os.WriteFile(file, buf.Bytes(), 0o666); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -300,5 +302,5 @@ func newWindow(t testing.TB, width, height int) *headless.Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func scale(sx, sy float32) op.TransformOp {
|
func scale(sx, sy float32) op.TransformOp {
|
||||||
return op.Affine(f32.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)
|
buf := buffer.(*Buffer)
|
||||||
cmdBuf := b.currentCmdBuf()
|
cmdBuf := b.currentCmdBuf()
|
||||||
for _, s := range b.pipe.pushRanges {
|
for _, s := range b.pipe.pushRanges {
|
||||||
off := s.Offset()
|
off := vk.PushConstantRangeOffset(s)
|
||||||
vk.CmdPushConstants(cmdBuf, b.pipe.desc.layout, s.StageFlags(), off, buf.store[off:off+s.Size()])
|
vk.CmdPushConstants(cmdBuf, b.pipe.desc.layout, vk.PushConstantRangeStageFlags(s), off, buf.store[off:off+vk.PushConstantRangeSize(s)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -10,10 +10,10 @@ import (
|
|||||||
func BenchmarkPacker(b *testing.B) {
|
func BenchmarkPacker(b *testing.B) {
|
||||||
var p packer
|
var p packer
|
||||||
p.maxDims = image.Point{X: 4096, Y: 4096}
|
p.maxDims = image.Point{X: 4096, Y: 4096}
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; b.Loop(); i++ {
|
||||||
p.clear()
|
p.clear()
|
||||||
p.newPage()
|
p.newPage()
|
||||||
for k := 0; k < 500; k++ {
|
for k := range 500 {
|
||||||
_, ok := p.tryAdd(xy(k))
|
_, ok := p.tryAdd(xy(k))
|
||||||
if !ok {
|
if !ok {
|
||||||
b.Fatal("add failed", i, k, xy(k))
|
b.Fatal("add failed", i, k, xy(k))
|
||||||
|
|||||||
+14
-8
@@ -30,7 +30,7 @@ type pather struct {
|
|||||||
|
|
||||||
type coverer struct {
|
type coverer struct {
|
||||||
ctx driver.Device
|
ctx driver.Device
|
||||||
pipelines [3]*pipeline
|
pipelines [2][3]*pipeline
|
||||||
texUniforms *coverTexUniforms
|
texUniforms *coverTexUniforms
|
||||||
colUniforms *coverColUniforms
|
colUniforms *coverColUniforms
|
||||||
linearGradientUniforms *coverLinearGradientUniforms
|
linearGradientUniforms *coverLinearGradientUniforms
|
||||||
@@ -150,7 +150,7 @@ func newCoverer(ctx driver.Device) *coverer {
|
|||||||
c.texUniforms = new(coverTexUniforms)
|
c.texUniforms = new(coverTexUniforms)
|
||||||
c.linearGradientUniforms = new(coverLinearGradientUniforms)
|
c.linearGradientUniforms = new(coverLinearGradientUniforms)
|
||||||
pipelines, err := createColorPrograms(ctx, gio.Shader_cover_vert, gio.Shader_cover_frag,
|
pipelines, err := createColorPrograms(ctx, gio.Shader_cover_vert, gio.Shader_cover_frag,
|
||||||
[3]interface{}{c.colUniforms, c.linearGradientUniforms, c.texUniforms},
|
[3]any{c.colUniforms, c.linearGradientUniforms, c.texUniforms},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -162,7 +162,7 @@ func newCoverer(ctx driver.Device) *coverer {
|
|||||||
func newStenciler(ctx driver.Device) *stenciler {
|
func newStenciler(ctx driver.Device) *stenciler {
|
||||||
// Allocate a suitably large index buffer for drawing paths.
|
// Allocate a suitably large index buffer for drawing paths.
|
||||||
indices := make([]uint16, pathBatchSize*6)
|
indices := make([]uint16, pathBatchSize*6)
|
||||||
for i := 0; i < pathBatchSize; i++ {
|
for i := range pathBatchSize {
|
||||||
i := uint16(i)
|
i := uint16(i)
|
||||||
indices[i*6+0] = i*4 + 0
|
indices[i*6+0] = i*4 + 0
|
||||||
indices[i*6+1] = i*4 + 1
|
indices[i*6+1] = i*4 + 1
|
||||||
@@ -309,7 +309,9 @@ func (p *pather) release() {
|
|||||||
|
|
||||||
func (c *coverer) release() {
|
func (c *coverer) release() {
|
||||||
for _, p := range c.pipelines {
|
for _, p := range c.pipelines {
|
||||||
p.Release()
|
for _, p := range p {
|
||||||
|
p.Release()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,7 +334,7 @@ func (p *pather) begin(sizes []image.Point) {
|
|||||||
p.stenciler.begin(sizes)
|
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)
|
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)
|
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())
|
s.ctx.Viewport(uv.X, uv.Y, bounds.Dx(), bounds.Dy())
|
||||||
// Transform UI coordinates to OpenGL coordinates.
|
// Transform UI coordinates to OpenGL coordinates.
|
||||||
texSize := f32.Point{X: float32(bounds.Dx()), Y: float32(bounds.Dy())}
|
texSize := f32.Point{X: float32(bounds.Dx()), Y: float32(bounds.Dy())}
|
||||||
scale := f32.Point{X: 2 / texSize.X, Y: 2 / texSize.Y}
|
scale := f32.Point{X: 2 / texSize.X, Y: 2 / texSize.Y}
|
||||||
orig := f32.Point{X: -1 - float32(bounds.Min.X)*2/texSize.X, Y: -1 - float32(bounds.Min.Y)*2/texSize.Y}
|
orig := f32.Point{X: -1 - float32(bounds.Min.X)*2/texSize.X, Y: -1 - float32(bounds.Min.Y)*2/texSize.Y}
|
||||||
s.pipeline.uniforms.transform = [4]float32{scale.X, scale.Y, orig.X, orig.Y}
|
s.pipeline.uniforms.transform = [4]float32{scale.X, scale.Y, orig.X, orig.Y}
|
||||||
s.pipeline.uniforms.pathOffset = [2]float32{offset.X, offset.Y}
|
s.pipeline.uniforms.pathOffset = [2]float32{float32(offset.X), float32(offset.Y)}
|
||||||
s.pipeline.pipeline.UploadUniforms(s.ctx)
|
s.pipeline.pipeline.UploadUniforms(s.ctx)
|
||||||
// Draw in batches that fit in uint16 indices.
|
// Draw in batches that fit in uint16 indices.
|
||||||
start := 0
|
start := 0
|
||||||
@@ -405,7 +407,11 @@ func (c *coverer) cover(mat materialType, isFBO bool, col f32color.RGBA, col1, c
|
|||||||
}
|
}
|
||||||
uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
|
uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
|
||||||
uniforms.uvCoverTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y}
|
uniforms.uvCoverTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y}
|
||||||
c.pipelines[mat].UploadUniforms(c.ctx)
|
fboIdx := 0
|
||||||
|
if isFBO {
|
||||||
|
fboIdx = 1
|
||||||
|
}
|
||||||
|
c.pipelines[fboIdx][mat].UploadUniforms(c.ctx)
|
||||||
c.ctx.DrawArrays(0, 4)
|
c.ctx.DrawArrays(0, 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Struct returns a byte slice view of a struct.
|
// Struct returns a byte slice view of a struct.
|
||||||
func Struct(s interface{}) []byte {
|
func Struct(s any) []byte {
|
||||||
v := reflect.ValueOf(s)
|
v := reflect.ValueOf(s)
|
||||||
sz := int(v.Elem().Type().Size())
|
sz := int(v.Elem().Type().Size())
|
||||||
return unsafe.Slice((*byte)(unsafe.Pointer(v.Pointer())), sz)
|
return unsafe.Slice((*byte)(unsafe.Pointer(v.Pointer())), sz)
|
||||||
@@ -27,7 +27,7 @@ func Uint32(s []uint32) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Slice returns a byte slice view of a slice.
|
// Slice returns a byte slice view of a slice.
|
||||||
func Slice(s interface{}) []byte {
|
func Slice(s any) []byte {
|
||||||
v := reflect.ValueOf(s)
|
v := reflect.ValueOf(s)
|
||||||
first := v.Index(0)
|
first := v.Index(0)
|
||||||
sz := int(first.Type().Size())
|
sz := int(first.Type().Size())
|
||||||
|
|||||||
+6
-13
@@ -9,16 +9,16 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gioui.org/gpu"
|
"gioui.org/gpu"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Context struct {
|
type Context struct {
|
||||||
disp _EGLDisplay
|
disp _EGLDisplay
|
||||||
eglCtx *eglContext
|
eglCtx *eglContext
|
||||||
eglSurf _EGLSurface
|
eglSurf _EGLSurface
|
||||||
width, height int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type eglContext struct {
|
type eglContext struct {
|
||||||
@@ -121,11 +121,9 @@ func (c *Context) VisualID() int {
|
|||||||
return c.eglCtx.visualID
|
return c.eglCtx.visualID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) CreateSurface(win NativeWindowType, width, height int) error {
|
func (c *Context) CreateSurface(win NativeWindowType) error {
|
||||||
eglSurf, err := createSurface(c.disp, c.eglCtx, win)
|
eglSurf, err := createSurface(c.disp, c.eglCtx, win)
|
||||||
c.eglSurf = eglSurf
|
c.eglSurf = eglSurf
|
||||||
c.width = width
|
|
||||||
c.height = height
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,12 +155,7 @@ func (c *Context) EnableVSync(enable bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func hasExtension(exts []string, ext string) bool {
|
func hasExtension(exts []string, ext string) bool {
|
||||||
for _, e := range exts {
|
return slices.Contains(exts, ext)
|
||||||
if ext == e {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func createContext(disp _EGLDisplay) (*eglContext, error) {
|
func createContext(disp _EGLDisplay) (*eglContext, error) {
|
||||||
|
|||||||
+49
-27
@@ -9,8 +9,6 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
syscall "golang.org/x/sys/windows"
|
syscall "golang.org/x/sys/windows"
|
||||||
|
|
||||||
"gioui.org/internal/gl"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -24,23 +22,23 @@ type (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
libEGL = syscall.NewLazyDLL("libEGL.dll")
|
libEGL = syscall.DLL{}
|
||||||
_eglChooseConfig = libEGL.NewProc("eglChooseConfig")
|
_eglChooseConfig *syscall.Proc
|
||||||
_eglCreateContext = libEGL.NewProc("eglCreateContext")
|
_eglCreateContext *syscall.Proc
|
||||||
_eglCreateWindowSurface = libEGL.NewProc("eglCreateWindowSurface")
|
_eglCreateWindowSurface *syscall.Proc
|
||||||
_eglDestroyContext = libEGL.NewProc("eglDestroyContext")
|
_eglDestroyContext *syscall.Proc
|
||||||
_eglDestroySurface = libEGL.NewProc("eglDestroySurface")
|
_eglDestroySurface *syscall.Proc
|
||||||
_eglGetConfigAttrib = libEGL.NewProc("eglGetConfigAttrib")
|
_eglGetConfigAttrib *syscall.Proc
|
||||||
_eglGetDisplay = libEGL.NewProc("eglGetDisplay")
|
_eglGetDisplay *syscall.Proc
|
||||||
_eglGetError = libEGL.NewProc("eglGetError")
|
_eglGetError *syscall.Proc
|
||||||
_eglInitialize = libEGL.NewProc("eglInitialize")
|
_eglInitialize *syscall.Proc
|
||||||
_eglMakeCurrent = libEGL.NewProc("eglMakeCurrent")
|
_eglMakeCurrent *syscall.Proc
|
||||||
_eglReleaseThread = libEGL.NewProc("eglReleaseThread")
|
_eglReleaseThread *syscall.Proc
|
||||||
_eglSwapInterval = libEGL.NewProc("eglSwapInterval")
|
_eglSwapInterval *syscall.Proc
|
||||||
_eglSwapBuffers = libEGL.NewProc("eglSwapBuffers")
|
_eglSwapBuffers *syscall.Proc
|
||||||
_eglTerminate = libEGL.NewProc("eglTerminate")
|
_eglTerminate *syscall.Proc
|
||||||
_eglQueryString = libEGL.NewProc("eglQueryString")
|
_eglQueryString *syscall.Proc
|
||||||
_eglWaitClient = libEGL.NewProc("eglWaitClient")
|
_eglWaitClient *syscall.Proc
|
||||||
)
|
)
|
||||||
|
|
||||||
var loadOnce sync.Once
|
var loadOnce sync.Once
|
||||||
@@ -54,21 +52,45 @@ func loadEGL() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadDLLs() error {
|
func loadDLLs() error {
|
||||||
if err := loadDLL(libEGL, "libEGL.dll"); err != nil {
|
if err := loadDLL(&libEGL, "libEGL.dll"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := loadDLL(gl.LibGLESv2, "libGLESv2.dll"); err != nil {
|
|
||||||
return err
|
procs := map[string]**syscall.Proc{
|
||||||
|
"eglChooseConfig": &_eglChooseConfig,
|
||||||
|
"eglCreateContext": &_eglCreateContext,
|
||||||
|
"eglCreateWindowSurface": &_eglCreateWindowSurface,
|
||||||
|
"eglDestroyContext": &_eglDestroyContext,
|
||||||
|
"eglDestroySurface": &_eglDestroySurface,
|
||||||
|
"eglGetConfigAttrib": &_eglGetConfigAttrib,
|
||||||
|
"eglGetDisplay": &_eglGetDisplay,
|
||||||
|
"eglGetError": &_eglGetError,
|
||||||
|
"eglInitialize": &_eglInitialize,
|
||||||
|
"eglMakeCurrent": &_eglMakeCurrent,
|
||||||
|
"eglReleaseThread": &_eglReleaseThread,
|
||||||
|
"eglSwapInterval": &_eglSwapInterval,
|
||||||
|
"eglSwapBuffers": &_eglSwapBuffers,
|
||||||
|
"eglTerminate": &_eglTerminate,
|
||||||
|
"eglQueryString": &_eglQueryString,
|
||||||
|
"eglWaitClient": &_eglWaitClient,
|
||||||
}
|
}
|
||||||
// d3dcompiler_47.dll is needed internally for shader compilation to function.
|
for name, proc := range procs {
|
||||||
return loadDLL(syscall.NewLazyDLL("d3dcompiler_47.dll"), "d3dcompiler_47.dll")
|
p, err := libEGL.FindProc(name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to locate %s in %s: %w", name, libEGL.Name, err)
|
||||||
|
}
|
||||||
|
*proc = p
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadDLL(dll *syscall.LazyDLL, name string) error {
|
func loadDLL(dll *syscall.DLL, name string) error {
|
||||||
err := dll.Load()
|
handle, err := syscall.LoadLibraryEx(name, 0, syscall.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("egl: failed to load %s: %v", name, err)
|
return fmt.Errorf("egl: failed to load %s: %v", name, err)
|
||||||
}
|
}
|
||||||
|
dll.Handle = handle
|
||||||
|
dll.Name = name
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,6 +186,6 @@ func eglWaitClient() bool {
|
|||||||
|
|
||||||
// issue34474KeepAlive calls runtime.KeepAlive as a
|
// issue34474KeepAlive calls runtime.KeepAlive as a
|
||||||
// workaround for golang.org/issue/34474.
|
// workaround for golang.org/issue/34474.
|
||||||
func issue34474KeepAlive(v interface{}) {
|
func issue34474KeepAlive(v any) {
|
||||||
runtime.KeepAlive(v)
|
runtime.KeepAlive(v)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ type Affine2D = f32.Affine2D
|
|||||||
|
|
||||||
var NewAffine2D = f32.NewAffine2D
|
var NewAffine2D = f32.NewAffine2D
|
||||||
|
|
||||||
|
var AffineId = f32.AffineId
|
||||||
|
|
||||||
// A Rectangle contains the points (X, Y) where Min.X <= X < Max.X,
|
// A Rectangle contains the points (X, Y) where Min.X <= X < Max.X,
|
||||||
// Min.Y <= Y < Max.Y.
|
// Min.Y <= Y < Max.Y.
|
||||||
type Rectangle struct {
|
type Rectangle struct {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ func main() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
printf := func(content string, args ...interface{}) {
|
printf := func(content string, args ...any) {
|
||||||
fmt.Fprintf(&b, content, args...)
|
fmt.Fprintf(&b, content, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ func main() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.WriteFile(*out, data, 0755)
|
err = os.WriteFile(*out, data, 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,17 +41,17 @@ var sink RGBA
|
|||||||
|
|
||||||
func BenchmarkLinearFromSRGB(b *testing.B) {
|
func BenchmarkLinearFromSRGB(b *testing.B) {
|
||||||
b.Run("opaque", func(b *testing.B) {
|
b.Run("opaque", func(b *testing.B) {
|
||||||
for i := 0; 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})
|
sink = LinearFromSRGB(color.NRGBA{R: byte(i), G: byte(i >> 8), B: byte(i >> 16), A: 0xFF})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
b.Run("translucent", func(b *testing.B) {
|
b.Run("translucent", func(b *testing.B) {
|
||||||
for i := 0; 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})
|
sink = LinearFromSRGB(color.NRGBA{R: byte(i), G: byte(i >> 8), B: byte(i >> 16), A: 0x50})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
b.Run("transparent", func(b *testing.B) {
|
b.Run("transparent", func(b *testing.B) {
|
||||||
for i := 0; 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})
|
sink = LinearFromSRGB(color.NRGBA{R: byte(i), G: byte(i >> 8), B: byte(i >> 16), A: 0x00})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ func (e *Extrapolation) Estimate() Estimate {
|
|||||||
first := e.get(0)
|
first := e.get(0)
|
||||||
t := first.t
|
t := first.t
|
||||||
// Walk backwards collecting samples.
|
// Walk backwards collecting samples.
|
||||||
for i := 0; i < len(e.samples); i++ {
|
for i := range e.samples {
|
||||||
p := e.get(-i)
|
p := e.get(-i)
|
||||||
age := first.t - p.t
|
age := first.t - p.t
|
||||||
if age >= maxAge || t-p.t >= maxSampleGap {
|
if age >= maxAge || t-p.t >= maxSampleGap {
|
||||||
@@ -172,9 +172,9 @@ func decomposeQR(A *matrix) (*matrix, *matrix, bool) {
|
|||||||
// https://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process
|
// https://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process
|
||||||
Q := newMatrix(A.rows, A.cols) // Column-major.
|
Q := newMatrix(A.rows, A.cols) // Column-major.
|
||||||
Rt := newMatrix(A.rows, A.rows) // R transposed, row-major.
|
Rt := newMatrix(A.rows, A.rows) // R transposed, row-major.
|
||||||
for i := 0; i < Q.rows; i++ {
|
for i := range Q.rows {
|
||||||
// Copy A column.
|
// Copy A column.
|
||||||
for j := 0; j < Q.cols; j++ {
|
for j := range Q.cols {
|
||||||
Q.set(i, j, A.get(i, j))
|
Q.set(i, j, A.get(i, j))
|
||||||
}
|
}
|
||||||
// Subtract projections. Note that int the projection
|
// Subtract projections. Note that int the projection
|
||||||
@@ -184,9 +184,9 @@ func decomposeQR(A *matrix) (*matrix, *matrix, bool) {
|
|||||||
// the normalized column e replaces u, where <e, e> = 1:
|
// the normalized column e replaces u, where <e, e> = 1:
|
||||||
//
|
//
|
||||||
// proje a = <e, a>/<e, e> e = <e, a> e
|
// proje a = <e, a>/<e, e> e = <e, a> e
|
||||||
for j := 0; j < i; j++ {
|
for j := range i {
|
||||||
d := dot(Q.col(j), Q.col(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))
|
Q.set(i, k, Q.get(i, k)-d*Q.get(j, k))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,7 +197,7 @@ func decomposeQR(A *matrix) (*matrix, *matrix, bool) {
|
|||||||
return nil, nil, false
|
return nil, nil, false
|
||||||
}
|
}
|
||||||
invNorm := 1 / n
|
invNorm := 1 / n
|
||||||
for j := 0; j < Q.cols; j++ {
|
for j := range Q.cols {
|
||||||
Q.set(i, j, Q.get(i, j)*invNorm)
|
Q.set(i, j, Q.get(i, j)*invNorm)
|
||||||
}
|
}
|
||||||
// Update Rt.
|
// Update Rt.
|
||||||
@@ -261,8 +261,8 @@ func (m *matrix) approxEqual(m2 *matrix) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const epsilon = 0.00001
|
const epsilon = 0.00001
|
||||||
for row := 0; row < m.rows; row++ {
|
for row := range m.rows {
|
||||||
for col := 0; col < m.cols; col++ {
|
for col := range m.cols {
|
||||||
d := m2.get(row, col) - m.get(row, col)
|
d := m2.get(row, col) - m.get(row, col)
|
||||||
if d < -epsilon || d > epsilon {
|
if d < -epsilon || d > epsilon {
|
||||||
return false
|
return false
|
||||||
@@ -278,8 +278,8 @@ func (m *matrix) transpose() *matrix {
|
|||||||
cols: m.rows,
|
cols: m.rows,
|
||||||
data: make([]float32, len(m.data)),
|
data: make([]float32, len(m.data)),
|
||||||
}
|
}
|
||||||
for i := 0; i < m.rows; i++ {
|
for i := range m.rows {
|
||||||
for j := 0; j < m.cols; j++ {
|
for j := range m.cols {
|
||||||
t.set(j, i, m.get(i, j))
|
t.set(j, i, m.get(i, j))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,10 +295,10 @@ func (m *matrix) mul(m2 *matrix) *matrix {
|
|||||||
cols: m2.cols,
|
cols: m2.cols,
|
||||||
data: make([]float32, m.rows*m2.cols),
|
data: make([]float32, m.rows*m2.cols),
|
||||||
}
|
}
|
||||||
for i := 0; i < mm.rows; i++ {
|
for i := range mm.rows {
|
||||||
for j := 0; j < mm.cols; j++ {
|
for j := range mm.cols {
|
||||||
var v float32
|
var v float32
|
||||||
for k := 0; k < m.rows; k++ {
|
for k := range m.rows {
|
||||||
v += m.get(k, j) * m2.get(i, k)
|
v += m.get(k, j) * m2.get(i, k)
|
||||||
}
|
}
|
||||||
mm.set(i, j, v)
|
mm.set(i, j, v)
|
||||||
@@ -309,8 +309,8 @@ func (m *matrix) mul(m2 *matrix) *matrix {
|
|||||||
|
|
||||||
func (m *matrix) String() string {
|
func (m *matrix) String() string {
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
for i := 0; i < m.rows; i++ {
|
for i := range m.rows {
|
||||||
for j := 0; j < m.cols; j++ {
|
for j := range m.cols {
|
||||||
v := m.get(i, j)
|
v := m.get(i, j)
|
||||||
b.WriteString(strconv.FormatFloat(float64(v), 'g', -1, 32))
|
b.WriteString(strconv.FormatFloat(float64(v), 'g', -1, 32))
|
||||||
b.WriteString(", ")
|
b.WriteString(", ")
|
||||||
|
|||||||
@@ -247,9 +247,11 @@ func (f *Functions) getExtension(name string) js.Value {
|
|||||||
func (f *Functions) ActiveTexture(t Enum) {
|
func (f *Functions) ActiveTexture(t Enum) {
|
||||||
f._activeTexture.Invoke(int(t))
|
f._activeTexture.Invoke(int(t))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) AttachShader(p Program, s Shader) {
|
func (f *Functions) AttachShader(p Program, s Shader) {
|
||||||
f._attachShader.Invoke(js.Value(p), js.Value(s))
|
f._attachShader.Invoke(js.Value(p), js.Value(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BeginQuery(target Enum, query Query) {
|
func (f *Functions) BeginQuery(target Enum, query Query) {
|
||||||
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
||||||
f._beginQuery.Invoke(int(target), js.Value(query))
|
f._beginQuery.Invoke(int(target), js.Value(query))
|
||||||
@@ -257,36 +259,47 @@ func (f *Functions) BeginQuery(target Enum, query Query) {
|
|||||||
f.EXT_disjoint_timer_query.Call("beginQueryEXT", int(target), js.Value(query))
|
f.EXT_disjoint_timer_query.Call("beginQueryEXT", int(target), js.Value(query))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BindAttribLocation(p Program, a Attrib, name string) {
|
func (f *Functions) BindAttribLocation(p Program, a Attrib, name string) {
|
||||||
f._bindAttribLocation.Invoke(js.Value(p), int(a), name)
|
f._bindAttribLocation.Invoke(js.Value(p), int(a), name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BindBuffer(target Enum, b Buffer) {
|
func (f *Functions) BindBuffer(target Enum, b Buffer) {
|
||||||
f._bindBuffer.Invoke(int(target), js.Value(b))
|
f._bindBuffer.Invoke(int(target), js.Value(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BindBufferBase(target Enum, index int, b Buffer) {
|
func (f *Functions) BindBufferBase(target Enum, index int, b Buffer) {
|
||||||
f._bindBufferBase.Invoke(int(target), index, js.Value(b))
|
f._bindBufferBase.Invoke(int(target), index, js.Value(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BindFramebuffer(target Enum, fb Framebuffer) {
|
func (f *Functions) BindFramebuffer(target Enum, fb Framebuffer) {
|
||||||
f._bindFramebuffer.Invoke(int(target), js.Value(fb))
|
f._bindFramebuffer.Invoke(int(target), js.Value(fb))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BindRenderbuffer(target Enum, rb Renderbuffer) {
|
func (f *Functions) BindRenderbuffer(target Enum, rb Renderbuffer) {
|
||||||
f._bindRenderbuffer.Invoke(int(target), js.Value(rb))
|
f._bindRenderbuffer.Invoke(int(target), js.Value(rb))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BindTexture(target Enum, t Texture) {
|
func (f *Functions) BindTexture(target Enum, t Texture) {
|
||||||
f._bindTexture.Invoke(int(target), js.Value(t))
|
f._bindTexture.Invoke(int(target), js.Value(t))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BindImageTexture(unit int, t Texture, level int, layered bool, layer int, access, format Enum) {
|
func (f *Functions) BindImageTexture(unit int, t Texture, level int, layered bool, layer int, access, format Enum) {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BindVertexArray(a VertexArray) {
|
func (f *Functions) BindVertexArray(a VertexArray) {
|
||||||
panic("not supported")
|
panic("not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BlendEquation(mode Enum) {
|
func (f *Functions) BlendEquation(mode Enum) {
|
||||||
f._blendEquation.Invoke(int(mode))
|
f._blendEquation.Invoke(int(mode))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BlendFuncSeparate(srcRGB, dstRGB, srcA, dstA Enum) {
|
func (f *Functions) BlendFuncSeparate(srcRGB, dstRGB, srcA, dstA Enum) {
|
||||||
f._blendFunc.Invoke(int(srcRGB), int(dstRGB), int(srcA), int(dstA))
|
f._blendFunc.Invoke(int(srcRGB), int(dstRGB), int(srcA), int(dstA))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BufferData(target Enum, size int, usage Enum, data []byte) {
|
func (f *Functions) BufferData(target Enum, size int, usage Enum, data []byte) {
|
||||||
if data == nil {
|
if data == nil {
|
||||||
f._bufferData.Invoke(int(target), size, int(usage))
|
f._bufferData.Invoke(int(target), size, int(usage))
|
||||||
@@ -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))
|
f._bufferData.Invoke(int(target), f.byteArrayOf(data), int(usage))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
|
func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
|
||||||
f._bufferSubData.Invoke(int(target), offset, f.byteArrayOf(src))
|
f._bufferSubData.Invoke(int(target), offset, f.byteArrayOf(src))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) CheckFramebufferStatus(target Enum) Enum {
|
func (f *Functions) CheckFramebufferStatus(target Enum) Enum {
|
||||||
status := Enum(f._checkFramebufferStatus.Invoke(int(target)).Int())
|
status := Enum(f._checkFramebufferStatus.Invoke(int(target)).Int())
|
||||||
if status != FRAMEBUFFER_COMPLETE && f.Ctx.Call("isContextLost").Bool() {
|
if status != FRAMEBUFFER_COMPLETE && f.Ctx.Call("isContextLost").Bool() {
|
||||||
@@ -308,54 +323,71 @@ func (f *Functions) CheckFramebufferStatus(target Enum) Enum {
|
|||||||
}
|
}
|
||||||
return status
|
return status
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) Clear(mask Enum) {
|
func (f *Functions) Clear(mask Enum) {
|
||||||
f._clear.Invoke(int(mask))
|
f._clear.Invoke(int(mask))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) ClearColor(red, green, blue, alpha float32) {
|
func (f *Functions) ClearColor(red, green, blue, alpha float32) {
|
||||||
f._clearColor.Invoke(red, green, blue, alpha)
|
f._clearColor.Invoke(red, green, blue, alpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) ClearDepthf(d float32) {
|
func (f *Functions) ClearDepthf(d float32) {
|
||||||
f._clearDepth.Invoke(d)
|
f._clearDepth.Invoke(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) CompileShader(s Shader) {
|
func (f *Functions) CompileShader(s Shader) {
|
||||||
f._compileShader.Invoke(js.Value(s))
|
f._compileShader.Invoke(js.Value(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) CopyTexSubImage2D(target Enum, level, xoffset, yoffset, x, y, width, height int) {
|
func (f *Functions) CopyTexSubImage2D(target Enum, level, xoffset, yoffset, x, y, width, height int) {
|
||||||
f._copyTexSubImage2D.Invoke(int(target), level, xoffset, yoffset, x, y, width, height)
|
f._copyTexSubImage2D.Invoke(int(target), level, xoffset, yoffset, x, y, width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) CreateBuffer() Buffer {
|
func (f *Functions) CreateBuffer() Buffer {
|
||||||
return Buffer(f._createBuffer.Invoke())
|
return Buffer(f._createBuffer.Invoke())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) CreateFramebuffer() Framebuffer {
|
func (f *Functions) CreateFramebuffer() Framebuffer {
|
||||||
return Framebuffer(f._createFramebuffer.Invoke())
|
return Framebuffer(f._createFramebuffer.Invoke())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) CreateProgram() Program {
|
func (f *Functions) CreateProgram() Program {
|
||||||
return Program(f._createProgram.Invoke())
|
return Program(f._createProgram.Invoke())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) CreateQuery() Query {
|
func (f *Functions) CreateQuery() Query {
|
||||||
return Query(f._createQuery.Invoke())
|
return Query(f._createQuery.Invoke())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) CreateRenderbuffer() Renderbuffer {
|
func (f *Functions) CreateRenderbuffer() Renderbuffer {
|
||||||
return Renderbuffer(f._createRenderbuffer.Invoke())
|
return Renderbuffer(f._createRenderbuffer.Invoke())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) CreateShader(ty Enum) Shader {
|
func (f *Functions) CreateShader(ty Enum) Shader {
|
||||||
return Shader(f._createShader.Invoke(int(ty)))
|
return Shader(f._createShader.Invoke(int(ty)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) CreateTexture() Texture {
|
func (f *Functions) CreateTexture() Texture {
|
||||||
return Texture(f._createTexture.Invoke())
|
return Texture(f._createTexture.Invoke())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) CreateVertexArray() VertexArray {
|
func (f *Functions) CreateVertexArray() VertexArray {
|
||||||
panic("not supported")
|
panic("not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DeleteBuffer(v Buffer) {
|
func (f *Functions) DeleteBuffer(v Buffer) {
|
||||||
f._deleteBuffer.Invoke(js.Value(v))
|
f._deleteBuffer.Invoke(js.Value(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DeleteFramebuffer(v Framebuffer) {
|
func (f *Functions) DeleteFramebuffer(v Framebuffer) {
|
||||||
f._deleteFramebuffer.Invoke(js.Value(v))
|
f._deleteFramebuffer.Invoke(js.Value(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DeleteProgram(p Program) {
|
func (f *Functions) DeleteProgram(p Program) {
|
||||||
f._deleteProgram.Invoke(js.Value(p))
|
f._deleteProgram.Invoke(js.Value(p))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DeleteQuery(query Query) {
|
func (f *Functions) DeleteQuery(query Query) {
|
||||||
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
||||||
f._deleteQuery.Invoke(js.Value(query))
|
f._deleteQuery.Invoke(js.Value(query))
|
||||||
@@ -363,45 +395,59 @@ func (f *Functions) DeleteQuery(query Query) {
|
|||||||
f.EXT_disjoint_timer_query.Call("deleteQueryEXT", js.Value(query))
|
f.EXT_disjoint_timer_query.Call("deleteQueryEXT", js.Value(query))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DeleteShader(s Shader) {
|
func (f *Functions) DeleteShader(s Shader) {
|
||||||
f._deleteShader.Invoke(js.Value(s))
|
f._deleteShader.Invoke(js.Value(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DeleteRenderbuffer(v Renderbuffer) {
|
func (f *Functions) DeleteRenderbuffer(v Renderbuffer) {
|
||||||
f._deleteRenderbuffer.Invoke(js.Value(v))
|
f._deleteRenderbuffer.Invoke(js.Value(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DeleteTexture(v Texture) {
|
func (f *Functions) DeleteTexture(v Texture) {
|
||||||
f._deleteTexture.Invoke(js.Value(v))
|
f._deleteTexture.Invoke(js.Value(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DeleteVertexArray(a VertexArray) {
|
func (f *Functions) DeleteVertexArray(a VertexArray) {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DepthFunc(fn Enum) {
|
func (f *Functions) DepthFunc(fn Enum) {
|
||||||
f._depthFunc.Invoke(int(fn))
|
f._depthFunc.Invoke(int(fn))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DepthMask(mask bool) {
|
func (f *Functions) DepthMask(mask bool) {
|
||||||
f._depthMask.Invoke(mask)
|
f._depthMask.Invoke(mask)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DisableVertexAttribArray(a Attrib) {
|
func (f *Functions) DisableVertexAttribArray(a Attrib) {
|
||||||
f._disableVertexAttribArray.Invoke(int(a))
|
f._disableVertexAttribArray.Invoke(int(a))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) Disable(cap Enum) {
|
func (f *Functions) Disable(cap Enum) {
|
||||||
f._disable.Invoke(int(cap))
|
f._disable.Invoke(int(cap))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DrawArrays(mode Enum, first, count int) {
|
func (f *Functions) DrawArrays(mode Enum, first, count int) {
|
||||||
f._drawArrays.Invoke(int(mode), first, count)
|
f._drawArrays.Invoke(int(mode), first, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DrawElements(mode Enum, count int, ty Enum, offset int) {
|
func (f *Functions) DrawElements(mode Enum, count int, ty Enum, offset int) {
|
||||||
f._drawElements.Invoke(int(mode), count, int(ty), offset)
|
f._drawElements.Invoke(int(mode), count, int(ty), offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DispatchCompute(x, y, z int) {
|
func (f *Functions) DispatchCompute(x, y, z int) {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) Enable(cap Enum) {
|
func (f *Functions) Enable(cap Enum) {
|
||||||
f._enable.Invoke(int(cap))
|
f._enable.Invoke(int(cap))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) EnableVertexAttribArray(a Attrib) {
|
func (f *Functions) EnableVertexAttribArray(a Attrib) {
|
||||||
f._enableVertexAttribArray.Invoke(int(a))
|
f._enableVertexAttribArray.Invoke(int(a))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) EndQuery(target Enum) {
|
func (f *Functions) EndQuery(target Enum) {
|
||||||
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
||||||
f._endQuery.Invoke(int(target))
|
f._endQuery.Invoke(int(target))
|
||||||
@@ -409,28 +455,36 @@ func (f *Functions) EndQuery(target Enum) {
|
|||||||
f.EXT_disjoint_timer_query.Call("endQueryEXT", int(target))
|
f.EXT_disjoint_timer_query.Call("endQueryEXT", int(target))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) Finish() {
|
func (f *Functions) Finish() {
|
||||||
f._finish.Invoke()
|
f._finish.Invoke()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) Flush() {
|
func (f *Functions) Flush() {
|
||||||
f._flush.Invoke()
|
f._flush.Invoke()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer) {
|
func (f *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer) {
|
||||||
f._framebufferRenderbuffer.Invoke(int(target), int(attachment), int(renderbuffertarget), js.Value(renderbuffer))
|
f._framebufferRenderbuffer.Invoke(int(target), int(attachment), int(renderbuffertarget), js.Value(renderbuffer))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int) {
|
func (f *Functions) FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int) {
|
||||||
f._framebufferTexture2D.Invoke(int(target), int(attachment), int(texTarget), js.Value(t), level)
|
f._framebufferTexture2D.Invoke(int(target), int(attachment), int(texTarget), js.Value(t), level)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GenerateMipmap(target Enum) {
|
func (f *Functions) GenerateMipmap(target Enum) {
|
||||||
f._generateMipmap.Invoke(int(target))
|
f._generateMipmap.Invoke(int(target))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetError() Enum {
|
func (f *Functions) GetError() Enum {
|
||||||
// Avoid slow getError calls. See gio#179.
|
// Avoid slow getError calls. See gio#179.
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetRenderbufferParameteri(target, pname Enum) int {
|
func (f *Functions) GetRenderbufferParameteri(target, pname Enum) int {
|
||||||
return paramVal(f._getRenderbufferParameteri.Invoke(int(pname)))
|
return paramVal(f._getRenderbufferParameteri.Invoke(int(pname)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int {
|
func (f *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int {
|
||||||
if !f.isWebGL2 && pname == FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING {
|
if !f.isWebGL2 && pname == FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING {
|
||||||
// FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING is only available on WebGL 2
|
// FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING is only available on WebGL 2
|
||||||
@@ -438,6 +492,7 @@ func (f *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname
|
|||||||
}
|
}
|
||||||
return paramVal(f._getFramebufferAttachmentParameter.Invoke(int(target), int(attachment), int(pname)))
|
return paramVal(f._getFramebufferAttachmentParameter.Invoke(int(target), int(attachment), int(pname)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetBinding(pname Enum) Object {
|
func (f *Functions) GetBinding(pname Enum) Object {
|
||||||
obj := f._getParameter.Invoke(int(pname))
|
obj := f._getParameter.Invoke(int(pname))
|
||||||
if !obj.Truthy() {
|
if !obj.Truthy() {
|
||||||
@@ -445,6 +500,7 @@ func (f *Functions) GetBinding(pname Enum) Object {
|
|||||||
}
|
}
|
||||||
return Object(obj)
|
return Object(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetBindingi(pname Enum, idx int) Object {
|
func (f *Functions) GetBindingi(pname Enum, idx int) Object {
|
||||||
obj := f._getIndexedParameter.Invoke(int(pname), idx)
|
obj := f._getIndexedParameter.Invoke(int(pname), idx)
|
||||||
if !obj.Truthy() {
|
if !obj.Truthy() {
|
||||||
@@ -452,6 +508,7 @@ func (f *Functions) GetBindingi(pname Enum, idx int) Object {
|
|||||||
}
|
}
|
||||||
return Object(obj)
|
return Object(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetInteger(pname Enum) int {
|
func (f *Functions) GetInteger(pname Enum) int {
|
||||||
if !f.isWebGL2 {
|
if !f.isWebGL2 {
|
||||||
switch pname {
|
switch pname {
|
||||||
@@ -461,9 +518,11 @@ func (f *Functions) GetInteger(pname Enum) int {
|
|||||||
}
|
}
|
||||||
return paramVal(f._getParameter.Invoke(int(pname)))
|
return paramVal(f._getParameter.Invoke(int(pname)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetFloat(pname Enum) float32 {
|
func (f *Functions) GetFloat(pname Enum) float32 {
|
||||||
return float32(f._getParameter.Invoke(int(pname)).Float())
|
return float32(f._getParameter.Invoke(int(pname)).Float())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetInteger4(pname Enum) [4]int {
|
func (f *Functions) GetInteger4(pname Enum) [4]int {
|
||||||
arr := f._getParameter.Invoke(int(pname))
|
arr := f._getParameter.Invoke(int(pname))
|
||||||
var res [4]int
|
var res [4]int
|
||||||
@@ -472,6 +531,7 @@ func (f *Functions) GetInteger4(pname Enum) [4]int {
|
|||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetFloat4(pname Enum) [4]float32 {
|
func (f *Functions) GetFloat4(pname Enum) [4]float32 {
|
||||||
arr := f._getParameter.Invoke(int(pname))
|
arr := f._getParameter.Invoke(int(pname))
|
||||||
var res [4]float32
|
var res [4]float32
|
||||||
@@ -480,12 +540,15 @@ func (f *Functions) GetFloat4(pname Enum) [4]float32 {
|
|||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetProgrami(p Program, pname Enum) int {
|
func (f *Functions) GetProgrami(p Program, pname Enum) int {
|
||||||
return paramVal(f._getProgramParameter.Invoke(js.Value(p), int(pname)))
|
return paramVal(f._getProgramParameter.Invoke(js.Value(p), int(pname)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetProgramInfoLog(p Program) string {
|
func (f *Functions) GetProgramInfoLog(p Program) string {
|
||||||
return f._getProgramInfoLog.Invoke(js.Value(p)).String()
|
return f._getProgramInfoLog.Invoke(js.Value(p)).String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetQueryObjectuiv(query Query, pname Enum) uint {
|
func (f *Functions) GetQueryObjectuiv(query Query, pname Enum) uint {
|
||||||
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
||||||
return uint(paramVal(f._getQueryParameter.Invoke(js.Value(query), int(pname))))
|
return uint(paramVal(f._getQueryParameter.Invoke(js.Value(query), int(pname))))
|
||||||
@@ -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))))
|
return uint(paramVal(f.EXT_disjoint_timer_query.Call("getQueryObjectEXT", js.Value(query), int(pname))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetShaderi(s Shader, pname Enum) int {
|
func (f *Functions) GetShaderi(s Shader, pname Enum) int {
|
||||||
return paramVal(f._getShaderParameter.Invoke(js.Value(s), int(pname)))
|
return paramVal(f._getShaderParameter.Invoke(js.Value(s), int(pname)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetShaderInfoLog(s Shader) string {
|
func (f *Functions) GetShaderInfoLog(s Shader) string {
|
||||||
return f._getShaderInfoLog.Invoke(js.Value(s)).String()
|
return f._getShaderInfoLog.Invoke(js.Value(s)).String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetString(pname Enum) string {
|
func (f *Functions) GetString(pname Enum) string {
|
||||||
switch pname {
|
switch pname {
|
||||||
case EXTENSIONS:
|
case EXTENSIONS:
|
||||||
@@ -512,15 +578,19 @@ func (f *Functions) GetString(pname Enum) string {
|
|||||||
return f._getParameter.Invoke(int(pname)).String()
|
return f._getParameter.Invoke(int(pname)).String()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetUniformBlockIndex(p Program, name string) uint {
|
func (f *Functions) GetUniformBlockIndex(p Program, name string) uint {
|
||||||
return uint(paramVal(f._getUniformBlockIndex.Invoke(js.Value(p), name)))
|
return uint(paramVal(f._getUniformBlockIndex.Invoke(js.Value(p), name)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetUniformLocation(p Program, name string) Uniform {
|
func (f *Functions) GetUniformLocation(p Program, name string) Uniform {
|
||||||
return Uniform(f._getUniformLocation.Invoke(js.Value(p), name))
|
return Uniform(f._getUniformLocation.Invoke(js.Value(p), name))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetVertexAttrib(index int, pname Enum) int {
|
func (f *Functions) GetVertexAttrib(index int, pname Enum) int {
|
||||||
return paramVal(f._getVertexAttrib.Invoke(index, int(pname)))
|
return paramVal(f._getVertexAttrib.Invoke(index, int(pname)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetVertexAttribBinding(index int, pname Enum) Object {
|
func (f *Functions) GetVertexAttribBinding(index int, pname Enum) Object {
|
||||||
obj := f._getVertexAttrib.Invoke(index, int(pname))
|
obj := f._getVertexAttrib.Invoke(index, int(pname))
|
||||||
if !obj.Truthy() {
|
if !obj.Truthy() {
|
||||||
@@ -528,9 +598,11 @@ func (f *Functions) GetVertexAttribBinding(index int, pname Enum) Object {
|
|||||||
}
|
}
|
||||||
return Object(obj)
|
return Object(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetVertexAttribPointer(index int, pname Enum) uintptr {
|
func (f *Functions) GetVertexAttribPointer(index int, pname Enum) uintptr {
|
||||||
return uintptr(f._getVertexAttribOffset.Invoke(index, int(pname)).Int())
|
return uintptr(f._getVertexAttribOffset.Invoke(index, int(pname)).Int())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) InvalidateFramebuffer(target, attachment Enum) {
|
func (f *Functions) InvalidateFramebuffer(target, attachment Enum) {
|
||||||
fn := f.Ctx.Get("invalidateFramebuffer")
|
fn := f.Ctx.Get("invalidateFramebuffer")
|
||||||
if !fn.IsUndefined() {
|
if !fn.IsUndefined() {
|
||||||
@@ -541,74 +613,97 @@ func (f *Functions) InvalidateFramebuffer(target, attachment Enum) {
|
|||||||
f._invalidateFramebuffer.Invoke(int(target), f.int32Buf)
|
f._invalidateFramebuffer.Invoke(int(target), f.int32Buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) IsEnabled(cap Enum) bool {
|
func (f *Functions) IsEnabled(cap Enum) bool {
|
||||||
return f._isEnabled.Invoke(int(cap)).Truthy()
|
return f._isEnabled.Invoke(int(cap)).Truthy()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) LinkProgram(p Program) {
|
func (f *Functions) LinkProgram(p Program) {
|
||||||
f._linkProgram.Invoke(js.Value(p))
|
f._linkProgram.Invoke(js.Value(p))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) PixelStorei(pname Enum, param int) {
|
func (f *Functions) PixelStorei(pname Enum, param int) {
|
||||||
f._pixelStorei.Invoke(int(pname), param)
|
f._pixelStorei.Invoke(int(pname), param)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) MemoryBarrier(barriers Enum) {
|
func (f *Functions) MemoryBarrier(barriers Enum) {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) MapBufferRange(target Enum, offset, length int, access Enum) []byte {
|
func (f *Functions) MapBufferRange(target Enum, offset, length int, access Enum) []byte {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) RenderbufferStorage(target, internalformat Enum, width, height int) {
|
func (f *Functions) RenderbufferStorage(target, internalformat Enum, width, height int) {
|
||||||
f._renderbufferStorage.Invoke(int(target), int(internalformat), width, height)
|
f._renderbufferStorage.Invoke(int(target), int(internalformat), width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) ReadPixels(x, y, width, height int, format, ty Enum, data []byte) {
|
func (f *Functions) ReadPixels(x, y, width, height int, format, ty Enum, data []byte) {
|
||||||
ba := f.byteArrayOf(data)
|
ba := f.byteArrayOf(data)
|
||||||
f._readPixels.Invoke(x, y, width, height, int(format), int(ty), ba)
|
f._readPixels.Invoke(x, y, width, height, int(format), int(ty), ba)
|
||||||
js.CopyBytesToGo(data, ba)
|
js.CopyBytesToGo(data, ba)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) Scissor(x, y, width, height int32) {
|
func (f *Functions) Scissor(x, y, width, height int32) {
|
||||||
f._scissor.Invoke(x, y, width, height)
|
f._scissor.Invoke(x, y, width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) ShaderSource(s Shader, src string) {
|
func (f *Functions) ShaderSource(s Shader, src string) {
|
||||||
f._shaderSource.Invoke(js.Value(s), src)
|
f._shaderSource.Invoke(js.Value(s), src)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) TexImage2D(target Enum, level int, internalFormat Enum, width, height int, format, ty Enum) {
|
func (f *Functions) TexImage2D(target Enum, level int, internalFormat Enum, width, height int, format, ty Enum) {
|
||||||
f._texImage2D.Invoke(int(target), int(level), int(internalFormat), int(width), int(height), 0, int(format), int(ty), nil)
|
f._texImage2D.Invoke(int(target), int(level), int(internalFormat), int(width), int(height), 0, int(format), int(ty), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) TexStorage2D(target Enum, levels int, internalFormat Enum, width, height int) {
|
func (f *Functions) TexStorage2D(target Enum, levels int, internalFormat Enum, width, height int) {
|
||||||
f._texStorage2D.Invoke(int(target), levels, int(internalFormat), width, height)
|
f._texStorage2D.Invoke(int(target), levels, int(internalFormat), width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte) {
|
func (f *Functions) TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte) {
|
||||||
f._texSubImage2D.Invoke(int(target), level, x, y, width, height, int(format), int(ty), f.byteArrayOf(data))
|
f._texSubImage2D.Invoke(int(target), level, x, y, width, height, int(format), int(ty), f.byteArrayOf(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) TexParameteri(target, pname Enum, param int) {
|
func (f *Functions) TexParameteri(target, pname Enum, param int) {
|
||||||
f._texParameteri.Invoke(int(target), int(pname), int(param))
|
f._texParameteri.Invoke(int(target), int(pname), int(param))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) UniformBlockBinding(p Program, uniformBlockIndex uint, uniformBlockBinding uint) {
|
func (f *Functions) UniformBlockBinding(p Program, uniformBlockIndex uint, uniformBlockBinding uint) {
|
||||||
f._uniformBlockBinding.Invoke(js.Value(p), int(uniformBlockIndex), int(uniformBlockBinding))
|
f._uniformBlockBinding.Invoke(js.Value(p), int(uniformBlockIndex), int(uniformBlockBinding))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) Uniform1f(dst Uniform, v float32) {
|
func (f *Functions) Uniform1f(dst Uniform, v float32) {
|
||||||
f._uniform1f.Invoke(js.Value(dst), v)
|
f._uniform1f.Invoke(js.Value(dst), v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) Uniform1i(dst Uniform, v int) {
|
func (f *Functions) Uniform1i(dst Uniform, v int) {
|
||||||
f._uniform1i.Invoke(js.Value(dst), v)
|
f._uniform1i.Invoke(js.Value(dst), v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) Uniform2f(dst Uniform, v0, v1 float32) {
|
func (f *Functions) Uniform2f(dst Uniform, v0, v1 float32) {
|
||||||
f._uniform2f.Invoke(js.Value(dst), v0, v1)
|
f._uniform2f.Invoke(js.Value(dst), v0, v1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) Uniform3f(dst Uniform, v0, v1, v2 float32) {
|
func (f *Functions) Uniform3f(dst Uniform, v0, v1, v2 float32) {
|
||||||
f._uniform3f.Invoke(js.Value(dst), v0, v1, v2)
|
f._uniform3f.Invoke(js.Value(dst), v0, v1, v2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) Uniform4f(dst Uniform, v0, v1, v2, v3 float32) {
|
func (f *Functions) Uniform4f(dst Uniform, v0, v1, v2, v3 float32) {
|
||||||
f._uniform4f.Invoke(js.Value(dst), v0, v1, v2, v3)
|
f._uniform4f.Invoke(js.Value(dst), v0, v1, v2, v3)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) UseProgram(p Program) {
|
func (f *Functions) UseProgram(p Program) {
|
||||||
f._useProgram.Invoke(js.Value(p))
|
f._useProgram.Invoke(js.Value(p))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) UnmapBuffer(target Enum) bool {
|
func (f *Functions) UnmapBuffer(target Enum) bool {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int) {
|
func (f *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int) {
|
||||||
f._vertexAttribPointer.Invoke(int(dst), size, int(ty), normalized, stride, offset)
|
f._vertexAttribPointer.Invoke(int(dst), size, int(ty), normalized, stride, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) Viewport(x, y, width, height int) {
|
func (f *Functions) Viewport(x, y, width, height int) {
|
||||||
f._viewport.Invoke(x, y, width, height)
|
f._viewport.Invoke(x, y, width, height)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -665,7 +665,7 @@ func (f *Functions) load(forceES bool) error {
|
|||||||
case runtime.GOOS == "android":
|
case runtime.GOOS == "android":
|
||||||
libNames = []string{"libGLESv2.so", "libGLESv3.so"}
|
libNames = []string{"libGLESv2.so", "libGLESv3.so"}
|
||||||
default:
|
default:
|
||||||
libNames = []string{"libGLESv2.so.2"}
|
libNames = []string{"libGLESv2.so.2", "libGLESv2.so.3.0"}
|
||||||
}
|
}
|
||||||
for _, lib := range libNames {
|
for _, lib := range libNames {
|
||||||
if h := dlopen(lib); h != nil {
|
if h := dlopen(lib); h != nil {
|
||||||
|
|||||||
+306
-91
@@ -3,103 +3,217 @@
|
|||||||
package gl
|
package gl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func loadGLESv2Procs() error {
|
||||||
|
dllName := "libGLESv2.dll"
|
||||||
|
handle, err := windows.LoadLibraryEx(dllName, 0, windows.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("gl: failed to load %s: %v", dllName, err)
|
||||||
|
}
|
||||||
|
gles := windows.DLL{Handle: handle, Name: dllName}
|
||||||
|
// d3dcompiler_47.dll is needed internally for shader compilation to function.
|
||||||
|
dllName = "d3dcompiler_47.dll"
|
||||||
|
_, err = windows.LoadLibraryEx(dllName, 0, windows.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("gl: failed to load %s: %v", dllName, err)
|
||||||
|
}
|
||||||
|
procs := map[string]**windows.Proc{
|
||||||
|
"glActiveTexture": &_glActiveTexture,
|
||||||
|
"glAttachShader": &_glAttachShader,
|
||||||
|
"glBeginQuery": &_glBeginQuery,
|
||||||
|
"glBindAttribLocation": &_glBindAttribLocation,
|
||||||
|
"glBindBuffer": &_glBindBuffer,
|
||||||
|
"glBindBufferBase": &_glBindBufferBase,
|
||||||
|
"glBindFramebuffer": &_glBindFramebuffer,
|
||||||
|
"glBindRenderbuffer": &_glBindRenderbuffer,
|
||||||
|
"glBindTexture": &_glBindTexture,
|
||||||
|
"glBindVertexArray": &_glBindVertexArray,
|
||||||
|
"glBlendEquation": &_glBlendEquation,
|
||||||
|
"glBlendFuncSeparate": &_glBlendFuncSeparate,
|
||||||
|
"glBufferData": &_glBufferData,
|
||||||
|
"glBufferSubData": &_glBufferSubData,
|
||||||
|
"glCheckFramebufferStatus": &_glCheckFramebufferStatus,
|
||||||
|
"glClear": &_glClear,
|
||||||
|
"glClearColor": &_glClearColor,
|
||||||
|
"glClearDepthf": &_glClearDepthf,
|
||||||
|
"glDeleteQueries": &_glDeleteQueries,
|
||||||
|
"glDeleteVertexArrays": &_glDeleteVertexArrays,
|
||||||
|
"glCompileShader": &_glCompileShader,
|
||||||
|
"glCopyTexSubImage2D": &_glCopyTexSubImage2D,
|
||||||
|
"glGenerateMipmap": &_glGenerateMipmap,
|
||||||
|
"glGenBuffers": &_glGenBuffers,
|
||||||
|
"glGenFramebuffers": &_glGenFramebuffers,
|
||||||
|
"glGenVertexArrays": &_glGenVertexArrays,
|
||||||
|
"glGetUniformBlockIndex": &_glGetUniformBlockIndex,
|
||||||
|
"glCreateProgram": &_glCreateProgram,
|
||||||
|
"glGenRenderbuffers": &_glGenRenderbuffers,
|
||||||
|
"glCreateShader": &_glCreateShader,
|
||||||
|
"glGenTextures": &_glGenTextures,
|
||||||
|
"glDeleteBuffers": &_glDeleteBuffers,
|
||||||
|
"glDeleteFramebuffers": &_glDeleteFramebuffers,
|
||||||
|
"glDeleteProgram": &_glDeleteProgram,
|
||||||
|
"glDeleteShader": &_glDeleteShader,
|
||||||
|
"glDeleteRenderbuffers": &_glDeleteRenderbuffers,
|
||||||
|
"glDeleteTextures": &_glDeleteTextures,
|
||||||
|
"glDepthFunc": &_glDepthFunc,
|
||||||
|
"glDepthMask": &_glDepthMask,
|
||||||
|
"glDisableVertexAttribArray": &_glDisableVertexAttribArray,
|
||||||
|
"glDisable": &_glDisable,
|
||||||
|
"glDrawArrays": &_glDrawArrays,
|
||||||
|
"glDrawElements": &_glDrawElements,
|
||||||
|
"glEnable": &_glEnable,
|
||||||
|
"glEnableVertexAttribArray": &_glEnableVertexAttribArray,
|
||||||
|
"glEndQuery": &_glEndQuery,
|
||||||
|
"glFinish": &_glFinish,
|
||||||
|
"glFlush": &_glFlush,
|
||||||
|
"glFramebufferRenderbuffer": &_glFramebufferRenderbuffer,
|
||||||
|
"glFramebufferTexture2D": &_glFramebufferTexture2D,
|
||||||
|
"glGenQueries": &_glGenQueries,
|
||||||
|
"glGetError": &_glGetError,
|
||||||
|
"glGetRenderbufferParameteriv": &_glGetRenderbufferParameteriv,
|
||||||
|
"glGetFloatv": &_glGetFloatv,
|
||||||
|
"glGetFramebufferAttachmentParameteriv": &_glGetFramebufferAttachmentParameteriv,
|
||||||
|
"glGetIntegerv": &_glGetIntegerv,
|
||||||
|
"glGetIntegeri_v": &_glGetIntegeri_v,
|
||||||
|
"glGetProgramiv": &_glGetProgramiv,
|
||||||
|
"glGetProgramInfoLog": &_glGetProgramInfoLog,
|
||||||
|
"glGetQueryObjectuiv": &_glGetQueryObjectuiv,
|
||||||
|
"glGetShaderiv": &_glGetShaderiv,
|
||||||
|
"glGetShaderInfoLog": &_glGetShaderInfoLog,
|
||||||
|
"glGetString": &_glGetString,
|
||||||
|
"glGetUniformLocation": &_glGetUniformLocation,
|
||||||
|
"glGetVertexAttribiv": &_glGetVertexAttribiv,
|
||||||
|
"glGetVertexAttribPointerv": &_glGetVertexAttribPointerv,
|
||||||
|
"glInvalidateFramebuffer": &_glInvalidateFramebuffer,
|
||||||
|
"glIsEnabled": &_glIsEnabled,
|
||||||
|
"glLinkProgram": &_glLinkProgram,
|
||||||
|
"glPixelStorei": &_glPixelStorei,
|
||||||
|
"glReadPixels": &_glReadPixels,
|
||||||
|
"glRenderbufferStorage": &_glRenderbufferStorage,
|
||||||
|
"glScissor": &_glScissor,
|
||||||
|
"glShaderSource": &_glShaderSource,
|
||||||
|
"glTexImage2D": &_glTexImage2D,
|
||||||
|
"glTexStorage2D": &_glTexStorage2D,
|
||||||
|
"glTexSubImage2D": &_glTexSubImage2D,
|
||||||
|
"glTexParameteri": &_glTexParameteri,
|
||||||
|
"glUniformBlockBinding": &_glUniformBlockBinding,
|
||||||
|
"glUniform1f": &_glUniform1f,
|
||||||
|
"glUniform1i": &_glUniform1i,
|
||||||
|
"glUniform2f": &_glUniform2f,
|
||||||
|
"glUniform3f": &_glUniform3f,
|
||||||
|
"glUniform4f": &_glUniform4f,
|
||||||
|
"glUseProgram": &_glUseProgram,
|
||||||
|
"glVertexAttribPointer": &_glVertexAttribPointer,
|
||||||
|
"glViewport": &_glViewport,
|
||||||
|
}
|
||||||
|
for name, proc := range procs {
|
||||||
|
p, err := gles.FindProc(name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to locate %s in %s: %w", name, gles.Name, err)
|
||||||
|
}
|
||||||
|
*proc = p
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
LibGLESv2 = windows.NewLazyDLL("libGLESv2.dll")
|
glInitOnce sync.Once
|
||||||
_glActiveTexture = LibGLESv2.NewProc("glActiveTexture")
|
_glActiveTexture *windows.Proc
|
||||||
_glAttachShader = LibGLESv2.NewProc("glAttachShader")
|
_glAttachShader *windows.Proc
|
||||||
_glBeginQuery = LibGLESv2.NewProc("glBeginQuery")
|
_glBeginQuery *windows.Proc
|
||||||
_glBindAttribLocation = LibGLESv2.NewProc("glBindAttribLocation")
|
_glBindAttribLocation *windows.Proc
|
||||||
_glBindBuffer = LibGLESv2.NewProc("glBindBuffer")
|
_glBindBuffer *windows.Proc
|
||||||
_glBindBufferBase = LibGLESv2.NewProc("glBindBufferBase")
|
_glBindBufferBase *windows.Proc
|
||||||
_glBindFramebuffer = LibGLESv2.NewProc("glBindFramebuffer")
|
_glBindFramebuffer *windows.Proc
|
||||||
_glBindRenderbuffer = LibGLESv2.NewProc("glBindRenderbuffer")
|
_glBindRenderbuffer *windows.Proc
|
||||||
_glBindTexture = LibGLESv2.NewProc("glBindTexture")
|
_glBindTexture *windows.Proc
|
||||||
_glBindVertexArray = LibGLESv2.NewProc("glBindVertexArray")
|
_glBindVertexArray *windows.Proc
|
||||||
_glBlendEquation = LibGLESv2.NewProc("glBlendEquation")
|
_glBlendEquation *windows.Proc
|
||||||
_glBlendFuncSeparate = LibGLESv2.NewProc("glBlendFuncSeparate")
|
_glBlendFuncSeparate *windows.Proc
|
||||||
_glBufferData = LibGLESv2.NewProc("glBufferData")
|
_glBufferData *windows.Proc
|
||||||
_glBufferSubData = LibGLESv2.NewProc("glBufferSubData")
|
_glBufferSubData *windows.Proc
|
||||||
_glCheckFramebufferStatus = LibGLESv2.NewProc("glCheckFramebufferStatus")
|
_glCheckFramebufferStatus *windows.Proc
|
||||||
_glClear = LibGLESv2.NewProc("glClear")
|
_glClear *windows.Proc
|
||||||
_glClearColor = LibGLESv2.NewProc("glClearColor")
|
_glClearColor *windows.Proc
|
||||||
_glClearDepthf = LibGLESv2.NewProc("glClearDepthf")
|
_glClearDepthf *windows.Proc
|
||||||
_glDeleteQueries = LibGLESv2.NewProc("glDeleteQueries")
|
_glDeleteQueries *windows.Proc
|
||||||
_glDeleteVertexArrays = LibGLESv2.NewProc("glDeleteVertexArrays")
|
_glDeleteVertexArrays *windows.Proc
|
||||||
_glCompileShader = LibGLESv2.NewProc("glCompileShader")
|
_glCompileShader *windows.Proc
|
||||||
_glCopyTexSubImage2D = LibGLESv2.NewProc("glCopyTexSubImage2D")
|
_glCopyTexSubImage2D *windows.Proc
|
||||||
_glGenerateMipmap = LibGLESv2.NewProc("glGenerateMipmap")
|
_glGenerateMipmap *windows.Proc
|
||||||
_glGenBuffers = LibGLESv2.NewProc("glGenBuffers")
|
_glGenBuffers *windows.Proc
|
||||||
_glGenFramebuffers = LibGLESv2.NewProc("glGenFramebuffers")
|
_glGenFramebuffers *windows.Proc
|
||||||
_glGenVertexArrays = LibGLESv2.NewProc("glGenVertexArrays")
|
_glGenVertexArrays *windows.Proc
|
||||||
_glGetUniformBlockIndex = LibGLESv2.NewProc("glGetUniformBlockIndex")
|
_glGetUniformBlockIndex *windows.Proc
|
||||||
_glCreateProgram = LibGLESv2.NewProc("glCreateProgram")
|
_glCreateProgram *windows.Proc
|
||||||
_glGenRenderbuffers = LibGLESv2.NewProc("glGenRenderbuffers")
|
_glGenRenderbuffers *windows.Proc
|
||||||
_glCreateShader = LibGLESv2.NewProc("glCreateShader")
|
_glCreateShader *windows.Proc
|
||||||
_glGenTextures = LibGLESv2.NewProc("glGenTextures")
|
_glGenTextures *windows.Proc
|
||||||
_glDeleteBuffers = LibGLESv2.NewProc("glDeleteBuffers")
|
_glDeleteBuffers *windows.Proc
|
||||||
_glDeleteFramebuffers = LibGLESv2.NewProc("glDeleteFramebuffers")
|
_glDeleteFramebuffers *windows.Proc
|
||||||
_glDeleteProgram = LibGLESv2.NewProc("glDeleteProgram")
|
_glDeleteProgram *windows.Proc
|
||||||
_glDeleteShader = LibGLESv2.NewProc("glDeleteShader")
|
_glDeleteShader *windows.Proc
|
||||||
_glDeleteRenderbuffers = LibGLESv2.NewProc("glDeleteRenderbuffers")
|
_glDeleteRenderbuffers *windows.Proc
|
||||||
_glDeleteTextures = LibGLESv2.NewProc("glDeleteTextures")
|
_glDeleteTextures *windows.Proc
|
||||||
_glDepthFunc = LibGLESv2.NewProc("glDepthFunc")
|
_glDepthFunc *windows.Proc
|
||||||
_glDepthMask = LibGLESv2.NewProc("glDepthMask")
|
_glDepthMask *windows.Proc
|
||||||
_glDisableVertexAttribArray = LibGLESv2.NewProc("glDisableVertexAttribArray")
|
_glDisableVertexAttribArray *windows.Proc
|
||||||
_glDisable = LibGLESv2.NewProc("glDisable")
|
_glDisable *windows.Proc
|
||||||
_glDrawArrays = LibGLESv2.NewProc("glDrawArrays")
|
_glDrawArrays *windows.Proc
|
||||||
_glDrawElements = LibGLESv2.NewProc("glDrawElements")
|
_glDrawElements *windows.Proc
|
||||||
_glEnable = LibGLESv2.NewProc("glEnable")
|
_glEnable *windows.Proc
|
||||||
_glEnableVertexAttribArray = LibGLESv2.NewProc("glEnableVertexAttribArray")
|
_glEnableVertexAttribArray *windows.Proc
|
||||||
_glEndQuery = LibGLESv2.NewProc("glEndQuery")
|
_glEndQuery *windows.Proc
|
||||||
_glFinish = LibGLESv2.NewProc("glFinish")
|
_glFinish *windows.Proc
|
||||||
_glFlush = LibGLESv2.NewProc("glFlush")
|
_glFlush *windows.Proc
|
||||||
_glFramebufferRenderbuffer = LibGLESv2.NewProc("glFramebufferRenderbuffer")
|
_glFramebufferRenderbuffer *windows.Proc
|
||||||
_glFramebufferTexture2D = LibGLESv2.NewProc("glFramebufferTexture2D")
|
_glFramebufferTexture2D *windows.Proc
|
||||||
_glGenQueries = LibGLESv2.NewProc("glGenQueries")
|
_glGenQueries *windows.Proc
|
||||||
_glGetError = LibGLESv2.NewProc("glGetError")
|
_glGetError *windows.Proc
|
||||||
_glGetRenderbufferParameteriv = LibGLESv2.NewProc("glGetRenderbufferParameteriv")
|
_glGetRenderbufferParameteriv *windows.Proc
|
||||||
_glGetFloatv = LibGLESv2.NewProc("glGetFloatv")
|
_glGetFloatv *windows.Proc
|
||||||
_glGetFramebufferAttachmentParameteriv = LibGLESv2.NewProc("glGetFramebufferAttachmentParameteriv")
|
_glGetFramebufferAttachmentParameteriv *windows.Proc
|
||||||
_glGetIntegerv = LibGLESv2.NewProc("glGetIntegerv")
|
_glGetIntegerv *windows.Proc
|
||||||
_glGetIntegeri_v = LibGLESv2.NewProc("glGetIntegeri_v")
|
_glGetIntegeri_v *windows.Proc
|
||||||
_glGetProgramiv = LibGLESv2.NewProc("glGetProgramiv")
|
_glGetProgramiv *windows.Proc
|
||||||
_glGetProgramInfoLog = LibGLESv2.NewProc("glGetProgramInfoLog")
|
_glGetProgramInfoLog *windows.Proc
|
||||||
_glGetQueryObjectuiv = LibGLESv2.NewProc("glGetQueryObjectuiv")
|
_glGetQueryObjectuiv *windows.Proc
|
||||||
_glGetShaderiv = LibGLESv2.NewProc("glGetShaderiv")
|
_glGetShaderiv *windows.Proc
|
||||||
_glGetShaderInfoLog = LibGLESv2.NewProc("glGetShaderInfoLog")
|
_glGetShaderInfoLog *windows.Proc
|
||||||
_glGetString = LibGLESv2.NewProc("glGetString")
|
_glGetString *windows.Proc
|
||||||
_glGetUniformLocation = LibGLESv2.NewProc("glGetUniformLocation")
|
_glGetUniformLocation *windows.Proc
|
||||||
_glGetVertexAttribiv = LibGLESv2.NewProc("glGetVertexAttribiv")
|
_glGetVertexAttribiv *windows.Proc
|
||||||
_glGetVertexAttribPointerv = LibGLESv2.NewProc("glGetVertexAttribPointerv")
|
_glGetVertexAttribPointerv *windows.Proc
|
||||||
_glInvalidateFramebuffer = LibGLESv2.NewProc("glInvalidateFramebuffer")
|
_glInvalidateFramebuffer *windows.Proc
|
||||||
_glIsEnabled = LibGLESv2.NewProc("glIsEnabled")
|
_glIsEnabled *windows.Proc
|
||||||
_glLinkProgram = LibGLESv2.NewProc("glLinkProgram")
|
_glLinkProgram *windows.Proc
|
||||||
_glPixelStorei = LibGLESv2.NewProc("glPixelStorei")
|
_glPixelStorei *windows.Proc
|
||||||
_glReadPixels = LibGLESv2.NewProc("glReadPixels")
|
_glReadPixels *windows.Proc
|
||||||
_glRenderbufferStorage = LibGLESv2.NewProc("glRenderbufferStorage")
|
_glRenderbufferStorage *windows.Proc
|
||||||
_glScissor = LibGLESv2.NewProc("glScissor")
|
_glScissor *windows.Proc
|
||||||
_glShaderSource = LibGLESv2.NewProc("glShaderSource")
|
_glShaderSource *windows.Proc
|
||||||
_glTexImage2D = LibGLESv2.NewProc("glTexImage2D")
|
_glTexImage2D *windows.Proc
|
||||||
_glTexStorage2D = LibGLESv2.NewProc("glTexStorage2D")
|
_glTexStorage2D *windows.Proc
|
||||||
_glTexSubImage2D = LibGLESv2.NewProc("glTexSubImage2D")
|
_glTexSubImage2D *windows.Proc
|
||||||
_glTexParameteri = LibGLESv2.NewProc("glTexParameteri")
|
_glTexParameteri *windows.Proc
|
||||||
_glUniformBlockBinding = LibGLESv2.NewProc("glUniformBlockBinding")
|
_glUniformBlockBinding *windows.Proc
|
||||||
_glUniform1f = LibGLESv2.NewProc("glUniform1f")
|
_glUniform1f *windows.Proc
|
||||||
_glUniform1i = LibGLESv2.NewProc("glUniform1i")
|
_glUniform1i *windows.Proc
|
||||||
_glUniform2f = LibGLESv2.NewProc("glUniform2f")
|
_glUniform2f *windows.Proc
|
||||||
_glUniform3f = LibGLESv2.NewProc("glUniform3f")
|
_glUniform3f *windows.Proc
|
||||||
_glUniform4f = LibGLESv2.NewProc("glUniform4f")
|
_glUniform4f *windows.Proc
|
||||||
_glUseProgram = LibGLESv2.NewProc("glUseProgram")
|
_glUseProgram *windows.Proc
|
||||||
_glVertexAttribPointer = LibGLESv2.NewProc("glVertexAttribPointer")
|
_glVertexAttribPointer *windows.Proc
|
||||||
_glViewport = LibGLESv2.NewProc("glViewport")
|
_glViewport *windows.Proc
|
||||||
)
|
)
|
||||||
|
|
||||||
type Functions struct {
|
type Functions struct {
|
||||||
@@ -109,57 +223,74 @@ type Functions struct {
|
|||||||
uintptrs [100]uintptr
|
uintptrs [100]uintptr
|
||||||
}
|
}
|
||||||
|
|
||||||
type Context interface{}
|
type Context any
|
||||||
|
|
||||||
func NewFunctions(ctx Context, forceES bool) (*Functions, error) {
|
func NewFunctions(ctx Context, forceES bool) (*Functions, error) {
|
||||||
if ctx != nil {
|
if ctx != nil {
|
||||||
panic("non-nil context")
|
panic("non-nil context")
|
||||||
}
|
}
|
||||||
return new(Functions), nil
|
var err error
|
||||||
|
glInitOnce.Do(func() {
|
||||||
|
err = loadGLESv2Procs()
|
||||||
|
})
|
||||||
|
return new(Functions), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) ActiveTexture(t Enum) {
|
func (c *Functions) ActiveTexture(t Enum) {
|
||||||
syscall.Syscall(_glActiveTexture.Addr(), 1, uintptr(t), 0, 0)
|
syscall.Syscall(_glActiveTexture.Addr(), 1, uintptr(t), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) AttachShader(p Program, s Shader) {
|
func (c *Functions) AttachShader(p Program, s Shader) {
|
||||||
syscall.Syscall(_glAttachShader.Addr(), 2, uintptr(p.V), uintptr(s.V), 0)
|
syscall.Syscall(_glAttachShader.Addr(), 2, uintptr(p.V), uintptr(s.V), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BeginQuery(target Enum, query Query) {
|
func (f *Functions) BeginQuery(target Enum, query Query) {
|
||||||
syscall.Syscall(_glBeginQuery.Addr(), 2, uintptr(target), uintptr(query.V), 0)
|
syscall.Syscall(_glBeginQuery.Addr(), 2, uintptr(target), uintptr(query.V), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) BindAttribLocation(p Program, a Attrib, name string) {
|
func (c *Functions) BindAttribLocation(p Program, a Attrib, name string) {
|
||||||
cname := cString(name)
|
cname := cString(name)
|
||||||
c0 := &cname[0]
|
c0 := &cname[0]
|
||||||
syscall.Syscall(_glBindAttribLocation.Addr(), 3, uintptr(p.V), uintptr(a), uintptr(unsafe.Pointer(c0)))
|
syscall.Syscall(_glBindAttribLocation.Addr(), 3, uintptr(p.V), uintptr(a), uintptr(unsafe.Pointer(c0)))
|
||||||
issue34474KeepAlive(c)
|
issue34474KeepAlive(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) BindBuffer(target Enum, b Buffer) {
|
func (c *Functions) BindBuffer(target Enum, b Buffer) {
|
||||||
syscall.Syscall(_glBindBuffer.Addr(), 2, uintptr(target), uintptr(b.V), 0)
|
syscall.Syscall(_glBindBuffer.Addr(), 2, uintptr(target), uintptr(b.V), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) BindBufferBase(target Enum, index int, b Buffer) {
|
func (c *Functions) BindBufferBase(target Enum, index int, b Buffer) {
|
||||||
syscall.Syscall(_glBindBufferBase.Addr(), 3, uintptr(target), uintptr(index), uintptr(b.V))
|
syscall.Syscall(_glBindBufferBase.Addr(), 3, uintptr(target), uintptr(index), uintptr(b.V))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) BindFramebuffer(target Enum, fb Framebuffer) {
|
func (c *Functions) BindFramebuffer(target Enum, fb Framebuffer) {
|
||||||
syscall.Syscall(_glBindFramebuffer.Addr(), 2, uintptr(target), uintptr(fb.V), 0)
|
syscall.Syscall(_glBindFramebuffer.Addr(), 2, uintptr(target), uintptr(fb.V), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) BindRenderbuffer(target Enum, rb Renderbuffer) {
|
func (c *Functions) BindRenderbuffer(target Enum, rb Renderbuffer) {
|
||||||
syscall.Syscall(_glBindRenderbuffer.Addr(), 2, uintptr(target), uintptr(rb.V), 0)
|
syscall.Syscall(_glBindRenderbuffer.Addr(), 2, uintptr(target), uintptr(rb.V), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BindImageTexture(unit int, t Texture, level int, layered bool, layer int, access, format Enum) {
|
func (f *Functions) BindImageTexture(unit int, t Texture, level int, layered bool, layer int, access, format Enum) {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) BindTexture(target Enum, t Texture) {
|
func (c *Functions) BindTexture(target Enum, t Texture) {
|
||||||
syscall.Syscall(_glBindTexture.Addr(), 2, uintptr(target), uintptr(t.V), 0)
|
syscall.Syscall(_glBindTexture.Addr(), 2, uintptr(target), uintptr(t.V), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) BindVertexArray(a VertexArray) {
|
func (c *Functions) BindVertexArray(a VertexArray) {
|
||||||
syscall.Syscall(_glBindVertexArray.Addr(), 1, uintptr(a.V), 0, 0)
|
syscall.Syscall(_glBindVertexArray.Addr(), 1, uintptr(a.V), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) BlendEquation(mode Enum) {
|
func (c *Functions) BlendEquation(mode Enum) {
|
||||||
syscall.Syscall(_glBlendEquation.Addr(), 1, uintptr(mode), 0, 0)
|
syscall.Syscall(_glBlendEquation.Addr(), 1, uintptr(mode), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) BlendFuncSeparate(srcRGB, dstRGB, srcA, dstA Enum) {
|
func (c *Functions) BlendFuncSeparate(srcRGB, dstRGB, srcA, dstA Enum) {
|
||||||
syscall.Syscall6(_glBlendFuncSeparate.Addr(), 4, uintptr(srcRGB), uintptr(dstRGB), uintptr(srcA), uintptr(dstA), 0, 0)
|
syscall.Syscall6(_glBlendFuncSeparate.Addr(), 4, uintptr(srcRGB), uintptr(dstRGB), uintptr(srcA), uintptr(dstA), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) BufferData(target Enum, size int, usage Enum, data []byte) {
|
func (c *Functions) BufferData(target Enum, size int, usage Enum, data []byte) {
|
||||||
var p unsafe.Pointer
|
var p unsafe.Pointer
|
||||||
if len(data) > 0 {
|
if len(data) > 0 {
|
||||||
@@ -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)
|
syscall.Syscall6(_glBufferData.Addr(), 4, uintptr(target), uintptr(size), uintptr(p), uintptr(usage), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
|
func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
|
||||||
if n := len(src); n > 0 {
|
if n := len(src); n > 0 {
|
||||||
s0 := &src[0]
|
s0 := &src[0]
|
||||||
@@ -174,93 +306,118 @@ func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
|
|||||||
issue34474KeepAlive(s0)
|
issue34474KeepAlive(s0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) CheckFramebufferStatus(target Enum) Enum {
|
func (c *Functions) CheckFramebufferStatus(target Enum) Enum {
|
||||||
s, _, _ := syscall.Syscall(_glCheckFramebufferStatus.Addr(), 1, uintptr(target), 0, 0)
|
s, _, _ := syscall.Syscall(_glCheckFramebufferStatus.Addr(), 1, uintptr(target), 0, 0)
|
||||||
return Enum(s)
|
return Enum(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) Clear(mask Enum) {
|
func (c *Functions) Clear(mask Enum) {
|
||||||
syscall.Syscall(_glClear.Addr(), 1, uintptr(mask), 0, 0)
|
syscall.Syscall(_glClear.Addr(), 1, uintptr(mask), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) ClearColor(red, green, blue, alpha float32) {
|
func (c *Functions) ClearColor(red, green, blue, alpha float32) {
|
||||||
syscall.Syscall6(_glClearColor.Addr(), 4, uintptr(math.Float32bits(red)), uintptr(math.Float32bits(green)), uintptr(math.Float32bits(blue)), uintptr(math.Float32bits(alpha)), 0, 0)
|
syscall.Syscall6(_glClearColor.Addr(), 4, uintptr(math.Float32bits(red)), uintptr(math.Float32bits(green)), uintptr(math.Float32bits(blue)), uintptr(math.Float32bits(alpha)), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) ClearDepthf(d float32) {
|
func (c *Functions) ClearDepthf(d float32) {
|
||||||
syscall.Syscall(_glClearDepthf.Addr(), 1, uintptr(math.Float32bits(d)), 0, 0)
|
syscall.Syscall(_glClearDepthf.Addr(), 1, uintptr(math.Float32bits(d)), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) CompileShader(s Shader) {
|
func (c *Functions) CompileShader(s Shader) {
|
||||||
syscall.Syscall(_glCompileShader.Addr(), 1, uintptr(s.V), 0, 0)
|
syscall.Syscall(_glCompileShader.Addr(), 1, uintptr(s.V), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) CopyTexSubImage2D(target Enum, level, xoffset, yoffset, x, y, width, height int) {
|
func (f *Functions) CopyTexSubImage2D(target Enum, level, xoffset, yoffset, x, y, width, height int) {
|
||||||
syscall.Syscall9(_glCopyTexSubImage2D.Addr(), 8, uintptr(target), uintptr(level), uintptr(xoffset), uintptr(yoffset), uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0)
|
syscall.Syscall9(_glCopyTexSubImage2D.Addr(), 8, uintptr(target), uintptr(level), uintptr(xoffset), uintptr(yoffset), uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GenerateMipmap(target Enum) {
|
func (f *Functions) GenerateMipmap(target Enum) {
|
||||||
syscall.Syscall(_glGenerateMipmap.Addr(), 1, uintptr(target), 0, 0)
|
syscall.Syscall(_glGenerateMipmap.Addr(), 1, uintptr(target), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) CreateBuffer() Buffer {
|
func (c *Functions) CreateBuffer() Buffer {
|
||||||
var buf uintptr
|
var buf uintptr
|
||||||
syscall.Syscall(_glGenBuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&buf)), 0)
|
syscall.Syscall(_glGenBuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&buf)), 0)
|
||||||
return Buffer{uint(buf)}
|
return Buffer{uint(buf)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) CreateFramebuffer() Framebuffer {
|
func (c *Functions) CreateFramebuffer() Framebuffer {
|
||||||
var fb uintptr
|
var fb uintptr
|
||||||
syscall.Syscall(_glGenFramebuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&fb)), 0)
|
syscall.Syscall(_glGenFramebuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&fb)), 0)
|
||||||
return Framebuffer{uint(fb)}
|
return Framebuffer{uint(fb)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) CreateProgram() Program {
|
func (c *Functions) CreateProgram() Program {
|
||||||
p, _, _ := syscall.Syscall(_glCreateProgram.Addr(), 0, 0, 0, 0)
|
p, _, _ := syscall.Syscall(_glCreateProgram.Addr(), 0, 0, 0, 0)
|
||||||
return Program{uint(p)}
|
return Program{uint(p)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) CreateQuery() Query {
|
func (f *Functions) CreateQuery() Query {
|
||||||
var q uintptr
|
var q uintptr
|
||||||
syscall.Syscall(_glGenQueries.Addr(), 2, 1, uintptr(unsafe.Pointer(&q)), 0)
|
syscall.Syscall(_glGenQueries.Addr(), 2, 1, uintptr(unsafe.Pointer(&q)), 0)
|
||||||
return Query{uint(q)}
|
return Query{uint(q)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) CreateRenderbuffer() Renderbuffer {
|
func (c *Functions) CreateRenderbuffer() Renderbuffer {
|
||||||
var rb uintptr
|
var rb uintptr
|
||||||
syscall.Syscall(_glGenRenderbuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&rb)), 0)
|
syscall.Syscall(_glGenRenderbuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&rb)), 0)
|
||||||
return Renderbuffer{uint(rb)}
|
return Renderbuffer{uint(rb)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) CreateShader(ty Enum) Shader {
|
func (c *Functions) CreateShader(ty Enum) Shader {
|
||||||
s, _, _ := syscall.Syscall(_glCreateShader.Addr(), 1, uintptr(ty), 0, 0)
|
s, _, _ := syscall.Syscall(_glCreateShader.Addr(), 1, uintptr(ty), 0, 0)
|
||||||
return Shader{uint(s)}
|
return Shader{uint(s)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) CreateTexture() Texture {
|
func (c *Functions) CreateTexture() Texture {
|
||||||
var t uintptr
|
var t uintptr
|
||||||
syscall.Syscall(_glGenTextures.Addr(), 2, 1, uintptr(unsafe.Pointer(&t)), 0)
|
syscall.Syscall(_glGenTextures.Addr(), 2, 1, uintptr(unsafe.Pointer(&t)), 0)
|
||||||
return Texture{uint(t)}
|
return Texture{uint(t)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) CreateVertexArray() VertexArray {
|
func (c *Functions) CreateVertexArray() VertexArray {
|
||||||
var t uintptr
|
var t uintptr
|
||||||
syscall.Syscall(_glGenVertexArrays.Addr(), 2, 1, uintptr(unsafe.Pointer(&t)), 0)
|
syscall.Syscall(_glGenVertexArrays.Addr(), 2, 1, uintptr(unsafe.Pointer(&t)), 0)
|
||||||
return VertexArray{uint(t)}
|
return VertexArray{uint(t)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) DeleteBuffer(v Buffer) {
|
func (c *Functions) DeleteBuffer(v Buffer) {
|
||||||
syscall.Syscall(_glDeleteBuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v)), 0)
|
syscall.Syscall(_glDeleteBuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v)), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) DeleteFramebuffer(v Framebuffer) {
|
func (c *Functions) DeleteFramebuffer(v Framebuffer) {
|
||||||
syscall.Syscall(_glDeleteFramebuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
|
syscall.Syscall(_glDeleteFramebuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) DeleteProgram(p Program) {
|
func (c *Functions) DeleteProgram(p Program) {
|
||||||
syscall.Syscall(_glDeleteProgram.Addr(), 1, uintptr(p.V), 0, 0)
|
syscall.Syscall(_glDeleteProgram.Addr(), 1, uintptr(p.V), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DeleteQuery(query Query) {
|
func (f *Functions) DeleteQuery(query Query) {
|
||||||
syscall.Syscall(_glDeleteQueries.Addr(), 2, 1, uintptr(unsafe.Pointer(&query.V)), 0)
|
syscall.Syscall(_glDeleteQueries.Addr(), 2, 1, uintptr(unsafe.Pointer(&query.V)), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) DeleteShader(s Shader) {
|
func (c *Functions) DeleteShader(s Shader) {
|
||||||
syscall.Syscall(_glDeleteShader.Addr(), 1, uintptr(s.V), 0, 0)
|
syscall.Syscall(_glDeleteShader.Addr(), 1, uintptr(s.V), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) DeleteRenderbuffer(v Renderbuffer) {
|
func (c *Functions) DeleteRenderbuffer(v Renderbuffer) {
|
||||||
syscall.Syscall(_glDeleteRenderbuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
|
syscall.Syscall(_glDeleteRenderbuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) DeleteTexture(v Texture) {
|
func (c *Functions) DeleteTexture(v Texture) {
|
||||||
syscall.Syscall(_glDeleteTextures.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
|
syscall.Syscall(_glDeleteTextures.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DeleteVertexArray(array VertexArray) {
|
func (f *Functions) DeleteVertexArray(array VertexArray) {
|
||||||
syscall.Syscall(_glDeleteVertexArrays.Addr(), 2, 1, uintptr(unsafe.Pointer(&array.V)), 0)
|
syscall.Syscall(_glDeleteVertexArrays.Addr(), 2, 1, uintptr(unsafe.Pointer(&array.V)), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) DepthFunc(f Enum) {
|
func (c *Functions) DepthFunc(f Enum) {
|
||||||
syscall.Syscall(_glDepthFunc.Addr(), 1, uintptr(f), 0, 0)
|
syscall.Syscall(_glDepthFunc.Addr(), 1, uintptr(f), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) DepthMask(mask bool) {
|
func (c *Functions) DepthMask(mask bool) {
|
||||||
var m uintptr
|
var m uintptr
|
||||||
if mask {
|
if mask {
|
||||||
@@ -268,42 +425,55 @@ func (c *Functions) DepthMask(mask bool) {
|
|||||||
}
|
}
|
||||||
syscall.Syscall(_glDepthMask.Addr(), 1, m, 0, 0)
|
syscall.Syscall(_glDepthMask.Addr(), 1, m, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) DisableVertexAttribArray(a Attrib) {
|
func (c *Functions) DisableVertexAttribArray(a Attrib) {
|
||||||
syscall.Syscall(_glDisableVertexAttribArray.Addr(), 1, uintptr(a), 0, 0)
|
syscall.Syscall(_glDisableVertexAttribArray.Addr(), 1, uintptr(a), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) Disable(cap Enum) {
|
func (c *Functions) Disable(cap Enum) {
|
||||||
syscall.Syscall(_glDisable.Addr(), 1, uintptr(cap), 0, 0)
|
syscall.Syscall(_glDisable.Addr(), 1, uintptr(cap), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) DrawArrays(mode Enum, first, count int) {
|
func (c *Functions) DrawArrays(mode Enum, first, count int) {
|
||||||
syscall.Syscall(_glDrawArrays.Addr(), 3, uintptr(mode), uintptr(first), uintptr(count))
|
syscall.Syscall(_glDrawArrays.Addr(), 3, uintptr(mode), uintptr(first), uintptr(count))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) DrawElements(mode Enum, count int, ty Enum, offset int) {
|
func (c *Functions) DrawElements(mode Enum, count int, ty Enum, offset int) {
|
||||||
syscall.Syscall6(_glDrawElements.Addr(), 4, uintptr(mode), uintptr(count), uintptr(ty), uintptr(offset), 0, 0)
|
syscall.Syscall6(_glDrawElements.Addr(), 4, uintptr(mode), uintptr(count), uintptr(ty), uintptr(offset), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) DispatchCompute(x, y, z int) {
|
func (f *Functions) DispatchCompute(x, y, z int) {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) Enable(cap Enum) {
|
func (c *Functions) Enable(cap Enum) {
|
||||||
syscall.Syscall(_glEnable.Addr(), 1, uintptr(cap), 0, 0)
|
syscall.Syscall(_glEnable.Addr(), 1, uintptr(cap), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) EnableVertexAttribArray(a Attrib) {
|
func (c *Functions) EnableVertexAttribArray(a Attrib) {
|
||||||
syscall.Syscall(_glEnableVertexAttribArray.Addr(), 1, uintptr(a), 0, 0)
|
syscall.Syscall(_glEnableVertexAttribArray.Addr(), 1, uintptr(a), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) EndQuery(target Enum) {
|
func (f *Functions) EndQuery(target Enum) {
|
||||||
syscall.Syscall(_glEndQuery.Addr(), 1, uintptr(target), 0, 0)
|
syscall.Syscall(_glEndQuery.Addr(), 1, uintptr(target), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) Finish() {
|
func (c *Functions) Finish() {
|
||||||
syscall.Syscall(_glFinish.Addr(), 0, 0, 0, 0)
|
syscall.Syscall(_glFinish.Addr(), 0, 0, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) Flush() {
|
func (c *Functions) Flush() {
|
||||||
syscall.Syscall(_glFlush.Addr(), 0, 0, 0, 0)
|
syscall.Syscall(_glFlush.Addr(), 0, 0, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer) {
|
func (c *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer) {
|
||||||
syscall.Syscall6(_glFramebufferRenderbuffer.Addr(), 4, uintptr(target), uintptr(attachment), uintptr(renderbuffertarget), uintptr(renderbuffer.V), 0, 0)
|
syscall.Syscall6(_glFramebufferRenderbuffer.Addr(), 4, uintptr(target), uintptr(attachment), uintptr(renderbuffertarget), uintptr(renderbuffer.V), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int) {
|
func (c *Functions) FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int) {
|
||||||
syscall.Syscall6(_glFramebufferTexture2D.Addr(), 5, uintptr(target), uintptr(attachment), uintptr(texTarget), uintptr(t.V), uintptr(level), 0)
|
syscall.Syscall6(_glFramebufferTexture2D.Addr(), 5, uintptr(target), uintptr(attachment), uintptr(texTarget), uintptr(t.V), uintptr(level), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) GetUniformBlockIndex(p Program, name string) uint {
|
func (f *Functions) GetUniformBlockIndex(p Program, name string) uint {
|
||||||
cname := cString(name)
|
cname := cString(name)
|
||||||
c0 := &cname[0]
|
c0 := &cname[0]
|
||||||
@@ -311,24 +481,30 @@ func (f *Functions) GetUniformBlockIndex(p Program, name string) uint {
|
|||||||
issue34474KeepAlive(c0)
|
issue34474KeepAlive(c0)
|
||||||
return uint(u)
|
return uint(u)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetBinding(pname Enum) Object {
|
func (c *Functions) GetBinding(pname Enum) Object {
|
||||||
return Object{uint(c.GetInteger(pname))}
|
return Object{uint(c.GetInteger(pname))}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetBindingi(pname Enum, idx int) Object {
|
func (c *Functions) GetBindingi(pname Enum, idx int) Object {
|
||||||
return Object{uint(c.GetIntegeri(pname, idx))}
|
return Object{uint(c.GetIntegeri(pname, idx))}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetError() Enum {
|
func (c *Functions) GetError() Enum {
|
||||||
e, _, _ := syscall.Syscall(_glGetError.Addr(), 0, 0, 0, 0)
|
e, _, _ := syscall.Syscall(_glGetError.Addr(), 0, 0, 0, 0)
|
||||||
return Enum(e)
|
return Enum(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetRenderbufferParameteri(target, pname Enum) int {
|
func (c *Functions) GetRenderbufferParameteri(target, pname Enum) int {
|
||||||
syscall.Syscall(_glGetRenderbufferParameteriv.Addr(), 3, uintptr(target), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
syscall.Syscall(_glGetRenderbufferParameteriv.Addr(), 3, uintptr(target), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
||||||
return int(c.int32s[0])
|
return int(c.int32s[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int {
|
func (c *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int {
|
||||||
syscall.Syscall6(_glGetFramebufferAttachmentParameteriv.Addr(), 4, uintptr(target), uintptr(attachment), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0, 0)
|
syscall.Syscall6(_glGetFramebufferAttachmentParameteriv.Addr(), 4, uintptr(target), uintptr(attachment), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0, 0)
|
||||||
return int(c.int32s[0])
|
return int(c.int32s[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetInteger4(pname Enum) [4]int {
|
func (c *Functions) GetInteger4(pname Enum) [4]int {
|
||||||
syscall.Syscall(_glGetIntegerv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0)
|
syscall.Syscall(_glGetIntegerv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0)
|
||||||
var r [4]int
|
var r [4]int
|
||||||
@@ -337,52 +513,66 @@ func (c *Functions) GetInteger4(pname Enum) [4]int {
|
|||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetInteger(pname Enum) int {
|
func (c *Functions) GetInteger(pname Enum) int {
|
||||||
syscall.Syscall(_glGetIntegerv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0)
|
syscall.Syscall(_glGetIntegerv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0)
|
||||||
return int(c.int32s[0])
|
return int(c.int32s[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetIntegeri(pname Enum, idx int) int {
|
func (c *Functions) GetIntegeri(pname Enum, idx int) int {
|
||||||
syscall.Syscall(_glGetIntegeri_v.Addr(), 3, uintptr(pname), uintptr(idx), uintptr(unsafe.Pointer(&c.int32s[0])))
|
syscall.Syscall(_glGetIntegeri_v.Addr(), 3, uintptr(pname), uintptr(idx), uintptr(unsafe.Pointer(&c.int32s[0])))
|
||||||
return int(c.int32s[0])
|
return int(c.int32s[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetFloat(pname Enum) float32 {
|
func (c *Functions) GetFloat(pname Enum) float32 {
|
||||||
syscall.Syscall(_glGetFloatv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.float32s[0])), 0)
|
syscall.Syscall(_glGetFloatv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.float32s[0])), 0)
|
||||||
return c.float32s[0]
|
return c.float32s[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetFloat4(pname Enum) [4]float32 {
|
func (c *Functions) GetFloat4(pname Enum) [4]float32 {
|
||||||
syscall.Syscall(_glGetFloatv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.float32s[0])), 0)
|
syscall.Syscall(_glGetFloatv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.float32s[0])), 0)
|
||||||
var r [4]float32
|
var r [4]float32
|
||||||
copy(r[:], c.float32s[:])
|
copy(r[:], c.float32s[:])
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetProgrami(p Program, pname Enum) int {
|
func (c *Functions) GetProgrami(p Program, pname Enum) int {
|
||||||
syscall.Syscall(_glGetProgramiv.Addr(), 3, uintptr(p.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
syscall.Syscall(_glGetProgramiv.Addr(), 3, uintptr(p.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
||||||
return int(c.int32s[0])
|
return int(c.int32s[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetProgramInfoLog(p Program) string {
|
func (c *Functions) GetProgramInfoLog(p Program) string {
|
||||||
n := c.GetProgrami(p, INFO_LOG_LENGTH)
|
n := c.GetProgrami(p, INFO_LOG_LENGTH)
|
||||||
|
if n == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
buf := make([]byte, n)
|
buf := make([]byte, n)
|
||||||
syscall.Syscall6(_glGetProgramInfoLog.Addr(), 4, uintptr(p.V), uintptr(len(buf)), 0, uintptr(unsafe.Pointer(&buf[0])), 0, 0)
|
syscall.Syscall6(_glGetProgramInfoLog.Addr(), 4, uintptr(p.V), uintptr(len(buf)), 0, uintptr(unsafe.Pointer(&buf[0])), 0, 0)
|
||||||
return string(buf)
|
return string(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetQueryObjectuiv(query Query, pname Enum) uint {
|
func (c *Functions) GetQueryObjectuiv(query Query, pname Enum) uint {
|
||||||
syscall.Syscall(_glGetQueryObjectuiv.Addr(), 3, uintptr(query.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
syscall.Syscall(_glGetQueryObjectuiv.Addr(), 3, uintptr(query.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
||||||
return uint(c.int32s[0])
|
return uint(c.int32s[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetShaderi(s Shader, pname Enum) int {
|
func (c *Functions) GetShaderi(s Shader, pname Enum) int {
|
||||||
syscall.Syscall(_glGetShaderiv.Addr(), 3, uintptr(s.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
syscall.Syscall(_glGetShaderiv.Addr(), 3, uintptr(s.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
||||||
return int(c.int32s[0])
|
return int(c.int32s[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetShaderInfoLog(s Shader) string {
|
func (c *Functions) GetShaderInfoLog(s Shader) string {
|
||||||
n := c.GetShaderi(s, INFO_LOG_LENGTH)
|
n := c.GetShaderi(s, INFO_LOG_LENGTH)
|
||||||
buf := make([]byte, n)
|
buf := make([]byte, n)
|
||||||
syscall.Syscall6(_glGetShaderInfoLog.Addr(), 4, uintptr(s.V), uintptr(len(buf)), 0, uintptr(unsafe.Pointer(&buf[0])), 0, 0)
|
syscall.Syscall6(_glGetShaderInfoLog.Addr(), 4, uintptr(s.V), uintptr(len(buf)), 0, uintptr(unsafe.Pointer(&buf[0])), 0, 0)
|
||||||
return string(buf)
|
return string(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetString(pname Enum) string {
|
func (c *Functions) GetString(pname Enum) string {
|
||||||
s, _, _ := syscall.Syscall(_glGetString.Addr(), 1, uintptr(pname), 0, 0)
|
s, _, _ := syscall.Syscall(_glGetString.Addr(), 1, uintptr(pname), 0, 0)
|
||||||
return windows.BytePtrToString((*byte)(unsafe.Pointer(s)))
|
return windows.BytePtrToString((*byte)(unsafe.Pointer(s)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetUniformLocation(p Program, name string) Uniform {
|
func (c *Functions) GetUniformLocation(p Program, name string) Uniform {
|
||||||
cname := cString(name)
|
cname := cString(name)
|
||||||
c0 := &cname[0]
|
c0 := &cname[0]
|
||||||
@@ -390,6 +580,7 @@ func (c *Functions) GetUniformLocation(p Program, name string) Uniform {
|
|||||||
issue34474KeepAlive(c0)
|
issue34474KeepAlive(c0)
|
||||||
return Uniform{int(u)}
|
return Uniform{int(u)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) GetVertexAttrib(index int, pname Enum) int {
|
func (c *Functions) GetVertexAttrib(index int, pname Enum) int {
|
||||||
syscall.Syscall(_glGetVertexAttribiv.Addr(), 3, uintptr(index), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
syscall.Syscall(_glGetVertexAttribiv.Addr(), 3, uintptr(index), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
||||||
return int(c.int32s[0])
|
return int(c.int32s[0])
|
||||||
@@ -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])))
|
syscall.Syscall(_glGetVertexAttribPointerv.Addr(), 3, uintptr(index), uintptr(pname), uintptr(unsafe.Pointer(&c.uintptrs[0])))
|
||||||
return c.uintptrs[0]
|
return c.uintptrs[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) InvalidateFramebuffer(target, attachment Enum) {
|
func (c *Functions) InvalidateFramebuffer(target, attachment Enum) {
|
||||||
addr := _glInvalidateFramebuffer.Addr()
|
addr := _glInvalidateFramebuffer.Addr()
|
||||||
if addr == 0 {
|
if addr == 0 {
|
||||||
@@ -411,77 +603,99 @@ func (c *Functions) InvalidateFramebuffer(target, attachment Enum) {
|
|||||||
}
|
}
|
||||||
syscall.Syscall(addr, 3, uintptr(target), 1, uintptr(unsafe.Pointer(&attachment)))
|
syscall.Syscall(addr, 3, uintptr(target), 1, uintptr(unsafe.Pointer(&attachment)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) IsEnabled(cap Enum) bool {
|
func (f *Functions) IsEnabled(cap Enum) bool {
|
||||||
u, _, _ := syscall.Syscall(_glIsEnabled.Addr(), 1, uintptr(cap), 0, 0)
|
u, _, _ := syscall.Syscall(_glIsEnabled.Addr(), 1, uintptr(cap), 0, 0)
|
||||||
return u == TRUE
|
return u == TRUE
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) LinkProgram(p Program) {
|
func (c *Functions) LinkProgram(p Program) {
|
||||||
syscall.Syscall(_glLinkProgram.Addr(), 1, uintptr(p.V), 0, 0)
|
syscall.Syscall(_glLinkProgram.Addr(), 1, uintptr(p.V), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) PixelStorei(pname Enum, param int) {
|
func (c *Functions) PixelStorei(pname Enum, param int) {
|
||||||
syscall.Syscall(_glPixelStorei.Addr(), 2, uintptr(pname), uintptr(param), 0)
|
syscall.Syscall(_glPixelStorei.Addr(), 2, uintptr(pname), uintptr(param), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) MemoryBarrier(barriers Enum) {
|
func (f *Functions) MemoryBarrier(barriers Enum) {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) MapBufferRange(target Enum, offset, length int, access Enum) []byte {
|
func (f *Functions) MapBufferRange(target Enum, offset, length int, access Enum) []byte {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) ReadPixels(x, y, width, height int, format, ty Enum, data []byte) {
|
func (f *Functions) ReadPixels(x, y, width, height int, format, ty Enum, data []byte) {
|
||||||
d0 := &data[0]
|
d0 := &data[0]
|
||||||
syscall.Syscall9(_glReadPixels.Addr(), 7, uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(format), uintptr(ty), uintptr(unsafe.Pointer(d0)), 0, 0)
|
syscall.Syscall9(_glReadPixels.Addr(), 7, uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(format), uintptr(ty), uintptr(unsafe.Pointer(d0)), 0, 0)
|
||||||
issue34474KeepAlive(d0)
|
issue34474KeepAlive(d0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) RenderbufferStorage(target, internalformat Enum, width, height int) {
|
func (c *Functions) RenderbufferStorage(target, internalformat Enum, width, height int) {
|
||||||
syscall.Syscall6(_glRenderbufferStorage.Addr(), 4, uintptr(target), uintptr(internalformat), uintptr(width), uintptr(height), 0, 0)
|
syscall.Syscall6(_glRenderbufferStorage.Addr(), 4, uintptr(target), uintptr(internalformat), uintptr(width), uintptr(height), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) Scissor(x, y, width, height int32) {
|
func (c *Functions) Scissor(x, y, width, height int32) {
|
||||||
syscall.Syscall6(_glScissor.Addr(), 4, uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0, 0)
|
syscall.Syscall6(_glScissor.Addr(), 4, uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) ShaderSource(s Shader, src string) {
|
func (c *Functions) ShaderSource(s Shader, src string) {
|
||||||
var n uintptr = uintptr(len(src))
|
var n uintptr = uintptr(len(src))
|
||||||
psrc := &src
|
psrc := &src
|
||||||
syscall.Syscall6(_glShaderSource.Addr(), 4, uintptr(s.V), 1, uintptr(unsafe.Pointer(psrc)), uintptr(unsafe.Pointer(&n)), 0, 0)
|
syscall.Syscall6(_glShaderSource.Addr(), 4, uintptr(s.V), 1, uintptr(unsafe.Pointer(psrc)), uintptr(unsafe.Pointer(&n)), 0, 0)
|
||||||
issue34474KeepAlive(psrc)
|
issue34474KeepAlive(psrc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) TexImage2D(target Enum, level int, internalFormat Enum, width int, height int, format Enum, ty Enum) {
|
func (f *Functions) TexImage2D(target Enum, level int, internalFormat Enum, width int, height int, format Enum, ty Enum) {
|
||||||
syscall.Syscall9(_glTexImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(internalFormat), uintptr(width), uintptr(height), 0, uintptr(format), uintptr(ty), 0)
|
syscall.Syscall9(_glTexImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(internalFormat), uintptr(width), uintptr(height), 0, uintptr(format), uintptr(ty), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) TexStorage2D(target Enum, levels int, internalFormat Enum, width, height int) {
|
func (f *Functions) TexStorage2D(target Enum, levels int, internalFormat Enum, width, height int) {
|
||||||
syscall.Syscall6(_glTexStorage2D.Addr(), 5, uintptr(target), uintptr(levels), uintptr(internalFormat), uintptr(width), uintptr(height), 0)
|
syscall.Syscall6(_glTexStorage2D.Addr(), 5, uintptr(target), uintptr(levels), uintptr(internalFormat), uintptr(width), uintptr(height), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte) {
|
func (c *Functions) TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte) {
|
||||||
d0 := &data[0]
|
d0 := &data[0]
|
||||||
syscall.Syscall9(_glTexSubImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(format), uintptr(ty), uintptr(unsafe.Pointer(d0)))
|
syscall.Syscall9(_glTexSubImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(format), uintptr(ty), uintptr(unsafe.Pointer(d0)))
|
||||||
issue34474KeepAlive(d0)
|
issue34474KeepAlive(d0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) TexParameteri(target, pname Enum, param int) {
|
func (c *Functions) TexParameteri(target, pname Enum, param int) {
|
||||||
syscall.Syscall(_glTexParameteri.Addr(), 3, uintptr(target), uintptr(pname), uintptr(param))
|
syscall.Syscall(_glTexParameteri.Addr(), 3, uintptr(target), uintptr(pname), uintptr(param))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) UniformBlockBinding(p Program, uniformBlockIndex uint, uniformBlockBinding uint) {
|
func (f *Functions) UniformBlockBinding(p Program, uniformBlockIndex uint, uniformBlockBinding uint) {
|
||||||
syscall.Syscall(_glUniformBlockBinding.Addr(), 3, uintptr(p.V), uintptr(uniformBlockIndex), uintptr(uniformBlockBinding))
|
syscall.Syscall(_glUniformBlockBinding.Addr(), 3, uintptr(p.V), uintptr(uniformBlockIndex), uintptr(uniformBlockBinding))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) Uniform1f(dst Uniform, v float32) {
|
func (c *Functions) Uniform1f(dst Uniform, v float32) {
|
||||||
syscall.Syscall(_glUniform1f.Addr(), 2, uintptr(dst.V), uintptr(math.Float32bits(v)), 0)
|
syscall.Syscall(_glUniform1f.Addr(), 2, uintptr(dst.V), uintptr(math.Float32bits(v)), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) Uniform1i(dst Uniform, v int) {
|
func (c *Functions) Uniform1i(dst Uniform, v int) {
|
||||||
syscall.Syscall(_glUniform1i.Addr(), 2, uintptr(dst.V), uintptr(v), 0)
|
syscall.Syscall(_glUniform1i.Addr(), 2, uintptr(dst.V), uintptr(v), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) Uniform2f(dst Uniform, v0, v1 float32) {
|
func (c *Functions) Uniform2f(dst Uniform, v0, v1 float32) {
|
||||||
syscall.Syscall(_glUniform2f.Addr(), 3, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)))
|
syscall.Syscall(_glUniform2f.Addr(), 3, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) Uniform3f(dst Uniform, v0, v1, v2 float32) {
|
func (c *Functions) Uniform3f(dst Uniform, v0, v1, v2 float32) {
|
||||||
syscall.Syscall6(_glUniform3f.Addr(), 4, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)), uintptr(math.Float32bits(v2)), 0, 0)
|
syscall.Syscall6(_glUniform3f.Addr(), 4, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)), uintptr(math.Float32bits(v2)), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) Uniform4f(dst Uniform, v0, v1, v2, v3 float32) {
|
func (c *Functions) Uniform4f(dst Uniform, v0, v1, v2, v3 float32) {
|
||||||
syscall.Syscall6(_glUniform4f.Addr(), 5, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)), uintptr(math.Float32bits(v2)), uintptr(math.Float32bits(v3)), 0)
|
syscall.Syscall6(_glUniform4f.Addr(), 5, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)), uintptr(math.Float32bits(v2)), uintptr(math.Float32bits(v3)), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) UseProgram(p Program) {
|
func (c *Functions) UseProgram(p Program) {
|
||||||
syscall.Syscall(_glUseProgram.Addr(), 1, uintptr(p.V), 0, 0)
|
syscall.Syscall(_glUseProgram.Addr(), 1, uintptr(p.V), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Functions) UnmapBuffer(target Enum) bool {
|
func (f *Functions) UnmapBuffer(target Enum) bool {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int) {
|
func (c *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int) {
|
||||||
var norm uintptr
|
var norm uintptr
|
||||||
if normalized {
|
if normalized {
|
||||||
@@ -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))
|
syscall.Syscall6(_glVertexAttribPointer.Addr(), 6, uintptr(dst), uintptr(size), uintptr(ty), norm, uintptr(stride), uintptr(offset))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Functions) Viewport(x, y, width, height int) {
|
func (c *Functions) Viewport(x, y, width, height int) {
|
||||||
syscall.Syscall6(_glViewport.Addr(), 4, uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0, 0)
|
syscall.Syscall6(_glViewport.Addr(), 4, uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0, 0)
|
||||||
}
|
}
|
||||||
@@ -501,6 +716,6 @@ func cString(s string) []byte {
|
|||||||
|
|
||||||
// issue34474KeepAlive calls runtime.KeepAlive as a
|
// issue34474KeepAlive calls runtime.KeepAlive as a
|
||||||
// workaround for golang.org/issue/34474.
|
// workaround for golang.org/issue/34474.
|
||||||
func issue34474KeepAlive(v interface{}) {
|
func issue34474KeepAlive(v any) {
|
||||||
runtime.KeepAlive(v)
|
runtime.KeepAlive(v)
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-65
@@ -18,7 +18,7 @@ type Ops struct {
|
|||||||
// data contains the serialized operations.
|
// data contains the serialized operations.
|
||||||
data []byte
|
data []byte
|
||||||
// refs hold external references for operations.
|
// refs hold external references for operations.
|
||||||
refs []interface{}
|
refs []any
|
||||||
// stringRefs provides space for string references, pointers to which will
|
// stringRefs provides space for string references, pointers to which will
|
||||||
// be stored in refs. Storing a string directly in refs would cause a heap
|
// be stored in refs. Storing a string directly in refs would cause a heap
|
||||||
// allocation, to store the string header in an interface value. The backing
|
// allocation, to store the string header in an interface value. The backing
|
||||||
@@ -55,28 +55,19 @@ const (
|
|||||||
TypePopTransform
|
TypePopTransform
|
||||||
TypePushOpacity
|
TypePushOpacity
|
||||||
TypePopOpacity
|
TypePopOpacity
|
||||||
TypeInvalidate
|
|
||||||
TypeImage
|
TypeImage
|
||||||
TypePaint
|
TypePaint
|
||||||
TypeColor
|
TypeColor
|
||||||
TypeLinearGradient
|
TypeLinearGradient
|
||||||
TypePass
|
TypePass
|
||||||
TypePopPass
|
TypePopPass
|
||||||
TypePointerInput
|
TypeInput
|
||||||
TypeClipboardRead
|
TypeKeyInputHint
|
||||||
TypeClipboardWrite
|
|
||||||
TypeSource
|
|
||||||
TypeTarget
|
|
||||||
TypeOffer
|
|
||||||
TypeKeyInput
|
|
||||||
TypeKeyFocus
|
|
||||||
TypeKeySoftKeyboard
|
|
||||||
TypeSave
|
TypeSave
|
||||||
TypeLoad
|
TypeLoad
|
||||||
TypeAux
|
TypeAux
|
||||||
TypeClip
|
TypeClip
|
||||||
TypePopClip
|
TypePopClip
|
||||||
TypeProfile
|
|
||||||
TypeCursor
|
TypeCursor
|
||||||
TypePath
|
TypePath
|
||||||
TypeStroke
|
TypeStroke
|
||||||
@@ -85,8 +76,6 @@ const (
|
|||||||
TypeSemanticClass
|
TypeSemanticClass
|
||||||
TypeSemanticSelected
|
TypeSemanticSelected
|
||||||
TypeSemanticEnabled
|
TypeSemanticEnabled
|
||||||
TypeSnippet
|
|
||||||
TypeSelection
|
|
||||||
TypeActionInput
|
TypeActionInput
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -148,21 +137,13 @@ const (
|
|||||||
TypeLinearGradientLen = 1 + 8*2 + 4*2
|
TypeLinearGradientLen = 1 + 8*2 + 4*2
|
||||||
TypePassLen = 1
|
TypePassLen = 1
|
||||||
TypePopPassLen = 1
|
TypePopPassLen = 1
|
||||||
TypePointerInputLen = 1 + 1 + 1*2 + 2*4 + 2*4
|
TypeInputLen = 1
|
||||||
TypeClipboardReadLen = 1
|
TypeKeyInputHintLen = 1 + 1
|
||||||
TypeClipboardWriteLen = 1
|
|
||||||
TypeSourceLen = 1
|
|
||||||
TypeTargetLen = 1
|
|
||||||
TypeOfferLen = 1
|
|
||||||
TypeKeyInputLen = 1 + 1
|
|
||||||
TypeKeyFocusLen = 1 + 1
|
|
||||||
TypeKeySoftKeyboardLen = 1 + 1
|
|
||||||
TypeSaveLen = 1 + 4
|
TypeSaveLen = 1 + 4
|
||||||
TypeLoadLen = 1 + 4
|
TypeLoadLen = 1 + 4
|
||||||
TypeAuxLen = 1
|
TypeAuxLen = 1
|
||||||
TypeClipLen = 1 + 4*4 + 1 + 1
|
TypeClipLen = 1 + 4*4 + 1 + 1
|
||||||
TypePopClipLen = 1
|
TypePopClipLen = 1
|
||||||
TypeProfileLen = 1
|
|
||||||
TypeCursorLen = 2
|
TypeCursorLen = 2
|
||||||
TypePathLen = 8 + 1
|
TypePathLen = 8 + 1
|
||||||
TypeStrokeLen = 1 + 4
|
TypeStrokeLen = 1 + 4
|
||||||
@@ -171,8 +152,6 @@ const (
|
|||||||
TypeSemanticClassLen = 2
|
TypeSemanticClassLen = 2
|
||||||
TypeSemanticSelectedLen = 2
|
TypeSemanticSelectedLen = 2
|
||||||
TypeSemanticEnabledLen = 2
|
TypeSemanticEnabledLen = 2
|
||||||
TypeSnippetLen = 1 + 4 + 4
|
|
||||||
TypeSelectionLen = 1 + 2*4 + 2*4 + 4 + 4
|
|
||||||
TypeActionInputLen = 1 + 1
|
TypeActionInputLen = 1 + 1
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -277,7 +256,7 @@ func PopOp(o *Ops, kind StackKind, sid StackID, macroID uint32) {
|
|||||||
o.stacks[kind].pop(sid)
|
o.stacks[kind].pop(sid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Write1(o *Ops, n int, ref1 interface{}) []byte {
|
func Write1(o *Ops, n int, ref1 any) []byte {
|
||||||
o.data = append(o.data, make([]byte, n)...)
|
o.data = append(o.data, make([]byte, n)...)
|
||||||
o.refs = append(o.refs, ref1)
|
o.refs = append(o.refs, ref1)
|
||||||
return o.data[len(o.data)-n:]
|
return o.data[len(o.data)-n:]
|
||||||
@@ -290,20 +269,20 @@ func Write1String(o *Ops, n int, ref1 string) []byte {
|
|||||||
return o.data[len(o.data)-n:]
|
return o.data[len(o.data)-n:]
|
||||||
}
|
}
|
||||||
|
|
||||||
func Write2(o *Ops, n int, ref1, ref2 interface{}) []byte {
|
func Write2(o *Ops, n int, ref1, ref2 any) []byte {
|
||||||
o.data = append(o.data, make([]byte, n)...)
|
o.data = append(o.data, make([]byte, n)...)
|
||||||
o.refs = append(o.refs, ref1, ref2)
|
o.refs = append(o.refs, ref1, ref2)
|
||||||
return o.data[len(o.data)-n:]
|
return o.data[len(o.data)-n:]
|
||||||
}
|
}
|
||||||
|
|
||||||
func Write2String(o *Ops, n int, ref1 interface{}, ref2 string) []byte {
|
func Write2String(o *Ops, n int, ref1 any, ref2 string) []byte {
|
||||||
o.data = append(o.data, make([]byte, n)...)
|
o.data = append(o.data, make([]byte, n)...)
|
||||||
o.stringRefs = append(o.stringRefs, ref2)
|
o.stringRefs = append(o.stringRefs, ref2)
|
||||||
o.refs = append(o.refs, ref1, &o.stringRefs[len(o.stringRefs)-1])
|
o.refs = append(o.refs, ref1, &o.stringRefs[len(o.stringRefs)-1])
|
||||||
return o.data[len(o.data)-n:]
|
return o.data[len(o.data)-n:]
|
||||||
}
|
}
|
||||||
|
|
||||||
func Write3(o *Ops, n int, ref1, ref2, ref3 interface{}) []byte {
|
func Write3(o *Ops, n int, ref1, ref2, ref3 any) []byte {
|
||||||
o.data = append(o.data, make([]byte, n)...)
|
o.data = append(o.data, make([]byte, n)...)
|
||||||
o.refs = append(o.refs, ref1, ref2, ref3)
|
o.refs = append(o.refs, ref1, ref2, ref3)
|
||||||
return o.data[len(o.data)-n:]
|
return o.data[len(o.data)-n:]
|
||||||
@@ -425,28 +404,19 @@ var opProps = [0x100]opProp{
|
|||||||
TypePopTransform: {Size: TypePopTransformLen, NumRefs: 0},
|
TypePopTransform: {Size: TypePopTransformLen, NumRefs: 0},
|
||||||
TypePushOpacity: {Size: TypePushOpacityLen, NumRefs: 0},
|
TypePushOpacity: {Size: TypePushOpacityLen, NumRefs: 0},
|
||||||
TypePopOpacity: {Size: TypePopOpacityLen, NumRefs: 0},
|
TypePopOpacity: {Size: TypePopOpacityLen, NumRefs: 0},
|
||||||
TypeInvalidate: {Size: TypeRedrawLen, NumRefs: 0},
|
|
||||||
TypeImage: {Size: TypeImageLen, NumRefs: 2},
|
TypeImage: {Size: TypeImageLen, NumRefs: 2},
|
||||||
TypePaint: {Size: TypePaintLen, NumRefs: 0},
|
TypePaint: {Size: TypePaintLen, NumRefs: 0},
|
||||||
TypeColor: {Size: TypeColorLen, NumRefs: 0},
|
TypeColor: {Size: TypeColorLen, NumRefs: 0},
|
||||||
TypeLinearGradient: {Size: TypeLinearGradientLen, NumRefs: 0},
|
TypeLinearGradient: {Size: TypeLinearGradientLen, NumRefs: 0},
|
||||||
TypePass: {Size: TypePassLen, NumRefs: 0},
|
TypePass: {Size: TypePassLen, NumRefs: 0},
|
||||||
TypePopPass: {Size: TypePopPassLen, NumRefs: 0},
|
TypePopPass: {Size: TypePopPassLen, NumRefs: 0},
|
||||||
TypePointerInput: {Size: TypePointerInputLen, NumRefs: 1},
|
TypeInput: {Size: TypeInputLen, NumRefs: 1},
|
||||||
TypeClipboardRead: {Size: TypeClipboardReadLen, NumRefs: 1},
|
TypeKeyInputHint: {Size: TypeKeyInputHintLen, NumRefs: 1},
|
||||||
TypeClipboardWrite: {Size: TypeClipboardWriteLen, NumRefs: 1},
|
|
||||||
TypeSource: {Size: TypeSourceLen, NumRefs: 2},
|
|
||||||
TypeTarget: {Size: TypeTargetLen, NumRefs: 2},
|
|
||||||
TypeOffer: {Size: TypeOfferLen, NumRefs: 3},
|
|
||||||
TypeKeyInput: {Size: TypeKeyInputLen, NumRefs: 2},
|
|
||||||
TypeKeyFocus: {Size: TypeKeyFocusLen, NumRefs: 1},
|
|
||||||
TypeKeySoftKeyboard: {Size: TypeKeySoftKeyboardLen, NumRefs: 0},
|
|
||||||
TypeSave: {Size: TypeSaveLen, NumRefs: 0},
|
TypeSave: {Size: TypeSaveLen, NumRefs: 0},
|
||||||
TypeLoad: {Size: TypeLoadLen, NumRefs: 0},
|
TypeLoad: {Size: TypeLoadLen, NumRefs: 0},
|
||||||
TypeAux: {Size: TypeAuxLen, NumRefs: 0},
|
TypeAux: {Size: TypeAuxLen, NumRefs: 0},
|
||||||
TypeClip: {Size: TypeClipLen, NumRefs: 0},
|
TypeClip: {Size: TypeClipLen, NumRefs: 0},
|
||||||
TypePopClip: {Size: TypePopClipLen, NumRefs: 0},
|
TypePopClip: {Size: TypePopClipLen, NumRefs: 0},
|
||||||
TypeProfile: {Size: TypeProfileLen, NumRefs: 1},
|
|
||||||
TypeCursor: {Size: TypeCursorLen, NumRefs: 0},
|
TypeCursor: {Size: TypeCursorLen, NumRefs: 0},
|
||||||
TypePath: {Size: TypePathLen, NumRefs: 0},
|
TypePath: {Size: TypePathLen, NumRefs: 0},
|
||||||
TypeStroke: {Size: TypeStrokeLen, NumRefs: 0},
|
TypeStroke: {Size: TypeStrokeLen, NumRefs: 0},
|
||||||
@@ -455,8 +425,6 @@ var opProps = [0x100]opProp{
|
|||||||
TypeSemanticClass: {Size: TypeSemanticClassLen, NumRefs: 0},
|
TypeSemanticClass: {Size: TypeSemanticClassLen, NumRefs: 0},
|
||||||
TypeSemanticSelected: {Size: TypeSemanticSelectedLen, NumRefs: 0},
|
TypeSemanticSelected: {Size: TypeSemanticSelectedLen, NumRefs: 0},
|
||||||
TypeSemanticEnabled: {Size: TypeSemanticEnabledLen, NumRefs: 0},
|
TypeSemanticEnabled: {Size: TypeSemanticEnabledLen, NumRefs: 0},
|
||||||
TypeSnippet: {Size: TypeSnippetLen, NumRefs: 2},
|
|
||||||
TypeSelection: {Size: TypeSelectionLen, NumRefs: 1},
|
|
||||||
TypeActionInput: {Size: TypeActionInputLen, NumRefs: 0},
|
TypeActionInput: {Size: TypeActionInputLen, NumRefs: 0},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -489,8 +457,6 @@ func (t OpType) String() string {
|
|||||||
return "PushOpacity"
|
return "PushOpacity"
|
||||||
case TypePopOpacity:
|
case TypePopOpacity:
|
||||||
return "PopOpacity"
|
return "PopOpacity"
|
||||||
case TypeInvalidate:
|
|
||||||
return "Invalidate"
|
|
||||||
case TypeImage:
|
case TypeImage:
|
||||||
return "Image"
|
return "Image"
|
||||||
case TypePaint:
|
case TypePaint:
|
||||||
@@ -503,24 +469,10 @@ func (t OpType) String() string {
|
|||||||
return "Pass"
|
return "Pass"
|
||||||
case TypePopPass:
|
case TypePopPass:
|
||||||
return "PopPass"
|
return "PopPass"
|
||||||
case TypePointerInput:
|
case TypeInput:
|
||||||
return "PointerInput"
|
return "Input"
|
||||||
case TypeClipboardRead:
|
case TypeKeyInputHint:
|
||||||
return "ClipboardRead"
|
return "KeyInputHint"
|
||||||
case TypeClipboardWrite:
|
|
||||||
return "ClipboardWrite"
|
|
||||||
case TypeSource:
|
|
||||||
return "Source"
|
|
||||||
case TypeTarget:
|
|
||||||
return "Target"
|
|
||||||
case TypeOffer:
|
|
||||||
return "Offer"
|
|
||||||
case TypeKeyInput:
|
|
||||||
return "KeyInput"
|
|
||||||
case TypeKeyFocus:
|
|
||||||
return "KeyFocus"
|
|
||||||
case TypeKeySoftKeyboard:
|
|
||||||
return "KeySoftKeyboard"
|
|
||||||
case TypeSave:
|
case TypeSave:
|
||||||
return "Save"
|
return "Save"
|
||||||
case TypeLoad:
|
case TypeLoad:
|
||||||
@@ -531,8 +483,6 @@ func (t OpType) String() string {
|
|||||||
return "Clip"
|
return "Clip"
|
||||||
case TypePopClip:
|
case TypePopClip:
|
||||||
return "PopClip"
|
return "PopClip"
|
||||||
case TypeProfile:
|
|
||||||
return "Profile"
|
|
||||||
case TypeCursor:
|
case TypeCursor:
|
||||||
return "Cursor"
|
return "Cursor"
|
||||||
case TypePath:
|
case TypePath:
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ type Reader struct {
|
|||||||
type EncodedOp struct {
|
type EncodedOp struct {
|
||||||
Key Key
|
Key Key
|
||||||
Data []byte
|
Data []byte
|
||||||
Refs []interface{}
|
Refs []any
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key is a unique key for a given op.
|
// Key is a unique key for a given op.
|
||||||
@@ -175,7 +175,7 @@ func (op *opMacroDef) decode(data []byte) {
|
|||||||
op.endpc.refs = bo.Uint32(data[5:])
|
op.endpc.refs = bo.Uint32(data[5:])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *macroOp) decode(data []byte, refs []interface{}) {
|
func (m *macroOp) decode(data []byte, refs []any) {
|
||||||
if len(data) < TypeCallLen || len(refs) < 1 || OpType(data[0]) != TypeCall {
|
if len(data) < TypeCallLen || len(refs) < 1 || OpType(data[0]) != TypeCall {
|
||||||
panic("invalid op")
|
panic("invalid op")
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-10
@@ -87,7 +87,7 @@ func (qs *StrokeQuads) lineTo(pt f32.Point) {
|
|||||||
func (qs *StrokeQuads) arc(f1, f2 f32.Point, angle float32) {
|
func (qs *StrokeQuads) arc(f1, f2 f32.Point, angle float32) {
|
||||||
pen := qs.pen()
|
pen := qs.pen()
|
||||||
m, segments := ArcTransform(pen, f1.Add(pen), f2.Add(pen), angle)
|
m, segments := ArcTransform(pen, f1.Add(pen), f2.Add(pen), angle)
|
||||||
for i := 0; i < segments; i++ {
|
for range segments {
|
||||||
p0 := qs.pen()
|
p0 := qs.pen()
|
||||||
p1 := m.Transform(p0)
|
p1 := m.Transform(p0)
|
||||||
p2 := m.Transform(p1)
|
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 rot90CW(p f32.Point) f32.Point { return f32.Pt(+p.Y, -p.X) }
|
||||||
|
|
||||||
func normPt(p f32.Point, l float32) f32.Point {
|
func normPt(p f32.Point, l float32) f32.Point {
|
||||||
|
if (p.X == 0 && p.Y == 0) || l == 0 {
|
||||||
|
return f32.Point{}
|
||||||
|
}
|
||||||
|
isVerticalUnit := p.X == 0 && (p.Y == l || p.Y == -l)
|
||||||
|
isHorizontalUnit := p.Y == 0 && (p.X == l || p.X == -l)
|
||||||
|
if isVerticalUnit || isHorizontalUnit {
|
||||||
|
if math.Signbit(float64(l)) {
|
||||||
|
return f32.Point{X: -p.X, Y: -p.Y}
|
||||||
|
} else {
|
||||||
|
return f32.Point{X: p.X, Y: p.Y}
|
||||||
|
}
|
||||||
|
}
|
||||||
d := math.Hypot(float64(p.X), float64(p.Y))
|
d := math.Hypot(float64(p.X), float64(p.Y))
|
||||||
l64 := float64(l)
|
l64 := float64(l)
|
||||||
if math.Abs(d-l64) < 1e-10 {
|
if math.Abs(d-l64) < 1e-10 {
|
||||||
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)
|
n := float32(l64 / d)
|
||||||
return f32.Point{X: p.X * n, Y: p.Y * n}
|
return f32.Point{X: p.X * n, Y: p.Y * n}
|
||||||
@@ -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) {
|
func (qs *StrokeQuads) addLine(p0, ctrl, p1 f32.Point, t, d float32) {
|
||||||
|
|
||||||
switch i := len(*qs); i {
|
switch i := len(*qs); i {
|
||||||
case 0:
|
case 0:
|
||||||
p0 = p0.Add(strokePathNorm(p0, ctrl, p1, 0, d))
|
p0 = p0.Add(strokePathNorm(p0, ctrl, p1, 0, d))
|
||||||
@@ -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,
|
// quadBezierSplit returns the pair of triplets (from,ctrl,to) Bézier curve,
|
||||||
// split before (resp. after) the provided parametric t value.
|
// split before (resp. after) the provided parametric t value.
|
||||||
func quadBezierSplit(p0, p1, p2 f32.Point, t float32) (f32.Point, f32.Point, f32.Point, f32.Point, f32.Point, f32.Point) {
|
func quadBezierSplit(p0, p1, p2 f32.Point, t float32) (f32.Point, f32.Point, f32.Point, f32.Point, f32.Point, f32.Point) {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
b0 = p0
|
b0 = p0
|
||||||
b1 = quadInterp(p0, p1, t)
|
b1 = quadInterp(p0, p1, t)
|
||||||
@@ -574,12 +588,10 @@ func ArcTransform(p, f1, f2 f32.Point, angle float32) (transform f32.Affine2D, s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
θ := angle / float32(segments)
|
||||||
θ = angle / float32(segments)
|
ref := f32.AffineId() // transform from absolute frame to ellipse-based one
|
||||||
ref f32.Affine2D // transform from absolute frame to ellipse-based one
|
rot := f32.AffineId() // rotation matrix for each segment
|
||||||
rot f32.Affine2D // rotation matrix for each segment
|
inv := f32.AffineId() // transform from ellipse-based frame to absolute one
|
||||||
inv f32.Affine2D // transform from ellipse-based frame to absolute one
|
|
||||||
)
|
|
||||||
center := f32.Point{
|
center := f32.Point{
|
||||||
X: 0.5 * (f1.X + f2.X),
|
X: 0.5 * (f1.X + f2.X),
|
||||||
Y: 0.5 * (f1.Y + f2.Y),
|
Y: 0.5 * (f1.Y + f2.Y),
|
||||||
|
|||||||
@@ -9,6 +9,111 @@ import (
|
|||||||
"gioui.org/internal/f32"
|
"gioui.org/internal/f32"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestNormPt(t *testing.T) {
|
||||||
|
type scenario struct {
|
||||||
|
l float32
|
||||||
|
ptIn, ptOut f32.Point
|
||||||
|
}
|
||||||
|
|
||||||
|
scenarios := []scenario{
|
||||||
|
// l!=0 && X=Y=0
|
||||||
|
{l: 10, ptIn: f32.Point{X: 0, Y: 0}, ptOut: f32.Point{X: 0, Y: 0}},
|
||||||
|
{l: -10, ptIn: f32.Point{X: 0, Y: 0}, ptOut: f32.Point{X: 0, Y: 0}},
|
||||||
|
|
||||||
|
// l>0 & X
|
||||||
|
{l: +20, ptIn: f32.Point{X: +30, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
|
||||||
|
{l: +20, ptIn: f32.Point{X: +20, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
|
||||||
|
{l: +20, ptIn: f32.Point{X: +10, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
|
||||||
|
{l: +20, ptIn: f32.Point{X: -10, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
|
||||||
|
{l: +20, ptIn: f32.Point{X: -20, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
|
||||||
|
{l: +20, ptIn: f32.Point{X: -30, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
|
||||||
|
|
||||||
|
// l<0 & X
|
||||||
|
{l: -20, ptIn: f32.Point{X: +30, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
|
||||||
|
{l: -20, ptIn: f32.Point{X: +20, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
|
||||||
|
{l: -20, ptIn: f32.Point{X: +10, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
|
||||||
|
{l: -20, ptIn: f32.Point{X: -10, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
|
||||||
|
{l: -20, ptIn: f32.Point{X: -20, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
|
||||||
|
{l: -20, ptIn: f32.Point{X: -30, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
|
||||||
|
|
||||||
|
// l>0 & Y
|
||||||
|
{l: +20, ptIn: f32.Point{X: 0, Y: +30}, ptOut: f32.Point{X: 0, Y: +20}},
|
||||||
|
{l: +20, ptIn: f32.Point{X: 0, Y: +20}, ptOut: f32.Point{X: 0, Y: +20}},
|
||||||
|
{l: +20, ptIn: f32.Point{X: 0, Y: +10}, ptOut: f32.Point{X: 0, Y: +20}},
|
||||||
|
{l: +20, ptIn: f32.Point{X: 0, Y: -10}, ptOut: f32.Point{X: 0, Y: -20}},
|
||||||
|
{l: +20, ptIn: f32.Point{X: 0, Y: -20}, ptOut: f32.Point{X: 0, Y: -20}},
|
||||||
|
{l: +20, ptIn: f32.Point{X: 0, Y: -30}, ptOut: f32.Point{X: 0, Y: -20}},
|
||||||
|
|
||||||
|
// l<0 & Y
|
||||||
|
{l: -20, ptIn: f32.Point{X: 0, Y: +30}, ptOut: f32.Point{X: 0, Y: -20}},
|
||||||
|
{l: -20, ptIn: f32.Point{X: 0, Y: +20}, ptOut: f32.Point{X: 0, Y: -20}},
|
||||||
|
{l: -20, ptIn: f32.Point{X: 0, Y: +10}, ptOut: f32.Point{X: 0, Y: -20}},
|
||||||
|
{l: -20, ptIn: f32.Point{X: 0, Y: -10}, ptOut: f32.Point{X: 0, Y: +20}},
|
||||||
|
{l: -20, ptIn: f32.Point{X: 0, Y: -20}, ptOut: f32.Point{X: 0, Y: +20}},
|
||||||
|
{l: -20, ptIn: f32.Point{X: 0, Y: -30}, ptOut: f32.Point{X: 0, Y: +20}},
|
||||||
|
|
||||||
|
// l>0 && X=Y
|
||||||
|
{l: +20, ptIn: f32.Point{X: +90, Y: +90}, ptOut: f32.Point{X: +14.142137, Y: +14.142137}},
|
||||||
|
{l: +20, ptIn: f32.Point{X: +30, Y: +30}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
|
||||||
|
{l: +20, ptIn: f32.Point{X: +20, Y: +20}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
|
||||||
|
{l: +20, ptIn: f32.Point{X: +10, Y: +10}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
|
||||||
|
{l: +20, ptIn: f32.Point{X: -10, Y: -10}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
|
||||||
|
{l: +20, ptIn: f32.Point{X: -20, Y: -20}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
|
||||||
|
{l: +20, ptIn: f32.Point{X: -30, Y: -30}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
|
||||||
|
{l: +20, ptIn: f32.Point{X: -90, Y: -90}, ptOut: f32.Point{X: -14.142137, Y: -14.142137}},
|
||||||
|
|
||||||
|
// l>0 && X=-Y
|
||||||
|
{l: +20, ptIn: f32.Point{X: +90, Y: -90}, ptOut: f32.Point{X: +14.142137, Y: -14.142137}},
|
||||||
|
{l: +20, ptIn: f32.Point{X: +30, Y: -30}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
|
||||||
|
{l: +20, ptIn: f32.Point{X: +20, Y: -20}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
|
||||||
|
{l: +20, ptIn: f32.Point{X: +10, Y: -10}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
|
||||||
|
{l: +20, ptIn: f32.Point{X: -10, Y: +10}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
|
||||||
|
{l: +20, ptIn: f32.Point{X: -20, Y: +20}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
|
||||||
|
{l: +20, ptIn: f32.Point{X: -30, Y: +30}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
|
||||||
|
{l: +20, ptIn: f32.Point{X: -90, Y: +90}, ptOut: f32.Point{X: -14.142137, Y: +14.142137}},
|
||||||
|
|
||||||
|
// l<0 && X=Y
|
||||||
|
{l: -20, ptIn: f32.Point{X: +90, Y: +90}, ptOut: f32.Point{X: -14.142137, Y: -14.142137}},
|
||||||
|
{l: -20, ptIn: f32.Point{X: +30, Y: +30}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
|
||||||
|
{l: -20, ptIn: f32.Point{X: +20, Y: +20}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
|
||||||
|
{l: -20, ptIn: f32.Point{X: +10, Y: +10}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
|
||||||
|
{l: -20, ptIn: f32.Point{X: -10, Y: -10}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
|
||||||
|
{l: -20, ptIn: f32.Point{X: -20, Y: -20}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
|
||||||
|
{l: -20, ptIn: f32.Point{X: -30, Y: -30}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
|
||||||
|
{l: -20, ptIn: f32.Point{X: -90, Y: -90}, ptOut: f32.Point{X: +14.142137, Y: +14.142137}},
|
||||||
|
|
||||||
|
// l<0 && X=-Y
|
||||||
|
{l: -20, ptIn: f32.Point{X: +90, Y: -90}, ptOut: f32.Point{X: -14.142137, Y: +14.142137}},
|
||||||
|
{l: -20, ptIn: f32.Point{X: +30, Y: -30}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
|
||||||
|
{l: -20, ptIn: f32.Point{X: +20, Y: -20}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
|
||||||
|
{l: -20, ptIn: f32.Point{X: +10, Y: -10}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
|
||||||
|
{l: -20, ptIn: f32.Point{X: -10, Y: +10}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
|
||||||
|
{l: -20, ptIn: f32.Point{X: -20, Y: +20}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
|
||||||
|
{l: -20, ptIn: f32.Point{X: -30, Y: +30}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
|
||||||
|
{l: -20, ptIn: f32.Point{X: -90, Y: +90}, ptOut: f32.Point{X: +14.142137, Y: -14.142137}},
|
||||||
|
|
||||||
|
// l!=0 && Hypot=l
|
||||||
|
{l: 5, ptIn: f32.Point{X: 3, Y: 4}, ptOut: f32.Point{X: 3, Y: 4}},
|
||||||
|
{l: 5, ptIn: f32.Point{X: 3, Y: -4}, ptOut: f32.Point{X: 3, Y: -4}},
|
||||||
|
{l: 5, ptIn: f32.Point{X: -3, Y: -4}, ptOut: f32.Point{X: -3, Y: -4}},
|
||||||
|
{l: 5, ptIn: f32.Point{X: -3, Y: 4}, ptOut: f32.Point{X: -3, Y: 4}},
|
||||||
|
{l: -5, ptIn: f32.Point{X: 3, Y: 4}, ptOut: f32.Point{X: -3, Y: -4}},
|
||||||
|
{l: -5, ptIn: f32.Point{X: 3, Y: -4}, ptOut: f32.Point{X: -3, Y: 4}},
|
||||||
|
{l: -5, ptIn: f32.Point{X: -3, Y: -4}, ptOut: f32.Point{X: 3, Y: 4}},
|
||||||
|
{l: -5, ptIn: f32.Point{X: -3, Y: 4}, ptOut: f32.Point{X: 3, Y: -4}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, s := range scenarios {
|
||||||
|
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||||
|
actual := normPt(s.ptIn, s.l)
|
||||||
|
if actual != s.ptOut {
|
||||||
|
t.Errorf("in: %v*%v, expected: %v, actual: %v", s.l, s.ptIn, s.ptOut, actual)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkSplitCubic(b *testing.B) {
|
func BenchmarkSplitCubic(b *testing.B) {
|
||||||
type scenario struct {
|
type scenario struct {
|
||||||
segments int
|
segments int
|
||||||
@@ -52,7 +157,7 @@ func BenchmarkSplitCubic(b *testing.B) {
|
|||||||
from, ctrl0, ctrl1, to := s.from, s.ctrl0, s.ctrl1, s.to
|
from, ctrl0, ctrl1, to := s.from, s.ctrl0, s.ctrl1, s.to
|
||||||
quads := make([]QuadSegment, s.segments)
|
quads := make([]QuadSegment, s.segments)
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for b.Loop() {
|
||||||
quads = SplitCubic(from, ctrl0, ctrl1, to, quads[:0])
|
quads = SplitCubic(from, ctrl0, ctrl1, to, quads[:0])
|
||||||
}
|
}
|
||||||
if len(quads) != s.segments {
|
if len(quads) != s.segments {
|
||||||
|
|||||||
@@ -385,6 +385,7 @@ static VkResult vkQueuePresentKHR(PFN_vkQueuePresentKHR f, VkQueue queue, const
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"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 nilFramebuffer, fmt.Errorf("vulkan: vkCreateFramebuffer: %w", err)
|
||||||
}
|
}
|
||||||
return fbo, nil
|
return fbo, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func DestroyFramebuffer(d Device, f Framebuffer) {
|
func DestroyFramebuffer(d Device, f Framebuffer) {
|
||||||
@@ -1893,27 +1893,27 @@ func BuildWriteDescriptorSetBuffer(set DescriptorSet, binding int, typ Descripto
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r PushConstantRange) StageFlags() ShaderStageFlags {
|
func PushConstantRangeStageFlags(r PushConstantRange) ShaderStageFlags {
|
||||||
return r.stageFlags
|
return r.stageFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r PushConstantRange) Offset() int {
|
func PushConstantRangeOffset(r PushConstantRange) int {
|
||||||
return int(r.offset)
|
return int(r.offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r PushConstantRange) Size() int {
|
func PushConstantRangeSize(r PushConstantRange) int {
|
||||||
return int(r.size)
|
return int(r.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p QueueFamilyProperties) Flags() QueueFlags {
|
func QueueFamilyPropertiesFlags(p QueueFamilyProperties) QueueFlags {
|
||||||
return p.queueFlags
|
return p.queueFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c SurfaceCapabilities) MinExtent() image.Point {
|
func SurfaceCapabilitiesMinExtent(c SurfaceCapabilities) image.Point {
|
||||||
return image.Pt(int(c.minImageExtent.width), int(c.minImageExtent.height))
|
return image.Pt(int(c.minImageExtent.width), int(c.minImageExtent.height))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c SurfaceCapabilities) MaxExtent() image.Point {
|
func SurfaceCapabilitiesMaxExtent(c SurfaceCapabilities) image.Point {
|
||||||
return image.Pt(int(c.maxImageExtent.width), int(c.maxImageExtent.height))
|
return image.Pt(int(c.maxImageExtent.width), int(c.maxImageExtent.height))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ static VkResult vkCreateAndroidSurfaceKHR(PFN_vkCreateAndroidSurfaceKHR f, VkIns
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ static VkResult vkCreateWaylandSurfaceKHR(PFN_vkCreateWaylandSurfaceKHR f, VkIns
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ static VkResult vkCreateXlibSurfaceKHR(PFN_vkCreateXlibSurfaceKHR f, VkInstance
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|||||||
+11
-24
@@ -3,35 +3,22 @@
|
|||||||
package clipboard
|
package clipboard
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gioui.org/internal/ops"
|
"io"
|
||||||
|
|
||||||
"gioui.org/io/event"
|
"gioui.org/io/event"
|
||||||
"gioui.org/op"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Event is generated when the clipboard content is requested.
|
// WriteCmd copies Text to the clipboard.
|
||||||
type Event struct {
|
type WriteCmd struct {
|
||||||
Text string
|
Type string
|
||||||
|
Data io.ReadCloser
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadOp requests the text of the clipboard, delivered to
|
// ReadCmd requests the text of the clipboard, delivered to
|
||||||
// the current handler through an Event.
|
// the handler through an [io/transfer.DataEvent].
|
||||||
type ReadOp struct {
|
type ReadCmd struct {
|
||||||
Tag event.Tag
|
Tag event.Tag
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteOp copies Text to the clipboard.
|
func (WriteCmd) ImplementsCommand() {}
|
||||||
type WriteOp struct {
|
func (ReadCmd) ImplementsCommand() {}
|
||||||
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() {}
|
|
||||||
|
|||||||
+21
-35
@@ -1,47 +1,33 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
/*
|
// Package event contains types for event handling.
|
||||||
Package event contains the types for event handling.
|
|
||||||
|
|
||||||
The Queue interface is the protocol for receiving external events.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
var queue event.Queue = ...
|
|
||||||
|
|
||||||
for _, e := range queue.Events(h) {
|
|
||||||
switch e.(type) {
|
|
||||||
...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
In general, handlers must be declared before events become
|
|
||||||
available. Other packages such as pointer and key provide
|
|
||||||
the means for declaring handlers for specific event types.
|
|
||||||
|
|
||||||
The following example declares a handler ready for key input:
|
|
||||||
|
|
||||||
import gioui.org/io/key
|
|
||||||
|
|
||||||
ops := new(op.Ops)
|
|
||||||
var h *Handler = ...
|
|
||||||
key.InputOp{Tag: h, Filter: ...}.Add(ops)
|
|
||||||
*/
|
|
||||||
package event
|
package event
|
||||||
|
|
||||||
// Queue maps an event handler key to the events
|
import (
|
||||||
// available to the handler.
|
"gioui.org/internal/ops"
|
||||||
type Queue interface {
|
"gioui.org/op"
|
||||||
// Events returns the available events for an
|
)
|
||||||
// event handler tag.
|
|
||||||
Events(t Tag) []Event
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tag is the stable identifier for an event handler.
|
// Tag is the stable identifier for an event handler.
|
||||||
// For a handler h, the tag is typically &h.
|
// For a handler h, the tag is typically &h.
|
||||||
type Tag interface{}
|
type Tag any
|
||||||
|
|
||||||
// Event is the marker interface for events.
|
// Event is the marker interface for events.
|
||||||
type Event interface {
|
type Event interface {
|
||||||
ImplementsEvent()
|
ImplementsEvent()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter represents a filter for [Event] types.
|
||||||
|
type Filter interface {
|
||||||
|
ImplementsFilter()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Op declares a tag for input routing at the current transformation
|
||||||
|
// and clip area hierarchy. It panics if tag is nil.
|
||||||
|
func Op(o *op.Ops, tag Tag) {
|
||||||
|
if tag == nil {
|
||||||
|
panic("Tag must be non-nil")
|
||||||
|
}
|
||||||
|
data := ops.Write1(&o.Internal, ops.TypeInputLen, tag)
|
||||||
|
data[0] = byte(ops.TypeInput)
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
package router
|
package input
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
f32internal "gioui.org/internal/f32"
|
f32internal "gioui.org/internal/f32"
|
||||||
"gioui.org/internal/ops"
|
"gioui.org/internal/ops"
|
||||||
"gioui.org/io/event"
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/key"
|
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/semantic"
|
"gioui.org/io/semantic"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
@@ -18,14 +18,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type pointerQueue struct {
|
type pointerQueue struct {
|
||||||
hitTree []hitNode
|
hitTree []hitNode
|
||||||
areas []areaNode
|
areas []areaNode
|
||||||
cursor pointer.Cursor
|
|
||||||
handlers map[event.Tag]*pointerHandler
|
|
||||||
pointers []pointerInfo
|
|
||||||
transfers []io.ReadCloser // pending data transfers
|
|
||||||
|
|
||||||
scratch []event.Tag
|
|
||||||
|
|
||||||
semantic struct {
|
semantic struct {
|
||||||
idsAssigned bool
|
idsAssigned bool
|
||||||
@@ -43,10 +37,15 @@ type hitNode struct {
|
|||||||
|
|
||||||
// For handler nodes.
|
// For handler nodes.
|
||||||
tag event.Tag
|
tag event.Tag
|
||||||
ktag event.Tag
|
|
||||||
pass bool
|
pass bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pointerState is the input state related to pointer events.
|
||||||
|
type pointerState struct {
|
||||||
|
cursor pointer.Cursor
|
||||||
|
pointers []pointerInfo
|
||||||
|
}
|
||||||
|
|
||||||
type pointerInfo struct {
|
type pointerInfo struct {
|
||||||
id pointer.ID
|
id pointer.ID
|
||||||
pressed bool
|
pressed bool
|
||||||
@@ -63,17 +62,21 @@ type pointerInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type pointerHandler struct {
|
type pointerHandler struct {
|
||||||
area int
|
// areaPlusOne is the index into the list of pointerQueue.areas, plus 1.
|
||||||
active bool
|
areaPlusOne int
|
||||||
wantsGrab bool
|
// setup tracks whether the handler has received
|
||||||
types pointer.Kind
|
// 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
|
// min and max horizontal/vertical scroll
|
||||||
scrollRange image.Rectangle
|
scrollX, scrollY pointer.ScrollRange
|
||||||
|
|
||||||
sourceMimes []string
|
sourceMimes []string
|
||||||
targetMimes []string
|
targetMimes []string
|
||||||
offeredMime string
|
|
||||||
data io.ReadCloser
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type areaOp struct {
|
type areaOp struct {
|
||||||
@@ -141,7 +144,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (c *pointerCollector) resetState() {
|
func (c *pointerCollector) resetState() {
|
||||||
c.state = collectState{}
|
c.state = collectState{
|
||||||
|
t: f32.AffineId(),
|
||||||
|
}
|
||||||
c.nodeStack = c.nodeStack[:0]
|
c.nodeStack = c.nodeStack[:0]
|
||||||
// Pop every node except the root.
|
// Pop every node except the root.
|
||||||
if len(c.q.hitTree) > 0 {
|
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.
|
// 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()
|
areaID := c.currentArea()
|
||||||
c.addHitNode(hitNode{
|
c.addHitNode(hitNode{
|
||||||
area: areaID,
|
area: areaID,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
pass: c.state.pass > 0,
|
pass: c.state.pass > 0,
|
||||||
})
|
})
|
||||||
h, ok := c.q.handlers[tag]
|
state.areaPlusOne = areaID + 1
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *pointerCollector) keyInputOp(op key.InputOp) {
|
func (s *pointerHandler) Reset() {
|
||||||
areaID := c.currentArea()
|
s.areaPlusOne = 0
|
||||||
c.addHitNode(hitNode{
|
|
||||||
area: areaID,
|
|
||||||
ktag: op.Tag,
|
|
||||||
pass: true,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *pointerCollector) actionInputOp(act system.Action) {
|
func (c *pointerCollector) actionInputOp(act system.Action) {
|
||||||
@@ -264,21 +254,108 @@ func (c *pointerCollector) actionInputOp(act system.Action) {
|
|||||||
area.action = act
|
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()
|
areaID := c.currentArea()
|
||||||
area := &c.q.areas[areaID]
|
area := &c.q.areas[areaID]
|
||||||
area.semantic.content.tag = op.Tag
|
area.semantic.content.tag = tag
|
||||||
if op.Kinds&(pointer.Press|pointer.Release) != 0 {
|
c.newHandler(tag, state)
|
||||||
area.semantic.content.gestures |= ClickGesture
|
}
|
||||||
|
|
||||||
|
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
|
return false
|
||||||
h := c.newHandler(op.Tag, events)
|
}
|
||||||
h.wantsGrab = h.wantsGrab || op.Grab
|
|
||||||
h.types = h.types | op.Kinds
|
func (p *pointerFilter) Merge(p2 pointerFilter) {
|
||||||
h.scrollRange = op.ScrollBounds
|
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) {
|
func (c *pointerCollector) semanticLabel(lbl string) {
|
||||||
@@ -322,23 +399,28 @@ func (c *pointerCollector) cursor(cursor pointer.Cursor) {
|
|||||||
area.cursor = cursor
|
area.cursor = cursor
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *pointerCollector) sourceOp(op transfer.SourceOp, events *handlerEvents) {
|
func (q *pointerQueue) offerData(handlers map[event.Tag]*handler, state pointerState, req transfer.OfferCmd) (pointerState, []taggedEvent) {
|
||||||
h := c.newHandler(op.Tag, events)
|
var evts []taggedEvent
|
||||||
h.sourceMimes = append(h.sourceMimes, op.Type)
|
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) {
|
func (c *pointerCollector) Reset() {
|
||||||
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() {
|
|
||||||
c.q.reset()
|
c.q.reset()
|
||||||
c.resetState()
|
c.resetState()
|
||||||
c.ensureRoot()
|
c.ensureRoot()
|
||||||
@@ -493,20 +575,6 @@ func (q *pointerQueue) hitTest(pos f32.Point, onNode func(*hitNode) bool) pointe
|
|||||||
return cursor
|
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 {
|
func (q *pointerQueue) invTransform(areaIdx int, p f32.Point) f32.Point {
|
||||||
if areaIdx == -1 {
|
if areaIdx == -1 {
|
||||||
return p
|
return p
|
||||||
@@ -531,24 +599,13 @@ func (q *pointerQueue) hit(areaIdx int, p f32.Point) (bool, pointer.Cursor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (q *pointerQueue) reset() {
|
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.hitTree = q.hitTree[:0]
|
||||||
q.areas = q.areas[:0]
|
q.areas = q.areas[:0]
|
||||||
q.semantic.idsAssigned = false
|
q.semantic.idsAssigned = false
|
||||||
for k, ids := range q.semantic.contentIDs {
|
for k, ids := range q.semantic.contentIDs {
|
||||||
for i := len(ids) - 1; i >= 0; i-- {
|
for i := len(ids) - 1; i >= 0; i-- {
|
||||||
if !ids[i].used {
|
if !ids[i].used {
|
||||||
ids = append(ids[:i], ids[i+1:]...)
|
ids = slices.Delete(ids, i, i+1)
|
||||||
} else {
|
} else {
|
||||||
ids[i].used = false
|
ids[i].used = false
|
||||||
}
|
}
|
||||||
@@ -559,80 +616,71 @@ func (q *pointerQueue) reset() {
|
|||||||
delete(q.semantic.contentIDs, k)
|
delete(q.semantic.contentIDs, k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, rc := range q.transfers {
|
|
||||||
if rc != nil {
|
|
||||||
rc.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
q.transfers = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *pointerQueue) Frame(events *handlerEvents) {
|
func (q *pointerQueue) Frame(handlers map[event.Tag]*handler, state pointerState) (pointerState, []taggedEvent) {
|
||||||
for k, h := range q.handlers {
|
for _, h := range handlers {
|
||||||
if !h.active {
|
if h.pointer.areaPlusOne != 0 {
|
||||||
q.dropHandler(nil, k)
|
area := &q.areas[h.pointer.areaPlusOne-1]
|
||||||
delete(q.handlers, k)
|
if h.filter.pointer.kinds&(pointer.Press|pointer.Release) != 0 {
|
||||||
}
|
area.semantic.content.gestures |= ClickGesture
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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 {
|
var evts []taggedEvent
|
||||||
p := &q.pointers[i]
|
for i, p := range state.pointers {
|
||||||
q.deliverEnterLeaveEvents(p, events, p.last)
|
changed := false
|
||||||
q.deliverTransferDataEvent(p, events)
|
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) {
|
func dropHandler(state pointerState, tag event.Tag) pointerState {
|
||||||
if events != nil {
|
pointers := state.pointers
|
||||||
events.Add(tag, pointer.Event{Kind: pointer.Cancel})
|
state.pointers = nil
|
||||||
}
|
for _, p := range pointers {
|
||||||
for i := range q.pointers {
|
handlers := p.handlers
|
||||||
p := &q.pointers[i]
|
p.handlers = nil
|
||||||
for i := len(p.handlers) - 1; i >= 0; i-- {
|
for _, h := range handlers {
|
||||||
if p.handlers[i] == tag {
|
if h != tag {
|
||||||
p.handlers = append(p.handlers[:i], p.handlers[i+1:]...)
|
p.handlers = append(p.handlers, h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i := len(p.entered) - 1; i >= 0; i-- {
|
entered := p.entered
|
||||||
if p.entered[i] == tag {
|
p.entered = nil
|
||||||
p.entered = append(p.entered[:i], p.entered[i+1:]...)
|
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.
|
// pointerOf returns the pointerInfo index corresponding to the pointer in e.
|
||||||
func (q *pointerQueue) pointerOf(e pointer.Event) int {
|
func (s pointerState) pointerOf(e pointer.Event) (pointerState, int) {
|
||||||
for i, p := range q.pointers {
|
for i, p := range s.pointers {
|
||||||
if p.id == e.PointerID {
|
if p.id == e.PointerID {
|
||||||
return i
|
return s, i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
q.pointers = append(q.pointers, pointerInfo{id: e.PointerID})
|
n := len(s.pointers)
|
||||||
return len(q.pointers) - 1
|
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.
|
// Deliver is like Push, but delivers an event to a particular area.
|
||||||
func (q *pointerQueue) Deliver(areaIdx int, e pointer.Event, events *handlerEvents) {
|
func (q *pointerQueue) Deliver(handlers map[event.Tag]*handler, areaIdx int, e pointer.Event) []taggedEvent {
|
||||||
var sx, sy = e.Scroll.X, e.Scroll.Y
|
scroll := e.Scroll
|
||||||
idx := len(q.hitTree) - 1
|
idx := len(q.hitTree) - 1
|
||||||
// Locate first potential receiver.
|
// Locate first potential receiver.
|
||||||
for idx != -1 {
|
for idx != -1 {
|
||||||
@@ -642,31 +690,28 @@ func (q *pointerQueue) Deliver(areaIdx int, e pointer.Event, events *handlerEven
|
|||||||
}
|
}
|
||||||
idx--
|
idx--
|
||||||
}
|
}
|
||||||
|
var evts []taggedEvent
|
||||||
for idx != -1 {
|
for idx != -1 {
|
||||||
n := &q.hitTree[idx]
|
n := &q.hitTree[idx]
|
||||||
idx = n.next
|
idx = n.next
|
||||||
if n.tag == nil {
|
h, ok := handlers[n.tag]
|
||||||
continue
|
if !ok || !h.filter.pointer.Matches(e) {
|
||||||
}
|
|
||||||
h := q.handlers[n.tag]
|
|
||||||
if e.Kind&h.types == 0 {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
e := e
|
e := e
|
||||||
if e.Kind == pointer.Scroll {
|
if e.Kind == pointer.Scroll {
|
||||||
if sx == 0 && sy == 0 {
|
if scroll == (f32.Point{}) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
// Distribute the scroll to the handler based on its ScrollRange.
|
scroll, e.Scroll = h.filter.pointer.clampScroll(scroll)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
e.Position = q.invTransform(h.area, e.Position)
|
e.Position = q.invTransform(h.pointer.areaPlusOne-1, e.Position)
|
||||||
events.Add(n.tag, e)
|
evts = append(evts, taggedEvent{tag: n.tag, event: e})
|
||||||
if e.Kind != pointer.Scroll {
|
if e.Kind != pointer.Scroll {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return evts
|
||||||
}
|
}
|
||||||
|
|
||||||
// SemanticArea returns the sematic content for area, and its parent area.
|
// 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
|
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 {
|
if e.Kind == pointer.Cancel {
|
||||||
q.pointers = q.pointers[:0]
|
for k := range handlers {
|
||||||
for k := range q.handlers {
|
evts = append(evts, taggedEvent{
|
||||||
q.dropHandler(events, k)
|
event: pointer.Event{Kind: pointer.Cancel},
|
||||||
|
tag: k,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return
|
state.pointers = nil
|
||||||
|
return state, evts
|
||||||
}
|
}
|
||||||
pidx := q.pointerOf(e)
|
state, pidx := state.pointerOf(e)
|
||||||
p := &q.pointers[pidx]
|
p := state.pointers[pidx]
|
||||||
p.last = e
|
|
||||||
|
|
||||||
switch e.Kind {
|
switch e.Kind {
|
||||||
case pointer.Press:
|
case pointer.Press:
|
||||||
q.deliverEnterLeaveEvents(p, events, e)
|
p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e)
|
||||||
p.pressed = true
|
p.pressed = true
|
||||||
q.deliverEvent(p, events, e)
|
evts = q.deliverEvent(handlers, p, evts, e)
|
||||||
case pointer.Move:
|
case pointer.Move:
|
||||||
if p.pressed {
|
if p.pressed {
|
||||||
e.Kind = pointer.Drag
|
e.Kind = pointer.Drag
|
||||||
}
|
}
|
||||||
q.deliverEnterLeaveEvents(p, events, e)
|
p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e)
|
||||||
q.deliverEvent(p, events, e)
|
evts = q.deliverEvent(handlers, p, evts, e)
|
||||||
if p.pressed {
|
if p.pressed {
|
||||||
q.deliverDragEvent(p, events)
|
p, evts = q.deliverDragEvent(handlers, p, evts)
|
||||||
}
|
}
|
||||||
case pointer.Release:
|
case pointer.Release:
|
||||||
q.deliverEvent(p, events, e)
|
evts = q.deliverEvent(handlers, p, evts, e)
|
||||||
p.pressed = false
|
p.pressed = false
|
||||||
q.deliverEnterLeaveEvents(p, events, e)
|
p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e)
|
||||||
q.deliverDropEvent(p, events)
|
p, evts = q.deliverDropEvent(handlers, p, evts)
|
||||||
case pointer.Scroll:
|
case pointer.Scroll:
|
||||||
q.deliverEnterLeaveEvents(p, events, e)
|
p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e)
|
||||||
q.deliverEvent(p, events, e)
|
evts = q.deliverEvent(handlers, p, evts, e)
|
||||||
default:
|
default:
|
||||||
panic("unsupported pointer event type")
|
panic("unsupported pointer event type")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.last = e
|
||||||
|
|
||||||
if !p.pressed && len(p.entered) == 0 {
|
if !p.pressed && len(p.entered) == 0 {
|
||||||
// No longer need to track pointer.
|
// 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) {
|
func (q *pointerQueue) deliverEvent(handlers map[event.Tag]*handler, p pointerInfo, evts []taggedEvent, e pointer.Event) []taggedEvent {
|
||||||
foremost := true
|
|
||||||
if p.pressed && len(p.handlers) == 1 {
|
if p.pressed && len(p.handlers) == 1 {
|
||||||
e.Priority = pointer.Grabbed
|
e.Priority = pointer.Grabbed
|
||||||
foremost = false
|
|
||||||
}
|
}
|
||||||
var sx, sy = e.Scroll.X, e.Scroll.Y
|
scroll := e.Scroll
|
||||||
for _, k := range p.handlers {
|
for _, k := range p.handlers {
|
||||||
h := q.handlers[k]
|
h, ok := handlers[k]
|
||||||
if e.Kind == pointer.Scroll {
|
if !ok {
|
||||||
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 {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
e := e
|
f := h.filter.pointer
|
||||||
if foremost {
|
if !f.Matches(e) {
|
||||||
foremost = false
|
continue
|
||||||
e.Priority = pointer.Foremost
|
|
||||||
}
|
}
|
||||||
e.Position = q.invTransform(h.area, e.Position)
|
if e.Kind == pointer.Scroll {
|
||||||
events.Add(k, e)
|
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
|
var hits []event.Tag
|
||||||
if e.Source != pointer.Mouse && !p.pressed && e.Kind != pointer.Press {
|
if e.Source != pointer.Mouse && !p.pressed && e.Kind != pointer.Press {
|
||||||
// Consider non-mouse pointers leaving when they're released.
|
// Consider non-mouse pointers leaving when they're released.
|
||||||
} else {
|
} else {
|
||||||
hits, q.cursor = q.opHit(e.Position)
|
var transSrc *pointerFilter
|
||||||
if p.pressed {
|
if p.dataSource != nil {
|
||||||
// Filter out non-participating handlers,
|
transSrc = &handlers[p.dataSource].filter.pointer
|
||||||
// except potential transfer targets when a transfer has been initiated.
|
}
|
||||||
var hitsHaveTarget bool
|
cursor = q.hitTest(e.Position, func(n *hitNode) bool {
|
||||||
if p.dataSource != nil {
|
h, ok := handlers[n.tag]
|
||||||
transferSource := q.handlers[p.dataSource]
|
if !ok {
|
||||||
for _, hit := range hits {
|
return true
|
||||||
if _, ok := firstMimeMatch(transferSource, q.handlers[hit]); ok {
|
}
|
||||||
hitsHaveTarget = true
|
add := true
|
||||||
break
|
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 add {
|
||||||
if _, found := searchTag(p.handlers, hits[i]); !found && !hitsHaveTarget {
|
hits = addHandler(hits, n.tag)
|
||||||
hits = append(hits[:i], hits[i+1:]...)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
return true
|
||||||
p.handlers = append(p.handlers[:0], hits...)
|
})
|
||||||
|
if !p.pressed {
|
||||||
|
changed = true
|
||||||
|
p.handlers = hits
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Deliver Leave events.
|
// Deliver Leave events.
|
||||||
@@ -789,111 +851,94 @@ func (q *pointerQueue) deliverEnterLeaveEvents(p *pointerInfo, events *handlerEv
|
|||||||
if _, found := searchTag(hits, k); found {
|
if _, found := searchTag(hits, k); found {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
h := q.handlers[k]
|
h, ok := handlers[k]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
changed = true
|
||||||
|
e := e
|
||||||
e.Kind = pointer.Leave
|
e.Kind = pointer.Leave
|
||||||
|
|
||||||
if e.Kind&h.types != 0 {
|
if h.filter.pointer.Matches(e) {
|
||||||
e := e
|
e.Position = q.invTransform(h.pointer.areaPlusOne-1, e.Position)
|
||||||
e.Position = q.invTransform(h.area, e.Position)
|
evts = append(evts, taggedEvent{tag: k, event: e})
|
||||||
events.Add(k, e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Deliver Enter events.
|
// Deliver Enter events.
|
||||||
for _, k := range hits {
|
for _, k := range hits {
|
||||||
h := q.handlers[k]
|
|
||||||
if _, found := searchTag(p.entered, k); found {
|
if _, found := searchTag(p.entered, k); found {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
h, ok := handlers[k]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
changed = true
|
||||||
|
e := e
|
||||||
e.Kind = pointer.Enter
|
e.Kind = pointer.Enter
|
||||||
|
|
||||||
if e.Kind&h.types != 0 {
|
if h.filter.pointer.Matches(e) {
|
||||||
e := e
|
e.Position = q.invTransform(h.pointer.areaPlusOne-1, e.Position)
|
||||||
e.Position = q.invTransform(h.area, e.Position)
|
evts = append(evts, taggedEvent{tag: k, event: e})
|
||||||
events.Add(k, 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 {
|
if p.dataSource != nil {
|
||||||
return
|
return p, evts
|
||||||
}
|
}
|
||||||
// Identify the data source.
|
// Identify the data source.
|
||||||
for _, k := range p.entered {
|
for _, k := range p.entered {
|
||||||
src := q.handlers[k]
|
src := &handlers[k].filter.pointer
|
||||||
if len(src.sourceMimes) == 0 {
|
if len(src.sourceMimes) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// One data source handler per pointer.
|
// One data source handler per pointer.
|
||||||
p.dataSource = k
|
p.dataSource = k
|
||||||
// Notify all potential targets.
|
// Notify all potential targets.
|
||||||
for k, tgt := range q.handlers {
|
for k, tgt := range handlers {
|
||||||
if _, ok := firstMimeMatch(src, tgt); ok {
|
if _, ok := firstMimeMatch(src, &tgt.filter.pointer); ok {
|
||||||
events.Add(k, transfer.InitiateEvent{})
|
evts = append(evts, taggedEvent{tag: k, event: transfer.InitiateEvent{}})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
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 {
|
if p.dataSource == nil {
|
||||||
return
|
return p, evts
|
||||||
}
|
}
|
||||||
// Request data from the source.
|
// Request data from the source.
|
||||||
src := q.handlers[p.dataSource]
|
src := &handlers[p.dataSource].filter.pointer
|
||||||
for _, k := range p.entered {
|
for _, k := range p.entered {
|
||||||
h := q.handlers[k]
|
h := handlers[k]
|
||||||
if m, ok := firstMimeMatch(src, h); ok {
|
if m, ok := firstMimeMatch(src, &h.filter.pointer); ok {
|
||||||
p.dataTarget = k
|
p.dataTarget = k
|
||||||
events.Add(p.dataSource, transfer.RequestEvent{Type: m})
|
evts = append(evts, taggedEvent{tag: p.dataSource, event: transfer.RequestEvent{Type: m}})
|
||||||
return
|
return p, evts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// No valid target found, abort.
|
// No valid target found, abort.
|
||||||
q.deliverTransferCancelEvent(p, events)
|
return q.deliverTransferCancelEvent(handlers, p, evts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *pointerQueue) deliverTransferDataEvent(p *pointerInfo, events *handlerEvents) {
|
func (q *pointerQueue) deliverTransferCancelEvent(handlers map[event.Tag]*handler, p pointerInfo, evts []taggedEvent) (pointerInfo, []taggedEvent) {
|
||||||
if p.dataSource == nil {
|
evts = append(evts, taggedEvent{tag: p.dataSource, event: transfer.CancelEvent{}})
|
||||||
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{})
|
|
||||||
// Cancel all potential targets.
|
// Cancel all potential targets.
|
||||||
src := q.handlers[p.dataSource]
|
src := &handlers[p.dataSource].filter.pointer
|
||||||
for k, h := range q.handlers {
|
for k, h := range handlers {
|
||||||
if _, ok := firstMimeMatch(src, h); ok {
|
if _, ok := firstMimeMatch(src, &h.filter.pointer); ok {
|
||||||
events.Add(k, transfer.CancelEvent{})
|
evts = append(evts, taggedEvent{tag: k, event: transfer.CancelEvent{}})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
src.offeredMime = ""
|
|
||||||
src.data = nil
|
|
||||||
p.dataSource = nil
|
p.dataSource = nil
|
||||||
p.dataTarget = nil
|
p.dataTarget = nil
|
||||||
|
return p, evts
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClipFor clips r to the parents of area.
|
// 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.
|
// addHandler adds tag to the slice if not present.
|
||||||
func addHandler(tags []event.Tag, tag event.Tag) []event.Tag {
|
func addHandler(tags []event.Tag, tag event.Tag) []event.Tag {
|
||||||
for _, t := range tags {
|
if slices.Contains(tags, tag) {
|
||||||
if t == tag {
|
return tags
|
||||||
return tags
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return append(tags, tag)
|
return append(tags, tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
// firstMimeMatch returns the first type match between src and tgt.
|
// 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 _, m1 := range tgt.targetMimes {
|
||||||
for _, m2 := range src.sourceMimes {
|
if slices.Contains(src.sourceMimes, m1) {
|
||||||
if m1 == m2 {
|
return m1, true
|
||||||
return m1, true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", false
|
return "", false
|
||||||
@@ -965,13 +1006,3 @@ func (a *areaNode) bounds() image.Rectangle {
|
|||||||
Max: a.trans.Transform(f32internal.FPt(a.area.rect.Max)),
|
Max: a.trans.Transform(f32internal.FPt(a.area.rect.Max)),
|
||||||
}.Round()
|
}.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