5 Commits

Author SHA1 Message Date
Elias Naur 3879921b80 app: [API] remove Main
All platforms already allow the omission of the call to Main and running
Windows on the main goroutine. This change just gets rid of Main, and
documents the special requirement on Window.Event.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 18:53:33 +00:00
Elias Naur b1f84da679 app: [macOS] implement custom event dispatching
To get rid of app.Main, we need to control the main thread. The macOS
[NSApp run] must be called on the main goroutine and never yields control.

Implement the escape hatch which is calling [NSApp stop] to force
[NSApp run] to return and allow us to fetch and dispatch events one at a
time.

This change is separate from the larger change removing app.Main to ease
bisecting.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 18:53:12 +00:00
Elias Naur 43024fcca2 app: [macOS] implement non-blocking window resizing
Despite the option to control the main thread event loop, some gestures
still block the event loop until completion. This change disables the
blocking resize gestures and implements a non-blocking replacement.

A complete replacement is left for future work, or the implementation of
https://github.com/golang/go/issues/64755.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 18:52:04 +00:00
Elias Naur 9fe8b684e2 app: introduce Config.Focused that tracks the window focus state
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 18:52:04 +00:00
Elias Naur 2a18a0c135 app: [macOS] synchronize rendering with Core Animation for smooth resizes
Magic incantations lifted from

https://thume.ca/2019/06/19/glitchless-metal-window-resizing/

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 18:52:04 +00:00
126 changed files with 4550 additions and 3141 deletions
+2 -2
View File
@@ -29,7 +29,7 @@ environment:
tasks: tasks:
- install_go: | - install_go: |
mkdir -p /home/build/sdk mkdir -p /home/build/sdk
curl -s https://dl.google.com/go/go1.24.2.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf - curl -s https://dl.google.com/go/go1.19.11.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
- prepare_toolchain: | - prepare_toolchain: |
mkdir -p $APPLE_TOOLCHAIN_ROOT mkdir -p $APPLE_TOOLCHAIN_ROOT
cd $APPLE_TOOLCHAIN_ROOT cd $APPLE_TOOLCHAIN_ROOT
@@ -71,4 +71,4 @@ tasks:
CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-macos GOOS=darwin CGO_ENABLED=1 go build ./... CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-macos GOOS=darwin CGO_ENABLED=1 go build ./...
- test_ios: | - test_ios: |
cd gio cd gio
CGO_CFLAGS=-Wno-deprecated-module-dot-map CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-ios GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go build -tags ios ./... CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-ios GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go build -tags ios ./...
+2 -2
View File
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: Unlicense OR MIT # SPDX-License-Identifier: Unlicense OR MIT
image: freebsd/latest image: freebsd/13.x
packages: packages:
- libX11 - libX11
- libxkbcommon - libxkbcommon
@@ -16,7 +16,7 @@ environment:
tasks: tasks:
- install_go: | - install_go: |
mkdir -p /home/build/sdk mkdir -p /home/build/sdk
curl https://dl.google.com/go/go1.24.2.freebsd-amd64.tar.gz | tar -C /home/build/sdk -xzf - curl https://dl.google.com/go/go1.19.11.freebsd-amd64.tar.gz | tar -C /home/build/sdk -xzf -
- test_gio: | - test_gio: |
cd gio cd gio
go test ./... go test ./...
+1 -3
View File
@@ -40,7 +40,7 @@ secrets:
tasks: tasks:
- install_go: | - install_go: |
mkdir -p /home/build/sdk mkdir -p /home/build/sdk
curl -s https://dl.google.com/go/go1.24.2.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf - curl -s https://dl.google.com/go/go1.19.11.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
- check_gofmt: | - check_gofmt: |
cd gio cd gio
test -z "$(gofmt -s -l .)" test -z "$(gofmt -s -l .)"
@@ -80,8 +80,6 @@ tasks:
unzip -q ndk.zip unzip -q ndk.zip
rm ndk.zip rm ndk.zip
mv android-ndk-* ndk-bundle mv android-ndk-* ndk-bundle
# sdkmanager needs lots of file descriptors
ulimit -n 10000
yes|sdkmanager --licenses yes|sdkmanager --licenses
sdkmanager "platforms;android-31" "build-tools;32.0.0" sdkmanager "platforms;android-31" "build-tools;32.0.0"
- test_android: | - test_android: |
+1 -1
View File
@@ -10,7 +10,7 @@ environment:
tasks: tasks:
- install_go: | - install_go: |
mkdir -p /home/build/sdk mkdir -p /home/build/sdk
curl https://dl.google.com/go/go1.24.2.src.tar.gz | tar -C /home/build/sdk -xzf - curl https://dl.google.com/go/go1.19.11.src.tar.gz | tar -C /home/build/sdk -xzf -
cd /home/build/sdk/go/src cd /home/build/sdk/go/src
./make.bash ./make.bash
- test_gio: | - test_gio: |
-4
View File
@@ -259,10 +259,6 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal
} }
private void setHighRefreshRate() { private void setHighRefreshRate() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
return;
}
Context context = getContext(); Context context = getContext();
Display display = context.getDisplay(); Display display = context.getDisplay();
Display.Mode[] supportedModes = display.getSupportedModes(); Display.Mode[] supportedModes = display.getSupportedModes();
-16
View File
@@ -61,10 +61,6 @@ type FrameEvent struct {
type ViewEvent interface { type ViewEvent interface {
implementsViewEvent() implementsViewEvent()
ImplementsEvent() ImplementsEvent()
// Valid will return true when the ViewEvent does contains valid handles.
// If a window receives an invalid ViewEvent, it should deinitialize any
// state referring to handles from a previous ViewEvent.
Valid() bool
} }
// Insets is the space taken up by // Insets is the space taken up by
@@ -124,18 +120,6 @@ func DataDir() (string, error) {
return dataDir() return dataDir()
} }
// Main must be called last from the program main function.
// On most platforms Main blocks forever, for Android and
// iOS it returns immediately to give control of the main
// thread back to the system.
//
// Calling Main is necessary because some operating systems
// require control of the main thread of the program for
// running windows.
func Main() {
osMain()
}
func (FrameEvent) ImplementsEvent() {} func (FrameEvent) ImplementsEvent() {}
func init() { func init() {
+5 -21
View File
@@ -33,28 +33,12 @@ 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 Thread
The Main function must be called from a program's main function, to hand over Some GUI platform need access to the main thread of the program. To avoid a
control of the main thread to operating systems that need it. deadlock on such platforms, at least one Window must have its Event method
called by the main goroutine. It doesn't have to be any particular Window;
Because Main is also blocking on some platforms, the event loop of a Window must run in a goroutine. even a destroyed Window suffices.
For example, to display a blank but otherwise functional window:
package main
import "gioui.org/app"
func main() {
go func() {
w := app.NewWindow()
for {
w.Event()
}
}()
app.Main()
}
# Permissions # Permissions
+6 -4
View File
@@ -17,8 +17,9 @@ import (
) )
type androidContext struct { type androidContext struct {
win *window win *window
eglSurf egl.NativeWindowType eglSurf egl.NativeWindowType
width, height int
*egl.Context *egl.Context
} }
@@ -44,8 +45,9 @@ func (c *androidContext) Refresh() error {
if err := c.win.setVisual(c.Context.VisualID()); err != nil { if err := c.win.setVisual(c.Context.VisualID()); err != nil {
return err return err
} }
win, _, _ := c.win.nativeWindow() win, width, height := c.win.nativeWindow()
c.eglSurf = egl.NativeWindowType(unsafe.Pointer(win)) c.eglSurf = egl.NativeWindowType(unsafe.Pointer(win))
c.width, c.height = width, height
return nil return nil
} }
@@ -53,7 +55,7 @@ func (c *androidContext) Lock() error {
// The Android emulator creates a broken surface if it is not // The Android emulator creates a broken surface if it is not
// created on the same thread as the context is made current. // created on the same thread as the context is made current.
if c.eglSurf != nil { if c.eglSurf != nil {
if err := c.Context.CreateSurface(c.eglSurf); err != nil { if err := c.Context.CreateSurface(c.eglSurf, c.width, c.height); err != nil {
return err return err
} }
c.eglSurf = nil c.eglSurf = nil
+22 -19
View File
@@ -38,24 +38,7 @@ func init() {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &wlContext{Context: ctx, win: w}, nil
surf, width, height := w.surface()
if surf == nil {
return nil, errors.New("wayland: no surface")
}
eglWin := C.wl_egl_window_create(surf, C.int(width), C.int(height))
if eglWin == nil {
return nil, errors.New("wayland: wl_egl_window_create failed")
}
eglSurf := egl.NativeWindowType(uintptr(unsafe.Pointer(eglWin)))
if err := ctx.CreateSurface(eglSurf); err != nil {
return nil, err
}
// We're in charge of the frame callbacks, don't let eglSwapBuffers
// wait for callbacks that may never arrive.
ctx.EnableVSync(false)
return &wlContext{Context: ctx, win: w, eglWin: eglWin}, nil
} }
} }
@@ -71,11 +54,31 @@ func (c *wlContext) Release() {
} }
func (c *wlContext) Refresh() error { func (c *wlContext) Refresh() error {
c.Context.ReleaseSurface()
if c.eglWin != nil {
C.wl_egl_window_destroy(c.eglWin)
c.eglWin = nil
}
surf, width, height := c.win.surface() surf, width, height := c.win.surface()
if surf == nil { if surf == nil {
return errors.New("wayland: no surface") return errors.New("wayland: no surface")
} }
C.wl_egl_window_resize(c.eglWin, C.int(width), C.int(height), 0, 0) eglWin := C.wl_egl_window_create(surf, C.int(width), C.int(height))
if eglWin == nil {
return errors.New("wayland: wl_egl_window_create failed")
}
c.eglWin = eglWin
eglSurf := egl.NativeWindowType(uintptr(unsafe.Pointer(eglWin)))
if err := c.Context.CreateSurface(eglSurf, width, height); err != nil {
return err
}
if err := c.Context.MakeCurrent(); err != nil {
return err
}
defer c.Context.ReleaseCurrent()
// We're in charge of the frame callbacks, don't let eglSwapBuffers
// wait for callbacks that may never arrive.
c.Context.EnableVSync(false)
return nil return nil
} }
+17 -12
View File
@@ -5,6 +5,8 @@
package app package app
import ( import (
"golang.org/x/sys/windows"
"gioui.org/internal/egl" "gioui.org/internal/egl"
) )
@@ -22,18 +24,6 @@ func init() {
if err != nil { if err != nil {
return nil, err return nil, err
} }
win, _, _ := w.HWND()
eglSurf := egl.NativeWindowType(win)
if err := ctx.CreateSurface(eglSurf); err != nil {
ctx.Release()
return nil, err
}
if err := ctx.MakeCurrent(); err != nil {
ctx.Release()
return nil, err
}
defer ctx.ReleaseCurrent()
ctx.EnableVSync(true)
return &glContext{win: w, Context: ctx}, nil return &glContext{win: w, Context: ctx}, nil
}, },
}) })
@@ -47,6 +37,21 @@ func (c *glContext) Release() {
} }
func (c *glContext) Refresh() error { func (c *glContext) Refresh() error {
c.Context.ReleaseSurface()
var (
win windows.Handle
width, height int
)
win, width, height = c.win.HWND()
eglSurf := egl.NativeWindowType(win)
if err := c.Context.CreateSurface(eglSurf, width, height); err != nil {
return err
}
if err := c.Context.MakeCurrent(); err != nil {
return err
}
c.Context.EnableVSync(true)
c.Context.ReleaseCurrent()
return nil return nil
} }
+11 -12
View File
@@ -25,18 +25,6 @@ func init() {
if err != nil { if err != nil {
return nil, err return nil, err
} }
win, _, _ := w.window()
eglSurf := egl.NativeWindowType(uintptr(win))
if err := ctx.CreateSurface(eglSurf); err != nil {
ctx.Release()
return nil, err
}
if err := ctx.MakeCurrent(); err != nil {
ctx.Release()
return nil, err
}
defer ctx.ReleaseCurrent()
ctx.EnableVSync(true)
return &x11Context{win: w, Context: ctx}, nil return &x11Context{win: w, Context: ctx}, nil
} }
} }
@@ -49,6 +37,17 @@ func (c *x11Context) Release() {
} }
func (c *x11Context) Refresh() error { func (c *x11Context) Refresh() error {
c.Context.ReleaseSurface()
win, width, height := c.win.window()
eglSurf := egl.NativeWindowType(uintptr(win))
if err := c.Context.CreateSurface(eglSurf, width, height); err != nil {
return err
}
if err := c.Context.MakeCurrent(); err != nil {
return err
}
defer c.Context.ReleaseCurrent()
c.Context.EnableVSync(true)
return nil return nil
} }
+1
View File
@@ -8,6 +8,7 @@ package app
import ( import (
"errors" "errors"
"runtime" "runtime"
"unsafe" "unsafe"
"gioui.org/gpu" "gioui.org/gpu"
+1 -1
View File
@@ -7,7 +7,7 @@
#include <OpenGL/OpenGL.h> #include <OpenGL/OpenGL.h>
#include "_cgo_export.h" #include "_cgo_export.h"
CALayer *gio_layerFactory(BOOL presentWithTrans) { CALayer *gio_layerFactory(void) {
@autoreleasepool { @autoreleasepool {
return [CALayer layer]; return [CALayer layer];
} }
-27
View File
@@ -1,11 +1,9 @@
// SPDX-License-Identifier: Unlicense OR MIT // SPDX-License-Identifier: Unlicense OR MIT
package app package app
import ( import (
"unicode" "unicode"
"unicode/utf16" "unicode/utf16"
"unicode/utf8"
"gioui.org/io/input" "gioui.org/io/input"
"gioui.org/io/key" "gioui.org/io/key"
@@ -118,28 +116,3 @@ func (e *editorState) RunesIndex(chars int) int {
// Assume runes after snippets are one UTF-16 character each. // Assume runes after snippets are one UTF-16 character each.
return runes + chars 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
}
-3
View File
@@ -6,7 +6,6 @@
package app package app
import ( import (
"gioui.org/f32"
"testing" "testing"
"unicode/utf8" "unicode/utf8"
@@ -41,7 +40,6 @@ func FuzzIME(f *testing.F) {
r.Frame(gtx.Ops) r.Frame(gtx.Ops)
var state editorState var state editorState
state.Selection.Transform = f32.AffineId()
const ( const (
cmdReplace = iota cmdReplace = iota
cmdSelect cmdSelect
@@ -141,7 +139,6 @@ func FuzzIME(f *testing.F) {
func TestEditorIndices(t *testing.T) { func TestEditorIndices(t *testing.T) {
var s editorState var s editorState
s.Selection.Transform = f32.AffineId()
const str = "Hello, 😀" const str = "Hello, 😀"
s.Snippet = key.Snippet{ s.Snippet = key.Snippet{
Text: str, Text: str,
+38 -141
View File
@@ -108,74 +108,6 @@ type MonitorInfo struct {
Flags uint32 Flags uint32
} }
type POINTER_INPUT_TYPE int32
const (
PT_POINTER POINTER_INPUT_TYPE = 1
PT_TOUCH POINTER_INPUT_TYPE = 2
PT_PEN POINTER_INPUT_TYPE = 3
PT_MOUSE POINTER_INPUT_TYPE = 4
PT_TOUCHPAD POINTER_INPUT_TYPE = 5
)
type POINTER_INFO_POINTER_FLAGS int32
const (
POINTER_FLAG_NEW POINTER_INFO_POINTER_FLAGS = 0x00000001
POINTER_FLAG_INRANGE POINTER_INFO_POINTER_FLAGS = 0x00000002
POINTER_FLAG_INCONTACT POINTER_INFO_POINTER_FLAGS = 0x00000004
POINTER_FLAG_FIRSTBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000010
POINTER_FLAG_SECONDBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000020
POINTER_FLAG_THIRDBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000040
POINTER_FLAG_FOURTHBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000080
POINTER_FLAG_FIFTHBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000100
POINTER_FLAG_PRIMARY POINTER_INFO_POINTER_FLAGS = 0x00002000
POINTER_FLAG_CONFIDENCE POINTER_INFO_POINTER_FLAGS = 0x00004000
POINTER_FLAG_CANCELED POINTER_INFO_POINTER_FLAGS = 0x00008000
POINTER_FLAG_DOWN POINTER_INFO_POINTER_FLAGS = 0x00010000
POINTER_FLAG_UPDATE POINTER_INFO_POINTER_FLAGS = 0x00020000
POINTER_FLAG_UP POINTER_INFO_POINTER_FLAGS = 0x00040000
POINTER_FLAG_WHEEL POINTER_INFO_POINTER_FLAGS = 0x00080000
POINTER_FLAG_HWHEEL POINTER_INFO_POINTER_FLAGS = 0x00100000
POINTER_FLAG_CAPTURECHANGED POINTER_INFO_POINTER_FLAGS = 0x00200000
POINTER_FLAG_HASTRANSFORM POINTER_INFO_POINTER_FLAGS = 0x00400000
)
type POINTER_BUTTON_CHANGE_TYPE int32
const (
POINTER_CHANGE_NONE POINTER_BUTTON_CHANGE_TYPE = 0
POINTER_CHANGE_FIRSTBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 1
POINTER_CHANGE_FIRSTBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 2
POINTER_CHANGE_SECONDBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 3
POINTER_CHANGE_SECONDBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 4
POINTER_CHANGE_THIRDBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 5
POINTER_CHANGE_THIRDBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 6
POINTER_CHANGE_FOURTHBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 7
POINTER_CHANGE_FOURTHBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 8
POINTER_CHANGE_FIFTHBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 9
POINTER_CHANGE_FIFTHBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 10
)
type PointerInfo struct {
PointerType POINTER_INPUT_TYPE
PointerId uint32
FrameId uint32
PointerFlags POINTER_INFO_POINTER_FLAGS
SourceDevice syscall.Handle
HwndTarget syscall.Handle
PtPixelLocation Point
PtHimetricLocation Point
PtPixelLocationRaw Point
PtHimetricLocationRaw Point
DwTime uint32
HistoryCount uint32
InputData int32
DwKeyStates uint32
PerformanceCount uint64
ButtonChangeType POINTER_BUTTON_CHANGE_TYPE
}
const ( const (
TRUE = 1 TRUE = 1
@@ -312,51 +244,44 @@ const (
UNICODE_NOCHAR = 65535 UNICODE_NOCHAR = 65535
WM_CANCELMODE = 0x001F WM_CANCELMODE = 0x001F
WM_CHAR = 0x0102 WM_CHAR = 0x0102
WM_CLOSE = 0x0010 WM_CLOSE = 0x0010
WM_CREATE = 0x0001 WM_CREATE = 0x0001
WM_DPICHANGED = 0x02E0 WM_DPICHANGED = 0x02E0
WM_DESTROY = 0x0002 WM_DESTROY = 0x0002
WM_ERASEBKGND = 0x0014 WM_ERASEBKGND = 0x0014
WM_GETMINMAXINFO = 0x0024 WM_GETMINMAXINFO = 0x0024
WM_IME_COMPOSITION = 0x010F WM_IME_COMPOSITION = 0x010F
WM_IME_ENDCOMPOSITION = 0x010E WM_IME_ENDCOMPOSITION = 0x010E
WM_IME_STARTCOMPOSITION = 0x010D WM_IME_STARTCOMPOSITION = 0x010D
WM_KEYDOWN = 0x0100 WM_KEYDOWN = 0x0100
WM_KEYUP = 0x0101 WM_KEYUP = 0x0101
WM_KILLFOCUS = 0x0008 WM_KILLFOCUS = 0x0008
WM_LBUTTONDOWN = 0x0201 WM_LBUTTONDOWN = 0x0201
WM_LBUTTONUP = 0x0202 WM_LBUTTONUP = 0x0202
WM_MBUTTONDOWN = 0x0207 WM_MBUTTONDOWN = 0x0207
WM_MBUTTONUP = 0x0208 WM_MBUTTONUP = 0x0208
WM_MOUSEMOVE = 0x0200 WM_MOUSEMOVE = 0x0200
WM_MOUSEWHEEL = 0x020A WM_MOUSEWHEEL = 0x020A
WM_MOUSEHWHEEL = 0x020E WM_MOUSEHWHEEL = 0x020E
WM_NCACTIVATE = 0x0086 WM_NCACTIVATE = 0x0086
WM_NCHITTEST = 0x0084 WM_NCHITTEST = 0x0084
WM_NCCALCSIZE = 0x0083 WM_NCCALCSIZE = 0x0083
WM_PAINT = 0x000F WM_PAINT = 0x000F
WM_POINTERCAPTURECHANGED = 0x024C WM_QUIT = 0x0012
WM_POINTERDOWN = 0x0246 WM_SETCURSOR = 0x0020
WM_POINTERUP = 0x0247 WM_SETFOCUS = 0x0007
WM_POINTERUPDATE = 0x0245 WM_SHOWWINDOW = 0x0018
WM_POINTERWHEEL = 0x024E WM_SIZE = 0x0005
WM_POINTERHWHEEL = 0x024F WM_SYSKEYDOWN = 0x0104
WM_QUIT = 0x0012 WM_SYSKEYUP = 0x0105
WM_RBUTTONDOWN = 0x0204 WM_RBUTTONDOWN = 0x0204
WM_RBUTTONUP = 0x0205 WM_RBUTTONUP = 0x0205
WM_SETCURSOR = 0x0020 WM_TIMER = 0x0113
WM_SETFOCUS = 0x0007 WM_UNICHAR = 0x0109
WM_SHOWWINDOW = 0x0018 WM_USER = 0x0400
WM_SIZE = 0x0005 WM_WINDOWPOSCHANGED = 0x0047
WM_STYLECHANGED = 0x007D
WM_SYSKEYDOWN = 0x0104
WM_SYSKEYUP = 0x0105
WM_TIMER = 0x0113
WM_UNICHAR = 0x0109
WM_USER = 0x0400
WM_WINDOWPOSCHANGED = 0x0047
WS_CLIPCHILDREN = 0x02000000 WS_CLIPCHILDREN = 0x02000000
WS_CLIPSIBLINGS = 0x04000000 WS_CLIPSIBLINGS = 0x04000000
@@ -420,7 +345,6 @@ var (
_DestroyWindow = user32.NewProc("DestroyWindow") _DestroyWindow = user32.NewProc("DestroyWindow")
_DispatchMessage = user32.NewProc("DispatchMessageW") _DispatchMessage = user32.NewProc("DispatchMessageW")
_EmptyClipboard = user32.NewProc("EmptyClipboard") _EmptyClipboard = user32.NewProc("EmptyClipboard")
_EnableMouseInPointer = user32.NewProc("EnableMouseInPointer")
_GetWindowRect = user32.NewProc("GetWindowRect") _GetWindowRect = user32.NewProc("GetWindowRect")
_GetClientRect = user32.NewProc("GetClientRect") _GetClientRect = user32.NewProc("GetClientRect")
_GetClipboardData = user32.NewProc("GetClipboardData") _GetClipboardData = user32.NewProc("GetClipboardData")
@@ -430,7 +354,6 @@ var (
_GetMessage = user32.NewProc("GetMessageW") _GetMessage = user32.NewProc("GetMessageW")
_GetMessageTime = user32.NewProc("GetMessageTime") _GetMessageTime = user32.NewProc("GetMessageTime")
_GetMonitorInfo = user32.NewProc("GetMonitorInfoW") _GetMonitorInfo = user32.NewProc("GetMonitorInfoW")
_GetPointerInfo = user32.NewProc("GetPointerInfo")
_GetSystemMetrics = user32.NewProc("GetSystemMetrics") _GetSystemMetrics = user32.NewProc("GetSystemMetrics")
_GetWindowLong = user32.NewProc("GetWindowLongPtrW") _GetWindowLong = user32.NewProc("GetWindowLongPtrW")
_GetWindowLong32 = user32.NewProc("GetWindowLongW") _GetWindowLong32 = user32.NewProc("GetWindowLongW")
@@ -448,7 +371,6 @@ var (
_PostQuitMessage = user32.NewProc("PostQuitMessage") _PostQuitMessage = user32.NewProc("PostQuitMessage")
_ReleaseCapture = user32.NewProc("ReleaseCapture") _ReleaseCapture = user32.NewProc("ReleaseCapture")
_RegisterClassExW = user32.NewProc("RegisterClassExW") _RegisterClassExW = user32.NewProc("RegisterClassExW")
_RegisterTouchWindow = user32.NewProc("RegisterTouchWindow")
_ReleaseDC = user32.NewProc("ReleaseDC") _ReleaseDC = user32.NewProc("ReleaseDC")
_ScreenToClient = user32.NewProc("ScreenToClient") _ScreenToClient = user32.NewProc("ScreenToClient")
_ShowWindow = user32.NewProc("ShowWindow") _ShowWindow = user32.NewProc("ShowWindow")
@@ -522,31 +444,6 @@ func CreateWindowEx(dwExStyle uint32, lpClassName uint16, lpWindowName string, d
return syscall.Handle(hwnd), nil return syscall.Handle(hwnd), nil
} }
func GetPointerInfo(pointerId uint32) (PointerInfo, error) {
var info PointerInfo
r1, _, err := _GetPointerInfo.Call(uintptr(pointerId), uintptr(unsafe.Pointer(&info)))
if r1 == 0 {
return PointerInfo{}, fmt.Errorf("GetPointerInfo failed: %v", err)
}
return info, nil
}
func RegisterTouchWindow(hwnd syscall.Handle, flags uint32) error {
r1, _, err := _RegisterTouchWindow.Call(uintptr(hwnd), uintptr(flags))
if r1 == 0 {
return fmt.Errorf("RegisterTouchWindow failed: %v", err)
}
return nil
}
func EnableMouseInPointer(enable uint) error {
r1, _, err := _EnableMouseInPointer.Call(uintptr(enable))
if r1 == 0 {
return fmt.Errorf("EnableMouseInPointer failed: %v", err)
}
return nil
}
func DefWindowProc(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr { func DefWindowProc(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
r, _, _ := _DefWindowProc.Call(uintptr(hwnd), uintptr(msg), wparam, lparam) r, _, _ := _DefWindowProc.Call(uintptr(hwnd), uintptr(msg), wparam, lparam)
return r return r
+2 -3
View File
@@ -4,15 +4,14 @@ 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.NewLazySystemDLL("kernel32") kernel32 = syscall.NewLazyDLL("kernel32")
outputDebugStringW = kernel32.NewProc("OutputDebugStringW") outputDebugStringW = kernel32.NewProc("OutputDebugStringW")
debugView *logger debugView *logger
) )
+1 -1
View File
@@ -96,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 queue == 0 { if layer == 0 {
C.CFRelease(dev) C.CFRelease(dev)
C.CFRelease(layer) C.CFRelease(layer)
return nil, errors.New("metal: [MTLDevice newCommandQueue] failed") return nil, errors.New("metal: [MTLDevice newCommandQueue] failed")
+2 -2
View File
@@ -12,12 +12,12 @@ package app
#import <QuartzCore/CAMetalLayer.h> #import <QuartzCore/CAMetalLayer.h>
#include <CoreFoundation/CoreFoundation.h> #include <CoreFoundation/CoreFoundation.h>
CALayer *gio_layerFactory(BOOL presentWithTrans) { CALayer *gio_layerFactory(void) {
@autoreleasepool { @autoreleasepool {
CAMetalLayer *l = [CAMetalLayer layer]; CAMetalLayer *l = [CAMetalLayer layer];
l.autoresizingMask = kCALayerHeightSizable|kCALayerWidthSizable; l.autoresizingMask = kCALayerHeightSizable|kCALayerWidthSizable;
l.needsDisplayOnBoundsChange = YES; l.needsDisplayOnBoundsChange = YES;
l.presentsWithTransaction = presentWithTrans; l.presentsWithTransaction = YES;
return l; return l;
} }
} }
+12 -4
View File
@@ -173,13 +173,19 @@ type context interface {
Unlock() Unlock()
} }
// driver is the interface for the platform implementation // basicDriver is the subset of [driver] that may be called even after
// of a window. // a window is destroyed.
type driver interface { type basicDriver interface {
// Event blocks until an event is available and returns it. // Event blocks until an even is available and returns it.
Event() event.Event Event() event.Event
// Invalidate requests a FrameEvent. // Invalidate requests a FrameEvent.
Invalidate() Invalidate()
}
// driver is the interface for the platform implementation
// of a window.
type driver interface {
basicDriver
// 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)
@@ -195,6 +201,8 @@ type driver interface {
Configure([]Option) Configure([]Option)
// SetCursor updates the current cursor to name. // SetCursor updates the current cursor to name.
SetCursor(cursor pointer.Cursor) SetCursor(cursor pointer.Cursor)
// Wakeup wakes up the event loop and sends a WakeupEvent.
// Wakeup()
// Perform actions on the window. // Perform actions on the window.
Perform(system.Action) Perform(system.Action)
// EditorStateChanged notifies the driver that the editor state changed. // EditorStateChanged notifies the driver that the editor state changed.
+4 -10
View File
@@ -575,7 +575,10 @@ func Java_org_gioui_GioView_onFrameCallback(env *C.JNIEnv, class C.jclass, view
if !exist { if !exist {
return return
} }
w.draw(env, false) if w.visible && w.animating {
w.draw(env, false)
callVoidMethod(env, w.view, gioView.postFrameCallback)
}
} }
//export Java_org_gioui_GioView_onBack //export Java_org_gioui_GioView_onBack
@@ -879,9 +882,6 @@ func (w *window) draw(env *C.JNIEnv, sync bool) {
}, },
Sync: sync, Sync: sync,
}) })
if w.animating {
callVoidMethod(env, w.view, gioView.postFrameCallback)
}
a11yActive, err := callBooleanMethod(env, w.view, gioView.isA11yActive) a11yActive, err := callBooleanMethod(env, w.view, gioView.isA11yActive)
if err != nil { if err != nil {
panic(err) panic(err)
@@ -1317,9 +1317,6 @@ func findClass(env *C.JNIEnv, name string) C.jclass {
return C.jni_FindClass(env, cn) return C.jni_FindClass(env, cn)
} }
func osMain() {
}
func newWindow(window *callbacks, options []Option) { func newWindow(window *callbacks, options []Option) {
mainWindow.in <- windowAndConfig{window, options} mainWindow.in <- windowAndConfig{window, options}
<-mainWindow.windows <-mainWindow.windows
@@ -1495,6 +1492,3 @@ func Java_org_gioui_Gio_scheduleMainFuncs(env *C.JNIEnv, cls C.jclass) {
func (AndroidViewEvent) implementsViewEvent() {} func (AndroidViewEvent) implementsViewEvent() {}
func (AndroidViewEvent) ImplementsEvent() {} func (AndroidViewEvent) ImplementsEvent() {}
func (a AndroidViewEvent) Valid() bool {
return a != (AndroidViewEvent{})
}
+22 -22
View File
@@ -5,7 +5,7 @@ package app
/* /*
#include <Foundation/Foundation.h> #include <Foundation/Foundation.h>
__attribute__ ((visibility ("hidden"))) void gio_runOnMain(uintptr_t h); __attribute__ ((visibility ("hidden"))) void gio_wakeupMainThread(void);
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createDisplayLink(void); __attribute__ ((visibility ("hidden"))) CFTypeRef gio_createDisplayLink(void);
__attribute__ ((visibility ("hidden"))) void gio_releaseDisplayLink(CFTypeRef dl); __attribute__ ((visibility ("hidden"))) void gio_releaseDisplayLink(CFTypeRef dl);
__attribute__ ((visibility ("hidden"))) int gio_startDisplayLink(CFTypeRef dl); __attribute__ ((visibility ("hidden"))) int gio_startDisplayLink(CFTypeRef dl);
@@ -15,8 +15,8 @@ __attribute__ ((visibility ("hidden"))) void gio_hideCursor();
__attribute__ ((visibility ("hidden"))) void gio_showCursor(); __attribute__ ((visibility ("hidden"))) void gio_showCursor();
__attribute__ ((visibility ("hidden"))) void gio_setCursor(NSUInteger curID); __attribute__ ((visibility ("hidden"))) void gio_setCursor(NSUInteger curID);
static bool isMainThread() { static int isMainThread() {
return [NSThread isMainThread]; return [NSThread isMainThread] ? 1 : 0;
} }
static NSUInteger nsstringLength(CFTypeRef cstr) { static NSUInteger nsstringLength(CFTypeRef cstr) {
@@ -40,10 +40,8 @@ static CFTypeRef newNSString(unichar *chars, NSUInteger length) {
} }
*/ */
import "C" import "C"
import ( import (
"errors" "errors"
"runtime/cgo"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@@ -75,9 +73,7 @@ type displayLink struct {
// displayLinks maps CFTypeRefs to *displayLinks. // displayLinks maps CFTypeRefs to *displayLinks.
var displayLinks sync.Map var displayLinks sync.Map
func isMainThread() bool { var mainFuncs = make(chan func(), 1)
return bool(C.isMainThread())
}
// runOnMain runs the function on the main thread. // runOnMain runs the function on the main thread.
func runOnMain(f func()) { func runOnMain(f func()) {
@@ -85,15 +81,26 @@ func runOnMain(f func()) {
f() f()
return return
} }
C.gio_runOnMain(C.uintptr_t(cgo.NewHandle(f))) go func() {
mainFuncs <- f
C.gio_wakeupMainThread()
}()
} }
//export gio_runFunc func isMainThread() bool {
func gio_runFunc(h C.uintptr_t) { return C.isMainThread() != 0
handle := cgo.Handle(h) }
defer handle.Delete()
f := handle.Value().(func()) //export gio_dispatchMainFuncs
f() func gio_dispatchMainFuncs() {
for {
select {
case f := <-mainFuncs:
f()
default:
return
}
}
} }
// nsstringToString converts a NSString to a Go string. // nsstringToString converts a NSString to a Go string.
@@ -256,10 +263,3 @@ func windowSetCursor(from, to pointer.Cursor) pointer.Cursor {
C.gio_setCursor(C.NSUInteger(macosCursorID[to])) C.gio_setCursor(C.NSUInteger(macosCursorID[to]))
return to return to
} }
func (w *window) wakeup() {
runOnMain(func() {
w.loop.Wakeup()
w.loop.FlushEvents()
})
}
-11
View File
@@ -1,11 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
#import <Foundation/Foundation.h>
#include "_cgo_export.h"
void gio_runOnMain(uintptr_t h) {
dispatch_async(dispatch_get_main_queue(), ^{
gio_runFunc(h);
});
}
+8 -50
View File
@@ -12,7 +12,6 @@ package app
#include <UIKit/UIKit.h> #include <UIKit/UIKit.h>
#include <stdint.h> #include <stdint.h>
__attribute__ ((visibility ("hidden"))) int gio_applicationMain(int argc, char *argv[]);
__attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle); __attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle);
struct drawParams { struct drawParams {
@@ -22,7 +21,6 @@ struct drawParams {
}; };
static void writeClipboard(unichar *chars, NSUInteger length) { static void writeClipboard(unichar *chars, NSUInteger length) {
#if !TARGET_OS_TV
@autoreleasepool { @autoreleasepool {
NSString *s = [NSString string]; NSString *s = [NSString string];
if (length > 0) { if (length > 0) {
@@ -31,18 +29,13 @@ static void writeClipboard(unichar *chars, NSUInteger length) {
UIPasteboard *p = UIPasteboard.generalPasteboard; UIPasteboard *p = UIPasteboard.generalPasteboard;
p.string = s; p.string = s;
} }
#endif
} }
static CFTypeRef readClipboard(void) { static CFTypeRef readClipboard(void) {
#if !TARGET_OS_TV
@autoreleasepool { @autoreleasepool {
UIPasteboard *p = UIPasteboard.generalPasteboard; UIPasteboard *p = UIPasteboard.generalPasteboard;
return (__bridge_retained CFTypeRef)p.string; return (__bridge_retained CFTypeRef)p.string;
} }
#else
return nil;
#endif
} }
static void showTextInput(CFTypeRef viewRef) { static void showTextInput(CFTypeRef viewRef) {
@@ -82,7 +75,6 @@ import "C"
import ( import (
"image" "image"
"io" "io"
"os"
"runtime" "runtime"
"runtime/cgo" "runtime/cgo"
"runtime/debug" "runtime/debug"
@@ -396,51 +388,17 @@ func newWindow(win *callbacks, options []Option) {
<-mainWindow.windows <-mainWindow.windows
} }
var mainMode = mainModeUndefined
const (
mainModeUndefined = iota
mainModeExe
mainModeLibrary
)
func osMain() {
if !isMainThread() {
panic("app.Main must be run on the main goroutine")
}
switch mainMode {
case mainModeUndefined:
mainMode = mainModeExe
var argv []*C.char
for _, arg := range os.Args {
a := C.CString(arg)
defer C.free(unsafe.Pointer(a))
argv = append(argv, a)
}
C.gio_applicationMain(C.int(len(argv)), unsafe.SliceData(argv))
case mainModeExe:
panic("app.Main may be called only once")
case mainModeLibrary:
// Do nothing, we're embedded as a library.
}
}
//export gio_runMain //export gio_runMain
func gio_runMain() { func gio_runMain() {
if !isMainThread() { runMain()
panic("app.Main must be run on the main goroutine") }
}
switch mainMode { func (w *window) wakeup() {
case mainModeUndefined: runOnMain(func() {
mainMode = mainModeLibrary w.loop.Wakeup()
runMain() w.loop.FlushEvents()
case mainModeExe: })
// Do nothing, main has already been called.
}
} }
func (UIKitViewEvent) implementsViewEvent() {} func (UIKitViewEvent) implementsViewEvent() {}
func (UIKitViewEvent) ImplementsEvent() {} func (UIKitViewEvent) ImplementsEvent() {}
func (u UIKitViewEvent) Valid() bool {
return u != (UIKitViewEvent{})
}
+5 -23
View File
@@ -26,13 +26,12 @@ CGFloat _keyboardHeight;
self.view.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0); self.view.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
UIView *drawView = [[GioView alloc] initWithFrame:zeroFrame]; UIView *drawView = [[GioView alloc] initWithFrame:zeroFrame];
[self.view addSubview: drawView]; [self.view addSubview: drawView];
#if !TARGET_OS_TV #ifndef TARGET_OS_TV
drawView.multipleTouchEnabled = YES; drawView.multipleTouchEnabled = YES;
#endif #endif
drawView.preservesSuperviewLayoutMargins = YES; drawView.preservesSuperviewLayoutMargins = YES;
drawView.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0); drawView.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
onCreate((__bridge CFTypeRef)drawView, (__bridge CFTypeRef)self); onCreate((__bridge CFTypeRef)drawView, (__bridge CFTypeRef)self);
#if !TARGET_OS_TV
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillChange:) selector:@selector(keyboardWillChange:)
name:UIKeyboardWillShowNotification name:UIKeyboardWillShowNotification
@@ -45,7 +44,6 @@ CGFloat _keyboardHeight;
selector:@selector(keyboardWillHide:) selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification name:UIKeyboardWillHideNotification
object:nil]; object:nil];
#endif
[[NSNotificationCenter defaultCenter] addObserver: self [[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector(applicationDidEnterBackground:) selector: @selector(applicationDidEnterBackground:)
name: UIApplicationDidEnterBackgroundNotification name: UIApplicationDidEnterBackgroundNotification
@@ -91,7 +89,6 @@ CGFloat _keyboardHeight;
[super didReceiveMemoryWarning]; [super didReceiveMemoryWarning];
} }
#if !TARGET_OS_TV
- (void)keyboardWillChange:(NSNotification *)note { - (void)keyboardWillChange:(NSNotification *)note {
NSDictionary *userInfo = note.userInfo; NSDictionary *userInfo = note.userInfo;
CGRect f = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; CGRect f = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
@@ -103,7 +100,6 @@ CGFloat _keyboardHeight;
_keyboardHeight = 0.0; _keyboardHeight = 0.0;
[self.view setNeedsLayout]; [self.view setNeedsLayout];
} }
#endif
@end @end
static void handleTouches(int last, GioView *view, NSSet<UITouch *> *touches, UIEvent *event) { static void handleTouches(int last, GioView *view, NSSet<UITouch *> *touches, UIEvent *event) {
@@ -281,22 +277,8 @@ void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
v.handle = handle; v.handle = handle;
} }
@interface _gioAppDelegate : UIResponder <UIApplicationDelegate> void gio_wakeupMainThread(void) {
@property (strong, nonatomic) UIWindow *window; dispatch_async(dispatch_get_main_queue(), ^{
@end gio_dispatchMainFuncs();
});
@implementation _gioAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
GioViewController *controller = [[GioViewController alloc] initWithNibName:nil bundle:nil];
self.window.rootViewController = controller;
[self.window makeKeyAndVisible];
return YES;
}
@end
int gio_applicationMain(int argc, char *argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([_gioAppDelegate class]));
}
} }
-7
View File
@@ -741,10 +741,6 @@ func (w *window) navigationColor(c color.NRGBA) {
theme.Set("content", fmt.Sprintf("#%06X", []uint8{rgba.R, rgba.G, rgba.B})) theme.Set("content", fmt.Sprintf("#%06X", []uint8{rgba.R, rgba.G, rgba.B}))
} }
func osMain() {
select {}
}
func translateKey(k string) (key.Name, bool) { func translateKey(k string) (key.Name, bool) {
var n key.Name var n key.Name
@@ -822,6 +818,3 @@ func translateKey(k string) (key.Name, bool) {
func (JSViewEvent) implementsViewEvent() {} func (JSViewEvent) implementsViewEvent() {}
func (JSViewEvent) ImplementsEvent() {} func (JSViewEvent) ImplementsEvent() {}
func (j JSViewEvent) Valid() bool {
return !(j.Element.IsNull() || j.Element.IsUndefined())
}
+211 -274
View File
@@ -39,10 +39,9 @@ import (
#define MOUSE_DOWN 3 #define MOUSE_DOWN 3
#define MOUSE_SCROLL 4 #define MOUSE_SCROLL 4
__attribute__ ((visibility ("hidden"))) void gio_main(void); __attribute__ ((visibility ("hidden"))) void gio_initApp(void);
__attribute__ ((visibility ("hidden"))) void gio_init(void); __attribute__ ((visibility ("hidden"))) CFTypeRef gio_createView(void);
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createView(int presentWithTrans); __attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight);
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height);
__attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle); __attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle);
static void writeClipboard(CFTypeRef str) { static void writeClipboard(CFTypeRef str) {
@@ -110,14 +109,6 @@ static void makeKeyAndOrderFront(CFTypeRef windowRef) {
} }
} }
static void makeFirstResponder(CFTypeRef windowRef, CFTypeRef viewRef) {
@autoreleasepool {
NSWindow *window = (__bridge NSWindow *)windowRef;
NSView *view = (__bridge NSView *)viewRef;
[window makeFirstResponder:view];
}
}
static void toggleFullScreen(CFTypeRef windowRef) { static void toggleFullScreen(CFTypeRef windowRef) {
@autoreleasepool { @autoreleasepool {
NSWindow *window = (__bridge NSWindow *)windowRef; NSWindow *window = (__bridge NSWindow *)windowRef;
@@ -193,7 +184,6 @@ static void setMaxSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
@autoreleasepool { @autoreleasepool {
NSWindow* window = (__bridge NSWindow *)windowRef; NSWindow* window = (__bridge NSWindow *)windowRef;
window.contentMaxSize = NSMakeSize(width, height); window.contentMaxSize = NSMakeSize(width, height);
window.maxFullScreenContentSize = NSMakeSize(width, height);
} }
} }
@@ -205,14 +195,6 @@ static void setScreenFrame(CFTypeRef windowRef, CGFloat x, CGFloat y, CGFloat w,
} }
} }
static void resetLayerFrame(CFTypeRef viewRef) {
@autoreleasepool {
NSView* view = (__bridge NSView *)viewRef;
NSRect r = view.frame;
view.layer.frame = r;
}
}
static void hideWindow(CFTypeRef windowRef) { static void hideWindow(CFTypeRef windowRef) {
@autoreleasepool { @autoreleasepool {
NSWindow* window = (__bridge NSWindow *)windowRef; NSWindow* window = (__bridge NSWindow *)windowRef;
@@ -248,13 +230,6 @@ static int isWindowZoomed(CFTypeRef windowRef) {
} }
} }
static int isWindowMiniaturized(CFTypeRef windowRef) {
@autoreleasepool {
NSWindow *window = (__bridge NSWindow *)windowRef;
return window.miniaturized ? 1 : 0;
}
}
static void zoomWindow(CFTypeRef windowRef) { static void zoomWindow(CFTypeRef windowRef) {
@autoreleasepool { @autoreleasepool {
NSWindow *window = (__bridge NSWindow *)windowRef; NSWindow *window = (__bridge NSWindow *)windowRef;
@@ -315,18 +290,13 @@ static void invalidateCharacterCoordinates(CFTypeRef viewRef) {
} }
} }
static void interpretKeyEvents(CFTypeRef viewRef, CFTypeRef eventRef) { static void dispatchEvent(void) {
@autoreleasepool { @autoreleasepool {
NSView *view = (__bridge NSView *)viewRef; NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny
NSEvent *event = (__bridge NSEvent *)eventRef; untilDate:[NSDate distantFuture]
[view interpretKeyEvents:[NSArray arrayWithObject:event]]; inMode:NSDefaultRunLoopMode
} dequeue:YES];
} [NSApp sendEvent:event];
static int isMiniaturized(CFTypeRef windowRef) {
@autoreleasepool {
NSWindow *window = (__bridge NSWindow *)windowRef;
return window.miniaturized ? 1 : 0;
} }
} }
*/ */
@@ -335,8 +305,6 @@ import "C"
func init() { func init() {
// Darwin requires that UI operations happen on the main thread only. // Darwin requires that UI operations happen on the main thread only.
runtime.LockOSThread() runtime.LockOSThread()
// Register launch finished listener.
C.gio_init()
} }
// AppKitViewEvent notifies the client of changes to the window AppKit handles. // AppKitViewEvent notifies the client of changes to the window AppKit handles.
@@ -352,6 +320,7 @@ type window struct {
view C.CFTypeRef view C.CFTypeRef
w *callbacks w *callbacks
anim bool anim bool
visible bool
displayLink *displayLink displayLink *displayLink
// redraw is a single entry channel for making sure only one // redraw is a single entry channel for making sure only one
// display link redraw request is in flight. // display link redraw request is in flight.
@@ -359,29 +328,21 @@ type window struct {
cursor pointer.Cursor cursor pointer.Cursor
pointerBtns pointer.Buttons pointerBtns pointer.Buttons
loop *eventLoop loop *eventLoop
lastMods C.NSUInteger
scale float32 scale float32
config Config config Config
keysDown map[key.Name]struct{}
// cmdKeys is for storing the current key event while
// waiting for a doCommandBySelector.
cmdKeys cmdKeys
} }
type cmdKeys struct { // launched is closed after gio_initApp returns.
eventStr string
eventMods key.Modifiers
}
// launched is closed when applicationDidFinishLaunching is called.
var launched = make(chan struct{}) var launched = make(chan struct{})
// nextTopLeft is the offset to use for the next window's call to // nextTopLeft is the offset to use for the next window's call to
// cascadeTopLeftFromPoint. // cascadeTopLeftFromPoint.
var nextTopLeft C.NSPoint var nextTopLeft C.NSPoint
// mainThreadWindow is the window currently in control of the main thread.
var mainThreadWindow *window
func windowFor(h C.uintptr_t) *window { func windowFor(h C.uintptr_t) *window {
return cgo.Handle(h).Value().(*window) return cgo.Handle(h).Value().(*window)
} }
@@ -411,23 +372,11 @@ func (w *window) WriteClipboard(mime string, s []byte) {
} }
func (w *window) updateWindowMode() { func (w *window) updateWindowMode() {
w.scale = float32(C.getViewBackingScale(w.view))
wf, hf := float32(C.viewWidth(w.view)), float32(C.viewHeight(w.view))
w.config.Size = image.Point{
X: int(wf*w.scale + .5),
Y: int(hf*w.scale + .5),
}
w.config.Mode = Windowed
window := C.windowForView(w.view)
if window == 0 {
return
}
style := int(C.getWindowStyleMask(C.windowForView(w.view))) style := int(C.getWindowStyleMask(C.windowForView(w.view)))
switch { if style&C.NSWindowStyleMaskFullScreen != 0 {
case style&C.NSWindowStyleMaskFullScreen != 0:
w.config.Mode = Fullscreen w.config.Mode = Fullscreen
case C.isWindowZoomed(window) != 0: } else {
w.config.Mode = Maximized w.config.Mode = Windowed
} }
w.config.Decorated = style&C.NSWindowStyleMaskFullSizeContentView == 0 w.config.Decorated = style&C.NSWindowStyleMaskFullSizeContentView == 0
} }
@@ -435,82 +384,102 @@ func (w *window) updateWindowMode() {
func (w *window) Configure(options []Option) { func (w *window) Configure(options []Option) {
screenScale := float32(C.getScreenBackingScale()) screenScale := float32(C.getScreenBackingScale())
cfg := configFor(screenScale) cfg := configFor(screenScale)
prev := w.config
w.updateWindowMode()
cnf := w.config cnf := w.config
cnf.apply(cfg, options) cnf.apply(cfg, options)
window := C.windowForView(w.view) window := C.windowForView(w.view)
mask := C.getWindowStyleMask(window)
fullscreen := mask&C.NSWindowStyleMaskFullScreen != 0
switch cnf.Mode { switch cnf.Mode {
case Fullscreen: case Fullscreen:
if C.isWindowMiniaturized(window) != 0 { switch prev.Mode {
case Fullscreen:
case Minimized:
C.unhideWindow(window) C.unhideWindow(window)
} fallthrough
if !fullscreen { default:
w.config.Mode = Fullscreen
C.toggleFullScreen(window) C.toggleFullScreen(window)
} }
case Minimized: case Minimized:
C.hideWindow(window) switch prev.Mode {
case Minimized, Fullscreen:
default:
w.config.Mode = Minimized
C.hideWindow(window)
}
case Maximized: case Maximized:
if C.isWindowMiniaturized(window) != 0 { switch prev.Mode {
case Fullscreen:
case Minimized:
C.unhideWindow(window) C.unhideWindow(window)
} fallthrough
if fullscreen { default:
C.toggleFullScreen(window) w.config.Mode = Maximized
} w.setTitle(prev, cnf)
w.setTitle(cnf.Title) if C.isWindowZoomed(window) == 0 {
if C.isWindowZoomed(window) == 0 { C.zoomWindow(window)
C.zoomWindow(window) }
} }
case Windowed: case Windowed:
if C.isWindowMiniaturized(window) != 0 { switch prev.Mode {
C.unhideWindow(window) case Fullscreen:
}
if fullscreen {
C.toggleFullScreen(window) C.toggleFullScreen(window)
case Minimized:
C.unhideWindow(window)
case Maximized:
if C.isWindowZoomed(window) != 0 {
C.zoomWindow(window)
}
} }
w.setTitle(cnf.Title) w.config.Mode = Windowed
w.config.Size = cnf.Size w.setTitle(prev, cnf)
cnf.Size = cnf.Size.Div(int(screenScale)) if prev.Size != cnf.Size {
C.setSize(window, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y)) w.config.Size = cnf.Size
w.config.MinSize = cnf.MinSize cnf.Size = cnf.Size.Div(int(screenScale))
cnf.MinSize = cnf.MinSize.Div(int(screenScale)) C.setSize(window, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y))
C.setMinSize(window, C.CGFloat(cnf.MinSize.X), C.CGFloat(cnf.MinSize.Y)) }
w.config.MaxSize = cnf.MaxSize if prev.MinSize != cnf.MinSize {
cnf.MaxSize = cnf.MaxSize.Div(int(screenScale)) w.config.MinSize = cnf.MinSize
if cnf.MaxSize != (image.Point{}) { cnf.MinSize = cnf.MinSize.Div(int(screenScale))
C.setMinSize(window, C.CGFloat(cnf.MinSize.X), C.CGFloat(cnf.MinSize.Y))
}
if prev.MaxSize != cnf.MaxSize {
w.config.MaxSize = cnf.MaxSize
cnf.MaxSize = cnf.MaxSize.Div(int(screenScale))
C.setMaxSize(window, C.CGFloat(cnf.MaxSize.X), C.CGFloat(cnf.MaxSize.Y)) C.setMaxSize(window, C.CGFloat(cnf.MaxSize.X), C.CGFloat(cnf.MaxSize.Y))
} }
if C.isWindowZoomed(window) != 0 { }
C.zoomWindow(window) if cnf.Decorated != prev.Decorated {
w.config.Decorated = cnf.Decorated
mask := C.getWindowStyleMask(window)
style := C.NSWindowStyleMask(C.NSWindowStyleMaskTitled | C.NSWindowStyleMaskResizable | C.NSWindowStyleMaskMiniaturizable | C.NSWindowStyleMaskClosable)
style = C.NSWindowStyleMaskFullSizeContentView
mask &^= style
barTrans := C.int(C.NO)
titleVis := C.NSWindowTitleVisibility(C.NSWindowTitleVisible)
if !cnf.Decorated {
mask |= style
barTrans = C.YES
titleVis = C.NSWindowTitleHidden
} }
C.setWindowTitlebarAppearsTransparent(window, barTrans)
C.setWindowTitleVisibility(window, titleVis)
C.setWindowStyleMask(window, mask)
C.setWindowStandardButtonHidden(window, C.NSWindowCloseButton, barTrans)
C.setWindowStandardButtonHidden(window, C.NSWindowMiniaturizeButton, barTrans)
C.setWindowStandardButtonHidden(window, C.NSWindowZoomButton, barTrans)
} }
style := C.NSWindowStyleMask(C.NSWindowStyleMaskTitled | C.NSWindowStyleMaskResizable | C.NSWindowStyleMaskMiniaturizable | C.NSWindowStyleMaskClosable) w.ProcessEvent(ConfigEvent{Config: w.config})
style = C.NSWindowStyleMaskFullSizeContentView
mask &^= style
barTrans := C.int(C.NO)
titleVis := C.NSWindowTitleVisibility(C.NSWindowTitleVisible)
if !cnf.Decorated {
mask |= style
barTrans = C.YES
titleVis = C.NSWindowTitleHidden
}
C.setWindowTitlebarAppearsTransparent(window, barTrans)
C.setWindowTitleVisibility(window, titleVis)
C.setWindowStyleMask(window, mask)
C.setWindowStandardButtonHidden(window, C.NSWindowCloseButton, barTrans)
C.setWindowStandardButtonHidden(window, C.NSWindowMiniaturizeButton, barTrans)
C.setWindowStandardButtonHidden(window, C.NSWindowZoomButton, barTrans)
// When toggling the titlebar, the layer doesn't update its frame
// until the next resize. Force it.
C.resetLayerFrame(w.view)
} }
func (w *window) setTitle(title string) { func (w *window) setTitle(prev, cnf Config) {
w.config.Title = title if prev.Title != cnf.Title {
titleC := stringToNSString(title) w.config.Title = cnf.Title
defer C.CFRelease(titleC) title := stringToNSString(cnf.Title)
C.setTitle(C.windowForView(w.view), titleC) defer C.CFRelease(title)
C.setTitle(C.windowForView(w.view), title)
}
} }
func (w *window) Perform(acts system.Action) { func (w *window) Perform(acts system.Action) {
@@ -538,7 +507,7 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
} }
func (w *window) EditorStateChanged(old, new editorState) { func (w *window) EditorStateChanged(old, new editorState) {
if old.Selection.Range != new.Selection.Range || !areSnippetsConsistent(old.Snippet, new.Snippet) { if old.Selection.Range != new.Selection.Range || old.Snippet != new.Snippet {
C.discardMarkedText(w.view) C.discardMarkedText(w.view)
w.w.SetComposingRegion(key.Range{Start: -1, End: -1}) w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
} }
@@ -553,8 +522,7 @@ func (w *window) SetInputHint(_ key.InputHint) {}
func (w *window) SetAnimating(anim bool) { func (w *window) SetAnimating(anim bool) {
w.anim = anim w.anim = anim
window := C.windowForView(w.view) if w.anim && w.visible {
if w.anim && window != 0 && C.isMiniaturized(window) == 0 {
w.displayLink.Start() w.displayLink.Start()
} else { } else {
w.displayLink.Stop() w.displayLink.Stop()
@@ -572,92 +540,23 @@ func (w *window) runOnMain(f func()) {
} }
//export gio_onKeys //export gio_onKeys
func gio_onKeys(h C.uintptr_t, event C.CFTypeRef, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger, keyDown C.bool) { func gio_onKeys(h C.uintptr_t, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger, keyDown C.bool) {
w := windowFor(h)
if w.keysDown == nil {
w.keysDown = make(map[key.Name]struct{})
}
str := nsstringToString(cstr) str := nsstringToString(cstr)
kmods := convertMods(mods) kmods := convertMods(mods)
ks := key.Release ks := key.Release
if keyDown { if keyDown {
ks = key.Press ks = key.Press
w.cmdKeys.eventStr = str
w.cmdKeys.eventMods = kmods
C.interpretKeyEvents(w.view, event)
} }
w := windowFor(h)
for _, k := range str { for _, k := range str {
if n, ok := convertKey(k); ok { if n, ok := convertKey(k); ok {
ke := key.Event{ w.ProcessEvent(key.Event{
Name: n, Name: n,
Modifiers: kmods, Modifiers: kmods,
State: ks, State: ks,
}
if keyDown {
w.keysDown[ke.Name] = struct{}{}
if _, isCmd := convertCommandKey(k); isCmd || kmods.Contain(key.ModCommand) {
// doCommandBySelector already processed the event.
return
}
} else {
if _, pressed := w.keysDown[n]; !pressed {
continue
}
delete(w.keysDown, n)
}
w.ProcessEvent(ke)
}
}
}
//export gio_onCommandBySelector
func gio_onCommandBySelector(h C.uintptr_t) C.bool {
w := windowFor(h)
ev := w.cmdKeys
w.cmdKeys = cmdKeys{}
handled := false
for _, k := range ev.eventStr {
n, ok := convertCommandKey(k)
if !ok && ev.eventMods.Contain(key.ModCommand) {
n, ok = convertKey(k)
}
if !ok {
continue
}
ke := key.Event{
Name: n,
Modifiers: ev.eventMods,
State: key.Press,
}
handled = w.processEvent(ke) || handled
}
return C.bool(handled)
}
//export gio_onFlagsChanged
func gio_onFlagsChanged(h C.uintptr_t, curMods C.NSUInteger) {
w := windowFor(h)
mods := []C.NSUInteger{C.NSControlKeyMask, C.NSAlternateKeyMask, C.NSShiftKeyMask, C.NSCommandKeyMask}
keys := []key.Name{key.NameCtrl, key.NameAlt, key.NameShift, key.NameCommand}
for i, mod := range mods {
wasPressed := w.lastMods&mod != 0
isPressed := curMods&mod != 0
if wasPressed != isPressed {
st := key.Release
if isPressed {
st = key.Press
}
w.ProcessEvent(key.Event{
Name: keys[i],
State: st,
}) })
} }
} }
w.lastMods = curMods
} }
//export gio_onText //export gio_onText
@@ -850,15 +749,6 @@ func gio_substringForProposedRange(h C.uintptr_t, crng C.NSRange, actual C.NSRan
//export gio_insertText //export gio_insertText
func gio_insertText(h C.uintptr_t, cstr C.CFTypeRef, crng C.NSRange) { func gio_insertText(h C.uintptr_t, cstr C.CFTypeRef, crng C.NSRange) {
w := windowFor(h) w := windowFor(h)
str := nsstringToString(cstr)
// macOS IME in some cases calls insertText for command keys such as backspace
// instead of doCommandBySelector.
for _, r := range str {
if _, ok := convertCommandKey(r); ok {
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
return
}
}
state := w.w.EditorState() state := w.w.EditorState()
rng := state.compose rng := state.compose
if rng.Start == -1 { if rng.Start == -1 {
@@ -870,6 +760,7 @@ func gio_insertText(h C.uintptr_t, cstr C.CFTypeRef, crng C.NSRange) {
End: state.RunesIndex(int(crng.location + crng.length)), End: state.RunesIndex(int(crng.location + crng.length)),
} }
} }
str := nsstringToString(cstr)
w.w.EditorReplace(rng, str) w.w.EditorReplace(rng, str)
w.w.SetComposingRegion(key.Range{Start: -1, End: -1}) w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
start := rng.Start start := rng.Start
@@ -896,7 +787,7 @@ func gio_firstRectForCharacterRange(h C.uintptr_t, crng C.NSRange, actual C.NSRa
// Transform to NSView local coordinates (lower left origin, undo backing scale). // Transform to NSView local coordinates (lower left origin, undo backing scale).
scale := 1. / float32(C.getViewBackingScale(w.view)) scale := 1. / float32(C.getViewBackingScale(w.view))
height := float32(C.viewHeight(w.view)) height := float32(C.viewHeight(w.view))
local := f32.AffineId().Scale(f32.Pt(0, 0), f32.Pt(scale, -scale)).Offset(f32.Pt(0, height)) local := f32.Affine2D{}.Scale(f32.Pt(0, 0), f32.Pt(scale, -scale)).Offset(f32.Pt(0, height))
t := local.Mul(sel.Transform) t := local.Mul(sel.Transform)
bounds := f32.Rectangle{ bounds := f32.Rectangle{
Min: t.Transform(sel.Pos.Sub(f32.Pt(0, sel.Ascent))), Min: t.Transform(sel.Pos.Sub(f32.Pt(0, sel.Ascent))),
@@ -910,19 +801,24 @@ func gio_firstRectForCharacterRange(h C.uintptr_t, crng C.NSRange, actual C.NSRa
} }
func (w *window) draw() { func (w *window) draw() {
cnf := w.config
w.updateWindowMode()
if w.config != cnf {
w.ProcessEvent(ConfigEvent{Config: w.config})
}
select { select {
case <-w.redraw: case <-w.redraw:
default: default:
} }
w.visible = true
if w.anim { if w.anim {
w.SetAnimating(w.anim) w.SetAnimating(w.anim)
} }
sz := w.config.Size w.scale = float32(C.getViewBackingScale(w.view))
wf, hf := float32(C.viewWidth(w.view)), float32(C.viewHeight(w.view))
sz := image.Point{
X: int(wf*w.scale + .5),
Y: int(hf*w.scale + .5),
}
if sz != w.config.Size {
w.config.Size = sz
w.ProcessEvent(ConfigEvent{Config: w.config})
}
if sz.X == 0 || sz.Y == 0 { if sz.X == 0 || sz.Y == 0 {
return return
} }
@@ -930,7 +826,7 @@ func (w *window) draw() {
w.ProcessEvent(frameEvent{ w.ProcessEvent(frameEvent{
FrameEvent: FrameEvent{ FrameEvent: FrameEvent{
Now: time.Now(), Now: time.Now(),
Size: sz, Size: w.config.Size,
Metric: cfg, Metric: cfg,
}, },
Sync: true, Sync: true,
@@ -938,29 +834,52 @@ func (w *window) draw() {
} }
func (w *window) ProcessEvent(e event.Event) { func (w *window) ProcessEvent(e event.Event) {
w.processEvent(e) w.w.ProcessEvent(e)
} // The main thread window deliver events in Event.
if w != mainThreadWindow {
func (w *window) processEvent(e event.Event) bool { w.loop.FlushEvents()
handled := w.w.ProcessEvent(e) }
w.loop.FlushEvents()
return handled
} }
func (w *window) Event() event.Event { func (w *window) Event() event.Event {
return w.loop.Event() if !isMainThread() {
return w.loop.Event()
}
mainThreadWindow = w
defer func() { mainThreadWindow = nil }()
for {
if evt, ok := w.loop.win.nextEvent(); ok {
return evt
}
C.dispatchEvent()
gio_dispatchMainFuncs()
}
} }
func (w *window) Invalidate() { func (w *window) Invalidate() {
if isMainThread() {
mainThreadWindow = w
defer func() { mainThreadWindow = nil }()
}
w.loop.Invalidate() w.loop.Invalidate()
} }
func (w *window) Run(f func()) { func (w *window) Run(f func()) {
if isMainThread() {
mainThreadWindow = w
defer func() { mainThreadWindow = nil }()
}
w.loop.Run(f) w.loop.Run(f)
} }
func (w *window) Frame(frame *op.Ops) { func (w *window) Frame(frame *op.Ops) {
w.loop.Frame(frame) if !isMainThread() {
w.loop.Frame(frame)
return
}
mainThreadWindow = w
defer func() { mainThreadWindow = nil }()
w.loop.win.ProcessFrame(frame, nil)
} }
func configFor(scale float32) unit.Metric { func configFor(scale float32) unit.Metric {
@@ -978,6 +897,7 @@ func gio_onAttached(h C.uintptr_t, attached C.int) {
w.ProcessEvent(AppKitViewEvent{View: uintptr(w.view), Layer: uintptr(layer)}) w.ProcessEvent(AppKitViewEvent{View: uintptr(w.view), Layer: uintptr(layer)})
} else { } else {
w.ProcessEvent(AppKitViewEvent{}) w.ProcessEvent(AppKitViewEvent{})
w.visible = false
w.SetAnimating(w.anim) w.SetAnimating(w.anim)
} }
} }
@@ -992,31 +912,64 @@ func gio_onDestroy(h C.uintptr_t) {
w.view = 0 w.view = 0
} }
//export gio_onFinishLaunching //export gio_onHide
func gio_onFinishLaunching() { func gio_onHide(h C.uintptr_t) {
close(launched) w := windowFor(h)
w.visible = false
w.SetAnimating(w.anim)
}
//export gio_onShow
func gio_onShow(h C.uintptr_t) {
w := windowFor(h)
w.draw()
}
//export gio_onFullscreen
func gio_onFullscreen(h C.uintptr_t) {
w := windowFor(h)
w.config.Mode = Fullscreen
w.ProcessEvent(ConfigEvent{Config: w.config})
}
//export gio_onWindowed
func gio_onWindowed(h C.uintptr_t) {
w := windowFor(h)
w.config.Mode = Windowed
w.ProcessEvent(ConfigEvent{Config: w.config})
} }
func newWindow(win *callbacks, options []Option) { func newWindow(win *callbacks, options []Option) {
<-launched w := &window{
res := make(chan struct{}) redraw: make(chan struct{}, 1),
runOnMain(func() { w: win,
w := &window{ }
redraw: make(chan struct{}, 1), w.loop = newEventLoop(w.w, w.wakeup)
w: win, if isMainThread() {
mainThreadWindow = w
defer func() { mainThreadWindow = nil }()
select {
case <-launched:
default:
// If we're the main thread, initialize the GUI.
C.gio_initApp()
close(launched)
} }
w.loop = newEventLoop(w.w, w.wakeup) } else {
<-launched
}
res := make(chan struct{}, 1)
runOnMain(func() {
win.SetDriver(w) win.SetDriver(w)
res <- struct{}{} res <- struct{}{}
var cnf Config if err := w.init(); err != nil {
cnf.apply(unit.Metric{}, options)
if err := w.init(cnf.CustomRenderer); err != nil {
w.ProcessEvent(DestroyEvent{Err: err}) w.ProcessEvent(DestroyEvent{Err: err})
return return
} }
window := C.gio_createWindow(w.view, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y)) window := C.gio_createWindow(w.view, 0, 0, 0, 0, 0, 0)
// Release our reference now that the NSWindow has it. // Release our reference now that the NSWindow has it.
C.CFRelease(w.view) C.CFRelease(w.view)
w.updateWindowMode()
w.Configure(options) w.Configure(options)
if nextTopLeft.x == 0 && nextTopLeft.y == 0 { if nextTopLeft.x == 0 && nextTopLeft.y == 0 {
// cascadeTopLeftFromPoint treats (0, 0) as a no-op, // cascadeTopLeftFromPoint treats (0, 0) as a no-op,
@@ -1024,19 +977,14 @@ func newWindow(win *callbacks, options []Option) {
nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft) nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft)
} }
nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft) nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft)
C.makeFirstResponder(window, w.view)
// makeKeyAndOrderFront assumes ownership of our window reference. // makeKeyAndOrderFront assumes ownership of our window reference.
C.makeKeyAndOrderFront(window) C.makeKeyAndOrderFront(window)
}) })
<-res <-res
} }
func (w *window) init(customRenderer bool) error { func (w *window) init() error {
presentWithTrans := 1 view := C.gio_createView()
if customRenderer {
presentWithTrans = 0
}
view := C.gio_createView(C.int(presentWithTrans))
if view == 0 { if view == 0 {
return errors.New("newOSWindow: failed to create view") return errors.New("newOSWindow: failed to create view")
} }
@@ -1049,7 +997,9 @@ func (w *window) init(customRenderer bool) error {
return return
} }
w.runOnMain(func() { w.runOnMain(func() {
C.setNeedsDisplay(w.view) if w.visible {
C.setNeedsDisplay(w.view)
}
}) })
}) })
w.displayLink = dl w.displayLink = dl
@@ -1062,17 +1012,10 @@ func (w *window) init(customRenderer bool) error {
return nil return nil
} }
func osMain() { func convertKey(k rune) (key.Name, bool) {
if !isMainThread() {
panic("app.Main must run on the main goroutine")
}
C.gio_main()
}
func convertCommandKey(k rune) (key.Name, bool) {
var n key.Name var n key.Name
switch k { switch k {
case '\x1b': // ASCII escape. case 0x1b:
n = key.NameEscape n = key.NameEscape
case C.NSLeftArrowFunctionKey: case C.NSLeftArrowFunctionKey:
n = key.NameLeftArrow n = key.NameLeftArrow
@@ -1082,36 +1025,22 @@ func convertCommandKey(k rune) (key.Name, bool) {
n = key.NameUpArrow n = key.NameUpArrow
case C.NSDownArrowFunctionKey: case C.NSDownArrowFunctionKey:
n = key.NameDownArrow n = key.NameDownArrow
case '\r': case 0xd:
n = key.NameReturn n = key.NameReturn
case '\x03': case 0x3:
n = key.NameEnter n = key.NameEnter
case C.NSHomeFunctionKey: case C.NSHomeFunctionKey:
n = key.NameHome n = key.NameHome
case C.NSEndFunctionKey: case C.NSEndFunctionKey:
n = key.NameEnd n = key.NameEnd
case '\x7f', '\b': case 0x7f:
n = key.NameDeleteBackward n = key.NameDeleteBackward
case C.NSDeleteFunctionKey: case C.NSDeleteFunctionKey:
n = key.NameDeleteForward n = key.NameDeleteForward
case '\t', 0x19:
n = key.NameTab
case C.NSPageUpFunctionKey: case C.NSPageUpFunctionKey:
n = key.NamePageUp n = key.NamePageUp
case C.NSPageDownFunctionKey: case C.NSPageDownFunctionKey:
n = key.NamePageDown n = key.NamePageDown
default:
return "", false
}
return n, true
}
func convertKey(k rune) (key.Name, bool) {
if n, ok := convertCommandKey(k); ok {
return n, true
}
var n key.Name
switch k {
case C.NSF1FunctionKey: case C.NSF1FunctionKey:
n = key.NameF1 n = key.NameF1
case C.NSF2FunctionKey: case C.NSF2FunctionKey:
@@ -1136,6 +1065,8 @@ func convertKey(k rune) (key.Name, bool) {
n = key.NameF11 n = key.NameF11
case C.NSF12FunctionKey: case C.NSF12FunctionKey:
n = key.NameF12 n = key.NameF12
case 0x09, 0x19:
n = key.NameTab
case 0x20: case 0x20:
n = key.NameSpace n = key.NameSpace
default: default:
@@ -1165,8 +1096,14 @@ func convertMods(mods C.NSUInteger) key.Modifiers {
return kmods return kmods
} }
func (w *window) wakeup() {
runOnMain(func() {
w.loop.Wakeup()
if w != mainThreadWindow {
w.loop.FlushEvents()
}
})
}
func (AppKitViewEvent) implementsViewEvent() {} func (AppKitViewEvent) implementsViewEvent() {}
func (AppKitViewEvent) ImplementsEvent() {} func (AppKitViewEvent) ImplementsEvent() {}
func (a AppKitViewEvent) Valid() bool {
return a != (AppKitViewEvent{})
}
+264 -66
View File
@@ -6,7 +6,7 @@
#include "_cgo_export.h" #include "_cgo_export.h"
__attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(BOOL presentWithTrans); __attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(void);
@interface GioAppDelegate : NSObject<NSApplicationDelegate> @interface GioAppDelegate : NSObject<NSApplicationDelegate>
@end @end
@@ -16,29 +16,28 @@ __attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(BOOL presentWi
@interface GioView : NSView <CALayerDelegate,NSTextInputClient> @interface GioView : NSView <CALayerDelegate,NSTextInputClient>
@property uintptr_t handle; @property uintptr_t handle;
@property BOOL presentWithTrans;
@end @end
@implementation GioWindowDelegate @implementation GioWindowDelegate
- (void)windowWillMiniaturize:(NSNotification *)notification { - (void)windowWillMiniaturize:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object]; NSWindow *window = (NSWindow *)[notification object];
GioView *view = (GioView *)window.contentView; GioView *view = (GioView *)window.contentView;
gio_onDraw(view.handle); gio_onHide(view.handle);
} }
- (void)windowDidDeminiaturize:(NSNotification *)notification { - (void)windowDidDeminiaturize:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object]; NSWindow *window = (NSWindow *)[notification object];
GioView *view = (GioView *)window.contentView; GioView *view = (GioView *)window.contentView;
gio_onDraw(view.handle); gio_onShow(view.handle);
} }
- (void)windowWillEnterFullScreen:(NSNotification *)notification { - (void)windowWillEnterFullScreen:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object]; NSWindow *window = (NSWindow *)[notification object];
GioView *view = (GioView *)window.contentView; GioView *view = (GioView *)window.contentView;
gio_onDraw(view.handle); gio_onFullscreen(view.handle);
} }
- (void)windowWillExitFullScreen:(NSNotification *)notification { - (void)windowWillExitFullScreen:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object]; NSWindow *window = (NSWindow *)[notification object];
GioView *view = (GioView *)window.contentView; GioView *view = (GioView *)window.contentView;
gio_onDraw(view.handle); gio_onWindowed(view.handle);
} }
- (void)windowDidChangeScreen:(NSNotification *)notification { - (void)windowDidChangeScreen:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object]; NSWindow *window = (NSWindow *)[notification object];
@@ -48,17 +47,13 @@ __attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(BOOL presentWi
} }
- (void)windowDidBecomeKey:(NSNotification *)notification { - (void)windowDidBecomeKey:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object]; NSWindow *window = (NSWindow *)[notification object];
GioView *view = (GioView *)window.contentView; GioView *view = (GioView *)window.contentView;
if ([window firstResponder] == view) { gio_onFocus(view.handle, 1);
gio_onFocus(view.handle, 1);
}
} }
- (void)windowDidResignKey:(NSNotification *)notification { - (void)windowDidResignKey:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object]; NSWindow *window = (NSWindow *)[notification object];
GioView *view = (GioView *)window.contentView; GioView *view = (GioView *)window.contentView;
if ([window firstResponder] == view) { gio_onFocus(view.handle, 0);
gio_onFocus(view.handle, 0);
}
} }
@end @end
@@ -74,6 +69,212 @@ static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFl
gio_onMouse(view.handle, (__bridge CFTypeRef)event, typ, event.buttonNumber, p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]); gio_onMouse(view.handle, (__bridge CFTypeRef)event, typ, event.buttonNumber, p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]);
} }
@interface GioApplication: NSApplication
@end
// Variables for tracking resizes.
static struct {
NSPoint dir;
NSEvent *lastMouseDown;
NSPoint off;
} resizeState = {};
static NSBitmapImageRep *nsImageBitmap(NSImage *img) {
NSArray<NSImageRep *> *reps = img.representations;
if ([reps count] == 0) {
return nil;
}
NSImageRep *rep = reps[0];
if (![rep isKindOfClass:[NSBitmapImageRep class]]) {
return nil;
}
return (NSBitmapImageRep *)rep;
}
static NSCursor *lookupPrivateNSCursor(SEL name) {
if (![NSCursor respondsToSelector:name]) {
return nil;
}
id obj = [NSCursor performSelector:name];
if (![obj isKindOfClass:[NSCursor class]]) {
return nil;
}
return (NSCursor *)obj;
}
static BOOL isEqualNSCursor(NSCursor *c1, SEL name2) {
NSCursor *c2 = lookupPrivateNSCursor(name2);
if (c2 == nil || !NSEqualPoints(c1.hotSpot, c2.hotSpot)) {
return NO;
}
NSImage *img1 = c1.image;
NSImage *img2 = c2.image;
if (!NSEqualSizes(img1.size, img2.size)) {
return NO;
}
NSBitmapImageRep *bit1 = nsImageBitmap(img1);
NSBitmapImageRep *bit2 = nsImageBitmap(img2);
if (bit1 == nil || bit2 == nil) {
return NO;
}
NSInteger n1 = bit1.numberOfPlanes*bit1.bytesPerPlane;
NSInteger n2 = bit1.numberOfPlanes*bit1.bytesPerPlane;
if (n1 != n2) {
return NO;
}
if (memcmp(bit1.bitmapData, bit2.bitmapData, n1) != 0) {
return NO;
}
return YES;
}
@implementation GioApplication
- (NSEvent *)nextEventMatchingMask:(NSEventMask)mask
untilDate:(NSDate *)expiration
inMode:(NSRunLoopMode)mode
dequeue:(BOOL)deqFlag {
if ([mode isEqualToString:NSEventTrackingRunLoopMode]) {
NSEvent *l = resizeState.lastMouseDown;
if (l != nil) {
//lastMouseDown = nil;
NSCursor *cur = [NSCursor currentSystemCursor];
NSPoint dir = {};
NSPoint off = {};
NSSize wsz = [l window].frame.size;
NSPoint center = NSMakePoint(wsz.width/2, wsz.height/2);
NSPoint p = [l locationInWindow];
if (p.x >= center.x) {
dir.x = 1;
off.x = p.x - wsz.width;
} else {
dir.x = -1;
off.x = p.x;
}
if (p.y >= center.y) {
dir.y = 1;
off.y = p.y - wsz.height;
} else {
dir.y = -1;
off.y = p.y;
}
// The button down coordinate distinguish the four quadrants. Use the
// cursor image to determine the precise direction.
SEL nw = @selector(_windowResizeNorthWestCursor);
SEL n = @selector(_windowResizeNorthCursor);
SEL ne = @selector(_windowResizeNorthEastCursor);
SEL e = @selector(_windowResizeEastCursor);
SEL se = @selector(_windowResizeSouthEastCursor);
SEL s = @selector(_windowResizeSouthCursor);
SEL sw = @selector(_windowResizeSouthWestCursor);
SEL w = @selector(_windowResizeWestCursor);
SEL ns = @selector(_windowResizeNorthSouthCursor);
SEL ew = @selector(_windowResizeEastWestCursor);
SEL nwse = @selector(_windowResizeNorthWestSouthEastCursor);
SEL nesw = @selector(_windowResizeNorthEastSouthWestCursor);
BOOL match = YES;
if (dir.x != 0 && (isEqualNSCursor(cur, ew) || isEqualNSCursor(cur, w) || isEqualNSCursor(cur, e))) {
dir.y = 0;
}
if (dir.y != 0 && (isEqualNSCursor(cur, ns) || isEqualNSCursor(cur, s) || isEqualNSCursor(cur, n))) {
dir.x = 0;
}
// If none of the cursors matched, we may deduce that the resize
// direction is one of the corners. However, to ensure that at least
// one cursor matches, check the corner cursors.
if (dir.x == 1 && dir.y == 1) {
if (!isEqualNSCursor(cur, nesw) && !isEqualNSCursor(cur, sw)) {
dir = NSZeroPoint;
}
} else if (dir.x == 1 && dir.y == -1) {
if (!isEqualNSCursor(cur, nwse) && !isEqualNSCursor(cur, nw)) {
dir = NSZeroPoint;
}
} else if (dir.x == -1 && dir.y == 1) {
if (!isEqualNSCursor(cur, nwse) && !isEqualNSCursor(cur, se)) {
dir = NSZeroPoint;
}
} else if (dir.x == -1 && dir.y == -1) {
if (!isEqualNSCursor(cur, nesw) && !isEqualNSCursor(cur, ne)) {
dir = NSZeroPoint;
}
}
if (!NSEqualPoints(dir, NSZeroPoint)) {
NSEvent *cancel = [NSEvent mouseEventWithType:NSEventTypeLeftMouseUp
location:l.locationInWindow
modifierFlags:l.modifierFlags
timestamp:l.timestamp
windowNumber:l.windowNumber
context:l.context
eventNumber:l.eventNumber
clickCount:l.clickCount
pressure:l.pressure];
resizeState.off = off;
resizeState.dir = dir;
return cancel;
}
}
}
return [super nextEventMatchingMask:mask untilDate:expiration inMode:mode dequeue:deqFlag];
}
@end
@interface GioWindow: NSWindow
@end
@implementation GioWindow
- (void)sendEvent:(NSEvent *)evt {
if (evt.type == NSEventTypeLeftMouseDown) {
resizeState.lastMouseDown = evt;
}
NSPoint dir = resizeState.dir;
if (NSEqualPoints(dir, NSZeroPoint)) {
[super sendEvent:evt];
return;
}
switch (evt.type) {
default:
return;
case NSEventTypeLeftMouseUp:
resizeState.dir = NSZeroPoint;
resizeState.lastMouseDown = nil;
return;
case NSEventTypeLeftMouseDragged:
// Ok to proceed.
break;
}
NSPoint loc = evt.locationInWindow;
NSPoint off = resizeState.off;
loc.x -= off.x;
loc.y -= off.y;
NSRect frame = [self frame];
NSSize min = [self minSize];
NSSize max = [self maxSize];
CGFloat width = frame.size.width;
if (dir.x > 0) {
width = loc.x;
} else if (dir.x < 0) {
width -= loc.x;
}
width = MIN(max.width, MAX(min.width, width));
if (dir.x < 0) {
frame.origin.x += frame.size.width - width;
}
frame.size.width = width;
CGFloat height = frame.size.height;
if (dir.y > 0) {
height = loc.y;
} else if (dir.y < 0) {
height -= loc.y;
}
height = MIN(max.height, MAX(min.height, height));
if (dir.y < 0) {
frame.origin.y += frame.size.height - height;
}
frame.size.height = height;
[self setFrame:frame display:YES animate:NO];
}
@end
@implementation GioView @implementation GioView
- (void)setFrameSize:(NSSize)newSize { - (void)setFrameSize:(NSSize)newSize {
[super setFrameSize:newSize]; [super setFrameSize:newSize];
@@ -89,7 +290,7 @@ static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFl
gio_onDraw(self.handle); gio_onDraw(self.handle);
} }
- (CALayer *)makeBackingLayer { - (CALayer *)makeBackingLayer {
CALayer *layer = gio_layerFactory(self.presentWithTrans); CALayer *layer = gio_layerFactory();
layer.delegate = self; layer.delegate = self;
return layer; return layer;
} }
@@ -132,24 +333,20 @@ static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFl
handleMouse(self, event, MOUSE_SCROLL, dx, dy); handleMouse(self, event, MOUSE_SCROLL, dx, dy);
} }
- (void)keyDown:(NSEvent *)event { - (void)keyDown:(NSEvent *)event {
NSString *keys = [event charactersIgnoringModifiers];
gio_onKeys(self.handle, (__bridge CFTypeRef)event, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true);
}
- (void)flagsChanged:(NSEvent *)event {
[self interpretKeyEvents:[NSArray arrayWithObject:event]]; [self interpretKeyEvents:[NSArray arrayWithObject:event]];
gio_onFlagsChanged(self.handle, [event modifierFlags]); NSString *keys = [event charactersIgnoringModifiers];
gio_onKeys(self.handle, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true);
} }
- (void)keyUp:(NSEvent *)event { - (void)keyUp:(NSEvent *)event {
NSString *keys = [event charactersIgnoringModifiers]; NSString *keys = [event charactersIgnoringModifiers];
gio_onKeys(self.handle, (__bridge CFTypeRef)event, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], false); gio_onKeys(self.handle, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], false);
} }
- (void)insertText:(id)string { - (void)insertText:(id)string {
gio_onText(self.handle, (__bridge CFTypeRef)string); gio_onText(self.handle, (__bridge CFTypeRef)string);
} }
- (void)doCommandBySelector:(SEL)action { - (void)doCommandBySelector:(SEL)sel {
if (!gio_onCommandBySelector(self.handle)) { // Don't pass commands up the responder chain.
[super doCommandBySelector:action]; // They will end up in a beep.
}
} }
- (BOOL)hasMarkedText { - (BOOL)hasMarkedText {
int res = gio_hasMarkedText(self.handle); int res = gio_hasMarkedText(self.handle);
@@ -205,22 +402,14 @@ static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFl
return [[self window] convertRectToScreen:r]; return [[self window] convertRectToScreen:r];
} }
- (void)applicationWillUnhide:(NSNotification *)notification { - (void)applicationWillUnhide:(NSNotification *)notification {
gio_onDraw(self.handle); gio_onShow(self.handle);
} }
- (void)applicationDidHide:(NSNotification *)notification { - (void)applicationDidHide:(NSNotification *)notification {
gio_onDraw(self.handle); gio_onHide(self.handle);
} }
- (void)dealloc { - (void)dealloc {
gio_onDestroy(self.handle); gio_onDestroy(self.handle);
} }
- (BOOL) becomeFirstResponder {
gio_onFocus(self.handle, 1);
return [super becomeFirstResponder];
}
- (BOOL) resignFirstResponder {
gio_onFocus(self.handle, 0);
return [super resignFirstResponder];
}
@end @end
// Delegates are weakly referenced from their peers. Nothing // Delegates are weakly referenced from their peers. Nothing
@@ -271,14 +460,11 @@ void gio_showCursor() {
// some cursors are not public, this tries to use a private cursor // some cursors are not public, this tries to use a private cursor
// and uses fallback when the use of private cursor fails. // and uses fallback when the use of private cursor fails.
static void trySetPrivateCursor(SEL cursorName, NSCursor* fallback) { static void trySetPrivateCursor(SEL cursorName, NSCursor* fallback) {
if ([NSCursor respondsToSelector:cursorName]) { NSCursor *cur = lookupPrivateNSCursor(cursorName);
id object = [NSCursor performSelector:cursorName]; if (cur == nil) {
if ([object isKindOfClass:[NSCursor class]]) { cur = fallback;
[(NSCursor*)object set];
return;
}
} }
[fallback set]; [cur set];
} }
void gio_setCursor(NSUInteger curID) { void gio_setCursor(NSUInteger curID) {
@@ -369,7 +555,7 @@ void gio_setCursor(NSUInteger curID) {
} }
} }
CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height) { CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight) {
@autoreleasepool { @autoreleasepool {
NSRect rect = NSMakeRect(0, 0, width, height); NSRect rect = NSMakeRect(0, 0, width, height);
NSUInteger styleMask = NSTitledWindowMask | NSUInteger styleMask = NSTitledWindowMask |
@@ -377,23 +563,29 @@ CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height) {
NSMiniaturizableWindowMask | NSMiniaturizableWindowMask |
NSClosableWindowMask; NSClosableWindowMask;
NSWindow* window = [[NSWindow alloc] initWithContentRect:rect GioWindow* window = [[GioWindow alloc] initWithContentRect:rect
styleMask:styleMask styleMask:styleMask
backing:NSBackingStoreBuffered backing:NSBackingStoreBuffered
defer:NO]; defer:NO];
if (minWidth > 0 || minHeight > 0) {
window.contentMinSize = NSMakeSize(minWidth, minHeight);
}
if (maxWidth > 0 || maxHeight > 0) {
window.contentMaxSize = NSMakeSize(maxWidth, maxHeight);
}
[window setAcceptsMouseMovedEvents:YES]; [window setAcceptsMouseMovedEvents:YES];
NSView *view = (__bridge NSView *)viewRef; NSView *view = (__bridge NSView *)viewRef;
[window setContentView:view]; [window setContentView:view];
[window makeFirstResponder:view];
window.delegate = globalWindowDel; window.delegate = globalWindowDel;
return (__bridge_retained CFTypeRef)window; return (__bridge_retained CFTypeRef)window;
} }
} }
CFTypeRef gio_createView(int presentWithTrans) { CFTypeRef gio_createView(void) {
@autoreleasepool { @autoreleasepool {
NSRect frame = NSMakeRect(0, 0, 0, 0); NSRect frame = NSMakeRect(0, 0, 0, 0);
GioView* view = [[GioView alloc] initWithFrame:frame]; GioView* view = [[GioView alloc] initWithFrame:frame];
view.presentWithTrans = presentWithTrans ? YES : NO;
view.wantsLayer = YES; view.wantsLayer = YES;
view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize; view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
@@ -420,12 +612,24 @@ void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
[NSApp activateIgnoringOtherApps:YES]; [NSApp activateIgnoringOtherApps:YES];
// Force the [NSApp run] call to return.
[NSApp stop:nil];
NSEvent *dummy = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
location:NSZeroPoint
modifierFlags:0
timestamp:0
windowNumber:0
context:nil
subtype:0
data1:0
data2:0];
[NSApp postEvent:dummy atStart:YES];
} }
@end @end
void gio_main() { void gio_initApp() {
@autoreleasepool { @autoreleasepool {
[NSApplication sharedApplication]; [GioApplication sharedApplication];
GioAppDelegate *del = [[GioAppDelegate alloc] init]; GioAppDelegate *del = [[GioAppDelegate alloc] init];
[NSApp setDelegate:del]; [NSApp setDelegate:del];
@@ -447,28 +651,22 @@ void gio_main() {
globalWindowDel = [[GioWindowDelegate alloc] init]; globalWindowDel = [[GioWindowDelegate alloc] init];
// Runs until stopped by applicationDidFinishLaunching.
[NSApp run]; [NSApp run];
} }
} }
@interface AppListener : NSObject void gio_wakeupMainThread(void) {
@end
static AppListener *appListener;
@implementation AppListener
- (void)launchFinished:(NSNotification *)notification {
appListener = nil;
gio_onFinishLaunching();
}
@end
void gio_init() {
@autoreleasepool { @autoreleasepool {
appListener = [[AppListener alloc] init]; NSEvent *dummy = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
[[NSNotificationCenter defaultCenter] addObserver:appListener location:NSZeroPoint
selector:@selector(launchFinished:) modifierFlags:0
name:NSApplicationDidFinishLaunchingNotification timestamp:0
object:nil]; windowNumber:0
context:nil
subtype:0
data1:0
data2:0];
[NSApp postEvent:dummy atStart:YES];
} }
} }
+24 -10
View File
@@ -9,6 +9,7 @@ import (
"errors" "errors"
"unsafe" "unsafe"
"gioui.org/io/event"
"gioui.org/io/pointer" "gioui.org/io/pointer"
) )
@@ -21,9 +22,6 @@ type X11ViewEvent struct {
func (X11ViewEvent) implementsViewEvent() {} func (X11ViewEvent) implementsViewEvent() {}
func (X11ViewEvent) ImplementsEvent() {} func (X11ViewEvent) ImplementsEvent() {}
func (x X11ViewEvent) Valid() bool {
return x != (X11ViewEvent{})
}
type WaylandViewEvent struct { type WaylandViewEvent struct {
// Display is the *wl_display returned by wl_display_connect. // Display is the *wl_display returned by wl_display_connect.
@@ -34,13 +32,6 @@ type WaylandViewEvent struct {
func (WaylandViewEvent) implementsViewEvent() {} func (WaylandViewEvent) implementsViewEvent() {}
func (WaylandViewEvent) ImplementsEvent() {} func (WaylandViewEvent) ImplementsEvent() {}
func (w WaylandViewEvent) Valid() bool {
return w != (WaylandViewEvent{})
}
func osMain() {
select {}
}
type windowDriver func(*callbacks, []Option) error type windowDriver func(*callbacks, []Option) error
@@ -62,12 +53,35 @@ func newWindow(window *callbacks, options []Option) {
errFirst = err errFirst = err
} }
} }
window.SetDriver(&dummyDriver{
win: window,
wakeups: make(chan event.Event, 1),
})
if errFirst == nil { if errFirst == nil {
errFirst = errors.New("app: no window driver available") errFirst = errors.New("app: no window driver available")
} }
window.ProcessEvent(DestroyEvent{Err: errFirst}) window.ProcessEvent(DestroyEvent{Err: errFirst})
} }
type dummyDriver struct {
win *callbacks
wakeups chan event.Event
}
func (d *dummyDriver) Event() event.Event {
if e, ok := d.win.nextEvent(); ok {
return e
}
return <-d.wakeups
}
func (d *dummyDriver) Invalidate() {
select {
case d.wakeups <- wakeupEvent{}:
default:
}
}
// xCursor contains mapping from pointer.Cursor to XCursor. // xCursor contains mapping from pointer.Cursor to XCursor.
var xCursor = [...]string{ var xCursor = [...]string{
pointer.CursorDefault: "left_ptr", pointer.CursorDefault: "left_ptr",
+38 -46
View File
@@ -116,8 +116,6 @@ type wlSeat struct {
// The most recent input serial. // The most recent input serial.
serial C.uint32_t serial C.uint32_t
// The most recent pointer enter serial.
pointerSerial C.uint32_t
pointerFocus *window pointerFocus *window
keyboardFocus *window keyboardFocus *window
@@ -156,6 +154,7 @@ type repeatState struct {
type window struct { type window struct {
w *callbacks w *callbacks
disp *wlDisplay disp *wlDisplay
seat *wlSeat
surf *C.struct_wl_surface surf *C.struct_wl_surface
wmSurf *C.struct_xdg_surface wmSurf *C.struct_xdg_surface
topLvl *C.struct_xdg_toplevel topLvl *C.struct_xdg_toplevel
@@ -217,7 +216,9 @@ type window struct {
wakeups chan struct{} wakeups chan struct{}
closing bool // invMu avoids the race between the destruction of disp and
// Invalidate waking it up.
invMu sync.Mutex
} }
type poller struct { type poller struct {
@@ -555,7 +556,7 @@ func gio_onXdgSurfaceConfigure(data unsafe.Pointer, wmSurf *C.struct_xdg_surface
//export gio_onToplevelClose //export gio_onToplevelClose
func gio_onToplevelClose(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel) { func gio_onToplevelClose(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel) {
w := callbackLoad(data).(*window) w := callbackLoad(data).(*window)
w.closing = true w.close(nil)
} }
//export gio_onToplevelConfigure //export gio_onToplevelConfigure
@@ -852,8 +853,8 @@ func gio_onTouchCancel(data unsafe.Pointer, touch *C.struct_wl_touch) {
func gio_onPointerEnter(data unsafe.Pointer, pointer *C.struct_wl_pointer, serial C.uint32_t, surf *C.struct_wl_surface, x, y C.wl_fixed_t) { func gio_onPointerEnter(data unsafe.Pointer, pointer *C.struct_wl_pointer, serial C.uint32_t, surf *C.struct_wl_surface, x, y C.wl_fixed_t) {
s := callbackLoad(data).(*wlSeat) s := callbackLoad(data).(*wlSeat)
s.serial = serial s.serial = serial
s.pointerSerial = serial
w := callbackLoad(unsafe.Pointer(surf)).(*window) w := callbackLoad(unsafe.Pointer(surf)).(*window)
w.seat = s
s.pointerFocus = w s.pointerFocus = w
w.setCursor(pointer, serial) w.setCursor(pointer, serial)
w.lastPos = f32.Point{X: fromFixed(x), Y: fromFixed(y)} w.lastPos = f32.Point{X: fromFixed(x), Y: fromFixed(y)}
@@ -862,9 +863,9 @@ func gio_onPointerEnter(data unsafe.Pointer, pointer *C.struct_wl_pointer, seria
//export gio_onPointerLeave //export gio_onPointerLeave
func gio_onPointerLeave(data unsafe.Pointer, p *C.struct_wl_pointer, serial C.uint32_t, surf *C.struct_wl_surface) { func gio_onPointerLeave(data unsafe.Pointer, p *C.struct_wl_pointer, serial C.uint32_t, surf *C.struct_wl_surface) {
w := callbackLoad(unsafe.Pointer(surf)).(*window) w := callbackLoad(unsafe.Pointer(surf)).(*window)
w.seat = nil
s := callbackLoad(data).(*wlSeat) s := callbackLoad(data).(*wlSeat)
s.serial = serial s.serial = serial
s.pointerFocus = nil
if w.inCompositor { if w.inCompositor {
w.inCompositor = false w.inCompositor = false
w.ProcessEvent(pointer.Event{Kind: pointer.Cancel}) w.ProcessEvent(pointer.Event{Kind: pointer.Cancel})
@@ -884,13 +885,11 @@ func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t,
s := callbackLoad(data).(*wlSeat) s := callbackLoad(data).(*wlSeat)
s.serial = serial s.serial = serial
w := s.pointerFocus w := s.pointerFocus
// From Linux: include/uapi/linux/input-event-codes.h // From linux-event-codes.h.
const ( const (
BTN_LEFT = 0x110 BTN_LEFT = 0x110
BTN_RIGHT = 0x111 BTN_RIGHT = 0x111
BTN_MIDDLE = 0x112 BTN_MIDDLE = 0x112
BTN_SIDE = 0x113
BTN_EXTRA = 0x114
) )
var btn pointer.Buttons var btn pointer.Buttons
switch wbtn { switch wbtn {
@@ -900,10 +899,6 @@ func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t,
btn = pointer.ButtonSecondary btn = pointer.ButtonSecondary
case BTN_MIDDLE: case BTN_MIDDLE:
btn = pointer.ButtonTertiary btn = pointer.ButtonTertiary
case BTN_SIDE:
btn = pointer.ButtonQuaternary
case BTN_EXTRA:
btn = pointer.ButtonQuinary
default: default:
return return
} }
@@ -970,9 +965,6 @@ func gio_onPointerAxis(data unsafe.Pointer, p *C.struct_wl_pointer, t, axis C.ui
func gio_onPointerFrame(data unsafe.Pointer, p *C.struct_wl_pointer) { func gio_onPointerFrame(data unsafe.Pointer, p *C.struct_wl_pointer) {
s := callbackLoad(data).(*wlSeat) s := callbackLoad(data).(*wlSeat)
w := s.pointerFocus w := s.pointerFocus
if w == nil {
return
}
w.flushScroll() w.flushScroll()
w.flushFling() w.flushFling()
} }
@@ -1147,23 +1139,22 @@ func (w *window) Perform(actions system.Action) {
walkActions(actions, func(action system.Action) { walkActions(actions, func(action system.Action) {
switch action { switch action {
case system.ActionClose: case system.ActionClose:
w.closing = true w.close(nil)
} }
}) })
} }
func (w *window) move(serial C.uint32_t) { func (w *window) move(serial C.uint32_t) {
s := w.disp.seat s := w.seat
if w.inCompositor || s.pointerFocus != w { if !w.inCompositor && s != nil {
return w.inCompositor = true
C.xdg_toplevel_move(w.topLvl, s.seat, serial)
} }
w.inCompositor = true
C.xdg_toplevel_move(w.topLvl, s.seat, serial)
} }
func (w *window) resize(serial, edge C.uint32_t) { func (w *window) resize(serial, edge C.uint32_t) {
s := w.disp.seat s := w.seat
if w.inCompositor || s.pointerFocus != w { if w.inCompositor || s == nil {
return return
} }
w.inCompositor = true w.inCompositor = true
@@ -1176,12 +1167,11 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
} }
func (w *window) updateCursor() { func (w *window) updateCursor() {
s := w.disp.seat ptr := w.disp.seat.pointer
ptr := s.pointer if ptr == nil {
if ptr == nil || s.pointerFocus != w {
return return
} }
w.setCursor(ptr, s.pointerSerial) w.setCursor(ptr, w.serial)
} }
func (w *window) setCursor(pointer *C.struct_wl_pointer, serial C.uint32_t) { func (w *window) setCursor(pointer *C.struct_wl_pointer, serial C.uint32_t) {
@@ -1190,7 +1180,7 @@ func (w *window) setCursor(pointer *C.struct_wl_pointer, serial C.uint32_t) {
c = w.cursor.cursor c = w.cursor.cursor
} }
if c == nil { if c == nil {
C.wl_pointer_set_cursor(pointer, serial, nil, 0, 0) C.wl_pointer_set_cursor(pointer, w.serial, nil, 0, 0)
return return
} }
// Get images[0]. // Get images[0].
@@ -1376,9 +1366,6 @@ func gio_onFrameDone(data unsafe.Pointer, callback *C.struct_wl_callback, t C.ui
func (w *window) close(err error) { func (w *window) close(err error) {
w.ProcessEvent(WaylandViewEvent{}) w.ProcessEvent(WaylandViewEvent{})
w.ProcessEvent(DestroyEvent{Err: err}) w.ProcessEvent(DestroyEvent{Err: err})
w.destroy()
w.disp.destroy()
w.disp = nil
} }
func (w *window) dispatch() { func (w *window) dispatch() {
@@ -1387,7 +1374,7 @@ func (w *window) dispatch() {
w.w.Invalidate() w.w.Invalidate()
return return
} }
if err := w.disp.dispatch(); err != nil || w.closing { if err := w.disp.dispatch(); err != nil {
w.close(err) w.close(err)
return return
} }
@@ -1412,6 +1399,13 @@ func (w *window) Event() event.Event {
w.dispatch() w.dispatch()
continue continue
} }
if _, destroy := evt.(DestroyEvent); destroy {
w.destroy()
w.invMu.Lock()
w.disp.destroy()
w.disp = nil
w.invMu.Unlock()
}
return evt return evt
} }
} }
@@ -1422,7 +1416,11 @@ func (w *window) Invalidate() {
default: default:
return return
} }
w.disp.wakeup() w.invMu.Lock()
defer w.invMu.Unlock()
if w.disp != nil {
w.disp.wakeup()
}
} }
func (w *window) Run(f func()) { func (w *window) Run(f func()) {
@@ -1517,10 +1515,6 @@ func (d *wlDisplay) wakeup() {
} }
func (w *window) destroy() { func (w *window) destroy() {
if w.lastFrameCallback != nil {
C.wl_callback_destroy(w.lastFrameCallback)
w.lastFrameCallback = nil
}
if w.cursor.surf != nil { if w.cursor.surf != nil {
C.wl_surface_destroy(w.cursor.surf) C.wl_surface_destroy(w.cursor.surf)
} }
@@ -1645,14 +1639,6 @@ func (w *window) flushScroll() {
if total == (f32.Point{}) { if total == (f32.Point{}) {
return return
} }
if w.scroll.steps == (image.Point{}) {
w.fling.xExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.X)
w.fling.yExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.Y)
}
// Zero scroll distance prior to calling ProcessEvent, otherwise we may recursively
// re-process the scroll distance.
w.scroll.dist = f32.Point{}
w.scroll.steps = image.Point{}
w.ProcessEvent(pointer.Event{ w.ProcessEvent(pointer.Event{
Kind: pointer.Scroll, Kind: pointer.Scroll,
Source: pointer.Mouse, Source: pointer.Mouse,
@@ -1662,6 +1648,12 @@ func (w *window) flushScroll() {
Time: w.scroll.time, Time: w.scroll.time,
Modifiers: w.disp.xkb.Modifiers(), Modifiers: w.disp.xkb.Modifiers(),
}) })
if w.scroll.steps == (image.Point{}) {
w.fling.xExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.X)
w.fling.yExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.Y)
}
w.scroll.dist = f32.Point{}
w.scroll.steps = image.Point{}
} }
func (w *window) onPointerMotion(x, y C.wl_fixed_t, t C.uint32_t) { func (w *window) onPointerMotion(x, y C.wl_fixed_t, t C.uint32_t) {
+108 -171
View File
@@ -36,22 +36,27 @@ type Win32ViewEvent struct {
} }
type window struct { type window struct {
hwnd syscall.Handle hwnd syscall.Handle
hdc syscall.Handle hdc syscall.Handle
w *callbacks w *callbacks
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
borderSize image.Point borderSize image.Point
config Config config Config
// frameDims stores the last seen window frame width and height. loop *eventLoop
frameDims image.Point
loop *eventLoop // invMu avoids the race between destroying the window and Invalidate.
invMu sync.Mutex
} }
const _WM_WAKEUP = windows.WM_USER + iota const _WM_WAKEUP = windows.WM_USER + iota
@@ -80,10 +85,6 @@ var resources struct {
cursor syscall.Handle cursor syscall.Handle
} }
func osMain() {
select {}
}
func newWindow(win *callbacks, options []Option) { func newWindow(win *callbacks, options []Option) {
done := make(chan struct{}) done := make(chan struct{})
go func() { go func() {
@@ -106,8 +107,8 @@ func newWindow(win *callbacks, options []Option) {
} }
winMap.Store(w.hwnd, w) winMap.Store(w.hwnd, w)
defer winMap.Delete(w.hwnd) defer winMap.Delete(w.hwnd)
w.Configure(options)
w.ProcessEvent(Win32ViewEvent{HWND: uintptr(w.hwnd)}) w.ProcessEvent(Win32ViewEvent{HWND: uintptr(w.hwnd)})
w.Configure(options)
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,
@@ -174,12 +175,6 @@ func (w *window) init() error {
if err != nil { if err != nil {
return err return err
} }
if err := windows.RegisterTouchWindow(hwnd, 0); err != nil {
return err
}
if err := windows.EnableMouseInPointer(1); err != nil {
return err
}
w.hdc, err = windows.GetDC(hwnd) w.hdc, err = windows.GetDC(hwnd)
if err != nil { if err != nil {
windows.DestroyWindow(hwnd) windows.DestroyWindow(hwnd)
@@ -189,39 +184,21 @@ func (w *window) init() error {
return nil return nil
} }
// update handles changes done by the user, and updates the configuration. // update() handles changes done by the user, and updates the configuration.
// It reads the window style and size/position and updates w.config. // It reads the window style and size/position and updates w.config.
// If anything has changed it emits a ConfigEvent to notify the application. // If anything has changed it emits a ConfigEvent to notify the application.
func (w *window) update() { func (w *window) update() {
p := windows.GetWindowPlacement(w.hwnd) cr := windows.GetClientRect(w.hwnd)
if !p.IsMinimized() { w.config.Size = image.Point{
r := windows.GetWindowRect(w.hwnd) X: int(cr.Right - cr.Left),
cr := windows.GetClientRect(w.hwnd) Y: int(cr.Bottom - cr.Top),
w.config.Size = image.Point{
X: int(cr.Right - cr.Left),
Y: int(cr.Bottom - cr.Top),
}
w.frameDims = image.Point{
X: int(r.Right - r.Left),
Y: int(r.Bottom - r.Top),
}.Sub(w.config.Size)
} }
w.borderSize = image.Pt( w.borderSize = image.Pt(
windows.GetSystemMetrics(windows.SM_CXSIZEFRAME), windows.GetSystemMetrics(windows.SM_CXSIZEFRAME),
windows.GetSystemMetrics(windows.SM_CYSIZEFRAME), windows.GetSystemMetrics(windows.SM_CYSIZEFRAME),
) )
style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE)
switch {
case p.IsMaximized() && style&windows.WS_OVERLAPPEDWINDOW != 0:
w.config.Mode = Maximized
case p.IsMaximized():
w.config.Mode = Fullscreen
default:
w.config.Mode = Windowed
}
w.ProcessEvent(ConfigEvent{Config: w.config}) w.ProcessEvent(ConfigEvent{Config: w.config})
w.draw(true)
} }
func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr { func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
@@ -270,32 +247,18 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
return 0 return 0
} }
} }
case windows.WM_POINTERDOWN, windows.WM_POINTERUP, windows.WM_POINTERUPDATE, windows.WM_POINTERCAPTURECHANGED: case windows.WM_LBUTTONDOWN:
pid := getPointerIDwParam(wParam) w.pointerButton(pointer.ButtonPrimary, true, lParam, getModifiers())
pi, err := windows.GetPointerInfo(uint32(pid)) case windows.WM_LBUTTONUP:
if err != nil { w.pointerButton(pointer.ButtonPrimary, false, lParam, getModifiers())
panic(err) case windows.WM_RBUTTONDOWN:
} w.pointerButton(pointer.ButtonSecondary, true, lParam, getModifiers())
switch msg { case windows.WM_RBUTTONUP:
case windows.WM_POINTERDOWN: w.pointerButton(pointer.ButtonSecondary, false, lParam, getModifiers())
windows.SetCapture(w.hwnd) case windows.WM_MBUTTONDOWN:
case windows.WM_POINTERUP: w.pointerButton(pointer.ButtonTertiary, true, lParam, getModifiers())
windows.ReleaseCapture() case windows.WM_MBUTTONUP:
} w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers())
kind := pointer.Move
switch pi.ButtonChangeType {
case windows.POINTER_CHANGE_FIRSTBUTTON_DOWN, windows.POINTER_CHANGE_SECONDBUTTON_DOWN, windows.POINTER_CHANGE_THIRDBUTTON_DOWN, windows.POINTER_CHANGE_FOURTHBUTTON_DOWN, windows.POINTER_CHANGE_FIFTHBUTTON_DOWN:
kind = pointer.Press
case windows.POINTER_CHANGE_FIRSTBUTTON_UP, windows.POINTER_CHANGE_SECONDBUTTON_UP, windows.POINTER_CHANGE_THIRDBUTTON_UP, windows.POINTER_CHANGE_FOURTHBUTTON_UP, windows.POINTER_CHANGE_FIFTHBUTTON_UP:
kind = pointer.Release
}
if (pi.PointerFlags&windows.POINTER_FLAG_CANCELED != 0) || (msg == windows.WM_POINTERCAPTURECHANGED) {
kind = pointer.Cancel
}
w.pointerUpdate(pi, pid, kind, lParam)
case windows.WM_CANCELMODE: case windows.WM_CANCELMODE:
w.ProcessEvent(pointer.Event{ w.ProcessEvent(pointer.Event{
Kind: pointer.Cancel, Kind: pointer.Cancel,
@@ -315,22 +278,33 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
np := windows.Point{X: int32(x), Y: int32(y)} np := windows.Point{X: int32(x), Y: int32(y)}
windows.ScreenToClient(w.hwnd, &np) windows.ScreenToClient(w.hwnd, &np)
return w.hitTest(int(np.X), int(np.Y)) return w.hitTest(int(np.X), int(np.Y))
case windows.WM_POINTERWHEEL: case windows.WM_MOUSEMOVE:
x, y := coordsFromlParam(lParam)
p := f32.Point{X: float32(x), Y: float32(y)}
w.ProcessEvent(pointer.Event{
Kind: pointer.Move,
Source: pointer.Mouse,
Position: p,
Buttons: w.pointerBtns,
Time: windows.GetMessageTime(),
Modifiers: getModifiers(),
})
case windows.WM_MOUSEWHEEL:
w.scrollEvent(wParam, lParam, false, getModifiers()) w.scrollEvent(wParam, lParam, false, getModifiers())
case windows.WM_POINTERHWHEEL: case windows.WM_MOUSEHWHEEL:
w.scrollEvent(wParam, lParam, true, getModifiers()) w.scrollEvent(wParam, lParam, true, getModifiers())
case windows.WM_DESTROY: case windows.WM_DESTROY:
w.ProcessEvent(Win32ViewEvent{}) w.ProcessEvent(Win32ViewEvent{})
w.ProcessEvent(DestroyEvent{}) w.ProcessEvent(DestroyEvent{})
w.w = nil
if w.hdc != 0 { if w.hdc != 0 {
windows.ReleaseDC(w.hdc) windows.ReleaseDC(w.hdc)
w.hdc = 0 w.hdc = 0
} }
w.invMu.Lock()
// The system destroys the HWND for us. // The system destroys the HWND for us.
w.hwnd = 0 w.hwnd = 0
w.invMu.Unlock()
windows.PostQuitMessage(0) windows.PostQuitMessage(0)
return 0
case windows.WM_NCCALCSIZE: case windows.WM_NCCALCSIZE:
if w.config.Decorated { if w.config.Decorated {
// Let Windows handle decorations. // Let Windows handle decorations.
@@ -355,31 +329,37 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
return 0 return 0
case windows.WM_PAINT: case windows.WM_PAINT:
w.draw(true) w.draw(true)
case windows.WM_STYLECHANGED:
w.update()
case windows.WM_WINDOWPOSCHANGED:
w.update()
case windows.WM_SIZE: case windows.WM_SIZE:
w.update() w.update()
switch wParam {
case windows.SIZE_MINIMIZED:
w.config.Mode = Minimized
case windows.SIZE_MAXIMIZED:
w.config.Mode = Maximized
case windows.SIZE_RESTORED:
if w.config.Mode != Fullscreen {
w.config.Mode = Windowed
}
}
case windows.WM_GETMINMAXINFO: case windows.WM_GETMINMAXINFO:
mm := (*windows.MinMaxInfo)(unsafe.Pointer(lParam)) mm := (*windows.MinMaxInfo)(unsafe.Pointer(lParam))
var bw, bh int32
var frameDims image.Point
if w.config.Decorated { if w.config.Decorated {
frameDims = w.frameDims r := windows.GetWindowRect(w.hwnd)
cr := windows.GetClientRect(w.hwnd)
bw = r.Right - r.Left - (cr.Right - cr.Left)
bh = r.Bottom - r.Top - (cr.Bottom - cr.Top)
} }
if p := w.config.MinSize; p.X > 0 || p.Y > 0 { if p := w.config.MinSize; p.X > 0 || p.Y > 0 {
p = p.Add(frameDims)
mm.PtMinTrackSize = windows.Point{ mm.PtMinTrackSize = windows.Point{
X: int32(p.X), X: int32(p.X) + bw,
Y: int32(p.Y), Y: int32(p.Y) + bh,
} }
} }
if p := w.config.MaxSize; p.X > 0 || p.Y > 0 { if p := w.config.MaxSize; p.X > 0 || p.Y > 0 {
p = p.Add(frameDims)
mm.PtMaxTrackSize = windows.Point{ mm.PtMaxTrackSize = windows.Point{
X: int32(p.X), X: int32(p.X) + bw,
Y: int32(p.Y), Y: int32(p.Y) + bh,
} }
} }
return 0 return 0
@@ -471,6 +451,9 @@ func getModifiers() key.Modifiers {
// hitTest returns the non-client area hit by the point, needed to // hitTest returns the non-client area hit by the point, needed to
// process WM_NCHITTEST. // process WM_NCHITTEST.
func (w *window) hitTest(x, y int) uintptr { func (w *window) hitTest(x, y int) uintptr {
if w.config.Mode == Fullscreen {
return windows.HTCLIENT
}
if w.config.Mode != Windowed { if w.config.Mode != Windowed {
// Only windowed mode should allow resizing. // Only windowed mode should allow resizing.
return windows.HTCLIENT return windows.HTCLIENT
@@ -507,28 +490,34 @@ func (w *window) hitTest(x, y int) uintptr {
return windows.HTCLIENT return windows.HTCLIENT
} }
func (w *window) pointerUpdate(pi windows.PointerInfo, pid pointer.ID, kind pointer.Kind, lParam uintptr) { func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) {
if !w.config.Focused { if !w.config.Focused {
windows.SetFocus(w.hwnd) windows.SetFocus(w.hwnd)
} }
src := pointer.Touch var kind pointer.Kind
if pi.PointerType == windows.PT_MOUSE { if press {
src = pointer.Mouse kind = pointer.Press
if w.pointerBtns == 0 {
windows.SetCapture(w.hwnd)
}
w.pointerBtns |= btn
} else {
kind = pointer.Release
w.pointerBtns &^= btn
if w.pointerBtns == 0 {
windows.ReleaseCapture()
}
} }
x, y := coordsFromlParam(lParam) x, y := coordsFromlParam(lParam)
np := windows.Point{X: int32(x), Y: int32(y)} p := f32.Point{X: float32(x), Y: float32(y)}
windows.ScreenToClient(w.hwnd, &np)
p := f32.Point{X: float32(np.X), Y: float32(np.Y)}
w.ProcessEvent(pointer.Event{ w.ProcessEvent(pointer.Event{
Kind: kind, Kind: kind,
Source: src, Source: pointer.Mouse,
Position: p, Position: p,
PointerID: pid, Buttons: w.pointerBtns,
Buttons: getPointerButtons(pi),
Time: windows.GetMessageTime(), Time: windows.GetMessageTime(),
Modifiers: getModifiers(), Modifiers: kmods,
}) })
} }
@@ -539,12 +528,6 @@ func coordsFromlParam(lParam uintptr) (int, int) {
} }
func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.Modifiers) { func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.Modifiers) {
pid := getPointerIDwParam(wParam)
pi, err := windows.GetPointerInfo(uint32(pid))
if err != nil {
panic(err)
}
x, y := coordsFromlParam(lParam) x, y := coordsFromlParam(lParam)
// The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast // The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast
// to other mouse events. // to other mouse events.
@@ -567,7 +550,7 @@ func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.
Kind: pointer.Scroll, Kind: pointer.Scroll,
Source: pointer.Mouse, Source: pointer.Mouse,
Position: p, Position: p,
Buttons: getPointerButtons(pi), Buttons: w.pointerBtns,
Scroll: sp, Scroll: sp,
Modifiers: kmods, Modifiers: kmods,
Time: windows.GetMessageTime(), Time: windows.GetMessageTime(),
@@ -580,8 +563,7 @@ func (w *window) runLoop() {
loop: loop:
for { for {
anim := w.animating anim := w.animating
p := windows.GetWindowPlacement(w.hwnd) if anim && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) {
if anim && !p.IsMinimized() && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) {
w.draw(false) w.draw(false)
continue continue
} }
@@ -634,6 +616,13 @@ func (w *window) Frame(frame *op.Ops) {
} }
func (w *window) wakeup() { func (w *window) wakeup() {
w.invMu.Lock()
defer w.invMu.Unlock()
if w.hwnd == 0 {
w.loop.Wakeup()
w.loop.FlushEvents()
return
}
if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil { if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil {
panic(err) panic(err)
} }
@@ -704,13 +693,8 @@ func (w *window) readClipboard() error {
func (w *window) Configure(options []Option) { func (w *window) Configure(options []Option) {
dpi := windows.GetSystemDPI() dpi := windows.GetSystemDPI()
metric := configForDPI(dpi) metric := configForDPI(dpi)
cnf := w.config w.config.apply(metric, options)
cnf.apply(metric, options) windows.SetWindowText(w.hwnd, w.config.Title)
w.config.Title = cnf.Title
w.config.Decorated = cnf.Decorated
w.config.MinSize = cnf.MinSize
w.config.MaxSize = cnf.MaxSize
windows.SetWindowText(w.hwnd, cnf.Title)
style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE) style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE)
var showMode int32 var showMode int32
@@ -718,7 +702,7 @@ func (w *window) Configure(options []Option) {
swpStyle := uintptr(windows.SWP_NOZORDER | windows.SWP_FRAMECHANGED) swpStyle := uintptr(windows.SWP_NOZORDER | windows.SWP_FRAMECHANGED)
winStyle := uintptr(windows.WS_OVERLAPPEDWINDOW) winStyle := uintptr(windows.WS_OVERLAPPEDWINDOW)
style &^= winStyle style &^= winStyle
switch cnf.Mode { switch w.config.Mode {
case Minimized: case Minimized:
style |= winStyle style |= winStyle
swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
@@ -733,13 +717,13 @@ func (w *window) Configure(options []Option) {
style |= winStyle style |= winStyle
showMode = windows.SW_SHOWNORMAL showMode = windows.SW_SHOWNORMAL
// Get target for client area size. // Get target for client area size.
width = int32(cnf.Size.X) width = int32(w.config.Size.X)
height = int32(cnf.Size.Y) height = int32(w.config.Size.Y)
// Get the current window size and position. // Get the current window size and position.
wr := windows.GetWindowRect(w.hwnd) wr := windows.GetWindowRect(w.hwnd)
x = wr.Left x = wr.Left
y = wr.Top y = wr.Top
if cnf.Decorated { if w.config.Decorated {
// Compute client size and position. Note that the client size is // Compute client size and position. Note that the client size is
// equal to the window size when we are in control of decorations. // equal to the window size when we are in control of decorations.
r := windows.Rect{ r := windows.Rect{
@@ -749,27 +733,25 @@ func (w *window) Configure(options []Option) {
windows.AdjustWindowRectEx(&r, uint32(style), 0, dwExStyle) windows.AdjustWindowRectEx(&r, uint32(style), 0, dwExStyle)
width = r.Right - r.Left width = r.Right - r.Left
height = r.Bottom - r.Top height = r.Bottom - r.Top
} else { }
if !w.config.Decorated {
// Enable drop shadows when we draw decorations. // Enable drop shadows when we draw decorations.
windows.DwmExtendFrameIntoClientArea(w.hwnd, windows.Margins{-1, -1, -1, -1}) windows.DwmExtendFrameIntoClientArea(w.hwnd, windows.Margins{-1, -1, -1, -1})
} }
case Fullscreen: case Fullscreen:
swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
mi := windows.GetMonitorInfo(w.hwnd)
x, y = mi.Monitor.Left, mi.Monitor.Top
width = mi.Monitor.Right - mi.Monitor.Left
height = mi.Monitor.Bottom - mi.Monitor.Top
showMode = windows.SW_SHOWMAXIMIZED showMode = windows.SW_SHOWMAXIMIZED
} }
// Disable window resizing if MinSize and MaxSize are equal.
if cnf.MaxSize != (image.Point{}) && cnf.MinSize == cnf.MaxSize {
style &^= windows.WS_MAXIMIZEBOX
style &^= windows.WS_THICKFRAME
}
// Note: these invocation all trigger the windows callback method which may process a pending system.ActionCenter
// action, so SetWindowPos should come first so as to not "overwrite" system.ActionCenter.
windows.SetWindowPos(w.hwnd, 0, x, y, width, height, swpStyle)
windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style) windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style)
windows.SetWindowPos(w.hwnd, 0, x, y, width, height, swpStyle)
windows.ShowWindow(w.hwnd, showMode) windows.ShowWindow(w.hwnd, showMode)
w.update()
} }
func (w *window) WriteClipboard(mime string, s []byte) { func (w *window) WriteClipboard(mime string, s []byte) {
@@ -1007,48 +989,3 @@ func configForDPI(dpi int) unit.Metric {
func (Win32ViewEvent) implementsViewEvent() {} func (Win32ViewEvent) implementsViewEvent() {}
func (Win32ViewEvent) ImplementsEvent() {} func (Win32ViewEvent) ImplementsEvent() {}
func (w Win32ViewEvent) Valid() bool {
return w != (Win32ViewEvent{})
}
// 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
}
+17 -12
View File
@@ -26,7 +26,6 @@ package app
*/ */
import "C" import "C"
import ( import (
"errors" "errors"
"fmt" "fmt"
@@ -114,6 +113,9 @@ type x11Window struct {
wakeups chan struct{} wakeups chan struct{}
handler x11EventHandler handler x11EventHandler
buf [100]byte buf [100]byte
// invMy avoids the race between destroy and Invalidate.
invMu sync.Mutex
} }
var ( var (
@@ -387,7 +389,6 @@ func (w *x11Window) ProcessEvent(e event.Event) {
func (w *x11Window) shutdown(err error) { func (w *x11Window) shutdown(err error) {
w.ProcessEvent(X11ViewEvent{}) w.ProcessEvent(X11ViewEvent{})
w.ProcessEvent(DestroyEvent{Err: err}) w.ProcessEvent(DestroyEvent{Err: err})
w.destroy()
} }
func (w *x11Window) Event() event.Event { func (w *x11Window) Event() event.Event {
@@ -397,6 +398,9 @@ func (w *x11Window) Event() event.Event {
w.dispatch() w.dispatch()
continue continue
} }
if _, destroy := evt.(DestroyEvent); destroy {
w.destroy()
}
return evt return evt
} }
} }
@@ -414,6 +418,11 @@ func (w *x11Window) Invalidate() {
case w.wakeups <- struct{}{}: case w.wakeups <- struct{}{}:
default: default:
} }
w.invMu.Lock()
defer w.invMu.Unlock()
if w.x == nil {
return
}
if _, err := syscall.Write(w.notify.write, x11OneByte); err != nil && err != syscall.EAGAIN { if _, err := syscall.Write(w.notify.write, x11OneByte); err != nil && err != syscall.EAGAIN {
panic(fmt.Errorf("failed to write to pipe: %v", err)) panic(fmt.Errorf("failed to write to pipe: %v", err))
} }
@@ -455,12 +464,7 @@ func (w *x11Window) dispatch() {
// Check for pending draw events before checking animation or blocking. // Check for pending draw events before checking animation or blocking.
// This fixes an issue on Xephyr where on startup XPending() > 0 but // This fixes an issue on Xephyr where on startup XPending() > 0 but
// poll will still block. This also prevents no-op calls to poll. // poll will still block. This also prevents no-op calls to poll.
syn = w.handler.handleEvents() if syn = w.handler.handleEvents(); !syn {
if w.x == nil {
// handleEvents received a close request and destroyed the window.
return
}
if !syn {
anim = w.animating anim = w.animating
if !anim { if !anim {
// Clear poll events. // Clear poll events.
@@ -472,9 +476,6 @@ func (w *x11Window) dispatch() {
switch { switch {
case *xEvents&syscall.POLLIN != 0: case *xEvents&syscall.POLLIN != 0:
syn = w.handler.handleEvents() syn = w.handler.handleEvents()
if w.x == nil {
return
}
case *xEvents&(syscall.POLLERR|syscall.POLLHUP) != 0: case *xEvents&(syscall.POLLERR|syscall.POLLHUP) != 0:
} }
} }
@@ -502,6 +503,8 @@ func (w *x11Window) dispatch() {
} }
func (w *x11Window) destroy() { func (w *x11Window) destroy() {
w.invMu.Lock()
defer w.invMu.Unlock()
if w.notify.write != 0 { if w.notify.write != 0 {
syscall.Close(w.notify.write) syscall.Close(w.notify.write)
w.notify.write = 0 w.notify.write = 0
@@ -752,7 +755,9 @@ func (h *x11EventHandler) handleEvents() bool {
return redraw return redraw
} }
var x11Threads sync.Once var (
x11Threads sync.Once
)
func init() { func init() {
x11Driver = newX11Window x11Driver = newX11Window
+1 -1
View File
@@ -25,6 +25,6 @@ func runMain() {
// Indirect call, since the linker does not know the address of main when // Indirect call, since the linker does not know the address of main when
// laying down this package. // laying down this package.
fn := mainMain fn := mainMain
fn() go fn()
}) })
} }
+1 -1
View File
@@ -175,7 +175,7 @@ func (c *vkContext) refresh(surf vk.Surface, width, height int) error {
if err != nil { if err != nil {
return err return err
} }
minExt, maxExt := vk.SurfaceCapabilitiesMinExtent(caps), vk.SurfaceCapabilitiesMaxExtent(caps) minExt, maxExt := caps.MinExtent(), caps.MaxExtent()
if width < minExt.X || maxExt.X < width || height < minExt.Y || maxExt.Y < height { if width < minExt.X || maxExt.X < width || height < minExt.Y || maxExt.Y < height {
return errOutOfDate return errOutOfDate
} }
+94 -136
View File
@@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"image" "image"
"image/color" "image/color"
"reflect"
"runtime" "runtime"
"sync" "sync"
"time" "time"
@@ -35,15 +36,14 @@ type Option func(unit.Metric, *Config)
// Window represents an operating system window. // Window represents an operating system window.
// //
// The zero-value Window is useful; the GUI window is created and shown the first // The zero-value Window is useful, and calling any method on
// time the [Event] method is called. On iOS or Android, the first Window represents // it creates and shows a new GUI window. On iOS or Android,
// the window previously created by the platform. // the first Window represents the the window previously
// created by the platform.
// //
// More than one Window is not supported on iOS, Android, WebAssembly. // More than one Window is not supported on iOS, Android,
// WebAssembly.
type Window struct { type Window struct {
initialOpts []Option
initialActions []system.Action
ctx context ctx context
gpu gpu.GPU gpu gpu.GPU
// timer tracks the delayed invalidate goroutine. // timer tracks the delayed invalidate goroutine.
@@ -89,18 +89,13 @@ type Window struct {
} }
imeState editorState imeState editorState
driver driver driver driver
// gpuErr tracks the GPU error that is to be reported when // basic is the driver interface that is needed even after the window is gone.
// the window is closed. basic basicDriver
gpuErr error once sync.Once
// invMu protects mayInvalidate.
invMu sync.Mutex
mayInvalidate bool
// coalesced tracks the most recent events waiting to be delivered // coalesced tracks the most recent events waiting to be delivered
// to the client. // to the client.
coalesced eventSummary coalesced eventSummary
// frame tracks the most recent frame event. // frame tracks the most recently frame event.
lastFrame struct { lastFrame struct {
sync bool sync bool
size image.Point size image.Point
@@ -110,12 +105,11 @@ type Window struct {
} }
type eventSummary struct { type eventSummary struct {
wakeup bool wakeup bool
cfg *ConfigEvent cfg *ConfigEvent
view *ViewEvent view *ViewEvent
frame *frameEvent frame *frameEvent
framePending bool destroy *DestroyEvent
destroy *DestroyEvent
} }
type callbacks struct { type callbacks struct {
@@ -142,11 +136,8 @@ func (w *Window) validateAndProcess(size image.Point, sync bool, frame *op.Ops,
if w.gpu == nil && !w.nocontext { if w.gpu == nil && !w.nocontext {
var err error var err error
if w.ctx == nil { if w.ctx == nil {
if w.ctx, err = w.driver.NewContext(); err != nil { w.ctx, err = w.driver.NewContext()
return err if err != nil {
}
if err = w.ctx.Lock(); err != nil {
w.destroyGPU()
return err return err
} }
sync = true sync = true
@@ -166,6 +157,12 @@ func (w *Window) validateAndProcess(size image.Point, sync bool, frame *op.Ops,
return err return err
} }
} }
if w.ctx != nil {
if err := w.ctx.Lock(); err != nil {
w.destroyGPU()
return err
}
}
if w.gpu == nil && !w.nocontext { if w.gpu == nil && !w.nocontext {
gpu, err := gpu.New(w.ctx.API()) gpu, err := gpu.New(w.ctx.API())
if err != nil { if err != nil {
@@ -197,6 +194,7 @@ func (w *Window) validateAndProcess(size image.Point, sync bool, frame *op.Ops,
var err error var err error
if w.gpu != nil { if w.gpu != nil {
err = w.ctx.Present() err = w.ctx.Present()
w.ctx.Unlock()
} }
return err return err
} }
@@ -218,7 +216,6 @@ func (w *Window) frame(frame *op.Ops, viewport image.Point) error {
} }
func (w *Window) processFrame(frame *op.Ops, ack chan<- struct{}) { func (w *Window) processFrame(frame *op.Ops, ack chan<- struct{}) {
w.coalesced.framePending = false
wrapper := &w.decorations.Ops wrapper := &w.decorations.Ops
off := op.Offset(w.lastFrame.off).Push(wrapper) off := op.Offset(w.lastFrame.off).Push(wrapper)
ops.AddCall(&wrapper.Internal, &frame.Internal, ops.PC{}, ops.PCFor(&frame.Internal)) ops.AddCall(&wrapper.Internal, &frame.Internal, ops.PC{}, ops.PCFor(&frame.Internal))
@@ -226,8 +223,7 @@ func (w *Window) processFrame(frame *op.Ops, ack chan<- struct{}) {
w.lastFrame.deco.Add(wrapper) w.lastFrame.deco.Add(wrapper)
if err := w.validateAndProcess(w.lastFrame.size, w.lastFrame.sync, wrapper, ack); err != nil { if err := w.validateAndProcess(w.lastFrame.size, w.lastFrame.sync, wrapper, ack); err != nil {
w.destroyGPU() w.destroyGPU()
w.gpuErr = err w.driver.ProcessEvent(DestroyEvent{Err: err})
w.driver.Perform(system.ActionClose)
return return
} }
w.updateState() w.updateState()
@@ -277,12 +273,8 @@ func (w *Window) updateState() {
// //
// Invalidate is safe for concurrent use. // Invalidate is safe for concurrent use.
func (w *Window) Invalidate() { func (w *Window) Invalidate() {
w.invMu.Lock() w.init()
defer w.invMu.Unlock() w.basic.Invalidate()
if w.mayInvalidate {
w.mayInvalidate = false
w.driver.Invalidate()
}
} }
// Option applies the options to the window. The options are hints; the platform is // Option applies the options to the window. The options are hints; the platform is
@@ -291,10 +283,7 @@ func (w *Window) Option(opts ...Option) {
if len(opts) == 0 { if len(opts) == 0 {
return return
} }
if w.driver == nil { w.init(opts...)
w.initialOpts = append(w.initialOpts, opts...)
return
}
w.Run(func() { w.Run(func() {
cnf := Config{Decorated: w.decorations.enabled} cnf := Config{Decorated: w.decorations.enabled}
for _, opt := range opts { for _, opt := range opts {
@@ -313,14 +302,16 @@ func (w *Window) Option(opts ...Option) {
} }
// Run f in the same thread as the native window event loop, and wait for f to // Run f in the same thread as the native window event loop, and wait for f to
// return or the window to close. If the window has not yet been created, // return or the window to close. Run is guaranteed not to deadlock if it is
// Run calls f directly. // invoked during the handling of a [ViewEvent], [FrameEvent],
// [StageEvent]; call Run in a separate goroutine to avoid deadlock in all
// other cases.
// //
// Note that most programs should not call Run; configuring a Window with // Note that most programs should not call Run; configuring a Window with
// [CustomRenderer] is a notable exception. // [CustomRenderer] is a notable exception.
func (w *Window) Run(f func()) { func (w *Window) Run(f func()) {
w.init()
if w.driver == nil { if w.driver == nil {
f()
return return
} }
done := make(chan struct{}) done := make(chan struct{})
@@ -386,13 +377,11 @@ func (w *Window) setNextFrame(at time.Time) {
} }
} }
func (c *callbacks) SetDriver(d driver) { func (c *callbacks) SetDriver(d basicDriver) {
if d == nil { c.w.basic = d
panic("nil driver") if d, ok := d.(driver); ok {
c.w.driver = d
} }
c.w.invMu.Lock()
defer c.w.invMu.Unlock()
c.w.driver = d
} }
func (c *callbacks) ProcessFrame(frame *op.Ops, ack chan<- struct{}) { func (c *callbacks) ProcessFrame(frame *op.Ops, ack chan<- struct{}) {
@@ -440,7 +429,10 @@ func (c *callbacks) SetComposingRegion(r key.Range) {
func (c *callbacks) EditorInsert(text string) { func (c *callbacks) EditorInsert(text string) {
sel := c.w.imeState.Selection.Range sel := c.w.imeState.Selection.Range
c.EditorReplace(sel, text) c.EditorReplace(sel, text)
start := min(sel.End, sel.Start) start := sel.Start
if sel.End < start {
start = sel.End
}
sel.Start = start + utf8.RuneCountInString(text) sel.Start = start + utf8.RuneCountInString(text)
sel.End = sel.Start sel.End = sel.Start
c.SetEditorSelection(sel) c.SetEditorSelection(sel)
@@ -556,20 +548,10 @@ func (c *callbacks) Invalidate() {
} }
func (c *callbacks) nextEvent() (event.Event, bool) { func (c *callbacks) nextEvent() (event.Event, bool) {
return c.w.nextEvent() s := &c.w.coalesced
} // Every event counts as a wakeup.
defer func() { s.wakeup = false }()
func (w *Window) nextEvent() (event.Event, bool) {
s := &w.coalesced
defer func() {
// Every event counts as a wakeup.
s.wakeup = false
}()
switch { switch {
case s.framePending:
// If the user didn't call FrameEvent.Event, process
// an empty frame.
w.processFrame(new(op.Ops), nil)
case s.view != nil: case s.view != nil:
e := *s.view e := *s.view
s.view = nil s.view = nil
@@ -586,14 +568,10 @@ func (w *Window) nextEvent() (event.Event, bool) {
case s.frame != nil: case s.frame != nil:
e := *s.frame e := *s.frame
s.frame = nil s.frame = nil
s.framePending = true
return e.FrameEvent, true return e.FrameEvent, true
case s.wakeup: case s.wakeup:
return wakeupEvent{}, true return wakeupEvent{}, true
} }
w.invMu.Lock()
defer w.invMu.Unlock()
w.mayInvalidate = w.driver != nil
return nil, false return nil, false
} }
@@ -636,21 +614,15 @@ func (w *Window) processEvent(e event.Event) bool {
e2.Size = e2.Size.Sub(offset) e2.Size = e2.Size.Sub(offset)
w.coalesced.frame = &e2 w.coalesced.frame = &e2
case DestroyEvent: case DestroyEvent:
if w.gpuErr != nil {
e2.Err = w.gpuErr
}
w.destroyGPU() w.destroyGPU()
w.invMu.Lock()
w.mayInvalidate = false
w.driver = nil w.driver = nil
w.invMu.Unlock()
if q := w.timer.quit; q != nil { if q := w.timer.quit; q != nil {
q <- struct{}{} q <- struct{}{}
<-q <-q
} }
w.coalesced.destroy = &e2 w.coalesced.destroy = &e2
case ViewEvent: case ViewEvent:
if !e2.Valid() && w.gpu != nil { if reflect.ValueOf(e2).IsZero() && w.gpu != nil {
w.ctx.Lock() w.ctx.Lock()
w.gpu.Release() w.gpu.Release()
w.gpu = nil w.gpu = nil
@@ -658,7 +630,6 @@ func (w *Window) processEvent(e event.Event) bool {
} }
w.coalesced.view = &e2 w.coalesced.view = &e2
case ConfigEvent: case ConfigEvent:
w.decorations.Decorations.Maximized = e2.Config.Mode == Maximized
wasFocused := w.decorations.Config.Focused wasFocused := w.decorations.Config.Focused
w.decorations.Config = e2.Config w.decorations.Config = e2.Config
e2.Config = w.effectiveConfig() e2.Config = w.effectiveConfig()
@@ -712,61 +683,55 @@ func (w *Window) processEvent(e event.Event) bool {
} }
// Event blocks until an event is received from the window, such as // Event blocks until an event is received from the window, such as
// [FrameEvent], or until [Invalidate] is called. The window is created // [FrameEvent], or until [Invalidate] is called.
// and shown the first time Event is called. //
// Note: if more than one Window is active, at least one must have
// its Event called from the main goroutine that runs the main
// function. This is necessary because some operating system GUI
// implementations require control of the main thread.
// For this reason, it is allowed to call Event even after a
// DestroyEvent has been received.
func (w *Window) Event() event.Event { func (w *Window) Event() event.Event {
if w.driver == nil { w.init()
w.init() return w.basic.Event()
}
if w.driver == nil {
e, ok := w.nextEvent()
if !ok {
panic("window initialization failed without a DestroyEvent")
}
return e
}
return w.driver.Event()
} }
func (w *Window) init() { func (w *Window) init(initial ...Option) {
debug.Parse() w.once.Do(func() {
// Measure decoration height. debug.Parse()
deco := new(widget.Decorations) // Measure decoration height.
theme := material.NewTheme() deco := new(widget.Decorations)
theme.Shaper = text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Regular())) theme := material.NewTheme()
decoStyle := material.Decorations(theme, deco, 0, "") theme.Shaper = text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Regular()))
gtx := layout.Context{ decoStyle := material.Decorations(theme, deco, 0, "")
Ops: new(op.Ops), gtx := layout.Context{
// Measure in Dp. Ops: new(op.Ops),
Metric: unit.Metric{}, // Measure in Dp.
} Metric: unit.Metric{},
// Allow plenty of space. }
gtx.Constraints.Max.Y = 200 // Allow plenty of space.
dims := decoStyle.Layout(gtx) gtx.Constraints.Max.Y = 200
decoHeight := unit.Dp(dims.Size.Y) dims := decoStyle.Layout(gtx)
defaultOptions := []Option{ decoHeight := unit.Dp(dims.Size.Y)
Size(800, 600), defaultOptions := []Option{
Title("Gio"), Size(800, 600),
Decorated(true), Title("Gio"),
decoHeightOpt(decoHeight), Decorated(true),
} decoHeightOpt(decoHeight),
options := append(defaultOptions, w.initialOpts...) }
w.initialOpts = nil options := append(defaultOptions, initial...)
var cnf Config var cnf Config
cnf.apply(unit.Metric{}, options) cnf.apply(unit.Metric{}, options)
w.nocontext = cnf.CustomRenderer w.nocontext = cnf.CustomRenderer
w.decorations.Theme = theme w.decorations.Theme = theme
w.decorations.Decorations = deco w.decorations.Decorations = deco
w.decorations.enabled = cnf.Decorated w.decorations.enabled = cnf.Decorated
w.decorations.height = decoHeight w.decorations.height = decoHeight
w.imeState.compose = key.Range{Start: -1, End: -1} w.imeState.compose = key.Range{Start: -1, End: -1}
w.semantic.ids = make(map[input.SemanticID]input.SemanticNode) w.semantic.ids = make(map[input.SemanticID]input.SemanticNode)
newWindow(&callbacks{w}, options) newWindow(&callbacks{w}, options)
for _, acts := range w.initialActions { })
w.Perform(acts)
}
w.initialActions = nil
} }
func (w *Window) updateCursor() { func (w *Window) updateCursor() {
@@ -804,6 +769,7 @@ func (w *Window) decorate(e FrameEvent, o *op.Ops) image.Point {
default: default:
panic(fmt.Errorf("unknown WindowMode %v", m)) panic(fmt.Errorf("unknown WindowMode %v", m))
} }
deco.Perform(actions)
gtx := layout.Context{ gtx := layout.Context{
Ops: o, Ops: o,
Now: e.Now, Now: e.Now,
@@ -813,12 +779,8 @@ func (w *Window) decorate(e FrameEvent, o *op.Ops) image.Point {
} }
// Update the window based on the actions on the decorations. // Update the window based on the actions on the decorations.
opts, acts := splitActions(deco.Update(gtx)) opts, acts := splitActions(deco.Update(gtx))
if len(opts) > 0 { w.driver.Configure(opts)
w.driver.Configure(opts) w.driver.Perform(acts)
}
if acts != 0 {
w.driver.Perform(acts)
}
style.Layout(gtx) style.Layout(gtx)
// Offset to place the frame content below the decorations. // Offset to place the frame content below the decorations.
decoHeight := gtx.Dp(w.decorations.Config.decoHeight) decoHeight := gtx.Dp(w.decorations.Config.decoHeight)
@@ -865,10 +827,6 @@ func (w *Window) Perform(actions system.Action) {
if acts == 0 { if acts == 0 {
return return
} }
if w.driver == nil {
w.initialActions = append(w.initialActions, acts)
return
}
w.Run(func() { w.Run(func() {
w.driver.Perform(actions) w.driver.Perform(actions)
}) })
+1 -10
View File
@@ -30,15 +30,6 @@ func NewAffine2D(sx, hx, ox, hy, sy, oy float32) Affine2D {
} }
} }
// AffineId returns an identity transformation matrix that represents no transformation
// when applied.
func AffineId() Affine2D {
return NewAffine2D(
1, 0, 0,
0, 1, 0,
)
}
// Offset the transformation. // Offset the transformation.
func (a Affine2D) Offset(offset Point) Affine2D { func (a Affine2D) Offset(offset Point) Affine2D {
return Affine2D{ return Affine2D{
@@ -123,7 +114,7 @@ func (a Affine2D) Elems() (sx, hx, ox, hy, sy, oy float32) {
// Split a transform into two parts, one which is pure offset and the // Split a transform into two parts, one which is pure offset and the
// other representing the scaling, shearing and rotation part. // other representing the scaling, shearing and rotation part.
func (a Affine2D) Split() (srs Affine2D, offset Point) { func (a *Affine2D) Split() (srs Affine2D, offset Point) {
return Affine2D{ return Affine2D{
a: a.a, b: a.b, c: 0, a: a.a, b: a.b, c: 0,
d: a.d, e: a.e, f: 0, d: a.d, e: a.e, f: 0,
+30 -160
View File
@@ -27,11 +27,11 @@ func TestTransformOffset(t *testing.T) {
p := Point{X: 1, Y: 2} p := Point{X: 1, Y: 2}
o := Point{X: 2, Y: -3} o := Point{X: 2, Y: -3}
r := AffineId().Offset(o).Transform(p) r := Affine2D{}.Offset(o).Transform(p)
if !eq(r, Pt(3, -1)) { if !eq(r, Pt(3, -1)) {
t.Errorf("offset transformation mismatch: have %v, want {3 -1}", r) t.Errorf("offset transformation mismatch: have %v, want {3 -1}", r)
} }
i := AffineId().Offset(o).Invert().Transform(r) i := Affine2D{}.Offset(o).Invert().Transform(r)
if !eq(i, p) { if !eq(i, p) {
t.Errorf("offset transformation inverse mismatch: have %v, want %v", i, p) t.Errorf("offset transformation inverse mismatch: have %v, want %v", i, p)
} }
@@ -51,9 +51,6 @@ func TestString(t *testing.T) {
}, { }, {
in: NewAffine2D(29.142342, 31.4123412, 37.53152, 43.51324213, 47.123412, 53.14312342), in: NewAffine2D(29.142342, 31.4123412, 37.53152, 43.51324213, 47.123412, 53.14312342),
exp: "[[29.1423 31.4123 37.5315] [43.5132 47.1234 53.1431]]", exp: "[[29.1423 31.4123 37.5315] [43.5132 47.1234 53.1431]]",
}, {
in: AffineId(),
exp: "[[1 0 0] [0 1 0]]",
}, },
} }
for _, test := range tests { for _, test := range tests {
@@ -67,11 +64,11 @@ func TestTransformScale(t *testing.T) {
p := Point{X: 1, Y: 2} p := Point{X: 1, Y: 2}
s := Point{X: -1, Y: 2} s := Point{X: -1, Y: 2}
r := AffineId().Scale(Point{}, s).Transform(p) r := Affine2D{}.Scale(Point{}, s).Transform(p)
if !eq(r, Pt(-1, 4)) { if !eq(r, Pt(-1, 4)) {
t.Errorf("scale transformation mismatch: have %v, want {-1 4}", r) t.Errorf("scale transformation mismatch: have %v, want {-1 4}", r)
} }
i := AffineId().Scale(Point{}, s).Invert().Transform(r) i := Affine2D{}.Scale(Point{}, s).Invert().Transform(r)
if !eq(i, p) { if !eq(i, p) {
t.Errorf("scale transformation inverse mismatch: have %v, want %v", i, p) t.Errorf("scale transformation inverse mismatch: have %v, want %v", i, p)
} }
@@ -81,11 +78,11 @@ func TestTransformRotate(t *testing.T) {
p := Point{X: 1, Y: 0} p := Point{X: 1, Y: 0}
a := float32(math.Pi / 2) a := float32(math.Pi / 2)
r := AffineId().Rotate(Point{}, a).Transform(p) r := Affine2D{}.Rotate(Point{}, a).Transform(p)
if !eq(r, Pt(0, 1)) { if !eq(r, Pt(0, 1)) {
t.Errorf("rotate transformation mismatch: have %v, want {0 1}", r) t.Errorf("rotate transformation mismatch: have %v, want {0 1}", r)
} }
i := AffineId().Rotate(Point{}, a).Invert().Transform(r) i := Affine2D{}.Rotate(Point{}, a).Invert().Transform(r)
if !eq(i, p) { if !eq(i, p) {
t.Errorf("rotate transformation inverse mismatch: have %v, want %v", i, p) t.Errorf("rotate transformation inverse mismatch: have %v, want %v", i, p)
} }
@@ -94,11 +91,11 @@ func TestTransformRotate(t *testing.T) {
func TestTransformShear(t *testing.T) { func TestTransformShear(t *testing.T) {
p := Point{X: 1, Y: 1} p := Point{X: 1, Y: 1}
r := AffineId().Shear(Point{}, math.Pi/4, 0).Transform(p) r := Affine2D{}.Shear(Point{}, math.Pi/4, 0).Transform(p)
if !eq(r, Pt(2, 1)) { if !eq(r, Pt(2, 1)) {
t.Errorf("shear transformation mismatch: have %v, want {2 1}", r) t.Errorf("shear transformation mismatch: have %v, want {2 1}", r)
} }
i := AffineId().Shear(Point{}, math.Pi/4, 0).Invert().Transform(r) i := Affine2D{}.Shear(Point{}, math.Pi/4, 0).Invert().Transform(r)
if !eq(i, p) { if !eq(i, p) {
t.Errorf("shear transformation inverse mismatch: have %v, want %v", i, p) t.Errorf("shear transformation inverse mismatch: have %v, want %v", i, p)
} }
@@ -110,11 +107,11 @@ func TestTransformMultiply(t *testing.T) {
s := Point{X: -1, Y: 2} s := Point{X: -1, Y: 2}
a := float32(-math.Pi / 2) a := float32(-math.Pi / 2)
r := AffineId().Offset(o).Scale(Point{}, s).Rotate(Point{}, a).Shear(Point{}, math.Pi/4, 0).Transform(p) r := Affine2D{}.Offset(o).Scale(Point{}, s).Rotate(Point{}, a).Shear(Point{}, math.Pi/4, 0).Transform(p)
if !eq(r, Pt(1, 3)) { if !eq(r, Pt(1, 3)) {
t.Errorf("complex transformation mismatch: have %v, want {1 3}", r) t.Errorf("complex transformation mismatch: have %v, want {1 3}", r)
} }
i := AffineId().Offset(o).Scale(Point{}, s).Rotate(Point{}, a).Shear(Point{}, math.Pi/4, 0).Invert().Transform(r) i := Affine2D{}.Offset(o).Scale(Point{}, s).Rotate(Point{}, a).Shear(Point{}, math.Pi/4, 0).Invert().Transform(r)
if !eq(i, p) { if !eq(i, p) {
t.Errorf("complex transformation inverse mismatch: have %v, want %v", i, p) t.Errorf("complex transformation inverse mismatch: have %v, want %v", i, p)
} }
@@ -166,7 +163,7 @@ func TestPrimes(t *testing.T) {
func TestTransformScaleAround(t *testing.T) { func TestTransformScaleAround(t *testing.T) {
p := Pt(-1, -1) p := Pt(-1, -1)
target := Pt(-6, -13) target := Pt(-6, -13)
pt := AffineId().Scale(Pt(4, 5), Pt(2, 3)).Transform(p) pt := Affine2D{}.Scale(Pt(4, 5), Pt(2, 3)).Transform(p)
if !eq(pt, target) { if !eq(pt, target) {
t.Log(pt, "!=", target) t.Log(pt, "!=", target)
t.Error("Scale not as expected") t.Error("Scale not as expected")
@@ -175,7 +172,7 @@ func TestTransformScaleAround(t *testing.T) {
func TestTransformRotateAround(t *testing.T) { func TestTransformRotateAround(t *testing.T) {
p := Pt(-1, -1) p := Pt(-1, -1)
pt := AffineId().Rotate(Pt(1, 1), -math.Pi/2).Transform(p) pt := Affine2D{}.Rotate(Pt(1, 1), -math.Pi/2).Transform(p)
target := Pt(-1, 3) target := Pt(-1, 3)
if !eq(pt, target) { if !eq(pt, target) {
t.Log(pt, "!=", target) t.Log(pt, "!=", target)
@@ -184,12 +181,12 @@ func TestTransformRotateAround(t *testing.T) {
} }
func TestMulOrder(t *testing.T) { func TestMulOrder(t *testing.T) {
A := AffineId().Offset(Pt(100, 100)) A := Affine2D{}.Offset(Pt(100, 100))
B := AffineId().Scale(Point{}, Pt(2, 2)) B := Affine2D{}.Scale(Point{}, Pt(2, 2))
_ = A _ = A
_ = B _ = B
T1 := AffineId().Offset(Pt(100, 100)).Scale(Point{}, Pt(2, 2)) T1 := Affine2D{}.Offset(Pt(100, 100)).Scale(Point{}, Pt(2, 2))
T2 := B.Mul(A) T2 := B.Mul(A)
if T1 != T2 { if T1 != T2 {
@@ -202,9 +199,9 @@ func TestMulOrder(t *testing.T) {
func BenchmarkTransformOffset(b *testing.B) { func BenchmarkTransformOffset(b *testing.B) {
p := Point{X: 1, Y: 2} p := Point{X: 1, Y: 2}
o := Point{X: 0.5, Y: 0.5} o := Point{X: 0.5, Y: 0.5}
aff := AffineId().Offset(o) aff := Affine2D{}.Offset(o)
for b.Loop() { for i := 0; i < b.N; i++ {
p = aff.Transform(p) p = aff.Transform(p)
} }
_ = p _ = p
@@ -213,8 +210,8 @@ func BenchmarkTransformOffset(b *testing.B) {
func BenchmarkTransformScale(b *testing.B) { func BenchmarkTransformScale(b *testing.B) {
p := Point{X: 1, Y: 2} p := Point{X: 1, Y: 2}
s := Point{X: 0.5, Y: 0.5} s := Point{X: 0.5, Y: 0.5}
aff := AffineId().Scale(Point{}, s) aff := Affine2D{}.Scale(Point{}, s)
for b.Loop() { for i := 0; i < b.N; i++ {
p = aff.Transform(p) p = aff.Transform(p)
} }
_ = p _ = p
@@ -223,163 +220,36 @@ func BenchmarkTransformScale(b *testing.B) {
func BenchmarkTransformRotate(b *testing.B) { func BenchmarkTransformRotate(b *testing.B) {
p := Point{X: 1, Y: 2} p := Point{X: 1, Y: 2}
a := float32(math.Pi / 2) a := float32(math.Pi / 2)
aff := AffineId().Rotate(Point{}, a) aff := Affine2D{}.Rotate(Point{}, a)
for b.Loop() { for i := 0; i < b.N; i++ {
p = aff.Transform(p) p = aff.Transform(p)
} }
_ = p _ = p
} }
func BenchmarkTransformTranslateMultiply(b *testing.B) { func BenchmarkTransformTranslateMultiply(b *testing.B) {
a := AffineId().Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3) a := Affine2D{}.Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
t := AffineId().Offset(Point{X: 0.5, Y: 0.5}) t := Affine2D{}.Offset(Point{X: 0.5, Y: 0.5})
for b.Loop() { for i := 0; i < b.N; i++ {
a = a.Mul(t) a = a.Mul(t)
} }
} }
func BenchmarkTransformScaleMultiply(b *testing.B) { func BenchmarkTransformScaleMultiply(b *testing.B) {
a := AffineId().Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3) a := Affine2D{}.Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
t := AffineId().Offset(Point{X: 0.5, Y: 0.5}).Scale(Point{}, Point{X: 0.4, Y: -0.5}) t := Affine2D{}.Offset(Point{X: 0.5, Y: 0.5}).Scale(Point{}, Point{X: 0.4, Y: -0.5})
for b.Loop() { for i := 0; i < b.N; i++ {
a = a.Mul(t) a = a.Mul(t)
} }
} }
func BenchmarkTransformMultiply(b *testing.B) { func BenchmarkTransformMultiply(b *testing.B) {
a := AffineId().Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3) a := Affine2D{}.Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
t := AffineId().Offset(Point{X: 0.5, Y: 0.5}).Rotate(Point{}, math.Pi/7) t := Affine2D{}.Offset(Point{X: 0.5, Y: 0.5}).Rotate(Point{}, math.Pi/7)
for b.Loop() { for i := 0; i < b.N; i++ {
a = a.Mul(t) a = a.Mul(t)
} }
} }
func TestNewAffine2D(t *testing.T) {
tests := []struct {
sx, hx, ox, hy, sy, oy float32
expected Affine2D
}{
{1, 0, 0, 0, 1, 0, AffineId()},
{2, 0, 5, 0, 3, 7, Affine2D{a: 1, b: 0, c: 5, d: 0, e: 2, f: 7}},
{-1, 2, 3, 4, -5, 6, Affine2D{a: -2, b: 2, c: 3, d: 4, e: -6, f: 6}},
}
for i, test := range tests {
got := NewAffine2D(test.sx, test.hx, test.ox, test.hy, test.sy, test.oy)
if !eqaff(got, test.expected) {
t.Errorf(
"Test %d: NewAffine2D(%v, %v, %v, %v, %v, %v) = %v, want %v",
i, test.sx, test.hx, test.ox, test.hy, test.sy, test.oy, got, test.expected,
)
}
}
}
func TestAffineId(t *testing.T) {
id := AffineId()
testPoints := []Point{
{0, 0},
{1, 0},
{0, 1},
{-1, -1},
{10, 20},
}
for _, p := range testPoints {
transformed := id.Transform(p)
if !eq(transformed, p) {
t.Errorf("Identity transform changed point: %v -> %v", p, transformed)
}
}
}
func TestElems(t *testing.T) {
tests := []struct {
aff Affine2D
sx, hx, ox, hy, sy, oy float32
}{
{AffineId(), 1, 0, 0, 0, 1, 0},
{Affine2D{a: 1, b: 2, c: 3, d: 4, e: 5, f: 6}, 2, 2, 3, 4, 6, 6},
{NewAffine2D(7, 8, 9, 10, 11, 12), 7, 8, 9, 10, 11, 12},
}
for i, test := range tests {
sx, hx, ox, hy, sy, oy := test.aff.Elems()
if sx != test.sx || hx != test.hx || ox != test.ox ||
hy != test.hy || sy != test.sy || oy != test.oy {
t.Errorf(
"Test %d: %v.Elems() = (%v, %v, %v, %v, %v, %v), want (%v, %v, %v, %v, %v, %v)",
i, test.aff, sx, hx, ox, hy, sy, oy, test.sx, test.hx, test.ox, test.hy, test.sy, test.oy,
)
}
}
}
func TestSplit(t *testing.T) {
tests := []struct {
aff Affine2D
expectedSRS Affine2D
expectedOffset Point
}{
{
AffineId(),
AffineId(),
Point{0, 0},
},
{
Affine2D{a: 1, b: 2, c: 3, d: 4, e: 5, f: 6},
Affine2D{a: 1, b: 2, c: 0, d: 4, e: 5, f: 0},
Point{3, 6},
},
{
NewAffine2D(2, 0, 10, 0, 3, 20),
NewAffine2D(2, 0, 0, 0, 3, 0),
Point{10, 20},
},
}
for i, test := range tests {
srs, offset := test.aff.Split()
if !eqaff(srs, test.expectedSRS) || !eq(offset, test.expectedOffset) {
t.Errorf(
"Test %d: %v.Split() = (%v, %v), want (%v, %v)",
i, test.aff, srs, offset, test.expectedSRS, test.expectedOffset,
)
}
}
}
func TestShear(t *testing.T) {
p := Pt(2, 3)
origin := Pt(1, 1)
shearX := AffineId().Shear(origin, math.Pi/4, 0)
resultX := shearX.Transform(p)
expectedX := Pt(4, 3)
if !eq(resultX, expectedX) {
t.Errorf("Shear around origin in X: got %v, want %v", resultX, expectedX)
}
inverseX := shearX.Invert().Transform(resultX)
if !eq(inverseX, p) {
t.Errorf("Inverse shear X: got %v, want %v", inverseX, p)
}
shearY := AffineId().Shear(origin, 0, math.Pi/4)
resultY := shearY.Transform(p)
expectedY := Pt(2, 4)
if !eq(resultY, expectedY) {
t.Errorf("Shear around origin in Y: got %v, want %v", resultY, expectedY)
}
inverseY := shearY.Invert().Transform(resultY)
if !eq(inverseY, p) {
t.Errorf("Inverse shear Y: got %v, want %v", inverseY, p)
}
}
Generated
+76 -17
View File
@@ -1,25 +1,87 @@
{ {
"nodes": { "nodes": {
"android": {
"inputs": {
"devshell": "devshell",
"flake-utils": "flake-utils",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1701721028,
"narHash": "sha256-2z4YrdHPLoMZNWR1MPOjNZMqPg057i1eZXaYI6RTahQ=",
"owner": "tadfisher",
"repo": "android-nixpkgs",
"rev": "c923f9ec0f4dd0d7dc725dc5b73fbf03658e50dd",
"type": "github"
},
"original": {
"owner": "tadfisher",
"repo": "android-nixpkgs",
"type": "github"
}
},
"devshell": {
"inputs": {
"nixpkgs": [
"android",
"nixpkgs"
],
"systems": "systems"
},
"locked": {
"lastModified": 1701697687,
"narHash": "sha256-dLLE5wQBVv+pIb4bWmKFSw2DvLVyuEk0F7ng6hpZPSU=",
"owner": "numtide",
"repo": "devshell",
"rev": "c3bd77911391eb1638af6ce773de86da57ee6df5",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "devshell",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1747953325, "lastModified": 1701282334,
"narHash": "sha256-y2ZtlIlNTuVJUZCqzZAhIw5rrKP4DOSklev6c8PyCkQ=", "narHash": "sha256-MxCVrXY6v4QmfTwIysjjaX0XUhqBbxTWWB4HXtDYsdk=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "55d1f923c480dadce40f5231feb472e81b0bab48", "rev": "057f9aecfb71c4437d2b27d3323df7f93c010b7e",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixos-25.05", "ref": "23.11",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
}, },
"root": { "root": {
"inputs": { "inputs": {
"nixpkgs": "nixpkgs", "android": "android",
"utils": "utils" "nixpkgs": "nixpkgs"
} }
}, },
"systems": { "systems": {
@@ -37,21 +99,18 @@
"type": "github" "type": "github"
} }
}, },
"utils": { "systems_2": {
"inputs": {
"systems": "systems"
},
"locked": { "locked": {
"lastModified": 1731533236, "lastModified": 1681028828,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "numtide", "owner": "nix-systems",
"repo": "flake-utils", "repo": "default",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "numtide", "owner": "nix-systems",
"repo": "flake-utils", "repo": "default",
"type": "github" "type": "github"
} }
} }
+46 -37
View File
@@ -3,38 +3,42 @@
description = "Gio build environment"; description = "Gio build environment";
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05"; nixpkgs.url = "github:NixOS/nixpkgs/23.11";
utils.url = "github:numtide/flake-utils"; android.url = "github:tadfisher/android-nixpkgs";
android.inputs.nixpkgs.follows = "nixpkgs";
}; };
outputs = { self, nixpkgs, utils }: outputs = { self, nixpkgs, android }:
utils.lib.eachDefaultSystem (system: let
let supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-darwin" ];
pkgs = import nixpkgs { forAllSystems = f: nixpkgs.lib.genAttrs supportedSystems (system: f system);
inherit system; in
{
# allow unfree Android packages. devShells = forAllSystems
config.allowUnfree = true; (system:
# accept the Android SDK license. let
config.android_sdk.accept_license = true; pkgs = import nixpkgs {
}; inherit system;
in {
devShells = let
android-sdk = let
androidComposition = pkgs.androidenv.composeAndroidPackages {
platformVersions = [ "latest" ];
abiVersions = [ "armeabi-v7a" "arm64-v8a" ];
# Omit the deprecated tools package.
toolsVersion = null;
includeNDK = true;
}; };
in androidComposition.androidsdk; android-sdk = android.sdk.${system} (sdkPkgs: with sdkPkgs;
in { [
default = with pkgs; build-tools-31-0-0
mkShell (rec { cmdline-tools-latest
ANDROID_HOME = "${android-sdk}/libexec/android-sdk"; platform-tools
packages = [ android-sdk jdk clang ] platforms-android-31
++ (if stdenv.isLinux then [ ndk-bundle
]);
in
{
default = with pkgs; mkShell
({
ANDROID_SDK_ROOT = "${android-sdk}/share/android-sdk";
JAVA_HOME = jdk17.home;
packages = [
android-sdk
jdk17
clang
] ++ (if stdenv.isLinux then [
vulkan-headers vulkan-headers
libxkbcommon libxkbcommon
wayland wayland
@@ -43,12 +47,17 @@
xorg.libXfixes xorg.libXfixes
libGL libGL
pkg-config pkg-config
] else ] else if stdenv.isDarwin then [
[ ]); darwin.apple_sdk_11_0.frameworks.Foundation
} // (if stdenv.isLinux then { darwin.apple_sdk_11_0.frameworks.Metal
LD_LIBRARY_PATH = "${vulkan-loader}/lib"; darwin.apple_sdk_11_0.frameworks.QuartzCore
} else darwin.apple_sdk_11_0.frameworks.AppKit
{ })); darwin.apple_sdk_11_0.MacOSX-SDK
}; ] else [ ]);
}); } // (if stdenv.isLinux then {
LD_LIBRARY_PATH = "${vulkan-loader}/lib";
} else { }));
}
);
};
} }
+1 -1
View File
@@ -34,7 +34,7 @@ type Font struct {
// Face is an opaque handle to a typeface. The concrete implementation depends // Face is an opaque handle to a typeface. The concrete implementation depends
// upon the kind of font and shaper in use. // upon the kind of font and shaper in use.
type Face interface { type Face interface {
Face() *font.Face Face() font.Face
} }
// Typeface identifies a list of font families to attempt to use for displaying // Typeface identifies a list of font families to attempt to use for displaying
+43 -41
View File
@@ -16,21 +16,23 @@ import (
_ "image/png" _ "image/png"
giofont "gioui.org/font" giofont "gioui.org/font"
fontapi "github.com/go-text/typesetting/font" "github.com/go-text/typesetting/font"
"github.com/go-text/typesetting/font/opentype" fontapi "github.com/go-text/typesetting/opentype/api/font"
"github.com/go-text/typesetting/opentype/api/metadata"
"github.com/go-text/typesetting/opentype/loader"
) )
// Face is a thread-safe representation of a loaded font. For efficiency, applications // Face is a thread-safe representation of a loaded font. For efficiency, applications
// should construct a face for any given font file once, reusing it across different // should construct a face for any given font file once, reusing it across different
// text shapers. // text shapers.
type Face struct { type Face struct {
face *fontapi.Font face font.Font
font giofont.Font font giofont.Font
} }
// Parse constructs a Face from source bytes. // Parse constructs a Face from source bytes.
func Parse(src []byte) (Face, error) { func Parse(src []byte) (Face, error) {
ld, err := opentype.NewLoader(bytes.NewReader(src)) ld, err := loader.NewLoader(bytes.NewReader(src))
if err != nil { if err != nil {
return Face{}, err return Face{}, err
} }
@@ -47,11 +49,11 @@ func Parse(src []byte) (Face, error) {
// ParseCollection parse an Opentype font file, with support for collections. // ParseCollection parse an Opentype font file, with support for collections.
// Single font files are supported, returning a slice with length 1. // Single font files are supported, returning a slice with length 1.
// The returned fonts are automatically wrapped in a text.FontFace with // The returned fonts are automatically wrapped in a text.FontFace with
// inferred font font. // inferred font metadata.
// BUG(whereswaldon): the only Variant that can be detected automatically is // BUG(whereswaldon): the only Variant that can be detected automatically is
// "Mono". // "Mono".
func ParseCollection(src []byte) ([]giofont.FontFace, error) { func ParseCollection(src []byte) ([]giofont.FontFace, error) {
lds, err := opentype.NewLoaders(bytes.NewReader(src)) lds, err := loader.NewLoaders(bytes.NewReader(src))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -74,7 +76,7 @@ func ParseCollection(src []byte) ([]giofont.FontFace, error) {
return out, nil return out, nil
} }
func DescriptionToFont(md fontapi.Description) giofont.Font { func DescriptionToFont(md metadata.Description) giofont.Font {
return giofont.Font{ return giofont.Font{
Typeface: giofont.Typeface(md.Family), Typeface: giofont.Typeface(md.Family),
Style: gioStyle(md.Aspect.Style), Style: gioStyle(md.Aspect.Style),
@@ -82,30 +84,30 @@ func DescriptionToFont(md fontapi.Description) giofont.Font {
} }
} }
func FontToDescription(font giofont.Font) fontapi.Description { func FontToDescription(font giofont.Font) metadata.Description {
return fontapi.Description{ return metadata.Description{
Family: string(font.Typeface), Family: string(font.Typeface),
Aspect: fontapi.Aspect{ Aspect: metadata.Aspect{
Style: mdStyle(font.Style), Style: mdStyle(font.Style),
Weight: mdWeight(font.Weight), Weight: mdWeight(font.Weight),
}, },
} }
} }
// parseLoader parses the contents of the loader into a face and its font. // parseLoader parses the contents of the loader into a face and its metadata.
func parseLoader(ld *opentype.Loader) (*fontapi.Font, giofont.Font, error) { func parseLoader(ld *loader.Loader) (font.Font, giofont.Font, error) {
ft, err := fontapi.NewFont(ld) ft, err := fontapi.NewFont(ld)
if err != nil { if err != nil {
return nil, giofont.Font{}, err return nil, giofont.Font{}, err
} }
data := DescriptionToFont(ft.Describe()) data := DescriptionToFont(metadata.Metadata(ld))
return ft, data, nil return ft, data, nil
} }
// Face returns a thread-unsafe wrapper for this Face suitable for use by a single shaper. // Face returns a thread-unsafe wrapper for this Face suitable for use by a single shaper.
// Face many be invoked any number of times and is safe so long as each return value is // Face many be invoked any number of times and is safe so long as each return value is
// only used by one goroutine. // only used by one goroutine.
func (f Face) Face() *fontapi.Face { func (f Face) Face() font.Face {
return &fontapi.Face{Font: f.face} return &fontapi.Face{Font: f.face}
} }
@@ -117,74 +119,74 @@ func (f Face) Font() giofont.Font {
return f.font return f.font
} }
func gioStyle(s fontapi.Style) giofont.Style { func gioStyle(s metadata.Style) giofont.Style {
switch s { switch s {
case fontapi.StyleItalic: case metadata.StyleItalic:
return giofont.Italic return giofont.Italic
case fontapi.StyleNormal: case metadata.StyleNormal:
fallthrough fallthrough
default: default:
return giofont.Regular return giofont.Regular
} }
} }
func mdStyle(g giofont.Style) fontapi.Style { func mdStyle(g giofont.Style) metadata.Style {
switch g { switch g {
case giofont.Italic: case giofont.Italic:
return fontapi.StyleItalic return metadata.StyleItalic
case giofont.Regular: case giofont.Regular:
fallthrough fallthrough
default: default:
return fontapi.StyleNormal return metadata.StyleNormal
} }
} }
func gioWeight(w fontapi.Weight) giofont.Weight { func gioWeight(w metadata.Weight) giofont.Weight {
switch w { switch w {
case fontapi.WeightThin: case metadata.WeightThin:
return giofont.Thin return giofont.Thin
case fontapi.WeightExtraLight: case metadata.WeightExtraLight:
return giofont.ExtraLight return giofont.ExtraLight
case fontapi.WeightLight: case metadata.WeightLight:
return giofont.Light return giofont.Light
case fontapi.WeightNormal: case metadata.WeightNormal:
return giofont.Normal return giofont.Normal
case fontapi.WeightMedium: case metadata.WeightMedium:
return giofont.Medium return giofont.Medium
case fontapi.WeightSemibold: case metadata.WeightSemibold:
return giofont.SemiBold return giofont.SemiBold
case fontapi.WeightBold: case metadata.WeightBold:
return giofont.Bold return giofont.Bold
case fontapi.WeightExtraBold: case metadata.WeightExtraBold:
return giofont.ExtraBold return giofont.ExtraBold
case fontapi.WeightBlack: case metadata.WeightBlack:
return giofont.Black return giofont.Black
default: default:
return giofont.Normal return giofont.Normal
} }
} }
func mdWeight(g giofont.Weight) fontapi.Weight { func mdWeight(g giofont.Weight) metadata.Weight {
switch g { switch g {
case giofont.Thin: case giofont.Thin:
return fontapi.WeightThin return metadata.WeightThin
case giofont.ExtraLight: case giofont.ExtraLight:
return fontapi.WeightExtraLight return metadata.WeightExtraLight
case giofont.Light: case giofont.Light:
return fontapi.WeightLight return metadata.WeightLight
case giofont.Normal: case giofont.Normal:
return fontapi.WeightNormal return metadata.WeightNormal
case giofont.Medium: case giofont.Medium:
return fontapi.WeightMedium return metadata.WeightMedium
case giofont.SemiBold: case giofont.SemiBold:
return fontapi.WeightSemibold return metadata.WeightSemibold
case giofont.Bold: case giofont.Bold:
return fontapi.WeightBold return metadata.WeightBold
case giofont.ExtraBold: case giofont.ExtraBold:
return fontapi.WeightExtraBold return metadata.WeightExtraBold
case giofont.Black: case giofont.Black:
return fontapi.WeightBlack return metadata.WeightBlack
default: default:
return fontapi.WeightNormal return metadata.WeightNormal
} }
} }
+6 -14
View File
@@ -271,13 +271,12 @@ func (s *Scroll) Stop() {
} }
// Update state and report the scroll distance along axis. // Update state and report the scroll distance along axis.
func (s *Scroll) Update(cfg unit.Metric, q input.Source, t time.Time, axis Axis, scrollx, scrolly pointer.ScrollRange) int { func (s *Scroll) Update(cfg unit.Metric, q input.Source, t time.Time, axis Axis, bounds image.Rectangle) int {
total := 0 total := 0
f := pointer.Filter{ f := pointer.Filter{
Target: s, Target: s,
Kinds: pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll | pointer.Cancel, Kinds: pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll | pointer.Cancel,
ScrollX: scrollx, ScrollBounds: bounds,
ScrollY: scrolly,
} }
for { for {
evt, ok := q.Event(f) evt, ok := q.Event(f)
@@ -322,8 +321,6 @@ func (s *Scroll) Update(cfg unit.Metric, q input.Source, t time.Time, axis Axis,
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)
@@ -355,15 +352,10 @@ func (s *Scroll) Update(cfg unit.Metric, q input.Source, t time.Time, axis Axis,
} }
func (s *Scroll) val(axis Axis, p f32.Point) float32 { func (s *Scroll) val(axis Axis, p f32.Point) float32 {
switch axis { if axis == Horizontal {
case Horizontal:
return p.X return p.X
case Vertical: } else {
return p.Y return p.Y
case Both:
return p.X + p.Y
default:
return 0
} }
} }
+9 -7
View File
@@ -1,14 +1,16 @@
module gioui.org module gioui.org
go 1.23.8 go 1.19
require ( require (
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2
gioui.org/shader v1.0.8 gioui.org/shader v1.0.8
github.com/go-text/typesetting v0.3.0 github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 golang.org/x/exp v0.0.0-20221012211006-4de253d81b95
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91
golang.org/x/image v0.26.0 golang.org/x/image v0.5.0
golang.org/x/sys v0.33.0 golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64
golang.org/x/text v0.24.0
) )
require golang.org/x/text v0.7.0
+38 -14
View File
@@ -1,19 +1,43 @@
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY= eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA= eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc=
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA= gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA=
gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM= gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
github.com/go-text/typesetting v0.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML4iF4= github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 h1:FQivqchis6bE2/9uF70M2gmmLpe82esEm2QadL0TEJo=
github.com/go-text/typesetting v0.3.0/go.mod h1:qjZLkhRgOEYMhU9eHBr3AR4sfnGJvOXNLt8yRAySFuY= github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k=
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0= github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI=
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 h1:tMSqXTK+AQdW3LpCbfatHSRPHeW6+2WuxaVQuHftn80= golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 h1:sBdrWpxhGDdTAYNqbgBLAR+ULAPPhfgncLr1X0lyWtg=
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8= golang.org/x/exp v0.0.0-20221012211006-4de253d81b95/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY= golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 h1:ryT6Nf0R83ZgD8WnFFdfI8wCeyqgdXWN4+CkFVNPAT0=
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c= golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+3 -2
View File
@@ -4,7 +4,8 @@ package gpu
import ( import (
"fmt" "fmt"
"image"
"gioui.org/internal/f32"
) )
type textureCacheKey struct { type textureCacheKey struct {
@@ -35,7 +36,7 @@ type opCache struct {
type opCacheValue struct { type opCacheValue struct {
data pathData data pathData
bounds image.Rectangle bounds f32.Rectangle
// the fields below are handled by opCache // the fields below are handled by opCache
key opKey key opKey
keep bool keep bool
+6 -6
View File
@@ -19,10 +19,10 @@ type quadSplitter struct {
func encodeQuadTo(data []byte, meta uint32, from, ctrl, to f32.Point) { func encodeQuadTo(data []byte, meta uint32, from, ctrl, to f32.Point) {
// inlined code: // inlined code:
// encodeVertex(data, meta, 1, -1, from, ctrl, to) // encodeVertex(data, meta, -1, 1, from, ctrl, to)
// encodeVertex(data[vertStride:], meta, 1, 1, from, ctrl, to) // encodeVertex(data[vertStride:], meta, 1, 1, from, ctrl, to)
// encodeVertex(data[vertStride*2:], meta, -1, -1, from, ctrl, to) // encodeVertex(data[vertStride*2:], meta, -1, -1, from, ctrl, to)
// encodeVertex(data[vertStride*3:], meta, -1, 1, from, ctrl, to) // encodeVertex(data[vertStride*3:], meta, 1, -1, from, ctrl, to)
// this code needs to stay in sync with `vertex.encode`. // this code needs to stay in sync with `vertex.encode`.
bo := binary.LittleEndian bo := binary.LittleEndian
@@ -48,10 +48,10 @@ func encodeQuadTo(data []byte, meta uint32, from, ctrl, to f32.Point) {
} }
const ( const (
nwCorner = 1*0.5 + 0*0.25 nwCorner = 1*0.25 + 0*0.5
neCorner = 1*0.5 + 1*0.25 neCorner = 1*0.25 + 1*0.5
swCorner = 0*0.5 + 0*0.25 swCorner = 0*0.25 + 0*0.5
seCorner = 0*0.5 + 1*0.25 seCorner = 0*0.25 + 1*0.5
) )
func encodeVertex(data []byte, meta uint32, cornerx, cornery int16, from, ctrl, to f32.Point) { func encodeVertex(data []byte, meta uint32, cornerx, cornery int16, from, ctrl, to f32.Point) {
+1 -1
View File
@@ -10,7 +10,7 @@ import (
func BenchmarkEncodeQuadTo(b *testing.B) { func BenchmarkEncodeQuadTo(b *testing.B) {
var data [vertStride * 4]byte var data [vertStride * 4]byte
for i := 0; b.Loop(); i++ { for i := 0; i < b.N; i++ {
v := float32(i) v := float32(i)
encodeQuadTo(data[:], 123, encodeQuadTo(data[:], 123,
f32.Point{X: v, Y: v}, f32.Point{X: v, Y: v},
+2193
View File
File diff suppressed because it is too large Load Diff
+129
View File
@@ -0,0 +1,129 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
import (
"unsafe"
"gioui.org/cpu"
)
// This file contains code specific to running compute shaders on the CPU.
// dispatcher dispatches CPU compute programs across multiple goroutines.
type dispatcher struct {
// done is notified when a worker completes its work slice.
done chan struct{}
// work receives work slice indices. It is closed when the dispatcher is released.
work chan work
// dispatch receives compute jobs, which is then split among workers.
dispatch chan dispatch
// sync receives notification when a Sync completes.
sync chan struct{}
}
type work struct {
ctx *cpu.DispatchContext
index int
}
type dispatch struct {
_type jobType
program *cpu.ProgramInfo
descSet unsafe.Pointer
x, y, z int
}
type jobType uint8
const (
jobDispatch jobType = iota
jobBarrier
jobSync
)
func newDispatcher(workers int) *dispatcher {
d := &dispatcher{
work: make(chan work, workers),
done: make(chan struct{}, workers),
// Leave some room to avoid blocking calls to Dispatch.
dispatch: make(chan dispatch, 20),
sync: make(chan struct{}),
}
for i := 0; i < workers; i++ {
go d.worker()
}
go d.dispatcher()
return d
}
func (d *dispatcher) dispatcher() {
defer close(d.work)
var free []*cpu.DispatchContext
defer func() {
for _, ctx := range free {
ctx.Free()
}
}()
var used []*cpu.DispatchContext
for job := range d.dispatch {
switch job._type {
case jobDispatch:
if len(free) == 0 {
free = append(free, cpu.NewDispatchContext())
}
ctx := free[len(free)-1]
free = free[:len(free)-1]
used = append(used, ctx)
ctx.Prepare(cap(d.work), job.program, job.descSet, job.x, job.y, job.z)
for i := 0; i < cap(d.work); i++ {
d.work <- work{
ctx: ctx,
index: i,
}
}
case jobBarrier:
// Wait for all outstanding dispatches to complete.
for i := 0; i < len(used)*cap(d.work); i++ {
<-d.done
}
free = append(free, used...)
used = used[:0]
case jobSync:
d.sync <- struct{}{}
}
}
}
func (d *dispatcher) worker() {
thread := cpu.NewThreadContext()
defer thread.Free()
for w := range d.work {
w.ctx.Dispatch(w.index, thread)
d.done <- struct{}{}
}
}
func (d *dispatcher) Barrier() {
d.dispatch <- dispatch{_type: jobBarrier}
}
func (d *dispatcher) Sync() {
d.dispatch <- dispatch{_type: jobSync}
<-d.sync
}
func (d *dispatcher) Dispatch(program *cpu.ProgramInfo, descSet unsafe.Pointer, x, y, z int) {
d.dispatch <- dispatch{
_type: jobDispatch,
program: program,
descSet: descSet,
x: x,
y: y,
z: z,
}
}
func (d *dispatcher) Stop() {
close(d.dispatch)
}
+168 -200
View File
@@ -9,13 +9,12 @@ package gpu
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"image" "image"
"image/color" "image/color"
"math" "math"
"os"
"reflect" "reflect"
"slices"
"time" "time"
"unsafe" "unsafe"
@@ -118,17 +117,17 @@ type drawState struct {
} }
type pathOp struct { type pathOp struct {
off image.Point off f32.Point
// rect tracks whether the clip stack can be represented by a // rect tracks whether the clip stack can be represented by a
// pixel-aligned rectangle. // pixel-aligned rectangle.
rect bool rect bool
// clip is the union of all // clip is the union of all
// later clip rectangles. // later clip rectangles.
clip image.Rectangle clip image.Rectangle
bounds image.Rectangle bounds f32.Rectangle
// intersect is the intersection of bounds and all // intersect is the intersection of bounds and all
// previous clip bounds. // previous clip bounds.
intersect image.Rectangle intersect f32.Rectangle
pathKey opKey pathKey opKey
path bool path bool
pathVerts []byte pathVerts []byte
@@ -190,7 +189,7 @@ const (
// imageOpData is the shadow of paint.ImageOp. // imageOpData is the shadow of paint.ImageOp.
type imageOpData struct { type imageOpData struct {
src *image.RGBA src *image.RGBA
handle any handle interface{}
filter byte filter byte
} }
@@ -201,7 +200,7 @@ type linearGradientOpData struct {
color2 color.NRGBA color2 color.NRGBA
} }
func decodeImageOp(data []byte, refs []any) imageOpData { func decodeImageOp(data []byte, refs []interface{}) imageOpData {
handle := refs[1] handle := refs[1]
if handle == nil { if handle == nil {
return imageOpData{} return imageOpData{}
@@ -262,7 +261,7 @@ type texture struct {
type blitter struct { type blitter struct {
ctx driver.Device ctx driver.Device
viewport image.Point viewport image.Point
pipelines [2][3]*pipeline pipelines [3]*pipeline
colUniforms *blitColUniforms colUniforms *blitColUniforms
texUniforms *blitTexUniforms texUniforms *blitTexUniforms
linearGradientUniforms *blitLinearGradientUniforms linearGradientUniforms *blitLinearGradientUniforms
@@ -344,12 +343,13 @@ func New(api API) (GPU, error) {
func NewWithDevice(d driver.Device) (GPU, error) { func NewWithDevice(d driver.Device) (GPU, error) {
d.BeginFrame(nil, false, image.Point{}) d.BeginFrame(nil, false, image.Point{})
defer d.EndFrame() defer d.EndFrame()
forceCompute := os.Getenv("GIORENDERER") == "forcecompute"
feats := d.Caps().Features feats := d.Caps().Features
switch { switch {
case feats.Has(driver.FeatureFloatRenderTargets) && feats.Has(driver.FeatureSRGB): case !forceCompute && feats.Has(driver.FeatureFloatRenderTargets) && feats.Has(driver.FeatureSRGB):
return newGPU(d) return newGPU(d)
} }
return nil, errors.New("no available GPU driver") return newCompute(d)
} }
func newGPU(ctx driver.Device) (*gpu, error) { func newGPU(ctx driver.Device) (*gpu, error) {
@@ -548,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]any{b.colUniforms, b.linearGradientUniforms, b.texUniforms}, [3]interface{}{b.colUniforms, b.linearGradientUniforms, b.texUniforms},
) )
if err != nil { if err != nil {
panic(err) panic(err)
@@ -560,24 +560,12 @@ func newBlitter(ctx driver.Device) *blitter {
func (b *blitter) release() { func (b *blitter) release() {
b.quadVerts.Release() b.quadVerts.Release()
for _, p := range b.pipelines { for _, p := range b.pipelines {
for _, p := range p { p.Release()
p.Release()
}
} }
} }
func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.Sources, uniforms [3]any) (pipelines [2][3]*pipeline, err error) { func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.Sources, uniforms [3]interface{}) ([3]*pipeline, error) {
defer func() { var pipelines [3]*pipeline
if err != nil {
for _, p := range pipelines {
for _, p := range p {
if p != nil {
p.Release()
}
}
}
}
}()
blend := driver.BlendDesc{ blend := driver.BlendDesc{
Enable: true, Enable: true,
SrcFactor: driver.BlendFactorOne, SrcFactor: driver.BlendFactorOne,
@@ -595,76 +583,86 @@ func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.
return pipelines, err return pipelines, err
} }
defer vsh.Release() defer vsh.Release()
for i, format := range []driver.TextureFormat{driver.TextureFormatOutput, driver.TextureFormatSRGBA} { {
{ fsh, err := b.NewFragmentShader(fsSrc[materialTexture])
fsh, err := b.NewFragmentShader(fsSrc[materialTexture]) if err != nil {
if err != nil { return pipelines, err
return pipelines, err
}
defer fsh.Release()
pipe, err := b.NewPipeline(driver.PipelineDesc{
VertexShader: vsh,
FragmentShader: fsh,
BlendDesc: blend,
VertexLayout: layout,
PixelFormat: format,
Topology: driver.TopologyTriangleStrip,
})
if err != nil {
return pipelines, err
}
var vertBuffer *uniformBuffer
if u := uniforms[materialTexture]; u != nil {
vertBuffer = newUniformBuffer(b, u)
}
pipelines[i][materialTexture] = &pipeline{pipe, vertBuffer}
} }
{ defer fsh.Release()
var vertBuffer *uniformBuffer pipe, err := b.NewPipeline(driver.PipelineDesc{
fsh, err := b.NewFragmentShader(fsSrc[materialColor]) VertexShader: vsh,
if err != nil { FragmentShader: fsh,
return pipelines, err BlendDesc: blend,
} VertexLayout: layout,
defer fsh.Release() PixelFormat: driver.TextureFormatOutput,
pipe, err := b.NewPipeline(driver.PipelineDesc{ Topology: driver.TopologyTriangleStrip,
VertexShader: vsh, })
FragmentShader: fsh, if err != nil {
BlendDesc: blend, return pipelines, err
VertexLayout: layout,
PixelFormat: format,
Topology: driver.TopologyTriangleStrip,
})
if err != nil {
return pipelines, err
}
if u := uniforms[materialColor]; u != nil {
vertBuffer = newUniformBuffer(b, u)
}
pipelines[i][materialColor] = &pipeline{pipe, vertBuffer}
} }
{ var vertBuffer *uniformBuffer
var vertBuffer *uniformBuffer if u := uniforms[materialTexture]; u != nil {
fsh, err := b.NewFragmentShader(fsSrc[materialLinearGradient]) vertBuffer = newUniformBuffer(b, u)
if err != nil {
return pipelines, err
}
defer fsh.Release()
pipe, err := b.NewPipeline(driver.PipelineDesc{
VertexShader: vsh,
FragmentShader: fsh,
BlendDesc: blend,
VertexLayout: layout,
PixelFormat: format,
Topology: driver.TopologyTriangleStrip,
})
if err != nil {
return pipelines, err
}
if u := uniforms[materialLinearGradient]; u != nil {
vertBuffer = newUniformBuffer(b, u)
}
pipelines[i][materialLinearGradient] = &pipeline{pipe, vertBuffer}
} }
pipelines[materialTexture] = &pipeline{pipe, vertBuffer}
}
{
var vertBuffer *uniformBuffer
fsh, err := b.NewFragmentShader(fsSrc[materialColor])
if err != nil {
pipelines[materialTexture].Release()
return pipelines, err
}
defer fsh.Release()
pipe, err := b.NewPipeline(driver.PipelineDesc{
VertexShader: vsh,
FragmentShader: fsh,
BlendDesc: blend,
VertexLayout: layout,
PixelFormat: driver.TextureFormatOutput,
Topology: driver.TopologyTriangleStrip,
})
if err != nil {
pipelines[materialTexture].Release()
return pipelines, err
}
if u := uniforms[materialColor]; u != nil {
vertBuffer = newUniformBuffer(b, u)
}
pipelines[materialColor] = &pipeline{pipe, vertBuffer}
}
{
var vertBuffer *uniformBuffer
fsh, err := b.NewFragmentShader(fsSrc[materialLinearGradient])
if err != nil {
pipelines[materialTexture].Release()
pipelines[materialColor].Release()
return pipelines, err
}
defer fsh.Release()
pipe, err := b.NewPipeline(driver.PipelineDesc{
VertexShader: vsh,
FragmentShader: fsh,
BlendDesc: blend,
VertexLayout: layout,
PixelFormat: driver.TextureFormatOutput,
Topology: driver.TopologyTriangleStrip,
})
if err != nil {
pipelines[materialTexture].Release()
pipelines[materialColor].Release()
return pipelines, err
}
if u := uniforms[materialLinearGradient]; u != nil {
vertBuffer = newUniformBuffer(b, u)
}
pipelines[materialLinearGradient] = &pipeline{pipe, vertBuffer}
}
if err != nil {
for _, p := range pipelines {
p.Release()
}
return pipelines, err
} }
return pipelines, nil return pipelines, nil
} }
@@ -823,7 +821,7 @@ func (r *renderer) packLayers(layers []opacityLayer) []opacityLayer {
layers[l.parent].clip = b.Union(l.clip) layers[l.parent].clip = b.Union(l.clip)
} }
if l.clip.Empty() { if l.clip.Empty() {
layers = slices.Delete(layers, i, i+1) layers = append(layers[:i], layers[i+1:]...)
} }
} }
// Pack layers. // Pack layers.
@@ -867,12 +865,12 @@ func (r *renderer) drawLayers(layers []opacityLayer, ops []imageOp) {
Min: l.place.Pos, Min: l.place.Pos,
Max: l.place.Pos.Add(l.clip.Size()), Max: l.place.Pos.Add(l.clip.Size()),
} }
r.ctx.Viewport(v.Min.X, v.Min.Y, v.Dx(), v.Dy()) r.ctx.Viewport(v.Min.X, v.Min.Y, v.Max.X, v.Max.Y)
f := r.layerFBOs.fbos[fbo] f := r.layerFBOs.fbos[fbo]
r.drawOps(true, l.clip.Min.Mul(-1), l.clip.Size(), ops[l.opStart:l.opEnd]) r.drawOps(true, l.clip.Min.Mul(-1), l.clip.Size(), ops[l.opStart:l.opEnd])
sr := f32.FRect(v) sr := f32.FRect(v)
uvScale, uvOffset := texSpaceTransform(sr, f.size) uvScale, uvOffset := texSpaceTransform(sr, f.size)
uvTrans := f32.AffineId().Scale(f32.Point{}, uvScale).Offset(uvOffset) uvTrans := f32.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset)
// Replace layer ops with one textured op. // Replace layer ops with one textured op.
ops[l.opStart] = imageOp{ ops[l.opStart] = imageOp{
clip: l.clip, clip: l.clip,
@@ -902,14 +900,16 @@ func (d *drawOps) reset(viewport image.Point) {
d.opacityStack = d.opacityStack[:0] d.opacityStack = d.opacityStack[:0]
} }
func (d *drawOps) collect(root *op.Ops, viewportSize image.Point) { func (d *drawOps) collect(root *op.Ops, viewport image.Point) {
viewport := image.Rectangle{Max: viewportSize} viewf := f32.Rectangle{
Max: f32.Point{X: float32(viewport.X), Y: float32(viewport.Y)},
}
var ops *ops.Ops var ops *ops.Ops
if root != nil { if root != nil {
ops = &root.Internal ops = &root.Internal
} }
d.reader.Reset(ops) d.reader.Reset(ops)
d.collectOps(&d.reader, viewport) d.collectOps(&d.reader, viewf)
} }
func (d *drawOps) buildPaths(ctx driver.Device) { func (d *drawOps) buildPaths(ctx driver.Device) {
@@ -930,7 +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 image.Rectangle, off image.Point) { func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey opKey, bounds f32.Rectangle, off f32.Point, push bool) {
npath := d.newPathOp() npath := d.newPathOp()
*npath = pathOp{ *npath = pathOp{
parent: state.cpath, parent: state.cpath,
@@ -955,9 +955,7 @@ func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey opKey, bounds
func (d *drawOps) save(id int, state f32.Affine2D) { func (d *drawOps) save(id int, state f32.Affine2D) {
if extra := id - len(d.states) + 1; extra > 0 { if extra := id - len(d.states) + 1; extra > 0 {
for range extra { d.states = append(d.states, make([]f32.Affine2D, extra)...)
d.states = append(d.states, f32.AffineId())
}
} }
d.states[id] = state d.states[id] = state
} }
@@ -971,14 +969,13 @@ func (k opKey) SetTransform(t f32.Affine2D) opKey {
return k return k
} }
func (d *drawOps) collectOps(r *ops.Reader, viewport image.Rectangle) { func (d *drawOps) collectOps(r *ops.Reader, viewport f32.Rectangle) {
var quads quadsOp var (
state := drawState{ quads quadsOp
t: f32.AffineId(), state drawState
} )
reset := func() { reset := func() {
state = drawState{ state = drawState{
t: f32.AffineId(),
color: color.NRGBA{A: 0xff}, color: color.NRGBA{A: 0xff},
} }
} }
@@ -1033,8 +1030,8 @@ loop:
var op ops.ClipOp var op ops.ClipOp
op.Decode(encOp.Data) op.Decode(encOp.Data)
quads.key.outline = op.Outline quads.key.outline = op.Outline
bounds := op.Bounds bounds := f32.FRect(op.Bounds)
trans, off := transformOffset(state.t) trans, off := state.t.Split()
if len(quads.aux) > 0 { if len(quads.aux) > 0 {
// There is a clipping path, build the gpu data and update the // There is a clipping path, build the gpu data and update the
// cache key such that it will be equal only if the transform is the // cache key such that it will be equal only if the transform is the
@@ -1045,11 +1042,11 @@ loop:
// Why is this not used for the offset shapes? // Why is this not used for the offset shapes?
bounds = v.bounds bounds = v.bounds
} else { } else {
newPathData, newBounds := d.buildVerts( var pathData []byte
pathData, bounds = d.buildVerts(
quads.aux, trans, quads.key.outline, quads.key.strokeWidth, quads.aux, trans, quads.key.outline, quads.key.strokeWidth,
) )
quads.aux = newPathData quads.aux = pathData
bounds = newBounds.Round()
// add it to the cache, without GPU data, so the transform can be // add it to the cache, without GPU data, so the transform can be
// reused. // reused.
d.pathCache.put(quads.key, opCacheValue{bounds: bounds}) d.pathCache.put(quads.key, opCacheValue{bounds: bounds})
@@ -1057,9 +1054,8 @@ loop:
} else { } else {
quads.aux, bounds, _ = d.boundsForTransformedRect(bounds, trans) quads.aux, bounds, _ = d.boundsForTransformedRect(bounds, trans)
quads.key = opKey{Key: encOp.Key} quads.key = opKey{Key: encOp.Key}
quads.key = quads.key.SetTransform(trans)
} }
d.addClipPath(&state, quads.aux, quads.key, bounds, off) d.addClipPath(&state, quads.aux, quads.key, bounds, off, true)
quads = quadsOp{} quads = quadsOp{}
case ops.TypePopClip: case ops.TypePopClip:
state.cpath = state.cpath.parent state.cpath = state.cpath.parent
@@ -1081,21 +1077,21 @@ loop:
// Transform (if needed) the painting rectangle and if so generate a clip path, // Transform (if needed) the painting rectangle and if so generate a clip path,
// for those cases also compute a partialTrans that maps texture coordinates between // for those cases also compute a partialTrans that maps texture coordinates between
// the new bounding rectangle and the transformed original paint rectangle. // the new bounding rectangle and the transformed original paint rectangle.
t, off := transformOffset(state.t) t, off := state.t.Split()
// Fill the clip area, unless the material is a (bounded) image. // Fill the clip area, unless the material is a (bounded) image.
// TODO: Find a tighter bound. // TODO: Find a tighter bound.
inf := int(1e6) inf := float32(1e6)
dst := image.Rect(-inf, -inf, inf, inf) dst := f32.Rect(-inf, -inf, inf, inf)
if state.matType == materialTexture { if state.matType == materialTexture {
sz := state.image.src.Rect.Size() sz := state.image.src.Rect.Size()
dst = image.Rectangle{Max: sz} dst = f32.Rectangle{Max: layout.FPt(sz)}
} }
clipData, bnd, partialTrans := d.boundsForTransformedRect(dst, t) clipData, bnd, partialTrans := d.boundsForTransformedRect(dst, t)
bounds := viewport.Intersect(bnd.Add(off)) cl := viewport.Intersect(bnd.Add(off))
if state.cpath != nil { if state.cpath != nil {
bounds = state.cpath.intersect.Intersect(bounds) cl = state.cpath.intersect.Intersect(cl)
} }
if bounds.Empty() { if cl.Empty() {
continue continue
} }
@@ -1103,10 +1099,11 @@ loop:
// The paint operation is sheared or rotated, add a clip path representing // The paint operation is sheared or rotated, add a clip path representing
// this transformed rectangle. // this transformed rectangle.
k := opKey{Key: encOp.Key} k := opKey{Key: encOp.Key}
k = k.SetTransform(t) k.SetTransform(t) // TODO: This call has no effect.
d.addClipPath(&state, clipData, k, bnd, off) d.addClipPath(&state, clipData, k, bnd, off, false)
} }
bounds := cl.Round()
mat := state.materialFor(bnd, off, partialTrans, bounds) mat := state.materialFor(bnd, off, partialTrans, bounds)
rect := state.cpath == nil || state.cpath.rect rect := state.cpath == nil || state.cpath.rect
@@ -1160,10 +1157,9 @@ func expandPathOp(p *pathOp, clip image.Rectangle) {
} }
} }
func (d *drawState) materialFor(rect image.Rectangle, off image.Point, partTrans f32.Affine2D, clip image.Rectangle) material { func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32.Affine2D, clip image.Rectangle) material {
m := material{ m := material{
opacity: 1., opacity: 1.,
uvTrans: f32.AffineId(),
} }
switch d.matType { switch d.matType {
case materialColor: case materialColor:
@@ -1180,7 +1176,7 @@ func (d *drawState) materialFor(rect image.Rectangle, off image.Point, partTrans
m.uvTrans = partTrans.Mul(gradientSpaceTransform(clip, off, d.stop1, d.stop2)) m.uvTrans = partTrans.Mul(gradientSpaceTransform(clip, off, d.stop1, d.stop2))
case materialTexture: case materialTexture:
m.material = materialTexture m.material = materialTexture
dr := rect.Add(off) dr := rect.Add(off).Round()
sz := d.image.src.Bounds().Size() sz := d.image.src.Bounds().Size()
sr := f32.Rectangle{ sr := f32.Rectangle{
Max: f32.Point{ Max: f32.Point{
@@ -1197,7 +1193,7 @@ func (d *drawState) materialFor(rect image.Rectangle, off image.Point, partTrans
sr.Min.Y += float32(clip.Min.Y-dr.Min.Y) * sdy / dy sr.Min.Y += float32(clip.Min.Y-dr.Min.Y) * sdy / dy
sr.Max.Y -= float32(dr.Max.Y-clip.Max.Y) * sdy / dy sr.Max.Y -= float32(dr.Max.Y-clip.Max.Y) * sdy / dy
uvScale, uvOffset := texSpaceTransform(sr, sz) uvScale, uvOffset := texSpaceTransform(sr, sz)
m.uvTrans = partTrans.Mul(f32.AffineId().Scale(f32.Point{}, uvScale).Offset(uvOffset)) m.uvTrans = partTrans.Mul(f32.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset))
m.data = d.image m.data = d.image
} }
return m return m
@@ -1234,7 +1230,7 @@ func (r *renderer) prepareDrawOps(ops []imageOp) {
} }
} }
func (r *renderer) drawOps(isFBO bool, opOff, viewport image.Point, ops []imageOp) { func (r *renderer) drawOps(isFBO bool, opOff image.Point, viewport image.Point, ops []imageOp) {
var coverTex driver.Texture var coverTex driver.Texture
for i := 0; i < len(ops); i++ { for i := 0; i < len(ops); i++ {
img := ops[i] img := ops[i]
@@ -1248,13 +1244,9 @@ func (r *renderer) drawOps(isFBO bool, opOff, viewport image.Point, ops []imageO
scale, off := clipSpaceTransform(drc, viewport) scale, off := clipSpaceTransform(drc, viewport)
var fbo FBO var fbo FBO
fboIdx := 0
if isFBO {
fboIdx = 1
}
switch img.clipType { switch img.clipType {
case clipTypeNone: case clipTypeNone:
p := r.blitter.pipelines[fboIdx][m.material] p := r.blitter.pipelines[m.material]
r.ctx.BindPipeline(p.pipeline) r.ctx.BindPipeline(p.pipeline)
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0) r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
r.blitter.blit(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.opacity, m.uvTrans) r.blitter.blit(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.opacity, m.uvTrans)
@@ -1273,7 +1265,7 @@ func (r *renderer) drawOps(isFBO bool, opOff, viewport image.Point, ops []imageO
Max: img.place.Pos.Add(drc.Size()), Max: img.place.Pos.Add(drc.Size()),
} }
coverScale, coverOff := texSpaceTransform(f32.FRect(uv), fbo.size) coverScale, coverOff := texSpaceTransform(f32.FRect(uv), fbo.size)
p := r.pather.coverer.pipelines[fboIdx][m.material] p := r.pather.coverer.pipelines[m.material]
r.ctx.BindPipeline(p.pipeline) r.ctx.BindPipeline(p.pipeline)
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0) r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
r.pather.cover(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.uvTrans, coverScale, coverOff) r.pather.cover(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.uvTrans, coverScale, coverOff)
@@ -1281,11 +1273,7 @@ func (r *renderer) drawOps(isFBO bool, opOff, viewport image.Point, ops []imageO
} }
func (b *blitter) blit(mat materialType, fbo bool, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, opacity float32, uvTrans f32.Affine2D) { func (b *blitter) blit(mat materialType, fbo bool, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, opacity float32, uvTrans f32.Affine2D) {
fboIdx := 0 p := b.pipelines[mat]
if fbo {
fboIdx = 1
}
p := b.pipelines[fboIdx][mat]
b.ctx.BindPipeline(p.pipeline) b.ctx.BindPipeline(p.pipeline)
var uniforms *blitUniforms var uniforms *blitUniforms
switch mat { switch mat {
@@ -1318,7 +1306,7 @@ func (b *blitter) blit(mat materialType, fbo bool, col f32color.RGBA, col1, col2
// newUniformBuffer creates a new GPU uniform buffer backed by the // newUniformBuffer creates a new GPU uniform buffer backed by the
// structure uniformBlock points to. // structure uniformBlock points to.
func newUniformBuffer(b driver.Device, uniformBlock any) *uniformBuffer { func newUniformBuffer(b driver.Device, uniformBlock interface{}) *uniformBuffer {
ref := reflect.ValueOf(uniformBlock) ref := reflect.ValueOf(uniformBlock)
// Determine the size of the uniforms structure, *uniforms. // Determine the size of the uniforms structure, *uniforms.
size := ref.Elem().Type().Size() size := ref.Elem().Type().Size()
@@ -1365,19 +1353,19 @@ func texSpaceTransform(r f32.Rectangle, bounds image.Point) (f32.Point, f32.Poin
} }
// gradientSpaceTransform transforms stop1 and stop2 to [(0,0), (1,1)]. // gradientSpaceTransform transforms stop1 and stop2 to [(0,0), (1,1)].
func gradientSpaceTransform(clip image.Rectangle, off image.Point, stop1, stop2 f32.Point) f32.Affine2D { func gradientSpaceTransform(clip image.Rectangle, off f32.Point, stop1, stop2 f32.Point) f32.Affine2D {
d := stop2.Sub(stop1) d := stop2.Sub(stop1)
l := float32(math.Sqrt(float64(d.X*d.X + d.Y*d.Y))) l := float32(math.Sqrt(float64(d.X*d.X + d.Y*d.Y)))
a := float32(math.Atan2(float64(-d.Y), float64(d.X))) a := float32(math.Atan2(float64(-d.Y), float64(d.X)))
// TODO: optimize // TODO: optimize
zp := f32.Point{} zp := f32.Point{}
return f32.AffineId(). return f32.Affine2D{}.
Scale(zp, layout.FPt(clip.Size())). // scale to pixel space Scale(zp, layout.FPt(clip.Size())). // scale to pixel space
Offset(zp.Sub(f32.FPt(off)).Add(layout.FPt(clip.Min))). // offset to clip space Offset(zp.Sub(off).Add(layout.FPt(clip.Min))). // offset to clip space
Offset(zp.Sub(stop1)). // offset to first stop point Offset(zp.Sub(stop1)). // offset to first stop point
Rotate(zp, a). // rotate to align gradient Rotate(zp, a). // rotate to align gradient
Scale(zp, f32.Pt(1/l, 1/l)) // scale gradient to right size Scale(zp, f32.Pt(1/l, 1/l)) // scale gradient to right size
} }
// clipSpaceTransform returns the scale and offset that transforms the given // clipSpaceTransform returns the scale and offset that transforms the given
@@ -1486,7 +1474,7 @@ func (d *drawOps) buildVerts(pathData []byte, tr f32.Affine2D, outline bool, str
// as needed and feeds them to the supplied splitter. // as needed and feeds them to the supplied splitter.
func decodeToOutlineQuads(qs *quadSplitter, tr f32.Affine2D, pathData []byte) { func decodeToOutlineQuads(qs *quadSplitter, tr f32.Affine2D, pathData []byte) {
for len(pathData) >= scene.CommandSize+4 { for len(pathData) >= scene.CommandSize+4 {
qs.contour = binary.LittleEndian.Uint32(pathData) qs.contour = bo.Uint32(pathData)
cmd := ops.DecodeCommand(pathData[4:]) cmd := ops.DecodeCommand(pathData[4:])
switch cmd.Op() { switch cmd.Op() {
case scene.OpLine: case scene.OpLine:
@@ -1521,38 +1509,37 @@ func decodeToOutlineQuads(qs *quadSplitter, tr f32.Affine2D, pathData []byte) {
} }
// create GPU vertices for transformed r, find the bounds and establish texture transform. // create GPU vertices for transformed r, find the bounds and establish texture transform.
func (d *drawOps) boundsForTransformedRect(r image.Rectangle, tr f32.Affine2D) (aux []byte, bnd image.Rectangle, ptr f32.Affine2D) { func (d *drawOps) boundsForTransformedRect(r f32.Rectangle, tr f32.Affine2D) (aux []byte, bnd f32.Rectangle, ptr f32.Affine2D) {
ptr = f32.AffineId() if isPureOffset(tr) {
if tr == f32.AffineId() { // fast-path to allow blitting of pure rectangles
// fast-path to allow blitting of pure rectangles. _, _, ox, _, _, oy := tr.Elems()
bnd = r off := f32.Pt(ox, oy)
bnd.Min = r.Min.Add(off)
bnd.Max = r.Max.Add(off)
return return
} }
// transform all corners, find new bounds // transform all corners, find new bounds
corners := [4]f32.Point{ corners := [4]f32.Point{
tr.Transform(f32.FPt(r.Min)), tr.Transform(f32.Pt(float32(r.Max.X), float32(r.Min.Y))), tr.Transform(r.Min), tr.Transform(f32.Pt(r.Max.X, r.Min.Y)),
tr.Transform(f32.FPt(r.Max)), tr.Transform(f32.Pt(float32(r.Min.X), float32(r.Max.Y))), tr.Transform(r.Max), tr.Transform(f32.Pt(r.Min.X, r.Max.Y)),
}
fBounds := f32.Rectangle{
Min: f32.Pt(math.MaxFloat32, math.MaxFloat32),
Max: f32.Pt(-math.MaxFloat32, -math.MaxFloat32),
} }
bnd.Min = f32.Pt(math.MaxFloat32, math.MaxFloat32)
bnd.Max = f32.Pt(-math.MaxFloat32, -math.MaxFloat32)
for _, c := range corners { for _, c := range corners {
if c.X < fBounds.Min.X { if c.X < bnd.Min.X {
fBounds.Min.X = c.X bnd.Min.X = c.X
} }
if c.Y < fBounds.Min.Y { if c.Y < bnd.Min.Y {
fBounds.Min.Y = c.Y bnd.Min.Y = c.Y
} }
if c.X > fBounds.Max.X { if c.X > bnd.Max.X {
fBounds.Max.X = c.X bnd.Max.X = c.X
} }
if c.Y > fBounds.Max.Y { if c.Y > bnd.Max.Y {
fBounds.Max.Y = c.Y bnd.Max.Y = c.Y
} }
} }
bnd = fBounds.Round()
// build the GPU vertices // build the GPU vertices
l := len(d.vertCache) l := len(d.vertCache)
@@ -1566,38 +1553,19 @@ func (d *drawOps) boundsForTransformedRect(r image.Rectangle, tr f32.Affine2D) (
// establish the transform mapping from bounds rectangle to transformed corners // establish the transform mapping from bounds rectangle to transformed corners
var P1, P2, P3 f32.Point var P1, P2, P3 f32.Point
P1.X = (corners[1].X - fBounds.Min.X) / (fBounds.Max.X - fBounds.Min.X) P1.X = (corners[1].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X)
P1.Y = (corners[1].Y - fBounds.Min.Y) / (fBounds.Max.Y - fBounds.Min.Y) P1.Y = (corners[1].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y)
P2.X = (corners[2].X - fBounds.Min.X) / (fBounds.Max.X - fBounds.Min.X) P2.X = (corners[2].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X)
P2.Y = (corners[2].Y - fBounds.Min.Y) / (fBounds.Max.Y - fBounds.Min.Y) P2.Y = (corners[2].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y)
P3.X = (corners[3].X - fBounds.Min.X) / (fBounds.Max.X - fBounds.Min.X) P3.X = (corners[3].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X)
P3.Y = (corners[3].Y - fBounds.Min.Y) / (fBounds.Max.Y - fBounds.Min.Y) P3.Y = (corners[3].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y)
sx, sy := P2.X-P3.X, P2.Y-P3.Y sx, sy := P2.X-P3.X, P2.Y-P3.Y
ptr = f32.NewAffine2D(sx, P2.X-P1.X, P1.X-sx, sy, P2.Y-P1.Y, P1.Y-sy).Invert() ptr = f32.NewAffine2D(sx, P2.X-P1.X, P1.X-sx, sy, P2.Y-P1.Y, P1.Y-sy).Invert()
return aux, bnd, ptr
}
// transformOffset a transform into two parts, one which is pure integer offset
// and the other representing the scaling, shearing and rotation and fractional
// offset.
func transformOffset(t f32.Affine2D) (f32.Affine2D, image.Point) {
sx, hx, ox, hy, sy, oy := t.Elems()
iox, fox := math.Modf(float64(ox))
ioy, foy := math.Modf(float64(oy))
ft := f32.NewAffine2D(sx, hx, float32(fox), hy, sy, float32(foy))
ip := image.Pt(int(iox), int(ioy))
return ft, ip
}
func newShaders(ctx driver.Device, vsrc, fsrc shader.Sources) (vert driver.VertexShader, frag driver.FragmentShader, err error) {
vert, err = ctx.NewVertexShader(vsrc)
if err != nil {
return
}
frag, err = ctx.NewFragmentShader(fsrc)
if err != nil {
vert.Release()
}
return return
} }
func isPureOffset(t f32.Affine2D) bool {
a, b, _, d, e, _ := t.Elems()
return a == 1 && b == 0 && d == 0 && e == 1
}
+3 -5
View File
@@ -21,10 +21,8 @@ import (
var dumpImages = flag.Bool("saveimages", false, "save test images") var dumpImages = flag.Bool("saveimages", false, "save test images")
var ( var clearCol = color.NRGBA{A: 0xff, R: 0xde, G: 0xad, B: 0xbe}
clearCol = color.NRGBA{A: 0xff, R: 0xde, G: 0xad, B: 0xbe} var clearColExpect = f32color.NRGBAToRGBA(clearCol)
clearColExpect = f32color.NRGBAToRGBA(clearCol)
)
func TestFramebufferClear(t *testing.T) { func TestFramebufferClear(t *testing.T) {
b := newDriver(t) b := newDriver(t)
@@ -204,5 +202,5 @@ func saveImage(file string, img image.Image) error {
if err := png.Encode(&buf, img); err != nil { if err := png.Encode(&buf, img); err != nil {
return err return err
} }
return os.WriteFile(file, buf.Bytes(), 0o666) return os.WriteFile(file, buf.Bytes(), 0666)
} }
+8 -3
View File
@@ -152,7 +152,9 @@ func newDirect3D11Device(api driver.Direct3D11) (driver.Device, error) {
} }
func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport image.Point) driver.Texture { func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport image.Point) driver.Texture {
var renderTarget *d3d11.RenderTargetView var (
renderTarget *d3d11.RenderTargetView
)
if target != nil { if target != nil {
switch t := target.(type) { switch t := target.(type) {
case driver.Direct3D11RenderTarget: case driver.Direct3D11RenderTarget:
@@ -227,7 +229,10 @@ func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, min
// Flags required by ID3D11DeviceContext::GenerateMips. // Flags required by ID3D11DeviceContext::GenerateMips.
bindFlags |= d3d11.BIND_SHADER_RESOURCE | d3d11.BIND_RENDER_TARGET bindFlags |= d3d11.BIND_SHADER_RESOURCE | d3d11.BIND_RENDER_TARGET
miscFlags |= d3d11.RESOURCE_MISC_GENERATE_MIPS miscFlags |= d3d11.RESOURCE_MISC_GENERATE_MIPS
dim := max(height, width) dim := width
if height > dim {
dim = height
}
log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1 log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1
nmipmaps = log2 + 1 nmipmaps = log2 + 1
} }
@@ -797,7 +802,7 @@ func (t *Texture) ReadPixels(src image.Rectangle, pixels []byte, stride int) err
mapSize := dstPitch * h mapSize := dstPitch * h
data := sliceOf(resMap.PData, mapSize) data := sliceOf(resMap.PData, mapSize)
width := w * 4 width := w * 4
for r := range h { for r := 0; r < h; r++ {
pixels := pixels[r*srcPitch:] pixels := pixels[r*srcPitch:]
copy(pixels[:width], data[r*dstPitch:]) copy(pixels[:width], data[r*dstPitch:])
} }
+3 -5
View File
@@ -96,10 +96,8 @@ type BlendFactor uint8
type Topology uint8 type Topology uint8
type ( type TextureFilter uint8
TextureFilter uint8 type TextureFormat uint8
TextureFormat uint8
)
type BufferBinding uint8 type BufferBinding uint8
@@ -219,7 +217,7 @@ func flipImageY(stride, height int, pixels []byte) {
// Flip image in y-direction. OpenGL's origin is in the lower // Flip image in y-direction. OpenGL's origin is in the lower
// left corner. // left corner.
row := make([]uint8, stride) row := make([]uint8, stride)
for y := range height / 2 { for y := 0; y < height/2; y++ {
y1 := height - y - 1 y1 := height - y - 1
dest := y1 * stride dest := y1 * stride
src := y * stride src := y * stride
+12 -5
View File
@@ -8,7 +8,6 @@ import (
"image" "image"
"math/bits" "math/bits"
"runtime" "runtime"
"slices"
"strings" "strings"
"time" "time"
"unsafe" "unsafe"
@@ -718,7 +717,10 @@ func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, min
if mipmap { if mipmap {
nmipmaps := 1 nmipmaps := 1
if mipmap { if mipmap {
dim := max(height, width) dim := width
if height > dim {
dim = height
}
log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1 log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1
nmipmaps = log2 + 1 nmipmaps = log2 + 1
} }
@@ -1130,7 +1132,7 @@ func (b *Backend) setupVertexArrays() {
enabled[inp.Location] = true enabled[inp.Location] = true
b.glstate.vertexAttribPointer(b.funcs, buf.obj, inp.Location, l.Size, gltyp, false, p.layout.Stride, buf.offset+l.Offset) b.glstate.vertexAttribPointer(b.funcs, buf.obj, inp.Location, l.Size, gltyp, false, p.layout.Stride, buf.offset+l.Offset)
} }
for i := range max { for i := 0; i < max; i++ {
b.glstate.setVertexAttribArray(b.funcs, i, enabled[i]) b.glstate.setVertexAttribArray(b.funcs, i, enabled[i])
} }
} }
@@ -1173,7 +1175,7 @@ func (t *texture) ReadPixels(src image.Rectangle, pixels []byte, stride int) err
} else { } else {
tmp := make([]byte, w*h*4) tmp := make([]byte, w*h*4)
t.backend.funcs.ReadPixels(src.Min.X, src.Min.Y, w, h, gl.RGBA, gl.UNSIGNED_BYTE, tmp) t.backend.funcs.ReadPixels(src.Min.X, src.Min.Y, w, h, gl.RGBA, gl.UNSIGNED_BYTE, tmp)
for y := range h { for y := 0; y < h; y++ {
copy(pixels[y*stride:], tmp[y*w*4:]) copy(pixels[y*stride:], tmp[y*w*4:])
} }
} }
@@ -1355,7 +1357,12 @@ func alphaTripleFor(ver [2]int) textureTriple {
} }
func hasExtension(exts []string, ext string) bool { func hasExtension(exts []string, ext string) bool {
return slices.Contains(exts, ext) for _, e := range exts {
if ext == e {
return true
}
}
return false
} }
func firstBufferType(typ driver.BufferBinding) gl.Enum { func firstBufferType(typ driver.BufferBinding) gl.Enum {
+21 -21
View File
@@ -66,8 +66,8 @@ func BenchmarkDrawUICached(b *testing.B) {
defer w.Release() defer w.Release()
drawCore(gtx, th) drawCore(gtx, th)
w.Frame(gtx.Ops) w.Frame(gtx.Ops)
b.ResetTimer()
for b.Loop() { for i := 0; i < b.N; i++ {
w.Frame(gtx.Ops) w.Frame(gtx.Ops)
} }
finishBenchmark(b, w) finishBenchmark(b, w)
@@ -83,12 +83,12 @@ func BenchmarkDrawUI(b *testing.B) {
drawCore(gtx, th) drawCore(gtx, th)
w.Frame(gtx.Ops) w.Frame(gtx.Ops)
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer()
for i := 0; b.Loop(); i++ { for i := 0; i < b.N; i++ {
resetOps(gtx) resetOps(gtx)
off := float32(math.Mod(float64(i)/10, 10)) off := float32(math.Mod(float64(i)/10, 10))
t := op.Affine(f32.AffineId().Offset(f32.Pt(off, off))).Push(gtx.Ops) t := op.Affine(f32.Affine2D{}.Offset(f32.Pt(off, off))).Push(gtx.Ops)
drawCore(gtx, th) drawCore(gtx, th)
@@ -105,12 +105,12 @@ func BenchmarkDrawUITransformed(b *testing.B) {
drawCore(gtx, th) drawCore(gtx, th)
w.Frame(gtx.Ops) w.Frame(gtx.Ops)
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer()
for i := 0; b.Loop(); i++ { for i := 0; i < b.N; i++ {
resetOps(gtx) resetOps(gtx)
angle := float32(math.Mod(float64(i)/1000, 0.05)) angle := float32(math.Mod(float64(i)/1000, 0.05))
a := f32.AffineId().Shear(f32.Point{}, angle, angle).Rotate(f32.Point{}, angle) a := f32.Affine2D{}.Shear(f32.Point{}, angle, angle).Rotate(f32.Point{}, angle)
t := op.Affine(a).Push(gtx.Ops) t := op.Affine(a).Push(gtx.Ops)
drawCore(gtx, th) drawCore(gtx, th)
@@ -130,8 +130,8 @@ func Benchmark1000Circles(b *testing.B) {
draw1000Circles(gtx) draw1000Circles(gtx)
w.Frame(gtx.Ops) w.Frame(gtx.Ops)
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer()
for b.Loop() { for i := 0; i < b.N; i++ {
resetOps(gtx) resetOps(gtx)
draw1000Circles(gtx) draw1000Circles(gtx)
w.Frame(gtx.Ops) w.Frame(gtx.Ops)
@@ -147,8 +147,8 @@ func Benchmark1000CirclesInstanced(b *testing.B) {
draw1000CirclesInstanced(gtx) draw1000CirclesInstanced(gtx)
w.Frame(gtx.Ops) w.Frame(gtx.Ops)
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer()
for b.Loop() { for i := 0; i < b.N; i++ {
resetOps(gtx) resetOps(gtx)
draw1000CirclesInstanced(gtx) draw1000CirclesInstanced(gtx)
w.Frame(gtx.Ops) w.Frame(gtx.Ops)
@@ -158,9 +158,9 @@ func Benchmark1000CirclesInstanced(b *testing.B) {
func draw1000Circles(gtx layout.Context) { func draw1000Circles(gtx layout.Context) {
ops := gtx.Ops ops := gtx.Ops
for x := range 100 { for x := 0; x < 100; x++ {
op.Offset(image.Pt(x*10, 0)).Add(ops) op.Offset(image.Pt(x*10, 0)).Add(ops)
for y := range 10 { for y := 0; y < 10; y++ {
paint.FillShape(ops, paint.FillShape(ops,
color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120}, color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120},
clip.RRect{Rect: image.Rect(0, 0, 10, 10), NE: 5, SE: 5, SW: 5, NW: 5}.Op(ops), clip.RRect{Rect: image.Rect(0, 0, 10, 10), NE: 5, SE: 5, SW: 5, NW: 5}.Op(ops),
@@ -179,9 +179,9 @@ func draw1000CirclesInstanced(gtx layout.Context) {
cl.Pop() cl.Pop()
c := r.Stop() c := r.Stop()
for x := range 100 { for x := 0; x < 100; x++ {
op.Offset(image.Pt(x*10, 0)).Add(ops) op.Offset(image.Pt(x*10, 0)).Add(ops)
for y := range 10 { for y := 0; y < 10; y++ {
paint.ColorOp{Color: color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120}}.Add(ops) paint.ColorOp{Color: color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120}}.Add(ops)
c.Add(ops) c.Add(ops)
op.Offset(image.Pt(0, 100)).Add(ops) op.Offset(image.Pt(0, 100)).Add(ops)
@@ -204,9 +204,9 @@ func drawIndividualShapes(gtx layout.Context, th *material.Theme) chan op.CallOp
go func() { go func() {
ops := &op1 ops := &op1
c := op.Record(ops) c := op.Record(ops)
for x := range 9 { for x := 0; x < 9; x++ {
op.Offset(image.Pt(x*50, 0)).Add(ops) op.Offset(image.Pt(x*50, 0)).Add(ops)
for y := range 9 { for y := 0; y < 9; y++ {
paint.FillShape(ops, paint.FillShape(ops,
color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120}, color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120},
clip.RRect{Rect: image.Rect(0, 0, 25, 25), NE: 10, SE: 10, SW: 10, NW: 10}.Op(ops), clip.RRect{Rect: image.Rect(0, 0, 25, 25), NE: 10, SE: 10, SW: 10, NW: 10}.Op(ops),
@@ -233,8 +233,8 @@ func drawShapeInstances(gtx layout.Context, th *material.Theme) chan op.CallOp {
squares.Add(ops) squares.Add(ops)
rad := float32(0) rad := float32(0)
for x := range 20 { for x := 0; x < 20; x++ {
for y := range 20 { for y := 0; y < 20; y++ {
t := op.Offset(image.Pt(x*50+25, y*50+25)).Push(ops) t := op.Offset(image.Pt(x*50+25, y*50+25)).Push(ops)
c.Add(ops) c.Add(ops)
t.Pop() t.Pop()
@@ -253,7 +253,7 @@ func drawText(gtx layout.Context, th *material.Theme) chan op.CallOp {
c := op.Record(ops) c := op.Record(ops)
txt := material.H6(th, "") txt := material.H6(th, "")
for x := range 40 { for x := 0; x < 40; x++ {
txt.Text = textRows[x] txt.Text = textRows[x]
t := op.Offset(image.Pt(0, 24*x)).Push(ops) t := op.Offset(image.Pt(0, 24*x)).Push(ops)
gtx.Ops = ops gtx.Ops = ops
+9 -40
View File
@@ -40,25 +40,6 @@ func TestPaintClippedRect(t *testing.T) {
}) })
} }
func TestPaintClippedRectOffset(t *testing.T) {
run(t, func(o *op.Ops) {
defer op.Affine(f32.AffineId().Offset(f32.Pt(0.5, 0.5))).Push(o).Pop()
defer clip.RRect{Rect: image.Rect(25, 25, 60, 60)}.Push(o).Pop()
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 50, 50)).Op())
}, func(r result) {
r.expect(0, 0, transparent)
r.expect(24, 35, transparent)
r.expect(24, 24, transparent)
r.expect(25, 25, color.RGBA{R: 137, A: 64})
r.expect(25, 35, color.RGBA{R: 187, A: 128})
r.expect(35, 25, color.RGBA{R: 187, A: 128})
r.expect(50, 50, color.RGBA{R: 137, A: 64})
r.expect(51, 51, transparent)
r.expect(50, 0, transparent)
r.expect(10, 50, transparent)
})
}
func TestPaintClippedCircle(t *testing.T) { func TestPaintClippedCircle(t *testing.T) {
run(t, func(o *op.Ops) { run(t, func(o *op.Ops) {
const r = 10 const r = 10
@@ -151,7 +132,8 @@ func TestTexturedStrokeClipped(t *testing.T) {
defer clip.RRect{Rect: image.Rect(-30, -30, 60, 60)}.Push(o).Pop() defer clip.RRect{Rect: image.Rect(-30, -30, 60, 60)}.Push(o).Pop()
defer op.Offset(image.Pt(-10, -10)).Push(o).Pop() defer op.Offset(image.Pt(-10, -10)).Push(o).Pop()
paint.PaintOp{}.Add(o) paint.PaintOp{}.Add(o)
}, nil) }, func(r result) {
})
} }
func TestTexturedStroke(t *testing.T) { func TestTexturedStroke(t *testing.T) {
@@ -164,7 +146,8 @@ func TestTexturedStroke(t *testing.T) {
}.Op().Push(o).Pop() }.Op().Push(o).Pop()
defer op.Offset(image.Pt(-10, -10)).Push(o).Pop() defer op.Offset(image.Pt(-10, -10)).Push(o).Pop()
paint.PaintOp{}.Add(o) paint.PaintOp{}.Add(o)
}, nil) }, func(r result) {
})
} }
func TestPaintClippedTexture(t *testing.T) { func TestPaintClippedTexture(t *testing.T) {
@@ -195,6 +178,7 @@ func TestStrokedPathZeroWidth(t *testing.T) {
paint.Fill(o, red) paint.Fill(o, red)
cl.Pop() cl.Pop()
} }
}, func(r result) { }, func(r result) {
r.expect(0, 0, transparent) r.expect(0, 0, transparent)
r.expect(10, 50, colornames.Black) r.expect(10, 50, colornames.Black)
@@ -268,7 +252,8 @@ func TestPathReuse(t *testing.T) {
stroke := clip.Stroke{Path: spec, Width: 3}.Op().Push(o) stroke := clip.Stroke{Path: spec, Width: 3}.Op().Push(o)
paint.Fill(o, color.NRGBA{B: 0xFF, A: 0xFF}) paint.Fill(o, color.NRGBA{B: 0xFF, A: 0xFF})
stroke.Pop() stroke.Pop()
}, nil) }, func(r result) {
})
} }
func TestPathInterleave(t *testing.T) { func TestPathInterleave(t *testing.T) {
@@ -305,22 +290,6 @@ func TestStrokedRect(t *testing.T) {
Width: 5, Width: 5,
}.Op(), }.Op(),
) )
}, nil) }, func(r result) {
} })
func TestInstancedRects(t *testing.T) {
run(t, func(o *op.Ops) {
macro := op.Record(o)
clip := clip.Rect{Max: image.Pt(20, 20)}.Push(o)
paint.ColorOp{Color: color.NRGBA{R: 0x80, A: 0xFF}}.Add(o)
paint.PaintOp{}.Add(o)
clip.Pop()
c := macro.Stop()
for range 2 {
op.Affine(f32.AffineId().Rotate(f32.Pt(0, 0), .2)).Add(o)
c.Add(o)
op.Offset(image.Pt(20, 20)).Add(o)
}
}, nil)
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 787 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 435 B

+17 -16
View File
@@ -24,6 +24,7 @@ func TestTransformMacro(t *testing.T) {
c := constSqPath() c := constSqPath()
run(t, func(o *op.Ops) { run(t, func(o *op.Ops) {
// render the first Stacked item // render the first Stacked item
m1 := op.Record(o) m1 := op.Record(o)
dr := image.Rect(0, 0, 128, 50) dr := image.Rect(0, 0, 128, 50)
@@ -87,10 +88,10 @@ func TestNoClipFromPaint(t *testing.T) {
// ensure that a paint operation does not pollute the state // ensure that a paint operation does not pollute the state
// by leaving any clip paths in place. // by leaving any clip paths in place.
run(t, func(o *op.Ops) { run(t, func(o *op.Ops) {
a := f32.AffineId().Rotate(f32.Pt(20, 20), math.Pi/4) a := f32.Affine2D{}.Rotate(f32.Pt(20, 20), math.Pi/4)
defer op.Affine(a).Push(o).Pop() defer op.Affine(a).Push(o).Pop()
paint.FillShape(o, red, clip.Rect(image.Rect(10, 10, 30, 30)).Op()) paint.FillShape(o, red, clip.Rect(image.Rect(10, 10, 30, 30)).Op())
a = f32.AffineId().Rotate(f32.Pt(20, 20), -math.Pi/4) a = f32.Affine2D{}.Rotate(f32.Pt(20, 20), -math.Pi/4)
defer op.Affine(a).Push(o).Pop() defer op.Affine(a).Push(o).Pop()
paint.FillShape(o, black, clip.Rect(image.Rect(0, 0, 50, 50)).Op()) paint.FillShape(o, black, clip.Rect(image.Rect(0, 0, 50, 50)).Op())
@@ -109,7 +110,7 @@ func TestDeferredPaint(t *testing.T) {
paint.PaintOp{}.Add(o) paint.PaintOp{}.Add(o)
cl.Pop() cl.Pop()
t := op.Affine(f32.AffineId().Offset(f32.Pt(20, 20))).Push(o) t := op.Affine(f32.Affine2D{}.Offset(f32.Pt(20, 20))).Push(o)
m := op.Record(o) m := op.Record(o)
cl2 := clip.Rect(image.Rect(0, 0, 80, 80)).Op().Push(o) cl2 := clip.Rect(image.Rect(0, 0, 80, 80)).Op().Push(o)
paint.ColorOp{Color: color.NRGBA{A: 0x60, R: 0xff, G: 0xff}}.Add(o) paint.ColorOp{Color: color.NRGBA{A: 0x60, R: 0xff, G: 0xff}}.Add(o)
@@ -119,11 +120,12 @@ func TestDeferredPaint(t *testing.T) {
op.Defer(o, paintMacro) op.Defer(o, paintMacro)
t.Pop() t.Pop()
defer op.Affine(f32.AffineId().Offset(f32.Pt(10, 10))).Push(o).Pop() defer op.Affine(f32.Affine2D{}.Offset(f32.Pt(10, 10))).Push(o).Pop()
defer clip.Rect(image.Rect(0, 0, 80, 80)).Op().Push(o).Pop() defer clip.Rect(image.Rect(0, 0, 80, 80)).Op().Push(o).Pop()
paint.ColorOp{Color: color.NRGBA{A: 0x60, B: 0xff}}.Add(o) paint.ColorOp{Color: color.NRGBA{A: 0x60, B: 0xff}}.Add(o)
paint.PaintOp{}.Add(o) paint.PaintOp{}.Add(o)
}, nil) }, func(r result) {
})
} }
func constSqPath() clip.Op { func constSqPath() clip.Op {
@@ -141,10 +143,8 @@ func constSqPath() clip.Op {
func constSqCirc() clip.Op { func constSqCirc() clip.Op {
innerOps := new(op.Ops) innerOps := new(op.Ops)
return clip.RRect{ return clip.RRect{Rect: image.Rect(0, 0, 40, 40),
Rect: image.Rect(0, 0, 40, 40), NW: 20, NE: 20, SW: 20, SE: 20}.Op(innerOps)
NW: 20, NE: 20, SW: 20, SE: 20,
}.Op(innerOps)
} }
func drawChild(ops *op.Ops, text clip.Op) op.CallOp { func drawChild(ops *op.Ops, text clip.Op) op.CallOp {
@@ -260,7 +260,7 @@ func TestLinearGradient(t *testing.T) {
Color2: g.To, Color2: g.To,
}.Add(ops) }.Add(ops)
cl := clip.RRect{Rect: gr.Round()}.Push(ops) cl := clip.RRect{Rect: gr.Round()}.Push(ops)
t1 := op.Affine(f32.AffineId().Offset(pixelAligned.Min)).Push(ops) t1 := op.Affine(f32.Affine2D{}.Offset(pixelAligned.Min)).Push(ops)
t2 := scale(pixelAligned.Dx()/128, 1).Push(ops) t2 := scale(pixelAligned.Dx()/128, 1).Push(ops)
paint.PaintOp{}.Add(ops) paint.PaintOp{}.Add(ops)
t2.Pop() t2.Pop()
@@ -323,7 +323,7 @@ func TestLinearGradientAngled(t *testing.T) {
cl = clip.Rect(image.Rect(0, 64, 64, 128)).Push(ops) cl = clip.Rect(image.Rect(0, 64, 64, 128)).Push(ops)
paint.PaintOp{}.Add(ops) paint.PaintOp{}.Add(ops)
cl.Pop() cl.Pop()
}, nil) }, func(r result) {})
} }
func TestZeroImage(t *testing.T) { func TestZeroImage(t *testing.T) {
@@ -363,7 +363,7 @@ func TestImageRGBA_ScaleLinear(t *testing.T) {
run(t, func(o *op.Ops) { run(t, func(o *op.Ops) {
w := newWindow(t, 128, 128) w := newWindow(t, 128, 128)
defer clip.Rect{Max: image.Pt(128, 128)}.Push(o).Pop() defer clip.Rect{Max: image.Pt(128, 128)}.Push(o).Pop()
op.Affine(f32.AffineId().Scale(f32.Point{}, f32.Pt(64, 64))).Add(o) op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(64, 64))).Add(o)
im := image.NewRGBA(image.Rect(0, 0, 2, 2)) im := image.NewRGBA(image.Rect(0, 0, 2, 2))
im.Set(0, 0, colornames.Red) im.Set(0, 0, colornames.Red)
@@ -397,7 +397,7 @@ func TestImageRGBA_ScaleLinear(t *testing.T) {
func TestImageRGBA_ScaleNearest(t *testing.T) { func TestImageRGBA_ScaleNearest(t *testing.T) {
run(t, func(o *op.Ops) { run(t, func(o *op.Ops) {
w := newWindow(t, 128, 128) w := newWindow(t, 128, 128)
op.Affine(f32.AffineId().Scale(f32.Point{}, f32.Pt(64, 64))).Add(o) op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(64, 64))).Add(o)
im := image.NewRGBA(image.Rect(0, 0, 2, 2)) im := image.NewRGBA(image.Rect(0, 0, 2, 2))
im.Set(0, 0, colornames.Red) im.Set(0, 0, colornames.Red)
@@ -483,16 +483,17 @@ func TestGapsInPath(t *testing.T) {
func TestOpacity(t *testing.T) { func TestOpacity(t *testing.T) {
run(t, func(ops *op.Ops) { run(t, func(ops *op.Ops) {
opc1 := paint.PushOpacity(ops, .3) opc1 := paint.PushOpacity(ops, .3)
// Fill screen to exercise the glClear optimization. // Fill screen to exercize the glClear optimization.
paint.FillShape(ops, color.NRGBA{R: 255, A: 255}, clip.Rect{Max: image.Pt(1024, 1024)}.Op()) paint.FillShape(ops, color.NRGBA{R: 255, A: 255}, clip.Rect{Max: image.Pt(1024, 1024)}.Op())
opc2 := paint.PushOpacity(ops, .6) opc2 := paint.PushOpacity(ops, .6)
paint.FillShape(ops, color.NRGBA{G: 255, A: 255}, clip.Rect{Min: image.Pt(20, 10), Max: image.Pt(64, 128)}.Op()) paint.FillShape(ops, color.NRGBA{G: 255, A: 255}, clip.Rect{Min: image.Pt(20, 10), Max: image.Pt(64, 128)}.Op())
opc2.Pop() opc2.Pop()
opc1.Pop() opc1.Pop()
opc3 := paint.PushOpacity(ops, .6) opc3 := paint.PushOpacity(ops, .6)
paint.FillShape(ops, color.NRGBA{B: 255, A: 255}, clip.Ellipse(image.Rectangle{Min: image.Pt(20+20, 10), Max: image.Pt(50+64, 128)}).Op(ops)) paint.FillShape(ops, color.NRGBA{G: 255, A: 255}, clip.Rect{Min: image.Pt(50+20, 10), Max: image.Pt(50+64, 128)}.Op())
opc3.Pop() opc3.Pop()
}, nil) }, func(r result) {
})
} }
// lerp calculates linear interpolation with color b and p. // lerp calculates linear interpolation with color b and p.
+12 -12
View File
@@ -29,7 +29,7 @@ func TestPaintOffset(t *testing.T) {
func TestPaintRotate(t *testing.T) { func TestPaintRotate(t *testing.T) {
run(t, func(o *op.Ops) { run(t, func(o *op.Ops) {
a := f32.AffineId().Rotate(f32.Pt(40, 40), -math.Pi/8) a := f32.Affine2D{}.Rotate(f32.Pt(40, 40), -math.Pi/8)
defer op.Affine(a).Push(o).Pop() defer op.Affine(a).Push(o).Pop()
paint.FillShape(o, red, clip.Rect(image.Rect(20, 20, 60, 60)).Op()) paint.FillShape(o, red, clip.Rect(image.Rect(20, 20, 60, 60)).Op())
}, func(r result) { }, func(r result) {
@@ -42,7 +42,7 @@ func TestPaintRotate(t *testing.T) {
func TestPaintShear(t *testing.T) { func TestPaintShear(t *testing.T) {
run(t, func(o *op.Ops) { run(t, func(o *op.Ops) {
a := f32.AffineId().Shear(f32.Point{}, math.Pi/4, 0) a := f32.Affine2D{}.Shear(f32.Point{}, math.Pi/4, 0)
defer op.Affine(a).Push(o).Pop() defer op.Affine(a).Push(o).Pop()
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 40, 40)).Op()) paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 40, 40)).Op())
}, func(r result) { }, func(r result) {
@@ -79,7 +79,7 @@ func TestClipOffset(t *testing.T) {
func TestClipScale(t *testing.T) { func TestClipScale(t *testing.T) {
run(t, func(o *op.Ops) { run(t, func(o *op.Ops) {
a := f32.AffineId().Scale(f32.Point{}, f32.Pt(2, 2)).Offset(f32.Pt(10, 10)) a := f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(2, 2)).Offset(f32.Pt(10, 10))
defer op.Affine(a).Push(o).Pop() defer op.Affine(a).Push(o).Pop()
defer clip.RRect{Rect: image.Rect(10, 10, 20, 20)}.Push(o).Pop() defer clip.RRect{Rect: image.Rect(10, 10, 20, 20)}.Push(o).Pop()
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 1000, 1000)).Op()) paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 1000, 1000)).Op())
@@ -93,7 +93,7 @@ func TestClipScale(t *testing.T) {
func TestClipRotate(t *testing.T) { func TestClipRotate(t *testing.T) {
run(t, func(o *op.Ops) { run(t, func(o *op.Ops) {
defer op.Affine(f32.AffineId().Rotate(f32.Pt(40, 40), -math.Pi/4)).Push(o).Pop() defer op.Affine(f32.Affine2D{}.Rotate(f32.Pt(40, 40), -math.Pi/4)).Push(o).Pop()
defer clip.RRect{Rect: image.Rect(30, 30, 50, 50)}.Push(o).Pop() defer clip.RRect{Rect: image.Rect(30, 30, 50, 50)}.Push(o).Pop()
paint.FillShape(o, red, clip.Rect(image.Rect(0, 40, 100, 100)).Op()) paint.FillShape(o, red, clip.Rect(image.Rect(0, 40, 100, 100)).Op())
}, func(r result) { }, func(r result) {
@@ -121,7 +121,7 @@ func TestOffsetScaleTexture(t *testing.T) {
run(t, func(o *op.Ops) { run(t, func(o *op.Ops) {
defer op.Offset(image.Pt(15, 15)).Push(o).Pop() defer op.Offset(image.Pt(15, 15)).Push(o).Pop()
squares.Add(o) squares.Add(o)
defer op.Affine(f32.AffineId().Scale(f32.Point{}, f32.Pt(2, 1))).Push(o).Pop() defer op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(2, 1))).Push(o).Pop()
defer scale(50.0/512, 50.0/512).Push(o).Pop() defer scale(50.0/512, 50.0/512).Push(o).Pop()
paint.PaintOp{}.Add(o) paint.PaintOp{}.Add(o)
}, func(r result) { }, func(r result) {
@@ -133,7 +133,7 @@ func TestOffsetScaleTexture(t *testing.T) {
func TestRotateTexture(t *testing.T) { func TestRotateTexture(t *testing.T) {
run(t, func(o *op.Ops) { run(t, func(o *op.Ops) {
squares.Add(o) squares.Add(o)
a := f32.AffineId().Offset(f32.Pt(30, 30)).Rotate(f32.Pt(40, 40), math.Pi/4) a := f32.Affine2D{}.Offset(f32.Pt(30, 30)).Rotate(f32.Pt(40, 40), math.Pi/4)
defer op.Affine(a).Push(o).Pop() defer op.Affine(a).Push(o).Pop()
defer scale(20.0/512, 20.0/512).Push(o).Pop() defer scale(20.0/512, 20.0/512).Push(o).Pop()
paint.PaintOp{}.Add(o) paint.PaintOp{}.Add(o)
@@ -146,10 +146,10 @@ func TestRotateTexture(t *testing.T) {
func TestRotateClipTexture(t *testing.T) { func TestRotateClipTexture(t *testing.T) {
run(t, func(o *op.Ops) { run(t, func(o *op.Ops) {
squares.Add(o) squares.Add(o)
a := f32.AffineId().Rotate(f32.Pt(40, 40), math.Pi/8) a := f32.Affine2D{}.Rotate(f32.Pt(40, 40), math.Pi/8)
defer op.Affine(a).Push(o).Pop() defer op.Affine(a).Push(o).Pop()
defer clip.RRect{Rect: image.Rect(30, 30, 50, 50)}.Push(o).Pop() defer clip.RRect{Rect: image.Rect(30, 30, 50, 50)}.Push(o).Pop()
defer op.Affine(f32.AffineId().Offset(f32.Pt(10, 10))).Push(o).Pop() defer op.Affine(f32.Affine2D{}.Offset(f32.Pt(10, 10))).Push(o).Pop()
defer scale(60.0/512, 60.0/512).Push(o).Pop() defer scale(60.0/512, 60.0/512).Push(o).Pop()
paint.PaintOp{}.Add(o) paint.PaintOp{}.Add(o)
}, func(r result) { }, func(r result) {
@@ -168,7 +168,7 @@ func TestComplicatedTransform(t *testing.T) {
defer clip.RRect{Rect: image.Rect(0, 0, 100, 100), SE: 50, SW: 50, NW: 50, NE: 50}.Push(o).Pop() defer clip.RRect{Rect: image.Rect(0, 0, 100, 100), SE: 50, SW: 50, NW: 50, NE: 50}.Push(o).Pop()
a := f32.AffineId().Shear(f32.Point{}, math.Pi/4, 0) a := f32.Affine2D{}.Shear(f32.Point{}, math.Pi/4, 0)
defer op.Affine(a).Push(o).Pop() defer op.Affine(a).Push(o).Pop()
defer clip.RRect{Rect: image.Rect(0, 0, 50, 40)}.Push(o).Pop() defer clip.RRect{Rect: image.Rect(0, 0, 50, 40)}.Push(o).Pop()
@@ -182,13 +182,13 @@ func TestComplicatedTransform(t *testing.T) {
func TestTransformOrder(t *testing.T) { func TestTransformOrder(t *testing.T) {
// check the ordering of operations bot in affine and in gpu stack. // check the ordering of operations bot in affine and in gpu stack.
run(t, func(o *op.Ops) { run(t, func(o *op.Ops) {
a := f32.AffineId().Offset(f32.Pt(64, 64)) a := f32.Affine2D{}.Offset(f32.Pt(64, 64))
defer op.Affine(a).Push(o).Pop() defer op.Affine(a).Push(o).Pop()
b := f32.AffineId().Scale(f32.Point{}, f32.Pt(8, 8)) b := f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(8, 8))
defer op.Affine(b).Push(o).Pop() defer op.Affine(b).Push(o).Pop()
c := f32.AffineId().Offset(f32.Pt(-10, -10)).Scale(f32.Point{}, f32.Pt(0.5, 0.5)) c := f32.Affine2D{}.Offset(f32.Pt(-10, -10)).Scale(f32.Point{}, f32.Pt(0.5, 0.5))
defer op.Affine(c).Push(o).Pop() defer op.Affine(c).Push(o).Pop()
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 20, 20)).Op()) paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 20, 20)).Op())
}, func(r result) { }, func(r result) {
+7 -9
View File
@@ -49,8 +49,8 @@ func buildSquares(size int) paint.ImageOp {
sub := size / 4 sub := size / 4
im := image.NewNRGBA(image.Rect(0, 0, size, size)) im := image.NewNRGBA(image.Rect(0, 0, size, size))
c1, c2 := image.NewUniform(colornames.Green), image.NewUniform(colornames.Blue) c1, c2 := image.NewUniform(colornames.Green), image.NewUniform(colornames.Blue)
for r := range 4 { for r := 0; r < 4; r++ {
for c := range 4 { for c := 0; c < 4; c++ {
c1, c2 = c2, c1 c1, c2 = c2, c1
draw.Draw(im, image.Rect(r*sub, c*sub, r*sub+sub, c*sub+sub), c1, image.Point{}, draw.Over) draw.Draw(im, image.Rect(r*sub, c*sub, r*sub+sub, c*sub+sub), c1, image.Point{}, draw.Over)
} }
@@ -78,7 +78,7 @@ func run(t *testing.T, f func(o *op.Ops), c func(r result)) {
var img *image.RGBA var img *image.RGBA
var err error var err error
ops := new(op.Ops) ops := new(op.Ops)
for i := range 3 { for i := 0; i < 3; i++ {
ops.Reset() ops.Reset()
img, err = drawImage(t, 128, ops, f) img, err = drawImage(t, 128, ops, f)
if err != nil { if err != nil {
@@ -90,9 +90,7 @@ func run(t *testing.T, f func(o *op.Ops), c func(r result)) {
name := fmt.Sprintf("%s-%d-bad.png", t.Name(), i) name := fmt.Sprintf("%s-%d-bad.png", t.Name(), i)
saveImage(t, name, img) saveImage(t, name, img)
} }
if c != nil { c(result{t: t, img: img})
c(result{t: t, img: img})
}
} }
} }
@@ -153,7 +151,7 @@ func verifyRef(t *testing.T, img *image.RGBA, frame int) (ok bool) {
} }
path = filepath.Join("refs", path+".png") path = filepath.Join("refs", path+".png")
if *dumpImages { if *dumpImages {
if err := os.MkdirAll(filepath.Dir(path), 0o766); err != nil { if err := os.MkdirAll(filepath.Dir(path), 0766); err != nil {
if !os.IsExist(err) { if !os.IsExist(err) {
t.Error(err) t.Error(err)
return return
@@ -287,7 +285,7 @@ func saveImage(t testing.TB, file string, img *image.RGBA) {
t.Error(err) t.Error(err)
return return
} }
if err := os.WriteFile(file, buf.Bytes(), 0o666); err != nil { if err := os.WriteFile(file, buf.Bytes(), 0666); err != nil {
t.Error(err) t.Error(err)
return return
} }
@@ -302,5 +300,5 @@ func newWindow(t testing.TB, width, height int) *headless.Window {
} }
func scale(sx, sy float32) op.TransformOp { func scale(sx, sy float32) op.TransformOp {
return op.Affine(f32.AffineId().Scale(f32.Point{}, f32.Pt(sx, sy))) return op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(sx, sy)))
} }
+2 -2
View File
@@ -862,8 +862,8 @@ func (b *Backend) BindUniforms(buffer driver.Buffer) {
buf := buffer.(*Buffer) buf := buffer.(*Buffer)
cmdBuf := b.currentCmdBuf() cmdBuf := b.currentCmdBuf()
for _, s := range b.pipe.pushRanges { for _, s := range b.pipe.pushRanges {
off := vk.PushConstantRangeOffset(s) off := s.Offset()
vk.CmdPushConstants(cmdBuf, b.pipe.desc.layout, vk.PushConstantRangeStageFlags(s), off, buf.store[off:off+vk.PushConstantRangeSize(s)]) vk.CmdPushConstants(cmdBuf, b.pipe.desc.layout, s.StageFlags(), off, buf.store[off:off+s.Size()])
} }
} }
+2 -2
View File
@@ -10,10 +10,10 @@ import (
func BenchmarkPacker(b *testing.B) { func BenchmarkPacker(b *testing.B) {
var p packer var p packer
p.maxDims = image.Point{X: 4096, Y: 4096} p.maxDims = image.Point{X: 4096, Y: 4096}
for i := 0; b.Loop(); i++ { for i := 0; i < b.N; i++ {
p.clear() p.clear()
p.newPage() p.newPage()
for k := range 500 { for k := 0; k < 500; k++ {
_, ok := p.tryAdd(xy(k)) _, ok := p.tryAdd(xy(k))
if !ok { if !ok {
b.Fatal("add failed", i, k, xy(k)) b.Fatal("add failed", i, k, xy(k))
+8 -14
View File
@@ -30,7 +30,7 @@ type pather struct {
type coverer struct { type coverer struct {
ctx driver.Device ctx driver.Device
pipelines [2][3]*pipeline pipelines [3]*pipeline
texUniforms *coverTexUniforms texUniforms *coverTexUniforms
colUniforms *coverColUniforms colUniforms *coverColUniforms
linearGradientUniforms *coverLinearGradientUniforms linearGradientUniforms *coverLinearGradientUniforms
@@ -150,7 +150,7 @@ func newCoverer(ctx driver.Device) *coverer {
c.texUniforms = new(coverTexUniforms) c.texUniforms = new(coverTexUniforms)
c.linearGradientUniforms = new(coverLinearGradientUniforms) c.linearGradientUniforms = new(coverLinearGradientUniforms)
pipelines, err := createColorPrograms(ctx, gio.Shader_cover_vert, gio.Shader_cover_frag, pipelines, err := createColorPrograms(ctx, gio.Shader_cover_vert, gio.Shader_cover_frag,
[3]any{c.colUniforms, c.linearGradientUniforms, c.texUniforms}, [3]interface{}{c.colUniforms, c.linearGradientUniforms, c.texUniforms},
) )
if err != nil { if err != nil {
panic(err) panic(err)
@@ -162,7 +162,7 @@ func newCoverer(ctx driver.Device) *coverer {
func newStenciler(ctx driver.Device) *stenciler { func newStenciler(ctx driver.Device) *stenciler {
// Allocate a suitably large index buffer for drawing paths. // Allocate a suitably large index buffer for drawing paths.
indices := make([]uint16, pathBatchSize*6) indices := make([]uint16, pathBatchSize*6)
for i := range pathBatchSize { for i := 0; i < pathBatchSize; i++ {
i := uint16(i) i := uint16(i)
indices[i*6+0] = i*4 + 0 indices[i*6+0] = i*4 + 0
indices[i*6+1] = i*4 + 1 indices[i*6+1] = i*4 + 1
@@ -309,9 +309,7 @@ func (p *pather) release() {
func (c *coverer) release() { func (c *coverer) release() {
for _, p := range c.pipelines { for _, p := range c.pipelines {
for _, p := range p { p.Release()
p.Release()
}
} }
} }
@@ -334,7 +332,7 @@ func (p *pather) begin(sizes []image.Point) {
p.stenciler.begin(sizes) p.stenciler.begin(sizes)
} }
func (p *pather) stencilPath(bounds image.Rectangle, offset image.Point, uv image.Point, data pathData) { func (p *pather) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) {
p.stenciler.stencilPath(bounds, offset, uv, data) p.stenciler.stencilPath(bounds, offset, uv, data)
} }
@@ -353,14 +351,14 @@ func (s *stenciler) begin(sizes []image.Point) {
s.fbos.resize(s.ctx, driver.TextureFormatFloat, sizes) s.fbos.resize(s.ctx, driver.TextureFormatFloat, sizes)
} }
func (s *stenciler) stencilPath(bounds image.Rectangle, offset image.Point, uv image.Point, data pathData) { func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) {
s.ctx.Viewport(uv.X, uv.Y, bounds.Dx(), bounds.Dy()) s.ctx.Viewport(uv.X, uv.Y, bounds.Dx(), bounds.Dy())
// Transform UI coordinates to OpenGL coordinates. // Transform UI coordinates to OpenGL coordinates.
texSize := f32.Point{X: float32(bounds.Dx()), Y: float32(bounds.Dy())} texSize := f32.Point{X: float32(bounds.Dx()), Y: float32(bounds.Dy())}
scale := f32.Point{X: 2 / texSize.X, Y: 2 / texSize.Y} scale := f32.Point{X: 2 / texSize.X, Y: 2 / texSize.Y}
orig := f32.Point{X: -1 - float32(bounds.Min.X)*2/texSize.X, Y: -1 - float32(bounds.Min.Y)*2/texSize.Y} orig := f32.Point{X: -1 - float32(bounds.Min.X)*2/texSize.X, Y: -1 - float32(bounds.Min.Y)*2/texSize.Y}
s.pipeline.uniforms.transform = [4]float32{scale.X, scale.Y, orig.X, orig.Y} s.pipeline.uniforms.transform = [4]float32{scale.X, scale.Y, orig.X, orig.Y}
s.pipeline.uniforms.pathOffset = [2]float32{float32(offset.X), float32(offset.Y)} s.pipeline.uniforms.pathOffset = [2]float32{offset.X, offset.Y}
s.pipeline.pipeline.UploadUniforms(s.ctx) s.pipeline.pipeline.UploadUniforms(s.ctx)
// Draw in batches that fit in uint16 indices. // Draw in batches that fit in uint16 indices.
start := 0 start := 0
@@ -407,11 +405,7 @@ func (c *coverer) cover(mat materialType, isFBO bool, col f32color.RGBA, col1, c
} }
uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y} uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
uniforms.uvCoverTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y} uniforms.uvCoverTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y}
fboIdx := 0 c.pipelines[mat].UploadUniforms(c.ctx)
if isFBO {
fboIdx = 1
}
c.pipelines[fboIdx][mat].UploadUniforms(c.ctx)
c.ctx.DrawArrays(0, 4) c.ctx.DrawArrays(0, 4)
} }
+2 -2
View File
@@ -10,7 +10,7 @@ import (
) )
// Struct returns a byte slice view of a struct. // Struct returns a byte slice view of a struct.
func Struct(s any) []byte { func Struct(s interface{}) []byte {
v := reflect.ValueOf(s) v := reflect.ValueOf(s)
sz := int(v.Elem().Type().Size()) sz := int(v.Elem().Type().Size())
return unsafe.Slice((*byte)(unsafe.Pointer(v.Pointer())), sz) return unsafe.Slice((*byte)(unsafe.Pointer(v.Pointer())), sz)
@@ -27,7 +27,7 @@ func Uint32(s []uint32) []byte {
} }
// Slice returns a byte slice view of a slice. // Slice returns a byte slice view of a slice.
func Slice(s any) []byte { func Slice(s interface{}) []byte {
v := reflect.ValueOf(s) v := reflect.ValueOf(s)
first := v.Index(0) first := v.Index(0)
sz := int(first.Type().Size()) sz := int(first.Type().Size())
+13 -6
View File
@@ -9,16 +9,16 @@ import (
"errors" "errors"
"fmt" "fmt"
"runtime" "runtime"
"slices"
"strings" "strings"
"gioui.org/gpu" "gioui.org/gpu"
) )
type Context struct { type Context struct {
disp _EGLDisplay disp _EGLDisplay
eglCtx *eglContext eglCtx *eglContext
eglSurf _EGLSurface eglSurf _EGLSurface
width, height int
} }
type eglContext struct { type eglContext struct {
@@ -121,9 +121,11 @@ func (c *Context) VisualID() int {
return c.eglCtx.visualID return c.eglCtx.visualID
} }
func (c *Context) CreateSurface(win NativeWindowType) error { func (c *Context) CreateSurface(win NativeWindowType, width, height int) error {
eglSurf, err := createSurface(c.disp, c.eglCtx, win) eglSurf, err := createSurface(c.disp, c.eglCtx, win)
c.eglSurf = eglSurf c.eglSurf = eglSurf
c.width = width
c.height = height
return err return err
} }
@@ -155,7 +157,12 @@ func (c *Context) EnableVSync(enable bool) {
} }
func hasExtension(exts []string, ext string) bool { func hasExtension(exts []string, ext string) bool {
return slices.Contains(exts, ext) for _, e := range exts {
if ext == e {
return true
}
}
return false
} }
func createContext(disp _EGLDisplay) (*eglContext, error) { func createContext(disp _EGLDisplay) (*eglContext, error) {
+27 -49
View File
@@ -9,6 +9,8 @@ import (
"unsafe" "unsafe"
syscall "golang.org/x/sys/windows" syscall "golang.org/x/sys/windows"
"gioui.org/internal/gl"
) )
type ( type (
@@ -22,23 +24,23 @@ type (
) )
var ( var (
libEGL = syscall.DLL{} libEGL = syscall.NewLazyDLL("libEGL.dll")
_eglChooseConfig *syscall.Proc _eglChooseConfig = libEGL.NewProc("eglChooseConfig")
_eglCreateContext *syscall.Proc _eglCreateContext = libEGL.NewProc("eglCreateContext")
_eglCreateWindowSurface *syscall.Proc _eglCreateWindowSurface = libEGL.NewProc("eglCreateWindowSurface")
_eglDestroyContext *syscall.Proc _eglDestroyContext = libEGL.NewProc("eglDestroyContext")
_eglDestroySurface *syscall.Proc _eglDestroySurface = libEGL.NewProc("eglDestroySurface")
_eglGetConfigAttrib *syscall.Proc _eglGetConfigAttrib = libEGL.NewProc("eglGetConfigAttrib")
_eglGetDisplay *syscall.Proc _eglGetDisplay = libEGL.NewProc("eglGetDisplay")
_eglGetError *syscall.Proc _eglGetError = libEGL.NewProc("eglGetError")
_eglInitialize *syscall.Proc _eglInitialize = libEGL.NewProc("eglInitialize")
_eglMakeCurrent *syscall.Proc _eglMakeCurrent = libEGL.NewProc("eglMakeCurrent")
_eglReleaseThread *syscall.Proc _eglReleaseThread = libEGL.NewProc("eglReleaseThread")
_eglSwapInterval *syscall.Proc _eglSwapInterval = libEGL.NewProc("eglSwapInterval")
_eglSwapBuffers *syscall.Proc _eglSwapBuffers = libEGL.NewProc("eglSwapBuffers")
_eglTerminate *syscall.Proc _eglTerminate = libEGL.NewProc("eglTerminate")
_eglQueryString *syscall.Proc _eglQueryString = libEGL.NewProc("eglQueryString")
_eglWaitClient *syscall.Proc _eglWaitClient = libEGL.NewProc("eglWaitClient")
) )
var loadOnce sync.Once var loadOnce sync.Once
@@ -52,45 +54,21 @@ func loadEGL() error {
} }
func loadDLLs() error { func loadDLLs() error {
if err := loadDLL(&libEGL, "libEGL.dll"); err != nil { if err := loadDLL(libEGL, "libEGL.dll"); err != nil {
return err return err
} }
if err := loadDLL(gl.LibGLESv2, "libGLESv2.dll"); err != nil {
procs := map[string]**syscall.Proc{ return err
"eglChooseConfig": &_eglChooseConfig,
"eglCreateContext": &_eglCreateContext,
"eglCreateWindowSurface": &_eglCreateWindowSurface,
"eglDestroyContext": &_eglDestroyContext,
"eglDestroySurface": &_eglDestroySurface,
"eglGetConfigAttrib": &_eglGetConfigAttrib,
"eglGetDisplay": &_eglGetDisplay,
"eglGetError": &_eglGetError,
"eglInitialize": &_eglInitialize,
"eglMakeCurrent": &_eglMakeCurrent,
"eglReleaseThread": &_eglReleaseThread,
"eglSwapInterval": &_eglSwapInterval,
"eglSwapBuffers": &_eglSwapBuffers,
"eglTerminate": &_eglTerminate,
"eglQueryString": &_eglQueryString,
"eglWaitClient": &_eglWaitClient,
} }
for name, proc := range procs { // d3dcompiler_47.dll is needed internally for shader compilation to function.
p, err := libEGL.FindProc(name) return loadDLL(syscall.NewLazyDLL("d3dcompiler_47.dll"), "d3dcompiler_47.dll")
if err != nil {
return fmt.Errorf("failed to locate %s in %s: %w", name, libEGL.Name, err)
}
*proc = p
}
return nil
} }
func loadDLL(dll *syscall.DLL, name string) error { func loadDLL(dll *syscall.LazyDLL, name string) error {
handle, err := syscall.LoadLibraryEx(name, 0, syscall.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS) err := dll.Load()
if err != nil { if err != nil {
return fmt.Errorf("egl: failed to load %s: %v", name, err) return fmt.Errorf("egl: failed to load %s: %v", name, err)
} }
dll.Handle = handle
dll.Name = name
return nil return nil
} }
@@ -186,6 +164,6 @@ func eglWaitClient() bool {
// issue34474KeepAlive calls runtime.KeepAlive as a // issue34474KeepAlive calls runtime.KeepAlive as a
// workaround for golang.org/issue/34474. // workaround for golang.org/issue/34474.
func issue34474KeepAlive(v any) { func issue34474KeepAlive(v interface{}) {
runtime.KeepAlive(v) runtime.KeepAlive(v)
} }
-2
View File
@@ -19,8 +19,6 @@ type Affine2D = f32.Affine2D
var NewAffine2D = f32.NewAffine2D var NewAffine2D = f32.NewAffine2D
var AffineId = f32.AffineId
// A Rectangle contains the points (X, Y) where Min.X <= X < Max.X, // A Rectangle contains the points (X, Y) where Min.X <= X < Max.X,
// Min.Y <= Y < Max.Y. // Min.Y <= Y < Max.Y.
type Rectangle struct { type Rectangle struct {
+2 -2
View File
@@ -16,7 +16,7 @@ func main() {
flag.Parse() flag.Parse()
var b bytes.Buffer var b bytes.Buffer
printf := func(content string, args ...any) { printf := func(content string, args ...interface{}) {
fmt.Fprintf(&b, content, args...) fmt.Fprintf(&b, content, args...)
} }
@@ -42,7 +42,7 @@ func main() {
panic(err) panic(err)
} }
err = os.WriteFile(*out, data, 0o755) err = os.WriteFile(*out, data, 0755)
if err != nil { if err != nil {
panic(err) panic(err)
} }
+3 -3
View File
@@ -41,17 +41,17 @@ var sink RGBA
func BenchmarkLinearFromSRGB(b *testing.B) { func BenchmarkLinearFromSRGB(b *testing.B) {
b.Run("opaque", func(b *testing.B) { b.Run("opaque", func(b *testing.B) {
for i := 0; b.Loop(); i++ { for i := 0; i < b.N; i++ {
sink = LinearFromSRGB(color.NRGBA{R: byte(i), G: byte(i >> 8), B: byte(i >> 16), A: 0xFF}) sink = LinearFromSRGB(color.NRGBA{R: byte(i), G: byte(i >> 8), B: byte(i >> 16), A: 0xFF})
} }
}) })
b.Run("translucent", func(b *testing.B) { b.Run("translucent", func(b *testing.B) {
for i := 0; b.Loop(); i++ { for i := 0; i < b.N; i++ {
sink = LinearFromSRGB(color.NRGBA{R: byte(i), G: byte(i >> 8), B: byte(i >> 16), A: 0x50}) sink = LinearFromSRGB(color.NRGBA{R: byte(i), G: byte(i >> 8), B: byte(i >> 16), A: 0x50})
} }
}) })
b.Run("transparent", func(b *testing.B) { b.Run("transparent", func(b *testing.B) {
for i := 0; b.Loop(); i++ { for i := 0; i < b.N; i++ {
sink = LinearFromSRGB(color.NRGBA{R: byte(i), G: byte(i >> 8), B: byte(i >> 16), A: 0x00}) sink = LinearFromSRGB(color.NRGBA{R: byte(i), G: byte(i >> 8), B: byte(i >> 16), A: 0x00})
} }
}) })
+15 -15
View File
@@ -90,7 +90,7 @@ func (e *Extrapolation) Estimate() Estimate {
first := e.get(0) first := e.get(0)
t := first.t t := first.t
// Walk backwards collecting samples. // Walk backwards collecting samples.
for i := range e.samples { for i := 0; i < len(e.samples); i++ {
p := e.get(-i) p := e.get(-i)
age := first.t - p.t age := first.t - p.t
if age >= maxAge || t-p.t >= maxSampleGap { if age >= maxAge || t-p.t >= maxSampleGap {
@@ -172,9 +172,9 @@ func decomposeQR(A *matrix) (*matrix, *matrix, bool) {
// https://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process // https://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process
Q := newMatrix(A.rows, A.cols) // Column-major. Q := newMatrix(A.rows, A.cols) // Column-major.
Rt := newMatrix(A.rows, A.rows) // R transposed, row-major. Rt := newMatrix(A.rows, A.rows) // R transposed, row-major.
for i := range Q.rows { for i := 0; i < Q.rows; i++ {
// Copy A column. // Copy A column.
for j := range Q.cols { for j := 0; j < Q.cols; j++ {
Q.set(i, j, A.get(i, j)) Q.set(i, j, A.get(i, j))
} }
// Subtract projections. Note that int the projection // Subtract projections. Note that int the projection
@@ -184,9 +184,9 @@ func decomposeQR(A *matrix) (*matrix, *matrix, bool) {
// the normalized column e replaces u, where <e, e> = 1: // the normalized column e replaces u, where <e, e> = 1:
// //
// proje a = <e, a>/<e, e> e = <e, a> e // proje a = <e, a>/<e, e> e = <e, a> e
for j := range i { for j := 0; j < i; j++ {
d := dot(Q.col(j), Q.col(i)) d := dot(Q.col(j), Q.col(i))
for k := range Q.cols { for k := 0; k < Q.cols; k++ {
Q.set(i, k, Q.get(i, k)-d*Q.get(j, k)) Q.set(i, k, Q.get(i, k)-d*Q.get(j, k))
} }
} }
@@ -197,7 +197,7 @@ func decomposeQR(A *matrix) (*matrix, *matrix, bool) {
return nil, nil, false return nil, nil, false
} }
invNorm := 1 / n invNorm := 1 / n
for j := range Q.cols { for j := 0; j < Q.cols; j++ {
Q.set(i, j, Q.get(i, j)*invNorm) Q.set(i, j, Q.get(i, j)*invNorm)
} }
// Update Rt. // Update Rt.
@@ -261,8 +261,8 @@ func (m *matrix) approxEqual(m2 *matrix) bool {
return false return false
} }
const epsilon = 0.00001 const epsilon = 0.00001
for row := range m.rows { for row := 0; row < m.rows; row++ {
for col := range m.cols { for col := 0; col < m.cols; col++ {
d := m2.get(row, col) - m.get(row, col) d := m2.get(row, col) - m.get(row, col)
if d < -epsilon || d > epsilon { if d < -epsilon || d > epsilon {
return false return false
@@ -278,8 +278,8 @@ func (m *matrix) transpose() *matrix {
cols: m.rows, cols: m.rows,
data: make([]float32, len(m.data)), data: make([]float32, len(m.data)),
} }
for i := range m.rows { for i := 0; i < m.rows; i++ {
for j := range m.cols { for j := 0; j < m.cols; j++ {
t.set(j, i, m.get(i, j)) t.set(j, i, m.get(i, j))
} }
} }
@@ -295,10 +295,10 @@ func (m *matrix) mul(m2 *matrix) *matrix {
cols: m2.cols, cols: m2.cols,
data: make([]float32, m.rows*m2.cols), data: make([]float32, m.rows*m2.cols),
} }
for i := range mm.rows { for i := 0; i < mm.rows; i++ {
for j := range mm.cols { for j := 0; j < mm.cols; j++ {
var v float32 var v float32
for k := range m.rows { for k := 0; k < m.rows; k++ {
v += m.get(k, j) * m2.get(i, k) v += m.get(k, j) * m2.get(i, k)
} }
mm.set(i, j, v) mm.set(i, j, v)
@@ -309,8 +309,8 @@ func (m *matrix) mul(m2 *matrix) *matrix {
func (m *matrix) String() string { func (m *matrix) String() string {
var b strings.Builder var b strings.Builder
for i := range m.rows { for i := 0; i < m.rows; i++ {
for j := range m.cols { for j := 0; j < m.cols; j++ {
v := m.get(i, j) v := m.get(i, j)
b.WriteString(strconv.FormatFloat(float64(v), 'g', -1, 32)) b.WriteString(strconv.FormatFloat(float64(v), 'g', -1, 32))
b.WriteString(", ") b.WriteString(", ")
-95
View File
@@ -247,11 +247,9 @@ func (f *Functions) getExtension(name string) js.Value {
func (f *Functions) ActiveTexture(t Enum) { func (f *Functions) ActiveTexture(t Enum) {
f._activeTexture.Invoke(int(t)) f._activeTexture.Invoke(int(t))
} }
func (f *Functions) AttachShader(p Program, s Shader) { func (f *Functions) AttachShader(p Program, s Shader) {
f._attachShader.Invoke(js.Value(p), js.Value(s)) f._attachShader.Invoke(js.Value(p), js.Value(s))
} }
func (f *Functions) BeginQuery(target Enum, query Query) { func (f *Functions) BeginQuery(target Enum, query Query) {
if !f.EXT_disjoint_timer_query_webgl2.IsNull() { if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
f._beginQuery.Invoke(int(target), js.Value(query)) f._beginQuery.Invoke(int(target), js.Value(query))
@@ -259,47 +257,36 @@ func (f *Functions) BeginQuery(target Enum, query Query) {
f.EXT_disjoint_timer_query.Call("beginQueryEXT", int(target), js.Value(query)) f.EXT_disjoint_timer_query.Call("beginQueryEXT", int(target), js.Value(query))
} }
} }
func (f *Functions) BindAttribLocation(p Program, a Attrib, name string) { func (f *Functions) BindAttribLocation(p Program, a Attrib, name string) {
f._bindAttribLocation.Invoke(js.Value(p), int(a), name) f._bindAttribLocation.Invoke(js.Value(p), int(a), name)
} }
func (f *Functions) BindBuffer(target Enum, b Buffer) { func (f *Functions) BindBuffer(target Enum, b Buffer) {
f._bindBuffer.Invoke(int(target), js.Value(b)) f._bindBuffer.Invoke(int(target), js.Value(b))
} }
func (f *Functions) BindBufferBase(target Enum, index int, b Buffer) { func (f *Functions) BindBufferBase(target Enum, index int, b Buffer) {
f._bindBufferBase.Invoke(int(target), index, js.Value(b)) f._bindBufferBase.Invoke(int(target), index, js.Value(b))
} }
func (f *Functions) BindFramebuffer(target Enum, fb Framebuffer) { func (f *Functions) BindFramebuffer(target Enum, fb Framebuffer) {
f._bindFramebuffer.Invoke(int(target), js.Value(fb)) f._bindFramebuffer.Invoke(int(target), js.Value(fb))
} }
func (f *Functions) BindRenderbuffer(target Enum, rb Renderbuffer) { func (f *Functions) BindRenderbuffer(target Enum, rb Renderbuffer) {
f._bindRenderbuffer.Invoke(int(target), js.Value(rb)) f._bindRenderbuffer.Invoke(int(target), js.Value(rb))
} }
func (f *Functions) BindTexture(target Enum, t Texture) { func (f *Functions) BindTexture(target Enum, t Texture) {
f._bindTexture.Invoke(int(target), js.Value(t)) f._bindTexture.Invoke(int(target), js.Value(t))
} }
func (f *Functions) BindImageTexture(unit int, t Texture, level int, layered bool, layer int, access, format Enum) { func (f *Functions) BindImageTexture(unit int, t Texture, level int, layered bool, layer int, access, format Enum) {
panic("not implemented") panic("not implemented")
} }
func (f *Functions) BindVertexArray(a VertexArray) { func (f *Functions) BindVertexArray(a VertexArray) {
panic("not supported") panic("not supported")
} }
func (f *Functions) BlendEquation(mode Enum) { func (f *Functions) BlendEquation(mode Enum) {
f._blendEquation.Invoke(int(mode)) f._blendEquation.Invoke(int(mode))
} }
func (f *Functions) BlendFuncSeparate(srcRGB, dstRGB, srcA, dstA Enum) { func (f *Functions) BlendFuncSeparate(srcRGB, dstRGB, srcA, dstA Enum) {
f._blendFunc.Invoke(int(srcRGB), int(dstRGB), int(srcA), int(dstA)) f._blendFunc.Invoke(int(srcRGB), int(dstRGB), int(srcA), int(dstA))
} }
func (f *Functions) BufferData(target Enum, size int, usage Enum, data []byte) { func (f *Functions) BufferData(target Enum, size int, usage Enum, data []byte) {
if data == nil { if data == nil {
f._bufferData.Invoke(int(target), size, int(usage)) f._bufferData.Invoke(int(target), size, int(usage))
@@ -310,11 +297,9 @@ func (f *Functions) BufferData(target Enum, size int, usage Enum, data []byte) {
f._bufferData.Invoke(int(target), f.byteArrayOf(data), int(usage)) f._bufferData.Invoke(int(target), f.byteArrayOf(data), int(usage))
} }
} }
func (f *Functions) BufferSubData(target Enum, offset int, src []byte) { func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
f._bufferSubData.Invoke(int(target), offset, f.byteArrayOf(src)) f._bufferSubData.Invoke(int(target), offset, f.byteArrayOf(src))
} }
func (f *Functions) CheckFramebufferStatus(target Enum) Enum { func (f *Functions) CheckFramebufferStatus(target Enum) Enum {
status := Enum(f._checkFramebufferStatus.Invoke(int(target)).Int()) status := Enum(f._checkFramebufferStatus.Invoke(int(target)).Int())
if status != FRAMEBUFFER_COMPLETE && f.Ctx.Call("isContextLost").Bool() { if status != FRAMEBUFFER_COMPLETE && f.Ctx.Call("isContextLost").Bool() {
@@ -323,71 +308,54 @@ func (f *Functions) CheckFramebufferStatus(target Enum) Enum {
} }
return status return status
} }
func (f *Functions) Clear(mask Enum) { func (f *Functions) Clear(mask Enum) {
f._clear.Invoke(int(mask)) f._clear.Invoke(int(mask))
} }
func (f *Functions) ClearColor(red, green, blue, alpha float32) { func (f *Functions) ClearColor(red, green, blue, alpha float32) {
f._clearColor.Invoke(red, green, blue, alpha) f._clearColor.Invoke(red, green, blue, alpha)
} }
func (f *Functions) ClearDepthf(d float32) { func (f *Functions) ClearDepthf(d float32) {
f._clearDepth.Invoke(d) f._clearDepth.Invoke(d)
} }
func (f *Functions) CompileShader(s Shader) { func (f *Functions) CompileShader(s Shader) {
f._compileShader.Invoke(js.Value(s)) f._compileShader.Invoke(js.Value(s))
} }
func (f *Functions) CopyTexSubImage2D(target Enum, level, xoffset, yoffset, x, y, width, height int) { func (f *Functions) CopyTexSubImage2D(target Enum, level, xoffset, yoffset, x, y, width, height int) {
f._copyTexSubImage2D.Invoke(int(target), level, xoffset, yoffset, x, y, width, height) f._copyTexSubImage2D.Invoke(int(target), level, xoffset, yoffset, x, y, width, height)
} }
func (f *Functions) CreateBuffer() Buffer { func (f *Functions) CreateBuffer() Buffer {
return Buffer(f._createBuffer.Invoke()) return Buffer(f._createBuffer.Invoke())
} }
func (f *Functions) CreateFramebuffer() Framebuffer { func (f *Functions) CreateFramebuffer() Framebuffer {
return Framebuffer(f._createFramebuffer.Invoke()) return Framebuffer(f._createFramebuffer.Invoke())
} }
func (f *Functions) CreateProgram() Program { func (f *Functions) CreateProgram() Program {
return Program(f._createProgram.Invoke()) return Program(f._createProgram.Invoke())
} }
func (f *Functions) CreateQuery() Query { func (f *Functions) CreateQuery() Query {
return Query(f._createQuery.Invoke()) return Query(f._createQuery.Invoke())
} }
func (f *Functions) CreateRenderbuffer() Renderbuffer { func (f *Functions) CreateRenderbuffer() Renderbuffer {
return Renderbuffer(f._createRenderbuffer.Invoke()) return Renderbuffer(f._createRenderbuffer.Invoke())
} }
func (f *Functions) CreateShader(ty Enum) Shader { func (f *Functions) CreateShader(ty Enum) Shader {
return Shader(f._createShader.Invoke(int(ty))) return Shader(f._createShader.Invoke(int(ty)))
} }
func (f *Functions) CreateTexture() Texture { func (f *Functions) CreateTexture() Texture {
return Texture(f._createTexture.Invoke()) return Texture(f._createTexture.Invoke())
} }
func (f *Functions) CreateVertexArray() VertexArray { func (f *Functions) CreateVertexArray() VertexArray {
panic("not supported") panic("not supported")
} }
func (f *Functions) DeleteBuffer(v Buffer) { func (f *Functions) DeleteBuffer(v Buffer) {
f._deleteBuffer.Invoke(js.Value(v)) f._deleteBuffer.Invoke(js.Value(v))
} }
func (f *Functions) DeleteFramebuffer(v Framebuffer) { func (f *Functions) DeleteFramebuffer(v Framebuffer) {
f._deleteFramebuffer.Invoke(js.Value(v)) f._deleteFramebuffer.Invoke(js.Value(v))
} }
func (f *Functions) DeleteProgram(p Program) { func (f *Functions) DeleteProgram(p Program) {
f._deleteProgram.Invoke(js.Value(p)) f._deleteProgram.Invoke(js.Value(p))
} }
func (f *Functions) DeleteQuery(query Query) { func (f *Functions) DeleteQuery(query Query) {
if !f.EXT_disjoint_timer_query_webgl2.IsNull() { if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
f._deleteQuery.Invoke(js.Value(query)) f._deleteQuery.Invoke(js.Value(query))
@@ -395,59 +363,45 @@ func (f *Functions) DeleteQuery(query Query) {
f.EXT_disjoint_timer_query.Call("deleteQueryEXT", js.Value(query)) f.EXT_disjoint_timer_query.Call("deleteQueryEXT", js.Value(query))
} }
} }
func (f *Functions) DeleteShader(s Shader) { func (f *Functions) DeleteShader(s Shader) {
f._deleteShader.Invoke(js.Value(s)) f._deleteShader.Invoke(js.Value(s))
} }
func (f *Functions) DeleteRenderbuffer(v Renderbuffer) { func (f *Functions) DeleteRenderbuffer(v Renderbuffer) {
f._deleteRenderbuffer.Invoke(js.Value(v)) f._deleteRenderbuffer.Invoke(js.Value(v))
} }
func (f *Functions) DeleteTexture(v Texture) { func (f *Functions) DeleteTexture(v Texture) {
f._deleteTexture.Invoke(js.Value(v)) f._deleteTexture.Invoke(js.Value(v))
} }
func (f *Functions) DeleteVertexArray(a VertexArray) { func (f *Functions) DeleteVertexArray(a VertexArray) {
panic("not implemented") panic("not implemented")
} }
func (f *Functions) DepthFunc(fn Enum) { func (f *Functions) DepthFunc(fn Enum) {
f._depthFunc.Invoke(int(fn)) f._depthFunc.Invoke(int(fn))
} }
func (f *Functions) DepthMask(mask bool) { func (f *Functions) DepthMask(mask bool) {
f._depthMask.Invoke(mask) f._depthMask.Invoke(mask)
} }
func (f *Functions) DisableVertexAttribArray(a Attrib) { func (f *Functions) DisableVertexAttribArray(a Attrib) {
f._disableVertexAttribArray.Invoke(int(a)) f._disableVertexAttribArray.Invoke(int(a))
} }
func (f *Functions) Disable(cap Enum) { func (f *Functions) Disable(cap Enum) {
f._disable.Invoke(int(cap)) f._disable.Invoke(int(cap))
} }
func (f *Functions) DrawArrays(mode Enum, first, count int) { func (f *Functions) DrawArrays(mode Enum, first, count int) {
f._drawArrays.Invoke(int(mode), first, count) f._drawArrays.Invoke(int(mode), first, count)
} }
func (f *Functions) DrawElements(mode Enum, count int, ty Enum, offset int) { func (f *Functions) DrawElements(mode Enum, count int, ty Enum, offset int) {
f._drawElements.Invoke(int(mode), count, int(ty), offset) f._drawElements.Invoke(int(mode), count, int(ty), offset)
} }
func (f *Functions) DispatchCompute(x, y, z int) { func (f *Functions) DispatchCompute(x, y, z int) {
panic("not implemented") panic("not implemented")
} }
func (f *Functions) Enable(cap Enum) { func (f *Functions) Enable(cap Enum) {
f._enable.Invoke(int(cap)) f._enable.Invoke(int(cap))
} }
func (f *Functions) EnableVertexAttribArray(a Attrib) { func (f *Functions) EnableVertexAttribArray(a Attrib) {
f._enableVertexAttribArray.Invoke(int(a)) f._enableVertexAttribArray.Invoke(int(a))
} }
func (f *Functions) EndQuery(target Enum) { func (f *Functions) EndQuery(target Enum) {
if !f.EXT_disjoint_timer_query_webgl2.IsNull() { if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
f._endQuery.Invoke(int(target)) f._endQuery.Invoke(int(target))
@@ -455,36 +409,28 @@ func (f *Functions) EndQuery(target Enum) {
f.EXT_disjoint_timer_query.Call("endQueryEXT", int(target)) f.EXT_disjoint_timer_query.Call("endQueryEXT", int(target))
} }
} }
func (f *Functions) Finish() { func (f *Functions) Finish() {
f._finish.Invoke() f._finish.Invoke()
} }
func (f *Functions) Flush() { func (f *Functions) Flush() {
f._flush.Invoke() f._flush.Invoke()
} }
func (f *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer) { func (f *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer) {
f._framebufferRenderbuffer.Invoke(int(target), int(attachment), int(renderbuffertarget), js.Value(renderbuffer)) f._framebufferRenderbuffer.Invoke(int(target), int(attachment), int(renderbuffertarget), js.Value(renderbuffer))
} }
func (f *Functions) FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int) { func (f *Functions) FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int) {
f._framebufferTexture2D.Invoke(int(target), int(attachment), int(texTarget), js.Value(t), level) f._framebufferTexture2D.Invoke(int(target), int(attachment), int(texTarget), js.Value(t), level)
} }
func (f *Functions) GenerateMipmap(target Enum) { func (f *Functions) GenerateMipmap(target Enum) {
f._generateMipmap.Invoke(int(target)) f._generateMipmap.Invoke(int(target))
} }
func (f *Functions) GetError() Enum { func (f *Functions) GetError() Enum {
// Avoid slow getError calls. See gio#179. // Avoid slow getError calls. See gio#179.
return 0 return 0
} }
func (f *Functions) GetRenderbufferParameteri(target, pname Enum) int { func (f *Functions) GetRenderbufferParameteri(target, pname Enum) int {
return paramVal(f._getRenderbufferParameteri.Invoke(int(pname))) return paramVal(f._getRenderbufferParameteri.Invoke(int(pname)))
} }
func (f *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int { func (f *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int {
if !f.isWebGL2 && pname == FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING { if !f.isWebGL2 && pname == FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING {
// FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING is only available on WebGL 2 // FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING is only available on WebGL 2
@@ -492,7 +438,6 @@ func (f *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname
} }
return paramVal(f._getFramebufferAttachmentParameter.Invoke(int(target), int(attachment), int(pname))) return paramVal(f._getFramebufferAttachmentParameter.Invoke(int(target), int(attachment), int(pname)))
} }
func (f *Functions) GetBinding(pname Enum) Object { func (f *Functions) GetBinding(pname Enum) Object {
obj := f._getParameter.Invoke(int(pname)) obj := f._getParameter.Invoke(int(pname))
if !obj.Truthy() { if !obj.Truthy() {
@@ -500,7 +445,6 @@ func (f *Functions) GetBinding(pname Enum) Object {
} }
return Object(obj) return Object(obj)
} }
func (f *Functions) GetBindingi(pname Enum, idx int) Object { func (f *Functions) GetBindingi(pname Enum, idx int) Object {
obj := f._getIndexedParameter.Invoke(int(pname), idx) obj := f._getIndexedParameter.Invoke(int(pname), idx)
if !obj.Truthy() { if !obj.Truthy() {
@@ -508,7 +452,6 @@ func (f *Functions) GetBindingi(pname Enum, idx int) Object {
} }
return Object(obj) return Object(obj)
} }
func (f *Functions) GetInteger(pname Enum) int { func (f *Functions) GetInteger(pname Enum) int {
if !f.isWebGL2 { if !f.isWebGL2 {
switch pname { switch pname {
@@ -518,11 +461,9 @@ func (f *Functions) GetInteger(pname Enum) int {
} }
return paramVal(f._getParameter.Invoke(int(pname))) return paramVal(f._getParameter.Invoke(int(pname)))
} }
func (f *Functions) GetFloat(pname Enum) float32 { func (f *Functions) GetFloat(pname Enum) float32 {
return float32(f._getParameter.Invoke(int(pname)).Float()) return float32(f._getParameter.Invoke(int(pname)).Float())
} }
func (f *Functions) GetInteger4(pname Enum) [4]int { func (f *Functions) GetInteger4(pname Enum) [4]int {
arr := f._getParameter.Invoke(int(pname)) arr := f._getParameter.Invoke(int(pname))
var res [4]int var res [4]int
@@ -531,7 +472,6 @@ func (f *Functions) GetInteger4(pname Enum) [4]int {
} }
return res return res
} }
func (f *Functions) GetFloat4(pname Enum) [4]float32 { func (f *Functions) GetFloat4(pname Enum) [4]float32 {
arr := f._getParameter.Invoke(int(pname)) arr := f._getParameter.Invoke(int(pname))
var res [4]float32 var res [4]float32
@@ -540,15 +480,12 @@ func (f *Functions) GetFloat4(pname Enum) [4]float32 {
} }
return res return res
} }
func (f *Functions) GetProgrami(p Program, pname Enum) int { func (f *Functions) GetProgrami(p Program, pname Enum) int {
return paramVal(f._getProgramParameter.Invoke(js.Value(p), int(pname))) return paramVal(f._getProgramParameter.Invoke(js.Value(p), int(pname)))
} }
func (f *Functions) GetProgramInfoLog(p Program) string { func (f *Functions) GetProgramInfoLog(p Program) string {
return f._getProgramInfoLog.Invoke(js.Value(p)).String() return f._getProgramInfoLog.Invoke(js.Value(p)).String()
} }
func (f *Functions) GetQueryObjectuiv(query Query, pname Enum) uint { func (f *Functions) GetQueryObjectuiv(query Query, pname Enum) uint {
if !f.EXT_disjoint_timer_query_webgl2.IsNull() { if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
return uint(paramVal(f._getQueryParameter.Invoke(js.Value(query), int(pname)))) return uint(paramVal(f._getQueryParameter.Invoke(js.Value(query), int(pname))))
@@ -556,15 +493,12 @@ func (f *Functions) GetQueryObjectuiv(query Query, pname Enum) uint {
return uint(paramVal(f.EXT_disjoint_timer_query.Call("getQueryObjectEXT", js.Value(query), int(pname)))) return uint(paramVal(f.EXT_disjoint_timer_query.Call("getQueryObjectEXT", js.Value(query), int(pname))))
} }
} }
func (f *Functions) GetShaderi(s Shader, pname Enum) int { func (f *Functions) GetShaderi(s Shader, pname Enum) int {
return paramVal(f._getShaderParameter.Invoke(js.Value(s), int(pname))) return paramVal(f._getShaderParameter.Invoke(js.Value(s), int(pname)))
} }
func (f *Functions) GetShaderInfoLog(s Shader) string { func (f *Functions) GetShaderInfoLog(s Shader) string {
return f._getShaderInfoLog.Invoke(js.Value(s)).String() return f._getShaderInfoLog.Invoke(js.Value(s)).String()
} }
func (f *Functions) GetString(pname Enum) string { func (f *Functions) GetString(pname Enum) string {
switch pname { switch pname {
case EXTENSIONS: case EXTENSIONS:
@@ -578,19 +512,15 @@ func (f *Functions) GetString(pname Enum) string {
return f._getParameter.Invoke(int(pname)).String() return f._getParameter.Invoke(int(pname)).String()
} }
} }
func (f *Functions) GetUniformBlockIndex(p Program, name string) uint { func (f *Functions) GetUniformBlockIndex(p Program, name string) uint {
return uint(paramVal(f._getUniformBlockIndex.Invoke(js.Value(p), name))) return uint(paramVal(f._getUniformBlockIndex.Invoke(js.Value(p), name)))
} }
func (f *Functions) GetUniformLocation(p Program, name string) Uniform { func (f *Functions) GetUniformLocation(p Program, name string) Uniform {
return Uniform(f._getUniformLocation.Invoke(js.Value(p), name)) return Uniform(f._getUniformLocation.Invoke(js.Value(p), name))
} }
func (f *Functions) GetVertexAttrib(index int, pname Enum) int { func (f *Functions) GetVertexAttrib(index int, pname Enum) int {
return paramVal(f._getVertexAttrib.Invoke(index, int(pname))) return paramVal(f._getVertexAttrib.Invoke(index, int(pname)))
} }
func (f *Functions) GetVertexAttribBinding(index int, pname Enum) Object { func (f *Functions) GetVertexAttribBinding(index int, pname Enum) Object {
obj := f._getVertexAttrib.Invoke(index, int(pname)) obj := f._getVertexAttrib.Invoke(index, int(pname))
if !obj.Truthy() { if !obj.Truthy() {
@@ -598,11 +528,9 @@ func (f *Functions) GetVertexAttribBinding(index int, pname Enum) Object {
} }
return Object(obj) return Object(obj)
} }
func (f *Functions) GetVertexAttribPointer(index int, pname Enum) uintptr { func (f *Functions) GetVertexAttribPointer(index int, pname Enum) uintptr {
return uintptr(f._getVertexAttribOffset.Invoke(index, int(pname)).Int()) return uintptr(f._getVertexAttribOffset.Invoke(index, int(pname)).Int())
} }
func (f *Functions) InvalidateFramebuffer(target, attachment Enum) { func (f *Functions) InvalidateFramebuffer(target, attachment Enum) {
fn := f.Ctx.Get("invalidateFramebuffer") fn := f.Ctx.Get("invalidateFramebuffer")
if !fn.IsUndefined() { if !fn.IsUndefined() {
@@ -613,97 +541,74 @@ func (f *Functions) InvalidateFramebuffer(target, attachment Enum) {
f._invalidateFramebuffer.Invoke(int(target), f.int32Buf) f._invalidateFramebuffer.Invoke(int(target), f.int32Buf)
} }
} }
func (f *Functions) IsEnabled(cap Enum) bool { func (f *Functions) IsEnabled(cap Enum) bool {
return f._isEnabled.Invoke(int(cap)).Truthy() return f._isEnabled.Invoke(int(cap)).Truthy()
} }
func (f *Functions) LinkProgram(p Program) { func (f *Functions) LinkProgram(p Program) {
f._linkProgram.Invoke(js.Value(p)) f._linkProgram.Invoke(js.Value(p))
} }
func (f *Functions) PixelStorei(pname Enum, param int) { func (f *Functions) PixelStorei(pname Enum, param int) {
f._pixelStorei.Invoke(int(pname), param) f._pixelStorei.Invoke(int(pname), param)
} }
func (f *Functions) MemoryBarrier(barriers Enum) { func (f *Functions) MemoryBarrier(barriers Enum) {
panic("not implemented") panic("not implemented")
} }
func (f *Functions) MapBufferRange(target Enum, offset, length int, access Enum) []byte { func (f *Functions) MapBufferRange(target Enum, offset, length int, access Enum) []byte {
panic("not implemented") panic("not implemented")
} }
func (f *Functions) RenderbufferStorage(target, internalformat Enum, width, height int) { func (f *Functions) RenderbufferStorage(target, internalformat Enum, width, height int) {
f._renderbufferStorage.Invoke(int(target), int(internalformat), width, height) f._renderbufferStorage.Invoke(int(target), int(internalformat), width, height)
} }
func (f *Functions) ReadPixels(x, y, width, height int, format, ty Enum, data []byte) { func (f *Functions) ReadPixels(x, y, width, height int, format, ty Enum, data []byte) {
ba := f.byteArrayOf(data) ba := f.byteArrayOf(data)
f._readPixels.Invoke(x, y, width, height, int(format), int(ty), ba) f._readPixels.Invoke(x, y, width, height, int(format), int(ty), ba)
js.CopyBytesToGo(data, ba) js.CopyBytesToGo(data, ba)
} }
func (f *Functions) Scissor(x, y, width, height int32) { func (f *Functions) Scissor(x, y, width, height int32) {
f._scissor.Invoke(x, y, width, height) f._scissor.Invoke(x, y, width, height)
} }
func (f *Functions) ShaderSource(s Shader, src string) { func (f *Functions) ShaderSource(s Shader, src string) {
f._shaderSource.Invoke(js.Value(s), src) f._shaderSource.Invoke(js.Value(s), src)
} }
func (f *Functions) TexImage2D(target Enum, level int, internalFormat Enum, width, height int, format, ty Enum) { func (f *Functions) TexImage2D(target Enum, level int, internalFormat Enum, width, height int, format, ty Enum) {
f._texImage2D.Invoke(int(target), int(level), int(internalFormat), int(width), int(height), 0, int(format), int(ty), nil) f._texImage2D.Invoke(int(target), int(level), int(internalFormat), int(width), int(height), 0, int(format), int(ty), nil)
} }
func (f *Functions) TexStorage2D(target Enum, levels int, internalFormat Enum, width, height int) { func (f *Functions) TexStorage2D(target Enum, levels int, internalFormat Enum, width, height int) {
f._texStorage2D.Invoke(int(target), levels, int(internalFormat), width, height) f._texStorage2D.Invoke(int(target), levels, int(internalFormat), width, height)
} }
func (f *Functions) TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte) { func (f *Functions) TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte) {
f._texSubImage2D.Invoke(int(target), level, x, y, width, height, int(format), int(ty), f.byteArrayOf(data)) f._texSubImage2D.Invoke(int(target), level, x, y, width, height, int(format), int(ty), f.byteArrayOf(data))
} }
func (f *Functions) TexParameteri(target, pname Enum, param int) { func (f *Functions) TexParameteri(target, pname Enum, param int) {
f._texParameteri.Invoke(int(target), int(pname), int(param)) f._texParameteri.Invoke(int(target), int(pname), int(param))
} }
func (f *Functions) UniformBlockBinding(p Program, uniformBlockIndex uint, uniformBlockBinding uint) { func (f *Functions) UniformBlockBinding(p Program, uniformBlockIndex uint, uniformBlockBinding uint) {
f._uniformBlockBinding.Invoke(js.Value(p), int(uniformBlockIndex), int(uniformBlockBinding)) f._uniformBlockBinding.Invoke(js.Value(p), int(uniformBlockIndex), int(uniformBlockBinding))
} }
func (f *Functions) Uniform1f(dst Uniform, v float32) { func (f *Functions) Uniform1f(dst Uniform, v float32) {
f._uniform1f.Invoke(js.Value(dst), v) f._uniform1f.Invoke(js.Value(dst), v)
} }
func (f *Functions) Uniform1i(dst Uniform, v int) { func (f *Functions) Uniform1i(dst Uniform, v int) {
f._uniform1i.Invoke(js.Value(dst), v) f._uniform1i.Invoke(js.Value(dst), v)
} }
func (f *Functions) Uniform2f(dst Uniform, v0, v1 float32) { func (f *Functions) Uniform2f(dst Uniform, v0, v1 float32) {
f._uniform2f.Invoke(js.Value(dst), v0, v1) f._uniform2f.Invoke(js.Value(dst), v0, v1)
} }
func (f *Functions) Uniform3f(dst Uniform, v0, v1, v2 float32) { func (f *Functions) Uniform3f(dst Uniform, v0, v1, v2 float32) {
f._uniform3f.Invoke(js.Value(dst), v0, v1, v2) f._uniform3f.Invoke(js.Value(dst), v0, v1, v2)
} }
func (f *Functions) Uniform4f(dst Uniform, v0, v1, v2, v3 float32) { func (f *Functions) Uniform4f(dst Uniform, v0, v1, v2, v3 float32) {
f._uniform4f.Invoke(js.Value(dst), v0, v1, v2, v3) f._uniform4f.Invoke(js.Value(dst), v0, v1, v2, v3)
} }
func (f *Functions) UseProgram(p Program) { func (f *Functions) UseProgram(p Program) {
f._useProgram.Invoke(js.Value(p)) f._useProgram.Invoke(js.Value(p))
} }
func (f *Functions) UnmapBuffer(target Enum) bool { func (f *Functions) UnmapBuffer(target Enum) bool {
panic("not implemented") panic("not implemented")
} }
func (f *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int) { func (f *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int) {
f._vertexAttribPointer.Invoke(int(dst), size, int(ty), normalized, stride, offset) f._vertexAttribPointer.Invoke(int(dst), size, int(ty), normalized, stride, offset)
} }
func (f *Functions) Viewport(x, y, width, height int) { func (f *Functions) Viewport(x, y, width, height int) {
f._viewport.Invoke(x, y, width, height) f._viewport.Invoke(x, y, width, height)
} }
+91 -306
View File
@@ -3,217 +3,103 @@
package gl package gl
import ( import (
"fmt"
"math" "math"
"runtime" "runtime"
"sync"
"syscall" "syscall"
"unsafe" "unsafe"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
) )
func loadGLESv2Procs() error {
dllName := "libGLESv2.dll"
handle, err := windows.LoadLibraryEx(dllName, 0, windows.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)
if err != nil {
return fmt.Errorf("gl: failed to load %s: %v", dllName, err)
}
gles := windows.DLL{Handle: handle, Name: dllName}
// d3dcompiler_47.dll is needed internally for shader compilation to function.
dllName = "d3dcompiler_47.dll"
_, err = windows.LoadLibraryEx(dllName, 0, windows.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)
if err != nil {
return fmt.Errorf("gl: failed to load %s: %v", dllName, err)
}
procs := map[string]**windows.Proc{
"glActiveTexture": &_glActiveTexture,
"glAttachShader": &_glAttachShader,
"glBeginQuery": &_glBeginQuery,
"glBindAttribLocation": &_glBindAttribLocation,
"glBindBuffer": &_glBindBuffer,
"glBindBufferBase": &_glBindBufferBase,
"glBindFramebuffer": &_glBindFramebuffer,
"glBindRenderbuffer": &_glBindRenderbuffer,
"glBindTexture": &_glBindTexture,
"glBindVertexArray": &_glBindVertexArray,
"glBlendEquation": &_glBlendEquation,
"glBlendFuncSeparate": &_glBlendFuncSeparate,
"glBufferData": &_glBufferData,
"glBufferSubData": &_glBufferSubData,
"glCheckFramebufferStatus": &_glCheckFramebufferStatus,
"glClear": &_glClear,
"glClearColor": &_glClearColor,
"glClearDepthf": &_glClearDepthf,
"glDeleteQueries": &_glDeleteQueries,
"glDeleteVertexArrays": &_glDeleteVertexArrays,
"glCompileShader": &_glCompileShader,
"glCopyTexSubImage2D": &_glCopyTexSubImage2D,
"glGenerateMipmap": &_glGenerateMipmap,
"glGenBuffers": &_glGenBuffers,
"glGenFramebuffers": &_glGenFramebuffers,
"glGenVertexArrays": &_glGenVertexArrays,
"glGetUniformBlockIndex": &_glGetUniformBlockIndex,
"glCreateProgram": &_glCreateProgram,
"glGenRenderbuffers": &_glGenRenderbuffers,
"glCreateShader": &_glCreateShader,
"glGenTextures": &_glGenTextures,
"glDeleteBuffers": &_glDeleteBuffers,
"glDeleteFramebuffers": &_glDeleteFramebuffers,
"glDeleteProgram": &_glDeleteProgram,
"glDeleteShader": &_glDeleteShader,
"glDeleteRenderbuffers": &_glDeleteRenderbuffers,
"glDeleteTextures": &_glDeleteTextures,
"glDepthFunc": &_glDepthFunc,
"glDepthMask": &_glDepthMask,
"glDisableVertexAttribArray": &_glDisableVertexAttribArray,
"glDisable": &_glDisable,
"glDrawArrays": &_glDrawArrays,
"glDrawElements": &_glDrawElements,
"glEnable": &_glEnable,
"glEnableVertexAttribArray": &_glEnableVertexAttribArray,
"glEndQuery": &_glEndQuery,
"glFinish": &_glFinish,
"glFlush": &_glFlush,
"glFramebufferRenderbuffer": &_glFramebufferRenderbuffer,
"glFramebufferTexture2D": &_glFramebufferTexture2D,
"glGenQueries": &_glGenQueries,
"glGetError": &_glGetError,
"glGetRenderbufferParameteriv": &_glGetRenderbufferParameteriv,
"glGetFloatv": &_glGetFloatv,
"glGetFramebufferAttachmentParameteriv": &_glGetFramebufferAttachmentParameteriv,
"glGetIntegerv": &_glGetIntegerv,
"glGetIntegeri_v": &_glGetIntegeri_v,
"glGetProgramiv": &_glGetProgramiv,
"glGetProgramInfoLog": &_glGetProgramInfoLog,
"glGetQueryObjectuiv": &_glGetQueryObjectuiv,
"glGetShaderiv": &_glGetShaderiv,
"glGetShaderInfoLog": &_glGetShaderInfoLog,
"glGetString": &_glGetString,
"glGetUniformLocation": &_glGetUniformLocation,
"glGetVertexAttribiv": &_glGetVertexAttribiv,
"glGetVertexAttribPointerv": &_glGetVertexAttribPointerv,
"glInvalidateFramebuffer": &_glInvalidateFramebuffer,
"glIsEnabled": &_glIsEnabled,
"glLinkProgram": &_glLinkProgram,
"glPixelStorei": &_glPixelStorei,
"glReadPixels": &_glReadPixels,
"glRenderbufferStorage": &_glRenderbufferStorage,
"glScissor": &_glScissor,
"glShaderSource": &_glShaderSource,
"glTexImage2D": &_glTexImage2D,
"glTexStorage2D": &_glTexStorage2D,
"glTexSubImage2D": &_glTexSubImage2D,
"glTexParameteri": &_glTexParameteri,
"glUniformBlockBinding": &_glUniformBlockBinding,
"glUniform1f": &_glUniform1f,
"glUniform1i": &_glUniform1i,
"glUniform2f": &_glUniform2f,
"glUniform3f": &_glUniform3f,
"glUniform4f": &_glUniform4f,
"glUseProgram": &_glUseProgram,
"glVertexAttribPointer": &_glVertexAttribPointer,
"glViewport": &_glViewport,
}
for name, proc := range procs {
p, err := gles.FindProc(name)
if err != nil {
return fmt.Errorf("failed to locate %s in %s: %w", name, gles.Name, err)
}
*proc = p
}
return nil
}
var ( var (
glInitOnce sync.Once LibGLESv2 = windows.NewLazyDLL("libGLESv2.dll")
_glActiveTexture *windows.Proc _glActiveTexture = LibGLESv2.NewProc("glActiveTexture")
_glAttachShader *windows.Proc _glAttachShader = LibGLESv2.NewProc("glAttachShader")
_glBeginQuery *windows.Proc _glBeginQuery = LibGLESv2.NewProc("glBeginQuery")
_glBindAttribLocation *windows.Proc _glBindAttribLocation = LibGLESv2.NewProc("glBindAttribLocation")
_glBindBuffer *windows.Proc _glBindBuffer = LibGLESv2.NewProc("glBindBuffer")
_glBindBufferBase *windows.Proc _glBindBufferBase = LibGLESv2.NewProc("glBindBufferBase")
_glBindFramebuffer *windows.Proc _glBindFramebuffer = LibGLESv2.NewProc("glBindFramebuffer")
_glBindRenderbuffer *windows.Proc _glBindRenderbuffer = LibGLESv2.NewProc("glBindRenderbuffer")
_glBindTexture *windows.Proc _glBindTexture = LibGLESv2.NewProc("glBindTexture")
_glBindVertexArray *windows.Proc _glBindVertexArray = LibGLESv2.NewProc("glBindVertexArray")
_glBlendEquation *windows.Proc _glBlendEquation = LibGLESv2.NewProc("glBlendEquation")
_glBlendFuncSeparate *windows.Proc _glBlendFuncSeparate = LibGLESv2.NewProc("glBlendFuncSeparate")
_glBufferData *windows.Proc _glBufferData = LibGLESv2.NewProc("glBufferData")
_glBufferSubData *windows.Proc _glBufferSubData = LibGLESv2.NewProc("glBufferSubData")
_glCheckFramebufferStatus *windows.Proc _glCheckFramebufferStatus = LibGLESv2.NewProc("glCheckFramebufferStatus")
_glClear *windows.Proc _glClear = LibGLESv2.NewProc("glClear")
_glClearColor *windows.Proc _glClearColor = LibGLESv2.NewProc("glClearColor")
_glClearDepthf *windows.Proc _glClearDepthf = LibGLESv2.NewProc("glClearDepthf")
_glDeleteQueries *windows.Proc _glDeleteQueries = LibGLESv2.NewProc("glDeleteQueries")
_glDeleteVertexArrays *windows.Proc _glDeleteVertexArrays = LibGLESv2.NewProc("glDeleteVertexArrays")
_glCompileShader *windows.Proc _glCompileShader = LibGLESv2.NewProc("glCompileShader")
_glCopyTexSubImage2D *windows.Proc _glCopyTexSubImage2D = LibGLESv2.NewProc("glCopyTexSubImage2D")
_glGenerateMipmap *windows.Proc _glGenerateMipmap = LibGLESv2.NewProc("glGenerateMipmap")
_glGenBuffers *windows.Proc _glGenBuffers = LibGLESv2.NewProc("glGenBuffers")
_glGenFramebuffers *windows.Proc _glGenFramebuffers = LibGLESv2.NewProc("glGenFramebuffers")
_glGenVertexArrays *windows.Proc _glGenVertexArrays = LibGLESv2.NewProc("glGenVertexArrays")
_glGetUniformBlockIndex *windows.Proc _glGetUniformBlockIndex = LibGLESv2.NewProc("glGetUniformBlockIndex")
_glCreateProgram *windows.Proc _glCreateProgram = LibGLESv2.NewProc("glCreateProgram")
_glGenRenderbuffers *windows.Proc _glGenRenderbuffers = LibGLESv2.NewProc("glGenRenderbuffers")
_glCreateShader *windows.Proc _glCreateShader = LibGLESv2.NewProc("glCreateShader")
_glGenTextures *windows.Proc _glGenTextures = LibGLESv2.NewProc("glGenTextures")
_glDeleteBuffers *windows.Proc _glDeleteBuffers = LibGLESv2.NewProc("glDeleteBuffers")
_glDeleteFramebuffers *windows.Proc _glDeleteFramebuffers = LibGLESv2.NewProc("glDeleteFramebuffers")
_glDeleteProgram *windows.Proc _glDeleteProgram = LibGLESv2.NewProc("glDeleteProgram")
_glDeleteShader *windows.Proc _glDeleteShader = LibGLESv2.NewProc("glDeleteShader")
_glDeleteRenderbuffers *windows.Proc _glDeleteRenderbuffers = LibGLESv2.NewProc("glDeleteRenderbuffers")
_glDeleteTextures *windows.Proc _glDeleteTextures = LibGLESv2.NewProc("glDeleteTextures")
_glDepthFunc *windows.Proc _glDepthFunc = LibGLESv2.NewProc("glDepthFunc")
_glDepthMask *windows.Proc _glDepthMask = LibGLESv2.NewProc("glDepthMask")
_glDisableVertexAttribArray *windows.Proc _glDisableVertexAttribArray = LibGLESv2.NewProc("glDisableVertexAttribArray")
_glDisable *windows.Proc _glDisable = LibGLESv2.NewProc("glDisable")
_glDrawArrays *windows.Proc _glDrawArrays = LibGLESv2.NewProc("glDrawArrays")
_glDrawElements *windows.Proc _glDrawElements = LibGLESv2.NewProc("glDrawElements")
_glEnable *windows.Proc _glEnable = LibGLESv2.NewProc("glEnable")
_glEnableVertexAttribArray *windows.Proc _glEnableVertexAttribArray = LibGLESv2.NewProc("glEnableVertexAttribArray")
_glEndQuery *windows.Proc _glEndQuery = LibGLESv2.NewProc("glEndQuery")
_glFinish *windows.Proc _glFinish = LibGLESv2.NewProc("glFinish")
_glFlush *windows.Proc _glFlush = LibGLESv2.NewProc("glFlush")
_glFramebufferRenderbuffer *windows.Proc _glFramebufferRenderbuffer = LibGLESv2.NewProc("glFramebufferRenderbuffer")
_glFramebufferTexture2D *windows.Proc _glFramebufferTexture2D = LibGLESv2.NewProc("glFramebufferTexture2D")
_glGenQueries *windows.Proc _glGenQueries = LibGLESv2.NewProc("glGenQueries")
_glGetError *windows.Proc _glGetError = LibGLESv2.NewProc("glGetError")
_glGetRenderbufferParameteriv *windows.Proc _glGetRenderbufferParameteriv = LibGLESv2.NewProc("glGetRenderbufferParameteriv")
_glGetFloatv *windows.Proc _glGetFloatv = LibGLESv2.NewProc("glGetFloatv")
_glGetFramebufferAttachmentParameteriv *windows.Proc _glGetFramebufferAttachmentParameteriv = LibGLESv2.NewProc("glGetFramebufferAttachmentParameteriv")
_glGetIntegerv *windows.Proc _glGetIntegerv = LibGLESv2.NewProc("glGetIntegerv")
_glGetIntegeri_v *windows.Proc _glGetIntegeri_v = LibGLESv2.NewProc("glGetIntegeri_v")
_glGetProgramiv *windows.Proc _glGetProgramiv = LibGLESv2.NewProc("glGetProgramiv")
_glGetProgramInfoLog *windows.Proc _glGetProgramInfoLog = LibGLESv2.NewProc("glGetProgramInfoLog")
_glGetQueryObjectuiv *windows.Proc _glGetQueryObjectuiv = LibGLESv2.NewProc("glGetQueryObjectuiv")
_glGetShaderiv *windows.Proc _glGetShaderiv = LibGLESv2.NewProc("glGetShaderiv")
_glGetShaderInfoLog *windows.Proc _glGetShaderInfoLog = LibGLESv2.NewProc("glGetShaderInfoLog")
_glGetString *windows.Proc _glGetString = LibGLESv2.NewProc("glGetString")
_glGetUniformLocation *windows.Proc _glGetUniformLocation = LibGLESv2.NewProc("glGetUniformLocation")
_glGetVertexAttribiv *windows.Proc _glGetVertexAttribiv = LibGLESv2.NewProc("glGetVertexAttribiv")
_glGetVertexAttribPointerv *windows.Proc _glGetVertexAttribPointerv = LibGLESv2.NewProc("glGetVertexAttribPointerv")
_glInvalidateFramebuffer *windows.Proc _glInvalidateFramebuffer = LibGLESv2.NewProc("glInvalidateFramebuffer")
_glIsEnabled *windows.Proc _glIsEnabled = LibGLESv2.NewProc("glIsEnabled")
_glLinkProgram *windows.Proc _glLinkProgram = LibGLESv2.NewProc("glLinkProgram")
_glPixelStorei *windows.Proc _glPixelStorei = LibGLESv2.NewProc("glPixelStorei")
_glReadPixels *windows.Proc _glReadPixels = LibGLESv2.NewProc("glReadPixels")
_glRenderbufferStorage *windows.Proc _glRenderbufferStorage = LibGLESv2.NewProc("glRenderbufferStorage")
_glScissor *windows.Proc _glScissor = LibGLESv2.NewProc("glScissor")
_glShaderSource *windows.Proc _glShaderSource = LibGLESv2.NewProc("glShaderSource")
_glTexImage2D *windows.Proc _glTexImage2D = LibGLESv2.NewProc("glTexImage2D")
_glTexStorage2D *windows.Proc _glTexStorage2D = LibGLESv2.NewProc("glTexStorage2D")
_glTexSubImage2D *windows.Proc _glTexSubImage2D = LibGLESv2.NewProc("glTexSubImage2D")
_glTexParameteri *windows.Proc _glTexParameteri = LibGLESv2.NewProc("glTexParameteri")
_glUniformBlockBinding *windows.Proc _glUniformBlockBinding = LibGLESv2.NewProc("glUniformBlockBinding")
_glUniform1f *windows.Proc _glUniform1f = LibGLESv2.NewProc("glUniform1f")
_glUniform1i *windows.Proc _glUniform1i = LibGLESv2.NewProc("glUniform1i")
_glUniform2f *windows.Proc _glUniform2f = LibGLESv2.NewProc("glUniform2f")
_glUniform3f *windows.Proc _glUniform3f = LibGLESv2.NewProc("glUniform3f")
_glUniform4f *windows.Proc _glUniform4f = LibGLESv2.NewProc("glUniform4f")
_glUseProgram *windows.Proc _glUseProgram = LibGLESv2.NewProc("glUseProgram")
_glVertexAttribPointer *windows.Proc _glVertexAttribPointer = LibGLESv2.NewProc("glVertexAttribPointer")
_glViewport *windows.Proc _glViewport = LibGLESv2.NewProc("glViewport")
) )
type Functions struct { type Functions struct {
@@ -223,74 +109,57 @@ type Functions struct {
uintptrs [100]uintptr uintptrs [100]uintptr
} }
type Context any type Context interface{}
func NewFunctions(ctx Context, forceES bool) (*Functions, error) { func NewFunctions(ctx Context, forceES bool) (*Functions, error) {
if ctx != nil { if ctx != nil {
panic("non-nil context") panic("non-nil context")
} }
var err error return new(Functions), nil
glInitOnce.Do(func() {
err = loadGLESv2Procs()
})
return new(Functions), err
} }
func (c *Functions) ActiveTexture(t Enum) { func (c *Functions) ActiveTexture(t Enum) {
syscall.Syscall(_glActiveTexture.Addr(), 1, uintptr(t), 0, 0) syscall.Syscall(_glActiveTexture.Addr(), 1, uintptr(t), 0, 0)
} }
func (c *Functions) AttachShader(p Program, s Shader) { func (c *Functions) AttachShader(p Program, s Shader) {
syscall.Syscall(_glAttachShader.Addr(), 2, uintptr(p.V), uintptr(s.V), 0) syscall.Syscall(_glAttachShader.Addr(), 2, uintptr(p.V), uintptr(s.V), 0)
} }
func (f *Functions) BeginQuery(target Enum, query Query) { func (f *Functions) BeginQuery(target Enum, query Query) {
syscall.Syscall(_glBeginQuery.Addr(), 2, uintptr(target), uintptr(query.V), 0) syscall.Syscall(_glBeginQuery.Addr(), 2, uintptr(target), uintptr(query.V), 0)
} }
func (c *Functions) BindAttribLocation(p Program, a Attrib, name string) { func (c *Functions) BindAttribLocation(p Program, a Attrib, name string) {
cname := cString(name) cname := cString(name)
c0 := &cname[0] c0 := &cname[0]
syscall.Syscall(_glBindAttribLocation.Addr(), 3, uintptr(p.V), uintptr(a), uintptr(unsafe.Pointer(c0))) syscall.Syscall(_glBindAttribLocation.Addr(), 3, uintptr(p.V), uintptr(a), uintptr(unsafe.Pointer(c0)))
issue34474KeepAlive(c) issue34474KeepAlive(c)
} }
func (c *Functions) BindBuffer(target Enum, b Buffer) { func (c *Functions) BindBuffer(target Enum, b Buffer) {
syscall.Syscall(_glBindBuffer.Addr(), 2, uintptr(target), uintptr(b.V), 0) syscall.Syscall(_glBindBuffer.Addr(), 2, uintptr(target), uintptr(b.V), 0)
} }
func (c *Functions) BindBufferBase(target Enum, index int, b Buffer) { func (c *Functions) BindBufferBase(target Enum, index int, b Buffer) {
syscall.Syscall(_glBindBufferBase.Addr(), 3, uintptr(target), uintptr(index), uintptr(b.V)) syscall.Syscall(_glBindBufferBase.Addr(), 3, uintptr(target), uintptr(index), uintptr(b.V))
} }
func (c *Functions) BindFramebuffer(target Enum, fb Framebuffer) { func (c *Functions) BindFramebuffer(target Enum, fb Framebuffer) {
syscall.Syscall(_glBindFramebuffer.Addr(), 2, uintptr(target), uintptr(fb.V), 0) syscall.Syscall(_glBindFramebuffer.Addr(), 2, uintptr(target), uintptr(fb.V), 0)
} }
func (c *Functions) BindRenderbuffer(target Enum, rb Renderbuffer) { func (c *Functions) BindRenderbuffer(target Enum, rb Renderbuffer) {
syscall.Syscall(_glBindRenderbuffer.Addr(), 2, uintptr(target), uintptr(rb.V), 0) syscall.Syscall(_glBindRenderbuffer.Addr(), 2, uintptr(target), uintptr(rb.V), 0)
} }
func (f *Functions) BindImageTexture(unit int, t Texture, level int, layered bool, layer int, access, format Enum) { func (f *Functions) BindImageTexture(unit int, t Texture, level int, layered bool, layer int, access, format Enum) {
panic("not implemented") panic("not implemented")
} }
func (c *Functions) BindTexture(target Enum, t Texture) { func (c *Functions) BindTexture(target Enum, t Texture) {
syscall.Syscall(_glBindTexture.Addr(), 2, uintptr(target), uintptr(t.V), 0) syscall.Syscall(_glBindTexture.Addr(), 2, uintptr(target), uintptr(t.V), 0)
} }
func (c *Functions) BindVertexArray(a VertexArray) { func (c *Functions) BindVertexArray(a VertexArray) {
syscall.Syscall(_glBindVertexArray.Addr(), 1, uintptr(a.V), 0, 0) syscall.Syscall(_glBindVertexArray.Addr(), 1, uintptr(a.V), 0, 0)
} }
func (c *Functions) BlendEquation(mode Enum) { func (c *Functions) BlendEquation(mode Enum) {
syscall.Syscall(_glBlendEquation.Addr(), 1, uintptr(mode), 0, 0) syscall.Syscall(_glBlendEquation.Addr(), 1, uintptr(mode), 0, 0)
} }
func (c *Functions) BlendFuncSeparate(srcRGB, dstRGB, srcA, dstA Enum) { func (c *Functions) BlendFuncSeparate(srcRGB, dstRGB, srcA, dstA Enum) {
syscall.Syscall6(_glBlendFuncSeparate.Addr(), 4, uintptr(srcRGB), uintptr(dstRGB), uintptr(srcA), uintptr(dstA), 0, 0) syscall.Syscall6(_glBlendFuncSeparate.Addr(), 4, uintptr(srcRGB), uintptr(dstRGB), uintptr(srcA), uintptr(dstA), 0, 0)
} }
func (c *Functions) BufferData(target Enum, size int, usage Enum, data []byte) { func (c *Functions) BufferData(target Enum, size int, usage Enum, data []byte) {
var p unsafe.Pointer var p unsafe.Pointer
if len(data) > 0 { if len(data) > 0 {
@@ -298,7 +167,6 @@ func (c *Functions) BufferData(target Enum, size int, usage Enum, data []byte) {
} }
syscall.Syscall6(_glBufferData.Addr(), 4, uintptr(target), uintptr(size), uintptr(p), uintptr(usage), 0, 0) syscall.Syscall6(_glBufferData.Addr(), 4, uintptr(target), uintptr(size), uintptr(p), uintptr(usage), 0, 0)
} }
func (f *Functions) BufferSubData(target Enum, offset int, src []byte) { func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
if n := len(src); n > 0 { if n := len(src); n > 0 {
s0 := &src[0] s0 := &src[0]
@@ -306,118 +174,93 @@ func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
issue34474KeepAlive(s0) issue34474KeepAlive(s0)
} }
} }
func (c *Functions) CheckFramebufferStatus(target Enum) Enum { func (c *Functions) CheckFramebufferStatus(target Enum) Enum {
s, _, _ := syscall.Syscall(_glCheckFramebufferStatus.Addr(), 1, uintptr(target), 0, 0) s, _, _ := syscall.Syscall(_glCheckFramebufferStatus.Addr(), 1, uintptr(target), 0, 0)
return Enum(s) return Enum(s)
} }
func (c *Functions) Clear(mask Enum) { func (c *Functions) Clear(mask Enum) {
syscall.Syscall(_glClear.Addr(), 1, uintptr(mask), 0, 0) syscall.Syscall(_glClear.Addr(), 1, uintptr(mask), 0, 0)
} }
func (c *Functions) ClearColor(red, green, blue, alpha float32) { func (c *Functions) ClearColor(red, green, blue, alpha float32) {
syscall.Syscall6(_glClearColor.Addr(), 4, uintptr(math.Float32bits(red)), uintptr(math.Float32bits(green)), uintptr(math.Float32bits(blue)), uintptr(math.Float32bits(alpha)), 0, 0) syscall.Syscall6(_glClearColor.Addr(), 4, uintptr(math.Float32bits(red)), uintptr(math.Float32bits(green)), uintptr(math.Float32bits(blue)), uintptr(math.Float32bits(alpha)), 0, 0)
} }
func (c *Functions) ClearDepthf(d float32) { func (c *Functions) ClearDepthf(d float32) {
syscall.Syscall(_glClearDepthf.Addr(), 1, uintptr(math.Float32bits(d)), 0, 0) syscall.Syscall(_glClearDepthf.Addr(), 1, uintptr(math.Float32bits(d)), 0, 0)
} }
func (c *Functions) CompileShader(s Shader) { func (c *Functions) CompileShader(s Shader) {
syscall.Syscall(_glCompileShader.Addr(), 1, uintptr(s.V), 0, 0) syscall.Syscall(_glCompileShader.Addr(), 1, uintptr(s.V), 0, 0)
} }
func (f *Functions) CopyTexSubImage2D(target Enum, level, xoffset, yoffset, x, y, width, height int) { func (f *Functions) CopyTexSubImage2D(target Enum, level, xoffset, yoffset, x, y, width, height int) {
syscall.Syscall9(_glCopyTexSubImage2D.Addr(), 8, uintptr(target), uintptr(level), uintptr(xoffset), uintptr(yoffset), uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0) syscall.Syscall9(_glCopyTexSubImage2D.Addr(), 8, uintptr(target), uintptr(level), uintptr(xoffset), uintptr(yoffset), uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0)
} }
func (f *Functions) GenerateMipmap(target Enum) { func (f *Functions) GenerateMipmap(target Enum) {
syscall.Syscall(_glGenerateMipmap.Addr(), 1, uintptr(target), 0, 0) syscall.Syscall(_glGenerateMipmap.Addr(), 1, uintptr(target), 0, 0)
} }
func (c *Functions) CreateBuffer() Buffer { func (c *Functions) CreateBuffer() Buffer {
var buf uintptr var buf uintptr
syscall.Syscall(_glGenBuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&buf)), 0) syscall.Syscall(_glGenBuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&buf)), 0)
return Buffer{uint(buf)} return Buffer{uint(buf)}
} }
func (c *Functions) CreateFramebuffer() Framebuffer { func (c *Functions) CreateFramebuffer() Framebuffer {
var fb uintptr var fb uintptr
syscall.Syscall(_glGenFramebuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&fb)), 0) syscall.Syscall(_glGenFramebuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&fb)), 0)
return Framebuffer{uint(fb)} return Framebuffer{uint(fb)}
} }
func (c *Functions) CreateProgram() Program { func (c *Functions) CreateProgram() Program {
p, _, _ := syscall.Syscall(_glCreateProgram.Addr(), 0, 0, 0, 0) p, _, _ := syscall.Syscall(_glCreateProgram.Addr(), 0, 0, 0, 0)
return Program{uint(p)} return Program{uint(p)}
} }
func (f *Functions) CreateQuery() Query { func (f *Functions) CreateQuery() Query {
var q uintptr var q uintptr
syscall.Syscall(_glGenQueries.Addr(), 2, 1, uintptr(unsafe.Pointer(&q)), 0) syscall.Syscall(_glGenQueries.Addr(), 2, 1, uintptr(unsafe.Pointer(&q)), 0)
return Query{uint(q)} return Query{uint(q)}
} }
func (c *Functions) CreateRenderbuffer() Renderbuffer { func (c *Functions) CreateRenderbuffer() Renderbuffer {
var rb uintptr var rb uintptr
syscall.Syscall(_glGenRenderbuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&rb)), 0) syscall.Syscall(_glGenRenderbuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&rb)), 0)
return Renderbuffer{uint(rb)} return Renderbuffer{uint(rb)}
} }
func (c *Functions) CreateShader(ty Enum) Shader { func (c *Functions) CreateShader(ty Enum) Shader {
s, _, _ := syscall.Syscall(_glCreateShader.Addr(), 1, uintptr(ty), 0, 0) s, _, _ := syscall.Syscall(_glCreateShader.Addr(), 1, uintptr(ty), 0, 0)
return Shader{uint(s)} return Shader{uint(s)}
} }
func (c *Functions) CreateTexture() Texture { func (c *Functions) CreateTexture() Texture {
var t uintptr var t uintptr
syscall.Syscall(_glGenTextures.Addr(), 2, 1, uintptr(unsafe.Pointer(&t)), 0) syscall.Syscall(_glGenTextures.Addr(), 2, 1, uintptr(unsafe.Pointer(&t)), 0)
return Texture{uint(t)} return Texture{uint(t)}
} }
func (c *Functions) CreateVertexArray() VertexArray { func (c *Functions) CreateVertexArray() VertexArray {
var t uintptr var t uintptr
syscall.Syscall(_glGenVertexArrays.Addr(), 2, 1, uintptr(unsafe.Pointer(&t)), 0) syscall.Syscall(_glGenVertexArrays.Addr(), 2, 1, uintptr(unsafe.Pointer(&t)), 0)
return VertexArray{uint(t)} return VertexArray{uint(t)}
} }
func (c *Functions) DeleteBuffer(v Buffer) { func (c *Functions) DeleteBuffer(v Buffer) {
syscall.Syscall(_glDeleteBuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v)), 0) syscall.Syscall(_glDeleteBuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v)), 0)
} }
func (c *Functions) DeleteFramebuffer(v Framebuffer) { func (c *Functions) DeleteFramebuffer(v Framebuffer) {
syscall.Syscall(_glDeleteFramebuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0) syscall.Syscall(_glDeleteFramebuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
} }
func (c *Functions) DeleteProgram(p Program) { func (c *Functions) DeleteProgram(p Program) {
syscall.Syscall(_glDeleteProgram.Addr(), 1, uintptr(p.V), 0, 0) syscall.Syscall(_glDeleteProgram.Addr(), 1, uintptr(p.V), 0, 0)
} }
func (f *Functions) DeleteQuery(query Query) { func (f *Functions) DeleteQuery(query Query) {
syscall.Syscall(_glDeleteQueries.Addr(), 2, 1, uintptr(unsafe.Pointer(&query.V)), 0) syscall.Syscall(_glDeleteQueries.Addr(), 2, 1, uintptr(unsafe.Pointer(&query.V)), 0)
} }
func (c *Functions) DeleteShader(s Shader) { func (c *Functions) DeleteShader(s Shader) {
syscall.Syscall(_glDeleteShader.Addr(), 1, uintptr(s.V), 0, 0) syscall.Syscall(_glDeleteShader.Addr(), 1, uintptr(s.V), 0, 0)
} }
func (c *Functions) DeleteRenderbuffer(v Renderbuffer) { func (c *Functions) DeleteRenderbuffer(v Renderbuffer) {
syscall.Syscall(_glDeleteRenderbuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0) syscall.Syscall(_glDeleteRenderbuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
} }
func (c *Functions) DeleteTexture(v Texture) { func (c *Functions) DeleteTexture(v Texture) {
syscall.Syscall(_glDeleteTextures.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0) syscall.Syscall(_glDeleteTextures.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
} }
func (f *Functions) DeleteVertexArray(array VertexArray) { func (f *Functions) DeleteVertexArray(array VertexArray) {
syscall.Syscall(_glDeleteVertexArrays.Addr(), 2, 1, uintptr(unsafe.Pointer(&array.V)), 0) syscall.Syscall(_glDeleteVertexArrays.Addr(), 2, 1, uintptr(unsafe.Pointer(&array.V)), 0)
} }
func (c *Functions) DepthFunc(f Enum) { func (c *Functions) DepthFunc(f Enum) {
syscall.Syscall(_glDepthFunc.Addr(), 1, uintptr(f), 0, 0) syscall.Syscall(_glDepthFunc.Addr(), 1, uintptr(f), 0, 0)
} }
func (c *Functions) DepthMask(mask bool) { func (c *Functions) DepthMask(mask bool) {
var m uintptr var m uintptr
if mask { if mask {
@@ -425,55 +268,42 @@ func (c *Functions) DepthMask(mask bool) {
} }
syscall.Syscall(_glDepthMask.Addr(), 1, m, 0, 0) syscall.Syscall(_glDepthMask.Addr(), 1, m, 0, 0)
} }
func (c *Functions) DisableVertexAttribArray(a Attrib) { func (c *Functions) DisableVertexAttribArray(a Attrib) {
syscall.Syscall(_glDisableVertexAttribArray.Addr(), 1, uintptr(a), 0, 0) syscall.Syscall(_glDisableVertexAttribArray.Addr(), 1, uintptr(a), 0, 0)
} }
func (c *Functions) Disable(cap Enum) { func (c *Functions) Disable(cap Enum) {
syscall.Syscall(_glDisable.Addr(), 1, uintptr(cap), 0, 0) syscall.Syscall(_glDisable.Addr(), 1, uintptr(cap), 0, 0)
} }
func (c *Functions) DrawArrays(mode Enum, first, count int) { func (c *Functions) DrawArrays(mode Enum, first, count int) {
syscall.Syscall(_glDrawArrays.Addr(), 3, uintptr(mode), uintptr(first), uintptr(count)) syscall.Syscall(_glDrawArrays.Addr(), 3, uintptr(mode), uintptr(first), uintptr(count))
} }
func (c *Functions) DrawElements(mode Enum, count int, ty Enum, offset int) { func (c *Functions) DrawElements(mode Enum, count int, ty Enum, offset int) {
syscall.Syscall6(_glDrawElements.Addr(), 4, uintptr(mode), uintptr(count), uintptr(ty), uintptr(offset), 0, 0) syscall.Syscall6(_glDrawElements.Addr(), 4, uintptr(mode), uintptr(count), uintptr(ty), uintptr(offset), 0, 0)
} }
func (f *Functions) DispatchCompute(x, y, z int) { func (f *Functions) DispatchCompute(x, y, z int) {
panic("not implemented") panic("not implemented")
} }
func (c *Functions) Enable(cap Enum) { func (c *Functions) Enable(cap Enum) {
syscall.Syscall(_glEnable.Addr(), 1, uintptr(cap), 0, 0) syscall.Syscall(_glEnable.Addr(), 1, uintptr(cap), 0, 0)
} }
func (c *Functions) EnableVertexAttribArray(a Attrib) { func (c *Functions) EnableVertexAttribArray(a Attrib) {
syscall.Syscall(_glEnableVertexAttribArray.Addr(), 1, uintptr(a), 0, 0) syscall.Syscall(_glEnableVertexAttribArray.Addr(), 1, uintptr(a), 0, 0)
} }
func (f *Functions) EndQuery(target Enum) { func (f *Functions) EndQuery(target Enum) {
syscall.Syscall(_glEndQuery.Addr(), 1, uintptr(target), 0, 0) syscall.Syscall(_glEndQuery.Addr(), 1, uintptr(target), 0, 0)
} }
func (c *Functions) Finish() { func (c *Functions) Finish() {
syscall.Syscall(_glFinish.Addr(), 0, 0, 0, 0) syscall.Syscall(_glFinish.Addr(), 0, 0, 0, 0)
} }
func (c *Functions) Flush() { func (c *Functions) Flush() {
syscall.Syscall(_glFlush.Addr(), 0, 0, 0, 0) syscall.Syscall(_glFlush.Addr(), 0, 0, 0, 0)
} }
func (c *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer) { func (c *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer) {
syscall.Syscall6(_glFramebufferRenderbuffer.Addr(), 4, uintptr(target), uintptr(attachment), uintptr(renderbuffertarget), uintptr(renderbuffer.V), 0, 0) syscall.Syscall6(_glFramebufferRenderbuffer.Addr(), 4, uintptr(target), uintptr(attachment), uintptr(renderbuffertarget), uintptr(renderbuffer.V), 0, 0)
} }
func (c *Functions) FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int) { func (c *Functions) FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int) {
syscall.Syscall6(_glFramebufferTexture2D.Addr(), 5, uintptr(target), uintptr(attachment), uintptr(texTarget), uintptr(t.V), uintptr(level), 0) syscall.Syscall6(_glFramebufferTexture2D.Addr(), 5, uintptr(target), uintptr(attachment), uintptr(texTarget), uintptr(t.V), uintptr(level), 0)
} }
func (f *Functions) GetUniformBlockIndex(p Program, name string) uint { func (f *Functions) GetUniformBlockIndex(p Program, name string) uint {
cname := cString(name) cname := cString(name)
c0 := &cname[0] c0 := &cname[0]
@@ -481,30 +311,24 @@ func (f *Functions) GetUniformBlockIndex(p Program, name string) uint {
issue34474KeepAlive(c0) issue34474KeepAlive(c0)
return uint(u) return uint(u)
} }
func (c *Functions) GetBinding(pname Enum) Object { func (c *Functions) GetBinding(pname Enum) Object {
return Object{uint(c.GetInteger(pname))} return Object{uint(c.GetInteger(pname))}
} }
func (c *Functions) GetBindingi(pname Enum, idx int) Object { func (c *Functions) GetBindingi(pname Enum, idx int) Object {
return Object{uint(c.GetIntegeri(pname, idx))} return Object{uint(c.GetIntegeri(pname, idx))}
} }
func (c *Functions) GetError() Enum { func (c *Functions) GetError() Enum {
e, _, _ := syscall.Syscall(_glGetError.Addr(), 0, 0, 0, 0) e, _, _ := syscall.Syscall(_glGetError.Addr(), 0, 0, 0, 0)
return Enum(e) return Enum(e)
} }
func (c *Functions) GetRenderbufferParameteri(target, pname Enum) int { func (c *Functions) GetRenderbufferParameteri(target, pname Enum) int {
syscall.Syscall(_glGetRenderbufferParameteriv.Addr(), 3, uintptr(target), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0]))) syscall.Syscall(_glGetRenderbufferParameteriv.Addr(), 3, uintptr(target), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
return int(c.int32s[0]) return int(c.int32s[0])
} }
func (c *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int { func (c *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int {
syscall.Syscall6(_glGetFramebufferAttachmentParameteriv.Addr(), 4, uintptr(target), uintptr(attachment), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0, 0) syscall.Syscall6(_glGetFramebufferAttachmentParameteriv.Addr(), 4, uintptr(target), uintptr(attachment), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0, 0)
return int(c.int32s[0]) return int(c.int32s[0])
} }
func (c *Functions) GetInteger4(pname Enum) [4]int { func (c *Functions) GetInteger4(pname Enum) [4]int {
syscall.Syscall(_glGetIntegerv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0) syscall.Syscall(_glGetIntegerv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0)
var r [4]int var r [4]int
@@ -513,66 +337,52 @@ func (c *Functions) GetInteger4(pname Enum) [4]int {
} }
return r return r
} }
func (c *Functions) GetInteger(pname Enum) int { func (c *Functions) GetInteger(pname Enum) int {
syscall.Syscall(_glGetIntegerv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0) syscall.Syscall(_glGetIntegerv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0)
return int(c.int32s[0]) return int(c.int32s[0])
} }
func (c *Functions) GetIntegeri(pname Enum, idx int) int { func (c *Functions) GetIntegeri(pname Enum, idx int) int {
syscall.Syscall(_glGetIntegeri_v.Addr(), 3, uintptr(pname), uintptr(idx), uintptr(unsafe.Pointer(&c.int32s[0]))) syscall.Syscall(_glGetIntegeri_v.Addr(), 3, uintptr(pname), uintptr(idx), uintptr(unsafe.Pointer(&c.int32s[0])))
return int(c.int32s[0]) return int(c.int32s[0])
} }
func (c *Functions) GetFloat(pname Enum) float32 { func (c *Functions) GetFloat(pname Enum) float32 {
syscall.Syscall(_glGetFloatv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.float32s[0])), 0) syscall.Syscall(_glGetFloatv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.float32s[0])), 0)
return c.float32s[0] return c.float32s[0]
} }
func (c *Functions) GetFloat4(pname Enum) [4]float32 { func (c *Functions) GetFloat4(pname Enum) [4]float32 {
syscall.Syscall(_glGetFloatv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.float32s[0])), 0) syscall.Syscall(_glGetFloatv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.float32s[0])), 0)
var r [4]float32 var r [4]float32
copy(r[:], c.float32s[:]) copy(r[:], c.float32s[:])
return r return r
} }
func (c *Functions) GetProgrami(p Program, pname Enum) int { func (c *Functions) GetProgrami(p Program, pname Enum) int {
syscall.Syscall(_glGetProgramiv.Addr(), 3, uintptr(p.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0]))) syscall.Syscall(_glGetProgramiv.Addr(), 3, uintptr(p.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
return int(c.int32s[0]) return int(c.int32s[0])
} }
func (c *Functions) GetProgramInfoLog(p Program) string { func (c *Functions) GetProgramInfoLog(p Program) string {
n := c.GetProgrami(p, INFO_LOG_LENGTH) n := c.GetProgrami(p, INFO_LOG_LENGTH)
if n == 0 {
return ""
}
buf := make([]byte, n) buf := make([]byte, n)
syscall.Syscall6(_glGetProgramInfoLog.Addr(), 4, uintptr(p.V), uintptr(len(buf)), 0, uintptr(unsafe.Pointer(&buf[0])), 0, 0) syscall.Syscall6(_glGetProgramInfoLog.Addr(), 4, uintptr(p.V), uintptr(len(buf)), 0, uintptr(unsafe.Pointer(&buf[0])), 0, 0)
return string(buf) return string(buf)
} }
func (c *Functions) GetQueryObjectuiv(query Query, pname Enum) uint { func (c *Functions) GetQueryObjectuiv(query Query, pname Enum) uint {
syscall.Syscall(_glGetQueryObjectuiv.Addr(), 3, uintptr(query.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0]))) syscall.Syscall(_glGetQueryObjectuiv.Addr(), 3, uintptr(query.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
return uint(c.int32s[0]) return uint(c.int32s[0])
} }
func (c *Functions) GetShaderi(s Shader, pname Enum) int { func (c *Functions) GetShaderi(s Shader, pname Enum) int {
syscall.Syscall(_glGetShaderiv.Addr(), 3, uintptr(s.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0]))) syscall.Syscall(_glGetShaderiv.Addr(), 3, uintptr(s.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
return int(c.int32s[0]) return int(c.int32s[0])
} }
func (c *Functions) GetShaderInfoLog(s Shader) string { func (c *Functions) GetShaderInfoLog(s Shader) string {
n := c.GetShaderi(s, INFO_LOG_LENGTH) n := c.GetShaderi(s, INFO_LOG_LENGTH)
buf := make([]byte, n) buf := make([]byte, n)
syscall.Syscall6(_glGetShaderInfoLog.Addr(), 4, uintptr(s.V), uintptr(len(buf)), 0, uintptr(unsafe.Pointer(&buf[0])), 0, 0) syscall.Syscall6(_glGetShaderInfoLog.Addr(), 4, uintptr(s.V), uintptr(len(buf)), 0, uintptr(unsafe.Pointer(&buf[0])), 0, 0)
return string(buf) return string(buf)
} }
func (c *Functions) GetString(pname Enum) string { func (c *Functions) GetString(pname Enum) string {
s, _, _ := syscall.Syscall(_glGetString.Addr(), 1, uintptr(pname), 0, 0) s, _, _ := syscall.Syscall(_glGetString.Addr(), 1, uintptr(pname), 0, 0)
return windows.BytePtrToString((*byte)(unsafe.Pointer(s))) return windows.BytePtrToString((*byte)(unsafe.Pointer(s)))
} }
func (c *Functions) GetUniformLocation(p Program, name string) Uniform { func (c *Functions) GetUniformLocation(p Program, name string) Uniform {
cname := cString(name) cname := cString(name)
c0 := &cname[0] c0 := &cname[0]
@@ -580,7 +390,6 @@ func (c *Functions) GetUniformLocation(p Program, name string) Uniform {
issue34474KeepAlive(c0) issue34474KeepAlive(c0)
return Uniform{int(u)} return Uniform{int(u)}
} }
func (c *Functions) GetVertexAttrib(index int, pname Enum) int { func (c *Functions) GetVertexAttrib(index int, pname Enum) int {
syscall.Syscall(_glGetVertexAttribiv.Addr(), 3, uintptr(index), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0]))) syscall.Syscall(_glGetVertexAttribiv.Addr(), 3, uintptr(index), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
return int(c.int32s[0]) return int(c.int32s[0])
@@ -594,7 +403,6 @@ func (c *Functions) GetVertexAttribPointer(index int, pname Enum) uintptr {
syscall.Syscall(_glGetVertexAttribPointerv.Addr(), 3, uintptr(index), uintptr(pname), uintptr(unsafe.Pointer(&c.uintptrs[0]))) syscall.Syscall(_glGetVertexAttribPointerv.Addr(), 3, uintptr(index), uintptr(pname), uintptr(unsafe.Pointer(&c.uintptrs[0])))
return c.uintptrs[0] return c.uintptrs[0]
} }
func (c *Functions) InvalidateFramebuffer(target, attachment Enum) { func (c *Functions) InvalidateFramebuffer(target, attachment Enum) {
addr := _glInvalidateFramebuffer.Addr() addr := _glInvalidateFramebuffer.Addr()
if addr == 0 { if addr == 0 {
@@ -603,99 +411,77 @@ func (c *Functions) InvalidateFramebuffer(target, attachment Enum) {
} }
syscall.Syscall(addr, 3, uintptr(target), 1, uintptr(unsafe.Pointer(&attachment))) syscall.Syscall(addr, 3, uintptr(target), 1, uintptr(unsafe.Pointer(&attachment)))
} }
func (f *Functions) IsEnabled(cap Enum) bool { func (f *Functions) IsEnabled(cap Enum) bool {
u, _, _ := syscall.Syscall(_glIsEnabled.Addr(), 1, uintptr(cap), 0, 0) u, _, _ := syscall.Syscall(_glIsEnabled.Addr(), 1, uintptr(cap), 0, 0)
return u == TRUE return u == TRUE
} }
func (c *Functions) LinkProgram(p Program) { func (c *Functions) LinkProgram(p Program) {
syscall.Syscall(_glLinkProgram.Addr(), 1, uintptr(p.V), 0, 0) syscall.Syscall(_glLinkProgram.Addr(), 1, uintptr(p.V), 0, 0)
} }
func (c *Functions) PixelStorei(pname Enum, param int) { func (c *Functions) PixelStorei(pname Enum, param int) {
syscall.Syscall(_glPixelStorei.Addr(), 2, uintptr(pname), uintptr(param), 0) syscall.Syscall(_glPixelStorei.Addr(), 2, uintptr(pname), uintptr(param), 0)
} }
func (f *Functions) MemoryBarrier(barriers Enum) { func (f *Functions) MemoryBarrier(barriers Enum) {
panic("not implemented") panic("not implemented")
} }
func (f *Functions) MapBufferRange(target Enum, offset, length int, access Enum) []byte { func (f *Functions) MapBufferRange(target Enum, offset, length int, access Enum) []byte {
panic("not implemented") panic("not implemented")
} }
func (f *Functions) ReadPixels(x, y, width, height int, format, ty Enum, data []byte) { func (f *Functions) ReadPixels(x, y, width, height int, format, ty Enum, data []byte) {
d0 := &data[0] d0 := &data[0]
syscall.Syscall9(_glReadPixels.Addr(), 7, uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(format), uintptr(ty), uintptr(unsafe.Pointer(d0)), 0, 0) syscall.Syscall9(_glReadPixels.Addr(), 7, uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(format), uintptr(ty), uintptr(unsafe.Pointer(d0)), 0, 0)
issue34474KeepAlive(d0) issue34474KeepAlive(d0)
} }
func (c *Functions) RenderbufferStorage(target, internalformat Enum, width, height int) { func (c *Functions) RenderbufferStorage(target, internalformat Enum, width, height int) {
syscall.Syscall6(_glRenderbufferStorage.Addr(), 4, uintptr(target), uintptr(internalformat), uintptr(width), uintptr(height), 0, 0) syscall.Syscall6(_glRenderbufferStorage.Addr(), 4, uintptr(target), uintptr(internalformat), uintptr(width), uintptr(height), 0, 0)
} }
func (c *Functions) Scissor(x, y, width, height int32) { func (c *Functions) Scissor(x, y, width, height int32) {
syscall.Syscall6(_glScissor.Addr(), 4, uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0, 0) syscall.Syscall6(_glScissor.Addr(), 4, uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0, 0)
} }
func (c *Functions) ShaderSource(s Shader, src string) { func (c *Functions) ShaderSource(s Shader, src string) {
var n uintptr = uintptr(len(src)) var n uintptr = uintptr(len(src))
psrc := &src psrc := &src
syscall.Syscall6(_glShaderSource.Addr(), 4, uintptr(s.V), 1, uintptr(unsafe.Pointer(psrc)), uintptr(unsafe.Pointer(&n)), 0, 0) syscall.Syscall6(_glShaderSource.Addr(), 4, uintptr(s.V), 1, uintptr(unsafe.Pointer(psrc)), uintptr(unsafe.Pointer(&n)), 0, 0)
issue34474KeepAlive(psrc) issue34474KeepAlive(psrc)
} }
func (f *Functions) TexImage2D(target Enum, level int, internalFormat Enum, width int, height int, format Enum, ty Enum) { func (f *Functions) TexImage2D(target Enum, level int, internalFormat Enum, width int, height int, format Enum, ty Enum) {
syscall.Syscall9(_glTexImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(internalFormat), uintptr(width), uintptr(height), 0, uintptr(format), uintptr(ty), 0) syscall.Syscall9(_glTexImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(internalFormat), uintptr(width), uintptr(height), 0, uintptr(format), uintptr(ty), 0)
} }
func (f *Functions) TexStorage2D(target Enum, levels int, internalFormat Enum, width, height int) { func (f *Functions) TexStorage2D(target Enum, levels int, internalFormat Enum, width, height int) {
syscall.Syscall6(_glTexStorage2D.Addr(), 5, uintptr(target), uintptr(levels), uintptr(internalFormat), uintptr(width), uintptr(height), 0) syscall.Syscall6(_glTexStorage2D.Addr(), 5, uintptr(target), uintptr(levels), uintptr(internalFormat), uintptr(width), uintptr(height), 0)
} }
func (c *Functions) TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte) { func (c *Functions) TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte) {
d0 := &data[0] d0 := &data[0]
syscall.Syscall9(_glTexSubImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(format), uintptr(ty), uintptr(unsafe.Pointer(d0))) syscall.Syscall9(_glTexSubImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(format), uintptr(ty), uintptr(unsafe.Pointer(d0)))
issue34474KeepAlive(d0) issue34474KeepAlive(d0)
} }
func (c *Functions) TexParameteri(target, pname Enum, param int) { func (c *Functions) TexParameteri(target, pname Enum, param int) {
syscall.Syscall(_glTexParameteri.Addr(), 3, uintptr(target), uintptr(pname), uintptr(param)) syscall.Syscall(_glTexParameteri.Addr(), 3, uintptr(target), uintptr(pname), uintptr(param))
} }
func (f *Functions) UniformBlockBinding(p Program, uniformBlockIndex uint, uniformBlockBinding uint) { func (f *Functions) UniformBlockBinding(p Program, uniformBlockIndex uint, uniformBlockBinding uint) {
syscall.Syscall(_glUniformBlockBinding.Addr(), 3, uintptr(p.V), uintptr(uniformBlockIndex), uintptr(uniformBlockBinding)) syscall.Syscall(_glUniformBlockBinding.Addr(), 3, uintptr(p.V), uintptr(uniformBlockIndex), uintptr(uniformBlockBinding))
} }
func (c *Functions) Uniform1f(dst Uniform, v float32) { func (c *Functions) Uniform1f(dst Uniform, v float32) {
syscall.Syscall(_glUniform1f.Addr(), 2, uintptr(dst.V), uintptr(math.Float32bits(v)), 0) syscall.Syscall(_glUniform1f.Addr(), 2, uintptr(dst.V), uintptr(math.Float32bits(v)), 0)
} }
func (c *Functions) Uniform1i(dst Uniform, v int) { func (c *Functions) Uniform1i(dst Uniform, v int) {
syscall.Syscall(_glUniform1i.Addr(), 2, uintptr(dst.V), uintptr(v), 0) syscall.Syscall(_glUniform1i.Addr(), 2, uintptr(dst.V), uintptr(v), 0)
} }
func (c *Functions) Uniform2f(dst Uniform, v0, v1 float32) { func (c *Functions) Uniform2f(dst Uniform, v0, v1 float32) {
syscall.Syscall(_glUniform2f.Addr(), 3, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1))) syscall.Syscall(_glUniform2f.Addr(), 3, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)))
} }
func (c *Functions) Uniform3f(dst Uniform, v0, v1, v2 float32) { func (c *Functions) Uniform3f(dst Uniform, v0, v1, v2 float32) {
syscall.Syscall6(_glUniform3f.Addr(), 4, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)), uintptr(math.Float32bits(v2)), 0, 0) syscall.Syscall6(_glUniform3f.Addr(), 4, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)), uintptr(math.Float32bits(v2)), 0, 0)
} }
func (c *Functions) Uniform4f(dst Uniform, v0, v1, v2, v3 float32) { func (c *Functions) Uniform4f(dst Uniform, v0, v1, v2, v3 float32) {
syscall.Syscall6(_glUniform4f.Addr(), 5, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)), uintptr(math.Float32bits(v2)), uintptr(math.Float32bits(v3)), 0) syscall.Syscall6(_glUniform4f.Addr(), 5, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)), uintptr(math.Float32bits(v2)), uintptr(math.Float32bits(v3)), 0)
} }
func (c *Functions) UseProgram(p Program) { func (c *Functions) UseProgram(p Program) {
syscall.Syscall(_glUseProgram.Addr(), 1, uintptr(p.V), 0, 0) syscall.Syscall(_glUseProgram.Addr(), 1, uintptr(p.V), 0, 0)
} }
func (f *Functions) UnmapBuffer(target Enum) bool { func (f *Functions) UnmapBuffer(target Enum) bool {
panic("not implemented") panic("not implemented")
} }
func (c *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int) { func (c *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int) {
var norm uintptr var norm uintptr
if normalized { if normalized {
@@ -703,7 +489,6 @@ func (c *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalize
} }
syscall.Syscall6(_glVertexAttribPointer.Addr(), 6, uintptr(dst), uintptr(size), uintptr(ty), norm, uintptr(stride), uintptr(offset)) syscall.Syscall6(_glVertexAttribPointer.Addr(), 6, uintptr(dst), uintptr(size), uintptr(ty), norm, uintptr(stride), uintptr(offset))
} }
func (c *Functions) Viewport(x, y, width, height int) { func (c *Functions) Viewport(x, y, width, height int) {
syscall.Syscall6(_glViewport.Addr(), 4, uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0, 0) syscall.Syscall6(_glViewport.Addr(), 4, uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0, 0)
} }
@@ -716,6 +501,6 @@ func cString(s string) []byte {
// issue34474KeepAlive calls runtime.KeepAlive as a // issue34474KeepAlive calls runtime.KeepAlive as a
// workaround for golang.org/issue/34474. // workaround for golang.org/issue/34474.
func issue34474KeepAlive(v any) { func issue34474KeepAlive(v interface{}) {
runtime.KeepAlive(v) runtime.KeepAlive(v)
} }
+5 -5
View File
@@ -18,7 +18,7 @@ type Ops struct {
// data contains the serialized operations. // data contains the serialized operations.
data []byte data []byte
// refs hold external references for operations. // refs hold external references for operations.
refs []any refs []interface{}
// stringRefs provides space for string references, pointers to which will // stringRefs provides space for string references, pointers to which will
// be stored in refs. Storing a string directly in refs would cause a heap // be stored in refs. Storing a string directly in refs would cause a heap
// allocation, to store the string header in an interface value. The backing // allocation, to store the string header in an interface value. The backing
@@ -256,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 any) []byte { func Write1(o *Ops, n int, ref1 interface{}) []byte {
o.data = append(o.data, make([]byte, n)...) o.data = append(o.data, make([]byte, n)...)
o.refs = append(o.refs, ref1) o.refs = append(o.refs, ref1)
return o.data[len(o.data)-n:] return o.data[len(o.data)-n:]
@@ -269,20 +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 any) []byte { func Write2(o *Ops, n int, ref1, ref2 interface{}) []byte {
o.data = append(o.data, make([]byte, n)...) o.data = append(o.data, make([]byte, n)...)
o.refs = append(o.refs, ref1, ref2) o.refs = append(o.refs, ref1, ref2)
return o.data[len(o.data)-n:] return o.data[len(o.data)-n:]
} }
func Write2String(o *Ops, n int, ref1 any, ref2 string) []byte { func Write2String(o *Ops, n int, ref1 interface{}, ref2 string) []byte {
o.data = append(o.data, make([]byte, n)...) o.data = append(o.data, make([]byte, n)...)
o.stringRefs = append(o.stringRefs, ref2) o.stringRefs = append(o.stringRefs, ref2)
o.refs = append(o.refs, ref1, &o.stringRefs[len(o.stringRefs)-1]) o.refs = append(o.refs, ref1, &o.stringRefs[len(o.stringRefs)-1])
return o.data[len(o.data)-n:] return o.data[len(o.data)-n:]
} }
func Write3(o *Ops, n int, ref1, ref2, ref3 any) []byte { func Write3(o *Ops, n int, ref1, ref2, ref3 interface{}) []byte {
o.data = append(o.data, make([]byte, n)...) o.data = append(o.data, make([]byte, n)...)
o.refs = append(o.refs, ref1, ref2, ref3) o.refs = append(o.refs, ref1, ref2, ref3)
return o.data[len(o.data)-n:] return o.data[len(o.data)-n:]
+2 -2
View File
@@ -20,7 +20,7 @@ type Reader struct {
type EncodedOp struct { type EncodedOp struct {
Key Key Key Key
Data []byte Data []byte
Refs []any Refs []interface{}
} }
// Key is a unique key for a given op. // Key is a unique key for a given op.
@@ -175,7 +175,7 @@ func (op *opMacroDef) decode(data []byte) {
op.endpc.refs = bo.Uint32(data[5:]) op.endpc.refs = bo.Uint32(data[5:])
} }
func (m *macroOp) decode(data []byte, refs []any) { func (m *macroOp) decode(data []byte, refs []interface{}) {
if len(data) < TypeCallLen || len(refs) < 1 || OpType(data[0]) != TypeCall { if len(data) < TypeCallLen || len(refs) < 1 || OpType(data[0]) != TypeCall {
panic("invalid op") panic("invalid op")
} }
+10 -22
View File
@@ -87,7 +87,7 @@ func (qs *StrokeQuads) lineTo(pt f32.Point) {
func (qs *StrokeQuads) arc(f1, f2 f32.Point, angle float32) { func (qs *StrokeQuads) arc(f1, f2 f32.Point, angle float32) {
pen := qs.pen() pen := qs.pen()
m, segments := ArcTransform(pen, f1.Add(pen), f2.Add(pen), angle) m, segments := ArcTransform(pen, f1.Add(pen), f2.Add(pen), angle)
for range segments { for i := 0; i < segments; i++ {
p0 := qs.pen() p0 := qs.pen()
p1 := m.Transform(p0) p1 := m.Transform(p0)
p2 := m.Transform(p1) p2 := m.Transform(p1)
@@ -327,26 +327,10 @@ func strokePathNorm(p0, p1, p2 f32.Point, t, d float32) f32.Point {
func rot90CW(p f32.Point) f32.Point { return f32.Pt(+p.Y, -p.X) } func rot90CW(p f32.Point) f32.Point { return f32.Pt(+p.Y, -p.X) }
func normPt(p f32.Point, l float32) f32.Point { func normPt(p f32.Point, l float32) f32.Point {
if (p.X == 0 && p.Y == 0) || l == 0 {
return f32.Point{}
}
isVerticalUnit := p.X == 0 && (p.Y == l || p.Y == -l)
isHorizontalUnit := p.Y == 0 && (p.X == l || p.X == -l)
if isVerticalUnit || isHorizontalUnit {
if math.Signbit(float64(l)) {
return f32.Point{X: -p.X, Y: -p.Y}
} else {
return f32.Point{X: p.X, Y: p.Y}
}
}
d := math.Hypot(float64(p.X), float64(p.Y)) d := math.Hypot(float64(p.X), float64(p.Y))
l64 := float64(l) l64 := float64(l)
if math.Abs(d-l64) < 1e-10 { if math.Abs(d-l64) < 1e-10 {
if math.Signbit(float64(l)) { return f32.Point{}
return f32.Point{X: -p.X, Y: -p.Y}
} else {
return f32.Point{X: p.X, Y: p.Y}
}
} }
n := float32(l64 / d) n := float32(l64 / d)
return f32.Point{X: p.X * n, Y: p.Y * n} return f32.Point{X: p.X * n, Y: p.Y * n}
@@ -453,6 +437,7 @@ func flattenQuadBezier(qs StrokeQuads, p0, p1, p2 f32.Point, d, flatness float32
} }
func (qs *StrokeQuads) addLine(p0, ctrl, p1 f32.Point, t, d float32) { func (qs *StrokeQuads) addLine(p0, ctrl, p1 f32.Point, t, d float32) {
switch i := len(*qs); i { switch i := len(*qs); i {
case 0: case 0:
p0 = p0.Add(strokePathNorm(p0, ctrl, p1, 0, d)) p0 = p0.Add(strokePathNorm(p0, ctrl, p1, 0, d))
@@ -485,6 +470,7 @@ func quadInterp(p, q f32.Point, t float32) f32.Point {
// quadBezierSplit returns the pair of triplets (from,ctrl,to) Bézier curve, // quadBezierSplit returns the pair of triplets (from,ctrl,to) Bézier curve,
// split before (resp. after) the provided parametric t value. // split before (resp. after) the provided parametric t value.
func quadBezierSplit(p0, p1, p2 f32.Point, t float32) (f32.Point, f32.Point, f32.Point, f32.Point, f32.Point, f32.Point) { func quadBezierSplit(p0, p1, p2 f32.Point, t float32) (f32.Point, f32.Point, f32.Point, f32.Point, f32.Point, f32.Point) {
var ( var (
b0 = p0 b0 = p0
b1 = quadInterp(p0, p1, t) b1 = quadInterp(p0, p1, t)
@@ -588,10 +574,12 @@ func ArcTransform(p, f1, f2 f32.Point, angle float32) (transform f32.Affine2D, s
} }
} }
θ := angle / float32(segments) var (
ref := f32.AffineId() // transform from absolute frame to ellipse-based one θ = angle / float32(segments)
rot := f32.AffineId() // rotation matrix for each segment ref f32.Affine2D // transform from absolute frame to ellipse-based one
inv := f32.AffineId() // transform from ellipse-based frame to absolute one rot f32.Affine2D // rotation matrix for each segment
inv f32.Affine2D // transform from ellipse-based frame to absolute one
)
center := f32.Point{ center := f32.Point{
X: 0.5 * (f1.X + f2.X), X: 0.5 * (f1.X + f2.X),
Y: 0.5 * (f1.Y + f2.Y), Y: 0.5 * (f1.Y + f2.Y),
+1 -106
View File
@@ -9,111 +9,6 @@ import (
"gioui.org/internal/f32" "gioui.org/internal/f32"
) )
func TestNormPt(t *testing.T) {
type scenario struct {
l float32
ptIn, ptOut f32.Point
}
scenarios := []scenario{
// l!=0 && X=Y=0
{l: 10, ptIn: f32.Point{X: 0, Y: 0}, ptOut: f32.Point{X: 0, Y: 0}},
{l: -10, ptIn: f32.Point{X: 0, Y: 0}, ptOut: f32.Point{X: 0, Y: 0}},
// l>0 & X
{l: +20, ptIn: f32.Point{X: +30, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
{l: +20, ptIn: f32.Point{X: +20, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
{l: +20, ptIn: f32.Point{X: +10, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
{l: +20, ptIn: f32.Point{X: -10, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
{l: +20, ptIn: f32.Point{X: -20, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
{l: +20, ptIn: f32.Point{X: -30, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
// l<0 & X
{l: -20, ptIn: f32.Point{X: +30, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
{l: -20, ptIn: f32.Point{X: +20, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
{l: -20, ptIn: f32.Point{X: +10, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
{l: -20, ptIn: f32.Point{X: -10, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
{l: -20, ptIn: f32.Point{X: -20, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
{l: -20, ptIn: f32.Point{X: -30, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
// l>0 & Y
{l: +20, ptIn: f32.Point{X: 0, Y: +30}, ptOut: f32.Point{X: 0, Y: +20}},
{l: +20, ptIn: f32.Point{X: 0, Y: +20}, ptOut: f32.Point{X: 0, Y: +20}},
{l: +20, ptIn: f32.Point{X: 0, Y: +10}, ptOut: f32.Point{X: 0, Y: +20}},
{l: +20, ptIn: f32.Point{X: 0, Y: -10}, ptOut: f32.Point{X: 0, Y: -20}},
{l: +20, ptIn: f32.Point{X: 0, Y: -20}, ptOut: f32.Point{X: 0, Y: -20}},
{l: +20, ptIn: f32.Point{X: 0, Y: -30}, ptOut: f32.Point{X: 0, Y: -20}},
// l<0 & Y
{l: -20, ptIn: f32.Point{X: 0, Y: +30}, ptOut: f32.Point{X: 0, Y: -20}},
{l: -20, ptIn: f32.Point{X: 0, Y: +20}, ptOut: f32.Point{X: 0, Y: -20}},
{l: -20, ptIn: f32.Point{X: 0, Y: +10}, ptOut: f32.Point{X: 0, Y: -20}},
{l: -20, ptIn: f32.Point{X: 0, Y: -10}, ptOut: f32.Point{X: 0, Y: +20}},
{l: -20, ptIn: f32.Point{X: 0, Y: -20}, ptOut: f32.Point{X: 0, Y: +20}},
{l: -20, ptIn: f32.Point{X: 0, Y: -30}, ptOut: f32.Point{X: 0, Y: +20}},
// l>0 && X=Y
{l: +20, ptIn: f32.Point{X: +90, Y: +90}, ptOut: f32.Point{X: +14.142137, Y: +14.142137}},
{l: +20, ptIn: f32.Point{X: +30, Y: +30}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
{l: +20, ptIn: f32.Point{X: +20, Y: +20}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
{l: +20, ptIn: f32.Point{X: +10, Y: +10}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
{l: +20, ptIn: f32.Point{X: -10, Y: -10}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
{l: +20, ptIn: f32.Point{X: -20, Y: -20}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
{l: +20, ptIn: f32.Point{X: -30, Y: -30}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
{l: +20, ptIn: f32.Point{X: -90, Y: -90}, ptOut: f32.Point{X: -14.142137, Y: -14.142137}},
// l>0 && X=-Y
{l: +20, ptIn: f32.Point{X: +90, Y: -90}, ptOut: f32.Point{X: +14.142137, Y: -14.142137}},
{l: +20, ptIn: f32.Point{X: +30, Y: -30}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
{l: +20, ptIn: f32.Point{X: +20, Y: -20}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
{l: +20, ptIn: f32.Point{X: +10, Y: -10}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
{l: +20, ptIn: f32.Point{X: -10, Y: +10}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
{l: +20, ptIn: f32.Point{X: -20, Y: +20}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
{l: +20, ptIn: f32.Point{X: -30, Y: +30}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
{l: +20, ptIn: f32.Point{X: -90, Y: +90}, ptOut: f32.Point{X: -14.142137, Y: +14.142137}},
// l<0 && X=Y
{l: -20, ptIn: f32.Point{X: +90, Y: +90}, ptOut: f32.Point{X: -14.142137, Y: -14.142137}},
{l: -20, ptIn: f32.Point{X: +30, Y: +30}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
{l: -20, ptIn: f32.Point{X: +20, Y: +20}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
{l: -20, ptIn: f32.Point{X: +10, Y: +10}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
{l: -20, ptIn: f32.Point{X: -10, Y: -10}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
{l: -20, ptIn: f32.Point{X: -20, Y: -20}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
{l: -20, ptIn: f32.Point{X: -30, Y: -30}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
{l: -20, ptIn: f32.Point{X: -90, Y: -90}, ptOut: f32.Point{X: +14.142137, Y: +14.142137}},
// l<0 && X=-Y
{l: -20, ptIn: f32.Point{X: +90, Y: -90}, ptOut: f32.Point{X: -14.142137, Y: +14.142137}},
{l: -20, ptIn: f32.Point{X: +30, Y: -30}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
{l: -20, ptIn: f32.Point{X: +20, Y: -20}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
{l: -20, ptIn: f32.Point{X: +10, Y: -10}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
{l: -20, ptIn: f32.Point{X: -10, Y: +10}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
{l: -20, ptIn: f32.Point{X: -20, Y: +20}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
{l: -20, ptIn: f32.Point{X: -30, Y: +30}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
{l: -20, ptIn: f32.Point{X: -90, Y: +90}, ptOut: f32.Point{X: +14.142137, Y: -14.142137}},
// l!=0 && Hypot=l
{l: 5, ptIn: f32.Point{X: 3, Y: 4}, ptOut: f32.Point{X: 3, Y: 4}},
{l: 5, ptIn: f32.Point{X: 3, Y: -4}, ptOut: f32.Point{X: 3, Y: -4}},
{l: 5, ptIn: f32.Point{X: -3, Y: -4}, ptOut: f32.Point{X: -3, Y: -4}},
{l: 5, ptIn: f32.Point{X: -3, Y: 4}, ptOut: f32.Point{X: -3, Y: 4}},
{l: -5, ptIn: f32.Point{X: 3, Y: 4}, ptOut: f32.Point{X: -3, Y: -4}},
{l: -5, ptIn: f32.Point{X: 3, Y: -4}, ptOut: f32.Point{X: -3, Y: 4}},
{l: -5, ptIn: f32.Point{X: -3, Y: -4}, ptOut: f32.Point{X: 3, Y: 4}},
{l: -5, ptIn: f32.Point{X: -3, Y: 4}, ptOut: f32.Point{X: 3, Y: -4}},
}
for i, s := range scenarios {
t.Run(strconv.Itoa(i), func(t *testing.T) {
actual := normPt(s.ptIn, s.l)
if actual != s.ptOut {
t.Errorf("in: %v*%v, expected: %v, actual: %v", s.l, s.ptIn, s.ptOut, actual)
}
})
}
}
func BenchmarkSplitCubic(b *testing.B) { func BenchmarkSplitCubic(b *testing.B) {
type scenario struct { type scenario struct {
segments int segments int
@@ -157,7 +52,7 @@ func BenchmarkSplitCubic(b *testing.B) {
from, ctrl0, ctrl1, to := s.from, s.ctrl0, s.ctrl1, s.to from, ctrl0, ctrl1, to := s.from, s.ctrl0, s.ctrl1, s.to
quads := make([]QuadSegment, s.segments) quads := make([]QuadSegment, s.segments)
b.ResetTimer() b.ResetTimer()
for b.Loop() { for i := 0; i < b.N; i++ {
quads = SplitCubic(from, ctrl0, ctrl1, to, quads[:0]) quads = SplitCubic(from, ctrl0, ctrl1, to, quads[:0])
} }
if len(quads) != s.segments { if len(quads) != s.segments {
+7 -7
View File
@@ -385,7 +385,6 @@ static VkResult vkQueuePresentKHR(PFN_vkQueuePresentKHR f, VkQueue queue, const
} }
*/ */
import "C" import "C"
import ( import (
"errors" "errors"
"fmt" "fmt"
@@ -1168,6 +1167,7 @@ func CreateFramebuffer(d Device, rp RenderPass, view ImageView, width, height in
return nilFramebuffer, fmt.Errorf("vulkan: vkCreateFramebuffer: %w", err) return nilFramebuffer, fmt.Errorf("vulkan: vkCreateFramebuffer: %w", err)
} }
return fbo, nil return fbo, nil
} }
func DestroyFramebuffer(d Device, f Framebuffer) { func DestroyFramebuffer(d Device, f Framebuffer) {
@@ -1893,27 +1893,27 @@ func BuildWriteDescriptorSetBuffer(set DescriptorSet, binding int, typ Descripto
} }
} }
func PushConstantRangeStageFlags(r PushConstantRange) ShaderStageFlags { func (r PushConstantRange) StageFlags() ShaderStageFlags {
return r.stageFlags return r.stageFlags
} }
func PushConstantRangeOffset(r PushConstantRange) int { func (r PushConstantRange) Offset() int {
return int(r.offset) return int(r.offset)
} }
func PushConstantRangeSize(r PushConstantRange) int { func (r PushConstantRange) Size() int {
return int(r.size) return int(r.size)
} }
func QueueFamilyPropertiesFlags(p QueueFamilyProperties) QueueFlags { func (p QueueFamilyProperties) Flags() QueueFlags {
return p.queueFlags return p.queueFlags
} }
func SurfaceCapabilitiesMinExtent(c SurfaceCapabilities) image.Point { func (c SurfaceCapabilities) MinExtent() image.Point {
return image.Pt(int(c.minImageExtent.width), int(c.minImageExtent.height)) return image.Pt(int(c.minImageExtent.width), int(c.minImageExtent.height))
} }
func SurfaceCapabilitiesMaxExtent(c SurfaceCapabilities) image.Point { func (c SurfaceCapabilities) MaxExtent() image.Point {
return image.Pt(int(c.maxImageExtent.width), int(c.maxImageExtent.height)) return image.Pt(int(c.maxImageExtent.width), int(c.maxImageExtent.height))
} }
-1
View File
@@ -17,7 +17,6 @@ static VkResult vkCreateAndroidSurfaceKHR(PFN_vkCreateAndroidSurfaceKHR f, VkIns
} }
*/ */
import "C" import "C"
import ( import (
"fmt" "fmt"
"unsafe" "unsafe"
-1
View File
@@ -19,7 +19,6 @@ static VkResult vkCreateWaylandSurfaceKHR(PFN_vkCreateWaylandSurfaceKHR f, VkIns
} }
*/ */
import "C" import "C"
import ( import (
"fmt" "fmt"
"unsafe" "unsafe"
-1
View File
@@ -17,7 +17,6 @@ static VkResult vkCreateXlibSurfaceKHR(PFN_vkCreateXlibSurfaceKHR f, VkInstance
} }
*/ */
import "C" import "C"
import ( import (
"fmt" "fmt"
"unsafe" "unsafe"
+1 -1
View File
@@ -10,7 +10,7 @@ import (
// Tag is the stable identifier for an event handler. // Tag is the stable identifier for an event handler.
// For a handler h, the tag is typically &h. // For a handler h, the tag is typically &h.
type Tag any type Tag interface{}
// Event is the marker interface for events. // Event is the marker interface for events.
type Event interface { type Event interface {
+4 -3
View File
@@ -4,7 +4,6 @@ package input
import ( import (
"io" "io"
"slices"
"gioui.org/io/clipboard" "gioui.org/io/clipboard"
"gioui.org/io/event" "gioui.org/io/event"
@@ -61,8 +60,10 @@ func (q *clipboardQueue) ProcessWriteClipboard(req clipboard.WriteCmd) {
} }
func (q *clipboardQueue) ProcessReadClipboard(state clipboardState, tag event.Tag) clipboardState { func (q *clipboardQueue) ProcessReadClipboard(state clipboardState, tag event.Tag) clipboardState {
if slices.Contains(state.receivers, tag) { for _, k := range state.receivers {
return state if k == tag {
return state
}
} }
n := len(state.receivers) n := len(state.receivers)
state.receivers = append(state.receivers[:n:n], tag) state.receivers = append(state.receivers[:n:n], tag)
+1 -1
View File
@@ -52,7 +52,7 @@ func TestQueueProcessReadClipboard(t *testing.T) {
assertClipboardReadCmd(t, r, 1) assertClipboardReadCmd(t, r, 1)
ops.Reset() ops.Reset()
for range 3 { for i := 0; i < 3; i++ {
// No ReadCmd // No ReadCmd
// One receiver must still wait for response // One receiver must still wait for response
+4 -4
View File
@@ -4,7 +4,6 @@ package input
import ( import (
"image" "image"
"slices"
"sort" "sort"
"gioui.org/f32" "gioui.org/f32"
@@ -281,7 +280,6 @@ func (q *keyQueue) Focus(handlers map[event.Tag]*handler, state keyState, focus
return state, nil return state, nil
} }
state.content = EditorState{} state.content = EditorState{}
state.content.Selection.Transform = f32.AffineId()
var evts []taggedEvent var evts []taggedEvent
if state.focus != nil { if state.focus != nil {
evts = append(evts, taggedEvent{tag: state.focus, event: key.FocusEvent{Focus: false}}) evts = append(evts, taggedEvent{tag: state.focus, event: key.FocusEvent{Focus: false}})
@@ -306,8 +304,10 @@ func (s keyState) softKeyboard(show bool) keyState {
} }
func (k *keyFilter) Add(f key.Filter) { func (k *keyFilter) Add(f key.Filter) {
if slices.Contains(*k, f) { for _, f2 := range *k {
return if f == f2 {
return
}
} }
*k = append(*k, f) *k = append(*k, f)
} }
+3 -4
View File
@@ -251,10 +251,9 @@ func TestFocusScroll(t *testing.T) {
filters := []event.Filter{ filters := []event.Filter{
key.FocusFilter{Target: h}, key.FocusFilter{Target: h},
pointer.Filter{ pointer.Filter{
Target: h, Target: h,
Kinds: pointer.Scroll, Kinds: pointer.Scroll,
ScrollX: pointer.ScrollRange{Min: -100, Max: +100}, ScrollBounds: image.Rect(-100, -100, 100, 100),
ScrollY: pointer.ScrollRange{Min: -100, Max: +100},
}, },
} }
events(r, -1, filters...) events(r, -1, filters...)
+41 -33
View File
@@ -5,7 +5,6 @@ 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"
@@ -73,7 +72,7 @@ type pointerHandler struct {
type pointerFilter struct { type pointerFilter struct {
kinds pointer.Kind kinds pointer.Kind
// min and max horizontal/vertical scroll // min and max horizontal/vertical scroll
scrollX, scrollY pointer.ScrollRange scrollRange image.Rectangle
sourceMimes []string sourceMimes []string
targetMimes []string targetMimes []string
@@ -144,9 +143,7 @@ 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 {
@@ -260,11 +257,6 @@ func (q *pointerQueue) grab(state pointerState, req pointer.GrabCmd) (pointerSta
if !p.pressed || p.id != req.ID { if !p.pressed || p.id != req.ID {
continue 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. // Drop other handlers that lost their grab.
for i := len(p.handlers) - 1; i >= 0; i-- { for i := len(p.handlers) - 1; i >= 0; i-- {
if tag := p.handlers[i]; tag != req.Tag { if tag := p.handlers[i]; tag != req.Tag {
@@ -290,19 +282,22 @@ func (c *pointerCollector) inputOp(tag event.Tag, state *pointerHandler) {
func (p *pointerFilter) Add(f event.Filter) { func (p *pointerFilter) Add(f event.Filter) {
switch f := f.(type) { switch f := f.(type) {
case transfer.SourceFilter: case transfer.SourceFilter:
if slices.Contains(p.sourceMimes, f.Type) { for _, m := range p.sourceMimes {
return if m == f.Type {
return
}
} }
p.sourceMimes = append(p.sourceMimes, f.Type) p.sourceMimes = append(p.sourceMimes, f.Type)
case transfer.TargetFilter: case transfer.TargetFilter:
if slices.Contains(p.targetMimes, f.Type) { for _, m := range p.targetMimes {
return if m == f.Type {
return
}
} }
p.targetMimes = append(p.targetMimes, f.Type) p.targetMimes = append(p.targetMimes, f.Type)
case pointer.Filter: case pointer.Filter:
p.kinds = p.kinds | f.Kinds p.kinds = p.kinds | f.Kinds
p.scrollX = p.scrollX.Union(f.ScrollX) p.scrollRange = p.scrollRange.Union(f.ScrollBounds)
p.scrollY = p.scrollY.Union(f.ScrollY)
} }
} }
@@ -313,12 +308,16 @@ func (p *pointerFilter) Matches(e event.Event) bool {
case transfer.CancelEvent, transfer.InitiateEvent: case transfer.CancelEvent, transfer.InitiateEvent:
return len(p.sourceMimes) > 0 || len(p.targetMimes) > 0 return len(p.sourceMimes) > 0 || len(p.targetMimes) > 0
case transfer.RequestEvent: case transfer.RequestEvent:
if slices.Contains(p.sourceMimes, e.Type) { for _, t := range p.sourceMimes {
return true if t == e.Type {
return true
}
} }
case transfer.DataEvent: case transfer.DataEvent:
if slices.Contains(p.targetMimes, e.Type) { for _, t := range p.targetMimes {
return true if t == e.Type {
return true
}
} }
} }
return false return false
@@ -326,8 +325,7 @@ func (p *pointerFilter) Matches(e event.Event) bool {
func (p *pointerFilter) Merge(p2 pointerFilter) { func (p *pointerFilter) Merge(p2 pointerFilter) {
p.kinds = p.kinds | p2.kinds p.kinds = p.kinds | p2.kinds
p.scrollX = p.scrollX.Union(p2.scrollX) p.scrollRange = p.scrollRange.Union(p2.scrollRange)
p.scrollY = p.scrollY.Union(p2.scrollY)
p.sourceMimes = append(p.sourceMimes, p2.sourceMimes...) p.sourceMimes = append(p.sourceMimes, p2.sourceMimes...)
p.targetMimes = append(p.targetMimes, p2.targetMimes...) p.targetMimes = append(p.targetMimes, p2.targetMimes...)
} }
@@ -335,8 +333,8 @@ func (p *pointerFilter) Merge(p2 pointerFilter) {
// clampScroll splits a scroll distance in the remaining scroll and the // clampScroll splits a scroll distance in the remaining scroll and the
// scroll accepted by the filter. // scroll accepted by the filter.
func (p *pointerFilter) clampScroll(scroll f32.Point) (left, scrolled f32.Point) { func (p *pointerFilter) clampScroll(scroll f32.Point) (left, scrolled f32.Point) {
left.X, scrolled.X = clampSplit(scroll.X, p.scrollX.Min, p.scrollX.Max) left.X, scrolled.X = clampSplit(scroll.X, p.scrollRange.Min.X, p.scrollRange.Max.X)
left.Y, scrolled.Y = clampSplit(scroll.Y, p.scrollY.Min, p.scrollY.Max) left.Y, scrolled.Y = clampSplit(scroll.Y, p.scrollRange.Min.Y, p.scrollRange.Max.Y)
return return
} }
@@ -413,7 +411,7 @@ func (q *pointerQueue) offerData(handlers map[event.Tag]*handler, state pointerS
}, },
}}) }})
} }
state.pointers = slices.Clone(state.pointers) state.pointers = append([]pointerInfo{}, state.pointers...)
state.pointers[i], evts = q.deliverTransferCancelEvent(handlers, p, evts) state.pointers[i], evts = q.deliverTransferCancelEvent(handlers, p, evts)
break break
} }
@@ -605,7 +603,7 @@ func (q *pointerQueue) reset() {
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 = slices.Delete(ids, i, i+1) ids = append(ids[:i], ids[i+1:]...)
} else { } else {
ids[i].used = false ids[i].used = false
} }
@@ -636,7 +634,7 @@ func (q *pointerQueue) Frame(handlers map[event.Tag]*handler, state pointerState
changed := false changed := false
p, evts, state.cursor, changed = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, p.last) p, evts, state.cursor, changed = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, p.last)
if changed { if changed {
state.pointers = slices.Clone(state.pointers) state.pointers = append([]pointerInfo{}, state.pointers...)
state.pointers[i] = p state.pointers[i] = p
} }
} }
@@ -772,17 +770,19 @@ func (q *pointerQueue) Push(handlers map[event.Tag]*handler, state pointerState,
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.
state.pointers = slices.Concat(state.pointers[:pidx:pidx], state.pointers[pidx+1:]) state.pointers = append(state.pointers[:pidx:pidx], state.pointers[pidx+1:]...)
} else { } else {
state.pointers = slices.Clone(state.pointers) state.pointers = append([]pointerInfo{}, state.pointers...)
state.pointers[pidx] = p state.pointers[pidx] = p
} }
return state, evts return state, evts
} }
func (q *pointerQueue) deliverEvent(handlers map[event.Tag]*handler, p pointerInfo, evts []taggedEvent, e pointer.Event) []taggedEvent { 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
} }
scroll := e.Scroll scroll := e.Scroll
for _, k := range p.handlers { for _, k := range p.handlers {
@@ -801,6 +801,10 @@ func (q *pointerQueue) deliverEvent(handlers map[event.Tag]*handler, p pointerIn
scroll, e.Scroll = f.clampScroll(scroll) scroll, e.Scroll = f.clampScroll(scroll)
} }
e := e e := e
if foremost {
foremost = false
e.Priority = pointer.Foremost
}
e.Position = q.invTransform(h.pointer.areaPlusOne-1, e.Position) e.Position = q.invTransform(h.pointer.areaPlusOne-1, e.Position)
evts = append(evts, taggedEvent{event: e, tag: k}) evts = append(evts, taggedEvent{event: e, tag: k})
} }
@@ -964,8 +968,10 @@ 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 {
if slices.Contains(tags, tag) { for _, t := range tags {
return tags if t == tag {
return tags
}
} }
return append(tags, tag) return append(tags, tag)
} }
@@ -973,8 +979,10 @@ func addHandler(tags []event.Tag, tag event.Tag) []event.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 *pointerFilter) (first string, matched bool) { func firstMimeMatch(src, tgt *pointerFilter) (first string, matched bool) {
for _, m1 := range tgt.targetMimes { for _, m1 := range tgt.targetMimes {
if slices.Contains(src.sourceMimes, m1) { for _, m2 := range src.sourceMimes {
return m1, true if m1 == m2 {
return m1, true
}
} }
} }
return "", false return "", false
+22 -91
View File
@@ -97,39 +97,6 @@ func TestPointerDragNegative(t *testing.T) {
assertEventPointerTypeSequence(t, events(&r, -1, f), pointer.Enter, pointer.Press, pointer.Leave, pointer.Drag) assertEventPointerTypeSequence(t, events(&r, -1, f), pointer.Enter, pointer.Press, pointer.Leave, pointer.Drag)
} }
func TestIgnoredGrab(t *testing.T) {
handler1 := new(int)
handler2 := new(int)
var ops op.Ops
filter := func(t event.Tag) event.Filter {
return pointer.Filter{Target: t, Kinds: pointer.Press | pointer.Release | pointer.Cancel}
}
event.Op(&ops, handler1)
event.Op(&ops, handler2)
var r Router
assertEventPointerTypeSequence(t, events(&r, -1, filter(handler1)), pointer.Cancel)
assertEventPointerTypeSequence(t, events(&r, -1, filter(handler2)), pointer.Cancel)
r.Frame(&ops)
r.Queue(
pointer.Event{
Kind: pointer.Press,
Position: f32.Pt(50, 50),
},
pointer.Event{
Kind: pointer.Release,
Position: f32.Pt(50, 50),
},
)
assertEventPointerTypeSequence(t, events(&r, 1, filter(handler1)), pointer.Press)
assertEventPointerTypeSequence(t, events(&r, 1, filter(handler2)), pointer.Press)
r.Source().Execute(pointer.GrabCmd{Tag: handler1})
r.Source().Execute(pointer.GrabCmd{Tag: handler2})
assertEventPointerTypeSequence(t, events(&r, 1, filter(handler1)), pointer.Release)
assertEventPointerTypeSequence(t, events(&r, 1, filter(handler2)), pointer.Cancel)
}
func TestPointerGrab(t *testing.T) { func TestPointerGrab(t *testing.T) {
handler1 := new(int) handler1 := new(int)
handler2 := new(int) handler2 := new(int)
@@ -333,9 +300,9 @@ func TestPointerPriority(t *testing.T) {
r1 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops) r1 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops)
f1 := func(t event.Tag) event.Filter { f1 := func(t event.Tag) event.Filter {
return pointer.Filter{ return pointer.Filter{
Target: t, Target: t,
Kinds: pointer.Scroll, Kinds: pointer.Scroll,
ScrollX: pointer.ScrollRange{Max: 100}, ScrollBounds: image.Rectangle{Max: image.Point{X: 100}},
} }
} }
events(&r, -1, f1(handler1)) events(&r, -1, f1(handler1))
@@ -344,9 +311,9 @@ func TestPointerPriority(t *testing.T) {
r2 := clip.Rect(image.Rect(0, 0, 100, 50)).Push(&ops) r2 := clip.Rect(image.Rect(0, 0, 100, 50)).Push(&ops)
f2 := func(t event.Tag) event.Filter { f2 := func(t event.Tag) event.Filter {
return pointer.Filter{ return pointer.Filter{
Target: t, Target: t,
Kinds: pointer.Scroll, Kinds: pointer.Scroll,
ScrollX: pointer.ScrollRange{Max: 20}, ScrollBounds: image.Rectangle{Max: image.Point{X: 20}},
} }
} }
events(&r, -1, f2(handler2)) events(&r, -1, f2(handler2))
@@ -357,10 +324,9 @@ func TestPointerPriority(t *testing.T) {
r3 := clip.Rect(image.Rect(0, 100, 100, 200)).Push(&ops) r3 := clip.Rect(image.Rect(0, 100, 100, 200)).Push(&ops)
f3 := func(t event.Tag) event.Filter { f3 := func(t event.Tag) event.Filter {
return pointer.Filter{ return pointer.Filter{
Target: t, Target: t,
Kinds: pointer.Scroll, Kinds: pointer.Scroll,
ScrollX: pointer.ScrollRange{Min: -20}, ScrollBounds: image.Rectangle{Min: image.Point{X: -20, Y: -40}},
ScrollY: pointer.ScrollRange{Min: -40},
} }
} }
events(&r, -1, f3(handler3)) events(&r, -1, f3(handler3))
@@ -400,9 +366,9 @@ func TestPointerPriority(t *testing.T) {
assertEventPointerTypeSequence(t, hev1, pointer.Scroll, pointer.Scroll) assertEventPointerTypeSequence(t, hev1, pointer.Scroll, pointer.Scroll)
assertEventPointerTypeSequence(t, hev2, pointer.Scroll) assertEventPointerTypeSequence(t, hev2, pointer.Scroll)
assertEventPointerTypeSequence(t, hev3, pointer.Scroll) assertEventPointerTypeSequence(t, hev3, pointer.Scroll)
assertEventPriorities(t, hev1, pointer.Shared, pointer.Shared) assertEventPriorities(t, hev1, pointer.Shared, pointer.Foremost)
assertEventPriorities(t, hev2, pointer.Shared) assertEventPriorities(t, hev2, pointer.Foremost)
assertEventPriorities(t, hev3, pointer.Shared) assertEventPriorities(t, hev3, pointer.Foremost)
assertScrollEvent(t, hev1[0], f32.Pt(30, 0)) assertScrollEvent(t, hev1[0], f32.Pt(30, 0))
assertScrollEvent(t, hev2[0], f32.Pt(20, 0)) assertScrollEvent(t, hev2[0], f32.Pt(20, 0))
assertScrollEvent(t, hev1[1], f32.Pt(50, 0)) assertScrollEvent(t, hev1[1], f32.Pt(50, 0))
@@ -711,31 +677,26 @@ func TestCursor(t *testing.T) {
cursors []pointer.Cursor cursors []pointer.Cursor
want pointer.Cursor want pointer.Cursor
}{ }{
{ {label: "no movement",
label: "no movement",
cursors: []pointer.Cursor{pointer.CursorPointer}, cursors: []pointer.Cursor{pointer.CursorPointer},
want: pointer.CursorDefault, want: pointer.CursorDefault,
}, },
{ {label: "move inside",
label: "move inside",
cursors: []pointer.Cursor{pointer.CursorPointer}, cursors: []pointer.Cursor{pointer.CursorPointer},
events: _at(50, 50), events: _at(50, 50),
want: pointer.CursorPointer, want: pointer.CursorPointer,
}, },
{ {label: "move outside",
label: "move outside",
cursors: []pointer.Cursor{pointer.CursorPointer}, cursors: []pointer.Cursor{pointer.CursorPointer},
events: _at(200, 200), events: _at(200, 200),
want: pointer.CursorDefault, want: pointer.CursorDefault,
}, },
{ {label: "move back inside",
label: "move back inside",
cursors: []pointer.Cursor{pointer.CursorPointer}, cursors: []pointer.Cursor{pointer.CursorPointer},
events: _at(50, 50), events: _at(50, 50),
want: pointer.CursorPointer, want: pointer.CursorPointer,
}, },
{ {label: "send key events while inside",
label: "send key events while inside",
cursors: []pointer.Cursor{pointer.CursorPointer}, cursors: []pointer.Cursor{pointer.CursorPointer},
events: []event.Event{ events: []event.Event{
key.Event{Name: "A", State: key.Press}, key.Event{Name: "A", State: key.Press},
@@ -743,8 +704,7 @@ func TestCursor(t *testing.T) {
}, },
want: pointer.CursorPointer, want: pointer.CursorPointer,
}, },
{ {label: "send key events while outside",
label: "send key events while outside",
cursors: []pointer.Cursor{pointer.CursorPointer}, cursors: []pointer.Cursor{pointer.CursorPointer},
events: append( events: append(
_at(200, 200), _at(200, 200),
@@ -753,8 +713,7 @@ func TestCursor(t *testing.T) {
), ),
want: pointer.CursorDefault, want: pointer.CursorDefault,
}, },
{ {label: "add new input on top while inside",
label: "add new input on top while inside",
cursors: []pointer.Cursor{pointer.CursorPointer, pointer.CursorCrosshair}, cursors: []pointer.Cursor{pointer.CursorPointer, pointer.CursorCrosshair},
events: append( events: append(
_at(50, 50), _at(50, 50),
@@ -765,8 +724,7 @@ func TestCursor(t *testing.T) {
), ),
want: pointer.CursorCrosshair, want: pointer.CursorCrosshair,
}, },
{ {label: "remove input on top while inside",
label: "remove input on top while inside",
cursors: []pointer.Cursor{pointer.CursorPointer}, cursors: []pointer.Cursor{pointer.CursorPointer},
events: append( events: append(
_at(50, 50), _at(50, 50),
@@ -1127,33 +1085,6 @@ func TestPassCursor(t *testing.T) {
} }
} }
func TestPartialEvent(t *testing.T) {
var ops op.Ops
var r Router
rect := clip.Rect(image.Rect(0, 0, 100, 100))
background := rect.Push(&ops)
event.Op(&ops, 1)
background.Pop()
overlayPass := pointer.PassOp{}.Push(&ops)
overlay := rect.Push(&ops)
event.Op(&ops, 2)
overlay.Pop()
overlayPass.Pop()
assertEventSequence(t, events(&r, -1, pointer.Filter{Target: 1, Kinds: pointer.Press}))
assertEventSequence(t, events(&r, -1, pointer.Filter{Target: 2, Kinds: pointer.Press}))
r.Frame(&ops)
r.Queue(pointer.Event{
Kind: pointer.Press,
})
assertEventSequence(t, events(&r, -1, pointer.Filter{Target: 1, Kinds: pointer.Press}, key.FocusFilter{Target: 1}),
key.FocusEvent{}, pointer.Event{Kind: pointer.Press, Source: pointer.Mouse, Priority: pointer.Shared})
r.Source().Execute(key.FocusCmd{Tag: 1})
assertEventSequence(t, events(&r, -1, pointer.Filter{Target: 2, Kinds: pointer.Press}),
pointer.Event{Kind: pointer.Press, Source: pointer.Mouse, Priority: pointer.Shared})
}
// offer satisfies io.ReadCloser for use in data transfers. // offer satisfies io.ReadCloser for use in data transfers.
type offer struct { type offer struct {
data string data string
@@ -1297,7 +1228,7 @@ func BenchmarkRouterAdd(b *testing.B) {
handlerCount := i handlerCount := i
b.Run(fmt.Sprintf("%d-handlers", i), func(b *testing.B) { b.Run(fmt.Sprintf("%d-handlers", i), func(b *testing.B) {
handlers := make([]event.Tag, handlerCount) handlers := make([]event.Tag, handlerCount)
for i := range handlerCount { for i := 0; i < handlerCount; i++ {
h := new(int) h := new(int)
*h = i *h = i
handlers[i] = h handlers[i] = h
@@ -1319,7 +1250,7 @@ func BenchmarkRouterAdd(b *testing.B) {
r.Frame(&ops) r.Frame(&ops)
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer() b.ResetTimer()
for b.Loop() { for i := 0; i < b.N; i++ {
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Kind: pointer.Move, Kind: pointer.Move,
+42 -53
View File
@@ -5,7 +5,6 @@ package input
import ( import (
"image" "image"
"io" "io"
"slices"
"strings" "strings"
"time" "time"
@@ -36,9 +35,10 @@ type Router struct {
queue keyQueue queue keyQueue
// The following fields have the same purpose as the fields in // The following fields have the same purpose as the fields in
// type handler, but for key.Events. // type handler, but for key.Events.
filter keyFilter filter keyFilter
nextFilter keyFilter nextFilter keyFilter
scratchFilter keyFilter processedFilter keyFilter
scratchFilter keyFilter
} }
cqueue clipboardQueue cqueue clipboardQueue
// states is the list of pending state changes resulting from // states is the list of pending state changes resulting from
@@ -61,10 +61,9 @@ type Router struct {
} }
// Source implements the interface between a Router and user interface widgets. // Source implements the interface between a Router and user interface widgets.
// The zero-value Source is disabled. // The value Source is disabled.
type Source struct { type Source struct {
r *Router r *Router
disabled bool
} }
// Command represents a request such as moving the focus, or initiating a clipboard read. // Command represents a request such as moving the focus, or initiating a clipboard read.
@@ -179,17 +178,10 @@ func (s Source) Execute(c Command) {
s.r.execute(c) s.r.execute(c)
} }
// Disabled returns a copy of this source that don't deliver any events.
func (s Source) Disabled() Source {
s2 := s
s2.disabled = true
return s2
}
// Enabled reports whether the source is enabled. Only enabled // Enabled reports whether the source is enabled. Only enabled
// Sources deliver events. // Sources deliver events and respond to commands.
func (s Source) Enabled() bool { func (s Source) Enabled() bool {
return s.r != nil && !s.disabled return s.r != nil
} }
// Focused reports whether tag is focused, according to the most recent // Focused reports whether tag is focused, according to the most recent
@@ -202,7 +194,6 @@ func (s Source) Focused(tag event.Tag) bool {
} }
// Event returns the next event that matches at least one of filters. // Event returns the next event that matches at least one of filters.
// If the source is disabled, no events will be reported.
func (s Source) Event(filters ...event.Filter) (event.Event, bool) { func (s Source) Event(filters ...event.Filter) (event.Event, bool) {
if !s.Enabled() { if !s.Enabled() {
return nil, false return nil, false
@@ -284,29 +275,28 @@ func (q *Router) Event(filters ...event.Filter) (event.Event, bool) {
} }
} }
} }
for i := range q.changes { if !q.deferring {
if q.deferring && i > 0 { for i := range q.changes {
break change := &q.changes[i]
} for j, evt := range change.events {
change := &q.changes[i] match := false
for j, evt := range change.events { switch e := evt.event.(type) {
match := false case key.Event:
switch e := evt.event.(type) { match = q.key.scratchFilter.Matches(change.state.keyState.focus, e, false)
case key.Event: default:
match = q.key.scratchFilter.Matches(change.state.keyState.focus, e, false) for _, tf := range q.scratchFilters {
default: if evt.tag == tf.tag && tf.filter.Matches(evt.event) {
for _, tf := range q.scratchFilters { match = true
if evt.tag == tf.tag && tf.filter.Matches(evt.event) { break
match = true }
break
} }
} }
} if match {
if match { change.events = append(change.events[:j], change.events[j+1:]...)
change.events = slices.Delete(change.events, j, j+1) // Fast forward state to last matched.
// Fast forward state to last matched. q.collapseState(i)
q.collapseState(i) return evt.event, true
return evt.event, true }
} }
} }
} }
@@ -314,6 +304,7 @@ func (q *Router) Event(filters ...event.Filter) (event.Event, bool) {
h := q.stateFor(tf.tag) h := q.stateFor(tf.tag)
h.processedFilter.Merge(tf.filter) h.processedFilter.Merge(tf.filter)
} }
q.key.processedFilter = append(q.key.processedFilter, q.key.scratchFilter...)
return nil, false return nil, false
} }
@@ -324,15 +315,15 @@ func (q *Router) collapseState(idx int) {
} }
first := &q.changes[0] first := &q.changes[0]
first.state = q.changes[idx].state first.state = q.changes[idx].state
for _, ch := range q.changes[1 : idx+1] { for i := 1; i <= idx; i++ {
first.events = append(first.events, ch.events...) first.events = append(first.events, q.changes[i].events...)
} }
q.changes = append(q.changes[:1], q.changes[idx+1:]...) q.changes = append(q.changes[:1], q.changes[idx+1:]...)
} }
// Frame completes the current frame and starts a new with the // Frame replaces the declared handlers from the supplied
// handlers from the frame argument. Remaining events are discarded, // operation list. The text input state, wakeup time and whether
// unless they were deferred by a command. // there are active profile handlers is also saved.
func (q *Router) Frame(frame *op.Ops) { func (q *Router) Frame(frame *op.Ops) {
var remaining []event.Event var remaining []event.Event
if n := len(q.changes); n > 0 { if n := len(q.changes); n > 0 {
@@ -628,11 +619,11 @@ func (q *Router) RevealFocus(viewport image.Rectangle) {
viewport = q.pointer.queue.ClipFor(area, viewport) viewport = q.pointer.queue.ClipFor(area, viewport)
topleft := bounds.Min.Sub(viewport.Min) topleft := bounds.Min.Sub(viewport.Min)
topleft = maxPoint(topleft, bounds.Max.Sub(viewport.Max)) topleft = max(topleft, bounds.Max.Sub(viewport.Max))
topleft = minPoint(image.Pt(0, 0), topleft) topleft = min(image.Pt(0, 0), topleft)
bottomright := bounds.Max.Sub(viewport.Max) bottomright := bounds.Max.Sub(viewport.Max)
bottomright = minPoint(bottomright, bounds.Min.Sub(viewport.Min)) bottomright = min(bottomright, bounds.Min.Sub(viewport.Min))
bottomright = maxPoint(image.Pt(0, 0), bottomright) bottomright = max(image.Pt(0, 0), bottomright)
s := topleft s := topleft
if s.X == 0 { if s.X == 0 {
s.X = bottomright.X s.X = bottomright.X
@@ -659,7 +650,7 @@ func (q *Router) ScrollFocus(dist image.Point) {
})) }))
} }
func maxPoint(p1, p2 image.Point) image.Point { func max(p1, p2 image.Point) image.Point {
m := p1 m := p1
if p2.X > m.X { if p2.X > m.X {
m.X = p2.X m.X = p2.X
@@ -670,7 +661,7 @@ func maxPoint(p1, p2 image.Point) image.Point {
return m return m
} }
func minPoint(p1, p2 image.Point) image.Point { func min(p1, p2 image.Point) image.Point {
m := p1 m := p1
if p2.X < m.X { if p2.X < m.X {
m.X = p2.X m.X = p2.X
@@ -779,15 +770,13 @@ func (q *Router) collect() {
pc.Reset() pc.Reset()
kq := &q.key.queue kq := &q.key.queue
q.key.queue.Reset() q.key.queue.Reset()
t := f32.AffineId() var t f32.Affine2D
for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() { for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() {
switch ops.OpType(encOp.Data[0]) { switch ops.OpType(encOp.Data[0]) {
case ops.TypeSave: case ops.TypeSave:
id := ops.DecodeSave(encOp.Data) id := ops.DecodeSave(encOp.Data)
if extra := id - len(q.savedTrans) + 1; extra > 0 { if extra := id - len(q.savedTrans) + 1; extra > 0 {
for range extra { q.savedTrans = append(q.savedTrans, make([]f32.Affine2D, extra)...)
q.savedTrans = append(q.savedTrans, f32.AffineId())
}
} }
q.savedTrans[id] = t q.savedTrans[id] = t
case ops.TypeLoad: case ops.TypeLoad:
+1 -1
View File
@@ -15,7 +15,7 @@ func TestNoFilterAllocs(t *testing.T) {
s := r.Source() s := r.Source()
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer() b.ResetTimer()
for b.Loop() { for i := 0; i < b.N; i++ {
s.Event(pointer.Filter{}) s.Event(pointer.Filter{})
} }
}) })
+1 -1
View File
@@ -125,7 +125,7 @@ func verifyTree(t *testing.T, parent SemanticID, n SemanticNode) {
} }
func printTree(indent int, n SemanticNode) { func printTree(indent int, n SemanticNode) {
for range indent { for i := 0; i < indent; i++ {
fmt.Print("\t") fmt.Print("\t")
} }
fmt.Printf("%d: %+v\n", n.ID, n.Desc) fmt.Printf("%d: %+v\n", n.ID, n.Desc)
+2 -2
View File
@@ -37,11 +37,11 @@ For example:
var h1, h2 *Handler var h1, h2 *Handler
area := clip.Rect(...).Push(ops) area := clip.Rect(...).Push(ops)
event.Op(Ops, h1) event.Op{Tag: h1}.Add(Ops)
area.Pop() area.Pop()
area := clip.Rect(...).Push(ops) area := clip.Rect(...).Push(ops)
event.Op(Ops, h2) event.Op{Tag: h2}.Add(ops)
area.Pop() area.Pop()
implies a tree of two inner nodes, each with one pointer handler attached. implies a tree of two inner nodes, each with one pointer handler attached.
+14 -33
View File
@@ -3,6 +3,7 @@
package pointer package pointer
import ( import (
"image"
"strings" "strings"
"time" "time"
@@ -19,7 +20,7 @@ type Event struct {
Source Source Source Source
// PointerID is the id for the pointer and can be used // PointerID is the id for the pointer and can be used
// to track a particular pointer from Press to // to track a particular pointer from Press to
// Release. // Release or Cancel.
PointerID ID PointerID ID
// Priority is the priority of the receiving handler // Priority is the priority of the receiving handler
// for this event. // for this event.
@@ -43,7 +44,8 @@ type Event struct {
// PassOp sets the pass-through mode. InputOps added while the pass-through // PassOp sets the pass-through mode. InputOps added while the pass-through
// mode is set don't block events to siblings. // mode is set don't block events to siblings.
type PassOp struct{} type PassOp struct {
}
// PassStack represents a PassOp on the pass stack. // PassStack represents a PassOp on the pass stack.
type PassStack struct { type PassStack struct {
@@ -59,19 +61,12 @@ type Filter struct {
Target event.Tag Target event.Tag
// Kinds is a bitwise-or of event types to match. // Kinds is a bitwise-or of event types to match.
Kinds Kind Kinds Kind
// ScrollX and ScrollY constrain the range of scrolling events delivered // ScrollBounds describe the maximum scrollable distances in both
// to Target. Specifically, any Event e delivered to Tag will satisfy // axes. Specifically, any Event e delivered to Tag will satisfy
// //
// ScrollX.Min <= e.Scroll.X <= ScrollX.Max (horizontal axis) // ScrollBounds.Min.X <= e.Scroll.X <= ScrollBounds.Max.X (horizontal axis)
// ScrollY.Min <= e.Scroll.Y <= ScrollY.Max (vertical axis) // ScrollBounds.Min.Y <= e.Scroll.Y <= ScrollBounds.Max.Y (vertical axis)
ScrollX ScrollRange ScrollBounds image.Rectangle
ScrollY ScrollRange
}
// ScrollRange describes the range of scrolling distances in an
// axis.
type ScrollRange struct {
Min, Max int
} }
// GrabCmd requests a pointer grab on the pointer identified by ID. // GrabCmd requests a pointer grab on the pointer identified by ID.
@@ -206,6 +201,9 @@ const (
// Shared priority is for handlers that // Shared priority is for handlers that
// are part of a matching set larger than 1. // are part of a matching set larger than 1.
Shared Priority = iota Shared Priority = iota
// Foremost priority is like Shared, but the
// handler is the foremost of the matching set.
Foremost
// Grabbed is used for matching sets of size 1. // Grabbed is used for matching sets of size 1.
Grabbed Grabbed
) )
@@ -219,21 +217,8 @@ const (
ButtonSecondary ButtonSecondary
// ButtonTertiary is the tertiary button, usually the middle button. // ButtonTertiary is the tertiary button, usually the middle button.
ButtonTertiary ButtonTertiary
// ButtonQuaternary is the fourth button, usually used for browser
// navigation (backward)
ButtonQuaternary
// ButtonQuinary is the fifth button, usually used for browser
// navigation (forward)
ButtonQuinary
) )
func (s ScrollRange) Union(s2 ScrollRange) ScrollRange {
return ScrollRange{
Min: min(s.Min, s2.Min),
Max: max(s.Max, s2.Max),
}
}
// Push the current pass mode to the pass stack and set the pass mode. // Push the current pass mode to the pass stack and set the pass mode.
func (p PassOp) Push(o *op.Ops) PassStack { func (p PassOp) Push(o *op.Ops) PassStack {
id, mid := ops.PushOp(&o.Internal, ops.PassStack) id, mid := ops.PushOp(&o.Internal, ops.PassStack)
@@ -297,6 +282,8 @@ func (p Priority) String() string {
switch p { switch p {
case Shared: case Shared:
return "Shared" return "Shared"
case Foremost:
return "Foremost"
case Grabbed: case Grabbed:
return "Grabbed" return "Grabbed"
default: default:
@@ -332,12 +319,6 @@ func (b Buttons) String() string {
if b.Contain(ButtonTertiary) { if b.Contain(ButtonTertiary) {
strs = append(strs, "ButtonTertiary") strs = append(strs, "ButtonTertiary")
} }
if b.Contain(ButtonQuaternary) {
strs = append(strs, "ButtonQuaternary")
}
if b.Contain(ButtonQuinary) {
strs = append(strs, "ButtonQuinary")
}
return strings.Join(strs, "|") return strings.Join(strs, "|")
} }
+3 -6
View File
@@ -28,10 +28,6 @@ type Context struct {
// Interested users must look up and populate these values manually. // Interested users must look up and populate these values manually.
Locale system.Locale Locale system.Locale
// Values is a map of program global data associated with the context.
// It is not for use by widgets.
Values map[string]any
input.Source input.Source
*op.Ops *op.Ops
} }
@@ -46,8 +42,9 @@ func (c Context) Sp(v unit.Sp) int {
return c.Metric.Sp(v) return c.Metric.Sp(v)
} }
// Disabled returns a copy of this context that don't deliver any events. // Disabled returns a copy of this context with a disabled Source,
// blocking widgets from changing its state and receiving events.
func (c Context) Disabled() Context { func (c Context) Disabled() Context {
c.Source = c.Source.Disabled() c.Source = input.Source{}
return c return c
} }
+14 -24
View File
@@ -30,6 +30,10 @@ type FlexChild struct {
weight float32 weight float32
widget Widget widget Widget
// Scratch space.
call op.CallOp
dims Dimensions
} }
// Spacing determine the spacing mode for a Flex. // Spacing determine the spacing mode for a Flex.
@@ -84,20 +88,6 @@ func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions {
remaining := mainMax remaining := mainMax
var totalWeight float32 var totalWeight float32
cgtx := gtx cgtx := gtx
// Note: previously the scratch space was inside FlexChild.
// child.call.Add(gtx.Ops) confused the go escape analysis and caused the
// entired children slice to be allocated on the heap, including all widgets
// in it. This produced a lot of object allocations. Now the scratch space
// is separate from children, and for cases len(children) <= 32, we will
// allocate the scratch space on the stack. For cases len(children) > 32,
// only the scratch space gets allocated from the heap, during append.
type scratchSpace struct {
call op.CallOp
dims Dimensions
}
var scratchArray [32]scratchSpace
scratch := scratchArray[:0]
scratch = append(scratch, make([]scratchSpace, len(children))...)
// Lay out Rigid children. // Lay out Rigid children.
for i, child := range children { for i, child := range children {
if child.flex { if child.flex {
@@ -114,8 +104,8 @@ func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions {
if remaining < 0 { if remaining < 0 {
remaining = 0 remaining = 0
} }
scratch[i].call = c children[i].call = c
scratch[i].dims = dims children[i].dims = dims
} }
if w := f.WeightSum; w != 0 { if w := f.WeightSum; w != 0 {
totalWeight = w totalWeight = w
@@ -149,16 +139,16 @@ func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions {
if remaining < 0 { if remaining < 0 {
remaining = 0 remaining = 0
} }
scratch[i].call = c children[i].call = c
scratch[i].dims = dims children[i].dims = dims
} }
maxCross := crossMin maxCross := crossMin
var maxBaseline int var maxBaseline int
for _, scratchChild := range scratch { for _, child := range children {
if c := f.Axis.Convert(scratchChild.dims.Size).Y; c > maxCross { if c := f.Axis.Convert(child.dims.Size).Y; c > maxCross {
maxCross = c maxCross = c
} }
if b := scratchChild.dims.Size.Y - scratchChild.dims.Baseline; b > maxBaseline { if b := child.dims.Size.Y - child.dims.Baseline; b > maxBaseline {
maxBaseline = b maxBaseline = b
} }
} }
@@ -179,8 +169,8 @@ func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions {
mainSize += space / (len(children) * 2) mainSize += space / (len(children) * 2)
} }
} }
for i, scratchChild := range scratch { for i, child := range children {
dims := scratchChild.dims dims := child.dims
b := dims.Size.Y - dims.Baseline b := dims.Size.Y - dims.Baseline
var cross int var cross int
switch f.Alignment { switch f.Alignment {
@@ -195,7 +185,7 @@ func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions {
} }
pt := f.Axis.Convert(image.Pt(mainSize, cross)) pt := f.Axis.Convert(image.Pt(mainSize, cross))
trans := op.Offset(pt).Push(gtx.Ops) trans := op.Offset(pt).Push(gtx.Ops)
scratchChild.call.Add(gtx.Ops) child.call.Add(gtx.Ops)
trans.Pop() trans.Pop()
mainSize += f.Axis.Convert(dims.Size).X mainSize += f.Axis.Convert(dims.Size).X
if i < len(children)-1 { if i < len(children)-1 {
+8 -15
View File
@@ -7,7 +7,6 @@ import (
"math" "math"
"gioui.org/gesture" "gioui.org/gesture"
"gioui.org/io/pointer"
"gioui.org/op" "gioui.org/op"
"gioui.org/op/clip" "gioui.org/op/clip"
) )
@@ -29,8 +28,6 @@ type List struct {
ScrollToEnd bool ScrollToEnd bool
// Alignment is the cross axis alignment of list elements. // Alignment is the cross axis alignment of list elements.
Alignment Alignment Alignment Alignment
// ScrollAnyAxis allows any scroll axis to scroll the list, not just the main axis.
ScrollAnyAxis bool
cs Constraints cs Constraints
scroll gesture.Scroll scroll gesture.Scroll
@@ -161,19 +158,11 @@ func (l *List) update(gtx Context) {
max = 0 max = 0
} }
} }
scrollRange := image.Rectangle{
xrange := pointer.ScrollRange{Min: min, Max: max} Min: l.Axis.Convert(image.Pt(min, 0)),
yrange := pointer.ScrollRange{} Max: l.Axis.Convert(image.Pt(max, 0)),
axis := gesture.Axis(l.Axis)
if l.ScrollAnyAxis {
axis = gesture.Both
yrange = xrange
} else if l.Axis == Vertical {
xrange, yrange = yrange, xrange
} }
d := l.scroll.Update(gtx.Metric, gtx.Source, gtx.Now, axis, xrange, yrange) d := l.scroll.Update(gtx.Metric, gtx.Source, gtx.Now, gesture.Axis(l.Axis), scrollRange)
l.scrollDelta = d l.scrollDelta = d
l.Position.Offset += d l.Position.Offset += d
} }
@@ -317,6 +306,10 @@ func (l *List) layout(ops *op.Ops, macro op.MacroOp) Dimensions {
cross = (maxCross - sz.Y) / 2 cross = (maxCross - sz.Y) / 2
} }
childSize := sz.X childSize := sz.X
min := pos
if min < 0 {
min = 0
}
pt := l.Axis.Convert(image.Pt(pos, cross)) pt := l.Axis.Convert(image.Pt(pos, cross))
trans := op.Offset(pt).Push(ops) trans := op.Offset(pt).Push(ops)
child.call.Add(ops) child.call.Add(ops)
+6 -12
View File
@@ -88,8 +88,7 @@ func TestListPosition(t *testing.T) {
{label: "1 visible 0 hidden", num: 1, count: 1, last: 10}, {label: "1 visible 0 hidden", num: 1, count: 1, last: 10},
{label: "2 visible 0 hidden", num: 2, count: 2}, {label: "2 visible 0 hidden", num: 2, count: 2},
{label: "2 visible 1 hidden", num: 3, count: 2}, {label: "2 visible 1 hidden", num: 3, count: 2},
{ {label: "3 visible 0 hidden small scroll", num: 3, count: 3, offset: 5, last: -5,
label: "3 visible 0 hidden small scroll", num: 3, count: 3, offset: 5, last: -5,
scroll: _s( scroll: _s(
pointer.Event{ pointer.Event{
Source: pointer.Mouse, Source: pointer.Mouse,
@@ -108,10 +107,8 @@ func TestListPosition(t *testing.T) {
Kind: pointer.Release, Kind: pointer.Release,
Position: f32.Pt(5, 0), Position: f32.Pt(5, 0),
}, },
), )},
}, {label: "3 visible 0 hidden small scroll 2", num: 3, count: 3, offset: 3, last: -7,
{
label: "3 visible 0 hidden small scroll 2", num: 3, count: 3, offset: 3, last: -7,
scroll: _s( scroll: _s(
pointer.Event{ pointer.Event{
Source: pointer.Mouse, Source: pointer.Mouse,
@@ -130,10 +127,8 @@ func TestListPosition(t *testing.T) {
Kind: pointer.Release, Kind: pointer.Release,
Position: f32.Pt(5, 0), Position: f32.Pt(5, 0),
}, },
), )},
}, {label: "2 visible 1 hidden large scroll", num: 3, count: 2, first: 1,
{
label: "2 visible 1 hidden large scroll", num: 3, count: 2, first: 1,
scroll: _s( scroll: _s(
pointer.Event{ pointer.Event{
Source: pointer.Mouse, Source: pointer.Mouse,
@@ -152,8 +147,7 @@ func TestListPosition(t *testing.T) {
Kind: pointer.Release, Kind: pointer.Release,
Position: f32.Pt(15, 0), Position: f32.Pt(15, 0),
}, },
), )},
},
} { } {
t.Run(tc.label, func(t *testing.T) { t.Run(tc.label, func(t *testing.T) {
gtx.Ops.Reset() gtx.Ops.Reset()
+12 -22
View File
@@ -20,6 +20,10 @@ type Stack struct {
type StackChild struct { type StackChild struct {
expanded bool expanded bool
widget Widget widget Widget
// Scratch space.
call op.CallOp
dims Dimensions
} }
// Stacked returns a Stack child that is laid out with no minimum // Stacked returns a Stack child that is laid out with no minimum
@@ -48,20 +52,6 @@ func (s Stack) Layout(gtx Context, children ...StackChild) Dimensions {
// First lay out Stacked children. // First lay out Stacked children.
cgtx := gtx cgtx := gtx
cgtx.Constraints.Min = image.Point{} cgtx.Constraints.Min = image.Point{}
// Note: previously the scratch space was inside StackChild.
// child.call.Add(gtx.Ops) confused the go escape analysis and caused the
// entired children slice to be allocated on the heap, including all widgets
// in it. This produced a lot of object allocations. Now the scratch space
// is separate from children, and for cases len(children) <= 32, we will
// allocate the scratch space on the stack. For cases len(children) > 32,
// only the scratch space gets allocated from the heap, during append.
type scratchSpace struct {
call op.CallOp
dims Dimensions
}
var scratchArray [32]scratchSpace
scratch := scratchArray[:0]
scratch = append(scratch, make([]scratchSpace, len(children))...)
for i, w := range children { for i, w := range children {
if w.expanded { if w.expanded {
continue continue
@@ -75,8 +65,8 @@ func (s Stack) Layout(gtx Context, children ...StackChild) Dimensions {
if h := dims.Size.Y; h > maxSZ.Y { if h := dims.Size.Y; h > maxSZ.Y {
maxSZ.Y = h maxSZ.Y = h
} }
scratch[i].call = call children[i].call = call
scratch[i].dims = dims children[i].dims = dims
} }
// Then lay out Expanded children. // Then lay out Expanded children.
for i, w := range children { for i, w := range children {
@@ -93,14 +83,14 @@ func (s Stack) Layout(gtx Context, children ...StackChild) Dimensions {
if h := dims.Size.Y; h > maxSZ.Y { if h := dims.Size.Y; h > maxSZ.Y {
maxSZ.Y = h maxSZ.Y = h
} }
scratch[i].call = call children[i].call = call
scratch[i].dims = dims children[i].dims = dims
} }
maxSZ = gtx.Constraints.Constrain(maxSZ) maxSZ = gtx.Constraints.Constrain(maxSZ)
var baseline int var baseline int
for _, scratchChild := range scratch { for _, ch := range children {
sz := scratchChild.dims.Size sz := ch.dims.Size
var p image.Point var p image.Point
switch s.Alignment { switch s.Alignment {
case N, S, Center: case N, S, Center:
@@ -115,10 +105,10 @@ func (s Stack) Layout(gtx Context, children ...StackChild) Dimensions {
p.Y = maxSZ.Y - sz.Y p.Y = maxSZ.Y - sz.Y
} }
trans := op.Offset(p).Push(gtx.Ops) trans := op.Offset(p).Push(gtx.Ops)
scratchChild.call.Add(gtx.Ops) ch.call.Add(gtx.Ops)
trans.Pop() trans.Pop()
if baseline == 0 { if baseline == 0 {
if b := scratchChild.dims.Baseline; b != 0 { if b := ch.dims.Baseline; b != 0 {
baseline = b + maxSZ.Y - sz.Y - p.Y baseline = b + maxSZ.Y - sz.Y - p.Y
} }
} }
+4 -2
View File
@@ -17,8 +17,9 @@ func BenchmarkStack(b *testing.B) {
}, },
} }
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer()
for b.Loop() { for i := 0; i < b.N; i++ {
gtx.Ops.Reset() gtx.Ops.Reset()
Stack{}.Layout(gtx, Stack{}.Layout(gtx,
@@ -40,8 +41,9 @@ func BenchmarkBackground(b *testing.B) {
}, },
} }
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer()
for b.Loop() { for i := 0; i < b.N; i++ {
gtx.Ops.Reset() gtx.Ops.Reset()
Background{}.Layout(gtx, Background{}.Layout(gtx,

Some files were not shown because too many files have changed in this diff Show More