Compare commits

..

2 Commits

Author SHA1 Message Date
Elias Naur 8e47316332 app: [Windows] suppress double-click behaviour for custom decorations
Fixes: https://todo.sr.ht/~eliasnaur/gio/600
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-07-15 13:01:41 +08:00
Chris Waldon 55ae5c5b84 app: [Wayland] prevent recursive scroll event processing
This commit zeroes the accumulated scroll distance on the window before invoking the
event delivery code, since the event delivery code is able to call back into the scroll
processing. Prior to this change, the callback could re-processing the scroll delta
while magnifying it by a factor of 10.

Updates: https://todo.sr.ht/~eliasnaur/gio/599
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2024-07-08 10:12:04 -04:00
132 changed files with 3954 additions and 3772 deletions
+3 -3
View File
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: Unlicense OR MIT
image: debian/stable
image: debian/testing
packages:
- clang
- cmake
@@ -29,7 +29,7 @@ environment:
tasks:
- install_go: |
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.22.2.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
- prepare_toolchain: |
mkdir -p $APPLE_TOOLCHAIN_ROOT
cd $APPLE_TOOLCHAIN_ROOT
@@ -71,4 +71,4 @@ tasks:
CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-macos GOOS=darwin CGO_ENABLED=1 go build ./...
- test_ios: |
cd gio
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
image: freebsd/latest
image: freebsd/13.x
packages:
- libX11
- libxkbcommon
@@ -16,7 +16,7 @@ environment:
tasks:
- install_go: |
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.22.2.freebsd-amd64.tar.gz | tar -C /home/build/sdk -xzf -
- test_gio: |
cd gio
go test ./...
+3 -9
View File
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: Unlicense OR MIT
image: debian/stable
image: debian/testing
packages:
- curl
- pkg-config
@@ -18,12 +18,6 @@ packages:
- libxinerama-dev
- libxi-dev
- libxxf86vm-dev
- libegl-mesa0
- libglx-mesa0
- libgl1-mesa-dri
- mesa-libgallium
- libgbm1
- libegl1
- mesa-vulkan-drivers
- wine
- xvfb
@@ -46,7 +40,7 @@ secrets:
tasks:
- install_go: |
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.22.2.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
- check_gofmt: |
cd gio
test -z "$(gofmt -s -l .)"
@@ -66,7 +60,7 @@ tasks:
- add_32bit_arch: |
sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install -y "libwayland-dev:i386" "libx11-dev:i386" "libx11-xcb-dev:i386" "libxkbcommon-dev:i386" "libxkbcommon-x11-dev:i386" "libgles2-mesa-dev:i386" "libegl1-mesa-dev:i386" "libffi-dev:i386" "libvulkan-dev:i386" "libxcursor-dev:i386" "libegl-mesa0:i386" "libglx-mesa0:i386" "libgbm1:i386" "mesa-libgallium:i386" "libgl1-mesa-dri:i386"
sudo apt-get install -y "libwayland-dev:i386" "libx11-dev:i386" "libx11-xcb-dev:i386" "libxkbcommon-dev:i386" "libxkbcommon-x11-dev:i386" "libgles2-mesa-dev:i386" "libegl1-mesa-dev:i386" "libffi-dev:i386" "libvulkan-dev:i386" "libxcursor-dev:i386"
- test_gio: |
cd gio
go test -race ./...
+1 -1
View File
@@ -10,7 +10,7 @@ environment:
tasks:
- install_go: |
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.22.2.src.tar.gz | tar -C /home/build/sdk -xzf -
cd /home/build/sdk/go/src
./make.bash
- test_gio: |
-17
View File
@@ -4,7 +4,6 @@ package org.gioui;
import android.app.Activity;
import android.os.Bundle;
import android.content.Intent;
import android.content.res.Configuration;
import android.view.ViewGroup;
import android.view.View;
@@ -30,7 +29,6 @@ public final class GioActivity extends Activity {
layer.addView(view);
setContentView(layer);
onNewIntent(this.getIntent());
}
@Override public void onDestroy() {
@@ -48,16 +46,6 @@ public final class GioActivity extends Activity {
super.onStop();
}
@Override public void onPause() {
super.onPause();
view.pause();
}
@Override public void onResume() {
super.onResume();
view.resume();
}
@Override public void onConfigurationChanged(Configuration c) {
super.onConfigurationChanged(c);
view.configurationChanged();
@@ -72,9 +60,4 @@ public final class GioActivity extends Activity {
if (!view.backPressed())
super.onBackPressed();
}
@Override protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
view.onIntentEvent(intent);
}
}
+7 -27
View File
@@ -12,7 +12,6 @@ import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Context;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
@@ -62,6 +61,7 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal
private static boolean jniLoaded;
private final SurfaceHolder.Callback surfCallbacks;
private final View.OnFocusChangeListener focusCallback;
private final InputMethodManager imm;
private final float scrollXScale;
private final float scrollYScale;
@@ -113,6 +113,12 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal
nhandle = onCreateView(this);
setFocusable(true);
setFocusableInTouchMode(true);
focusCallback = new View.OnFocusChangeListener() {
@Override public void onFocusChange(View v, boolean focus) {
GioView.this.onFocusChange(nhandle, focus);
}
};
setOnFocusChangeListener(focusCallback);
surfCallbacks = new SurfaceHolder.Callback() {
@Override public void surfaceCreated(SurfaceHolder holder) {
// Ignore; surfaceChanged is guaranteed to be called immediately after this.
@@ -253,10 +259,6 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal
}
private void setHighRefreshRate() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
return;
}
Context context = getContext();
Display display = context.getDisplay();
Display.Mode[] supportedModes = display.getSupportedModes();
@@ -309,15 +311,6 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal
window.setAttributes(layoutParams);
}
protected void onIntentEvent(Intent intent) {
if (intent == null) {
return;
}
if (intent.getData() != null) {
this.onOpenURI(nhandle, intent.getData().toString());
}
}
@Override protected boolean dispatchHoverEvent(MotionEvent event) {
if (!accessManager.isTouchExplorationEnabled()) {
return super.dispatchHoverEvent(event);
@@ -475,18 +468,6 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal
}
}
public void pause() {
if (nhandle != 0) {
onFocusChange(nhandle, false);
}
}
public void resume() {
if (nhandle != 0) {
onFocusChange(nhandle, true);
}
}
public void destroy() {
if (nhandle != 0) {
onDestroyView(nhandle);
@@ -568,7 +549,6 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal
static private native void onExitTouchExploration(long handle);
static private native void onA11yFocus(long handle, int viewId);
static private native void onClearA11yFocus(long handle, int viewId);
static private native void onOpenURI(long handle, String uri);
static private native void imeSetSnippet(long handle, int start, int end);
static private native String imeSnippet(long handle);
static private native int imeSnippetStart(long handle);
+2 -52
View File
@@ -3,10 +3,7 @@
package app
import (
"gioui.org/io/event"
"golang.org/x/net/idna"
"image"
"net/url"
"os"
"path/filepath"
"strings"
@@ -59,15 +56,6 @@ type FrameEvent struct {
Source input.Source
}
// URLEvent is generated for external requests to open a URL. Unlike window specific events,
// it is delivered through the [Events] iterator.
//
// In order to receive URLEvents the program must register one or more URL schemes. A scheme can
// be registered using gogio, with the `-schemes` flag.
type URLEvent struct {
URL *url.URL
}
// ViewEvent provides handles to the underlying window objects for the
// current display protocol.
type ViewEvent interface {
@@ -130,7 +118,8 @@ func NewContext(ops *op.Ops, e FrameEvent) layout.Context {
// On iOS NSDocumentDirectory is queried.
// For Android Context.getFilesDir is used.
//
// BUG: On Android, DataDir panics if called before main.
// BUG: DataDir blocks on Android until init functions
// have completed.
func DataDir() (string, error) {
return dataDir()
}
@@ -147,29 +136,7 @@ func Main() {
osMain()
}
// Events is an iterator that yields events that are not specific to any window,
// such as [URLEvent]. It never returns.
//
// Events must be called by the main goroutine, and replaces the
// call to [Main].
func Events(yield func(event.Event) bool) {
yieldGlobalEvent = yield
osMain()
}
var yieldGlobalEvent func(evt event.Event) bool
func processGlobalEvent(evt event.Event) {
if yieldGlobalEvent == nil {
return
}
if !yieldGlobalEvent(evt) {
yieldGlobalEvent = nil
}
}
func (FrameEvent) ImplementsEvent() {}
func (URLEvent) ImplementsEvent() {}
func init() {
if extraArgs != "" {
@@ -180,20 +147,3 @@ func init() {
ID = filepath.Base(os.Args[0])
}
}
// newURLEvent creates a URLEvent from a raw URL string, handling Punycode decoding.
func newURLEvent(rawurl string) (URLEvent, error) {
u, err := url.Parse(rawurl)
if err != nil {
return URLEvent{}, err
}
u.Host, err = idna.Punycode.ToUnicode(u.Hostname())
if err != nil {
return URLEvent{}, err
}
u, err = url.Parse(u.String())
if err != nil {
return URLEvent{}, err
}
return URLEvent{URL: u}, nil
}
+1
View File
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build !android
// +build !android
package app
+1 -6
View File
@@ -48,7 +48,7 @@ For example, to display a blank but otherwise functional window:
func main() {
go func() {
w := new(app.Window)
w := app.NewWindow()
for {
w.Event()
}
@@ -56,11 +56,6 @@ For example, to display a blank but otherwise functional window:
app.Main()
}
# Events
The [Events] iterator yields app-specific events such as [URLEvent]. [Window.Event]
yields events that target a particular window.
# Permissions
The packages under gioui.org/app/permission should be imported
+22 -19
View File
@@ -38,24 +38,7 @@ func init() {
if err != nil {
return nil, err
}
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
return &wlContext{Context: ctx, win: w}, nil
}
}
@@ -71,11 +54,31 @@ func (c *wlContext) Release() {
}
func (c *wlContext) Refresh() error {
c.Context.ReleaseSurface()
if c.eglWin != nil {
C.wl_egl_window_destroy(c.eglWin)
c.eglWin = nil
}
surf, width, height := c.win.surface()
if surf == nil {
return errors.New("wayland: no surface")
}
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); 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
}
+4
View File
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build darwin && ios && nometal
// +build darwin,ios,nometal
package app
@@ -115,6 +116,9 @@ func (c *context) Unlock() {
}
func (c *context) Refresh() error {
if C.gio_makeCurrent(c.ctx) == 0 {
return errors.New("[EAGLContext setCurrentContext] failed")
}
if !c.init {
c.init = true
c.frameBuffer = c.c.CreateFramebuffer()
+4
View File
@@ -1,12 +1,14 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build darwin && !ios && nometal
// +build darwin,!ios,nometal
package app
import (
"errors"
"runtime"
"unsafe"
"gioui.org/gpu"
@@ -111,6 +113,8 @@ func (c *glContext) Unlock() {
}
func (c *glContext) Refresh() error {
c.Lock()
defer c.Unlock()
C.gio_updateContext(c.ctx)
return nil
}
-27
View File
@@ -1,11 +1,9 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
import (
"unicode"
"unicode/utf16"
"unicode/utf8"
"gioui.org/io/input"
"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.
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 -3
View File
@@ -1,9 +1,11 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build go1.18
// +build go1.18
package app
import (
"gioui.org/f32"
"testing"
"unicode/utf8"
@@ -38,7 +40,6 @@ func FuzzIME(f *testing.F) {
r.Frame(gtx.Ops)
var state editorState
state.Selection.Transform = f32.AffineId()
const (
cmdReplace = iota
cmdSelect
@@ -138,7 +139,6 @@ func FuzzIME(f *testing.F) {
func TestEditorIndices(t *testing.T) {
var s editorState
s.Selection.Transform = f32.AffineId()
const str = "Hello, 😀"
s.Snippet = key.Snippet{
Text: str,
+41 -178
View File
@@ -108,80 +108,6 @@ type MonitorInfo struct {
Flags uint32
}
type CopyDataStruct struct {
DwData uintptr
CbData uint32
LpData uintptr
}
type POINTER_INPUT_TYPE int32
const (
PT_POINTER POINTER_INPUT_TYPE = 1
PT_TOUCH POINTER_INPUT_TYPE = 2
PT_PEN POINTER_INPUT_TYPE = 3
PT_MOUSE POINTER_INPUT_TYPE = 4
PT_TOUCHPAD POINTER_INPUT_TYPE = 5
)
type POINTER_INFO_POINTER_FLAGS int32
const (
POINTER_FLAG_NEW POINTER_INFO_POINTER_FLAGS = 0x00000001
POINTER_FLAG_INRANGE POINTER_INFO_POINTER_FLAGS = 0x00000002
POINTER_FLAG_INCONTACT POINTER_INFO_POINTER_FLAGS = 0x00000004
POINTER_FLAG_FIRSTBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000010
POINTER_FLAG_SECONDBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000020
POINTER_FLAG_THIRDBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000040
POINTER_FLAG_FOURTHBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000080
POINTER_FLAG_FIFTHBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000100
POINTER_FLAG_PRIMARY POINTER_INFO_POINTER_FLAGS = 0x00002000
POINTER_FLAG_CONFIDENCE POINTER_INFO_POINTER_FLAGS = 0x00004000
POINTER_FLAG_CANCELED POINTER_INFO_POINTER_FLAGS = 0x00008000
POINTER_FLAG_DOWN POINTER_INFO_POINTER_FLAGS = 0x00010000
POINTER_FLAG_UPDATE POINTER_INFO_POINTER_FLAGS = 0x00020000
POINTER_FLAG_UP POINTER_INFO_POINTER_FLAGS = 0x00040000
POINTER_FLAG_WHEEL POINTER_INFO_POINTER_FLAGS = 0x00080000
POINTER_FLAG_HWHEEL POINTER_INFO_POINTER_FLAGS = 0x00100000
POINTER_FLAG_CAPTURECHANGED POINTER_INFO_POINTER_FLAGS = 0x00200000
POINTER_FLAG_HASTRANSFORM POINTER_INFO_POINTER_FLAGS = 0x00400000
)
type POINTER_BUTTON_CHANGE_TYPE int32
const (
POINTER_CHANGE_NONE POINTER_BUTTON_CHANGE_TYPE = 0
POINTER_CHANGE_FIRSTBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 1
POINTER_CHANGE_FIRSTBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 2
POINTER_CHANGE_SECONDBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 3
POINTER_CHANGE_SECONDBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 4
POINTER_CHANGE_THIRDBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 5
POINTER_CHANGE_THIRDBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 6
POINTER_CHANGE_FOURTHBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 7
POINTER_CHANGE_FOURTHBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 8
POINTER_CHANGE_FIFTHBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 9
POINTER_CHANGE_FIFTHBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 10
)
type PointerInfo struct {
PointerType POINTER_INPUT_TYPE
PointerId uint32
FrameId uint32
PointerFlags POINTER_INFO_POINTER_FLAGS
SourceDevice syscall.Handle
HwndTarget syscall.Handle
PtPixelLocation Point
PtHimetricLocation Point
PtPixelLocationRaw Point
PtHimetricLocationRaw Point
DwTime uint32
HistoryCount uint32
InputData int32
DwKeyStates uint32
PerformanceCount uint64
ButtonChangeType POINTER_BUTTON_CHANGE_TYPE
}
const (
TRUE = 1
@@ -318,52 +244,45 @@ const (
UNICODE_NOCHAR = 65535
WM_CANCELMODE = 0x001F
WM_CHAR = 0x0102
WM_CLOSE = 0x0010
WM_COPYDATA = 0x004A
WM_CREATE = 0x0001
WM_DPICHANGED = 0x02E0
WM_DESTROY = 0x0002
WM_ERASEBKGND = 0x0014
WM_GETMINMAXINFO = 0x0024
WM_IME_COMPOSITION = 0x010F
WM_IME_ENDCOMPOSITION = 0x010E
WM_IME_STARTCOMPOSITION = 0x010D
WM_KEYDOWN = 0x0100
WM_KEYUP = 0x0101
WM_KILLFOCUS = 0x0008
WM_LBUTTONDOWN = 0x0201
WM_LBUTTONUP = 0x0202
WM_MBUTTONDOWN = 0x0207
WM_MBUTTONUP = 0x0208
WM_MOUSEMOVE = 0x0200
WM_MOUSEWHEEL = 0x020A
WM_MOUSEHWHEEL = 0x020E
WM_NCACTIVATE = 0x0086
WM_NCHITTEST = 0x0084
WM_NCCALCSIZE = 0x0083
WM_PAINT = 0x000F
WM_POINTERCAPTURECHANGED = 0x024C
WM_POINTERDOWN = 0x0246
WM_POINTERUP = 0x0247
WM_POINTERUPDATE = 0x0245
WM_POINTERWHEEL = 0x024E
WM_POINTERHWHEEL = 0x024F
WM_QUIT = 0x0012
WM_RBUTTONDOWN = 0x0204
WM_RBUTTONUP = 0x0205
WM_SETCURSOR = 0x0020
WM_SETFOCUS = 0x0007
WM_SHOWWINDOW = 0x0018
WM_SIZE = 0x0005
WM_STYLECHANGED = 0x007D
WM_SYSKEYDOWN = 0x0104
WM_SYSKEYUP = 0x0105
WM_TIMER = 0x0113
WM_UNICHAR = 0x0109
WM_USER = 0x0400
WM_WINDOWPOSCHANGED = 0x0047
WM_CANCELMODE = 0x001F
WM_CHAR = 0x0102
WM_CLOSE = 0x0010
WM_CREATE = 0x0001
WM_DPICHANGED = 0x02E0
WM_DESTROY = 0x0002
WM_ERASEBKGND = 0x0014
WM_GETMINMAXINFO = 0x0024
WM_IME_COMPOSITION = 0x010F
WM_IME_ENDCOMPOSITION = 0x010E
WM_IME_STARTCOMPOSITION = 0x010D
WM_KEYDOWN = 0x0100
WM_KEYUP = 0x0101
WM_KILLFOCUS = 0x0008
WM_LBUTTONDOWN = 0x0201
WM_LBUTTONUP = 0x0202
WM_MBUTTONDOWN = 0x0207
WM_MBUTTONUP = 0x0208
WM_MOUSEMOVE = 0x0200
WM_MOUSEWHEEL = 0x020A
WM_MOUSEHWHEEL = 0x020E
WM_NCACTIVATE = 0x0086
WM_NCLBUTTONDBLCLK = 0x00A3
WM_NCHITTEST = 0x0084
WM_NCCALCSIZE = 0x0083
WM_PAINT = 0x000F
WM_QUIT = 0x0012
WM_SETCURSOR = 0x0020
WM_SETFOCUS = 0x0007
WM_SHOWWINDOW = 0x0018
WM_SIZE = 0x0005
WM_SYSKEYDOWN = 0x0104
WM_SYSKEYUP = 0x0105
WM_RBUTTONDOWN = 0x0204
WM_RBUTTONUP = 0x0205
WM_TIMER = 0x0113
WM_UNICHAR = 0x0109
WM_USER = 0x0400
WM_WINDOWPOSCHANGED = 0x0047
WS_CLIPCHILDREN = 0x02000000
WS_CLIPSIBLINGS = 0x04000000
@@ -426,9 +345,7 @@ var (
_DefWindowProc = user32.NewProc("DefWindowProcW")
_DestroyWindow = user32.NewProc("DestroyWindow")
_DispatchMessage = user32.NewProc("DispatchMessageW")
_FindWindow = user32.NewProc("FindWindowW")
_EmptyClipboard = user32.NewProc("EmptyClipboard")
_EnableMouseInPointer = user32.NewProc("EnableMouseInPointer")
_GetWindowRect = user32.NewProc("GetWindowRect")
_GetClientRect = user32.NewProc("GetClientRect")
_GetClipboardData = user32.NewProc("GetClipboardData")
@@ -438,7 +355,6 @@ var (
_GetMessage = user32.NewProc("GetMessageW")
_GetMessageTime = user32.NewProc("GetMessageTime")
_GetMonitorInfo = user32.NewProc("GetMonitorInfoW")
_GetPointerInfo = user32.NewProc("GetPointerInfo")
_GetSystemMetrics = user32.NewProc("GetSystemMetrics")
_GetWindowLong = user32.NewProc("GetWindowLongPtrW")
_GetWindowLong32 = user32.NewProc("GetWindowLongW")
@@ -456,11 +372,9 @@ var (
_PostQuitMessage = user32.NewProc("PostQuitMessage")
_ReleaseCapture = user32.NewProc("ReleaseCapture")
_RegisterClassExW = user32.NewProc("RegisterClassExW")
_RegisterTouchWindow = user32.NewProc("RegisterTouchWindow")
_ReleaseDC = user32.NewProc("ReleaseDC")
_ScreenToClient = user32.NewProc("ScreenToClient")
_ShowWindow = user32.NewProc("ShowWindow")
_SendMessage = user32.NewProc("SendMessageW")
_SetCapture = user32.NewProc("SetCapture")
_SetCursor = user32.NewProc("SetCursor")
_SetClipboardData = user32.NewProc("SetClipboardData")
@@ -513,10 +427,7 @@ func CloseClipboard() error {
}
func CreateWindowEx(dwExStyle uint32, lpClassName uint16, lpWindowName string, dwStyle uint32, x, y, w, h int32, hWndParent, hMenu, hInstance syscall.Handle, lpParam uintptr) (syscall.Handle, error) {
wname, err := syscall.UTF16PtrFromString(lpWindowName)
if err != nil {
return 0, fmt.Errorf("CreateWindowEx failed: %v", err)
}
wname := syscall.StringToUTF16Ptr(lpWindowName)
hwnd, _, err := _CreateWindowEx.Call(
uintptr(dwExStyle),
uintptr(lpClassName),
@@ -534,31 +445,6 @@ func CreateWindowEx(dwExStyle uint32, lpClassName uint16, lpWindowName string, d
return syscall.Handle(hwnd), nil
}
func GetPointerInfo(pointerId uint32) (PointerInfo, error) {
var info PointerInfo
r1, _, err := _GetPointerInfo.Call(uintptr(pointerId), uintptr(unsafe.Pointer(&info)))
if r1 == 0 {
return PointerInfo{}, fmt.Errorf("GetPointerInfo failed: %v", err)
}
return info, nil
}
func RegisterTouchWindow(hwnd syscall.Handle, flags uint32) error {
r1, _, err := _RegisterTouchWindow.Call(uintptr(hwnd), uintptr(flags))
if r1 == 0 {
return fmt.Errorf("RegisterTouchWindow failed: %v", err)
}
return nil
}
func EnableMouseInPointer(enable uint) error {
r1, _, err := _EnableMouseInPointer.Call(uintptr(enable))
if r1 == 0 {
return fmt.Errorf("EnableMouseInPointer failed: %v", err)
}
return nil
}
func DefWindowProc(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
r, _, _ := _DefWindowProc.Call(uintptr(hwnd), uintptr(msg), wparam, lparam)
return r
@@ -588,18 +474,6 @@ func EmptyClipboard() error {
return nil
}
func FindWindow(lpClassName string) (syscall.Handle, error) {
className, err := syscall.UTF16PtrFromString(lpClassName)
if err != nil {
return 0, fmt.Errorf("FindWindow failed: %v", err)
}
hwnd, _, err := _FindWindow.Call(uintptr(unsafe.Pointer(className)), 0)
if hwnd == 0 {
return 0, fmt.Errorf("FindWindow failed: %v", err)
}
return syscall.Handle(hwnd), nil
}
func GetWindowRect(hwnd syscall.Handle) Rect {
var r Rect
_GetWindowRect.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&r)))
@@ -791,10 +665,7 @@ func SetWindowPos(hwnd syscall.Handle, hwndInsertAfter uint32, x, y, dx, dy int3
}
func SetWindowText(hwnd syscall.Handle, title string) {
wname, err := syscall.UTF16PtrFromString(title)
if err != nil {
panic(err)
}
wname := syscall.StringToUTF16Ptr(title)
_SetWindowText.Call(uintptr(hwnd), uintptr(unsafe.Pointer(wname)))
}
@@ -910,14 +781,6 @@ func ReleaseDC(hdc syscall.Handle) {
_ReleaseDC.Call(uintptr(hdc))
}
func SendMessage(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) error {
r, _, err := _SendMessage.Call(uintptr(hwnd), uintptr(msg), wParam, lParam)
if r == 0 {
return fmt.Errorf("SendMessage failed: %v", err)
}
return nil
}
func SetForegroundWindow(hwnd syscall.Handle) {
_SetForegroundWindow.Call(uintptr(hwnd))
}
+1
View File
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build darwin && ios
// +build darwin,ios
package app
+2 -3
View File
@@ -4,15 +4,14 @@ package app
import (
"log"
"syscall"
"unsafe"
syscall "golang.org/x/sys/windows"
)
type logger struct{}
var (
kernel32 = syscall.NewLazySystemDLL("kernel32")
kernel32 = syscall.NewLazyDLL("kernel32")
outputDebugStringW = kernel32.NewProc("OutputDebugStringW")
debugView *logger
)
+1 -1
View File
@@ -96,7 +96,7 @@ func newMtlContext(w *window) (*mtlContext, error) {
return nil, errors.New("metal: CAMetalLayer construction failed")
}
queue := C.newCommandQueue(dev)
if queue == 0 {
if layer == 0 {
C.CFRelease(dev)
C.CFRelease(layer)
return nil, errors.New("metal: [MTLDevice newCommandQueue] failed")
+1
View File
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build !nometal
// +build !nometal
package app
+3 -5
View File
@@ -45,9 +45,7 @@ type Config struct {
CustomRenderer bool
// Decorated reports whether window decorations are provided automatically.
Decorated bool
// TopMost windows render above all other non-top-most windows.
TopMost bool
// Focused reports whether the window is focused.
// Focused reports whether has the keyboard focus.
Focused bool
// decoHeight is the height of the fallback decoration for platforms such
// as Wayland that may need fallback client-side decorations.
@@ -165,8 +163,6 @@ type frameEvent struct {
Sync bool
}
// The caller must hold the context lock while using API, Refresh,
// RenderTarget, or Present.
type context interface {
API() gpu.API
RenderTarget() (gpu.RenderTarget, error)
@@ -199,6 +195,8 @@ type driver interface {
Configure([]Option)
// SetCursor updates the current cursor to name.
SetCursor(cursor pointer.Cursor)
// Wakeup wakes up the event loop and sends a WakeupEvent.
// Wakeup()
// Perform actions on the window.
Perform(system.Action)
// EditorStateChanged notifies the driver that the editor state changed.
+13 -21
View File
@@ -136,8 +136,6 @@ import (
"unicode/utf16"
"unsafe"
"gioui.org/io/transfer"
"gioui.org/internal/f32color"
"gioui.org/op"
@@ -148,6 +146,7 @@ import (
"gioui.org/io/pointer"
"gioui.org/io/semantic"
"gioui.org/io/system"
"gioui.org/io/transfer"
"gioui.org/unit"
)
@@ -218,6 +217,8 @@ type AndroidViewEvent struct {
type jvalue uint64 // The largest JNI type fits in 64 bits.
var dataDirChan = make(chan string, 1)
var android struct {
// mu protects all fields of this structure. However, once a
// non-nil jvm is returned from javaVM, all the other fields may
@@ -293,7 +294,8 @@ var mainWindow = newWindowRendezvous()
var mainFuncs = make(chan func(env *C.JNIEnv), 1)
var (
dataPath string
dataDirOnce sync.Once
dataPath string
)
var (
@@ -340,9 +342,9 @@ func (w *window) NewContext() (context, error) {
}
func dataDir() (string, error) {
if dataPath == "" {
panic("DataDir isn't valid before main")
}
dataDirOnce.Do(func() {
dataPath = <-dataDirChan
})
return dataPath, nil
}
@@ -395,7 +397,7 @@ func Java_org_gioui_Gio_runGoMain(env *C.JNIEnv, class C.jclass, jdataDir C.jbyt
os.Setenv("HOME", dataDir)
}
dataPath = dataDir
dataDirChan <- dataDir
C.jni_ReleaseByteArrayElements(env, jdataDir, dirBytes)
runMain()
@@ -573,7 +575,10 @@ func Java_org_gioui_GioView_onFrameCallback(env *C.JNIEnv, class C.jclass, view
if !exist {
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
@@ -662,15 +667,6 @@ func Java_org_gioui_GioView_onClearA11yFocus(env *C.JNIEnv, class C.jclass, view
}
}
//export Java_org_gioui_GioView_onOpenURI
func Java_org_gioui_GioView_onOpenURI(env *C.JNIEnv, class C.jclass, view C.jlong, uri C.jstring) {
evt, err := newURLEvent(goString(env, uri))
if err != nil {
return
}
processGlobalEvent(evt)
}
func (w *window) ProcessEvent(e event.Event) {
w.processEvent(e)
}
@@ -886,9 +882,6 @@ func (w *window) draw(env *C.JNIEnv, sync bool) {
},
Sync: sync,
})
if w.animating {
callVoidMethod(env, w.view, gioView.postFrameCallback)
}
a11yActive, err := callBooleanMethod(env, w.view, gioView.isA11yActive)
if err != nil {
panic(err)
@@ -1325,7 +1318,6 @@ func findClass(env *C.JNIEnv, name string) C.jclass {
}
func osMain() {
select {}
}
func newWindow(window *callbacks, options []Option) {
+17 -10
View File
@@ -5,7 +5,7 @@ package app
/*
#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"))) void gio_releaseDisplayLink(CFTypeRef dl);
__attribute__ ((visibility ("hidden"))) int gio_startDisplayLink(CFTypeRef dl);
@@ -40,10 +40,8 @@ static CFTypeRef newNSString(unichar *chars, NSUInteger length) {
}
*/
import "C"
import (
"errors"
"runtime/cgo"
"sync"
"sync/atomic"
"time"
@@ -75,6 +73,8 @@ type displayLink struct {
// displayLinks maps CFTypeRefs to *displayLinks.
var displayLinks sync.Map
var mainFuncs = make(chan func(), 1)
func isMainThread() bool {
return bool(C.isMainThread())
}
@@ -85,15 +85,22 @@ func runOnMain(f func()) {
f()
return
}
C.gio_runOnMain(C.uintptr_t(cgo.NewHandle(f)))
go func() {
mainFuncs <- f
C.gio_wakeupMainThread()
}()
}
//export gio_runFunc
func gio_runFunc(h C.uintptr_t) {
handle := cgo.Handle(h)
defer handle.Delete()
f := handle.Value().(func())
f()
//export gio_dispatchMainFuncs
func gio_dispatchMainFuncs() {
for {
select {
case f := <-mainFuncs:
f()
default:
return
}
}
}
// nsstringToString converts a NSString to a Go string.
+2 -2
View File
@@ -4,8 +4,8 @@
#include "_cgo_export.h"
void gio_runOnMain(uintptr_t h) {
void gio_wakeupMainThread(void) {
dispatch_async(dispatch_get_main_queue(), ^{
gio_runFunc(h);
gio_dispatchMainFuncs();
});
}
+4 -14
View File
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build darwin && ios
// +build darwin,ios
package app
@@ -404,12 +405,11 @@ const (
)
func osMain() {
if !isMainThread() {
panic("app.Main must be run on the main goroutine")
}
switch mainMode {
case mainModeUndefined:
if !isMainThread() {
panic("app.Main must be run on the main goroutine")
}
mainMode = mainModeExe
var argv []*C.char
for _, arg := range os.Args {
@@ -423,16 +423,6 @@ func osMain() {
case mainModeLibrary:
// Do nothing, we're embedded as a library.
}
select {}
}
//export gio_onOpenURI
func gio_onOpenURI(uri C.CFTypeRef) {
evt, err := newURLEvent(nsstringToString(uri))
if err != nil {
return
}
processGlobalEvent(evt)
}
//export gio_runMain
+11 -51
View File
@@ -134,59 +134,23 @@ NSArray<UIKeyCommand *> *_keyCommands;
return gio_layerClass();
}
- (void)willMoveToWindow:(UIWindow *)newWindow {
self.contentScaleFactor = newWindow.screen.nativeScale;
if (@available(iOS 13.0, *)) {
[self registerSceneNotifications:newWindow];
}else{
[self registerWindowNotifications:newWindow];
}
}
- (void)registerSceneNotifications:(UIWindow *)newWindow {
if (self.window != nil) {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UISceneDidActivateNotification
object:self.window.windowScene];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UISceneWillDeactivateNotification
object:self.window.windowScene];
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onSceneDidActivate:)
name:UISceneDidActivateNotification
object:newWindow.windowScene];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onSceneWillDeactivate:)
name:UISceneWillDeactivateNotification
object:newWindow.windowScene];
}
- (void)onSceneDidActivate:(NSNotification *)note API_AVAILABLE(ios(13.0)){
onFocus(self.handle, YES);
}
- (void)onSceneWillDeactivate:(NSNotification *)note API_AVAILABLE(ios(13.0)){
onFocus(self.handle, NO);
}
- (void)registerWindowNotifications:(UIWindow *)newWindow {
if (self.window != nil) {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIWindowDidBecomeKeyNotification
object:self.window];
name:UIWindowDidBecomeKeyNotification
object:self.window];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIWindowDidResignKeyNotification
object:self.window];
name:UIWindowDidResignKeyNotification
object:self.window];
}
self.contentScaleFactor = newWindow.screen.nativeScale;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onWindowDidBecomeKey:)
name:UIWindowDidBecomeKeyNotification
object:newWindow];
selector:@selector(onWindowDidBecomeKey:)
name:UIWindowDidBecomeKeyNotification
object:newWindow];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onWindowDidResignKey:)
name:UIWindowDidResignKeyNotification
object:newWindow];
selector:@selector(onWindowDidResignKey:)
name:UIWindowDidResignKeyNotification
object:newWindow];
}
- (void)onWindowDidBecomeKey:(NSNotification *)note {
@@ -329,10 +293,6 @@ void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
[self.window makeKeyAndVisible];
return YES;
}
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
gio_onOpenURI((__bridge CFTypeRef)url.absoluteString);
return YES;
}
@end
int gio_applicationMain(int argc, char *argv[]) {
+18 -352
View File
@@ -53,8 +53,7 @@ type window struct {
screenOrientation js.Value
cleanfuncs []func()
touches []js.Value
composing int
lastCursor int
composing bool
requestFocus bool
config Config
@@ -85,7 +84,6 @@ func newWindow(win *callbacks, options []Option) {
clipboard: js.Global().Get("navigator").Get("clipboard"),
wakeups: make(chan struct{}, 1),
w: win,
composing: -1,
}
w.w.SetDriver(w)
w.requestAnimationFrame = w.window.Get("requestAnimationFrame")
@@ -132,10 +130,8 @@ func getContainer(doc js.Value) js.Value {
}
func createTextArea(doc js.Value) js.Value {
tarea := doc.Call("createElement", "textarea")
tarea := doc.Call("createElement", "input")
style := tarea.Get("style")
// Position absolute so left/top coordinates actually place the element
style.Set("position", "absolute")
style.Set("width", "1px")
style.Set("height", "1px")
style.Set("opacity", "0")
@@ -145,12 +141,6 @@ func createTextArea(doc js.Value) js.Value {
tarea.Set("autocorrect", "off")
tarea.Set("autocapitalize", "off")
tarea.Set("spellcheck", false)
// Enable multiline text input for better composition support on some browsers.
tarea.Set("rows", 1)
style.Set("resize", "none")
style.Set("overflow", "hidden")
style.Set("white-space", "pre-wrap")
style.Set("word-break", "normal")
return tarea
}
@@ -273,15 +263,7 @@ func (w *window) addEventListeners() {
return nil
})
w.addEventListener(w.tarea, "blur", func(this js.Value, args []js.Value) interface{} {
if w.composing != -1 {
// If we're composing, try to cancel.
// On Javascript is not possible to cancel the composition once started.
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
w.composing = -1
}
w.config.Focused = false
w.lastCursor = 0 // Reset cursor tracking on blur
w.processEvent(ConfigEvent{Config: w.config})
w.blur()
return nil
@@ -295,205 +277,19 @@ func (w *window) addEventListeners() {
return nil
})
w.addEventListener(w.tarea, "compositionstart", func(this js.Value, args []js.Value) interface{} {
st := w.w.EditorState()
sel := st.Selection.Range
if sel.Start == -1 {
sel.Start = 0
sel.End = 0
}
w.w.SetEditorSnippet(key.Range{Start: sel.Start, End: sel.End})
w.composing = sel.Start
w.composing = true
return nil
})
w.addEventListener(w.tarea, "compositionend", func(this js.Value, args []js.Value) interface{} {
finalText := w.tarea.Get("value").String()
if w.composing != -1 && finalText != "" {
// Replace the entire composition range with the final text.
compEnd := w.composing + utf8.RuneCountInString(finalText)
replaceRange := key.Range{Start: w.composing, End: compEnd}
w.w.EditorReplace(replaceRange, finalText)
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
// Position cursor after the final composition text.
newEnd := w.composing + utf8.RuneCountInString(finalText)
w.w.SetEditorSelection(key.Range{Start: newEnd, End: newEnd})
}
w.composing = -1
w.tarea.Set("value", "")
w.composing = false
w.flushInput()
return nil
})
w.addEventListener(w.tarea, "input", func(this js.Value, args []js.Value) interface{} {
e := args[0]
inputType := e.Get("inputType").String()
dataVal := e.Get("data")
var data string
if dataVal.Truthy() {
data = dataVal.String()
if w.composing {
return nil
}
// Get the current textarea value.
tareaValue := w.tarea.Get("value").String()
st := w.w.EditorState()
sel := st.Selection.Range
var absStart, absEnd int
snippetStart := st.Snippet.Range.Start
snippetEnd := st.Snippet.Range.End
cursorPos := sel.Start
selectionEnd := sel.End
if cursorPos < 0 {
cursorPos = 0
selectionEnd = 0
}
// Check if we need to expand the snippet to include the range.
if st.Snippet.Range.Start == 0 && st.Snippet.Range.End == 0 && tareaValue != "" {
// Empty snippet - set it to include the selection/cursor.
w.w.SetEditorSnippet(key.Range{Start: cursorPos, End: selectionEnd})
absStart = cursorPos
absEnd = selectionEnd
} else if cursorPos < snippetStart || selectionEnd > snippetEnd {
// Selection is outside the snippet
newStart := snippetStart
newEnd := snippetEnd
if cursorPos < newStart {
newStart = cursorPos
}
if selectionEnd > newEnd {
newEnd = selectionEnd
}
w.w.SetEditorSnippet(key.Range{Start: newStart, End: newEnd})
// Refresh state after snippet update.
st = w.w.EditorState()
// Use the selection range directly.
absStart = cursorPos
absEnd = selectionEnd
} else {
// Selection is within snippet to absolute positions.
absStart = cursorPos
absEnd = selectionEnd
}
switch inputType {
case "insertCompositionText":
if w.composing == -1 {
break
}
compEnd := absEnd
if compEnd < w.composing {
compEnd = w.composing
}
replaceRange := key.Range{Start: w.composing, End: compEnd}
w.w.EditorReplace(replaceRange, data)
newEnd := w.composing + utf8.RuneCountInString(data)
w.w.SetComposingRegion(key.Range{Start: w.composing, End: newEnd})
w.w.SetEditorSelection(key.Range{Start: newEnd, End: newEnd})
case "deleteContentBackward", "deleteContentForward", "deleteByCut":
if w.composing != -1 {
compEnd := w.composing + utf8.RuneCountInString(tareaValue)
replaceRange := key.Range{Start: w.composing, End: compEnd}
w.w.EditorReplace(replaceRange, tareaValue)
newEnd := w.composing + utf8.RuneCountInString(tareaValue)
w.w.SetComposingRegion(key.Range{Start: w.composing, End: newEnd})
w.w.SetEditorSelection(key.Range{Start: newEnd, End: newEnd})
} else {
replaceRange := key.Range{Start: absStart, End: absEnd}
w.w.EditorReplace(replaceRange, "")
w.w.SetEditorSelection(key.Range{Start: absStart, End: absStart})
}
case "insertReplacementText":
if w.composing != -1 {
// During composition, replace the entire composition.
compEnd := w.composing + utf8.RuneCountInString(data)
replaceRange := key.Range{Start: w.composing, End: compEnd}
w.w.EditorReplace(replaceRange, data)
newEnd := w.composing + utf8.RuneCountInString(data)
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
w.w.SetEditorSelection(key.Range{Start: newEnd, End: newEnd})
w.composing = -1
w.lastCursor = newEnd
} else {
// Safari sends "insertReplacementText" for autocorrect, but the cursor is at the end of the word, so we need to find the word start.
insertLen := utf8.RuneCountInString(data)
wordStart := absStart
if absStart > snippetStart {
relPos := absStart - snippetStart
snippetRunes := []rune(st.Snippet.Text)
for i := relPos - 1; i >= 0; i-- {
if i >= len(snippetRunes) {
continue
}
r := snippetRunes[i]
if r == ' ' || r == '\t' || r == '\n' || r == '\r' {
break
}
wordStart = snippetStart + i
}
}
replaceRange := key.Range{Start: wordStart, End: absStart}
w.w.EditorReplace(replaceRange, data)
newCursor := wordStart + insertLen
w.w.SetEditorSelection(key.Range{Start: newCursor, End: newCursor})
w.lastCursor = newCursor
}
case "insertText":
if w.composing != -1 {
compEnd := w.composing + utf8.RuneCountInString(data)
replaceRange := key.Range{Start: w.composing, End: compEnd}
w.w.EditorReplace(replaceRange, data)
newEnd := w.composing + utf8.RuneCountInString(data)
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
w.w.SetEditorSelection(key.Range{Start: newEnd, End: newEnd})
w.composing = -1
w.lastCursor = newEnd
} else {
insertLen := utf8.RuneCountInString(data)
replaceRange := key.Range{Start: absStart, End: absStart}
if absStart != absEnd {
replaceRange = key.Range{Start: absStart, End: absEnd}
}
newCursor := replaceRange.Start + insertLen
w.w.EditorReplace(replaceRange, data)
w.w.SetEditorSelection(key.Range{Start: newCursor, End: newCursor})
w.lastCursor = newCursor
}
default: // paste and other input types
if w.composing != -1 {
compEnd := w.composing + utf8.RuneCountInString(tareaValue)
replaceRange := key.Range{Start: w.composing, End: compEnd}
w.w.EditorReplace(replaceRange, tareaValue)
newEnd := w.composing + utf8.RuneCountInString(tareaValue)
w.w.SetComposingRegion(key.Range{Start: w.composing, End: newEnd})
w.w.SetEditorSelection(key.Range{Start: newEnd, End: newEnd})
} else {
replaceRange := key.Range{Start: absStart, End: absEnd}
w.w.EditorReplace(replaceRange, tareaValue)
newCursor := absStart + utf8.RuneCountInString(tareaValue)
w.w.SetEditorSelection(key.Range{Start: newCursor, End: newCursor})
}
}
w.flushInput()
return nil
})
w.addEventListener(w.tarea, "paste", func(this js.Value, args []js.Value) interface{} {
@@ -510,6 +306,12 @@ func (w *window) addHistory() {
w.browserHistory.Call("pushState", nil, nil, w.window.Get("location").Get("href"))
}
func (w *window) flushInput() {
val := w.tarea.Get("value").String()
w.tarea.Set("value", "")
w.w.EditorInsert(string(val))
}
func (w *window) blur() {
w.tarea.Call("blur")
w.requestFocus = false
@@ -541,49 +343,11 @@ func (w *window) keyboard(hint key.InputHint) {
m = "text"
}
w.tarea.Set("inputMode", m)
// Update autocomplete / autocorrect attributes.
var autocomplete, autocorrect, autocapitalize string
var spellcheck bool
switch hint {
case key.HintAny, key.HintText:
autocomplete, autocorrect, autocapitalize, spellcheck = "on", "on", "on", true
case key.HintEmail:
autocomplete, autocorrect, autocapitalize, spellcheck = "email", "off", "off", false
case key.HintURL:
autocomplete, autocorrect, autocapitalize, spellcheck = "url", "off", "off", false
case key.HintTelephone:
autocomplete, autocorrect, autocapitalize, spellcheck = "tel", "off", "off", false
case key.HintPassword:
autocomplete, autocorrect, autocapitalize, spellcheck = "current-password", "off", "off", false
default: // key.HintNumeric and others
autocomplete, autocorrect, autocapitalize, spellcheck = "off", "off", "off", false
}
w.tarea.Set("autocomplete", autocomplete)
w.tarea.Set("autocorrect", autocorrect)
w.tarea.Set("autocapitalize", autocapitalize)
w.tarea.Set("spellcheck", spellcheck)
}
func (w *window) keyEvent(e js.Value, ks key.State) {
k := e.Get("key").String()
if n, ok := translateKey(k); ok {
if ks == key.Press {
isMod := n == key.NameAlt || n == key.NameCommand || n == key.NameCtrl || n == key.NameShift || n == key.NameSuper
isFunc := n == key.NameUpArrow || n == key.NameDownArrow || n == key.NameLeftArrow || n == key.NameRightArrow ||
n == key.NamePageUp || n == key.NamePageDown || n == key.NameHome || n == key.NameEnd ||
n == key.NameEscape || n == key.NameReturn || n == key.NameEnter || n == key.NameTab ||
n == key.NameDeleteBackward || n == key.NameDeleteForward
if isMod || isFunc {
// Gio will request the browser to change the selection/carret position natively.
e.Call("preventDefault")
}
}
cmd := key.Event{
Name: n,
Modifiers: modifiersFor(e),
@@ -650,12 +414,6 @@ func modifiersFor(e js.Value) key.Modifiers {
if e.Call("getModifierState", "Shift").Bool() {
mods |= key.ModShift
}
if e.Call("getModifierState", "Meta").Bool() {
mods |= key.ModCommand
}
if e.Call("getModifierState", "OS").Bool() {
mods |= key.ModSuper
}
return mods
}
@@ -676,9 +434,6 @@ func (w *window) touchEvent(kind pointer.Kind, e js.Value) {
if e.Get("ctrlKey").Bool() {
mods |= key.ModCtrl
}
if e.Get("metaKey").Bool() {
mods |= key.ModCommand
}
for i := 0; i < n; i++ {
touch := changedTouches.Index(i)
pid := w.touchIDFor(touch)
@@ -766,90 +521,7 @@ func (w *window) funcOf(f func(this js.Value, args []js.Value) interface{}) js.F
return jsf
}
func (w *window) EditorStateChanged(old, new editorState) {
if w.composing != -1 {
// Do not interfere with browser state while composing.
// On Javascript is not possible to cancel the composition once started!
return
}
// Update textarea value to match the snippet.
if old.Snippet != new.Snippet {
w.tarea.Set("value", new.Snippet.Text)
}
// Update selection to match Gio's selection.
if old.Selection.Range != new.Selection.Range || old.Snippet != new.Snippet {
if new.Selection.Range.Start != -1 && new.Selection.Range.End != -1 {
// Calculate selection positions relative to snippet start.
// The textarea contains only the snippet text.
snippetStart := new.Snippet.Range.Start
snippetEnd := new.Snippet.Range.End
selStart := new.Selection.Range.Start
selEnd := new.Selection.Range.End
if selStart < snippetStart {
selStart = snippetStart
}
if selStart > snippetEnd {
selStart = snippetEnd
}
if selEnd < snippetStart {
selEnd = snippetStart
}
if selEnd > snippetEnd {
selEnd = snippetEnd
}
// Convert absolute rune positions to UTF-16 positions for the textarea.
startUTF16 := new.UTF16Index(selStart)
endUTF16 := new.UTF16Index(selEnd)
// Convert to snippet-relative UTF-16 positions.
snippetStartUTF16 := new.UTF16Index(snippetStart)
start := startUTF16 - snippetStartUTF16
end := endUTF16 - snippetStartUTF16
if start < 0 {
start = 0
}
if end < 0 {
end = 0
}
// Calculate max UTF-16 length of snippet text.
textLen := new.UTF16Index(snippetEnd) - snippetStartUTF16
if start > textLen {
start = textLen
}
if end > textLen {
end = textLen
}
if start > end {
start, end = end, start
}
w.tarea.Set("selectionStart", start)
w.tarea.Set("selectionEnd", end)
}
}
// Move DOM element to position the caret.
if old.Selection.Caret != new.Selection.Caret || old.Selection.Transform != new.Selection.Transform {
pos := new.Selection.Transform.Transform(new.Selection.Caret.Pos.Add(f32.Pt(0, new.Selection.Caret.Descent)))
bounds := w.cnv.Call("getBoundingClientRect")
left := bounds.Get("left").Float() + float64(pos.X)/float64(w.scale)
top := bounds.Get("top").Float() + float64(pos.Y-new.Selection.Caret.Ascent)/float64(w.scale)
height := float64(new.Selection.Caret.Ascent+new.Selection.Caret.Descent) / float64(w.scale)
style := w.tarea.Get("style")
style.Set("left", fmt.Sprintf("%fpx", left))
style.Set("top", fmt.Sprintf("%fpx", top))
style.Set("height", fmt.Sprintf("%fpx", height))
style.Set("width", "1px")
}
}
func (w *window) EditorStateChanged(old, new editorState) {}
func (w *window) SetAnimating(anim bool) {
w.animating = anim
@@ -863,9 +535,10 @@ func (w *window) ReadClipboard() {
if w.clipboard.IsUndefined() {
return
}
if w.clipboard.Get("readText").Truthy() {
w.clipboard.Call("readText").Call("then", w.clipboardCallback)
if w.clipboard.Get("readText").IsUndefined() {
return
}
w.clipboard.Call("readText", w.clipboard).Call("then", w.clipboardCallback)
}
func (w *window) WriteClipboard(mime string, s []byte) {
@@ -948,13 +621,6 @@ func (w *window) ShowTextInput(show bool) {
if show {
w.focus()
} else {
// If we're composing, end composition first by clearing the textarea.
// That is a attempt to force the browser to end composition.
if w.composing != -1 {
w.tarea.Set("value", "")
w.composing = -1
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
}
w.blur()
}
}
+142 -255
View File
@@ -40,9 +40,8 @@ import (
#define MOUSE_SCROLL 4
__attribute__ ((visibility ("hidden"))) void gio_main(void);
__attribute__ ((visibility ("hidden"))) void gio_init(void);
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createView(int presentWithTrans);
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height);
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight);
__attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle);
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) {
@autoreleasepool {
NSWindow *window = (__bridge NSWindow *)windowRef;
@@ -193,7 +184,6 @@ static void setMaxSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
@autoreleasepool {
NSWindow* window = (__bridge NSWindow *)windowRef;
window.contentMaxSize = NSMakeSize(width, height);
window.maxFullScreenContentSize = NSMakeSize(width, height);
}
}
@@ -241,13 +231,6 @@ static void setTitle(CFTypeRef windowRef, CFTypeRef titleRef) {
}
}
static void setWindowLevel(CFTypeRef windowRef, NSWindowLevel level) {
@autoreleasepool {
NSWindow *window = (__bridge NSWindow *)windowRef;
window.level = level;
}
}
static int isWindowZoomed(CFTypeRef windowRef) {
@autoreleasepool {
NSWindow *window = (__bridge NSWindow *)windowRef;
@@ -255,13 +238,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) {
@autoreleasepool {
NSWindow *window = (__bridge NSWindow *)windowRef;
@@ -321,29 +297,12 @@ static void invalidateCharacterCoordinates(CFTypeRef viewRef) {
}
}
}
static void interpretKeyEvents(CFTypeRef viewRef, CFTypeRef eventRef) {
@autoreleasepool {
NSView *view = (__bridge NSView *)viewRef;
NSEvent *event = (__bridge NSEvent *)eventRef;
[view interpretKeyEvents:[NSArray arrayWithObject:event]];
}
}
static int isMiniaturized(CFTypeRef windowRef) {
@autoreleasepool {
NSWindow *window = (__bridge NSWindow *)windowRef;
return window.miniaturized ? 1 : 0;
}
}
*/
import "C"
func init() {
// Darwin requires that UI operations happen on the main thread only.
runtime.LockOSThread()
// Register launch finished listener.
C.gio_init()
}
// AppKitViewEvent notifies the client of changes to the window AppKit handles.
@@ -359,6 +318,7 @@ type window struct {
view C.CFTypeRef
w *callbacks
anim bool
visible bool
displayLink *displayLink
// redraw is a single entry channel for making sure only one
// display link redraw request is in flight.
@@ -366,20 +326,9 @@ type window struct {
cursor pointer.Cursor
pointerBtns pointer.Buttons
loop *eventLoop
lastMods C.NSUInteger
scale float32
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 {
eventStr string
eventMods key.Modifiers
}
// launched is closed when applicationDidFinishLaunching is called.
@@ -418,23 +367,11 @@ func (w *window) WriteClipboard(mime string, s []byte) {
}
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)))
switch {
case style&C.NSWindowStyleMaskFullScreen != 0:
if style&C.NSWindowStyleMaskFullScreen != 0 {
w.config.Mode = Fullscreen
case C.isWindowZoomed(window) != 0:
w.config.Mode = Maximized
} else {
w.config.Mode = Windowed
}
w.config.Decorated = style&C.NSWindowStyleMaskFullSizeContentView == 0
}
@@ -442,85 +379,105 @@ func (w *window) updateWindowMode() {
func (w *window) Configure(options []Option) {
screenScale := float32(C.getScreenBackingScale())
cfg := configFor(screenScale)
prev := w.config
w.updateWindowMode()
cnf := w.config
cnf.apply(cfg, options)
window := C.windowForView(w.view)
mask := C.getWindowStyleMask(window)
fullscreen := mask&C.NSWindowStyleMaskFullScreen != 0
switch cnf.Mode {
case Fullscreen:
if C.isWindowMiniaturized(window) != 0 {
switch prev.Mode {
case Fullscreen:
case Minimized:
C.unhideWindow(window)
}
if !fullscreen {
fallthrough
default:
w.config.Mode = Fullscreen
C.toggleFullScreen(window)
}
case Minimized:
C.hideWindow(window)
switch prev.Mode {
case Minimized, Fullscreen:
default:
w.config.Mode = Minimized
C.hideWindow(window)
}
case Maximized:
if C.isWindowMiniaturized(window) != 0 {
switch prev.Mode {
case Fullscreen:
case Minimized:
C.unhideWindow(window)
}
if fullscreen {
C.toggleFullScreen(window)
}
w.setTitle(cnf.Title)
if C.isWindowZoomed(window) == 0 {
C.zoomWindow(window)
fallthrough
default:
w.config.Mode = Maximized
w.setTitle(prev, cnf)
if C.isWindowZoomed(window) == 0 {
C.zoomWindow(window)
}
}
case Windowed:
if C.isWindowMiniaturized(window) != 0 {
C.unhideWindow(window)
}
if fullscreen {
switch prev.Mode {
case Fullscreen:
C.toggleFullScreen(window)
case Minimized:
C.unhideWindow(window)
case Maximized:
if C.isWindowZoomed(window) != 0 {
C.zoomWindow(window)
}
}
w.setTitle(cnf.Title)
w.config.Size = cnf.Size
cnf.Size = cnf.Size.Div(int(screenScale))
C.setSize(window, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y))
w.config.MinSize = cnf.MinSize
cnf.MinSize = cnf.MinSize.Div(int(screenScale))
C.setMinSize(window, C.CGFloat(cnf.MinSize.X), C.CGFloat(cnf.MinSize.Y))
w.config.MaxSize = cnf.MaxSize
cnf.MaxSize = cnf.MaxSize.Div(int(screenScale))
if cnf.MaxSize != (image.Point{}) {
w.config.Mode = Windowed
w.setTitle(prev, cnf)
if prev.Size != cnf.Size {
w.config.Size = cnf.Size
cnf.Size = cnf.Size.Div(int(screenScale))
C.setSize(window, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y))
}
if prev.MinSize != cnf.MinSize {
w.config.MinSize = cnf.MinSize
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))
}
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)
// When toggling the titlebar, the layer doesn't update its frame
// until the next resize. Force it.
C.resetLayerFrame(w.view)
}
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
}
if cnf.TopMost {
C.setWindowLevel(window, C.NSFloatingWindowLevel)
}
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)
w.ProcessEvent(ConfigEvent{Config: w.config})
}
func (w *window) setTitle(title string) {
w.config.Title = title
titleC := stringToNSString(title)
defer C.CFRelease(titleC)
C.setTitle(C.windowForView(w.view), titleC)
func (w *window) setTitle(prev, cnf Config) {
if prev.Title != cnf.Title {
w.config.Title = cnf.Title
title := stringToNSString(cnf.Title)
defer C.CFRelease(title)
C.setTitle(C.windowForView(w.view), title)
}
}
func (w *window) Perform(acts system.Action) {
@@ -548,7 +505,7 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
}
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)
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
}
@@ -563,8 +520,7 @@ func (w *window) SetInputHint(_ key.InputHint) {}
func (w *window) SetAnimating(anim bool) {
w.anim = anim
window := C.windowForView(w.view)
if w.anim && window != 0 && C.isMiniaturized(window) == 0 {
if w.anim && w.visible {
w.displayLink.Start()
} else {
w.displayLink.Stop()
@@ -582,92 +538,23 @@ func (w *window) runOnMain(f func()) {
}
//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) {
w := windowFor(h)
if w.keysDown == nil {
w.keysDown = make(map[key.Name]struct{})
}
func gio_onKeys(h C.uintptr_t, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger, keyDown C.bool) {
str := nsstringToString(cstr)
kmods := convertMods(mods)
ks := key.Release
if keyDown {
ks = key.Press
w.cmdKeys.eventStr = str
w.cmdKeys.eventMods = kmods
C.interpretKeyEvents(w.view, event)
}
w := windowFor(h)
for _, k := range str {
if n, ok := convertKey(k); ok {
ke := key.Event{
w.ProcessEvent(key.Event{
Name: n,
Modifiers: kmods,
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
@@ -860,15 +747,6 @@ func gio_substringForProposedRange(h C.uintptr_t, crng C.NSRange, actual C.NSRan
//export gio_insertText
func gio_insertText(h C.uintptr_t, cstr C.CFTypeRef, crng C.NSRange) {
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()
rng := state.compose
if rng.Start == -1 {
@@ -880,6 +758,7 @@ func gio_insertText(h C.uintptr_t, cstr C.CFTypeRef, crng C.NSRange) {
End: state.RunesIndex(int(crng.location + crng.length)),
}
}
str := nsstringToString(cstr)
w.w.EditorReplace(rng, str)
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
start := rng.Start
@@ -906,7 +785,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).
scale := 1. / float32(C.getViewBackingScale(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)
bounds := f32.Rectangle{
Min: t.Transform(sel.Pos.Sub(f32.Pt(0, sel.Ascent))),
@@ -920,19 +799,24 @@ func gio_firstRectForCharacterRange(h C.uintptr_t, crng C.NSRange, actual C.NSRa
}
func (w *window) draw() {
cnf := w.config
w.updateWindowMode()
if w.config != cnf {
w.ProcessEvent(ConfigEvent{Config: w.config})
}
select {
case <-w.redraw:
default:
}
w.visible = true
if 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 {
return
}
@@ -940,7 +824,7 @@ func (w *window) draw() {
w.ProcessEvent(frameEvent{
FrameEvent: FrameEvent{
Now: time.Now(),
Size: sz,
Size: w.config.Size,
Metric: cfg,
},
Sync: true,
@@ -948,13 +832,8 @@ func (w *window) draw() {
}
func (w *window) ProcessEvent(e event.Event) {
w.processEvent(e)
}
func (w *window) processEvent(e event.Event) bool {
handled := w.w.ProcessEvent(e)
w.w.ProcessEvent(e)
w.loop.FlushEvents()
return handled
}
func (w *window) Event() event.Event {
@@ -988,6 +867,7 @@ func gio_onAttached(h C.uintptr_t, attached C.int) {
w.ProcessEvent(AppKitViewEvent{View: uintptr(w.view), Layer: uintptr(layer)})
} else {
w.ProcessEvent(AppKitViewEvent{})
w.visible = false
w.SetAnimating(w.anim)
}
}
@@ -1002,21 +882,38 @@ func gio_onDestroy(h C.uintptr_t) {
w.view = 0
}
//export gio_onHide
func gio_onHide(h C.uintptr_t) {
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})
}
//export gio_onFinishLaunching
func gio_onFinishLaunching() {
close(launched)
}
//export gio_onOpenURI
func gio_onOpenURI(uri C.CFTypeRef) {
evt, err := newURLEvent(nsstringToString(uri))
if err != nil {
return
}
processGlobalEvent(evt)
}
func newWindow(win *callbacks, options []Option) {
<-launched
res := make(chan struct{})
@@ -1034,9 +931,10 @@ func newWindow(win *callbacks, options []Option) {
w.ProcessEvent(DestroyEvent{Err: err})
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.
C.CFRelease(w.view)
w.updateWindowMode()
w.Configure(options)
if nextTopLeft.x == 0 && nextTopLeft.y == 0 {
// cascadeTopLeftFromPoint treats (0, 0) as a no-op,
@@ -1044,7 +942,6 @@ func newWindow(win *callbacks, options []Option) {
nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft)
}
nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft)
C.makeFirstResponder(window, w.view)
// makeKeyAndOrderFront assumes ownership of our window reference.
C.makeKeyAndOrderFront(window)
})
@@ -1069,7 +966,9 @@ func (w *window) init(customRenderer bool) error {
return
}
w.runOnMain(func() {
C.setNeedsDisplay(w.view)
if w.visible {
C.setNeedsDisplay(w.view)
}
})
})
w.displayLink = dl
@@ -1089,10 +988,10 @@ func osMain() {
C.gio_main()
}
func convertCommandKey(k rune) (key.Name, bool) {
func convertKey(k rune) (key.Name, bool) {
var n key.Name
switch k {
case '\x1b': // ASCII escape.
case 0x1b:
n = key.NameEscape
case C.NSLeftArrowFunctionKey:
n = key.NameLeftArrow
@@ -1102,36 +1001,22 @@ func convertCommandKey(k rune) (key.Name, bool) {
n = key.NameUpArrow
case C.NSDownArrowFunctionKey:
n = key.NameDownArrow
case '\r':
case 0xd:
n = key.NameReturn
case '\x03':
case 0x3:
n = key.NameEnter
case C.NSHomeFunctionKey:
n = key.NameHome
case C.NSEndFunctionKey:
n = key.NameEnd
case '\x7f', '\b':
case 0x7f:
n = key.NameDeleteBackward
case C.NSDeleteFunctionKey:
n = key.NameDeleteForward
case '\t', 0x19:
n = key.NameTab
case C.NSPageUpFunctionKey:
n = key.NamePageUp
case C.NSPageDownFunctionKey:
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:
n = key.NameF1
case C.NSF2FunctionKey:
@@ -1156,6 +1041,8 @@ func convertKey(k rune) (key.Name, bool) {
n = key.NameF11
case C.NSF12FunctionKey:
n = key.NameF12
case 0x09, 0x19:
n = key.NameTab
case 0x20:
n = key.NameSpace
default:
+22 -44
View File
@@ -23,22 +23,22 @@ __attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(BOOL presentWi
- (void)windowWillMiniaturize:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object];
GioView *view = (GioView *)window.contentView;
gio_onDraw(view.handle);
gio_onHide(view.handle);
}
- (void)windowDidDeminiaturize:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object];
GioView *view = (GioView *)window.contentView;
gio_onDraw(view.handle);
gio_onShow(view.handle);
}
- (void)windowWillEnterFullScreen:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object];
GioView *view = (GioView *)window.contentView;
gio_onDraw(view.handle);
gio_onFullscreen(view.handle);
}
- (void)windowWillExitFullScreen:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object];
GioView *view = (GioView *)window.contentView;
gio_onDraw(view.handle);
gio_onWindowed(view.handle);
}
- (void)windowDidChangeScreen:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object];
@@ -132,25 +132,22 @@ static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFl
handleMouse(self, event, MOUSE_SCROLL, dx, dy);
}
- (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]];
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 {
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 {
gio_onText(self.handle, (__bridge CFTypeRef)string);
}
- (void)doCommandBySelector:(SEL)action {
if (!gio_onCommandBySelector(self.handle)) {
[super doCommandBySelector:action];
}
- (void)doCommandBySelector:(SEL)sel {
// Don't pass commands up the responder chain.
// They will end up in a beep.
}
- (BOOL)hasMarkedText {
int res = gio_hasMarkedText(self.handle);
return res ? YES : NO;
@@ -205,10 +202,10 @@ static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFl
return [[self window] convertRectToScreen:r];
}
- (void)applicationWillUnhide:(NSNotification *)notification {
gio_onDraw(self.handle);
gio_onShow(self.handle);
}
- (void)applicationDidHide:(NSNotification *)notification {
gio_onDraw(self.handle);
gio_onHide(self.handle);
}
- (void)dealloc {
gio_onDestroy(self.handle);
@@ -369,7 +366,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 {
NSRect rect = NSMakeRect(0, 0, width, height);
NSUInteger styleMask = NSTitledWindowMask |
@@ -381,9 +378,16 @@ CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height) {
styleMask:styleMask
backing:NSBackingStoreBuffered
defer:NO];
if (minWidth > 0 || minHeight > 0) {
window.contentMinSize = NSMakeSize(minWidth, minHeight);
}
if (maxWidth > 0 || maxHeight > 0) {
window.contentMaxSize = NSMakeSize(maxWidth, maxHeight);
}
[window setAcceptsMouseMovedEvents:YES];
NSView *view = (__bridge NSView *)viewRef;
[window setContentView:view];
[window makeFirstResponder:view];
window.delegate = globalWindowDel;
return (__bridge_retained CFTypeRef)window;
}
@@ -420,11 +424,7 @@ void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
[NSApp activateIgnoringOtherApps:YES];
}
- (void)application:(NSApplication *)application openURLs:(NSArray<NSURL *> *)urls {
for (NSURL *url in urls) {
gio_onOpenURI((__bridge CFTypeRef)url.absoluteString);
}
gio_onFinishLaunching();
}
@end
@@ -455,25 +455,3 @@ void gio_main() {
[NSApp run];
}
}
@interface AppListener : NSObject
@end
static AppListener *appListener;
@implementation AppListener
- (void)launchFinished:(NSNotification *)notification {
appListener = nil;
gio_onFinishLaunching();
}
@end
void gio_init() {
@autoreleasepool {
appListener = [[AppListener alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:appListener
selector:@selector(launchFinished:)
name:NSApplicationDidFinishLaunchingNotification
object:nil];
}
}
+1
View File
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build (linux && !android) || freebsd || openbsd
// +build linux,!android freebsd openbsd
package app
+14 -26
View File
@@ -116,8 +116,6 @@ type wlSeat struct {
// The most recent input serial.
serial C.uint32_t
// The most recent pointer enter serial.
pointerSerial C.uint32_t
pointerFocus *window
keyboardFocus *window
@@ -156,6 +154,7 @@ type repeatState struct {
type window struct {
w *callbacks
disp *wlDisplay
seat *wlSeat
surf *C.struct_wl_surface
wmSurf *C.struct_xdg_surface
topLvl *C.struct_xdg_toplevel
@@ -852,8 +851,8 @@ func gio_onTouchCancel(data unsafe.Pointer, touch *C.struct_wl_touch) {
func gio_onPointerEnter(data unsafe.Pointer, pointer *C.struct_wl_pointer, serial C.uint32_t, surf *C.struct_wl_surface, x, y C.wl_fixed_t) {
s := callbackLoad(data).(*wlSeat)
s.serial = serial
s.pointerSerial = serial
w := callbackLoad(unsafe.Pointer(surf)).(*window)
w.seat = s
s.pointerFocus = w
w.setCursor(pointer, serial)
w.lastPos = f32.Point{X: fromFixed(x), Y: fromFixed(y)}
@@ -862,9 +861,9 @@ func gio_onPointerEnter(data unsafe.Pointer, pointer *C.struct_wl_pointer, seria
//export gio_onPointerLeave
func gio_onPointerLeave(data unsafe.Pointer, p *C.struct_wl_pointer, serial C.uint32_t, surf *C.struct_wl_surface) {
w := callbackLoad(unsafe.Pointer(surf)).(*window)
w.seat = nil
s := callbackLoad(data).(*wlSeat)
s.serial = serial
s.pointerFocus = nil
if w.inCompositor {
w.inCompositor = false
w.ProcessEvent(pointer.Event{Kind: pointer.Cancel})
@@ -884,13 +883,11 @@ func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t,
s := callbackLoad(data).(*wlSeat)
s.serial = serial
w := s.pointerFocus
// From Linux: include/uapi/linux/input-event-codes.h
// From linux-event-codes.h.
const (
BTN_LEFT = 0x110
BTN_RIGHT = 0x111
BTN_MIDDLE = 0x112
BTN_SIDE = 0x113
BTN_EXTRA = 0x114
)
var btn pointer.Buttons
switch wbtn {
@@ -900,10 +897,6 @@ func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t,
btn = pointer.ButtonSecondary
case BTN_MIDDLE:
btn = pointer.ButtonTertiary
case BTN_SIDE:
btn = pointer.ButtonQuaternary
case BTN_EXTRA:
btn = pointer.ButtonQuinary
default:
return
}
@@ -970,9 +963,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) {
s := callbackLoad(data).(*wlSeat)
w := s.pointerFocus
if w == nil {
return
}
w.flushScroll()
w.flushFling()
}
@@ -1153,17 +1143,16 @@ func (w *window) Perform(actions system.Action) {
}
func (w *window) move(serial C.uint32_t) {
s := w.disp.seat
if w.inCompositor || s.pointerFocus != w {
return
s := w.seat
if !w.inCompositor && s != nil {
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) {
s := w.disp.seat
if w.inCompositor || s.pointerFocus != w {
s := w.seat
if w.inCompositor || s == nil {
return
}
w.inCompositor = true
@@ -1176,12 +1165,11 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
}
func (w *window) updateCursor() {
s := w.disp.seat
ptr := s.pointer
if ptr == nil || s.pointerFocus != w {
ptr := w.disp.seat.pointer
if ptr == nil {
return
}
w.setCursor(ptr, s.pointerSerial)
w.setCursor(ptr, w.serial)
}
func (w *window) setCursor(pointer *C.struct_wl_pointer, serial C.uint32_t) {
@@ -1190,7 +1178,7 @@ func (w *window) setCursor(pointer *C.struct_wl_pointer, serial C.uint32_t) {
c = w.cursor.cursor
}
if c == nil {
C.wl_pointer_set_cursor(pointer, serial, nil, 0, 0)
C.wl_pointer_set_cursor(pointer, w.serial, nil, 0, 0)
return
}
// Get images[0].
+106 -404
View File
@@ -5,12 +5,8 @@ package app
import (
"errors"
"fmt"
"gioui.org/io/transfer"
syscall "golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
"image"
"io"
"os"
"runtime"
"sort"
"strings"
@@ -20,6 +16,8 @@ import (
"unicode/utf8"
"unsafe"
syscall "golang.org/x/sys/windows"
"gioui.org/app/internal/windows"
"gioui.org/op"
"gioui.org/unit"
@@ -30,6 +28,7 @@ import (
"gioui.org/io/key"
"gioui.org/io/pointer"
"gioui.org/io/system"
"gioui.org/io/transfer"
)
type Win32ViewEvent struct {
@@ -37,28 +36,28 @@ type Win32ViewEvent struct {
}
type window struct {
hwnd syscall.Handle
hdc syscall.Handle
w *callbacks
hwnd syscall.Handle
hdc syscall.Handle
w *callbacks
pointerBtns pointer.Buttons
// cursorIn tracks whether the cursor was inside the window according
// to the most recent WM_SETCURSOR.
cursorIn bool
cursor syscall.Handle
// placement saves the previous window position when in full screen mode.
placement *windows.WindowPlacement
animating bool
borderSize image.Point
config Config
// frameDims stores the last seen window frame width and height.
frameDims image.Point
loop *eventLoop
loop *eventLoop
}
const _WM_WAKEUP = windows.WM_USER + iota
const copyDataURLType = 0xffffff00
type gpuAPI struct {
priority int
initializer func(w *window) (context, error)
@@ -84,7 +83,6 @@ var resources struct {
}
func osMain() {
processURLEvent(startupURI())
select {}
}
@@ -110,8 +108,8 @@ func newWindow(win *callbacks, options []Option) {
}
winMap.Store(w.hwnd, w)
defer winMap.Delete(w.hwnd)
w.Configure(options)
w.ProcessEvent(Win32ViewEvent{HWND: uintptr(w.hwnd)})
w.Configure(options)
windows.SetForegroundWindow(w.hwnd)
windows.SetFocus(w.hwnd)
// Since the window class for the cursor is null,
@@ -136,19 +134,13 @@ func initResources() error {
}
resources.cursor = c
icon, _ := windows.LoadImage(hInst, iconID, windows.IMAGE_ICON, 0, 0, windows.LR_DEFAULTSIZE|windows.LR_SHARED)
appid, err := syscall.UTF16PtrFromString(ID)
if err != nil {
return err
}
wcls := windows.WndClassEx{
CbSize: uint32(unsafe.Sizeof(windows.WndClassEx{})),
Style: windows.CS_HREDRAW | windows.CS_VREDRAW | windows.CS_OWNDC,
LpfnWndProc: syscall.NewCallback(windowProc),
HInstance: hInst,
HIcon: icon,
LpszClassName: appid,
LpszClassName: syscall.StringToUTF16Ptr("GioWindow"),
}
cls, err := windows.RegisterClassEx(&wcls)
if err != nil {
@@ -184,12 +176,6 @@ func (w *window) init() error {
if err != nil {
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)
if err != nil {
windows.DestroyWindow(hwnd)
@@ -199,39 +185,21 @@ func (w *window) init() error {
return nil
}
// update handles changes done by the user, and updates the configuration.
// update() handles changes done by the user, and updates the configuration.
// It reads the window style and size/position and updates w.config.
// If anything has changed it emits a ConfigEvent to notify the application.
func (w *window) update() {
p := windows.GetWindowPlacement(w.hwnd)
if !p.IsMinimized() {
r := windows.GetWindowRect(w.hwnd)
cr := windows.GetClientRect(w.hwnd)
w.config.Size = image.Point{
X: int(cr.Right - cr.Left),
Y: int(cr.Bottom - cr.Top),
}
w.frameDims = image.Point{
X: int(r.Right - r.Left),
Y: int(r.Bottom - r.Top),
}.Sub(w.config.Size)
cr := windows.GetClientRect(w.hwnd)
w.config.Size = image.Point{
X: int(cr.Right - cr.Left),
Y: int(cr.Bottom - cr.Top),
}
w.borderSize = image.Pt(
windows.GetSystemMetrics(windows.SM_CXSIZEFRAME),
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.draw(true)
}
func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
@@ -280,32 +248,18 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
return 0
}
}
case windows.WM_POINTERDOWN, windows.WM_POINTERUP, windows.WM_POINTERUPDATE, windows.WM_POINTERCAPTURECHANGED:
pid := getPointerIDwParam(wParam)
pi, err := windows.GetPointerInfo(uint32(pid))
if err != nil {
panic(err)
}
switch msg {
case windows.WM_POINTERDOWN:
windows.SetCapture(w.hwnd)
case windows.WM_POINTERUP:
windows.ReleaseCapture()
}
kind := pointer.Move
switch pi.ButtonChangeType {
case windows.POINTER_CHANGE_FIRSTBUTTON_DOWN, windows.POINTER_CHANGE_SECONDBUTTON_DOWN, windows.POINTER_CHANGE_THIRDBUTTON_DOWN, windows.POINTER_CHANGE_FOURTHBUTTON_DOWN, windows.POINTER_CHANGE_FIFTHBUTTON_DOWN:
kind = pointer.Press
case windows.POINTER_CHANGE_FIRSTBUTTON_UP, windows.POINTER_CHANGE_SECONDBUTTON_UP, windows.POINTER_CHANGE_THIRDBUTTON_UP, windows.POINTER_CHANGE_FOURTHBUTTON_UP, windows.POINTER_CHANGE_FIFTHBUTTON_UP:
kind = pointer.Release
}
if (pi.PointerFlags&windows.POINTER_FLAG_CANCELED != 0) || (msg == windows.WM_POINTERCAPTURECHANGED) {
kind = pointer.Cancel
}
w.pointerUpdate(pi, pid, kind, lParam)
case windows.WM_LBUTTONDOWN:
w.pointerButton(pointer.ButtonPrimary, true, lParam, getModifiers())
case windows.WM_LBUTTONUP:
w.pointerButton(pointer.ButtonPrimary, false, lParam, getModifiers())
case windows.WM_RBUTTONDOWN:
w.pointerButton(pointer.ButtonSecondary, true, lParam, getModifiers())
case windows.WM_RBUTTONUP:
w.pointerButton(pointer.ButtonSecondary, false, lParam, getModifiers())
case windows.WM_MBUTTONDOWN:
w.pointerButton(pointer.ButtonTertiary, true, lParam, getModifiers())
case windows.WM_MBUTTONUP:
w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers())
case windows.WM_CANCELMODE:
w.ProcessEvent(pointer.Event{
Kind: pointer.Cancel,
@@ -325,14 +279,24 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
np := windows.Point{X: int32(x), Y: int32(y)}
windows.ScreenToClient(w.hwnd, &np)
return w.hitTest(int(np.X), int(np.Y))
case windows.WM_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())
case windows.WM_POINTERHWHEEL:
case windows.WM_MOUSEHWHEEL:
w.scrollEvent(wParam, lParam, true, getModifiers())
case windows.WM_DESTROY:
w.ProcessEvent(Win32ViewEvent{})
w.ProcessEvent(DestroyEvent{})
w.w = nil
if w.hdc != 0 {
windows.ReleaseDC(w.hdc)
w.hdc = 0
@@ -340,7 +304,6 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
// The system destroys the HWND for us.
w.hwnd = 0
windows.PostQuitMessage(0)
return 0
case windows.WM_NCCALCSIZE:
if w.config.Decorated {
// Let Windows handle decorations.
@@ -363,33 +326,45 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
mi := windows.GetMonitorInfo(w.hwnd)
szp.Rgrc[0] = mi.WorkArea
return 0
case windows.WM_NCLBUTTONDBLCLK:
if !w.config.Decorated {
// Override Windows behaviour when we
// draw decorations.
return 0
}
case windows.WM_PAINT:
w.draw(true)
case windows.WM_STYLECHANGED:
w.update()
case windows.WM_WINDOWPOSCHANGED:
w.update()
case windows.WM_SIZE:
w.update()
switch wParam {
case windows.SIZE_MINIMIZED:
w.config.Mode = Minimized
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:
mm := (*windows.MinMaxInfo)(unsafe.Pointer(lParam))
var frameDims image.Point
var bw, bh int32
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 {
p = p.Add(frameDims)
mm.PtMinTrackSize = windows.Point{
X: int32(p.X),
Y: int32(p.Y),
X: int32(p.X) + bw,
Y: int32(p.Y) + bh,
}
}
if p := w.config.MaxSize; p.X > 0 || p.Y > 0 {
p = p.Add(frameDims)
mm.PtMaxTrackSize = windows.Point{
X: int32(p.X),
Y: int32(p.Y),
X: int32(p.X) + bw,
Y: int32(p.Y) + bh,
}
}
return 0
@@ -456,20 +431,6 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
case windows.WM_IME_ENDCOMPOSITION:
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
return windows.TRUE
case windows.WM_COPYDATA:
data := (*windows.CopyDataStruct)(unsafe.Pointer(lParam))
switch data.DwData {
case copyDataURLType:
if schemesURI == "" {
return windows.TRUE
}
uri := syscall.UTF16PtrToString((*uint16)(unsafe.Pointer(data.LpData)))
if processURLEvent(uri) {
w.Perform(system.ActionRaise)
}
return windows.TRUE
}
}
return windows.DefWindowProc(hwnd, msg, wParam, lParam)
@@ -495,6 +456,9 @@ func getModifiers() key.Modifiers {
// hitTest returns the non-client area hit by the point, needed to
// process WM_NCHITTEST.
func (w *window) hitTest(x, y int) uintptr {
if w.config.Mode == Fullscreen {
return windows.HTCLIENT
}
if w.config.Mode != Windowed {
// Only windowed mode should allow resizing.
return windows.HTCLIENT
@@ -531,28 +495,34 @@ func (w *window) hitTest(x, y int) uintptr {
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 {
windows.SetFocus(w.hwnd)
}
src := pointer.Touch
if pi.PointerType == windows.PT_MOUSE {
src = pointer.Mouse
var kind pointer.Kind
if press {
kind = pointer.Press
if w.pointerBtns == 0 {
windows.SetCapture(w.hwnd)
}
w.pointerBtns |= btn
} else {
kind = pointer.Release
w.pointerBtns &^= btn
if w.pointerBtns == 0 {
windows.ReleaseCapture()
}
}
x, y := coordsFromlParam(lParam)
np := windows.Point{X: int32(x), Y: int32(y)}
windows.ScreenToClient(w.hwnd, &np)
p := f32.Point{X: float32(np.X), Y: float32(np.Y)}
p := f32.Point{X: float32(x), Y: float32(y)}
w.ProcessEvent(pointer.Event{
Kind: kind,
Source: src,
Source: pointer.Mouse,
Position: p,
PointerID: pid,
Buttons: getPointerButtons(pi),
Buttons: w.pointerBtns,
Time: windows.GetMessageTime(),
Modifiers: getModifiers(),
Modifiers: kmods,
})
}
@@ -563,12 +533,6 @@ func coordsFromlParam(lParam uintptr) (int, int) {
}
func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.Modifiers) {
pid := getPointerIDwParam(wParam)
pi, err := windows.GetPointerInfo(uint32(pid))
if err != nil {
panic(err)
}
x, y := coordsFromlParam(lParam)
// The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast
// to other mouse events.
@@ -591,7 +555,7 @@ func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.
Kind: pointer.Scroll,
Source: pointer.Mouse,
Position: p,
Buttons: getPointerButtons(pi),
Buttons: w.pointerBtns,
Scroll: sp,
Modifiers: kmods,
Time: windows.GetMessageTime(),
@@ -604,8 +568,7 @@ func (w *window) runLoop() {
loop:
for {
anim := w.animating
p := windows.GetWindowPlacement(w.hwnd)
if anim && !p.IsMinimized() && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) {
if anim && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) {
w.draw(false)
continue
}
@@ -728,13 +691,8 @@ func (w *window) readClipboard() error {
func (w *window) Configure(options []Option) {
dpi := windows.GetSystemDPI()
metric := configForDPI(dpi)
cnf := w.config
cnf.apply(metric, options)
w.config.Title = cnf.Title
w.config.Decorated = cnf.Decorated
w.config.MinSize = cnf.MinSize
w.config.MaxSize = cnf.MaxSize
windows.SetWindowText(w.hwnd, cnf.Title)
w.config.apply(metric, options)
windows.SetWindowText(w.hwnd, w.config.Title)
style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE)
var showMode int32
@@ -742,7 +700,7 @@ func (w *window) Configure(options []Option) {
swpStyle := uintptr(windows.SWP_NOZORDER | windows.SWP_FRAMECHANGED)
winStyle := uintptr(windows.WS_OVERLAPPEDWINDOW)
style &^= winStyle
switch cnf.Mode {
switch w.config.Mode {
case Minimized:
style |= winStyle
swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
@@ -757,13 +715,13 @@ func (w *window) Configure(options []Option) {
style |= winStyle
showMode = windows.SW_SHOWNORMAL
// Get target for client area size.
width = int32(cnf.Size.X)
height = int32(cnf.Size.Y)
width = int32(w.config.Size.X)
height = int32(w.config.Size.Y)
// Get the current window size and position.
wr := windows.GetWindowRect(w.hwnd)
x = wr.Left
y = wr.Top
if cnf.Decorated {
if w.config.Decorated {
// Compute client size and position. Note that the client size is
// equal to the window size when we are in control of decorations.
r := windows.Rect{
@@ -773,27 +731,25 @@ func (w *window) Configure(options []Option) {
windows.AdjustWindowRectEx(&r, uint32(style), 0, dwExStyle)
width = r.Right - r.Left
height = r.Bottom - r.Top
} else {
}
if !w.config.Decorated {
// Enable drop shadows when we draw decorations.
windows.DwmExtendFrameIntoClientArea(w.hwnd, windows.Margins{-1, -1, -1, -1})
}
case Fullscreen:
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
}
// 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.SetWindowPos(w.hwnd, 0, x, y, width, height, swpStyle)
windows.ShowWindow(w.hwnd, showMode)
w.update()
}
func (w *window) WriteClipboard(mime string, s []byte) {
@@ -1034,257 +990,3 @@ 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
}
// schemesURI is a list of schemes, comma separated, that must be
// defined using -X compiler ldflag, that used in gogio.
var schemesURI string
func init() {
if schemesURI == "" {
return
}
currentSchemes := strings.Split(schemesURI, ",")
oldSchemes := registeredSchemes(ID)
for _, s := range currentSchemes {
for i, o := range oldSchemes {
if s == o {
oldSchemes = append(oldSchemes[:i], oldSchemes[i+1:]...)
break
}
}
}
if len(oldSchemes) > 0 {
go unregisterSchemes(ID, oldSchemes)
}
if len(currentSchemes) == 0 {
return
}
// On Windows, launching the app using a URI will start a new instance of the app,
// a new window. That behavior, by default, doesn't align with iOS/Android/macOS, where
// the deeplink sends the event to the running app (if any). We are emulating it.
if hwnd, _ := windows.FindWindow(ID); hwnd != 0 {
if u := startupURI(); u != "" {
broadcastURI(hwnd, u)
}
os.Exit(0)
return
}
go registerSchemes(ID, currentSchemes)
}
func startupURI() string {
if len(os.Args) == 3 && os.Args[1] == "-gio_launch_url" {
return os.Args[2]
}
return ""
}
func processURLEvent(rawurl string) bool {
if rawurl == "" {
return false
}
evt, err := newURLEvent(rawurl)
if err != nil {
return false
}
for _, scheme := range strings.Split(schemesURI, ",") {
if strings.EqualFold(scheme, evt.URL.Scheme) {
processGlobalEvent(evt)
return true
}
}
return false
}
func broadcastURI(hwnd syscall.Handle, uri string) {
data, err := syscall.UTF16FromString(uri)
if err != nil {
return // Only happens if uri contains NULL character.
}
pinner := new(runtime.Pinner)
defer pinner.Unpin()
pinner.Pin(unsafe.Pointer(&data[0]))
msg := &windows.CopyDataStruct{
DwData: copyDataURLType,
CbData: uint32(len(data) * int(unsafe.Sizeof(data[0]))),
LpData: uintptr(unsafe.Pointer(unsafe.SliceData(data))),
}
pinner.Pin(unsafe.Pointer(msg))
// SendMessage blocks until the message is processed.
windows.SendMessage(hwnd, windows.WM_COPYDATA, 0, uintptr(unsafe.Pointer(msg)))
}
func registeredSchemes(appid string) []string {
meta, err := registry.OpenKey(registry.CURRENT_USER, `Software\\`+appid, registry.ALL_ACCESS)
if err != nil {
return nil
}
defer meta.Close()
schemes, _, _ := meta.GetStringsValue("URISchemes")
return schemes
}
func registerSchemes(appid string, schemes []string) error {
reg := func(scheme string) error {
key, existent, err := registry.CreateKey(registry.CURRENT_USER, `Software\\Classes\\`+scheme, registry.ALL_ACCESS)
if err != nil {
return err
}
defer key.Close()
if existent {
// Check if the existent key belongs to the current application
id, _, err := key.GetStringValue("appid")
if err != nil || id != appid {
return fmt.Errorf("scheme %s already registered by another application", scheme)
}
}
path, err := os.Executable()
if err != nil {
return err
}
if err = key.SetStringValue("", "URL:"+scheme+" Protocol"); err != nil {
return err
}
if err = key.SetStringValue("URL Protocol", ""); err != nil {
return err
}
if err = key.SetStringValue("appid", appid); err != nil {
return err
}
icon, _, err := registry.CreateKey(key, `DefaultIcon`, registry.ALL_ACCESS)
if err != nil {
return err
}
defer icon.Close()
if err = icon.SetStringValue("", `"`+path+`",1`); err != nil {
return err
}
cmd, _, err := registry.CreateKey(key, `shell\\open\\command`, registry.ALL_ACCESS)
if err != nil {
return err
}
defer cmd.Close()
if err = cmd.SetStringValue("", `"`+path+`" -gio_launch_url "%1"`); err != nil {
return err
}
return nil
}
for _, scheme := range schemes {
if scheme == "" {
continue // just in case
}
if err := reg(scheme); err != nil {
return err
}
}
meta, _, err := registry.CreateKey(registry.CURRENT_USER, `Software\\`+appid, registry.ALL_ACCESS)
if err != nil {
return err
}
defer meta.Close()
if err = meta.SetStringsValue("URISchemes", schemes); err != nil {
return err
}
return nil
}
func unregisterSchemes(appid string, schemes []string) {
classes, err := registry.OpenKey(registry.CURRENT_USER, `Software\\Classes`, registry.ALL_ACCESS)
if err != nil {
return
}
defer classes.Close()
for _, scheme := range schemes {
if scheme == "" {
continue // just in case
}
key, err := registry.OpenKey(classes, scheme, registry.ALL_ACCESS)
if err != nil {
continue
}
id, _, err := key.GetStringValue("appid")
if err == nil && id != appid {
continue
}
for _, k := range []string{`DefaultIcon`, `shell\\open\\command`, `shell\\open`, `shell`} {
registry.DeleteKey(key, k)
}
if err := key.Close(); err != nil {
continue
}
registry.DeleteKey(classes, scheme)
}
}
+3 -2
View File
@@ -26,7 +26,6 @@ package app
*/
import "C"
import (
"errors"
"fmt"
@@ -752,7 +751,9 @@ func (h *x11EventHandler) handleEvents() bool {
return redraw
}
var x11Threads sync.Once
var (
x11Threads sync.Once
)
func init() {
x11Driver = newX11Window
-15
View File
@@ -1,15 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
/*
Package microphone implements permissions to access microphone hardware.
# Android
The following entries will be added to AndroidManifest.xml:
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
RECORD_AUDIO is a "dangerous" permission. See documentation for package
gioui.org/app/permission for more information.
*/
package microphone
+2 -1
View File
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build android || (darwin && ios)
// +build android darwin,ios
package app
@@ -24,6 +25,6 @@ func runMain() {
// Indirect call, since the linker does not know the address of main when
// laying down this package.
fn := mainMain
go fn()
fn()
})
}
+1 -1
View File
@@ -175,7 +175,7 @@ func (c *vkContext) refresh(surf vk.Surface, width, height int) error {
if err != nil {
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 {
return errOutOfDate
}
+1
View File
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build !novulkan
// +build !novulkan
package app
+20 -64
View File
@@ -46,10 +46,6 @@ type Window struct {
ctx context
gpu gpu.GPU
// ctxNeedsLock tracks whether the rendering context must be made
// current again before the next GPU operation. Refresh paths, surface
// loss, and explicit unlocks all invalidate the current binding.
ctxNeedsLock bool
// timer tracks the delayed invalidate goroutine.
timer struct {
// quit is shuts down the goroutine.
@@ -93,9 +89,6 @@ type Window struct {
}
imeState editorState
driver driver
// gpuErr tracks the GPU error that is to be reported when
// the window is closed.
gpuErr error
// invMu protects mayInvalidate.
invMu sync.Mutex
@@ -150,14 +143,9 @@ func (w *Window) validateAndProcess(size image.Point, sync bool, frame *op.Ops,
if err != nil {
return err
}
w.ctxNeedsLock = true
sync = true
}
}
if err := w.lockContext(); err != nil {
w.destroyGPU()
return err
}
if sync && w.ctx != nil {
if err := w.ctx.Refresh(); err != nil {
if errors.Is(err, errOutOfDate) {
@@ -171,8 +159,9 @@ func (w *Window) validateAndProcess(size image.Point, sync bool, frame *op.Ops,
}
return err
}
w.unlockContext()
if err := w.lockContext(); err != nil {
}
if w.ctx != nil {
if err := w.ctx.Lock(); err != nil {
w.destroyGPU()
return err
}
@@ -180,7 +169,7 @@ func (w *Window) validateAndProcess(size image.Point, sync bool, frame *op.Ops,
if w.gpu == nil && !w.nocontext {
gpu, err := gpu.New(w.ctx.API())
if err != nil {
w.unlockContext()
w.ctx.Unlock()
w.destroyGPU()
return err
}
@@ -188,7 +177,7 @@ func (w *Window) validateAndProcess(size image.Point, sync bool, frame *op.Ops,
}
if w.gpu != nil {
if err := w.frame(frame, size); err != nil {
w.unlockContext()
w.ctx.Unlock()
if errors.Is(err, errOutOfDate) {
// GPU surface needs refreshing.
sync = true
@@ -208,6 +197,7 @@ func (w *Window) validateAndProcess(size image.Point, sync bool, frame *op.Ops,
var err error
if w.gpu != nil {
err = w.ctx.Present()
w.ctx.Unlock()
}
return err
}
@@ -237,8 +227,7 @@ func (w *Window) processFrame(frame *op.Ops, ack chan<- struct{}) {
w.lastFrame.deco.Add(wrapper)
if err := w.validateAndProcess(w.lastFrame.size, w.lastFrame.sync, wrapper, ack); err != nil {
w.destroyGPU()
w.gpuErr = err
w.driver.Perform(system.ActionClose)
w.driver.ProcessEvent(DestroyEvent{Err: err})
return
}
w.updateState()
@@ -401,8 +390,6 @@ func (c *callbacks) SetDriver(d driver) {
if d == nil {
panic("nil driver")
}
c.w.invMu.Lock()
defer c.w.invMu.Unlock()
c.w.driver = d
}
@@ -451,7 +438,10 @@ func (c *callbacks) SetComposingRegion(r key.Range) {
func (c *callbacks) EditorInsert(text string) {
sel := c.w.imeState.Selection.Range
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.End = sel.Start
c.SetEditorSelection(sel)
@@ -510,37 +500,16 @@ func (c *callbacks) ActionAt(p f32.Point) (system.Action, bool) {
return c.w.queue.ActionAt(p)
}
func (w *Window) lockContext() error {
if w.ctx == nil || !w.ctxNeedsLock {
return nil
}
if err := w.ctx.Lock(); err != nil {
return err
}
w.ctxNeedsLock = false
return nil
}
func (w *Window) unlockContext() {
if w.ctx == nil || w.ctxNeedsLock {
return
}
w.ctx.Unlock()
w.ctxNeedsLock = true
}
func (w *Window) destroyGPU() {
if w.gpu != nil {
if err := w.lockContext(); err == nil {
w.gpu.Release()
w.unlockContext()
}
w.ctx.Lock()
w.gpu.Release()
w.ctx.Unlock()
w.gpu = nil
}
if w.ctx != nil {
w.ctx.Release()
w.ctx = nil
w.ctxNeedsLock = false
}
}
@@ -668,14 +637,11 @@ func (w *Window) processEvent(e event.Event) bool {
e2.Size = e2.Size.Sub(offset)
w.coalesced.frame = &e2
case DestroyEvent:
if w.gpuErr != nil {
e2.Err = w.gpuErr
}
w.destroyGPU()
w.invMu.Lock()
w.mayInvalidate = false
w.driver = nil
w.invMu.Unlock()
w.driver = nil
if q := w.timer.quit; q != nil {
q <- struct{}{}
<-q
@@ -683,15 +649,13 @@ func (w *Window) processEvent(e event.Event) bool {
w.coalesced.destroy = &e2
case ViewEvent:
if !e2.Valid() && w.gpu != nil {
if err := w.lockContext(); err == nil {
w.gpu.Release()
w.unlockContext()
}
w.ctx.Lock()
w.gpu.Release()
w.gpu = nil
w.ctx.Unlock()
}
w.coalesced.view = &e2
case ConfigEvent:
w.decorations.Decorations.Maximized = e2.Config.Mode == Maximized
wasFocused := w.decorations.Config.Focused
w.decorations.Config = e2.Config
e2.Config = w.effectiveConfig()
@@ -754,7 +718,7 @@ func (w *Window) Event() event.Event {
if w.driver == nil {
e, ok := w.nextEvent()
if !ok {
panic("window initialization failed without a DestroyEvent")
panic("window initializion failed without a DestroyEvent")
}
return e
}
@@ -837,6 +801,7 @@ func (w *Window) decorate(e FrameEvent, o *op.Ops) image.Point {
default:
panic(fmt.Errorf("unknown WindowMode %v", m))
}
deco.Perform(actions)
gtx := layout.Context{
Ops: o,
Now: e.Now,
@@ -998,15 +963,6 @@ func Decorated(enabled bool) Option {
}
}
// TopMost windows will be rendered above all other non-top-most windows.
//
// TopMost windows are only supported on MacOS currently.
func TopMost(enabled bool) Option {
return func(_ unit.Metric, cnf *Config) {
cnf.TopMost = enabled
}
}
// flushEvent is sent to detect when the user program
// has completed processing of all prior events. Its an
// [io/event.Event] but only for internal use.
+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.
func (a Affine2D) Offset(offset Point) 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
// other representing the scaling, shearing and rotation part.
func (a Affine2D) Split() (srs Affine2D, offset Point) {
func (a *Affine2D) Split() (srs Affine2D, offset Point) {
return Affine2D{
a: a.a, b: a.b, c: 0,
d: a.d, e: a.e, f: 0,
+30 -160
View File
@@ -27,11 +27,11 @@ func TestTransformOffset(t *testing.T) {
p := Point{X: 1, Y: 2}
o := Point{X: 2, Y: -3}
r := AffineId().Offset(o).Transform(p)
r := Affine2D{}.Offset(o).Transform(p)
if !eq(r, Pt(3, -1)) {
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) {
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),
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 {
@@ -67,11 +64,11 @@ func TestTransformScale(t *testing.T) {
p := 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)) {
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) {
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}
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)) {
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) {
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) {
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)) {
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) {
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}
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)) {
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) {
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) {
p := Pt(-1, -1)
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) {
t.Log(pt, "!=", target)
t.Error("Scale not as expected")
@@ -175,7 +172,7 @@ func TestTransformScaleAround(t *testing.T) {
func TestTransformRotateAround(t *testing.T) {
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)
if !eq(pt, target) {
t.Log(pt, "!=", target)
@@ -184,12 +181,12 @@ func TestTransformRotateAround(t *testing.T) {
}
func TestMulOrder(t *testing.T) {
A := AffineId().Offset(Pt(100, 100))
B := AffineId().Scale(Point{}, Pt(2, 2))
A := Affine2D{}.Offset(Pt(100, 100))
B := Affine2D{}.Scale(Point{}, Pt(2, 2))
_ = A
_ = 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)
if T1 != T2 {
@@ -202,9 +199,9 @@ func TestMulOrder(t *testing.T) {
func BenchmarkTransformOffset(b *testing.B) {
p := Point{X: 1, Y: 2}
o := Point{X: 0.5, Y: 0.5}
aff := AffineId().Offset(o)
aff := Affine2D{}.Offset(o)
for b.Loop() {
for i := 0; i < b.N; i++ {
p = aff.Transform(p)
}
_ = p
@@ -213,8 +210,8 @@ func BenchmarkTransformOffset(b *testing.B) {
func BenchmarkTransformScale(b *testing.B) {
p := Point{X: 1, Y: 2}
s := Point{X: 0.5, Y: 0.5}
aff := AffineId().Scale(Point{}, s)
for b.Loop() {
aff := Affine2D{}.Scale(Point{}, s)
for i := 0; i < b.N; i++ {
p = aff.Transform(p)
}
_ = p
@@ -223,163 +220,36 @@ func BenchmarkTransformScale(b *testing.B) {
func BenchmarkTransformRotate(b *testing.B) {
p := Point{X: 1, Y: 2}
a := float32(math.Pi / 2)
aff := AffineId().Rotate(Point{}, a)
for b.Loop() {
aff := Affine2D{}.Rotate(Point{}, a)
for i := 0; i < b.N; i++ {
p = aff.Transform(p)
}
_ = p
}
func BenchmarkTransformTranslateMultiply(b *testing.B) {
a := AffineId().Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
t := AffineId().Offset(Point{X: 0.5, Y: 0.5})
a := Affine2D{}.Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
t := Affine2D{}.Offset(Point{X: 0.5, Y: 0.5})
for b.Loop() {
for i := 0; i < b.N; i++ {
a = a.Mul(t)
}
}
func BenchmarkTransformScaleMultiply(b *testing.B) {
a := AffineId().Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
t := AffineId().Offset(Point{X: 0.5, Y: 0.5}).Scale(Point{}, Point{X: 0.4, Y: -0.5})
a := Affine2D{}.Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
t := Affine2D{}.Offset(Point{X: 0.5, Y: 0.5}).Scale(Point{}, Point{X: 0.4, Y: -0.5})
for b.Loop() {
for i := 0; i < b.N; i++ {
a = a.Mul(t)
}
}
func BenchmarkTransformMultiply(b *testing.B) {
a := AffineId().Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
t := AffineId().Offset(Point{X: 0.5, Y: 0.5}).Rotate(Point{}, math.Pi/7)
a := Affine2D{}.Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
t := Affine2D{}.Offset(Point{X: 0.5, Y: 0.5}).Rotate(Point{}, math.Pi/7)
for b.Loop() {
for i := 0; i < b.N; i++ {
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": {
"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": {
"locked": {
"lastModified": 1747953325,
"narHash": "sha256-y2ZtlIlNTuVJUZCqzZAhIw5rrKP4DOSklev6c8PyCkQ=",
"lastModified": 1701282334,
"narHash": "sha256-MxCVrXY6v4QmfTwIysjjaX0XUhqBbxTWWB4HXtDYsdk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "55d1f923c480dadce40f5231feb472e81b0bab48",
"rev": "057f9aecfb71c4437d2b27d3323df7f93c010b7e",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.05",
"ref": "23.11",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"utils": "utils"
"android": "android",
"nixpkgs": "nixpkgs"
}
},
"systems": {
@@ -37,21 +99,18 @@
"type": "github"
}
},
"utils": {
"inputs": {
"systems": "systems"
},
"systems_2": {
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
+46 -37
View File
@@ -3,38 +3,42 @@
description = "Gio build environment";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
utils.url = "github:numtide/flake-utils";
nixpkgs.url = "github:NixOS/nixpkgs/23.11";
android.url = "github:tadfisher/android-nixpkgs";
android.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { self, nixpkgs, utils }:
utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {
inherit system;
# allow unfree Android packages.
config.allowUnfree = true;
# accept the Android SDK license.
config.android_sdk.accept_license = true;
};
in {
devShells = let
android-sdk = let
androidComposition = pkgs.androidenv.composeAndroidPackages {
platformVersions = [ "latest" ];
abiVersions = [ "armeabi-v7a" "arm64-v8a" ];
# Omit the deprecated tools package.
toolsVersion = null;
includeNDK = true;
outputs = { self, nixpkgs, android }:
let
supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-darwin" ];
forAllSystems = f: nixpkgs.lib.genAttrs supportedSystems (system: f system);
in
{
devShells = forAllSystems
(system:
let
pkgs = import nixpkgs {
inherit system;
};
in androidComposition.androidsdk;
in {
default = with pkgs;
mkShell (rec {
ANDROID_HOME = "${android-sdk}/libexec/android-sdk";
packages = [ android-sdk jdk clang ]
++ (if stdenv.isLinux then [
android-sdk = android.sdk.${system} (sdkPkgs: with sdkPkgs;
[
build-tools-31-0-0
cmdline-tools-latest
platform-tools
platforms-android-31
ndk-bundle
]);
in
{
default = with pkgs; mkShell
({
ANDROID_SDK_ROOT = "${android-sdk}/share/android-sdk";
JAVA_HOME = jdk17.home;
packages = [
android-sdk
jdk17
clang
] ++ (if stdenv.isLinux then [
vulkan-headers
libxkbcommon
wayland
@@ -43,12 +47,17 @@
xorg.libXfixes
libGL
pkg-config
] else
[ ]);
} // (if stdenv.isLinux then {
LD_LIBRARY_PATH = "${vulkan-loader}/lib";
} else
{ }));
};
});
] else if stdenv.isDarwin then [
darwin.apple_sdk_11_0.frameworks.Foundation
darwin.apple_sdk_11_0.frameworks.Metal
darwin.apple_sdk_11_0.frameworks.QuartzCore
darwin.apple_sdk_11_0.frameworks.AppKit
darwin.apple_sdk_11_0.MacOSX-SDK
] else [ ]);
} // (if stdenv.isLinux then {
LD_LIBRARY_PATH = "${vulkan-loader}/lib";
} else { }));
}
);
};
}
+1 -1
View File
@@ -34,7 +34,7 @@ type Font struct {
// Face is an opaque handle to a typeface. The concrete implementation depends
// upon the kind of font and shaper in use.
type Face interface {
Face() *font.Face
Face() font.Face
}
// Typeface identifies a list of font families to attempt to use for displaying
+43 -41
View File
@@ -16,21 +16,23 @@ import (
_ "image/png"
giofont "gioui.org/font"
fontapi "github.com/go-text/typesetting/font"
"github.com/go-text/typesetting/font/opentype"
"github.com/go-text/typesetting/font"
fontapi "github.com/go-text/typesetting/opentype/api/font"
"github.com/go-text/typesetting/opentype/api/metadata"
"github.com/go-text/typesetting/opentype/loader"
)
// Face is a thread-safe representation of a loaded font. For efficiency, applications
// should construct a face for any given font file once, reusing it across different
// text shapers.
type Face struct {
face *fontapi.Font
face font.Font
font giofont.Font
}
// Parse constructs a Face from source bytes.
func Parse(src []byte) (Face, error) {
ld, err := opentype.NewLoader(bytes.NewReader(src))
ld, err := loader.NewLoader(bytes.NewReader(src))
if err != nil {
return Face{}, err
}
@@ -47,11 +49,11 @@ func Parse(src []byte) (Face, error) {
// ParseCollection parse an Opentype font file, with support for collections.
// Single font files are supported, returning a slice with length 1.
// The returned fonts are automatically wrapped in a text.FontFace with
// inferred font font.
// inferred font metadata.
// BUG(whereswaldon): the only Variant that can be detected automatically is
// "Mono".
func ParseCollection(src []byte) ([]giofont.FontFace, error) {
lds, err := opentype.NewLoaders(bytes.NewReader(src))
lds, err := loader.NewLoaders(bytes.NewReader(src))
if err != nil {
return nil, err
}
@@ -74,7 +76,7 @@ func ParseCollection(src []byte) ([]giofont.FontFace, error) {
return out, nil
}
func DescriptionToFont(md fontapi.Description) giofont.Font {
func DescriptionToFont(md metadata.Description) giofont.Font {
return giofont.Font{
Typeface: giofont.Typeface(md.Family),
Style: gioStyle(md.Aspect.Style),
@@ -82,30 +84,30 @@ func DescriptionToFont(md fontapi.Description) giofont.Font {
}
}
func FontToDescription(font giofont.Font) fontapi.Description {
return fontapi.Description{
func FontToDescription(font giofont.Font) metadata.Description {
return metadata.Description{
Family: string(font.Typeface),
Aspect: fontapi.Aspect{
Aspect: metadata.Aspect{
Style: mdStyle(font.Style),
Weight: mdWeight(font.Weight),
},
}
}
// parseLoader parses the contents of the loader into a face and its font.
func parseLoader(ld *opentype.Loader) (*fontapi.Font, giofont.Font, error) {
// parseLoader parses the contents of the loader into a face and its metadata.
func parseLoader(ld *loader.Loader) (font.Font, giofont.Font, error) {
ft, err := fontapi.NewFont(ld)
if err != nil {
return nil, giofont.Font{}, err
}
data := DescriptionToFont(ft.Describe())
data := DescriptionToFont(metadata.Metadata(ld))
return ft, data, nil
}
// Face returns a thread-unsafe wrapper for this Face suitable for use by a single shaper.
// Face many be invoked any number of times and is safe so long as each return value is
// only used by one goroutine.
func (f Face) Face() *fontapi.Face {
func (f Face) Face() font.Face {
return &fontapi.Face{Font: f.face}
}
@@ -117,74 +119,74 @@ func (f Face) Font() giofont.Font {
return f.font
}
func gioStyle(s fontapi.Style) giofont.Style {
func gioStyle(s metadata.Style) giofont.Style {
switch s {
case fontapi.StyleItalic:
case metadata.StyleItalic:
return giofont.Italic
case fontapi.StyleNormal:
case metadata.StyleNormal:
fallthrough
default:
return giofont.Regular
}
}
func mdStyle(g giofont.Style) fontapi.Style {
func mdStyle(g giofont.Style) metadata.Style {
switch g {
case giofont.Italic:
return fontapi.StyleItalic
return metadata.StyleItalic
case giofont.Regular:
fallthrough
default:
return fontapi.StyleNormal
return metadata.StyleNormal
}
}
func gioWeight(w fontapi.Weight) giofont.Weight {
func gioWeight(w metadata.Weight) giofont.Weight {
switch w {
case fontapi.WeightThin:
case metadata.WeightThin:
return giofont.Thin
case fontapi.WeightExtraLight:
case metadata.WeightExtraLight:
return giofont.ExtraLight
case fontapi.WeightLight:
case metadata.WeightLight:
return giofont.Light
case fontapi.WeightNormal:
case metadata.WeightNormal:
return giofont.Normal
case fontapi.WeightMedium:
case metadata.WeightMedium:
return giofont.Medium
case fontapi.WeightSemibold:
case metadata.WeightSemibold:
return giofont.SemiBold
case fontapi.WeightBold:
case metadata.WeightBold:
return giofont.Bold
case fontapi.WeightExtraBold:
case metadata.WeightExtraBold:
return giofont.ExtraBold
case fontapi.WeightBlack:
case metadata.WeightBlack:
return giofont.Black
default:
return giofont.Normal
}
}
func mdWeight(g giofont.Weight) fontapi.Weight {
func mdWeight(g giofont.Weight) metadata.Weight {
switch g {
case giofont.Thin:
return fontapi.WeightThin
return metadata.WeightThin
case giofont.ExtraLight:
return fontapi.WeightExtraLight
return metadata.WeightExtraLight
case giofont.Light:
return fontapi.WeightLight
return metadata.WeightLight
case giofont.Normal:
return fontapi.WeightNormal
return metadata.WeightNormal
case giofont.Medium:
return fontapi.WeightMedium
return metadata.WeightMedium
case giofont.SemiBold:
return fontapi.WeightSemibold
return metadata.WeightSemibold
case giofont.Bold:
return fontapi.WeightBold
return metadata.WeightBold
case giofont.ExtraBold:
return fontapi.WeightExtraBold
return metadata.WeightExtraBold
case giofont.Black:
return fontapi.WeightBlack
return metadata.WeightBlack
default:
return fontapi.WeightNormal
return metadata.WeightNormal
}
}
+2 -9
View File
@@ -322,8 +322,6 @@ func (s *Scroll) Update(cfg unit.Metric, q input.Source, t time.Time, axis Axis,
s.scroll += e.Scroll.X
case Vertical:
s.scroll += e.Scroll.Y
case Both:
s.scroll += e.Scroll.X + e.Scroll.Y
}
iscroll := int(s.scroll)
s.scroll -= float32(iscroll)
@@ -355,15 +353,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 {
switch axis {
case Horizontal:
if axis == Horizontal {
return p.X
case Vertical:
} else {
return p.Y
case Both:
return p.X + p.Y
default:
return 0
}
}
+8 -8
View File
@@ -1,16 +1,16 @@
module gioui.org
go 1.24.0
go 1.21
require (
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2
gioui.org/shader v1.0.8
github.com/go-text/typesetting v0.3.0
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0
golang.org/x/image v0.26.0
golang.org/x/sys v0.39.0
golang.org/x/text v0.32.0
github.com/go-text/typesetting v0.1.1
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91
golang.org/x/image v0.5.0
golang.org/x/sys v0.5.0
)
require golang.org/x/net v0.48.0
require golang.org/x/text v0.9.0
+40 -16
View File
@@ -1,21 +1,45 @@
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc=
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA=
gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
github.com/go-text/typesetting v0.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML4iF4=
github.com/go-text/typesetting v0.3.0/go.mod h1:qjZLkhRgOEYMhU9eHBr3AR4sfnGJvOXNLt8yRAySFuY=
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 h1:tMSqXTK+AQdW3LpCbfatHSRPHeW6+2WuxaVQuHftn80=
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8=
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
github.com/go-text/typesetting v0.1.1 h1:bGAesCuo85nXnEN5LmFMVGAGpGkCPtHrZLi//qD7EJo=
github.com/go-text/typesetting v0.1.1/go.mod h1:d22AnmeKq/on0HNv73UFriMKc4Ez6EqZAofLhAzpSzI=
github.com/go-text/typesetting-utils v0.0.0-20231211103740-d9332ae51f04 h1:zBx+p/W2aQYtNuyZNcTfinWvXBQwYtDfme051PR/lAY=
github.com/go-text/typesetting-utils v0.0.0-20231211103740-d9332ae51f04/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 h1:sBdrWpxhGDdTAYNqbgBLAR+ULAPPhfgncLr1X0lyWtg=
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 h1:ryT6Nf0R83ZgD8WnFFdfI8wCeyqgdXWN4+CkFVNPAT0=
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8=
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/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/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
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=
+6 -6
View File
@@ -19,10 +19,10 @@ type quadSplitter struct {
func encodeQuadTo(data []byte, meta uint32, from, ctrl, to f32.Point) {
// inlined code:
// encodeVertex(data, meta, 1, -1, from, ctrl, to)
// encodeVertex(data, meta, -1, 1, from, ctrl, to)
// encodeVertex(data[vertStride:], meta, 1, 1, from, ctrl, to)
// encodeVertex(data[vertStride*2:], meta, -1, -1, from, ctrl, to)
// encodeVertex(data[vertStride*3:], meta, -1, 1, from, ctrl, to)
// encodeVertex(data[vertStride*3:], meta, 1, -1, from, ctrl, to)
// this code needs to stay in sync with `vertex.encode`.
bo := binary.LittleEndian
@@ -48,10 +48,10 @@ func encodeQuadTo(data []byte, meta uint32, from, ctrl, to f32.Point) {
}
const (
nwCorner = 1*0.5 + 0*0.25
neCorner = 1*0.5 + 1*0.25
swCorner = 0*0.5 + 0*0.25
seCorner = 0*0.5 + 1*0.25
nwCorner = 1*0.25 + 0*0.5
neCorner = 1*0.25 + 1*0.5
swCorner = 0*0.25 + 0*0.5
seCorner = 0*0.25 + 1*0.5
)
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) {
var data [vertStride * 4]byte
for i := 0; b.Loop(); i++ {
for i := 0; i < b.N; i++ {
v := float32(i)
encodeQuadTo(data[:], 123,
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)
}
+33 -55
View File
@@ -9,13 +9,12 @@ package gpu
import (
"encoding/binary"
"errors"
"fmt"
"image"
"image/color"
"math"
"os"
"reflect"
"slices"
"time"
"unsafe"
@@ -190,7 +189,7 @@ const (
// imageOpData is the shadow of paint.ImageOp.
type imageOpData struct {
src *image.RGBA
handle any
handle interface{}
filter byte
}
@@ -201,7 +200,7 @@ type linearGradientOpData struct {
color2 color.NRGBA
}
func decodeImageOp(data []byte, refs []any) imageOpData {
func decodeImageOp(data []byte, refs []interface{}) imageOpData {
handle := refs[1]
if handle == nil {
return imageOpData{}
@@ -344,12 +343,13 @@ func New(api API) (GPU, error) {
func NewWithDevice(d driver.Device) (GPU, error) {
d.BeginFrame(nil, false, image.Point{})
defer d.EndFrame()
forceCompute := os.Getenv("GIORENDERER") == "forcecompute"
feats := d.Caps().Features
switch {
case feats.Has(driver.FeatureFloatRenderTargets) && feats.Has(driver.FeatureSRGB):
case !forceCompute && feats.Has(driver.FeatureFloatRenderTargets) && feats.Has(driver.FeatureSRGB):
return newGPU(d)
}
return nil, errors.New("no available GPU driver")
return newCompute(d)
}
func newGPU(ctx driver.Device) (*gpu, error) {
@@ -548,7 +548,7 @@ func newBlitter(ctx driver.Device) *blitter {
b.texUniforms = new(blitTexUniforms)
b.linearGradientUniforms = new(blitLinearGradientUniforms)
pipelines, err := createColorPrograms(ctx, gio.Shader_blit_vert, gio.Shader_blit_frag,
[3]any{b.colUniforms, b.linearGradientUniforms, b.texUniforms},
[3]interface{}{b.colUniforms, b.linearGradientUniforms, b.texUniforms},
)
if err != nil {
panic(err)
@@ -566,7 +566,7 @@ func (b *blitter) 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{}) (pipelines [2][3]*pipeline, err error) {
defer func() {
if err != nil {
for _, p := range pipelines {
@@ -823,7 +823,7 @@ func (r *renderer) packLayers(layers []opacityLayer) []opacityLayer {
layers[l.parent].clip = b.Union(l.clip)
}
if l.clip.Empty() {
layers = slices.Delete(layers, i, i+1)
layers = append(layers[:i], layers[i+1:]...)
}
}
// Pack layers.
@@ -872,7 +872,7 @@ func (r *renderer) drawLayers(layers []opacityLayer, ops []imageOp) {
r.drawOps(true, l.clip.Min.Mul(-1), l.clip.Size(), ops[l.opStart:l.opEnd])
sr := f32.FRect(v)
uvScale, uvOffset := texSpaceTransform(sr, f.size)
uvTrans := f32.AffineId().Scale(f32.Point{}, uvScale).Offset(uvOffset)
uvTrans := f32.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset)
// Replace layer ops with one textured op.
ops[l.opStart] = imageOp{
clip: l.clip,
@@ -957,9 +957,7 @@ func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey opKey, bounds
func (d *drawOps) save(id int, state f32.Affine2D) {
if extra := id - len(d.states) + 1; extra > 0 {
for range extra {
d.states = append(d.states, f32.AffineId())
}
d.states = append(d.states, make([]f32.Affine2D, extra)...)
}
d.states[id] = state
}
@@ -974,13 +972,12 @@ func (k opKey) SetTransform(t f32.Affine2D) opKey {
}
func (d *drawOps) collectOps(r *ops.Reader, viewport f32.Rectangle) {
var quads quadsOp
state := drawState{
t: f32.AffineId(),
}
var (
quads quadsOp
state drawState
)
reset := func() {
state = drawState{
t: f32.AffineId(),
color: color.NRGBA{A: 0xff},
}
}
@@ -1036,7 +1033,7 @@ loop:
op.Decode(encOp.Data)
quads.key.outline = op.Outline
bounds := f32.FRect(op.Bounds)
trans, off := transformOffset(state.t)
trans, off := state.t.Split()
if len(quads.aux) > 0 {
// There is a clipping path, build the gpu data and update the
// cache key such that it will be equal only if the transform is the
@@ -1059,7 +1056,6 @@ loop:
} else {
quads.aux, bounds, _ = d.boundsForTransformedRect(bounds, trans)
quads.key = opKey{Key: encOp.Key}
quads.key = quads.key.SetTransform(trans)
}
d.addClipPath(&state, quads.aux, quads.key, bounds, off)
quads = quadsOp{}
@@ -1083,7 +1079,7 @@ loop:
// Transform (if needed) the painting rectangle and if so generate a clip path,
// for those cases also compute a partialTrans that maps texture coordinates between
// the new bounding rectangle and the transformed original paint rectangle.
t, off := transformOffset(state.t)
t, off := state.t.Split()
// Fill the clip area, unless the material is a (bounded) image.
// TODO: Find a tighter bound.
inf := float32(1e6)
@@ -1105,7 +1101,7 @@ loop:
// The paint operation is sheared or rotated, add a clip path representing
// this transformed rectangle.
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)
}
@@ -1166,7 +1162,6 @@ func expandPathOp(p *pathOp, clip image.Rectangle) {
func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32.Affine2D, clip image.Rectangle) material {
m := material{
opacity: 1.,
uvTrans: f32.AffineId(),
}
switch d.matType {
case materialColor:
@@ -1200,7 +1195,7 @@ func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32
sr.Min.Y += float32(clip.Min.Y-dr.Min.Y) * sdy / dy
sr.Max.Y -= float32(dr.Max.Y-clip.Max.Y) * sdy / dy
uvScale, uvOffset := texSpaceTransform(sr, sz)
m.uvTrans = partTrans.Mul(f32.AffineId().Scale(f32.Point{}, uvScale).Offset(uvOffset))
m.uvTrans = partTrans.Mul(f32.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset))
m.data = d.image
}
return m
@@ -1321,7 +1316,7 @@ func (b *blitter) blit(mat materialType, fbo bool, col f32color.RGBA, col1, col2
// newUniformBuffer creates a new GPU uniform buffer backed by the
// structure uniformBlock points to.
func newUniformBuffer(b driver.Device, uniformBlock any) *uniformBuffer {
func newUniformBuffer(b driver.Device, uniformBlock interface{}) *uniformBuffer {
ref := reflect.ValueOf(uniformBlock)
// Determine the size of the uniforms structure, *uniforms.
size := ref.Elem().Type().Size()
@@ -1375,7 +1370,7 @@ func gradientSpaceTransform(clip image.Rectangle, off f32.Point, stop1, stop2 f3
// TODO: optimize
zp := f32.Point{}
return f32.AffineId().
return f32.Affine2D{}.
Scale(zp, layout.FPt(clip.Size())). // scale to pixel space
Offset(zp.Sub(off).Add(layout.FPt(clip.Min))). // offset to clip space
Offset(zp.Sub(stop1)). // offset to first stop point
@@ -1489,7 +1484,7 @@ func (d *drawOps) buildVerts(pathData []byte, tr f32.Affine2D, outline bool, str
// as needed and feeds them to the supplied splitter.
func decodeToOutlineQuads(qs *quadSplitter, tr f32.Affine2D, pathData []byte) {
for len(pathData) >= scene.CommandSize+4 {
qs.contour = binary.LittleEndian.Uint32(pathData)
qs.contour = bo.Uint32(pathData)
cmd := ops.DecodeCommand(pathData[4:])
switch cmd.Op() {
case scene.OpLine:
@@ -1525,10 +1520,12 @@ func decodeToOutlineQuads(qs *quadSplitter, tr f32.Affine2D, pathData []byte) {
// create GPU vertices for transformed r, find the bounds and establish texture transform.
func (d *drawOps) boundsForTransformedRect(r f32.Rectangle, tr f32.Affine2D) (aux []byte, bnd f32.Rectangle, ptr f32.Affine2D) {
ptr = f32.AffineId()
if tr == f32.AffineId() {
// fast-path to allow blitting of pure rectangles.
bnd = r
if isPureOffset(tr) {
// fast-path to allow blitting of pure rectangles
_, _, ox, _, _, oy := tr.Elems()
off := f32.Pt(ox, oy)
bnd.Min = r.Min.Add(off)
bnd.Max = r.Max.Add(off)
return
}
@@ -1575,29 +1572,10 @@ func (d *drawOps) boundsForTransformedRect(r f32.Rectangle, tr f32.Affine2D) (au
sx, sy := P2.X-P3.X, P2.Y-P3.Y
ptr = f32.NewAffine2D(sx, P2.X-P1.X, P1.X-sx, sy, P2.Y-P1.Y, P1.Y-sy).Invert()
return 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, f32.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 := f32.Pt(float32(iox), float32(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
}
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 (
clearCol = color.NRGBA{A: 0xff, R: 0xde, G: 0xad, B: 0xbe}
clearColExpect = f32color.NRGBAToRGBA(clearCol)
)
var clearCol = color.NRGBA{A: 0xff, R: 0xde, G: 0xad, B: 0xbe}
var clearColExpect = f32color.NRGBAToRGBA(clearCol)
func TestFramebufferClear(t *testing.T) {
b := newDriver(t)
@@ -204,5 +202,5 @@ func saveImage(file string, img image.Image) error {
if err := png.Encode(&buf, img); err != nil {
return err
}
return os.WriteFile(file, buf.Bytes(), 0o666)
return os.WriteFile(file, buf.Bytes(), 0666)
}
+1
View File
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build linux || freebsd || openbsd
// +build linux freebsd openbsd
package headless
+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 {
var renderTarget *d3d11.RenderTargetView
var (
renderTarget *d3d11.RenderTargetView
)
if target != nil {
switch t := target.(type) {
case driver.Direct3D11RenderTarget:
@@ -227,7 +229,10 @@ func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, min
// Flags required by ID3D11DeviceContext::GenerateMips.
bindFlags |= d3d11.BIND_SHADER_RESOURCE | d3d11.BIND_RENDER_TARGET
miscFlags |= d3d11.RESOURCE_MISC_GENERATE_MIPS
dim := max(height, width)
dim := width
if height > dim {
dim = height
}
log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1
nmipmaps = log2 + 1
}
@@ -797,7 +802,7 @@ func (t *Texture) ReadPixels(src image.Rectangle, pixels []byte, stride int) err
mapSize := dstPitch * h
data := sliceOf(resMap.PData, mapSize)
width := w * 4
for r := range h {
for r := 0; r < h; r++ {
pixels := pixels[r*srcPitch:]
copy(pixels[:width], data[r*dstPitch:])
}
+3 -5
View File
@@ -96,10 +96,8 @@ type BlendFactor uint8
type Topology uint8
type (
TextureFilter uint8
TextureFormat uint8
)
type TextureFilter uint8
type TextureFormat 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
// left corner.
row := make([]uint8, stride)
for y := range height / 2 {
for y := 0; y < height/2; y++ {
y1 := height - y - 1
dest := y1 * stride
src := y * stride
+12 -5
View File
@@ -8,7 +8,6 @@ import (
"image"
"math/bits"
"runtime"
"slices"
"strings"
"time"
"unsafe"
@@ -718,7 +717,10 @@ func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, min
if mipmap {
nmipmaps := 1
if mipmap {
dim := max(height, width)
dim := width
if height > dim {
dim = height
}
log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1
nmipmaps = log2 + 1
}
@@ -1130,7 +1132,7 @@ func (b *Backend) setupVertexArrays() {
enabled[inp.Location] = true
b.glstate.vertexAttribPointer(b.funcs, buf.obj, inp.Location, l.Size, gltyp, false, p.layout.Stride, buf.offset+l.Offset)
}
for i := range max {
for i := 0; i < max; 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 {
tmp := make([]byte, w*h*4)
t.backend.funcs.ReadPixels(src.Min.X, src.Min.Y, w, h, gl.RGBA, gl.UNSIGNED_BYTE, tmp)
for y := range h {
for y := 0; y < h; y++ {
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 {
return slices.Contains(exts, ext)
for _, e := range exts {
if ext == e {
return true
}
}
return false
}
func firstBufferType(typ driver.BufferBinding) gl.Enum {
+21 -21
View File
@@ -66,8 +66,8 @@ func BenchmarkDrawUICached(b *testing.B) {
defer w.Release()
drawCore(gtx, th)
w.Frame(gtx.Ops)
for b.Loop() {
b.ResetTimer()
for i := 0; i < b.N; i++ {
w.Frame(gtx.Ops)
}
finishBenchmark(b, w)
@@ -83,12 +83,12 @@ func BenchmarkDrawUI(b *testing.B) {
drawCore(gtx, th)
w.Frame(gtx.Ops)
b.ReportAllocs()
for i := 0; b.Loop(); i++ {
b.ResetTimer()
for i := 0; i < b.N; i++ {
resetOps(gtx)
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)
@@ -105,12 +105,12 @@ func BenchmarkDrawUITransformed(b *testing.B) {
drawCore(gtx, th)
w.Frame(gtx.Ops)
b.ReportAllocs()
for i := 0; b.Loop(); i++ {
b.ResetTimer()
for i := 0; i < b.N; i++ {
resetOps(gtx)
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)
drawCore(gtx, th)
@@ -130,8 +130,8 @@ func Benchmark1000Circles(b *testing.B) {
draw1000Circles(gtx)
w.Frame(gtx.Ops)
b.ReportAllocs()
for b.Loop() {
b.ResetTimer()
for i := 0; i < b.N; i++ {
resetOps(gtx)
draw1000Circles(gtx)
w.Frame(gtx.Ops)
@@ -147,8 +147,8 @@ func Benchmark1000CirclesInstanced(b *testing.B) {
draw1000CirclesInstanced(gtx)
w.Frame(gtx.Ops)
b.ReportAllocs()
for b.Loop() {
b.ResetTimer()
for i := 0; i < b.N; i++ {
resetOps(gtx)
draw1000CirclesInstanced(gtx)
w.Frame(gtx.Ops)
@@ -158,9 +158,9 @@ func Benchmark1000CirclesInstanced(b *testing.B) {
func draw1000Circles(gtx layout.Context) {
ops := gtx.Ops
for x := range 100 {
for x := 0; x < 100; x++ {
op.Offset(image.Pt(x*10, 0)).Add(ops)
for y := range 10 {
for y := 0; y < 10; y++ {
paint.FillShape(ops,
color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120},
clip.RRect{Rect: image.Rect(0, 0, 10, 10), NE: 5, SE: 5, SW: 5, NW: 5}.Op(ops),
@@ -179,9 +179,9 @@ func draw1000CirclesInstanced(gtx layout.Context) {
cl.Pop()
c := r.Stop()
for x := range 100 {
for x := 0; x < 100; x++ {
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)
c.Add(ops)
op.Offset(image.Pt(0, 100)).Add(ops)
@@ -204,9 +204,9 @@ func drawIndividualShapes(gtx layout.Context, th *material.Theme) chan op.CallOp
go func() {
ops := &op1
c := op.Record(ops)
for x := range 9 {
for x := 0; x < 9; x++ {
op.Offset(image.Pt(x*50, 0)).Add(ops)
for y := range 9 {
for y := 0; y < 9; y++ {
paint.FillShape(ops,
color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120},
clip.RRect{Rect: image.Rect(0, 0, 25, 25), NE: 10, SE: 10, SW: 10, NW: 10}.Op(ops),
@@ -233,8 +233,8 @@ func drawShapeInstances(gtx layout.Context, th *material.Theme) chan op.CallOp {
squares.Add(ops)
rad := float32(0)
for x := range 20 {
for y := range 20 {
for x := 0; x < 20; x++ {
for y := 0; y < 20; y++ {
t := op.Offset(image.Pt(x*50+25, y*50+25)).Push(ops)
c.Add(ops)
t.Pop()
@@ -253,7 +253,7 @@ func drawText(gtx layout.Context, th *material.Theme) chan op.CallOp {
c := op.Record(ops)
txt := material.H6(th, "")
for x := range 40 {
for x := 0; x < 40; x++ {
txt.Text = textRows[x]
t := op.Offset(image.Pt(0, 24*x)).Push(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) {
run(t, func(o *op.Ops) {
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 op.Offset(image.Pt(-10, -10)).Push(o).Pop()
paint.PaintOp{}.Add(o)
}, nil)
}, func(r result) {
})
}
func TestTexturedStroke(t *testing.T) {
@@ -164,7 +146,8 @@ func TestTexturedStroke(t *testing.T) {
}.Op().Push(o).Pop()
defer op.Offset(image.Pt(-10, -10)).Push(o).Pop()
paint.PaintOp{}.Add(o)
}, nil)
}, func(r result) {
})
}
func TestPaintClippedTexture(t *testing.T) {
@@ -195,6 +178,7 @@ func TestStrokedPathZeroWidth(t *testing.T) {
paint.Fill(o, red)
cl.Pop()
}
}, func(r result) {
r.expect(0, 0, transparent)
r.expect(10, 50, colornames.Black)
@@ -268,7 +252,8 @@ func TestPathReuse(t *testing.T) {
stroke := clip.Stroke{Path: spec, Width: 3}.Op().Push(o)
paint.Fill(o, color.NRGBA{B: 0xFF, A: 0xFF})
stroke.Pop()
}, nil)
}, func(r result) {
})
}
func TestPathInterleave(t *testing.T) {
@@ -305,22 +290,6 @@ func TestStrokedRect(t *testing.T) {
Width: 5,
}.Op(),
)
}, nil)
}
func TestInstancedRects(t *testing.T) {
run(t, func(o *op.Ops) {
macro := op.Record(o)
clip := clip.Rect{Max: image.Pt(20, 20)}.Push(o)
paint.ColorOp{Color: color.NRGBA{R: 0x80, A: 0xFF}}.Add(o)
paint.PaintOp{}.Add(o)
clip.Pop()
c := macro.Stop()
for range 2 {
op.Affine(f32.AffineId().Rotate(f32.Pt(0, 0), .2)).Add(o)
c.Add(o)
op.Offset(image.Pt(20, 20)).Add(o)
}
}, nil)
}, func(r result) {
})
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 787 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 435 B

+15 -14
View File
@@ -24,6 +24,7 @@ func TestTransformMacro(t *testing.T) {
c := constSqPath()
run(t, func(o *op.Ops) {
// render the first Stacked item
m1 := op.Record(o)
dr := image.Rect(0, 0, 128, 50)
@@ -87,10 +88,10 @@ func TestNoClipFromPaint(t *testing.T) {
// ensure that a paint operation does not pollute the state
// by leaving any clip paths in place.
run(t, func(o *op.Ops) {
a := f32.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()
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()
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)
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)
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)
@@ -119,11 +120,12 @@ func TestDeferredPaint(t *testing.T) {
op.Defer(o, paintMacro)
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()
paint.ColorOp{Color: color.NRGBA{A: 0x60, B: 0xff}}.Add(o)
paint.PaintOp{}.Add(o)
}, nil)
}, func(r result) {
})
}
func constSqPath() clip.Op {
@@ -141,10 +143,8 @@ func constSqPath() clip.Op {
func constSqCirc() clip.Op {
innerOps := new(op.Ops)
return clip.RRect{
Rect: image.Rect(0, 0, 40, 40),
NW: 20, NE: 20, SW: 20, SE: 20,
}.Op(innerOps)
return clip.RRect{Rect: image.Rect(0, 0, 40, 40),
NW: 20, NE: 20, SW: 20, SE: 20}.Op(innerOps)
}
func drawChild(ops *op.Ops, text clip.Op) op.CallOp {
@@ -260,7 +260,7 @@ func TestLinearGradient(t *testing.T) {
Color2: g.To,
}.Add(ops)
cl := clip.RRect{Rect: gr.Round()}.Push(ops)
t1 := op.Affine(f32.AffineId().Offset(pixelAligned.Min)).Push(ops)
t1 := op.Affine(f32.Affine2D{}.Offset(pixelAligned.Min)).Push(ops)
t2 := scale(pixelAligned.Dx()/128, 1).Push(ops)
paint.PaintOp{}.Add(ops)
t2.Pop()
@@ -323,7 +323,7 @@ func TestLinearGradientAngled(t *testing.T) {
cl = clip.Rect(image.Rect(0, 64, 64, 128)).Push(ops)
paint.PaintOp{}.Add(ops)
cl.Pop()
}, nil)
}, func(r result) {})
}
func TestZeroImage(t *testing.T) {
@@ -363,7 +363,7 @@ func TestImageRGBA_ScaleLinear(t *testing.T) {
run(t, func(o *op.Ops) {
w := newWindow(t, 128, 128)
defer clip.Rect{Max: image.Pt(128, 128)}.Push(o).Pop()
op.Affine(f32.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.Set(0, 0, colornames.Red)
@@ -397,7 +397,7 @@ func TestImageRGBA_ScaleLinear(t *testing.T) {
func TestImageRGBA_ScaleNearest(t *testing.T) {
run(t, func(o *op.Ops) {
w := newWindow(t, 128, 128)
op.Affine(f32.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.Set(0, 0, colornames.Red)
@@ -492,7 +492,8 @@ func TestOpacity(t *testing.T) {
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))
opc3.Pop()
}, nil)
}, func(r result) {
})
}
// 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) {
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()
paint.FillShape(o, red, clip.Rect(image.Rect(20, 20, 60, 60)).Op())
}, func(r result) {
@@ -42,7 +42,7 @@ func TestPaintRotate(t *testing.T) {
func TestPaintShear(t *testing.T) {
run(t, func(o *op.Ops) {
a := f32.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()
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 40, 40)).Op())
}, func(r result) {
@@ -79,7 +79,7 @@ func TestClipOffset(t *testing.T) {
func TestClipScale(t *testing.T) {
run(t, func(o *op.Ops) {
a := f32.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 clip.RRect{Rect: image.Rect(10, 10, 20, 20)}.Push(o).Pop()
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 1000, 1000)).Op())
@@ -93,7 +93,7 @@ func TestClipScale(t *testing.T) {
func TestClipRotate(t *testing.T) {
run(t, func(o *op.Ops) {
defer op.Affine(f32.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()
paint.FillShape(o, red, clip.Rect(image.Rect(0, 40, 100, 100)).Op())
}, func(r result) {
@@ -121,7 +121,7 @@ func TestOffsetScaleTexture(t *testing.T) {
run(t, func(o *op.Ops) {
defer op.Offset(image.Pt(15, 15)).Push(o).Pop()
squares.Add(o)
defer op.Affine(f32.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()
paint.PaintOp{}.Add(o)
}, func(r result) {
@@ -133,7 +133,7 @@ func TestOffsetScaleTexture(t *testing.T) {
func TestRotateTexture(t *testing.T) {
run(t, func(o *op.Ops) {
squares.Add(o)
a := f32.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 scale(20.0/512, 20.0/512).Push(o).Pop()
paint.PaintOp{}.Add(o)
@@ -146,10 +146,10 @@ func TestRotateTexture(t *testing.T) {
func TestRotateClipTexture(t *testing.T) {
run(t, func(o *op.Ops) {
squares.Add(o)
a := f32.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 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()
paint.PaintOp{}.Add(o)
}, func(r result) {
@@ -168,7 +168,7 @@ func TestComplicatedTransform(t *testing.T) {
defer clip.RRect{Rect: image.Rect(0, 0, 100, 100), SE: 50, SW: 50, NW: 50, NE: 50}.Push(o).Pop()
a := f32.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 clip.RRect{Rect: image.Rect(0, 0, 50, 40)}.Push(o).Pop()
@@ -182,13 +182,13 @@ func TestComplicatedTransform(t *testing.T) {
func TestTransformOrder(t *testing.T) {
// check the ordering of operations bot in affine and in gpu stack.
run(t, func(o *op.Ops) {
a := f32.AffineId().Offset(f32.Pt(64, 64))
a := f32.Affine2D{}.Offset(f32.Pt(64, 64))
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()
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()
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 20, 20)).Op())
}, func(r result) {
+7 -9
View File
@@ -49,8 +49,8 @@ func buildSquares(size int) paint.ImageOp {
sub := size / 4
im := image.NewNRGBA(image.Rect(0, 0, size, size))
c1, c2 := image.NewUniform(colornames.Green), image.NewUniform(colornames.Blue)
for r := range 4 {
for c := range 4 {
for r := 0; r < 4; r++ {
for c := 0; c < 4; c++ {
c1, c2 = c2, c1
draw.Draw(im, image.Rect(r*sub, c*sub, r*sub+sub, c*sub+sub), c1, image.Point{}, draw.Over)
}
@@ -78,7 +78,7 @@ func run(t *testing.T, f func(o *op.Ops), c func(r result)) {
var img *image.RGBA
var err error
ops := new(op.Ops)
for i := range 3 {
for i := 0; i < 3; i++ {
ops.Reset()
img, err = drawImage(t, 128, ops, f)
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)
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")
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) {
t.Error(err)
return
@@ -287,7 +285,7 @@ func saveImage(t testing.TB, file string, img *image.RGBA) {
t.Error(err)
return
}
if err := os.WriteFile(file, buf.Bytes(), 0o666); err != nil {
if err := os.WriteFile(file, buf.Bytes(), 0666); err != nil {
t.Error(err)
return
}
@@ -302,5 +300,5 @@ func newWindow(t testing.TB, width, height int) *headless.Window {
}
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)
cmdBuf := b.currentCmdBuf()
for _, s := range b.pipe.pushRanges {
off := vk.PushConstantRangeOffset(s)
vk.CmdPushConstants(cmdBuf, b.pipe.desc.layout, vk.PushConstantRangeStageFlags(s), off, buf.store[off:off+vk.PushConstantRangeSize(s)])
off := s.Offset()
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) {
var p packer
p.maxDims = image.Point{X: 4096, Y: 4096}
for i := 0; b.Loop(); i++ {
for i := 0; i < b.N; i++ {
p.clear()
p.newPage()
for k := range 500 {
for k := 0; k < 500; k++ {
_, ok := p.tryAdd(xy(k))
if !ok {
b.Fatal("add failed", i, k, xy(k))
+2 -2
View File
@@ -150,7 +150,7 @@ func newCoverer(ctx driver.Device) *coverer {
c.texUniforms = new(coverTexUniforms)
c.linearGradientUniforms = new(coverLinearGradientUniforms)
pipelines, err := createColorPrograms(ctx, gio.Shader_cover_vert, gio.Shader_cover_frag,
[3]any{c.colUniforms, c.linearGradientUniforms, c.texUniforms},
[3]interface{}{c.colUniforms, c.linearGradientUniforms, c.texUniforms},
)
if err != nil {
panic(err)
@@ -162,7 +162,7 @@ func newCoverer(ctx driver.Device) *coverer {
func newStenciler(ctx driver.Device) *stenciler {
// Allocate a suitably large index buffer for drawing paths.
indices := make([]uint16, pathBatchSize*6)
for i := range pathBatchSize {
for i := 0; i < pathBatchSize; i++ {
i := uint16(i)
indices[i*6+0] = i*4 + 0
indices[i*6+1] = i*4 + 1
+2 -2
View File
@@ -10,7 +10,7 @@ import (
)
// Struct returns a byte slice view of a struct.
func Struct(s any) []byte {
func Struct(s interface{}) []byte {
v := reflect.ValueOf(s)
sz := int(v.Elem().Type().Size())
return unsafe.Slice((*byte)(unsafe.Pointer(v.Pointer())), sz)
@@ -27,7 +27,7 @@ func Uint32(s []uint32) []byte {
}
// Slice returns a byte slice view of a slice.
func Slice(s any) []byte {
func Slice(s interface{}) []byte {
v := reflect.ValueOf(s)
first := v.Index(0)
sz := int(first.Type().Size())
+1 -1
View File
@@ -34,7 +34,7 @@ func Parse() {
}
print := false
silent := false
for part := range strings.SplitSeq(val, ",") {
for _, part := range strings.Split(val, ",") {
switch part {
case textSubsystem:
Text.Store(true)
+6 -2
View File
@@ -9,7 +9,6 @@ import (
"errors"
"fmt"
"runtime"
"slices"
"strings"
"gioui.org/gpu"
@@ -155,7 +154,12 @@ func (c *Context) EnableVSync(enable 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) {
+27 -49
View File
@@ -9,6 +9,8 @@ import (
"unsafe"
syscall "golang.org/x/sys/windows"
"gioui.org/internal/gl"
)
type (
@@ -22,23 +24,23 @@ type (
)
var (
libEGL = syscall.DLL{}
_eglChooseConfig *syscall.Proc
_eglCreateContext *syscall.Proc
_eglCreateWindowSurface *syscall.Proc
_eglDestroyContext *syscall.Proc
_eglDestroySurface *syscall.Proc
_eglGetConfigAttrib *syscall.Proc
_eglGetDisplay *syscall.Proc
_eglGetError *syscall.Proc
_eglInitialize *syscall.Proc
_eglMakeCurrent *syscall.Proc
_eglReleaseThread *syscall.Proc
_eglSwapInterval *syscall.Proc
_eglSwapBuffers *syscall.Proc
_eglTerminate *syscall.Proc
_eglQueryString *syscall.Proc
_eglWaitClient *syscall.Proc
libEGL = syscall.NewLazyDLL("libEGL.dll")
_eglChooseConfig = libEGL.NewProc("eglChooseConfig")
_eglCreateContext = libEGL.NewProc("eglCreateContext")
_eglCreateWindowSurface = libEGL.NewProc("eglCreateWindowSurface")
_eglDestroyContext = libEGL.NewProc("eglDestroyContext")
_eglDestroySurface = libEGL.NewProc("eglDestroySurface")
_eglGetConfigAttrib = libEGL.NewProc("eglGetConfigAttrib")
_eglGetDisplay = libEGL.NewProc("eglGetDisplay")
_eglGetError = libEGL.NewProc("eglGetError")
_eglInitialize = libEGL.NewProc("eglInitialize")
_eglMakeCurrent = libEGL.NewProc("eglMakeCurrent")
_eglReleaseThread = libEGL.NewProc("eglReleaseThread")
_eglSwapInterval = libEGL.NewProc("eglSwapInterval")
_eglSwapBuffers = libEGL.NewProc("eglSwapBuffers")
_eglTerminate = libEGL.NewProc("eglTerminate")
_eglQueryString = libEGL.NewProc("eglQueryString")
_eglWaitClient = libEGL.NewProc("eglWaitClient")
)
var loadOnce sync.Once
@@ -52,45 +54,21 @@ func loadEGL() error {
}
func loadDLLs() error {
if err := loadDLL(&libEGL, "libEGL.dll"); err != nil {
if err := loadDLL(libEGL, "libEGL.dll"); err != nil {
return err
}
procs := map[string]**syscall.Proc{
"eglChooseConfig": &_eglChooseConfig,
"eglCreateContext": &_eglCreateContext,
"eglCreateWindowSurface": &_eglCreateWindowSurface,
"eglDestroyContext": &_eglDestroyContext,
"eglDestroySurface": &_eglDestroySurface,
"eglGetConfigAttrib": &_eglGetConfigAttrib,
"eglGetDisplay": &_eglGetDisplay,
"eglGetError": &_eglGetError,
"eglInitialize": &_eglInitialize,
"eglMakeCurrent": &_eglMakeCurrent,
"eglReleaseThread": &_eglReleaseThread,
"eglSwapInterval": &_eglSwapInterval,
"eglSwapBuffers": &_eglSwapBuffers,
"eglTerminate": &_eglTerminate,
"eglQueryString": &_eglQueryString,
"eglWaitClient": &_eglWaitClient,
if err := loadDLL(gl.LibGLESv2, "libGLESv2.dll"); err != nil {
return err
}
for name, proc := range procs {
p, err := libEGL.FindProc(name)
if err != nil {
return fmt.Errorf("failed to locate %s in %s: %w", name, libEGL.Name, err)
}
*proc = p
}
return nil
// d3dcompiler_47.dll is needed internally for shader compilation to function.
return loadDLL(syscall.NewLazyDLL("d3dcompiler_47.dll"), "d3dcompiler_47.dll")
}
func loadDLL(dll *syscall.DLL, name string) error {
handle, err := syscall.LoadLibraryEx(name, 0, syscall.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)
func loadDLL(dll *syscall.LazyDLL, name string) error {
err := dll.Load()
if err != nil {
return fmt.Errorf("egl: failed to load %s: %v", name, err)
}
dll.Handle = handle
dll.Name = name
return nil
}
@@ -186,6 +164,6 @@ func eglWaitClient() bool {
// issue34474KeepAlive calls runtime.KeepAlive as a
// workaround for golang.org/issue/34474.
func issue34474KeepAlive(v any) {
func issue34474KeepAlive(v interface{}) {
runtime.KeepAlive(v)
}
-2
View File
@@ -19,8 +19,6 @@ type Affine2D = f32.Affine2D
var NewAffine2D = f32.NewAffine2D
var AffineId = f32.AffineId
// A Rectangle contains the points (X, Y) where Min.X <= X < Max.X,
// Min.Y <= Y < Max.Y.
type Rectangle struct {
+2 -2
View File
@@ -16,7 +16,7 @@ func main() {
flag.Parse()
var b bytes.Buffer
printf := func(content string, args ...any) {
printf := func(content string, args ...interface{}) {
fmt.Fprintf(&b, content, args...)
}
@@ -42,7 +42,7 @@ func main() {
panic(err)
}
err = os.WriteFile(*out, data, 0o755)
err = os.WriteFile(*out, data, 0755)
if err != nil {
panic(err)
}
+3 -3
View File
@@ -41,17 +41,17 @@ var sink RGBA
func BenchmarkLinearFromSRGB(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})
}
})
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})
}
})
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})
}
})
+15 -15
View File
@@ -90,7 +90,7 @@ func (e *Extrapolation) Estimate() Estimate {
first := e.get(0)
t := first.t
// Walk backwards collecting samples.
for i := range e.samples {
for i := 0; i < len(e.samples); i++ {
p := e.get(-i)
age := first.t - p.t
if age >= maxAge || t-p.t >= maxSampleGap {
@@ -172,9 +172,9 @@ func decomposeQR(A *matrix) (*matrix, *matrix, bool) {
// https://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process
Q := newMatrix(A.rows, A.cols) // Column-major.
Rt := newMatrix(A.rows, A.rows) // R transposed, row-major.
for i := range Q.rows {
for i := 0; i < Q.rows; i++ {
// Copy A column.
for j := range Q.cols {
for j := 0; j < Q.cols; j++ {
Q.set(i, j, A.get(i, j))
}
// Subtract projections. Note that int the projection
@@ -184,9 +184,9 @@ func decomposeQR(A *matrix) (*matrix, *matrix, bool) {
// the normalized column e replaces u, where <e, e> = 1:
//
// proje a = <e, a>/<e, e> e = <e, a> e
for j := range i {
for j := 0; j < i; j++ {
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))
}
}
@@ -197,7 +197,7 @@ func decomposeQR(A *matrix) (*matrix, *matrix, bool) {
return nil, nil, false
}
invNorm := 1 / n
for j := range Q.cols {
for j := 0; j < Q.cols; j++ {
Q.set(i, j, Q.get(i, j)*invNorm)
}
// Update Rt.
@@ -261,8 +261,8 @@ func (m *matrix) approxEqual(m2 *matrix) bool {
return false
}
const epsilon = 0.00001
for row := range m.rows {
for col := range m.cols {
for row := 0; row < m.rows; row++ {
for col := 0; col < m.cols; col++ {
d := m2.get(row, col) - m.get(row, col)
if d < -epsilon || d > epsilon {
return false
@@ -278,8 +278,8 @@ func (m *matrix) transpose() *matrix {
cols: m.rows,
data: make([]float32, len(m.data)),
}
for i := range m.rows {
for j := range m.cols {
for i := 0; i < m.rows; i++ {
for j := 0; j < m.cols; j++ {
t.set(j, i, m.get(i, j))
}
}
@@ -295,10 +295,10 @@ func (m *matrix) mul(m2 *matrix) *matrix {
cols: m2.cols,
data: make([]float32, m.rows*m2.cols),
}
for i := range mm.rows {
for j := range mm.cols {
for i := 0; i < mm.rows; i++ {
for j := 0; j < mm.cols; j++ {
var v float32
for k := range m.rows {
for k := 0; k < m.rows; k++ {
v += m.get(k, j) * m2.get(i, k)
}
mm.set(i, j, v)
@@ -309,8 +309,8 @@ func (m *matrix) mul(m2 *matrix) *matrix {
func (m *matrix) String() string {
var b strings.Builder
for i := range m.rows {
for j := range m.cols {
for i := 0; i < m.rows; i++ {
for j := 0; j < m.cols; j++ {
v := m.get(i, j)
b.WriteString(strconv.FormatFloat(float64(v), 'g', -1, 32))
b.WriteString(", ")
-95
View File
@@ -247,11 +247,9 @@ func (f *Functions) getExtension(name string) js.Value {
func (f *Functions) ActiveTexture(t Enum) {
f._activeTexture.Invoke(int(t))
}
func (f *Functions) AttachShader(p Program, s Shader) {
f._attachShader.Invoke(js.Value(p), js.Value(s))
}
func (f *Functions) BeginQuery(target Enum, query Query) {
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
f._beginQuery.Invoke(int(target), js.Value(query))
@@ -259,47 +257,36 @@ func (f *Functions) BeginQuery(target Enum, query Query) {
f.EXT_disjoint_timer_query.Call("beginQueryEXT", int(target), js.Value(query))
}
}
func (f *Functions) BindAttribLocation(p Program, a Attrib, name string) {
f._bindAttribLocation.Invoke(js.Value(p), int(a), name)
}
func (f *Functions) BindBuffer(target Enum, b Buffer) {
f._bindBuffer.Invoke(int(target), js.Value(b))
}
func (f *Functions) BindBufferBase(target Enum, index int, b Buffer) {
f._bindBufferBase.Invoke(int(target), index, js.Value(b))
}
func (f *Functions) BindFramebuffer(target Enum, fb Framebuffer) {
f._bindFramebuffer.Invoke(int(target), js.Value(fb))
}
func (f *Functions) BindRenderbuffer(target Enum, rb Renderbuffer) {
f._bindRenderbuffer.Invoke(int(target), js.Value(rb))
}
func (f *Functions) BindTexture(target Enum, t Texture) {
f._bindTexture.Invoke(int(target), js.Value(t))
}
func (f *Functions) BindImageTexture(unit int, t Texture, level int, layered bool, layer int, access, format Enum) {
panic("not implemented")
}
func (f *Functions) BindVertexArray(a VertexArray) {
panic("not supported")
}
func (f *Functions) BlendEquation(mode Enum) {
f._blendEquation.Invoke(int(mode))
}
func (f *Functions) BlendFuncSeparate(srcRGB, dstRGB, srcA, dstA Enum) {
f._blendFunc.Invoke(int(srcRGB), int(dstRGB), int(srcA), int(dstA))
}
func (f *Functions) BufferData(target Enum, size int, usage Enum, data []byte) {
if data == nil {
f._bufferData.Invoke(int(target), size, int(usage))
@@ -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))
}
}
func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
f._bufferSubData.Invoke(int(target), offset, f.byteArrayOf(src))
}
func (f *Functions) CheckFramebufferStatus(target Enum) Enum {
status := Enum(f._checkFramebufferStatus.Invoke(int(target)).Int())
if status != FRAMEBUFFER_COMPLETE && f.Ctx.Call("isContextLost").Bool() {
@@ -323,71 +308,54 @@ func (f *Functions) CheckFramebufferStatus(target Enum) Enum {
}
return status
}
func (f *Functions) Clear(mask Enum) {
f._clear.Invoke(int(mask))
}
func (f *Functions) ClearColor(red, green, blue, alpha float32) {
f._clearColor.Invoke(red, green, blue, alpha)
}
func (f *Functions) ClearDepthf(d float32) {
f._clearDepth.Invoke(d)
}
func (f *Functions) CompileShader(s Shader) {
f._compileShader.Invoke(js.Value(s))
}
func (f *Functions) CopyTexSubImage2D(target Enum, level, xoffset, yoffset, x, y, width, height int) {
f._copyTexSubImage2D.Invoke(int(target), level, xoffset, yoffset, x, y, width, height)
}
func (f *Functions) CreateBuffer() Buffer {
return Buffer(f._createBuffer.Invoke())
}
func (f *Functions) CreateFramebuffer() Framebuffer {
return Framebuffer(f._createFramebuffer.Invoke())
}
func (f *Functions) CreateProgram() Program {
return Program(f._createProgram.Invoke())
}
func (f *Functions) CreateQuery() Query {
return Query(f._createQuery.Invoke())
}
func (f *Functions) CreateRenderbuffer() Renderbuffer {
return Renderbuffer(f._createRenderbuffer.Invoke())
}
func (f *Functions) CreateShader(ty Enum) Shader {
return Shader(f._createShader.Invoke(int(ty)))
}
func (f *Functions) CreateTexture() Texture {
return Texture(f._createTexture.Invoke())
}
func (f *Functions) CreateVertexArray() VertexArray {
panic("not supported")
}
func (f *Functions) DeleteBuffer(v Buffer) {
f._deleteBuffer.Invoke(js.Value(v))
}
func (f *Functions) DeleteFramebuffer(v Framebuffer) {
f._deleteFramebuffer.Invoke(js.Value(v))
}
func (f *Functions) DeleteProgram(p Program) {
f._deleteProgram.Invoke(js.Value(p))
}
func (f *Functions) DeleteQuery(query Query) {
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
f._deleteQuery.Invoke(js.Value(query))
@@ -395,59 +363,45 @@ func (f *Functions) DeleteQuery(query Query) {
f.EXT_disjoint_timer_query.Call("deleteQueryEXT", js.Value(query))
}
}
func (f *Functions) DeleteShader(s Shader) {
f._deleteShader.Invoke(js.Value(s))
}
func (f *Functions) DeleteRenderbuffer(v Renderbuffer) {
f._deleteRenderbuffer.Invoke(js.Value(v))
}
func (f *Functions) DeleteTexture(v Texture) {
f._deleteTexture.Invoke(js.Value(v))
}
func (f *Functions) DeleteVertexArray(a VertexArray) {
panic("not implemented")
}
func (f *Functions) DepthFunc(fn Enum) {
f._depthFunc.Invoke(int(fn))
}
func (f *Functions) DepthMask(mask bool) {
f._depthMask.Invoke(mask)
}
func (f *Functions) DisableVertexAttribArray(a Attrib) {
f._disableVertexAttribArray.Invoke(int(a))
}
func (f *Functions) Disable(cap Enum) {
f._disable.Invoke(int(cap))
}
func (f *Functions) DrawArrays(mode Enum, first, count int) {
f._drawArrays.Invoke(int(mode), first, count)
}
func (f *Functions) DrawElements(mode Enum, count int, ty Enum, offset int) {
f._drawElements.Invoke(int(mode), count, int(ty), offset)
}
func (f *Functions) DispatchCompute(x, y, z int) {
panic("not implemented")
}
func (f *Functions) Enable(cap Enum) {
f._enable.Invoke(int(cap))
}
func (f *Functions) EnableVertexAttribArray(a Attrib) {
f._enableVertexAttribArray.Invoke(int(a))
}
func (f *Functions) EndQuery(target Enum) {
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
f._endQuery.Invoke(int(target))
@@ -455,36 +409,28 @@ func (f *Functions) EndQuery(target Enum) {
f.EXT_disjoint_timer_query.Call("endQueryEXT", int(target))
}
}
func (f *Functions) Finish() {
f._finish.Invoke()
}
func (f *Functions) Flush() {
f._flush.Invoke()
}
func (f *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer) {
f._framebufferRenderbuffer.Invoke(int(target), int(attachment), int(renderbuffertarget), js.Value(renderbuffer))
}
func (f *Functions) FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int) {
f._framebufferTexture2D.Invoke(int(target), int(attachment), int(texTarget), js.Value(t), level)
}
func (f *Functions) GenerateMipmap(target Enum) {
f._generateMipmap.Invoke(int(target))
}
func (f *Functions) GetError() Enum {
// Avoid slow getError calls. See gio#179.
return 0
}
func (f *Functions) GetRenderbufferParameteri(target, pname Enum) int {
return paramVal(f._getRenderbufferParameteri.Invoke(int(pname)))
}
func (f *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int {
if !f.isWebGL2 && pname == FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING {
// FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING is only available on WebGL 2
@@ -492,7 +438,6 @@ func (f *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname
}
return paramVal(f._getFramebufferAttachmentParameter.Invoke(int(target), int(attachment), int(pname)))
}
func (f *Functions) GetBinding(pname Enum) Object {
obj := f._getParameter.Invoke(int(pname))
if !obj.Truthy() {
@@ -500,7 +445,6 @@ func (f *Functions) GetBinding(pname Enum) Object {
}
return Object(obj)
}
func (f *Functions) GetBindingi(pname Enum, idx int) Object {
obj := f._getIndexedParameter.Invoke(int(pname), idx)
if !obj.Truthy() {
@@ -508,7 +452,6 @@ func (f *Functions) GetBindingi(pname Enum, idx int) Object {
}
return Object(obj)
}
func (f *Functions) GetInteger(pname Enum) int {
if !f.isWebGL2 {
switch pname {
@@ -518,11 +461,9 @@ func (f *Functions) GetInteger(pname Enum) int {
}
return paramVal(f._getParameter.Invoke(int(pname)))
}
func (f *Functions) GetFloat(pname Enum) float32 {
return float32(f._getParameter.Invoke(int(pname)).Float())
}
func (f *Functions) GetInteger4(pname Enum) [4]int {
arr := f._getParameter.Invoke(int(pname))
var res [4]int
@@ -531,7 +472,6 @@ func (f *Functions) GetInteger4(pname Enum) [4]int {
}
return res
}
func (f *Functions) GetFloat4(pname Enum) [4]float32 {
arr := f._getParameter.Invoke(int(pname))
var res [4]float32
@@ -540,15 +480,12 @@ func (f *Functions) GetFloat4(pname Enum) [4]float32 {
}
return res
}
func (f *Functions) GetProgrami(p Program, pname Enum) int {
return paramVal(f._getProgramParameter.Invoke(js.Value(p), int(pname)))
}
func (f *Functions) GetProgramInfoLog(p Program) string {
return f._getProgramInfoLog.Invoke(js.Value(p)).String()
}
func (f *Functions) GetQueryObjectuiv(query Query, pname Enum) uint {
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
return uint(paramVal(f._getQueryParameter.Invoke(js.Value(query), int(pname))))
@@ -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))))
}
}
func (f *Functions) GetShaderi(s Shader, pname Enum) int {
return paramVal(f._getShaderParameter.Invoke(js.Value(s), int(pname)))
}
func (f *Functions) GetShaderInfoLog(s Shader) string {
return f._getShaderInfoLog.Invoke(js.Value(s)).String()
}
func (f *Functions) GetString(pname Enum) string {
switch pname {
case EXTENSIONS:
@@ -578,19 +512,15 @@ func (f *Functions) GetString(pname Enum) string {
return f._getParameter.Invoke(int(pname)).String()
}
}
func (f *Functions) GetUniformBlockIndex(p Program, name string) uint {
return uint(paramVal(f._getUniformBlockIndex.Invoke(js.Value(p), name)))
}
func (f *Functions) GetUniformLocation(p Program, name string) Uniform {
return Uniform(f._getUniformLocation.Invoke(js.Value(p), name))
}
func (f *Functions) GetVertexAttrib(index int, pname Enum) int {
return paramVal(f._getVertexAttrib.Invoke(index, int(pname)))
}
func (f *Functions) GetVertexAttribBinding(index int, pname Enum) Object {
obj := f._getVertexAttrib.Invoke(index, int(pname))
if !obj.Truthy() {
@@ -598,11 +528,9 @@ func (f *Functions) GetVertexAttribBinding(index int, pname Enum) Object {
}
return Object(obj)
}
func (f *Functions) GetVertexAttribPointer(index int, pname Enum) uintptr {
return uintptr(f._getVertexAttribOffset.Invoke(index, int(pname)).Int())
}
func (f *Functions) InvalidateFramebuffer(target, attachment Enum) {
fn := f.Ctx.Get("invalidateFramebuffer")
if !fn.IsUndefined() {
@@ -613,97 +541,74 @@ func (f *Functions) InvalidateFramebuffer(target, attachment Enum) {
f._invalidateFramebuffer.Invoke(int(target), f.int32Buf)
}
}
func (f *Functions) IsEnabled(cap Enum) bool {
return f._isEnabled.Invoke(int(cap)).Truthy()
}
func (f *Functions) LinkProgram(p Program) {
f._linkProgram.Invoke(js.Value(p))
}
func (f *Functions) PixelStorei(pname Enum, param int) {
f._pixelStorei.Invoke(int(pname), param)
}
func (f *Functions) MemoryBarrier(barriers Enum) {
panic("not implemented")
}
func (f *Functions) MapBufferRange(target Enum, offset, length int, access Enum) []byte {
panic("not implemented")
}
func (f *Functions) RenderbufferStorage(target, internalformat Enum, width, height int) {
f._renderbufferStorage.Invoke(int(target), int(internalformat), width, height)
}
func (f *Functions) ReadPixels(x, y, width, height int, format, ty Enum, data []byte) {
ba := f.byteArrayOf(data)
f._readPixels.Invoke(x, y, width, height, int(format), int(ty), ba)
js.CopyBytesToGo(data, ba)
}
func (f *Functions) Scissor(x, y, width, height int32) {
f._scissor.Invoke(x, y, width, height)
}
func (f *Functions) ShaderSource(s Shader, src string) {
f._shaderSource.Invoke(js.Value(s), src)
}
func (f *Functions) TexImage2D(target Enum, level int, internalFormat Enum, width, height int, format, ty Enum) {
f._texImage2D.Invoke(int(target), int(level), int(internalFormat), int(width), int(height), 0, int(format), int(ty), nil)
}
func (f *Functions) TexStorage2D(target Enum, levels int, internalFormat Enum, width, height int) {
f._texStorage2D.Invoke(int(target), levels, int(internalFormat), width, height)
}
func (f *Functions) TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte) {
f._texSubImage2D.Invoke(int(target), level, x, y, width, height, int(format), int(ty), f.byteArrayOf(data))
}
func (f *Functions) TexParameteri(target, pname Enum, param int) {
f._texParameteri.Invoke(int(target), int(pname), int(param))
}
func (f *Functions) UniformBlockBinding(p Program, uniformBlockIndex uint, uniformBlockBinding uint) {
f._uniformBlockBinding.Invoke(js.Value(p), int(uniformBlockIndex), int(uniformBlockBinding))
}
func (f *Functions) Uniform1f(dst Uniform, v float32) {
f._uniform1f.Invoke(js.Value(dst), v)
}
func (f *Functions) Uniform1i(dst Uniform, v int) {
f._uniform1i.Invoke(js.Value(dst), v)
}
func (f *Functions) Uniform2f(dst Uniform, v0, v1 float32) {
f._uniform2f.Invoke(js.Value(dst), v0, v1)
}
func (f *Functions) Uniform3f(dst Uniform, v0, v1, v2 float32) {
f._uniform3f.Invoke(js.Value(dst), v0, v1, v2)
}
func (f *Functions) Uniform4f(dst Uniform, v0, v1, v2, v3 float32) {
f._uniform4f.Invoke(js.Value(dst), v0, v1, v2, v3)
}
func (f *Functions) UseProgram(p Program) {
f._useProgram.Invoke(js.Value(p))
}
func (f *Functions) UnmapBuffer(target Enum) bool {
panic("not implemented")
}
func (f *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int) {
f._vertexAttribPointer.Invoke(int(dst), size, int(ty), normalized, stride, offset)
}
func (f *Functions) Viewport(x, y, width, height int) {
f._viewport.Invoke(x, y, width, height)
}
+91 -303
View File
@@ -3,217 +3,103 @@
package gl
import (
"fmt"
"math"
"runtime"
"sync"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
func loadGLESv2Procs() error {
dllName := "libGLESv2.dll"
handle, err := windows.LoadLibraryEx(dllName, 0, windows.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)
if err != nil {
return fmt.Errorf("gl: failed to load %s: %v", dllName, err)
}
gles := windows.DLL{Handle: handle, Name: dllName}
// d3dcompiler_47.dll is needed internally for shader compilation to function.
dllName = "d3dcompiler_47.dll"
_, err = windows.LoadLibraryEx(dllName, 0, windows.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)
if err != nil {
return fmt.Errorf("gl: failed to load %s: %v", dllName, err)
}
procs := map[string]**windows.Proc{
"glActiveTexture": &_glActiveTexture,
"glAttachShader": &_glAttachShader,
"glBeginQuery": &_glBeginQuery,
"glBindAttribLocation": &_glBindAttribLocation,
"glBindBuffer": &_glBindBuffer,
"glBindBufferBase": &_glBindBufferBase,
"glBindFramebuffer": &_glBindFramebuffer,
"glBindRenderbuffer": &_glBindRenderbuffer,
"glBindTexture": &_glBindTexture,
"glBindVertexArray": &_glBindVertexArray,
"glBlendEquation": &_glBlendEquation,
"glBlendFuncSeparate": &_glBlendFuncSeparate,
"glBufferData": &_glBufferData,
"glBufferSubData": &_glBufferSubData,
"glCheckFramebufferStatus": &_glCheckFramebufferStatus,
"glClear": &_glClear,
"glClearColor": &_glClearColor,
"glClearDepthf": &_glClearDepthf,
"glDeleteQueries": &_glDeleteQueries,
"glDeleteVertexArrays": &_glDeleteVertexArrays,
"glCompileShader": &_glCompileShader,
"glCopyTexSubImage2D": &_glCopyTexSubImage2D,
"glGenerateMipmap": &_glGenerateMipmap,
"glGenBuffers": &_glGenBuffers,
"glGenFramebuffers": &_glGenFramebuffers,
"glGenVertexArrays": &_glGenVertexArrays,
"glGetUniformBlockIndex": &_glGetUniformBlockIndex,
"glCreateProgram": &_glCreateProgram,
"glGenRenderbuffers": &_glGenRenderbuffers,
"glCreateShader": &_glCreateShader,
"glGenTextures": &_glGenTextures,
"glDeleteBuffers": &_glDeleteBuffers,
"glDeleteFramebuffers": &_glDeleteFramebuffers,
"glDeleteProgram": &_glDeleteProgram,
"glDeleteShader": &_glDeleteShader,
"glDeleteRenderbuffers": &_glDeleteRenderbuffers,
"glDeleteTextures": &_glDeleteTextures,
"glDepthFunc": &_glDepthFunc,
"glDepthMask": &_glDepthMask,
"glDisableVertexAttribArray": &_glDisableVertexAttribArray,
"glDisable": &_glDisable,
"glDrawArrays": &_glDrawArrays,
"glDrawElements": &_glDrawElements,
"glEnable": &_glEnable,
"glEnableVertexAttribArray": &_glEnableVertexAttribArray,
"glEndQuery": &_glEndQuery,
"glFinish": &_glFinish,
"glFlush": &_glFlush,
"glFramebufferRenderbuffer": &_glFramebufferRenderbuffer,
"glFramebufferTexture2D": &_glFramebufferTexture2D,
"glGenQueries": &_glGenQueries,
"glGetError": &_glGetError,
"glGetRenderbufferParameteriv": &_glGetRenderbufferParameteriv,
"glGetFloatv": &_glGetFloatv,
"glGetFramebufferAttachmentParameteriv": &_glGetFramebufferAttachmentParameteriv,
"glGetIntegerv": &_glGetIntegerv,
"glGetIntegeri_v": &_glGetIntegeri_v,
"glGetProgramiv": &_glGetProgramiv,
"glGetProgramInfoLog": &_glGetProgramInfoLog,
"glGetQueryObjectuiv": &_glGetQueryObjectuiv,
"glGetShaderiv": &_glGetShaderiv,
"glGetShaderInfoLog": &_glGetShaderInfoLog,
"glGetString": &_glGetString,
"glGetUniformLocation": &_glGetUniformLocation,
"glGetVertexAttribiv": &_glGetVertexAttribiv,
"glGetVertexAttribPointerv": &_glGetVertexAttribPointerv,
"glInvalidateFramebuffer": &_glInvalidateFramebuffer,
"glIsEnabled": &_glIsEnabled,
"glLinkProgram": &_glLinkProgram,
"glPixelStorei": &_glPixelStorei,
"glReadPixels": &_glReadPixels,
"glRenderbufferStorage": &_glRenderbufferStorage,
"glScissor": &_glScissor,
"glShaderSource": &_glShaderSource,
"glTexImage2D": &_glTexImage2D,
"glTexStorage2D": &_glTexStorage2D,
"glTexSubImage2D": &_glTexSubImage2D,
"glTexParameteri": &_glTexParameteri,
"glUniformBlockBinding": &_glUniformBlockBinding,
"glUniform1f": &_glUniform1f,
"glUniform1i": &_glUniform1i,
"glUniform2f": &_glUniform2f,
"glUniform3f": &_glUniform3f,
"glUniform4f": &_glUniform4f,
"glUseProgram": &_glUseProgram,
"glVertexAttribPointer": &_glVertexAttribPointer,
"glViewport": &_glViewport,
}
for name, proc := range procs {
p, err := gles.FindProc(name)
if err != nil {
return fmt.Errorf("failed to locate %s in %s: %w", name, gles.Name, err)
}
*proc = p
}
return nil
}
var (
glInitOnce sync.Once
_glActiveTexture *windows.Proc
_glAttachShader *windows.Proc
_glBeginQuery *windows.Proc
_glBindAttribLocation *windows.Proc
_glBindBuffer *windows.Proc
_glBindBufferBase *windows.Proc
_glBindFramebuffer *windows.Proc
_glBindRenderbuffer *windows.Proc
_glBindTexture *windows.Proc
_glBindVertexArray *windows.Proc
_glBlendEquation *windows.Proc
_glBlendFuncSeparate *windows.Proc
_glBufferData *windows.Proc
_glBufferSubData *windows.Proc
_glCheckFramebufferStatus *windows.Proc
_glClear *windows.Proc
_glClearColor *windows.Proc
_glClearDepthf *windows.Proc
_glDeleteQueries *windows.Proc
_glDeleteVertexArrays *windows.Proc
_glCompileShader *windows.Proc
_glCopyTexSubImage2D *windows.Proc
_glGenerateMipmap *windows.Proc
_glGenBuffers *windows.Proc
_glGenFramebuffers *windows.Proc
_glGenVertexArrays *windows.Proc
_glGetUniformBlockIndex *windows.Proc
_glCreateProgram *windows.Proc
_glGenRenderbuffers *windows.Proc
_glCreateShader *windows.Proc
_glGenTextures *windows.Proc
_glDeleteBuffers *windows.Proc
_glDeleteFramebuffers *windows.Proc
_glDeleteProgram *windows.Proc
_glDeleteShader *windows.Proc
_glDeleteRenderbuffers *windows.Proc
_glDeleteTextures *windows.Proc
_glDepthFunc *windows.Proc
_glDepthMask *windows.Proc
_glDisableVertexAttribArray *windows.Proc
_glDisable *windows.Proc
_glDrawArrays *windows.Proc
_glDrawElements *windows.Proc
_glEnable *windows.Proc
_glEnableVertexAttribArray *windows.Proc
_glEndQuery *windows.Proc
_glFinish *windows.Proc
_glFlush *windows.Proc
_glFramebufferRenderbuffer *windows.Proc
_glFramebufferTexture2D *windows.Proc
_glGenQueries *windows.Proc
_glGetError *windows.Proc
_glGetRenderbufferParameteriv *windows.Proc
_glGetFloatv *windows.Proc
_glGetFramebufferAttachmentParameteriv *windows.Proc
_glGetIntegerv *windows.Proc
_glGetIntegeri_v *windows.Proc
_glGetProgramiv *windows.Proc
_glGetProgramInfoLog *windows.Proc
_glGetQueryObjectuiv *windows.Proc
_glGetShaderiv *windows.Proc
_glGetShaderInfoLog *windows.Proc
_glGetString *windows.Proc
_glGetUniformLocation *windows.Proc
_glGetVertexAttribiv *windows.Proc
_glGetVertexAttribPointerv *windows.Proc
_glInvalidateFramebuffer *windows.Proc
_glIsEnabled *windows.Proc
_glLinkProgram *windows.Proc
_glPixelStorei *windows.Proc
_glReadPixels *windows.Proc
_glRenderbufferStorage *windows.Proc
_glScissor *windows.Proc
_glShaderSource *windows.Proc
_glTexImage2D *windows.Proc
_glTexStorage2D *windows.Proc
_glTexSubImage2D *windows.Proc
_glTexParameteri *windows.Proc
_glUniformBlockBinding *windows.Proc
_glUniform1f *windows.Proc
_glUniform1i *windows.Proc
_glUniform2f *windows.Proc
_glUniform3f *windows.Proc
_glUniform4f *windows.Proc
_glUseProgram *windows.Proc
_glVertexAttribPointer *windows.Proc
_glViewport *windows.Proc
LibGLESv2 = windows.NewLazyDLL("libGLESv2.dll")
_glActiveTexture = LibGLESv2.NewProc("glActiveTexture")
_glAttachShader = LibGLESv2.NewProc("glAttachShader")
_glBeginQuery = LibGLESv2.NewProc("glBeginQuery")
_glBindAttribLocation = LibGLESv2.NewProc("glBindAttribLocation")
_glBindBuffer = LibGLESv2.NewProc("glBindBuffer")
_glBindBufferBase = LibGLESv2.NewProc("glBindBufferBase")
_glBindFramebuffer = LibGLESv2.NewProc("glBindFramebuffer")
_glBindRenderbuffer = LibGLESv2.NewProc("glBindRenderbuffer")
_glBindTexture = LibGLESv2.NewProc("glBindTexture")
_glBindVertexArray = LibGLESv2.NewProc("glBindVertexArray")
_glBlendEquation = LibGLESv2.NewProc("glBlendEquation")
_glBlendFuncSeparate = LibGLESv2.NewProc("glBlendFuncSeparate")
_glBufferData = LibGLESv2.NewProc("glBufferData")
_glBufferSubData = LibGLESv2.NewProc("glBufferSubData")
_glCheckFramebufferStatus = LibGLESv2.NewProc("glCheckFramebufferStatus")
_glClear = LibGLESv2.NewProc("glClear")
_glClearColor = LibGLESv2.NewProc("glClearColor")
_glClearDepthf = LibGLESv2.NewProc("glClearDepthf")
_glDeleteQueries = LibGLESv2.NewProc("glDeleteQueries")
_glDeleteVertexArrays = LibGLESv2.NewProc("glDeleteVertexArrays")
_glCompileShader = LibGLESv2.NewProc("glCompileShader")
_glCopyTexSubImage2D = LibGLESv2.NewProc("glCopyTexSubImage2D")
_glGenerateMipmap = LibGLESv2.NewProc("glGenerateMipmap")
_glGenBuffers = LibGLESv2.NewProc("glGenBuffers")
_glGenFramebuffers = LibGLESv2.NewProc("glGenFramebuffers")
_glGenVertexArrays = LibGLESv2.NewProc("glGenVertexArrays")
_glGetUniformBlockIndex = LibGLESv2.NewProc("glGetUniformBlockIndex")
_glCreateProgram = LibGLESv2.NewProc("glCreateProgram")
_glGenRenderbuffers = LibGLESv2.NewProc("glGenRenderbuffers")
_glCreateShader = LibGLESv2.NewProc("glCreateShader")
_glGenTextures = LibGLESv2.NewProc("glGenTextures")
_glDeleteBuffers = LibGLESv2.NewProc("glDeleteBuffers")
_glDeleteFramebuffers = LibGLESv2.NewProc("glDeleteFramebuffers")
_glDeleteProgram = LibGLESv2.NewProc("glDeleteProgram")
_glDeleteShader = LibGLESv2.NewProc("glDeleteShader")
_glDeleteRenderbuffers = LibGLESv2.NewProc("glDeleteRenderbuffers")
_glDeleteTextures = LibGLESv2.NewProc("glDeleteTextures")
_glDepthFunc = LibGLESv2.NewProc("glDepthFunc")
_glDepthMask = LibGLESv2.NewProc("glDepthMask")
_glDisableVertexAttribArray = LibGLESv2.NewProc("glDisableVertexAttribArray")
_glDisable = LibGLESv2.NewProc("glDisable")
_glDrawArrays = LibGLESv2.NewProc("glDrawArrays")
_glDrawElements = LibGLESv2.NewProc("glDrawElements")
_glEnable = LibGLESv2.NewProc("glEnable")
_glEnableVertexAttribArray = LibGLESv2.NewProc("glEnableVertexAttribArray")
_glEndQuery = LibGLESv2.NewProc("glEndQuery")
_glFinish = LibGLESv2.NewProc("glFinish")
_glFlush = LibGLESv2.NewProc("glFlush")
_glFramebufferRenderbuffer = LibGLESv2.NewProc("glFramebufferRenderbuffer")
_glFramebufferTexture2D = LibGLESv2.NewProc("glFramebufferTexture2D")
_glGenQueries = LibGLESv2.NewProc("glGenQueries")
_glGetError = LibGLESv2.NewProc("glGetError")
_glGetRenderbufferParameteriv = LibGLESv2.NewProc("glGetRenderbufferParameteriv")
_glGetFloatv = LibGLESv2.NewProc("glGetFloatv")
_glGetFramebufferAttachmentParameteriv = LibGLESv2.NewProc("glGetFramebufferAttachmentParameteriv")
_glGetIntegerv = LibGLESv2.NewProc("glGetIntegerv")
_glGetIntegeri_v = LibGLESv2.NewProc("glGetIntegeri_v")
_glGetProgramiv = LibGLESv2.NewProc("glGetProgramiv")
_glGetProgramInfoLog = LibGLESv2.NewProc("glGetProgramInfoLog")
_glGetQueryObjectuiv = LibGLESv2.NewProc("glGetQueryObjectuiv")
_glGetShaderiv = LibGLESv2.NewProc("glGetShaderiv")
_glGetShaderInfoLog = LibGLESv2.NewProc("glGetShaderInfoLog")
_glGetString = LibGLESv2.NewProc("glGetString")
_glGetUniformLocation = LibGLESv2.NewProc("glGetUniformLocation")
_glGetVertexAttribiv = LibGLESv2.NewProc("glGetVertexAttribiv")
_glGetVertexAttribPointerv = LibGLESv2.NewProc("glGetVertexAttribPointerv")
_glInvalidateFramebuffer = LibGLESv2.NewProc("glInvalidateFramebuffer")
_glIsEnabled = LibGLESv2.NewProc("glIsEnabled")
_glLinkProgram = LibGLESv2.NewProc("glLinkProgram")
_glPixelStorei = LibGLESv2.NewProc("glPixelStorei")
_glReadPixels = LibGLESv2.NewProc("glReadPixels")
_glRenderbufferStorage = LibGLESv2.NewProc("glRenderbufferStorage")
_glScissor = LibGLESv2.NewProc("glScissor")
_glShaderSource = LibGLESv2.NewProc("glShaderSource")
_glTexImage2D = LibGLESv2.NewProc("glTexImage2D")
_glTexStorage2D = LibGLESv2.NewProc("glTexStorage2D")
_glTexSubImage2D = LibGLESv2.NewProc("glTexSubImage2D")
_glTexParameteri = LibGLESv2.NewProc("glTexParameteri")
_glUniformBlockBinding = LibGLESv2.NewProc("glUniformBlockBinding")
_glUniform1f = LibGLESv2.NewProc("glUniform1f")
_glUniform1i = LibGLESv2.NewProc("glUniform1i")
_glUniform2f = LibGLESv2.NewProc("glUniform2f")
_glUniform3f = LibGLESv2.NewProc("glUniform3f")
_glUniform4f = LibGLESv2.NewProc("glUniform4f")
_glUseProgram = LibGLESv2.NewProc("glUseProgram")
_glVertexAttribPointer = LibGLESv2.NewProc("glVertexAttribPointer")
_glViewport = LibGLESv2.NewProc("glViewport")
)
type Functions struct {
@@ -223,74 +109,57 @@ type Functions struct {
uintptrs [100]uintptr
}
type Context any
type Context interface{}
func NewFunctions(ctx Context, forceES bool) (*Functions, error) {
if ctx != nil {
panic("non-nil context")
}
var err error
glInitOnce.Do(func() {
err = loadGLESv2Procs()
})
return new(Functions), err
return new(Functions), nil
}
func (c *Functions) ActiveTexture(t Enum) {
syscall.Syscall(_glActiveTexture.Addr(), 1, uintptr(t), 0, 0)
}
func (c *Functions) AttachShader(p Program, s Shader) {
syscall.Syscall(_glAttachShader.Addr(), 2, uintptr(p.V), uintptr(s.V), 0)
}
func (f *Functions) BeginQuery(target Enum, query Query) {
syscall.Syscall(_glBeginQuery.Addr(), 2, uintptr(target), uintptr(query.V), 0)
}
func (c *Functions) BindAttribLocation(p Program, a Attrib, name string) {
cname := cString(name)
c0 := &cname[0]
syscall.Syscall(_glBindAttribLocation.Addr(), 3, uintptr(p.V), uintptr(a), uintptr(unsafe.Pointer(c0)))
issue34474KeepAlive(c)
}
func (c *Functions) BindBuffer(target Enum, b Buffer) {
syscall.Syscall(_glBindBuffer.Addr(), 2, uintptr(target), uintptr(b.V), 0)
}
func (c *Functions) BindBufferBase(target Enum, index int, b Buffer) {
syscall.Syscall(_glBindBufferBase.Addr(), 3, uintptr(target), uintptr(index), uintptr(b.V))
}
func (c *Functions) BindFramebuffer(target Enum, fb Framebuffer) {
syscall.Syscall(_glBindFramebuffer.Addr(), 2, uintptr(target), uintptr(fb.V), 0)
}
func (c *Functions) BindRenderbuffer(target Enum, rb Renderbuffer) {
syscall.Syscall(_glBindRenderbuffer.Addr(), 2, uintptr(target), uintptr(rb.V), 0)
}
func (f *Functions) BindImageTexture(unit int, t Texture, level int, layered bool, layer int, access, format Enum) {
panic("not implemented")
}
func (c *Functions) BindTexture(target Enum, t Texture) {
syscall.Syscall(_glBindTexture.Addr(), 2, uintptr(target), uintptr(t.V), 0)
}
func (c *Functions) BindVertexArray(a VertexArray) {
syscall.Syscall(_glBindVertexArray.Addr(), 1, uintptr(a.V), 0, 0)
}
func (c *Functions) BlendEquation(mode Enum) {
syscall.Syscall(_glBlendEquation.Addr(), 1, uintptr(mode), 0, 0)
}
func (c *Functions) BlendFuncSeparate(srcRGB, dstRGB, srcA, dstA Enum) {
syscall.Syscall6(_glBlendFuncSeparate.Addr(), 4, uintptr(srcRGB), uintptr(dstRGB), uintptr(srcA), uintptr(dstA), 0, 0)
}
func (c *Functions) BufferData(target Enum, size int, usage Enum, data []byte) {
var p unsafe.Pointer
if len(data) > 0 {
@@ -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)
}
func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
if n := len(src); n > 0 {
s0 := &src[0]
@@ -306,118 +174,93 @@ func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
issue34474KeepAlive(s0)
}
}
func (c *Functions) CheckFramebufferStatus(target Enum) Enum {
s, _, _ := syscall.Syscall(_glCheckFramebufferStatus.Addr(), 1, uintptr(target), 0, 0)
return Enum(s)
}
func (c *Functions) Clear(mask Enum) {
syscall.Syscall(_glClear.Addr(), 1, uintptr(mask), 0, 0)
}
func (c *Functions) ClearColor(red, green, blue, alpha float32) {
syscall.Syscall6(_glClearColor.Addr(), 4, uintptr(math.Float32bits(red)), uintptr(math.Float32bits(green)), uintptr(math.Float32bits(blue)), uintptr(math.Float32bits(alpha)), 0, 0)
}
func (c *Functions) ClearDepthf(d float32) {
syscall.Syscall(_glClearDepthf.Addr(), 1, uintptr(math.Float32bits(d)), 0, 0)
}
func (c *Functions) CompileShader(s Shader) {
syscall.Syscall(_glCompileShader.Addr(), 1, uintptr(s.V), 0, 0)
}
func (f *Functions) CopyTexSubImage2D(target Enum, level, xoffset, yoffset, x, y, width, height int) {
syscall.Syscall9(_glCopyTexSubImage2D.Addr(), 8, uintptr(target), uintptr(level), uintptr(xoffset), uintptr(yoffset), uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0)
}
func (f *Functions) GenerateMipmap(target Enum) {
syscall.Syscall(_glGenerateMipmap.Addr(), 1, uintptr(target), 0, 0)
}
func (c *Functions) CreateBuffer() Buffer {
var buf uintptr
syscall.Syscall(_glGenBuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&buf)), 0)
return Buffer{uint(buf)}
}
func (c *Functions) CreateFramebuffer() Framebuffer {
var fb uintptr
syscall.Syscall(_glGenFramebuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&fb)), 0)
return Framebuffer{uint(fb)}
}
func (c *Functions) CreateProgram() Program {
p, _, _ := syscall.Syscall(_glCreateProgram.Addr(), 0, 0, 0, 0)
return Program{uint(p)}
}
func (f *Functions) CreateQuery() Query {
var q uintptr
syscall.Syscall(_glGenQueries.Addr(), 2, 1, uintptr(unsafe.Pointer(&q)), 0)
return Query{uint(q)}
}
func (c *Functions) CreateRenderbuffer() Renderbuffer {
var rb uintptr
syscall.Syscall(_glGenRenderbuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&rb)), 0)
return Renderbuffer{uint(rb)}
}
func (c *Functions) CreateShader(ty Enum) Shader {
s, _, _ := syscall.Syscall(_glCreateShader.Addr(), 1, uintptr(ty), 0, 0)
return Shader{uint(s)}
}
func (c *Functions) CreateTexture() Texture {
var t uintptr
syscall.Syscall(_glGenTextures.Addr(), 2, 1, uintptr(unsafe.Pointer(&t)), 0)
return Texture{uint(t)}
}
func (c *Functions) CreateVertexArray() VertexArray {
var t uintptr
syscall.Syscall(_glGenVertexArrays.Addr(), 2, 1, uintptr(unsafe.Pointer(&t)), 0)
return VertexArray{uint(t)}
}
func (c *Functions) DeleteBuffer(v Buffer) {
syscall.Syscall(_glDeleteBuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v)), 0)
}
func (c *Functions) DeleteFramebuffer(v Framebuffer) {
syscall.Syscall(_glDeleteFramebuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
}
func (c *Functions) DeleteProgram(p Program) {
syscall.Syscall(_glDeleteProgram.Addr(), 1, uintptr(p.V), 0, 0)
}
func (f *Functions) DeleteQuery(query Query) {
syscall.Syscall(_glDeleteQueries.Addr(), 2, 1, uintptr(unsafe.Pointer(&query.V)), 0)
}
func (c *Functions) DeleteShader(s Shader) {
syscall.Syscall(_glDeleteShader.Addr(), 1, uintptr(s.V), 0, 0)
}
func (c *Functions) DeleteRenderbuffer(v Renderbuffer) {
syscall.Syscall(_glDeleteRenderbuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
}
func (c *Functions) DeleteTexture(v Texture) {
syscall.Syscall(_glDeleteTextures.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
}
func (f *Functions) DeleteVertexArray(array VertexArray) {
syscall.Syscall(_glDeleteVertexArrays.Addr(), 2, 1, uintptr(unsafe.Pointer(&array.V)), 0)
}
func (c *Functions) DepthFunc(f Enum) {
syscall.Syscall(_glDepthFunc.Addr(), 1, uintptr(f), 0, 0)
}
func (c *Functions) DepthMask(mask bool) {
var m uintptr
if mask {
@@ -425,55 +268,42 @@ func (c *Functions) DepthMask(mask bool) {
}
syscall.Syscall(_glDepthMask.Addr(), 1, m, 0, 0)
}
func (c *Functions) DisableVertexAttribArray(a Attrib) {
syscall.Syscall(_glDisableVertexAttribArray.Addr(), 1, uintptr(a), 0, 0)
}
func (c *Functions) Disable(cap Enum) {
syscall.Syscall(_glDisable.Addr(), 1, uintptr(cap), 0, 0)
}
func (c *Functions) DrawArrays(mode Enum, first, count int) {
syscall.Syscall(_glDrawArrays.Addr(), 3, uintptr(mode), uintptr(first), uintptr(count))
}
func (c *Functions) DrawElements(mode Enum, count int, ty Enum, offset int) {
syscall.Syscall6(_glDrawElements.Addr(), 4, uintptr(mode), uintptr(count), uintptr(ty), uintptr(offset), 0, 0)
}
func (f *Functions) DispatchCompute(x, y, z int) {
panic("not implemented")
}
func (c *Functions) Enable(cap Enum) {
syscall.Syscall(_glEnable.Addr(), 1, uintptr(cap), 0, 0)
}
func (c *Functions) EnableVertexAttribArray(a Attrib) {
syscall.Syscall(_glEnableVertexAttribArray.Addr(), 1, uintptr(a), 0, 0)
}
func (f *Functions) EndQuery(target Enum) {
syscall.Syscall(_glEndQuery.Addr(), 1, uintptr(target), 0, 0)
}
func (c *Functions) Finish() {
syscall.Syscall(_glFinish.Addr(), 0, 0, 0, 0)
}
func (c *Functions) Flush() {
syscall.Syscall(_glFlush.Addr(), 0, 0, 0, 0)
}
func (c *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer) {
syscall.Syscall6(_glFramebufferRenderbuffer.Addr(), 4, uintptr(target), uintptr(attachment), uintptr(renderbuffertarget), uintptr(renderbuffer.V), 0, 0)
}
func (c *Functions) FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int) {
syscall.Syscall6(_glFramebufferTexture2D.Addr(), 5, uintptr(target), uintptr(attachment), uintptr(texTarget), uintptr(t.V), uintptr(level), 0)
}
func (f *Functions) GetUniformBlockIndex(p Program, name string) uint {
cname := cString(name)
c0 := &cname[0]
@@ -481,30 +311,24 @@ func (f *Functions) GetUniformBlockIndex(p Program, name string) uint {
issue34474KeepAlive(c0)
return uint(u)
}
func (c *Functions) GetBinding(pname Enum) Object {
return Object{uint(c.GetInteger(pname))}
}
func (c *Functions) GetBindingi(pname Enum, idx int) Object {
return Object{uint(c.GetIntegeri(pname, idx))}
}
func (c *Functions) GetError() Enum {
e, _, _ := syscall.Syscall(_glGetError.Addr(), 0, 0, 0, 0)
return Enum(e)
}
func (c *Functions) GetRenderbufferParameteri(target, pname Enum) int {
syscall.Syscall(_glGetRenderbufferParameteriv.Addr(), 3, uintptr(target), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
return int(c.int32s[0])
}
func (c *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int {
syscall.Syscall6(_glGetFramebufferAttachmentParameteriv.Addr(), 4, uintptr(target), uintptr(attachment), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0, 0)
return int(c.int32s[0])
}
func (c *Functions) GetInteger4(pname Enum) [4]int {
syscall.Syscall(_glGetIntegerv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0)
var r [4]int
@@ -513,34 +337,28 @@ func (c *Functions) GetInteger4(pname Enum) [4]int {
}
return r
}
func (c *Functions) GetInteger(pname Enum) int {
syscall.Syscall(_glGetIntegerv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0)
return int(c.int32s[0])
}
func (c *Functions) GetIntegeri(pname Enum, idx int) int {
syscall.Syscall(_glGetIntegeri_v.Addr(), 3, uintptr(pname), uintptr(idx), uintptr(unsafe.Pointer(&c.int32s[0])))
return int(c.int32s[0])
}
func (c *Functions) GetFloat(pname Enum) float32 {
syscall.Syscall(_glGetFloatv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.float32s[0])), 0)
return c.float32s[0]
}
func (c *Functions) GetFloat4(pname Enum) [4]float32 {
syscall.Syscall(_glGetFloatv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.float32s[0])), 0)
var r [4]float32
copy(r[:], c.float32s[:])
return r
}
func (c *Functions) GetProgrami(p Program, pname Enum) int {
syscall.Syscall(_glGetProgramiv.Addr(), 3, uintptr(p.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
return int(c.int32s[0])
}
func (c *Functions) GetProgramInfoLog(p Program) string {
n := c.GetProgrami(p, INFO_LOG_LENGTH)
if n == 0 {
@@ -550,29 +368,24 @@ func (c *Functions) GetProgramInfoLog(p Program) string {
syscall.Syscall6(_glGetProgramInfoLog.Addr(), 4, uintptr(p.V), uintptr(len(buf)), 0, uintptr(unsafe.Pointer(&buf[0])), 0, 0)
return string(buf)
}
func (c *Functions) GetQueryObjectuiv(query Query, pname Enum) uint {
syscall.Syscall(_glGetQueryObjectuiv.Addr(), 3, uintptr(query.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
return uint(c.int32s[0])
}
func (c *Functions) GetShaderi(s Shader, pname Enum) int {
syscall.Syscall(_glGetShaderiv.Addr(), 3, uintptr(s.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
return int(c.int32s[0])
}
func (c *Functions) GetShaderInfoLog(s Shader) string {
n := c.GetShaderi(s, INFO_LOG_LENGTH)
buf := make([]byte, n)
syscall.Syscall6(_glGetShaderInfoLog.Addr(), 4, uintptr(s.V), uintptr(len(buf)), 0, uintptr(unsafe.Pointer(&buf[0])), 0, 0)
return string(buf)
}
func (c *Functions) GetString(pname Enum) string {
s, _, _ := syscall.Syscall(_glGetString.Addr(), 1, uintptr(pname), 0, 0)
return windows.BytePtrToString((*byte)(unsafe.Pointer(s)))
}
func (c *Functions) GetUniformLocation(p Program, name string) Uniform {
cname := cString(name)
c0 := &cname[0]
@@ -580,7 +393,6 @@ func (c *Functions) GetUniformLocation(p Program, name string) Uniform {
issue34474KeepAlive(c0)
return Uniform{int(u)}
}
func (c *Functions) GetVertexAttrib(index int, pname Enum) int {
syscall.Syscall(_glGetVertexAttribiv.Addr(), 3, uintptr(index), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
return int(c.int32s[0])
@@ -594,7 +406,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])))
return c.uintptrs[0]
}
func (c *Functions) InvalidateFramebuffer(target, attachment Enum) {
addr := _glInvalidateFramebuffer.Addr()
if addr == 0 {
@@ -603,99 +414,77 @@ func (c *Functions) InvalidateFramebuffer(target, attachment Enum) {
}
syscall.Syscall(addr, 3, uintptr(target), 1, uintptr(unsafe.Pointer(&attachment)))
}
func (f *Functions) IsEnabled(cap Enum) bool {
u, _, _ := syscall.Syscall(_glIsEnabled.Addr(), 1, uintptr(cap), 0, 0)
return u == TRUE
}
func (c *Functions) LinkProgram(p Program) {
syscall.Syscall(_glLinkProgram.Addr(), 1, uintptr(p.V), 0, 0)
}
func (c *Functions) PixelStorei(pname Enum, param int) {
syscall.Syscall(_glPixelStorei.Addr(), 2, uintptr(pname), uintptr(param), 0)
}
func (f *Functions) MemoryBarrier(barriers Enum) {
panic("not implemented")
}
func (f *Functions) MapBufferRange(target Enum, offset, length int, access Enum) []byte {
panic("not implemented")
}
func (f *Functions) ReadPixels(x, y, width, height int, format, ty Enum, data []byte) {
d0 := &data[0]
syscall.Syscall9(_glReadPixels.Addr(), 7, uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(format), uintptr(ty), uintptr(unsafe.Pointer(d0)), 0, 0)
issue34474KeepAlive(d0)
}
func (c *Functions) RenderbufferStorage(target, internalformat Enum, width, height int) {
syscall.Syscall6(_glRenderbufferStorage.Addr(), 4, uintptr(target), uintptr(internalformat), uintptr(width), uintptr(height), 0, 0)
}
func (c *Functions) Scissor(x, y, width, height int32) {
syscall.Syscall6(_glScissor.Addr(), 4, uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0, 0)
}
func (c *Functions) ShaderSource(s Shader, src string) {
var n uintptr = uintptr(len(src))
psrc := &src
syscall.Syscall6(_glShaderSource.Addr(), 4, uintptr(s.V), 1, uintptr(unsafe.Pointer(psrc)), uintptr(unsafe.Pointer(&n)), 0, 0)
issue34474KeepAlive(psrc)
}
func (f *Functions) TexImage2D(target Enum, level int, internalFormat Enum, width int, height int, format Enum, ty Enum) {
syscall.Syscall9(_glTexImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(internalFormat), uintptr(width), uintptr(height), 0, uintptr(format), uintptr(ty), 0)
}
func (f *Functions) TexStorage2D(target Enum, levels int, internalFormat Enum, width, height int) {
syscall.Syscall6(_glTexStorage2D.Addr(), 5, uintptr(target), uintptr(levels), uintptr(internalFormat), uintptr(width), uintptr(height), 0)
}
func (c *Functions) TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte) {
d0 := &data[0]
syscall.Syscall9(_glTexSubImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(format), uintptr(ty), uintptr(unsafe.Pointer(d0)))
issue34474KeepAlive(d0)
}
func (c *Functions) TexParameteri(target, pname Enum, param int) {
syscall.Syscall(_glTexParameteri.Addr(), 3, uintptr(target), uintptr(pname), uintptr(param))
}
func (f *Functions) UniformBlockBinding(p Program, uniformBlockIndex uint, uniformBlockBinding uint) {
syscall.Syscall(_glUniformBlockBinding.Addr(), 3, uintptr(p.V), uintptr(uniformBlockIndex), uintptr(uniformBlockBinding))
}
func (c *Functions) Uniform1f(dst Uniform, v float32) {
syscall.Syscall(_glUniform1f.Addr(), 2, uintptr(dst.V), uintptr(math.Float32bits(v)), 0)
}
func (c *Functions) Uniform1i(dst Uniform, v int) {
syscall.Syscall(_glUniform1i.Addr(), 2, uintptr(dst.V), uintptr(v), 0)
}
func (c *Functions) Uniform2f(dst Uniform, v0, v1 float32) {
syscall.Syscall(_glUniform2f.Addr(), 3, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)))
}
func (c *Functions) Uniform3f(dst Uniform, v0, v1, v2 float32) {
syscall.Syscall6(_glUniform3f.Addr(), 4, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)), uintptr(math.Float32bits(v2)), 0, 0)
}
func (c *Functions) Uniform4f(dst Uniform, v0, v1, v2, v3 float32) {
syscall.Syscall6(_glUniform4f.Addr(), 5, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)), uintptr(math.Float32bits(v2)), uintptr(math.Float32bits(v3)), 0)
}
func (c *Functions) UseProgram(p Program) {
syscall.Syscall(_glUseProgram.Addr(), 1, uintptr(p.V), 0, 0)
}
func (f *Functions) UnmapBuffer(target Enum) bool {
panic("not implemented")
}
func (c *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int) {
var norm uintptr
if normalized {
@@ -703,7 +492,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))
}
func (c *Functions) Viewport(x, y, width, height int) {
syscall.Syscall6(_glViewport.Addr(), 4, uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0, 0)
}
@@ -716,6 +504,6 @@ func cString(s string) []byte {
// issue34474KeepAlive calls runtime.KeepAlive as a
// workaround for golang.org/issue/34474.
func issue34474KeepAlive(v any) {
func issue34474KeepAlive(v interface{}) {
runtime.KeepAlive(v)
}
+1
View File
@@ -1,4 +1,5 @@
//go:build !js
// +build !js
package gl
+5 -5
View File
@@ -18,7 +18,7 @@ type Ops struct {
// data contains the serialized operations.
data []byte
// refs hold external references for operations.
refs []any
refs []interface{}
// stringRefs provides space for string references, pointers to which will
// be stored in refs. Storing a string directly in refs would cause a heap
// allocation, to store the string header in an interface value. The backing
@@ -256,7 +256,7 @@ func PopOp(o *Ops, kind StackKind, sid StackID, macroID uint32) {
o.stacks[kind].pop(sid)
}
func Write1(o *Ops, n int, ref1 any) []byte {
func Write1(o *Ops, n int, ref1 interface{}) []byte {
o.data = append(o.data, make([]byte, n)...)
o.refs = append(o.refs, ref1)
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:]
}
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.refs = append(o.refs, ref1, ref2)
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.stringRefs = append(o.stringRefs, ref2)
o.refs = append(o.refs, ref1, &o.stringRefs[len(o.stringRefs)-1])
return o.data[len(o.data)-n:]
}
func Write3(o *Ops, n int, ref1, ref2, ref3 any) []byte {
func Write3(o *Ops, n int, ref1, ref2, ref3 interface{}) []byte {
o.data = append(o.data, make([]byte, n)...)
o.refs = append(o.refs, ref1, ref2, ref3)
return o.data[len(o.data)-n:]
+2 -2
View File
@@ -20,7 +20,7 @@ type Reader struct {
type EncodedOp struct {
Key Key
Data []byte
Refs []any
Refs []interface{}
}
// Key is a unique key for a given op.
@@ -175,7 +175,7 @@ func (op *opMacroDef) decode(data []byte) {
op.endpc.refs = bo.Uint32(data[5:])
}
func (m *macroOp) decode(data []byte, refs []any) {
func (m *macroOp) decode(data []byte, refs []interface{}) {
if len(data) < TypeCallLen || len(refs) < 1 || OpType(data[0]) != TypeCall {
panic("invalid op")
}
+12 -21
View File
@@ -87,7 +87,7 @@ func (qs *StrokeQuads) lineTo(pt f32.Point) {
func (qs *StrokeQuads) arc(f1, f2 f32.Point, angle float32) {
pen := qs.pen()
m, segments := ArcTransform(pen, f1.Add(pen), f2.Add(pen), angle)
for range segments {
for i := 0; i < segments; i++ {
p0 := qs.pen()
p1 := m.Transform(p0)
p2 := m.Transform(p1)
@@ -327,26 +327,13 @@ func strokePathNorm(p0, p1, p2 f32.Point, t, d float32) f32.Point {
func rot90CW(p f32.Point) f32.Point { return f32.Pt(+p.Y, -p.X) }
func normPt(p f32.Point, l float32) f32.Point {
if (p.X == 0 && p.Y == 0) || l == 0 {
return f32.Point{}
}
isVerticalUnit := p.X == 0 && (p.Y == l || p.Y == -l)
isHorizontalUnit := p.Y == 0 && (p.X == l || p.X == -l)
if isVerticalUnit || isHorizontalUnit {
if math.Signbit(float64(l)) {
return f32.Point{X: -p.X, Y: -p.Y}
} else {
return f32.Point{X: p.X, Y: p.Y}
}
if (p.X == 0 && p.Y == l) || (p.Y == 0 && p.X == l) {
return f32.Point{X: p.X, Y: p.Y}
}
d := math.Hypot(float64(p.X), float64(p.Y))
l64 := float64(l)
if math.Abs(d-l64) < 1e-10 {
if math.Signbit(float64(l)) {
return f32.Point{X: -p.X, Y: -p.Y}
} else {
return f32.Point{X: p.X, Y: p.Y}
}
return f32.Point{}
}
n := float32(l64 / d)
return f32.Point{X: p.X * n, Y: p.Y * n}
@@ -453,6 +440,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) {
switch i := len(*qs); i {
case 0:
p0 = p0.Add(strokePathNorm(p0, ctrl, p1, 0, d))
@@ -485,6 +473,7 @@ func quadInterp(p, q f32.Point, t float32) f32.Point {
// quadBezierSplit returns the pair of triplets (from,ctrl,to) Bézier curve,
// split before (resp. after) the provided parametric t value.
func quadBezierSplit(p0, p1, p2 f32.Point, t float32) (f32.Point, f32.Point, f32.Point, f32.Point, f32.Point, f32.Point) {
var (
b0 = p0
b1 = quadInterp(p0, p1, t)
@@ -588,10 +577,12 @@ func ArcTransform(p, f1, f2 f32.Point, angle float32) (transform f32.Affine2D, s
}
}
θ := angle / float32(segments)
ref := f32.AffineId() // transform from absolute frame to ellipse-based one
rot := f32.AffineId() // rotation matrix for each segment
inv := f32.AffineId() // transform from ellipse-based frame to absolute one
var (
θ = angle / float32(segments)
ref f32.Affine2D // transform from absolute frame to ellipse-based one
rot f32.Affine2D // rotation matrix for each segment
inv f32.Affine2D // transform from ellipse-based frame to absolute one
)
center := f32.Point{
X: 0.5 * (f1.X + f2.X),
Y: 0.5 * (f1.Y + f2.Y),
+2 -106
View File
@@ -9,111 +9,6 @@ import (
"gioui.org/internal/f32"
)
func TestNormPt(t *testing.T) {
type scenario struct {
l float32
ptIn, ptOut f32.Point
}
scenarios := []scenario{
// l!=0 && X=Y=0
{l: 10, ptIn: f32.Point{X: 0, Y: 0}, ptOut: f32.Point{X: 0, Y: 0}},
{l: -10, ptIn: f32.Point{X: 0, Y: 0}, ptOut: f32.Point{X: 0, Y: 0}},
// l>0 & X
{l: +20, ptIn: f32.Point{X: +30, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
{l: +20, ptIn: f32.Point{X: +20, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
{l: +20, ptIn: f32.Point{X: +10, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
{l: +20, ptIn: f32.Point{X: -10, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
{l: +20, ptIn: f32.Point{X: -20, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
{l: +20, ptIn: f32.Point{X: -30, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
// l<0 & X
{l: -20, ptIn: f32.Point{X: +30, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
{l: -20, ptIn: f32.Point{X: +20, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
{l: -20, ptIn: f32.Point{X: +10, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
{l: -20, ptIn: f32.Point{X: -10, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
{l: -20, ptIn: f32.Point{X: -20, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
{l: -20, ptIn: f32.Point{X: -30, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
// l>0 & Y
{l: +20, ptIn: f32.Point{X: 0, Y: +30}, ptOut: f32.Point{X: 0, Y: +20}},
{l: +20, ptIn: f32.Point{X: 0, Y: +20}, ptOut: f32.Point{X: 0, Y: +20}},
{l: +20, ptIn: f32.Point{X: 0, Y: +10}, ptOut: f32.Point{X: 0, Y: +20}},
{l: +20, ptIn: f32.Point{X: 0, Y: -10}, ptOut: f32.Point{X: 0, Y: -20}},
{l: +20, ptIn: f32.Point{X: 0, Y: -20}, ptOut: f32.Point{X: 0, Y: -20}},
{l: +20, ptIn: f32.Point{X: 0, Y: -30}, ptOut: f32.Point{X: 0, Y: -20}},
// l<0 & Y
{l: -20, ptIn: f32.Point{X: 0, Y: +30}, ptOut: f32.Point{X: 0, Y: -20}},
{l: -20, ptIn: f32.Point{X: 0, Y: +20}, ptOut: f32.Point{X: 0, Y: -20}},
{l: -20, ptIn: f32.Point{X: 0, Y: +10}, ptOut: f32.Point{X: 0, Y: -20}},
{l: -20, ptIn: f32.Point{X: 0, Y: -10}, ptOut: f32.Point{X: 0, Y: +20}},
{l: -20, ptIn: f32.Point{X: 0, Y: -20}, ptOut: f32.Point{X: 0, Y: +20}},
{l: -20, ptIn: f32.Point{X: 0, Y: -30}, ptOut: f32.Point{X: 0, Y: +20}},
// l>0 && X=Y
{l: +20, ptIn: f32.Point{X: +90, Y: +90}, ptOut: f32.Point{X: +14.142137, Y: +14.142137}},
{l: +20, ptIn: f32.Point{X: +30, Y: +30}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
{l: +20, ptIn: f32.Point{X: +20, Y: +20}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
{l: +20, ptIn: f32.Point{X: +10, Y: +10}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
{l: +20, ptIn: f32.Point{X: -10, Y: -10}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
{l: +20, ptIn: f32.Point{X: -20, Y: -20}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
{l: +20, ptIn: f32.Point{X: -30, Y: -30}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
{l: +20, ptIn: f32.Point{X: -90, Y: -90}, ptOut: f32.Point{X: -14.142137, Y: -14.142137}},
// l>0 && X=-Y
{l: +20, ptIn: f32.Point{X: +90, Y: -90}, ptOut: f32.Point{X: +14.142137, Y: -14.142137}},
{l: +20, ptIn: f32.Point{X: +30, Y: -30}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
{l: +20, ptIn: f32.Point{X: +20, Y: -20}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
{l: +20, ptIn: f32.Point{X: +10, Y: -10}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
{l: +20, ptIn: f32.Point{X: -10, Y: +10}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
{l: +20, ptIn: f32.Point{X: -20, Y: +20}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
{l: +20, ptIn: f32.Point{X: -30, Y: +30}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
{l: +20, ptIn: f32.Point{X: -90, Y: +90}, ptOut: f32.Point{X: -14.142137, Y: +14.142137}},
// l<0 && X=Y
{l: -20, ptIn: f32.Point{X: +90, Y: +90}, ptOut: f32.Point{X: -14.142137, Y: -14.142137}},
{l: -20, ptIn: f32.Point{X: +30, Y: +30}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
{l: -20, ptIn: f32.Point{X: +20, Y: +20}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
{l: -20, ptIn: f32.Point{X: +10, Y: +10}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
{l: -20, ptIn: f32.Point{X: -10, Y: -10}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
{l: -20, ptIn: f32.Point{X: -20, Y: -20}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
{l: -20, ptIn: f32.Point{X: -30, Y: -30}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
{l: -20, ptIn: f32.Point{X: -90, Y: -90}, ptOut: f32.Point{X: +14.142137, Y: +14.142137}},
// l<0 && X=-Y
{l: -20, ptIn: f32.Point{X: +90, Y: -90}, ptOut: f32.Point{X: -14.142137, Y: +14.142137}},
{l: -20, ptIn: f32.Point{X: +30, Y: -30}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
{l: -20, ptIn: f32.Point{X: +20, Y: -20}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
{l: -20, ptIn: f32.Point{X: +10, Y: -10}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
{l: -20, ptIn: f32.Point{X: -10, Y: +10}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
{l: -20, ptIn: f32.Point{X: -20, Y: +20}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
{l: -20, ptIn: f32.Point{X: -30, Y: +30}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
{l: -20, ptIn: f32.Point{X: -90, Y: +90}, ptOut: f32.Point{X: +14.142137, Y: -14.142137}},
// l!=0 && Hypot=l
{l: 5, ptIn: f32.Point{X: 3, Y: 4}, ptOut: f32.Point{X: 3, Y: 4}},
{l: 5, ptIn: f32.Point{X: 3, Y: -4}, ptOut: f32.Point{X: 3, Y: -4}},
{l: 5, ptIn: f32.Point{X: -3, Y: -4}, ptOut: f32.Point{X: -3, Y: -4}},
{l: 5, ptIn: f32.Point{X: -3, Y: 4}, ptOut: f32.Point{X: -3, Y: 4}},
{l: -5, ptIn: f32.Point{X: 3, Y: 4}, ptOut: f32.Point{X: -3, Y: -4}},
{l: -5, ptIn: f32.Point{X: 3, Y: -4}, ptOut: f32.Point{X: -3, Y: 4}},
{l: -5, ptIn: f32.Point{X: -3, Y: -4}, ptOut: f32.Point{X: 3, Y: 4}},
{l: -5, ptIn: f32.Point{X: -3, Y: 4}, ptOut: f32.Point{X: 3, Y: -4}},
}
for i, s := range scenarios {
t.Run(strconv.Itoa(i), func(t *testing.T) {
actual := normPt(s.ptIn, s.l)
if actual != s.ptOut {
t.Errorf("in: %v*%v, expected: %v, actual: %v", s.l, s.ptIn, s.ptOut, actual)
}
})
}
}
func BenchmarkSplitCubic(b *testing.B) {
type scenario struct {
segments int
@@ -152,11 +47,12 @@ func BenchmarkSplitCubic(b *testing.B) {
}
for _, s := range scenarios {
s := s
b.Run(strconv.Itoa(s.segments), func(b *testing.B) {
from, ctrl0, ctrl1, to := s.from, s.ctrl0, s.ctrl1, s.to
quads := make([]QuadSegment, s.segments)
b.ResetTimer()
for b.Loop() {
for i := 0; i < b.N; i++ {
quads = SplitCubic(from, ctrl0, ctrl1, to, quads[:0])
}
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 (
"errors"
"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 fbo, nil
}
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
}
func PushConstantRangeOffset(r PushConstantRange) int {
func (r PushConstantRange) Offset() int {
return int(r.offset)
}
func PushConstantRangeSize(r PushConstantRange) int {
func (r PushConstantRange) Size() int {
return int(r.size)
}
func QueueFamilyPropertiesFlags(p QueueFamilyProperties) QueueFlags {
func (p QueueFamilyProperties) Flags() 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))
}
func SurfaceCapabilitiesMaxExtent(c SurfaceCapabilities) image.Point {
func (c SurfaceCapabilities) MaxExtent() image.Point {
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 (
"fmt"
"unsafe"
-1
View File
@@ -19,7 +19,6 @@ static VkResult vkCreateWaylandSurfaceKHR(PFN_vkCreateWaylandSurfaceKHR f, VkIns
}
*/
import "C"
import (
"fmt"
"unsafe"
-1
View File
@@ -17,7 +17,6 @@ static VkResult vkCreateXlibSurfaceKHR(PFN_vkCreateXlibSurfaceKHR f, VkInstance
}
*/
import "C"
import (
"fmt"
"unsafe"
+1 -1
View File
@@ -10,7 +10,7 @@ import (
// Tag is the stable identifier for an event handler.
// For a handler h, the tag is typically &h.
type Tag any
type Tag interface{}
// Event is the marker interface for events.
type Event interface {
+4 -3
View File
@@ -4,7 +4,6 @@ package input
import (
"io"
"slices"
"gioui.org/io/clipboard"
"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 {
if slices.Contains(state.receivers, tag) {
return state
for _, k := range state.receivers {
if k == tag {
return state
}
}
n := len(state.receivers)
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)
ops.Reset()
for range 3 {
for i := 0; i < 3; i++ {
// No ReadCmd
// One receiver must still wait for response
+4 -4
View File
@@ -4,7 +4,6 @@ package input
import (
"image"
"slices"
"sort"
"gioui.org/f32"
@@ -281,7 +280,6 @@ func (q *keyQueue) Focus(handlers map[event.Tag]*handler, state keyState, focus
return state, nil
}
state.content = EditorState{}
state.content.Selection.Transform = f32.AffineId()
var evts []taggedEvent
if state.focus != nil {
evts = append(evts, taggedEvent{tag: state.focus, event: key.FocusEvent{Focus: false}})
@@ -306,8 +304,10 @@ func (s keyState) softKeyboard(show bool) keyState {
}
func (k *keyFilter) Add(f key.Filter) {
if slices.Contains(*k, f) {
return
for _, f2 := range *k {
if f == f2 {
return
}
}
*k = append(*k, f)
}
+36 -26
View File
@@ -5,7 +5,6 @@ package input
import (
"image"
"io"
"slices"
"gioui.org/f32"
f32internal "gioui.org/internal/f32"
@@ -144,9 +143,7 @@ const (
)
func (c *pointerCollector) resetState() {
c.state = collectState{
t: f32.AffineId(),
}
c.state = collectState{}
c.nodeStack = c.nodeStack[:0]
// Pop every node except the root.
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 {
continue
}
// Verify that the grabber is among the handlers.
found := slices.Contains(p.handlers, req.Tag)
if !found {
continue
}
// Drop other handlers that lost their grab.
for i := len(p.handlers) - 1; i >= 0; i-- {
if tag := p.handlers[i]; tag != req.Tag {
@@ -290,13 +282,17 @@ func (c *pointerCollector) inputOp(tag event.Tag, state *pointerHandler) {
func (p *pointerFilter) Add(f event.Filter) {
switch f := f.(type) {
case transfer.SourceFilter:
if slices.Contains(p.sourceMimes, f.Type) {
return
for _, m := range p.sourceMimes {
if m == f.Type {
return
}
}
p.sourceMimes = append(p.sourceMimes, f.Type)
case transfer.TargetFilter:
if slices.Contains(p.targetMimes, f.Type) {
return
for _, m := range p.targetMimes {
if m == f.Type {
return
}
}
p.targetMimes = append(p.targetMimes, f.Type)
case pointer.Filter:
@@ -313,12 +309,16 @@ func (p *pointerFilter) Matches(e event.Event) bool {
case transfer.CancelEvent, transfer.InitiateEvent:
return len(p.sourceMimes) > 0 || len(p.targetMimes) > 0
case transfer.RequestEvent:
if slices.Contains(p.sourceMimes, e.Type) {
return true
for _, t := range p.sourceMimes {
if t == e.Type {
return true
}
}
case transfer.DataEvent:
if slices.Contains(p.targetMimes, e.Type) {
return true
for _, t := range p.targetMimes {
if t == e.Type {
return true
}
}
}
return false
@@ -413,7 +413,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)
break
}
@@ -605,7 +605,7 @@ func (q *pointerQueue) reset() {
for k, ids := range q.semantic.contentIDs {
for i := len(ids) - 1; i >= 0; i-- {
if !ids[i].used {
ids = slices.Delete(ids, i, i+1)
ids = append(ids[:i], ids[i+1:]...)
} else {
ids[i].used = false
}
@@ -636,7 +636,7 @@ func (q *pointerQueue) Frame(handlers map[event.Tag]*handler, state pointerState
changed := false
p, evts, state.cursor, changed = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, p.last)
if changed {
state.pointers = slices.Clone(state.pointers)
state.pointers = append([]pointerInfo{}, state.pointers...)
state.pointers[i] = p
}
}
@@ -772,17 +772,19 @@ func (q *pointerQueue) Push(handlers map[event.Tag]*handler, state pointerState,
if !p.pressed && len(p.entered) == 0 {
// 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 {
state.pointers = slices.Clone(state.pointers)
state.pointers = append([]pointerInfo{}, state.pointers...)
state.pointers[pidx] = p
}
return state, evts
}
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 {
e.Priority = pointer.Grabbed
foremost = false
}
scroll := e.Scroll
for _, k := range p.handlers {
@@ -801,6 +803,10 @@ func (q *pointerQueue) deliverEvent(handlers map[event.Tag]*handler, p pointerIn
scroll, e.Scroll = f.clampScroll(scroll)
}
e := e
if foremost {
foremost = false
e.Priority = pointer.Foremost
}
e.Position = q.invTransform(h.pointer.areaPlusOne-1, e.Position)
evts = append(evts, taggedEvent{event: e, tag: k})
}
@@ -964,8 +970,10 @@ func searchTag(tags []event.Tag, tag event.Tag) (int, bool) {
// addHandler adds tag to the slice if not present.
func addHandler(tags []event.Tag, tag event.Tag) []event.Tag {
if slices.Contains(tags, tag) {
return tags
for _, t := range tags {
if t == tag {
return tags
}
}
return append(tags, tag)
}
@@ -973,8 +981,10 @@ func addHandler(tags []event.Tag, tag event.Tag) []event.Tag {
// firstMimeMatch returns the first type match between src and tgt.
func firstMimeMatch(src, tgt *pointerFilter) (first string, matched bool) {
for _, m1 := range tgt.targetMimes {
if slices.Contains(src.sourceMimes, m1) {
return m1, true
for _, m2 := range src.sourceMimes {
if m1 == m2 {
return m1, true
}
}
}
return "", false
+14 -55
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)
}
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) {
handler1 := new(int)
handler2 := new(int)
@@ -400,9 +367,9 @@ func TestPointerPriority(t *testing.T) {
assertEventPointerTypeSequence(t, hev1, pointer.Scroll, pointer.Scroll)
assertEventPointerTypeSequence(t, hev2, pointer.Scroll)
assertEventPointerTypeSequence(t, hev3, pointer.Scroll)
assertEventPriorities(t, hev1, pointer.Shared, pointer.Shared)
assertEventPriorities(t, hev2, pointer.Shared)
assertEventPriorities(t, hev3, pointer.Shared)
assertEventPriorities(t, hev1, pointer.Shared, pointer.Foremost)
assertEventPriorities(t, hev2, pointer.Foremost)
assertEventPriorities(t, hev3, pointer.Foremost)
assertScrollEvent(t, hev1[0], f32.Pt(30, 0))
assertScrollEvent(t, hev2[0], f32.Pt(20, 0))
assertScrollEvent(t, hev1[1], f32.Pt(50, 0))
@@ -711,31 +678,26 @@ func TestCursor(t *testing.T) {
cursors []pointer.Cursor
want pointer.Cursor
}{
{
label: "no movement",
{label: "no movement",
cursors: []pointer.Cursor{pointer.CursorPointer},
want: pointer.CursorDefault,
},
{
label: "move inside",
{label: "move inside",
cursors: []pointer.Cursor{pointer.CursorPointer},
events: _at(50, 50),
want: pointer.CursorPointer,
},
{
label: "move outside",
{label: "move outside",
cursors: []pointer.Cursor{pointer.CursorPointer},
events: _at(200, 200),
want: pointer.CursorDefault,
},
{
label: "move back inside",
{label: "move back inside",
cursors: []pointer.Cursor{pointer.CursorPointer},
events: _at(50, 50),
want: pointer.CursorPointer,
},
{
label: "send key events while inside",
{label: "send key events while inside",
cursors: []pointer.Cursor{pointer.CursorPointer},
events: []event.Event{
key.Event{Name: "A", State: key.Press},
@@ -743,8 +705,7 @@ func TestCursor(t *testing.T) {
},
want: pointer.CursorPointer,
},
{
label: "send key events while outside",
{label: "send key events while outside",
cursors: []pointer.Cursor{pointer.CursorPointer},
events: append(
_at(200, 200),
@@ -753,8 +714,7 @@ func TestCursor(t *testing.T) {
),
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},
events: append(
_at(50, 50),
@@ -765,8 +725,7 @@ func TestCursor(t *testing.T) {
),
want: pointer.CursorCrosshair,
},
{
label: "remove input on top while inside",
{label: "remove input on top while inside",
cursors: []pointer.Cursor{pointer.CursorPointer},
events: append(
_at(50, 50),
@@ -1151,7 +1110,7 @@ func TestPartialEvent(t *testing.T) {
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})
pointer.Event{Kind: pointer.Press, Source: pointer.Mouse, Priority: pointer.Foremost})
}
// offer satisfies io.ReadCloser for use in data transfers.
@@ -1297,7 +1256,7 @@ func BenchmarkRouterAdd(b *testing.B) {
handlerCount := i
b.Run(fmt.Sprintf("%d-handlers", i), func(b *testing.B) {
handlers := make([]event.Tag, handlerCount)
for i := range handlerCount {
for i := 0; i < handlerCount; i++ {
h := new(int)
*h = i
handlers[i] = h
@@ -1319,7 +1278,7 @@ func BenchmarkRouterAdd(b *testing.B) {
r.Frame(&ops)
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
for i := 0; i < b.N; i++ {
r.Queue(
pointer.Event{
Kind: pointer.Move,
+13 -25
View File
@@ -5,7 +5,6 @@ package input
import (
"image"
"io"
"slices"
"strings"
"time"
@@ -61,10 +60,9 @@ type Router struct {
}
// 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 {
r *Router
disabled bool
r *Router
}
// Command represents a request such as moving the focus, or initiating a clipboard read.
@@ -179,17 +177,10 @@ func (s Source) Execute(c Command) {
s.r.execute(c)
}
// Disabled returns a copy of this source that don't deliver any events.
func (s Source) Disabled() Source {
s2 := s
s2.disabled = true
return s2
}
// Enabled reports whether the source is enabled. Only enabled
// Sources deliver events.
// Sources deliver events and respond to commands.
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
@@ -202,7 +193,6 @@ func (s Source) Focused(tag event.Tag) bool {
}
// Event returns the next event that matches at least one of filters.
// If the source is disabled, no events will be reported.
func (s Source) Event(filters ...event.Filter) (event.Event, bool) {
if !s.Enabled() {
return nil, false
@@ -303,7 +293,7 @@ func (q *Router) Event(filters ...event.Filter) (event.Event, bool) {
}
}
if match {
change.events = slices.Delete(change.events, j, j+1)
change.events = append(change.events[:j], change.events[j+1:]...)
// Fast forward state to last matched.
q.collapseState(i)
return evt.event, true
@@ -628,11 +618,11 @@ func (q *Router) RevealFocus(viewport image.Rectangle) {
viewport = q.pointer.queue.ClipFor(area, viewport)
topleft := bounds.Min.Sub(viewport.Min)
topleft = maxPoint(topleft, bounds.Max.Sub(viewport.Max))
topleft = minPoint(image.Pt(0, 0), topleft)
topleft = max(topleft, bounds.Max.Sub(viewport.Max))
topleft = min(image.Pt(0, 0), topleft)
bottomright := bounds.Max.Sub(viewport.Max)
bottomright = minPoint(bottomright, bounds.Min.Sub(viewport.Min))
bottomright = maxPoint(image.Pt(0, 0), bottomright)
bottomright = min(bottomright, bounds.Min.Sub(viewport.Min))
bottomright = max(image.Pt(0, 0), bottomright)
s := topleft
if s.X == 0 {
s.X = bottomright.X
@@ -659,7 +649,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
if p2.X > m.X {
m.X = p2.X
@@ -670,7 +660,7 @@ func maxPoint(p1, p2 image.Point) image.Point {
return m
}
func minPoint(p1, p2 image.Point) image.Point {
func min(p1, p2 image.Point) image.Point {
m := p1
if p2.X < m.X {
m.X = p2.X
@@ -779,15 +769,13 @@ func (q *Router) collect() {
pc.Reset()
kq := &q.key.queue
q.key.queue.Reset()
t := f32.AffineId()
var t f32.Affine2D
for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() {
switch ops.OpType(encOp.Data[0]) {
case ops.TypeSave:
id := ops.DecodeSave(encOp.Data)
if extra := id - len(q.savedTrans) + 1; extra > 0 {
for range extra {
q.savedTrans = append(q.savedTrans, f32.AffineId())
}
q.savedTrans = append(q.savedTrans, make([]f32.Affine2D, extra)...)
}
q.savedTrans[id] = t
case ops.TypeLoad:
+1 -1
View File
@@ -15,7 +15,7 @@ func TestNoFilterAllocs(t *testing.T) {
s := r.Source()
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
for i := 0; i < b.N; i++ {
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) {
for range indent {
for i := 0; i < indent; i++ {
fmt.Print("\t")
}
fmt.Printf("%d: %+v\n", n.ID, n.Desc)
+2 -1
View File
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build !darwin && !js
//go:build !darwin
// +build !darwin
package key
-38
View File
@@ -1,38 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package key
import (
"strings"
"syscall/js"
)
// ModShortcut is the platform's shortcut modifier, usually the ctrl
// modifier. On Apple platforms it is the cmd key.
var ModShortcut = ModCtrl
// ModShortcut is the platform's alternative shortcut modifier,
// usually the ctrl modifier. On Apple platforms it is the alt modifier.
var ModShortcutAlt = ModCtrl
func init() {
nav := js.Global().Get("navigator")
if !nav.Truthy() {
return // Almost impossible to happen
}
platform := ""
if p := nav.Get("platform"); p.Truthy() {
platform = p.String()
}
platform = strings.ToLower(platform)
// Based on https://developer.mozilla.org/en-US/docs/Web/API/Navigator/platform#examples
for _, darwinPlatform := range []string{"mac", "iphone", "ipad", "ipod"} {
if strings.HasPrefix(platform, darwinPlatform) {
ModShortcut = ModCommand
ModShortcutAlt = ModAlt
return
}
}
}
+8 -14
View File
@@ -19,7 +19,7 @@ type Event struct {
Source Source
// PointerID is the id for the pointer and can be used
// to track a particular pointer from Press to
// Release.
// Release or Cancel.
PointerID ID
// Priority is the priority of the receiving handler
// for this event.
@@ -43,7 +43,8 @@ type Event struct {
// PassOp sets the pass-through mode. InputOps added while the pass-through
// mode is set don't block events to siblings.
type PassOp struct{}
type PassOp struct {
}
// PassStack represents a PassOp on the pass stack.
type PassStack struct {
@@ -206,6 +207,9 @@ const (
// Shared priority is for handlers that
// are part of a matching set larger than 1.
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
)
@@ -219,12 +223,6 @@ const (
ButtonSecondary
// ButtonTertiary is the tertiary button, usually the middle button.
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 {
@@ -297,6 +295,8 @@ func (p Priority) String() string {
switch p {
case Shared:
return "Shared"
case Foremost:
return "Foremost"
case Grabbed:
return "Grabbed"
default:
@@ -332,12 +332,6 @@ func (b Buttons) String() string {
if b.Contain(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, "|")
}
+1
View File
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build !race
// +build !race
package layout
+3 -6
View File
@@ -28,10 +28,6 @@ type Context struct {
// Interested users must look up and populate these values manually.
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
*op.Ops
}
@@ -46,8 +42,9 @@ func (c Context) Sp(v unit.Sp) int {
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 {
c.Source = c.Source.Disabled()
c.Source = input.Source{}
return c
}
+14 -38
View File
@@ -22,8 +22,6 @@ type Flex struct {
// size of Flexed children. If WeightSum is zero, the sum
// of all Flexed weights is used.
WeightSum float32
// Gap is the space in pixels between children.
Gap int
}
// FlexChild is the descriptor for a Flex child.
@@ -32,6 +30,10 @@ type FlexChild struct {
weight float32
widget Widget
// Scratch space.
call op.CallOp
dims Dimensions
}
// Spacing determine the spacing mode for a Flex.
@@ -84,30 +86,8 @@ func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions {
mainMin, mainMax := f.Axis.mainConstraint(cs)
crossMin, crossMax := f.Axis.crossConstraint(cs)
remaining := mainMax
// Reserve space for gaps between children.
if len(children) > 1 && f.Gap > 0 {
totalGap := f.Gap * (len(children) - 1)
remaining -= totalGap
if remaining < 0 {
remaining = 0
}
}
var totalWeight float32
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.
for i, child := range children {
if child.flex {
@@ -124,8 +104,8 @@ func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions {
if remaining < 0 {
remaining = 0
}
scratch[i].call = c
scratch[i].dims = dims
children[i].call = c
children[i].dims = dims
}
if w := f.WeightSum; w != 0 {
totalWeight = w
@@ -159,22 +139,19 @@ func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions {
if remaining < 0 {
remaining = 0
}
scratch[i].call = c
scratch[i].dims = dims
children[i].call = c
children[i].dims = dims
}
maxCross := crossMin
var maxBaseline int
for _, scratchChild := range scratch {
if c := f.Axis.Convert(scratchChild.dims.Size).Y; c > maxCross {
for _, child := range children {
if c := f.Axis.Convert(child.dims.Size).Y; c > maxCross {
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
}
}
if len(children) > 1 && f.Gap > 0 {
size += f.Gap * (len(children) - 1)
}
var space int
if mainMin > size {
space = mainMin - size
@@ -192,8 +169,8 @@ func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions {
mainSize += space / (len(children) * 2)
}
}
for i, scratchChild := range scratch {
dims := scratchChild.dims
for i, child := range children {
dims := child.dims
b := dims.Size.Y - dims.Baseline
var cross int
switch f.Alignment {
@@ -208,11 +185,10 @@ func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions {
}
pt := f.Axis.Convert(image.Pt(mainSize, cross))
trans := op.Offset(pt).Push(gtx.Ops)
scratchChild.call.Add(gtx.Ops)
child.call.Add(gtx.Ops)
trans.Pop()
mainSize += f.Axis.Convert(dims.Size).X
if i < len(children)-1 {
mainSize += f.Gap
switch f.Spacing {
case SpaceEvenly:
mainSize += space / (1 + len(children))

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