mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
Compare commits
91 Commits
v0.8.0
...
a8fe27488f
| Author | SHA1 | Date | |
|---|---|---|---|
| a8fe27488f | |||
| 06307313cd | |||
| 15335a2b37 | |||
| acf5635575 | |||
| caccb608a5 | |||
| d52632b475 | |||
| dec57aea1c | |||
| e2e2c1a046 | |||
| e8c1e1ba11 | |||
| b1cadbdd76 | |||
| 451b7d3a74 | |||
| e49c5b02c7 | |||
| dfe4ff0200 | |||
| 65d86895b8 | |||
| c3a6e85f5c | |||
| 8c2e45b8f8 | |||
| 47ab4c97b2 | |||
| 760369174d | |||
| 92fa23b59b | |||
| f98baf7f76 | |||
| a6da4083de | |||
| bbb54d5f54 | |||
| 8b96643490 | |||
| 4ed9695d57 | |||
| 9b38545fc2 | |||
| 0d08eaa55c | |||
| 9ab8095d1a | |||
| 3d6cafa94d | |||
| 9966e922f9 | |||
| 3af0ebb3a8 | |||
| 99647591f6 | |||
| e38f80adc6 | |||
| 93419a77bd | |||
| c250d7d562 | |||
| 6e5bbfe8d4 | |||
| 42bc707f7c | |||
| 7bcb315ee1 | |||
| 74671a7f9e | |||
| f48cc2c47f | |||
| 818061a18a | |||
| 45963441c1 | |||
| be8d9df848 | |||
| 3f4f8ba7c1 | |||
| 6553915e59 | |||
| 4c0e526c0b | |||
| f45039734f | |||
| 30f8ac10b7 | |||
| 176570527d | |||
| 36a2fa37c7 | |||
| bbb6d05f09 | |||
| a274f6fe0f | |||
| 0c145b3815 | |||
| ba82ae46d0 | |||
| 4e5a344cc2 | |||
| 78b54615cc | |||
| 31564b98c9 | |||
| 3b1effb7f5 | |||
| d76b4272aa | |||
| 3e601e73c4 | |||
| b2b12d6288 | |||
| b2f6707ad1 | |||
| 420f4c32f4 | |||
| ea979b436d | |||
| d50ef687b8 | |||
| 6ce7ffa4ca | |||
| c3ce484b5e | |||
| 8104d527c7 | |||
| 809a6d0dc7 | |||
| 0eac4f2c6a | |||
| 1a17e9ea37 | |||
| 0a209f7d39 | |||
| 0225334124 | |||
| f73287be87 | |||
| 86668e8b45 | |||
| e18db64991 | |||
| efd31ad621 | |||
| 35ec76e516 | |||
| cc6048bc25 | |||
| 016714a668 | |||
| a3117d3823 | |||
| fff2375470 | |||
| 72a72a2bc2 | |||
| 14a9fbcc0d | |||
| 0d23240556 | |||
| 77709d1771 | |||
| 4f720af6f2 | |||
| af446e8b87 | |||
| 95354d80bd | |||
| a5068a1996 | |||
| 593c5fbf4a | |||
| adaace864d |
+2
-2
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: Unlicense OR MIT
|
||||
image: debian/testing
|
||||
image: debian/stable
|
||||
packages:
|
||||
- clang
|
||||
- cmake
|
||||
@@ -29,7 +29,7 @@ environment:
|
||||
tasks:
|
||||
- install_go: |
|
||||
mkdir -p /home/build/sdk
|
||||
curl -s https://dl.google.com/go/go1.22.2.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
curl -s https://dl.google.com/go/go1.24.2.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
- prepare_toolchain: |
|
||||
mkdir -p $APPLE_TOOLCHAIN_ROOT
|
||||
cd $APPLE_TOOLCHAIN_ROOT
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ environment:
|
||||
tasks:
|
||||
- install_go: |
|
||||
mkdir -p /home/build/sdk
|
||||
curl https://dl.google.com/go/go1.22.2.freebsd-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
curl https://dl.google.com/go/go1.24.2.freebsd-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
- test_gio: |
|
||||
cd gio
|
||||
go test ./...
|
||||
|
||||
+9
-3
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: Unlicense OR MIT
|
||||
image: debian/testing
|
||||
image: debian/stable
|
||||
packages:
|
||||
- curl
|
||||
- pkg-config
|
||||
@@ -18,6 +18,12 @@ packages:
|
||||
- libxinerama-dev
|
||||
- libxi-dev
|
||||
- libxxf86vm-dev
|
||||
- libegl-mesa0
|
||||
- libglx-mesa0
|
||||
- libgl1-mesa-dri
|
||||
- mesa-libgallium
|
||||
- libgbm1
|
||||
- libegl1
|
||||
- mesa-vulkan-drivers
|
||||
- wine
|
||||
- xvfb
|
||||
@@ -40,7 +46,7 @@ secrets:
|
||||
tasks:
|
||||
- install_go: |
|
||||
mkdir -p /home/build/sdk
|
||||
curl -s https://dl.google.com/go/go1.22.2.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
curl -s https://dl.google.com/go/go1.24.2.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
- check_gofmt: |
|
||||
cd gio
|
||||
test -z "$(gofmt -s -l .)"
|
||||
@@ -60,7 +66,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"
|
||||
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"
|
||||
- test_gio: |
|
||||
cd gio
|
||||
go test -race ./...
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ environment:
|
||||
tasks:
|
||||
- install_go: |
|
||||
mkdir -p /home/build/sdk
|
||||
curl https://dl.google.com/go/go1.22.2.src.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
curl https://dl.google.com/go/go1.24.2.src.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
cd /home/build/sdk/go/src
|
||||
./make.bash
|
||||
- test_gio: |
|
||||
|
||||
@@ -4,6 +4,7 @@ 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;
|
||||
@@ -29,6 +30,7 @@ public final class GioActivity extends Activity {
|
||||
|
||||
layer.addView(view);
|
||||
setContentView(layer);
|
||||
onNewIntent(this.getIntent());
|
||||
}
|
||||
|
||||
@Override public void onDestroy() {
|
||||
@@ -46,6 +48,16 @@ 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();
|
||||
@@ -60,4 +72,9 @@ public final class GioActivity extends Activity {
|
||||
if (!view.backPressed())
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
view.onIntentEvent(intent);
|
||||
}
|
||||
}
|
||||
|
||||
+23
-7
@@ -12,6 +12,7 @@ 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;
|
||||
@@ -61,7 +62,6 @@ 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,12 +113,6 @@ 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.
|
||||
@@ -315,6 +309,15 @@ 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);
|
||||
@@ -472,6 +475,18 @@ 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);
|
||||
@@ -553,6 +568,7 @@ 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);
|
||||
|
||||
+52
-2
@@ -3,7 +3,10 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"gioui.org/io/event"
|
||||
"golang.org/x/net/idna"
|
||||
"image"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -56,6 +59,15 @@ 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 {
|
||||
@@ -118,8 +130,7 @@ func NewContext(ops *op.Ops, e FrameEvent) layout.Context {
|
||||
// On iOS NSDocumentDirectory is queried.
|
||||
// For Android Context.getFilesDir is used.
|
||||
//
|
||||
// BUG: DataDir blocks on Android until init functions
|
||||
// have completed.
|
||||
// BUG: On Android, DataDir panics if called before main.
|
||||
func DataDir() (string, error) {
|
||||
return dataDir()
|
||||
}
|
||||
@@ -136,7 +147,29 @@ 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 != "" {
|
||||
@@ -147,3 +180,20 @@ 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,7 +1,6 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build !android
|
||||
// +build !android
|
||||
|
||||
package app
|
||||
|
||||
|
||||
+6
-1
@@ -48,7 +48,7 @@ For example, to display a blank but otherwise functional window:
|
||||
|
||||
func main() {
|
||||
go func() {
|
||||
w := app.NewWindow()
|
||||
w := new(app.Window)
|
||||
for {
|
||||
w.Event()
|
||||
}
|
||||
@@ -56,6 +56,11 @@ 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
|
||||
|
||||
+19
-22
@@ -38,7 +38,24 @@ func init() {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &wlContext{Context: ctx, win: w}, nil
|
||||
|
||||
surf, width, height := w.surface()
|
||||
if surf == nil {
|
||||
return nil, errors.New("wayland: no surface")
|
||||
}
|
||||
eglWin := C.wl_egl_window_create(surf, C.int(width), C.int(height))
|
||||
if eglWin == nil {
|
||||
return nil, errors.New("wayland: wl_egl_window_create failed")
|
||||
}
|
||||
eglSurf := egl.NativeWindowType(uintptr(unsafe.Pointer(eglWin)))
|
||||
if err := ctx.CreateSurface(eglSurf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// We're in charge of the frame callbacks, don't let eglSwapBuffers
|
||||
// wait for callbacks that may never arrive.
|
||||
ctx.EnableVSync(false)
|
||||
|
||||
return &wlContext{Context: ctx, win: w, eglWin: eglWin}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,31 +71,11 @@ func (c *wlContext) Release() {
|
||||
}
|
||||
|
||||
func (c *wlContext) Refresh() error {
|
||||
c.Context.ReleaseSurface()
|
||||
if c.eglWin != nil {
|
||||
C.wl_egl_window_destroy(c.eglWin)
|
||||
c.eglWin = nil
|
||||
}
|
||||
surf, width, height := c.win.surface()
|
||||
if surf == nil {
|
||||
return errors.New("wayland: no surface")
|
||||
}
|
||||
eglWin := C.wl_egl_window_create(surf, C.int(width), C.int(height))
|
||||
if eglWin == nil {
|
||||
return errors.New("wayland: wl_egl_window_create failed")
|
||||
}
|
||||
c.eglWin = eglWin
|
||||
eglSurf := egl.NativeWindowType(uintptr(unsafe.Pointer(eglWin)))
|
||||
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)
|
||||
C.wl_egl_window_resize(c.eglWin, C.int(width), C.int(height), 0, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build darwin && ios && nometal
|
||||
// +build darwin,ios,nometal
|
||||
|
||||
package app
|
||||
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build darwin && !ios && nometal
|
||||
// +build darwin,!ios,nometal
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/gpu"
|
||||
|
||||
+27
@@ -1,9 +1,11 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
|
||||
"gioui.org/io/input"
|
||||
"gioui.org/io/key"
|
||||
@@ -116,3 +118,28 @@ 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
@@ -1,11 +1,9 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"gioui.org/f32"
|
||||
"testing"
|
||||
"unicode/utf8"
|
||||
|
||||
@@ -40,6 +38,7 @@ func FuzzIME(f *testing.F) {
|
||||
r.Frame(gtx.Ops)
|
||||
|
||||
var state editorState
|
||||
state.Selection.Transform = f32.AffineId()
|
||||
const (
|
||||
cmdReplace = iota
|
||||
cmdSelect
|
||||
@@ -139,6 +138,7 @@ func FuzzIME(f *testing.F) {
|
||||
|
||||
func TestEditorIndices(t *testing.T) {
|
||||
var s editorState
|
||||
s.Selection.Transform = f32.AffineId()
|
||||
const str = "Hello, 😀"
|
||||
s.Snippet = key.Snippet{
|
||||
Text: str,
|
||||
|
||||
+178
-41
@@ -108,6 +108,80 @@ 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
|
||||
|
||||
@@ -244,45 +318,52 @@ const (
|
||||
|
||||
UNICODE_NOCHAR = 65535
|
||||
|
||||
WM_CANCELMODE = 0x001F
|
||||
WM_CHAR = 0x0102
|
||||
WM_CLOSE = 0x0010
|
||||
WM_CREATE = 0x0001
|
||||
WM_DPICHANGED = 0x02E0
|
||||
WM_DESTROY = 0x0002
|
||||
WM_ERASEBKGND = 0x0014
|
||||
WM_GETMINMAXINFO = 0x0024
|
||||
WM_IME_COMPOSITION = 0x010F
|
||||
WM_IME_ENDCOMPOSITION = 0x010E
|
||||
WM_IME_STARTCOMPOSITION = 0x010D
|
||||
WM_KEYDOWN = 0x0100
|
||||
WM_KEYUP = 0x0101
|
||||
WM_KILLFOCUS = 0x0008
|
||||
WM_LBUTTONDOWN = 0x0201
|
||||
WM_LBUTTONUP = 0x0202
|
||||
WM_MBUTTONDOWN = 0x0207
|
||||
WM_MBUTTONUP = 0x0208
|
||||
WM_MOUSEMOVE = 0x0200
|
||||
WM_MOUSEWHEEL = 0x020A
|
||||
WM_MOUSEHWHEEL = 0x020E
|
||||
WM_NCACTIVATE = 0x0086
|
||||
WM_NCHITTEST = 0x0084
|
||||
WM_NCCALCSIZE = 0x0083
|
||||
WM_PAINT = 0x000F
|
||||
WM_QUIT = 0x0012
|
||||
WM_SETCURSOR = 0x0020
|
||||
WM_SETFOCUS = 0x0007
|
||||
WM_SHOWWINDOW = 0x0018
|
||||
WM_SIZE = 0x0005
|
||||
WM_STYLECHANGED = 0x007D
|
||||
WM_SYSKEYDOWN = 0x0104
|
||||
WM_SYSKEYUP = 0x0105
|
||||
WM_RBUTTONDOWN = 0x0204
|
||||
WM_RBUTTONUP = 0x0205
|
||||
WM_TIMER = 0x0113
|
||||
WM_UNICHAR = 0x0109
|
||||
WM_USER = 0x0400
|
||||
WM_WINDOWPOSCHANGED = 0x0047
|
||||
WM_CANCELMODE = 0x001F
|
||||
WM_CHAR = 0x0102
|
||||
WM_CLOSE = 0x0010
|
||||
WM_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
|
||||
|
||||
WS_CLIPCHILDREN = 0x02000000
|
||||
WS_CLIPSIBLINGS = 0x04000000
|
||||
@@ -345,7 +426,9 @@ 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")
|
||||
@@ -355,6 +438,7 @@ var (
|
||||
_GetMessage = user32.NewProc("GetMessageW")
|
||||
_GetMessageTime = user32.NewProc("GetMessageTime")
|
||||
_GetMonitorInfo = user32.NewProc("GetMonitorInfoW")
|
||||
_GetPointerInfo = user32.NewProc("GetPointerInfo")
|
||||
_GetSystemMetrics = user32.NewProc("GetSystemMetrics")
|
||||
_GetWindowLong = user32.NewProc("GetWindowLongPtrW")
|
||||
_GetWindowLong32 = user32.NewProc("GetWindowLongW")
|
||||
@@ -372,9 +456,11 @@ 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")
|
||||
@@ -427,7 +513,10 @@ 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 := syscall.StringToUTF16Ptr(lpWindowName)
|
||||
wname, err := syscall.UTF16PtrFromString(lpWindowName)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("CreateWindowEx failed: %v", err)
|
||||
}
|
||||
hwnd, _, err := _CreateWindowEx.Call(
|
||||
uintptr(dwExStyle),
|
||||
uintptr(lpClassName),
|
||||
@@ -445,6 +534,31 @@ func CreateWindowEx(dwExStyle uint32, lpClassName uint16, lpWindowName string, d
|
||||
return syscall.Handle(hwnd), nil
|
||||
}
|
||||
|
||||
func GetPointerInfo(pointerId uint32) (PointerInfo, error) {
|
||||
var info PointerInfo
|
||||
r1, _, err := _GetPointerInfo.Call(uintptr(pointerId), uintptr(unsafe.Pointer(&info)))
|
||||
if r1 == 0 {
|
||||
return PointerInfo{}, fmt.Errorf("GetPointerInfo failed: %v", err)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func RegisterTouchWindow(hwnd syscall.Handle, flags uint32) error {
|
||||
r1, _, err := _RegisterTouchWindow.Call(uintptr(hwnd), uintptr(flags))
|
||||
if r1 == 0 {
|
||||
return fmt.Errorf("RegisterTouchWindow failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func EnableMouseInPointer(enable uint) error {
|
||||
r1, _, err := _EnableMouseInPointer.Call(uintptr(enable))
|
||||
if r1 == 0 {
|
||||
return fmt.Errorf("EnableMouseInPointer failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DefWindowProc(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
|
||||
r, _, _ := _DefWindowProc.Call(uintptr(hwnd), uintptr(msg), wparam, lparam)
|
||||
return r
|
||||
@@ -474,6 +588,18 @@ 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)))
|
||||
@@ -665,7 +791,10 @@ func SetWindowPos(hwnd syscall.Handle, hwndInsertAfter uint32, x, y, dx, dy int3
|
||||
}
|
||||
|
||||
func SetWindowText(hwnd syscall.Handle, title string) {
|
||||
wname := syscall.StringToUTF16Ptr(title)
|
||||
wname, err := syscall.UTF16PtrFromString(title)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_SetWindowText.Call(uintptr(hwnd), uintptr(unsafe.Pointer(wname)))
|
||||
}
|
||||
|
||||
@@ -781,6 +910,14 @@ 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,7 +1,6 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build darwin && ios
|
||||
// +build darwin,ios
|
||||
|
||||
package app
|
||||
|
||||
|
||||
+1
-1
@@ -96,7 +96,7 @@ func newMtlContext(w *window) (*mtlContext, error) {
|
||||
return nil, errors.New("metal: CAMetalLayer construction failed")
|
||||
}
|
||||
queue := C.newCommandQueue(dev)
|
||||
if layer == 0 {
|
||||
if queue == 0 {
|
||||
C.CFRelease(dev)
|
||||
C.CFRelease(layer)
|
||||
return nil, errors.New("metal: [MTLDevice newCommandQueue] failed")
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build !nometal
|
||||
// +build !nometal
|
||||
|
||||
package app
|
||||
|
||||
|
||||
@@ -45,7 +45,9 @@ type Config struct {
|
||||
CustomRenderer bool
|
||||
// Decorated reports whether window decorations are provided automatically.
|
||||
Decorated bool
|
||||
// Focused reports whether has the keyboard focus.
|
||||
// TopMost windows render above all other non-top-most windows.
|
||||
TopMost bool
|
||||
// Focused reports whether the window is focused.
|
||||
Focused bool
|
||||
// decoHeight is the height of the fallback decoration for platforms such
|
||||
// as Wayland that may need fallback client-side decorations.
|
||||
|
||||
+17
-9
@@ -136,6 +136,8 @@ import (
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/io/transfer"
|
||||
|
||||
"gioui.org/internal/f32color"
|
||||
"gioui.org/op"
|
||||
|
||||
@@ -146,7 +148,6 @@ import (
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/semantic"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/io/transfer"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
@@ -217,8 +218,6 @@ 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
|
||||
@@ -294,8 +293,7 @@ var mainWindow = newWindowRendezvous()
|
||||
var mainFuncs = make(chan func(env *C.JNIEnv), 1)
|
||||
|
||||
var (
|
||||
dataDirOnce sync.Once
|
||||
dataPath string
|
||||
dataPath string
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -342,9 +340,9 @@ func (w *window) NewContext() (context, error) {
|
||||
}
|
||||
|
||||
func dataDir() (string, error) {
|
||||
dataDirOnce.Do(func() {
|
||||
dataPath = <-dataDirChan
|
||||
})
|
||||
if dataPath == "" {
|
||||
panic("DataDir isn't valid before main")
|
||||
}
|
||||
return dataPath, nil
|
||||
}
|
||||
|
||||
@@ -397,7 +395,7 @@ func Java_org_gioui_Gio_runGoMain(env *C.JNIEnv, class C.jclass, jdataDir C.jbyt
|
||||
os.Setenv("HOME", dataDir)
|
||||
}
|
||||
|
||||
dataDirChan <- dataDir
|
||||
dataPath = dataDir
|
||||
C.jni_ReleaseByteArrayElements(env, jdataDir, dirBytes)
|
||||
|
||||
runMain()
|
||||
@@ -664,6 +662,15 @@ 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)
|
||||
}
|
||||
@@ -1318,6 +1325,7 @@ func findClass(env *C.JNIEnv, name string) C.jclass {
|
||||
}
|
||||
|
||||
func osMain() {
|
||||
select {}
|
||||
}
|
||||
|
||||
func newWindow(window *callbacks, options []Option) {
|
||||
|
||||
+10
-17
@@ -5,7 +5,7 @@ package app
|
||||
/*
|
||||
#include <Foundation/Foundation.h>
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) void gio_wakeupMainThread(void);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_runOnMain(uintptr_t h);
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createDisplayLink(void);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_releaseDisplayLink(CFTypeRef dl);
|
||||
__attribute__ ((visibility ("hidden"))) int gio_startDisplayLink(CFTypeRef dl);
|
||||
@@ -40,8 +40,10 @@ static CFTypeRef newNSString(unichar *chars, NSUInteger length) {
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime/cgo"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -73,8 +75,6 @@ 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,22 +85,15 @@ func runOnMain(f func()) {
|
||||
f()
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
mainFuncs <- f
|
||||
C.gio_wakeupMainThread()
|
||||
}()
|
||||
C.gio_runOnMain(C.uintptr_t(cgo.NewHandle(f)))
|
||||
}
|
||||
|
||||
//export gio_dispatchMainFuncs
|
||||
func gio_dispatchMainFuncs() {
|
||||
for {
|
||||
select {
|
||||
case f := <-mainFuncs:
|
||||
f()
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
//export gio_runFunc
|
||||
func gio_runFunc(h C.uintptr_t) {
|
||||
handle := cgo.Handle(h)
|
||||
defer handle.Delete()
|
||||
f := handle.Value().(func())
|
||||
f()
|
||||
}
|
||||
|
||||
// nsstringToString converts a NSString to a Go string.
|
||||
|
||||
+2
-2
@@ -4,8 +4,8 @@
|
||||
|
||||
#include "_cgo_export.h"
|
||||
|
||||
void gio_wakeupMainThread(void) {
|
||||
void gio_runOnMain(uintptr_t h) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
gio_dispatchMainFuncs();
|
||||
gio_runFunc(h);
|
||||
});
|
||||
}
|
||||
|
||||
+14
-4
@@ -1,7 +1,6 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build darwin && ios
|
||||
// +build darwin,ios
|
||||
|
||||
package app
|
||||
|
||||
@@ -405,11 +404,12 @@ 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,6 +423,16 @@ 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
|
||||
|
||||
+51
-11
@@ -134,23 +134,59 @@ 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 {
|
||||
@@ -293,6 +329,10 @@ 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[]) {
|
||||
|
||||
+352
-18
@@ -53,7 +53,8 @@ type window struct {
|
||||
screenOrientation js.Value
|
||||
cleanfuncs []func()
|
||||
touches []js.Value
|
||||
composing bool
|
||||
composing int
|
||||
lastCursor int
|
||||
requestFocus bool
|
||||
|
||||
config Config
|
||||
@@ -84,6 +85,7 @@ 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")
|
||||
@@ -130,8 +132,10 @@ func getContainer(doc js.Value) js.Value {
|
||||
}
|
||||
|
||||
func createTextArea(doc js.Value) js.Value {
|
||||
tarea := doc.Call("createElement", "input")
|
||||
tarea := doc.Call("createElement", "textarea")
|
||||
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")
|
||||
@@ -141,6 +145,12 @@ 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
|
||||
}
|
||||
|
||||
@@ -263,7 +273,15 @@ 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
|
||||
@@ -277,19 +295,205 @@ func (w *window) addEventListeners() {
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.tarea, "compositionstart", func(this js.Value, args []js.Value) interface{} {
|
||||
w.composing = true
|
||||
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
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.tarea, "compositionend", func(this js.Value, args []js.Value) interface{} {
|
||||
w.composing = false
|
||||
w.flushInput()
|
||||
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", "")
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.tarea, "input", func(this js.Value, args []js.Value) interface{} {
|
||||
if w.composing {
|
||||
return nil
|
||||
e := args[0]
|
||||
inputType := e.Get("inputType").String()
|
||||
|
||||
dataVal := e.Get("data")
|
||||
var data string
|
||||
if dataVal.Truthy() {
|
||||
data = dataVal.String()
|
||||
}
|
||||
w.flushInput()
|
||||
|
||||
// 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})
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.tarea, "paste", func(this js.Value, args []js.Value) interface{} {
|
||||
@@ -306,12 +510,6 @@ 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
|
||||
@@ -343,11 +541,49 @@ 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),
|
||||
@@ -414,6 +650,12 @@ 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
|
||||
}
|
||||
|
||||
@@ -434,6 +676,9 @@ 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)
|
||||
@@ -521,7 +766,90 @@ func (w *window) funcOf(f func(this js.Value, args []js.Value) interface{}) js.F
|
||||
return jsf
|
||||
}
|
||||
|
||||
func (w *window) EditorStateChanged(old, new editorState) {}
|
||||
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) SetAnimating(anim bool) {
|
||||
w.animating = anim
|
||||
@@ -535,10 +863,9 @@ func (w *window) ReadClipboard() {
|
||||
if w.clipboard.IsUndefined() {
|
||||
return
|
||||
}
|
||||
if w.clipboard.Get("readText").IsUndefined() {
|
||||
return
|
||||
if w.clipboard.Get("readText").Truthy() {
|
||||
w.clipboard.Call("readText").Call("then", w.clipboardCallback)
|
||||
}
|
||||
w.clipboard.Call("readText", w.clipboard).Call("then", w.clipboardCallback)
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(mime string, s []byte) {
|
||||
@@ -621,6 +948,13 @@ 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()
|
||||
}
|
||||
}
|
||||
|
||||
+28
-4
@@ -40,8 +40,9 @@ 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, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight);
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle);
|
||||
|
||||
static void writeClipboard(CFTypeRef str) {
|
||||
@@ -192,6 +193,7 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,6 +241,13 @@ 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;
|
||||
@@ -333,6 +342,8 @@ 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.
|
||||
@@ -491,6 +502,9 @@ func (w *window) Configure(options []Option) {
|
||||
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)
|
||||
@@ -534,7 +548,7 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
||||
}
|
||||
|
||||
func (w *window) EditorStateChanged(old, new editorState) {
|
||||
if old.Selection.Range != new.Selection.Range || old.Snippet != new.Snippet {
|
||||
if old.Selection.Range != new.Selection.Range || !areSnippetsConsistent(old.Snippet, new.Snippet) {
|
||||
C.discardMarkedText(w.view)
|
||||
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
|
||||
}
|
||||
@@ -892,7 +906,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.Affine2D{}.Scale(f32.Pt(0, 0), f32.Pt(scale, -scale)).Offset(f32.Pt(0, height))
|
||||
local := f32.AffineId().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))),
|
||||
@@ -993,6 +1007,16 @@ 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{})
|
||||
@@ -1010,7 +1034,7 @@ 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), 0, 0, 0, 0)
|
||||
window := C.gio_createWindow(w.view, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y))
|
||||
// Release our reference now that the NSWindow has it.
|
||||
C.CFRelease(w.view)
|
||||
w.Configure(options)
|
||||
|
||||
+28
-8
@@ -369,7 +369,7 @@ void gio_setCursor(NSUInteger curID) {
|
||||
}
|
||||
}
|
||||
|
||||
CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight) {
|
||||
CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height) {
|
||||
@autoreleasepool {
|
||||
NSRect rect = NSMakeRect(0, 0, width, height);
|
||||
NSUInteger styleMask = NSTitledWindowMask |
|
||||
@@ -381,12 +381,6 @@ CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGF
|
||||
styleMask:styleMask
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:NO];
|
||||
if (minWidth > 0 || minHeight > 0) {
|
||||
window.contentMinSize = NSMakeSize(minWidth, minHeight);
|
||||
}
|
||||
if (maxWidth > 0 || maxHeight > 0) {
|
||||
window.contentMaxSize = NSMakeSize(maxWidth, maxHeight);
|
||||
}
|
||||
[window setAcceptsMouseMovedEvents:YES];
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
[window setContentView:view];
|
||||
@@ -426,7 +420,11 @@ void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
gio_onFinishLaunching();
|
||||
}
|
||||
- (void)application:(NSApplication *)application openURLs:(NSArray<NSURL *> *)urls {
|
||||
for (NSURL *url in urls) {
|
||||
gio_onOpenURI((__bridge CFTypeRef)url.absoluteString);
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
@@ -457,3 +455,25 @@ void gio_main() {
|
||||
[NSApp run];
|
||||
}
|
||||
}
|
||||
|
||||
@interface AppListener : NSObject
|
||||
@end
|
||||
|
||||
static AppListener *appListener;
|
||||
|
||||
@implementation AppListener
|
||||
- (void)launchFinished:(NSNotification *)notification {
|
||||
appListener = nil;
|
||||
gio_onFinishLaunching();
|
||||
}
|
||||
@end
|
||||
|
||||
void gio_init() {
|
||||
@autoreleasepool {
|
||||
appListener = [[AppListener alloc] init];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:appListener
|
||||
selector:@selector(launchFinished:)
|
||||
name:NSApplicationDidFinishLaunchingNotification
|
||||
object:nil];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build (linux && !android) || freebsd || openbsd
|
||||
// +build linux,!android freebsd openbsd
|
||||
|
||||
package app
|
||||
|
||||
|
||||
+26
-14
@@ -116,6 +116,8 @@ type wlSeat struct {
|
||||
|
||||
// The most recent input serial.
|
||||
serial C.uint32_t
|
||||
// The most recent pointer enter serial.
|
||||
pointerSerial C.uint32_t
|
||||
|
||||
pointerFocus *window
|
||||
keyboardFocus *window
|
||||
@@ -154,7 +156,6 @@ type repeatState struct {
|
||||
type window struct {
|
||||
w *callbacks
|
||||
disp *wlDisplay
|
||||
seat *wlSeat
|
||||
surf *C.struct_wl_surface
|
||||
wmSurf *C.struct_xdg_surface
|
||||
topLvl *C.struct_xdg_toplevel
|
||||
@@ -851,8 +852,8 @@ func gio_onTouchCancel(data unsafe.Pointer, touch *C.struct_wl_touch) {
|
||||
func gio_onPointerEnter(data unsafe.Pointer, pointer *C.struct_wl_pointer, serial C.uint32_t, surf *C.struct_wl_surface, x, y C.wl_fixed_t) {
|
||||
s := callbackLoad(data).(*wlSeat)
|
||||
s.serial = serial
|
||||
s.pointerSerial = serial
|
||||
w := callbackLoad(unsafe.Pointer(surf)).(*window)
|
||||
w.seat = s
|
||||
s.pointerFocus = w
|
||||
w.setCursor(pointer, serial)
|
||||
w.lastPos = f32.Point{X: fromFixed(x), Y: fromFixed(y)}
|
||||
@@ -861,9 +862,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})
|
||||
@@ -883,11 +884,13 @@ func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t,
|
||||
s := callbackLoad(data).(*wlSeat)
|
||||
s.serial = serial
|
||||
w := s.pointerFocus
|
||||
// From linux-event-codes.h.
|
||||
// From Linux: include/uapi/linux/input-event-codes.h
|
||||
const (
|
||||
BTN_LEFT = 0x110
|
||||
BTN_RIGHT = 0x111
|
||||
BTN_MIDDLE = 0x112
|
||||
BTN_SIDE = 0x113
|
||||
BTN_EXTRA = 0x114
|
||||
)
|
||||
var btn pointer.Buttons
|
||||
switch wbtn {
|
||||
@@ -897,6 +900,10 @@ func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t,
|
||||
btn = pointer.ButtonSecondary
|
||||
case BTN_MIDDLE:
|
||||
btn = pointer.ButtonTertiary
|
||||
case BTN_SIDE:
|
||||
btn = pointer.ButtonQuaternary
|
||||
case BTN_EXTRA:
|
||||
btn = pointer.ButtonQuinary
|
||||
default:
|
||||
return
|
||||
}
|
||||
@@ -963,6 +970,9 @@ func gio_onPointerAxis(data unsafe.Pointer, p *C.struct_wl_pointer, t, axis C.ui
|
||||
func gio_onPointerFrame(data unsafe.Pointer, p *C.struct_wl_pointer) {
|
||||
s := callbackLoad(data).(*wlSeat)
|
||||
w := s.pointerFocus
|
||||
if w == nil {
|
||||
return
|
||||
}
|
||||
w.flushScroll()
|
||||
w.flushFling()
|
||||
}
|
||||
@@ -1143,16 +1153,17 @@ func (w *window) Perform(actions system.Action) {
|
||||
}
|
||||
|
||||
func (w *window) move(serial C.uint32_t) {
|
||||
s := w.seat
|
||||
if !w.inCompositor && s != nil {
|
||||
w.inCompositor = true
|
||||
C.xdg_toplevel_move(w.topLvl, s.seat, serial)
|
||||
s := w.disp.seat
|
||||
if w.inCompositor || s.pointerFocus != w {
|
||||
return
|
||||
}
|
||||
w.inCompositor = true
|
||||
C.xdg_toplevel_move(w.topLvl, s.seat, serial)
|
||||
}
|
||||
|
||||
func (w *window) resize(serial, edge C.uint32_t) {
|
||||
s := w.seat
|
||||
if w.inCompositor || s == nil {
|
||||
s := w.disp.seat
|
||||
if w.inCompositor || s.pointerFocus != w {
|
||||
return
|
||||
}
|
||||
w.inCompositor = true
|
||||
@@ -1165,11 +1176,12 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
||||
}
|
||||
|
||||
func (w *window) updateCursor() {
|
||||
ptr := w.disp.seat.pointer
|
||||
if ptr == nil {
|
||||
s := w.disp.seat
|
||||
ptr := s.pointer
|
||||
if ptr == nil || s.pointerFocus != w {
|
||||
return
|
||||
}
|
||||
w.setCursor(ptr, w.serial)
|
||||
w.setCursor(ptr, s.pointerSerial)
|
||||
}
|
||||
|
||||
func (w *window) setCursor(pointer *C.struct_wl_pointer, serial C.uint32_t) {
|
||||
@@ -1178,7 +1190,7 @@ func (w *window) setCursor(pointer *C.struct_wl_pointer, serial C.uint32_t) {
|
||||
c = w.cursor.cursor
|
||||
}
|
||||
if c == nil {
|
||||
C.wl_pointer_set_cursor(pointer, w.serial, nil, 0, 0)
|
||||
C.wl_pointer_set_cursor(pointer, serial, nil, 0, 0)
|
||||
return
|
||||
}
|
||||
// Get images[0].
|
||||
|
||||
+376
-83
@@ -5,8 +5,12 @@ 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"
|
||||
@@ -16,8 +20,6 @@ import (
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
|
||||
syscall "golang.org/x/sys/windows"
|
||||
|
||||
"gioui.org/app/internal/windows"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
@@ -28,7 +30,6 @@ import (
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/io/transfer"
|
||||
)
|
||||
|
||||
type Win32ViewEvent struct {
|
||||
@@ -36,10 +37,9 @@ type Win32ViewEvent struct {
|
||||
}
|
||||
|
||||
type window struct {
|
||||
hwnd syscall.Handle
|
||||
hdc syscall.Handle
|
||||
w *callbacks
|
||||
pointerBtns pointer.Buttons
|
||||
hwnd syscall.Handle
|
||||
hdc syscall.Handle
|
||||
w *callbacks
|
||||
|
||||
// cursorIn tracks whether the cursor was inside the window according
|
||||
// to the most recent WM_SETCURSOR.
|
||||
@@ -57,6 +57,8 @@ type window struct {
|
||||
|
||||
const _WM_WAKEUP = windows.WM_USER + iota
|
||||
|
||||
const copyDataURLType = 0xffffff00
|
||||
|
||||
type gpuAPI struct {
|
||||
priority int
|
||||
initializer func(w *window) (context, error)
|
||||
@@ -82,6 +84,7 @@ var resources struct {
|
||||
}
|
||||
|
||||
func osMain() {
|
||||
processURLEvent(startupURI())
|
||||
select {}
|
||||
}
|
||||
|
||||
@@ -133,13 +136,19 @@ 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: syscall.StringToUTF16Ptr("GioWindow"),
|
||||
LpszClassName: appid,
|
||||
}
|
||||
cls, err := windows.RegisterClassEx(&wcls)
|
||||
if err != nil {
|
||||
@@ -175,6 +184,12 @@ 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)
|
||||
@@ -265,18 +280,32 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
||||
return 0
|
||||
}
|
||||
}
|
||||
case windows.WM_LBUTTONDOWN:
|
||||
w.pointerButton(pointer.ButtonPrimary, true, lParam, getModifiers())
|
||||
case windows.WM_LBUTTONUP:
|
||||
w.pointerButton(pointer.ButtonPrimary, false, lParam, getModifiers())
|
||||
case windows.WM_RBUTTONDOWN:
|
||||
w.pointerButton(pointer.ButtonSecondary, true, lParam, getModifiers())
|
||||
case windows.WM_RBUTTONUP:
|
||||
w.pointerButton(pointer.ButtonSecondary, false, lParam, getModifiers())
|
||||
case windows.WM_MBUTTONDOWN:
|
||||
w.pointerButton(pointer.ButtonTertiary, true, lParam, getModifiers())
|
||||
case windows.WM_MBUTTONUP:
|
||||
w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers())
|
||||
case windows.WM_POINTERDOWN, windows.WM_POINTERUP, windows.WM_POINTERUPDATE, windows.WM_POINTERCAPTURECHANGED:
|
||||
pid := getPointerIDwParam(wParam)
|
||||
pi, err := windows.GetPointerInfo(uint32(pid))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
switch msg {
|
||||
case windows.WM_POINTERDOWN:
|
||||
windows.SetCapture(w.hwnd)
|
||||
case windows.WM_POINTERUP:
|
||||
windows.ReleaseCapture()
|
||||
}
|
||||
|
||||
kind := pointer.Move
|
||||
switch pi.ButtonChangeType {
|
||||
case windows.POINTER_CHANGE_FIRSTBUTTON_DOWN, windows.POINTER_CHANGE_SECONDBUTTON_DOWN, windows.POINTER_CHANGE_THIRDBUTTON_DOWN, windows.POINTER_CHANGE_FOURTHBUTTON_DOWN, windows.POINTER_CHANGE_FIFTHBUTTON_DOWN:
|
||||
kind = pointer.Press
|
||||
case windows.POINTER_CHANGE_FIRSTBUTTON_UP, windows.POINTER_CHANGE_SECONDBUTTON_UP, windows.POINTER_CHANGE_THIRDBUTTON_UP, windows.POINTER_CHANGE_FOURTHBUTTON_UP, windows.POINTER_CHANGE_FIFTHBUTTON_UP:
|
||||
kind = pointer.Release
|
||||
}
|
||||
|
||||
if (pi.PointerFlags&windows.POINTER_FLAG_CANCELED != 0) || (msg == windows.WM_POINTERCAPTURECHANGED) {
|
||||
kind = pointer.Cancel
|
||||
}
|
||||
|
||||
w.pointerUpdate(pi, pid, kind, lParam)
|
||||
case windows.WM_CANCELMODE:
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Cancel,
|
||||
@@ -296,20 +325,9 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
||||
np := windows.Point{X: int32(x), Y: int32(y)}
|
||||
windows.ScreenToClient(w.hwnd, &np)
|
||||
return w.hitTest(int(np.X), int(np.Y))
|
||||
case windows.WM_MOUSEMOVE:
|
||||
x, y := coordsFromlParam(lParam)
|
||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Move,
|
||||
Source: pointer.Mouse,
|
||||
Position: p,
|
||||
Buttons: w.pointerBtns,
|
||||
Time: windows.GetMessageTime(),
|
||||
Modifiers: getModifiers(),
|
||||
})
|
||||
case windows.WM_MOUSEWHEEL:
|
||||
case windows.WM_POINTERWHEEL:
|
||||
w.scrollEvent(wParam, lParam, false, getModifiers())
|
||||
case windows.WM_MOUSEHWHEEL:
|
||||
case windows.WM_POINTERHWHEEL:
|
||||
w.scrollEvent(wParam, lParam, true, getModifiers())
|
||||
case windows.WM_DESTROY:
|
||||
w.ProcessEvent(Win32ViewEvent{})
|
||||
@@ -351,8 +369,7 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
||||
w.update()
|
||||
case windows.WM_WINDOWPOSCHANGED:
|
||||
w.update()
|
||||
case windows.WM_SIZE:
|
||||
w.update()
|
||||
return 0
|
||||
case windows.WM_GETMINMAXINFO:
|
||||
mm := (*windows.MinMaxInfo)(unsafe.Pointer(lParam))
|
||||
|
||||
@@ -395,6 +412,7 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
||||
icaret := image.Pt(int(caret.X+.5), int(caret.Y+.5))
|
||||
windows.ImmSetCompositionWindow(imc, icaret.X, icaret.Y)
|
||||
windows.ImmSetCandidateWindow(imc, icaret.X, icaret.Y)
|
||||
return windows.TRUE
|
||||
case windows.WM_IME_COMPOSITION:
|
||||
imc := windows.ImmGetContext(w.hwnd)
|
||||
if imc == 0 {
|
||||
@@ -438,6 +456,20 @@ 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)
|
||||
@@ -463,34 +495,32 @@ 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 != Windowed {
|
||||
// Only windowed mode should allow resizing.
|
||||
return windows.HTCLIENT
|
||||
}
|
||||
// Check for resize handle before system actions; otherwise it can be impossible to
|
||||
// resize a custom-decorations window when the system move area is flush with the
|
||||
// edge of the window.
|
||||
top := y <= w.borderSize.Y
|
||||
bottom := y >= w.config.Size.Y-w.borderSize.Y
|
||||
left := x <= w.borderSize.X
|
||||
right := x >= w.config.Size.X-w.borderSize.X
|
||||
switch {
|
||||
case top && left:
|
||||
return windows.HTTOPLEFT
|
||||
case top && right:
|
||||
return windows.HTTOPRIGHT
|
||||
case bottom && left:
|
||||
return windows.HTBOTTOMLEFT
|
||||
case bottom && right:
|
||||
return windows.HTBOTTOMRIGHT
|
||||
case top:
|
||||
return windows.HTTOP
|
||||
case bottom:
|
||||
return windows.HTBOTTOM
|
||||
case left:
|
||||
return windows.HTLEFT
|
||||
case right:
|
||||
return windows.HTRIGHT
|
||||
if w.config.Mode == Windowed {
|
||||
// Check for resize handle before system actions; otherwise it can be impossible to
|
||||
// resize a custom-decorations window when the system move area is flush with the
|
||||
// edge of the window.
|
||||
top := y <= w.borderSize.Y
|
||||
bottom := y >= w.config.Size.Y-w.borderSize.Y
|
||||
left := x <= w.borderSize.X
|
||||
right := x >= w.config.Size.X-w.borderSize.X
|
||||
switch {
|
||||
case top && left:
|
||||
return windows.HTTOPLEFT
|
||||
case top && right:
|
||||
return windows.HTTOPRIGHT
|
||||
case bottom && left:
|
||||
return windows.HTBOTTOMLEFT
|
||||
case bottom && right:
|
||||
return windows.HTBOTTOMRIGHT
|
||||
case top:
|
||||
return windows.HTTOP
|
||||
case bottom:
|
||||
return windows.HTBOTTOM
|
||||
case left:
|
||||
return windows.HTLEFT
|
||||
case right:
|
||||
return windows.HTRIGHT
|
||||
}
|
||||
}
|
||||
p := f32.Pt(float32(x), float32(y))
|
||||
if a, ok := w.w.ActionAt(p); ok && a == system.ActionMove {
|
||||
@@ -499,34 +529,28 @@ func (w *window) hitTest(x, y int) uintptr {
|
||||
return windows.HTCLIENT
|
||||
}
|
||||
|
||||
func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) {
|
||||
func (w *window) pointerUpdate(pi windows.PointerInfo, pid pointer.ID, kind pointer.Kind, lParam uintptr) {
|
||||
if !w.config.Focused {
|
||||
windows.SetFocus(w.hwnd)
|
||||
}
|
||||
|
||||
var kind pointer.Kind
|
||||
if press {
|
||||
kind = pointer.Press
|
||||
if w.pointerBtns == 0 {
|
||||
windows.SetCapture(w.hwnd)
|
||||
}
|
||||
w.pointerBtns |= btn
|
||||
} else {
|
||||
kind = pointer.Release
|
||||
w.pointerBtns &^= btn
|
||||
if w.pointerBtns == 0 {
|
||||
windows.ReleaseCapture()
|
||||
}
|
||||
src := pointer.Touch
|
||||
if pi.PointerType == windows.PT_MOUSE {
|
||||
src = pointer.Mouse
|
||||
}
|
||||
|
||||
x, y := coordsFromlParam(lParam)
|
||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||
np := windows.Point{X: int32(x), Y: int32(y)}
|
||||
windows.ScreenToClient(w.hwnd, &np)
|
||||
p := f32.Point{X: float32(np.X), Y: float32(np.Y)}
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: kind,
|
||||
Source: pointer.Mouse,
|
||||
Source: src,
|
||||
Position: p,
|
||||
Buttons: w.pointerBtns,
|
||||
PointerID: pid,
|
||||
Buttons: getPointerButtons(pi),
|
||||
Time: windows.GetMessageTime(),
|
||||
Modifiers: kmods,
|
||||
Modifiers: getModifiers(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -537,6 +561,12 @@ func coordsFromlParam(lParam uintptr) (int, int) {
|
||||
}
|
||||
|
||||
func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.Modifiers) {
|
||||
pid := getPointerIDwParam(wParam)
|
||||
pi, err := windows.GetPointerInfo(uint32(pid))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
x, y := coordsFromlParam(lParam)
|
||||
// The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast
|
||||
// to other mouse events.
|
||||
@@ -559,7 +589,7 @@ func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.
|
||||
Kind: pointer.Scroll,
|
||||
Source: pointer.Mouse,
|
||||
Position: p,
|
||||
Buttons: w.pointerBtns,
|
||||
Buttons: getPointerButtons(pi),
|
||||
Scroll: sp,
|
||||
Modifiers: kmods,
|
||||
Time: windows.GetMessageTime(),
|
||||
@@ -750,8 +780,17 @@ func (w *window) Configure(options []Option) {
|
||||
swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
|
||||
showMode = windows.SW_SHOWMAXIMIZED
|
||||
}
|
||||
windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style)
|
||||
|
||||
// Disable window resizing if MinSize and MaxSize are equal.
|
||||
if cnf.MaxSize != (image.Point{}) && cnf.MinSize == cnf.MaxSize {
|
||||
style &^= windows.WS_MAXIMIZEBOX
|
||||
style &^= windows.WS_THICKFRAME
|
||||
}
|
||||
|
||||
// Note: these invocation all trigger the windows callback method which may process a pending system.ActionCenter
|
||||
// action, so SetWindowPos should come first so as to not "overwrite" system.ActionCenter.
|
||||
windows.SetWindowPos(w.hwnd, 0, x, y, width, height, swpStyle)
|
||||
windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style)
|
||||
windows.ShowWindow(w.hwnd, showMode)
|
||||
}
|
||||
|
||||
@@ -993,3 +1032,257 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-3
@@ -26,6 +26,7 @@ package app
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -751,9 +752,7 @@ func (h *x11EventHandler) handleEvents() bool {
|
||||
return redraw
|
||||
}
|
||||
|
||||
var (
|
||||
x11Threads sync.Once
|
||||
)
|
||||
var x11Threads sync.Once
|
||||
|
||||
func init() {
|
||||
x11Driver = newX11Window
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// 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
|
||||
+1
-2
@@ -1,7 +1,6 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build android || (darwin && ios)
|
||||
// +build android darwin,ios
|
||||
|
||||
package app
|
||||
|
||||
@@ -25,6 +24,6 @@ func runMain() {
|
||||
// Indirect call, since the linker does not know the address of main when
|
||||
// laying down this package.
|
||||
fn := mainMain
|
||||
fn()
|
||||
go fn()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build !novulkan
|
||||
// +build !novulkan
|
||||
|
||||
package app
|
||||
|
||||
|
||||
+10
-4
@@ -444,10 +444,7 @@ func (c *callbacks) SetComposingRegion(r key.Range) {
|
||||
func (c *callbacks) EditorInsert(text string) {
|
||||
sel := c.w.imeState.Selection.Range
|
||||
c.EditorReplace(sel, text)
|
||||
start := sel.Start
|
||||
if sel.End < start {
|
||||
start = sel.End
|
||||
}
|
||||
start := min(sel.End, sel.Start)
|
||||
sel.Start = start + utf8.RuneCountInString(text)
|
||||
sel.End = sel.Start
|
||||
c.SetEditorSelection(sel)
|
||||
@@ -972,6 +969,15 @@ 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.
|
||||
|
||||
+10
-1
@@ -30,6 +30,15 @@ func NewAffine2D(sx, hx, ox, hy, sy, oy float32) Affine2D {
|
||||
}
|
||||
}
|
||||
|
||||
// AffineId returns an identity transformation matrix that represents no transformation
|
||||
// when applied.
|
||||
func AffineId() Affine2D {
|
||||
return NewAffine2D(
|
||||
1, 0, 0,
|
||||
0, 1, 0,
|
||||
)
|
||||
}
|
||||
|
||||
// Offset the transformation.
|
||||
func (a Affine2D) Offset(offset Point) Affine2D {
|
||||
return Affine2D{
|
||||
@@ -114,7 +123,7 @@ func (a Affine2D) Elems() (sx, hx, ox, hy, sy, oy float32) {
|
||||
|
||||
// Split a transform into two parts, one which is pure offset and the
|
||||
// other representing the scaling, shearing and rotation part.
|
||||
func (a *Affine2D) Split() (srs Affine2D, offset Point) {
|
||||
func (a Affine2D) Split() (srs Affine2D, offset Point) {
|
||||
return Affine2D{
|
||||
a: a.a, b: a.b, c: 0,
|
||||
d: a.d, e: a.e, f: 0,
|
||||
|
||||
+160
-30
@@ -27,11 +27,11 @@ func TestTransformOffset(t *testing.T) {
|
||||
p := Point{X: 1, Y: 2}
|
||||
o := Point{X: 2, Y: -3}
|
||||
|
||||
r := Affine2D{}.Offset(o).Transform(p)
|
||||
r := AffineId().Offset(o).Transform(p)
|
||||
if !eq(r, Pt(3, -1)) {
|
||||
t.Errorf("offset transformation mismatch: have %v, want {3 -1}", r)
|
||||
}
|
||||
i := Affine2D{}.Offset(o).Invert().Transform(r)
|
||||
i := AffineId().Offset(o).Invert().Transform(r)
|
||||
if !eq(i, p) {
|
||||
t.Errorf("offset transformation inverse mismatch: have %v, want %v", i, p)
|
||||
}
|
||||
@@ -51,6 +51,9 @@ func TestString(t *testing.T) {
|
||||
}, {
|
||||
in: NewAffine2D(29.142342, 31.4123412, 37.53152, 43.51324213, 47.123412, 53.14312342),
|
||||
exp: "[[29.1423 31.4123 37.5315] [43.5132 47.1234 53.1431]]",
|
||||
}, {
|
||||
in: AffineId(),
|
||||
exp: "[[1 0 0] [0 1 0]]",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
@@ -64,11 +67,11 @@ func TestTransformScale(t *testing.T) {
|
||||
p := Point{X: 1, Y: 2}
|
||||
s := Point{X: -1, Y: 2}
|
||||
|
||||
r := Affine2D{}.Scale(Point{}, s).Transform(p)
|
||||
r := AffineId().Scale(Point{}, s).Transform(p)
|
||||
if !eq(r, Pt(-1, 4)) {
|
||||
t.Errorf("scale transformation mismatch: have %v, want {-1 4}", r)
|
||||
}
|
||||
i := Affine2D{}.Scale(Point{}, s).Invert().Transform(r)
|
||||
i := AffineId().Scale(Point{}, s).Invert().Transform(r)
|
||||
if !eq(i, p) {
|
||||
t.Errorf("scale transformation inverse mismatch: have %v, want %v", i, p)
|
||||
}
|
||||
@@ -78,11 +81,11 @@ func TestTransformRotate(t *testing.T) {
|
||||
p := Point{X: 1, Y: 0}
|
||||
a := float32(math.Pi / 2)
|
||||
|
||||
r := Affine2D{}.Rotate(Point{}, a).Transform(p)
|
||||
r := AffineId().Rotate(Point{}, a).Transform(p)
|
||||
if !eq(r, Pt(0, 1)) {
|
||||
t.Errorf("rotate transformation mismatch: have %v, want {0 1}", r)
|
||||
}
|
||||
i := Affine2D{}.Rotate(Point{}, a).Invert().Transform(r)
|
||||
i := AffineId().Rotate(Point{}, a).Invert().Transform(r)
|
||||
if !eq(i, p) {
|
||||
t.Errorf("rotate transformation inverse mismatch: have %v, want %v", i, p)
|
||||
}
|
||||
@@ -91,11 +94,11 @@ func TestTransformRotate(t *testing.T) {
|
||||
func TestTransformShear(t *testing.T) {
|
||||
p := Point{X: 1, Y: 1}
|
||||
|
||||
r := Affine2D{}.Shear(Point{}, math.Pi/4, 0).Transform(p)
|
||||
r := AffineId().Shear(Point{}, math.Pi/4, 0).Transform(p)
|
||||
if !eq(r, Pt(2, 1)) {
|
||||
t.Errorf("shear transformation mismatch: have %v, want {2 1}", r)
|
||||
}
|
||||
i := Affine2D{}.Shear(Point{}, math.Pi/4, 0).Invert().Transform(r)
|
||||
i := AffineId().Shear(Point{}, math.Pi/4, 0).Invert().Transform(r)
|
||||
if !eq(i, p) {
|
||||
t.Errorf("shear transformation inverse mismatch: have %v, want %v", i, p)
|
||||
}
|
||||
@@ -107,11 +110,11 @@ func TestTransformMultiply(t *testing.T) {
|
||||
s := Point{X: -1, Y: 2}
|
||||
a := float32(-math.Pi / 2)
|
||||
|
||||
r := Affine2D{}.Offset(o).Scale(Point{}, s).Rotate(Point{}, a).Shear(Point{}, math.Pi/4, 0).Transform(p)
|
||||
r := AffineId().Offset(o).Scale(Point{}, s).Rotate(Point{}, a).Shear(Point{}, math.Pi/4, 0).Transform(p)
|
||||
if !eq(r, Pt(1, 3)) {
|
||||
t.Errorf("complex transformation mismatch: have %v, want {1 3}", r)
|
||||
}
|
||||
i := Affine2D{}.Offset(o).Scale(Point{}, s).Rotate(Point{}, a).Shear(Point{}, math.Pi/4, 0).Invert().Transform(r)
|
||||
i := AffineId().Offset(o).Scale(Point{}, s).Rotate(Point{}, a).Shear(Point{}, math.Pi/4, 0).Invert().Transform(r)
|
||||
if !eq(i, p) {
|
||||
t.Errorf("complex transformation inverse mismatch: have %v, want %v", i, p)
|
||||
}
|
||||
@@ -163,7 +166,7 @@ func TestPrimes(t *testing.T) {
|
||||
func TestTransformScaleAround(t *testing.T) {
|
||||
p := Pt(-1, -1)
|
||||
target := Pt(-6, -13)
|
||||
pt := Affine2D{}.Scale(Pt(4, 5), Pt(2, 3)).Transform(p)
|
||||
pt := AffineId().Scale(Pt(4, 5), Pt(2, 3)).Transform(p)
|
||||
if !eq(pt, target) {
|
||||
t.Log(pt, "!=", target)
|
||||
t.Error("Scale not as expected")
|
||||
@@ -172,7 +175,7 @@ func TestTransformScaleAround(t *testing.T) {
|
||||
|
||||
func TestTransformRotateAround(t *testing.T) {
|
||||
p := Pt(-1, -1)
|
||||
pt := Affine2D{}.Rotate(Pt(1, 1), -math.Pi/2).Transform(p)
|
||||
pt := AffineId().Rotate(Pt(1, 1), -math.Pi/2).Transform(p)
|
||||
target := Pt(-1, 3)
|
||||
if !eq(pt, target) {
|
||||
t.Log(pt, "!=", target)
|
||||
@@ -181,12 +184,12 @@ func TestTransformRotateAround(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMulOrder(t *testing.T) {
|
||||
A := Affine2D{}.Offset(Pt(100, 100))
|
||||
B := Affine2D{}.Scale(Point{}, Pt(2, 2))
|
||||
A := AffineId().Offset(Pt(100, 100))
|
||||
B := AffineId().Scale(Point{}, Pt(2, 2))
|
||||
_ = A
|
||||
_ = B
|
||||
|
||||
T1 := Affine2D{}.Offset(Pt(100, 100)).Scale(Point{}, Pt(2, 2))
|
||||
T1 := AffineId().Offset(Pt(100, 100)).Scale(Point{}, Pt(2, 2))
|
||||
T2 := B.Mul(A)
|
||||
|
||||
if T1 != T2 {
|
||||
@@ -199,9 +202,9 @@ func TestMulOrder(t *testing.T) {
|
||||
func BenchmarkTransformOffset(b *testing.B) {
|
||||
p := Point{X: 1, Y: 2}
|
||||
o := Point{X: 0.5, Y: 0.5}
|
||||
aff := Affine2D{}.Offset(o)
|
||||
aff := AffineId().Offset(o)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
p = aff.Transform(p)
|
||||
}
|
||||
_ = p
|
||||
@@ -210,8 +213,8 @@ func BenchmarkTransformOffset(b *testing.B) {
|
||||
func BenchmarkTransformScale(b *testing.B) {
|
||||
p := Point{X: 1, Y: 2}
|
||||
s := Point{X: 0.5, Y: 0.5}
|
||||
aff := Affine2D{}.Scale(Point{}, s)
|
||||
for i := 0; i < b.N; i++ {
|
||||
aff := AffineId().Scale(Point{}, s)
|
||||
for b.Loop() {
|
||||
p = aff.Transform(p)
|
||||
}
|
||||
_ = p
|
||||
@@ -220,36 +223,163 @@ func BenchmarkTransformScale(b *testing.B) {
|
||||
func BenchmarkTransformRotate(b *testing.B) {
|
||||
p := Point{X: 1, Y: 2}
|
||||
a := float32(math.Pi / 2)
|
||||
aff := Affine2D{}.Rotate(Point{}, a)
|
||||
for i := 0; i < b.N; i++ {
|
||||
aff := AffineId().Rotate(Point{}, a)
|
||||
for b.Loop() {
|
||||
p = aff.Transform(p)
|
||||
}
|
||||
_ = p
|
||||
}
|
||||
|
||||
func BenchmarkTransformTranslateMultiply(b *testing.B) {
|
||||
a := Affine2D{}.Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
|
||||
t := Affine2D{}.Offset(Point{X: 0.5, Y: 0.5})
|
||||
a := AffineId().Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
|
||||
t := AffineId().Offset(Point{X: 0.5, Y: 0.5})
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
a = a.Mul(t)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTransformScaleMultiply(b *testing.B) {
|
||||
a := Affine2D{}.Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
|
||||
t := Affine2D{}.Offset(Point{X: 0.5, Y: 0.5}).Scale(Point{}, Point{X: 0.4, Y: -0.5})
|
||||
a := AffineId().Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
|
||||
t := AffineId().Offset(Point{X: 0.5, Y: 0.5}).Scale(Point{}, Point{X: 0.4, Y: -0.5})
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
a = a.Mul(t)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTransformMultiply(b *testing.B) {
|
||||
a := Affine2D{}.Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
|
||||
t := Affine2D{}.Offset(Point{X: 0.5, Y: 0.5}).Rotate(Point{}, math.Pi/7)
|
||||
a := AffineId().Offset(Point{X: 1, Y: 1}).Rotate(Point{}, math.Pi/3)
|
||||
t := AffineId().Offset(Point{X: 0.5, Y: 0.5}).Rotate(Point{}, math.Pi/7)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
a = a.Mul(t)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewAffine2D(t *testing.T) {
|
||||
tests := []struct {
|
||||
sx, hx, ox, hy, sy, oy float32
|
||||
expected Affine2D
|
||||
}{
|
||||
{1, 0, 0, 0, 1, 0, AffineId()},
|
||||
{2, 0, 5, 0, 3, 7, Affine2D{a: 1, b: 0, c: 5, d: 0, e: 2, f: 7}},
|
||||
{-1, 2, 3, 4, -5, 6, Affine2D{a: -2, b: 2, c: 3, d: 4, e: -6, f: 6}},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
got := NewAffine2D(test.sx, test.hx, test.ox, test.hy, test.sy, test.oy)
|
||||
if !eqaff(got, test.expected) {
|
||||
t.Errorf(
|
||||
"Test %d: NewAffine2D(%v, %v, %v, %v, %v, %v) = %v, want %v",
|
||||
i, test.sx, test.hx, test.ox, test.hy, test.sy, test.oy, got, test.expected,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAffineId(t *testing.T) {
|
||||
id := AffineId()
|
||||
|
||||
testPoints := []Point{
|
||||
{0, 0},
|
||||
{1, 0},
|
||||
{0, 1},
|
||||
{-1, -1},
|
||||
{10, 20},
|
||||
}
|
||||
|
||||
for _, p := range testPoints {
|
||||
transformed := id.Transform(p)
|
||||
if !eq(transformed, p) {
|
||||
t.Errorf("Identity transform changed point: %v -> %v", p, transformed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestElems(t *testing.T) {
|
||||
tests := []struct {
|
||||
aff Affine2D
|
||||
sx, hx, ox, hy, sy, oy float32
|
||||
}{
|
||||
{AffineId(), 1, 0, 0, 0, 1, 0},
|
||||
{Affine2D{a: 1, b: 2, c: 3, d: 4, e: 5, f: 6}, 2, 2, 3, 4, 6, 6},
|
||||
{NewAffine2D(7, 8, 9, 10, 11, 12), 7, 8, 9, 10, 11, 12},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
sx, hx, ox, hy, sy, oy := test.aff.Elems()
|
||||
if sx != test.sx || hx != test.hx || ox != test.ox ||
|
||||
hy != test.hy || sy != test.sy || oy != test.oy {
|
||||
t.Errorf(
|
||||
"Test %d: %v.Elems() = (%v, %v, %v, %v, %v, %v), want (%v, %v, %v, %v, %v, %v)",
|
||||
i, test.aff, sx, hx, ox, hy, sy, oy, test.sx, test.hx, test.ox, test.hy, test.sy, test.oy,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplit(t *testing.T) {
|
||||
tests := []struct {
|
||||
aff Affine2D
|
||||
expectedSRS Affine2D
|
||||
expectedOffset Point
|
||||
}{
|
||||
{
|
||||
AffineId(),
|
||||
AffineId(),
|
||||
Point{0, 0},
|
||||
},
|
||||
{
|
||||
Affine2D{a: 1, b: 2, c: 3, d: 4, e: 5, f: 6},
|
||||
Affine2D{a: 1, b: 2, c: 0, d: 4, e: 5, f: 0},
|
||||
Point{3, 6},
|
||||
},
|
||||
{
|
||||
NewAffine2D(2, 0, 10, 0, 3, 20),
|
||||
NewAffine2D(2, 0, 0, 0, 3, 0),
|
||||
Point{10, 20},
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
srs, offset := test.aff.Split()
|
||||
if !eqaff(srs, test.expectedSRS) || !eq(offset, test.expectedOffset) {
|
||||
t.Errorf(
|
||||
"Test %d: %v.Split() = (%v, %v), want (%v, %v)",
|
||||
i, test.aff, srs, offset, test.expectedSRS, test.expectedOffset,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestShear(t *testing.T) {
|
||||
p := Pt(2, 3)
|
||||
origin := Pt(1, 1)
|
||||
|
||||
shearX := AffineId().Shear(origin, math.Pi/4, 0)
|
||||
resultX := shearX.Transform(p)
|
||||
expectedX := Pt(4, 3)
|
||||
|
||||
if !eq(resultX, expectedX) {
|
||||
t.Errorf("Shear around origin in X: got %v, want %v", resultX, expectedX)
|
||||
}
|
||||
|
||||
inverseX := shearX.Invert().Transform(resultX)
|
||||
if !eq(inverseX, p) {
|
||||
t.Errorf("Inverse shear X: got %v, want %v", inverseX, p)
|
||||
}
|
||||
|
||||
shearY := AffineId().Shear(origin, 0, math.Pi/4)
|
||||
resultY := shearY.Transform(p)
|
||||
expectedY := Pt(2, 4)
|
||||
|
||||
if !eq(resultY, expectedY) {
|
||||
t.Errorf("Shear around origin in Y: got %v, want %v", resultY, expectedY)
|
||||
}
|
||||
|
||||
inverseY := shearY.Invert().Transform(resultY)
|
||||
if !eq(inverseY, p) {
|
||||
t.Errorf("Inverse shear Y: got %v, want %v", inverseY, p)
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+24
-67
@@ -1,86 +1,25 @@
|
||||
{
|
||||
"nodes": {
|
||||
"android": {
|
||||
"inputs": {
|
||||
"devshell": "devshell",
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1733430059,
|
||||
"narHash": "sha256-o3O5tjrMMebRLuHQt7BbEw3jZgWRW5vnOptNXv8WdO4=",
|
||||
"owner": "tadfisher",
|
||||
"repo": "android-nixpkgs",
|
||||
"rev": "d2f3c1ea99c0bea9d28a0e59daeb482f50d4cd35",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "tadfisher",
|
||||
"repo": "android-nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"devshell": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"android",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1728330715,
|
||||
"narHash": "sha256-xRJ2nPOXb//u1jaBnDP56M7v5ldavjbtR6lfGqSvcKg=",
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"rev": "dd6b80932022cea34a019e2bb32f6fa9e494dfef",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1733261153,
|
||||
"narHash": "sha256-eq51hyiaIwtWo19fPEeE0Zr2s83DYMKJoukNLgGGpek=",
|
||||
"lastModified": 1747953325,
|
||||
"narHash": "sha256-y2ZtlIlNTuVJUZCqzZAhIw5rrKP4DOSklev6c8PyCkQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b681065d0919f7eb5309a93cea2cfa84dec9aa88",
|
||||
"rev": "55d1f923c480dadce40f5231feb472e81b0bab48",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-24.11",
|
||||
"ref": "nixos-25.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"android": "android",
|
||||
"nixpkgs": "nixpkgs"
|
||||
"nixpkgs": "nixpkgs",
|
||||
"utils": "utils"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
@@ -97,6 +36,24 @@
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
||||
@@ -3,42 +3,38 @@
|
||||
description = "Gio build environment";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
|
||||
android.url = "github:tadfisher/android-nixpkgs";
|
||||
android.inputs.nixpkgs.follows = "nixpkgs";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
|
||||
utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, android }:
|
||||
let
|
||||
supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-darwin" ];
|
||||
forAllSystems = f: nixpkgs.lib.genAttrs supportedSystems (system: f system);
|
||||
in
|
||||
{
|
||||
devShells = forAllSystems
|
||||
(system:
|
||||
let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
outputs = { self, nixpkgs, utils }:
|
||||
utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
|
||||
# allow unfree Android packages.
|
||||
config.allowUnfree = true;
|
||||
# accept the Android SDK license.
|
||||
config.android_sdk.accept_license = true;
|
||||
};
|
||||
in {
|
||||
devShells = let
|
||||
android-sdk = let
|
||||
androidComposition = pkgs.androidenv.composeAndroidPackages {
|
||||
platformVersions = [ "latest" ];
|
||||
abiVersions = [ "armeabi-v7a" "arm64-v8a" ];
|
||||
# Omit the deprecated tools package.
|
||||
toolsVersion = null;
|
||||
includeNDK = true;
|
||||
};
|
||||
android-sdk = android.sdk.${system} (sdkPkgs: with sdkPkgs;
|
||||
[
|
||||
build-tools-31-0-0
|
||||
cmdline-tools-latest
|
||||
platform-tools
|
||||
platforms-android-31
|
||||
ndk-bundle
|
||||
]);
|
||||
in
|
||||
{
|
||||
default = with pkgs; mkShell
|
||||
({
|
||||
ANDROID_SDK_ROOT = "${android-sdk}/share/android-sdk";
|
||||
JAVA_HOME = jdk17.home;
|
||||
packages = [
|
||||
android-sdk
|
||||
jdk17
|
||||
clang
|
||||
] ++ (if stdenv.isLinux then [
|
||||
in androidComposition.androidsdk;
|
||||
in {
|
||||
default = with pkgs;
|
||||
mkShell (rec {
|
||||
ANDROID_HOME = "${android-sdk}/libexec/android-sdk";
|
||||
packages = [ android-sdk jdk clang ]
|
||||
++ (if stdenv.isLinux then [
|
||||
vulkan-headers
|
||||
libxkbcommon
|
||||
wayland
|
||||
@@ -47,11 +43,12 @@
|
||||
xorg.libXfixes
|
||||
libGL
|
||||
pkg-config
|
||||
] else [ ]);
|
||||
} // (if stdenv.isLinux then {
|
||||
LD_LIBRARY_PATH = "${vulkan-loader}/lib";
|
||||
} else { }));
|
||||
}
|
||||
);
|
||||
};
|
||||
] else
|
||||
[ ]);
|
||||
} // (if stdenv.isLinux then {
|
||||
LD_LIBRARY_PATH = "${vulkan-loader}/lib";
|
||||
} else
|
||||
{ }));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ func parseLoader(ld *opentype.Loader) (*fontapi.Font, giofont.Font, error) {
|
||||
// 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 {
|
||||
return &fontapi.Face{Font: f.face}
|
||||
return fontapi.NewFace(f.face)
|
||||
}
|
||||
|
||||
// FontFace returns a text.Font with populated font metadata for the
|
||||
|
||||
+12
-14
@@ -61,12 +61,8 @@ func (h *Hover) Update(q input.Source) bool {
|
||||
h.entered = false
|
||||
}
|
||||
case pointer.Enter:
|
||||
if !h.entered {
|
||||
h.pid = e.PointerID
|
||||
}
|
||||
if h.pid == e.PointerID {
|
||||
h.entered = true
|
||||
}
|
||||
h.pid = e.PointerID
|
||||
h.entered = true
|
||||
}
|
||||
}
|
||||
return h.entered
|
||||
@@ -222,12 +218,7 @@ func (c *Click) Update(q input.Source) (ClickEvent, bool) {
|
||||
if e.Source == pointer.Mouse && e.Buttons != pointer.ButtonPrimary {
|
||||
break
|
||||
}
|
||||
if !c.hovered {
|
||||
c.pid = e.PointerID
|
||||
}
|
||||
if c.pid != e.PointerID {
|
||||
break
|
||||
}
|
||||
c.pid = e.PointerID
|
||||
c.pressed = true
|
||||
if e.Time-c.clickedAt < doubleClickDuration {
|
||||
c.clicks++
|
||||
@@ -322,6 +313,8 @@ 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)
|
||||
@@ -353,10 +346,15 @@ 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 {
|
||||
if axis == Horizontal {
|
||||
switch axis {
|
||||
case Horizontal:
|
||||
return p.X
|
||||
} else {
|
||||
case Vertical:
|
||||
return p.Y
|
||||
case Both:
|
||||
return p.X + p.Y
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,6 +100,78 @@ func TestMouseClicks(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestClickPointerIDReassignment(t *testing.T) {
|
||||
// A Click must accept a Press from a PointerID that differs from the
|
||||
// one its hovered state was previously associated with. Some backends
|
||||
// reassign a single physical pointer's ID over its lifetime — e.g. the
|
||||
// Windows pointer API across focus changes — and locking the gesture
|
||||
// to the first observed ID would silently drop every subsequent press.
|
||||
//
|
||||
// The sequence below puts the gesture into the buggy state through
|
||||
// public events alone: a press under PointerID 1 starts an active
|
||||
// press cycle, a Move under PointerID 2 arrives mid-press (which the
|
||||
// router routes as an Enter for PID 2 but the gesture's Enter handler
|
||||
// is a no-op for pid while pressed), then PID 1 releases. After this,
|
||||
// the router has the gesture entered for PID 2 (so the next event
|
||||
// under PID 2 won't trigger another Enter) but the gesture itself
|
||||
// still has pid=1.
|
||||
var click Click
|
||||
var ops op.Ops
|
||||
rect := image.Rect(0, 0, 100, 100)
|
||||
stack := clip.Rect(rect).Push(&ops)
|
||||
click.Add(&ops)
|
||||
stack.Pop()
|
||||
|
||||
var r input.Router
|
||||
click.Update(r.Source())
|
||||
r.Frame(&ops)
|
||||
|
||||
drain := func() {
|
||||
for {
|
||||
if _, ok := click.Update(r.Source()); !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Press under PointerID 1.
|
||||
r.Queue(
|
||||
pointer.Event{Kind: pointer.Move, Source: pointer.Mouse, Position: f32.Pt(50, 50), PointerID: 1},
|
||||
pointer.Event{Kind: pointer.Press, Source: pointer.Mouse, Buttons: pointer.ButtonPrimary, Position: f32.Pt(50, 50), PointerID: 1},
|
||||
)
|
||||
drain()
|
||||
|
||||
// Move under PointerID 2 while PointerID 1 is still pressed. The
|
||||
// router records the gesture as entered for PointerID 2 but the
|
||||
// gesture's Enter handler is a no-op for pid because c.pressed.
|
||||
r.Queue(pointer.Event{Kind: pointer.Move, Source: pointer.Mouse, Position: f32.Pt(50, 50), PointerID: 2})
|
||||
drain()
|
||||
|
||||
// Release PointerID 1. PointerID 1's press tracking ends; the
|
||||
// gesture's recorded pid stays at 1.
|
||||
r.Queue(pointer.Event{Kind: pointer.Release, Source: pointer.Mouse, Position: f32.Pt(50, 50), PointerID: 1})
|
||||
drain()
|
||||
|
||||
// Press under PointerID 2. The router won't refire Enter for PID 2
|
||||
// (the gesture is already in PID 2's entered set), so the gesture's
|
||||
// only chance to refresh its pid is the Press handler itself.
|
||||
r.Queue(pointer.Event{Kind: pointer.Press, Source: pointer.Mouse, Buttons: pointer.ButtonPrimary, Position: f32.Pt(50, 50), PointerID: 2})
|
||||
|
||||
var sawPress bool
|
||||
for {
|
||||
ev, ok := click.Update(r.Source())
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if ev.Kind == KindPress {
|
||||
sawPress = true
|
||||
}
|
||||
}
|
||||
if !sawPress {
|
||||
t.Fatal("expected KindPress for press under reassigned PointerID; gesture dropped the press because of stale recorded pid")
|
||||
}
|
||||
}
|
||||
|
||||
func mouseClickEvents(times ...time.Duration) []event.Event {
|
||||
press := pointer.Event{
|
||||
Kind: pointer.Press,
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
module gioui.org
|
||||
|
||||
go 1.21
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d
|
||||
gioui.org/shader v1.0.8
|
||||
github.com/go-text/typesetting v0.2.1
|
||||
golang.org/x/exp v0.0.0-20240707233637-46b078467d37
|
||||
golang.org/x/exp/shiny v0.0.0-20240707233637-46b078467d37
|
||||
golang.org/x/image v0.18.0
|
||||
golang.org/x/sys v0.22.0
|
||||
golang.org/x/text v0.16.0
|
||||
github.com/go-text/typesetting v0.3.4
|
||||
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
|
||||
)
|
||||
|
||||
require golang.org/x/net v0.48.0
|
||||
|
||||
@@ -3,17 +3,19 @@ eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8v
|
||||
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/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.2.1 h1:x0jMOGyO3d1qFAPI0j4GSsh7M0Q3Ypjzr4+CEVg82V8=
|
||||
github.com/go-text/typesetting v0.2.1/go.mod h1:mTOxEwasOFpAMBjEQDhdWRckoLLeI/+qrQeBCTGEt6M=
|
||||
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-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w=
|
||||
golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/exp/shiny v0.0.0-20240707233637-46b078467d37 h1:SOSg7+sueresE4IbmmGM60GmlIys+zNX63d6/J4CMtU=
|
||||
golang.org/x/exp/shiny v0.0.0-20240707233637-46b078467d37/go.mod h1:3F+MieQB7dRYLTmnncoFbb1crS5lfQoTfDgQy6K4N0o=
|
||||
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
||||
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
github.com/go-text/typesetting v0.3.4 h1:YYurUOtEb9kGSOz4uE3k4OpBGsp1dDL8+fjCeaFamAU=
|
||||
github.com/go-text/typesetting v0.3.4/go.mod h1:4qZCQphq4KSgGTAeI0uMEkVbROgfah8BuyF5LRYr7XY=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20260223113751-2d88ac90dae3 h1:drBZzMgdYPbmyXqOto4YhhJGrFIQCX94FpR4MzTCsos=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20260223113751-2d88ac90dae3/go.mod h1:3/62I4La/HBRX9TcTpBj4eipLiwzf+vhI+7whTc9V7o=
|
||||
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=
|
||||
|
||||
+6
-6
@@ -19,10 +19,10 @@ type quadSplitter struct {
|
||||
|
||||
func encodeQuadTo(data []byte, meta uint32, from, ctrl, to f32.Point) {
|
||||
// inlined code:
|
||||
// encodeVertex(data, meta, -1, 1, from, ctrl, to)
|
||||
// encodeVertex(data, meta, 1, -1, from, ctrl, to)
|
||||
// encodeVertex(data[vertStride:], meta, 1, 1, from, ctrl, to)
|
||||
// encodeVertex(data[vertStride*2:], meta, -1, -1, from, ctrl, to)
|
||||
// encodeVertex(data[vertStride*3:], meta, 1, -1, from, ctrl, to)
|
||||
// encodeVertex(data[vertStride*3:], meta, -1, 1, from, ctrl, to)
|
||||
// this code needs to stay in sync with `vertex.encode`.
|
||||
|
||||
bo := binary.LittleEndian
|
||||
@@ -48,10 +48,10 @@ func encodeQuadTo(data []byte, meta uint32, from, ctrl, to f32.Point) {
|
||||
}
|
||||
|
||||
const (
|
||||
nwCorner = 1*0.25 + 0*0.5
|
||||
neCorner = 1*0.25 + 1*0.5
|
||||
swCorner = 0*0.25 + 0*0.5
|
||||
seCorner = 0*0.25 + 1*0.5
|
||||
nwCorner = 1*0.5 + 0*0.25
|
||||
neCorner = 1*0.5 + 1*0.25
|
||||
swCorner = 0*0.5 + 0*0.25
|
||||
seCorner = 0*0.5 + 1*0.25
|
||||
)
|
||||
|
||||
func encodeVertex(data []byte, meta uint32, cornerx, cornery int16, from, ctrl, to f32.Point) {
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
func BenchmarkEncodeQuadTo(b *testing.B) {
|
||||
var data [vertStride * 4]byte
|
||||
for i := 0; i < b.N; i++ {
|
||||
for i := 0; b.Loop(); i++ {
|
||||
v := float32(i)
|
||||
encodeQuadTo(data[:], 123,
|
||||
f32.Point{X: v, Y: v},
|
||||
|
||||
+38
-27
@@ -15,6 +15,7 @@ import (
|
||||
"image/color"
|
||||
"math"
|
||||
"reflect"
|
||||
"slices"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
@@ -189,7 +190,7 @@ const (
|
||||
// imageOpData is the shadow of paint.ImageOp.
|
||||
type imageOpData struct {
|
||||
src *image.RGBA
|
||||
handle interface{}
|
||||
handle any
|
||||
filter byte
|
||||
}
|
||||
|
||||
@@ -200,7 +201,7 @@ type linearGradientOpData struct {
|
||||
color2 color.NRGBA
|
||||
}
|
||||
|
||||
func decodeImageOp(data []byte, refs []interface{}) imageOpData {
|
||||
func decodeImageOp(data []byte, refs []any) imageOpData {
|
||||
handle := refs[1]
|
||||
if handle == nil {
|
||||
return imageOpData{}
|
||||
@@ -547,7 +548,7 @@ func newBlitter(ctx driver.Device) *blitter {
|
||||
b.texUniforms = new(blitTexUniforms)
|
||||
b.linearGradientUniforms = new(blitLinearGradientUniforms)
|
||||
pipelines, err := createColorPrograms(ctx, gio.Shader_blit_vert, gio.Shader_blit_frag,
|
||||
[3]interface{}{b.colUniforms, b.linearGradientUniforms, b.texUniforms},
|
||||
[3]any{b.colUniforms, b.linearGradientUniforms, b.texUniforms},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -565,7 +566,7 @@ func (b *blitter) release() {
|
||||
}
|
||||
}
|
||||
|
||||
func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.Sources, uniforms [3]interface{}) (pipelines [2][3]*pipeline, err error) {
|
||||
func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.Sources, uniforms [3]any) (pipelines [2][3]*pipeline, err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
for _, p := range pipelines {
|
||||
@@ -822,7 +823,7 @@ func (r *renderer) packLayers(layers []opacityLayer) []opacityLayer {
|
||||
layers[l.parent].clip = b.Union(l.clip)
|
||||
}
|
||||
if l.clip.Empty() {
|
||||
layers = append(layers[:i], layers[i+1:]...)
|
||||
layers = slices.Delete(layers, i, i+1)
|
||||
}
|
||||
}
|
||||
// Pack layers.
|
||||
@@ -871,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.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset)
|
||||
uvTrans := f32.AffineId().Scale(f32.Point{}, uvScale).Offset(uvOffset)
|
||||
// Replace layer ops with one textured op.
|
||||
ops[l.opStart] = imageOp{
|
||||
clip: l.clip,
|
||||
@@ -956,7 +957,9 @@ func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey opKey, bounds
|
||||
|
||||
func (d *drawOps) save(id int, state f32.Affine2D) {
|
||||
if extra := id - len(d.states) + 1; extra > 0 {
|
||||
d.states = append(d.states, make([]f32.Affine2D, extra)...)
|
||||
for range extra {
|
||||
d.states = append(d.states, f32.AffineId())
|
||||
}
|
||||
}
|
||||
d.states[id] = state
|
||||
}
|
||||
@@ -971,12 +974,13 @@ func (k opKey) SetTransform(t f32.Affine2D) opKey {
|
||||
}
|
||||
|
||||
func (d *drawOps) collectOps(r *ops.Reader, viewport f32.Rectangle) {
|
||||
var (
|
||||
quads quadsOp
|
||||
state drawState
|
||||
)
|
||||
var quads quadsOp
|
||||
state := drawState{
|
||||
t: f32.AffineId(),
|
||||
}
|
||||
reset := func() {
|
||||
state = drawState{
|
||||
t: f32.AffineId(),
|
||||
color: color.NRGBA{A: 0xff},
|
||||
}
|
||||
}
|
||||
@@ -1032,7 +1036,7 @@ loop:
|
||||
op.Decode(encOp.Data)
|
||||
quads.key.outline = op.Outline
|
||||
bounds := f32.FRect(op.Bounds)
|
||||
trans, off := state.t.Split()
|
||||
trans, off := transformOffset(state.t)
|
||||
if len(quads.aux) > 0 {
|
||||
// There is a clipping path, build the gpu data and update the
|
||||
// cache key such that it will be equal only if the transform is the
|
||||
@@ -1055,6 +1059,7 @@ 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{}
|
||||
@@ -1078,7 +1083,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 := state.t.Split()
|
||||
t, off := transformOffset(state.t)
|
||||
// Fill the clip area, unless the material is a (bounded) image.
|
||||
// TODO: Find a tighter bound.
|
||||
inf := float32(1e6)
|
||||
@@ -1100,7 +1105,7 @@ loop:
|
||||
// The paint operation is sheared or rotated, add a clip path representing
|
||||
// this transformed rectangle.
|
||||
k := opKey{Key: encOp.Key}
|
||||
k.SetTransform(t) // TODO: This call has no effect.
|
||||
k = k.SetTransform(t)
|
||||
d.addClipPath(&state, clipData, k, bnd, off)
|
||||
}
|
||||
|
||||
@@ -1161,6 +1166,7 @@ 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:
|
||||
@@ -1194,7 +1200,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.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset))
|
||||
m.uvTrans = partTrans.Mul(f32.AffineId().Scale(f32.Point{}, uvScale).Offset(uvOffset))
|
||||
m.data = d.image
|
||||
}
|
||||
return m
|
||||
@@ -1315,7 +1321,7 @@ func (b *blitter) blit(mat materialType, fbo bool, col f32color.RGBA, col1, col2
|
||||
|
||||
// newUniformBuffer creates a new GPU uniform buffer backed by the
|
||||
// structure uniformBlock points to.
|
||||
func newUniformBuffer(b driver.Device, uniformBlock interface{}) *uniformBuffer {
|
||||
func newUniformBuffer(b driver.Device, uniformBlock any) *uniformBuffer {
|
||||
ref := reflect.ValueOf(uniformBlock)
|
||||
// Determine the size of the uniforms structure, *uniforms.
|
||||
size := ref.Elem().Type().Size()
|
||||
@@ -1369,7 +1375,7 @@ func gradientSpaceTransform(clip image.Rectangle, off f32.Point, stop1, stop2 f3
|
||||
|
||||
// TODO: optimize
|
||||
zp := f32.Point{}
|
||||
return f32.Affine2D{}.
|
||||
return f32.AffineId().
|
||||
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
|
||||
@@ -1519,12 +1525,10 @@ func decodeToOutlineQuads(qs *quadSplitter, tr f32.Affine2D, pathData []byte) {
|
||||
|
||||
// create GPU vertices for transformed r, find the bounds and establish texture transform.
|
||||
func (d *drawOps) boundsForTransformedRect(r f32.Rectangle, tr f32.Affine2D) (aux []byte, bnd f32.Rectangle, ptr f32.Affine2D) {
|
||||
if isPureOffset(tr) {
|
||||
// fast-path to allow blitting of pure rectangles
|
||||
_, _, ox, _, _, oy := tr.Elems()
|
||||
off := f32.Pt(ox, oy)
|
||||
bnd.Min = r.Min.Add(off)
|
||||
bnd.Max = r.Max.Add(off)
|
||||
ptr = f32.AffineId()
|
||||
if tr == f32.AffineId() {
|
||||
// fast-path to allow blitting of pure rectangles.
|
||||
bnd = r
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1571,12 +1575,19 @@ 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
|
||||
return aux, bnd, ptr
|
||||
}
|
||||
|
||||
func isPureOffset(t f32.Affine2D) bool {
|
||||
a, b, _, d, e, _ := t.Elems()
|
||||
return a == 1 && b == 0 && d == 0 && e == 1
|
||||
// transformOffset a transform into two parts, one which is pure integer offset
|
||||
// and the other representing the scaling, shearing and rotation and fractional
|
||||
// offset.
|
||||
func transformOffset(t f32.Affine2D) (f32.Affine2D, 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) {
|
||||
|
||||
@@ -21,8 +21,10 @@ import (
|
||||
|
||||
var dumpImages = flag.Bool("saveimages", false, "save test images")
|
||||
|
||||
var clearCol = color.NRGBA{A: 0xff, R: 0xde, G: 0xad, B: 0xbe}
|
||||
var clearColExpect = f32color.NRGBAToRGBA(clearCol)
|
||||
var (
|
||||
clearCol = color.NRGBA{A: 0xff, R: 0xde, G: 0xad, B: 0xbe}
|
||||
clearColExpect = f32color.NRGBAToRGBA(clearCol)
|
||||
)
|
||||
|
||||
func TestFramebufferClear(t *testing.T) {
|
||||
b := newDriver(t)
|
||||
@@ -202,5 +204,5 @@ func saveImage(file string, img image.Image) error {
|
||||
if err := png.Encode(&buf, img); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(file, buf.Bytes(), 0666)
|
||||
return os.WriteFile(file, buf.Bytes(), 0o666)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build linux || freebsd || openbsd
|
||||
// +build linux freebsd openbsd
|
||||
|
||||
package headless
|
||||
|
||||
|
||||
@@ -152,9 +152,7 @@ func newDirect3D11Device(api driver.Direct3D11) (driver.Device, error) {
|
||||
}
|
||||
|
||||
func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport image.Point) driver.Texture {
|
||||
var (
|
||||
renderTarget *d3d11.RenderTargetView
|
||||
)
|
||||
var renderTarget *d3d11.RenderTargetView
|
||||
if target != nil {
|
||||
switch t := target.(type) {
|
||||
case driver.Direct3D11RenderTarget:
|
||||
@@ -229,10 +227,7 @@ func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, min
|
||||
// Flags required by ID3D11DeviceContext::GenerateMips.
|
||||
bindFlags |= d3d11.BIND_SHADER_RESOURCE | d3d11.BIND_RENDER_TARGET
|
||||
miscFlags |= d3d11.RESOURCE_MISC_GENERATE_MIPS
|
||||
dim := width
|
||||
if height > dim {
|
||||
dim = height
|
||||
}
|
||||
dim := max(height, width)
|
||||
log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1
|
||||
nmipmaps = log2 + 1
|
||||
}
|
||||
@@ -802,7 +797,7 @@ func (t *Texture) ReadPixels(src image.Rectangle, pixels []byte, stride int) err
|
||||
mapSize := dstPitch * h
|
||||
data := sliceOf(resMap.PData, mapSize)
|
||||
width := w * 4
|
||||
for r := 0; r < h; r++ {
|
||||
for r := range h {
|
||||
pixels := pixels[r*srcPitch:]
|
||||
copy(pixels[:width], data[r*dstPitch:])
|
||||
}
|
||||
|
||||
@@ -96,8 +96,10 @@ type BlendFactor uint8
|
||||
|
||||
type Topology uint8
|
||||
|
||||
type TextureFilter uint8
|
||||
type TextureFormat uint8
|
||||
type (
|
||||
TextureFilter uint8
|
||||
TextureFormat uint8
|
||||
)
|
||||
|
||||
type BufferBinding uint8
|
||||
|
||||
@@ -217,7 +219,7 @@ func flipImageY(stride, height int, pixels []byte) {
|
||||
// Flip image in y-direction. OpenGL's origin is in the lower
|
||||
// left corner.
|
||||
row := make([]uint8, stride)
|
||||
for y := 0; y < height/2; y++ {
|
||||
for y := range height / 2 {
|
||||
y1 := height - y - 1
|
||||
dest := y1 * stride
|
||||
src := y * stride
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"image"
|
||||
"math/bits"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
@@ -717,10 +718,7 @@ func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, min
|
||||
if mipmap {
|
||||
nmipmaps := 1
|
||||
if mipmap {
|
||||
dim := width
|
||||
if height > dim {
|
||||
dim = height
|
||||
}
|
||||
dim := max(height, width)
|
||||
log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1
|
||||
nmipmaps = log2 + 1
|
||||
}
|
||||
@@ -1132,7 +1130,7 @@ func (b *Backend) setupVertexArrays() {
|
||||
enabled[inp.Location] = true
|
||||
b.glstate.vertexAttribPointer(b.funcs, buf.obj, inp.Location, l.Size, gltyp, false, p.layout.Stride, buf.offset+l.Offset)
|
||||
}
|
||||
for i := 0; i < max; i++ {
|
||||
for i := range max {
|
||||
b.glstate.setVertexAttribArray(b.funcs, i, enabled[i])
|
||||
}
|
||||
}
|
||||
@@ -1175,7 +1173,7 @@ func (t *texture) ReadPixels(src image.Rectangle, pixels []byte, stride int) err
|
||||
} else {
|
||||
tmp := make([]byte, w*h*4)
|
||||
t.backend.funcs.ReadPixels(src.Min.X, src.Min.Y, w, h, gl.RGBA, gl.UNSIGNED_BYTE, tmp)
|
||||
for y := 0; y < h; y++ {
|
||||
for y := range h {
|
||||
copy(pixels[y*stride:], tmp[y*w*4:])
|
||||
}
|
||||
}
|
||||
@@ -1357,12 +1355,7 @@ func alphaTripleFor(ver [2]int) textureTriple {
|
||||
}
|
||||
|
||||
func hasExtension(exts []string, ext string) bool {
|
||||
for _, e := range exts {
|
||||
if ext == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.Contains(exts, ext)
|
||||
}
|
||||
|
||||
func firstBufferType(typ driver.BufferBinding) gl.Enum {
|
||||
|
||||
@@ -66,8 +66,8 @@ func BenchmarkDrawUICached(b *testing.B) {
|
||||
defer w.Release()
|
||||
drawCore(gtx, th)
|
||||
w.Frame(gtx.Ops)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
||||
for b.Loop() {
|
||||
w.Frame(gtx.Ops)
|
||||
}
|
||||
finishBenchmark(b, w)
|
||||
@@ -83,12 +83,12 @@ func BenchmarkDrawUI(b *testing.B) {
|
||||
drawCore(gtx, th)
|
||||
w.Frame(gtx.Ops)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
||||
for i := 0; b.Loop(); i++ {
|
||||
resetOps(gtx)
|
||||
|
||||
off := float32(math.Mod(float64(i)/10, 10))
|
||||
t := op.Affine(f32.Affine2D{}.Offset(f32.Pt(off, off))).Push(gtx.Ops)
|
||||
t := op.Affine(f32.AffineId().Offset(f32.Pt(off, off))).Push(gtx.Ops)
|
||||
|
||||
drawCore(gtx, th)
|
||||
|
||||
@@ -105,12 +105,12 @@ func BenchmarkDrawUITransformed(b *testing.B) {
|
||||
drawCore(gtx, th)
|
||||
w.Frame(gtx.Ops)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
||||
for i := 0; b.Loop(); i++ {
|
||||
resetOps(gtx)
|
||||
|
||||
angle := float32(math.Mod(float64(i)/1000, 0.05))
|
||||
a := f32.Affine2D{}.Shear(f32.Point{}, angle, angle).Rotate(f32.Point{}, angle)
|
||||
a := f32.AffineId().Shear(f32.Point{}, angle, angle).Rotate(f32.Point{}, angle)
|
||||
t := op.Affine(a).Push(gtx.Ops)
|
||||
|
||||
drawCore(gtx, th)
|
||||
@@ -130,8 +130,8 @@ func Benchmark1000Circles(b *testing.B) {
|
||||
draw1000Circles(gtx)
|
||||
w.Frame(gtx.Ops)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
||||
for b.Loop() {
|
||||
resetOps(gtx)
|
||||
draw1000Circles(gtx)
|
||||
w.Frame(gtx.Ops)
|
||||
@@ -147,8 +147,8 @@ func Benchmark1000CirclesInstanced(b *testing.B) {
|
||||
draw1000CirclesInstanced(gtx)
|
||||
w.Frame(gtx.Ops)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
||||
for b.Loop() {
|
||||
resetOps(gtx)
|
||||
draw1000CirclesInstanced(gtx)
|
||||
w.Frame(gtx.Ops)
|
||||
@@ -158,9 +158,9 @@ func Benchmark1000CirclesInstanced(b *testing.B) {
|
||||
|
||||
func draw1000Circles(gtx layout.Context) {
|
||||
ops := gtx.Ops
|
||||
for x := 0; x < 100; x++ {
|
||||
for x := range 100 {
|
||||
op.Offset(image.Pt(x*10, 0)).Add(ops)
|
||||
for y := 0; y < 10; y++ {
|
||||
for y := range 10 {
|
||||
paint.FillShape(ops,
|
||||
color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120},
|
||||
clip.RRect{Rect: image.Rect(0, 0, 10, 10), NE: 5, SE: 5, SW: 5, NW: 5}.Op(ops),
|
||||
@@ -179,9 +179,9 @@ func draw1000CirclesInstanced(gtx layout.Context) {
|
||||
cl.Pop()
|
||||
c := r.Stop()
|
||||
|
||||
for x := 0; x < 100; x++ {
|
||||
for x := range 100 {
|
||||
op.Offset(image.Pt(x*10, 0)).Add(ops)
|
||||
for y := 0; y < 10; y++ {
|
||||
for y := range 10 {
|
||||
paint.ColorOp{Color: color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120}}.Add(ops)
|
||||
c.Add(ops)
|
||||
op.Offset(image.Pt(0, 100)).Add(ops)
|
||||
@@ -204,9 +204,9 @@ func drawIndividualShapes(gtx layout.Context, th *material.Theme) chan op.CallOp
|
||||
go func() {
|
||||
ops := &op1
|
||||
c := op.Record(ops)
|
||||
for x := 0; x < 9; x++ {
|
||||
for x := range 9 {
|
||||
op.Offset(image.Pt(x*50, 0)).Add(ops)
|
||||
for y := 0; y < 9; y++ {
|
||||
for y := range 9 {
|
||||
paint.FillShape(ops,
|
||||
color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120},
|
||||
clip.RRect{Rect: image.Rect(0, 0, 25, 25), NE: 10, SE: 10, SW: 10, NW: 10}.Op(ops),
|
||||
@@ -233,8 +233,8 @@ func drawShapeInstances(gtx layout.Context, th *material.Theme) chan op.CallOp {
|
||||
|
||||
squares.Add(ops)
|
||||
rad := float32(0)
|
||||
for x := 0; x < 20; x++ {
|
||||
for y := 0; y < 20; y++ {
|
||||
for x := range 20 {
|
||||
for y := range 20 {
|
||||
t := op.Offset(image.Pt(x*50+25, y*50+25)).Push(ops)
|
||||
c.Add(ops)
|
||||
t.Pop()
|
||||
@@ -253,7 +253,7 @@ func drawText(gtx layout.Context, th *material.Theme) chan op.CallOp {
|
||||
c := op.Record(ops)
|
||||
|
||||
txt := material.H6(th, "")
|
||||
for x := 0; x < 40; x++ {
|
||||
for x := range 40 {
|
||||
txt.Text = textRows[x]
|
||||
t := op.Offset(image.Pt(0, 24*x)).Push(ops)
|
||||
gtx.Ops = ops
|
||||
|
||||
@@ -40,6 +40,25 @@ func TestPaintClippedRect(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestPaintClippedRectOffset(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
defer op.Affine(f32.AffineId().Offset(f32.Pt(0.5, 0.5))).Push(o).Pop()
|
||||
defer clip.RRect{Rect: image.Rect(25, 25, 60, 60)}.Push(o).Pop()
|
||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 50, 50)).Op())
|
||||
}, func(r result) {
|
||||
r.expect(0, 0, transparent)
|
||||
r.expect(24, 35, transparent)
|
||||
r.expect(24, 24, transparent)
|
||||
r.expect(25, 25, color.RGBA{R: 137, A: 64})
|
||||
r.expect(25, 35, color.RGBA{R: 187, A: 128})
|
||||
r.expect(35, 25, color.RGBA{R: 187, A: 128})
|
||||
r.expect(50, 50, color.RGBA{R: 137, A: 64})
|
||||
r.expect(51, 51, transparent)
|
||||
r.expect(50, 0, transparent)
|
||||
r.expect(10, 50, transparent)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPaintClippedCircle(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
const r = 10
|
||||
@@ -132,8 +151,7 @@ func TestTexturedStrokeClipped(t *testing.T) {
|
||||
defer clip.RRect{Rect: image.Rect(-30, -30, 60, 60)}.Push(o).Pop()
|
||||
defer op.Offset(image.Pt(-10, -10)).Push(o).Pop()
|
||||
paint.PaintOp{}.Add(o)
|
||||
}, func(r result) {
|
||||
})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func TestTexturedStroke(t *testing.T) {
|
||||
@@ -146,8 +164,7 @@ func TestTexturedStroke(t *testing.T) {
|
||||
}.Op().Push(o).Pop()
|
||||
defer op.Offset(image.Pt(-10, -10)).Push(o).Pop()
|
||||
paint.PaintOp{}.Add(o)
|
||||
}, func(r result) {
|
||||
})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func TestPaintClippedTexture(t *testing.T) {
|
||||
@@ -178,7 +195,6 @@ func TestStrokedPathZeroWidth(t *testing.T) {
|
||||
paint.Fill(o, red)
|
||||
cl.Pop()
|
||||
}
|
||||
|
||||
}, func(r result) {
|
||||
r.expect(0, 0, transparent)
|
||||
r.expect(10, 50, colornames.Black)
|
||||
@@ -252,8 +268,7 @@ func TestPathReuse(t *testing.T) {
|
||||
stroke := clip.Stroke{Path: spec, Width: 3}.Op().Push(o)
|
||||
paint.Fill(o, color.NRGBA{B: 0xFF, A: 0xFF})
|
||||
stroke.Pop()
|
||||
}, func(r result) {
|
||||
})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func TestPathInterleave(t *testing.T) {
|
||||
@@ -290,6 +305,22 @@ func TestStrokedRect(t *testing.T) {
|
||||
Width: 5,
|
||||
}.Op(),
|
||||
)
|
||||
}, func(r result) {
|
||||
})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func TestInstancedRects(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
macro := op.Record(o)
|
||||
clip := clip.Rect{Max: image.Pt(20, 20)}.Push(o)
|
||||
paint.ColorOp{Color: color.NRGBA{R: 0x80, A: 0xFF}}.Add(o)
|
||||
paint.PaintOp{}.Add(o)
|
||||
clip.Pop()
|
||||
c := macro.Stop()
|
||||
|
||||
for range 2 {
|
||||
op.Affine(f32.AffineId().Rotate(f32.Pt(0, 0), .2)).Add(o)
|
||||
c.Add(o)
|
||||
op.Offset(image.Pt(20, 20)).Add(o)
|
||||
}
|
||||
}, nil)
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 787 B |
Binary file not shown.
|
After Width: | Height: | Size: 435 B |
@@ -24,7 +24,6 @@ func TestTransformMacro(t *testing.T) {
|
||||
c := constSqPath()
|
||||
|
||||
run(t, func(o *op.Ops) {
|
||||
|
||||
// render the first Stacked item
|
||||
m1 := op.Record(o)
|
||||
dr := image.Rect(0, 0, 128, 50)
|
||||
@@ -88,10 +87,10 @@ func TestNoClipFromPaint(t *testing.T) {
|
||||
// ensure that a paint operation does not pollute the state
|
||||
// by leaving any clip paths in place.
|
||||
run(t, func(o *op.Ops) {
|
||||
a := f32.Affine2D{}.Rotate(f32.Pt(20, 20), math.Pi/4)
|
||||
a := f32.AffineId().Rotate(f32.Pt(20, 20), math.Pi/4)
|
||||
defer op.Affine(a).Push(o).Pop()
|
||||
paint.FillShape(o, red, clip.Rect(image.Rect(10, 10, 30, 30)).Op())
|
||||
a = f32.Affine2D{}.Rotate(f32.Pt(20, 20), -math.Pi/4)
|
||||
a = f32.AffineId().Rotate(f32.Pt(20, 20), -math.Pi/4)
|
||||
defer op.Affine(a).Push(o).Pop()
|
||||
|
||||
paint.FillShape(o, black, clip.Rect(image.Rect(0, 0, 50, 50)).Op())
|
||||
@@ -110,7 +109,7 @@ func TestDeferredPaint(t *testing.T) {
|
||||
paint.PaintOp{}.Add(o)
|
||||
cl.Pop()
|
||||
|
||||
t := op.Affine(f32.Affine2D{}.Offset(f32.Pt(20, 20))).Push(o)
|
||||
t := op.Affine(f32.AffineId().Offset(f32.Pt(20, 20))).Push(o)
|
||||
m := op.Record(o)
|
||||
cl2 := clip.Rect(image.Rect(0, 0, 80, 80)).Op().Push(o)
|
||||
paint.ColorOp{Color: color.NRGBA{A: 0x60, R: 0xff, G: 0xff}}.Add(o)
|
||||
@@ -120,12 +119,11 @@ func TestDeferredPaint(t *testing.T) {
|
||||
op.Defer(o, paintMacro)
|
||||
t.Pop()
|
||||
|
||||
defer op.Affine(f32.Affine2D{}.Offset(f32.Pt(10, 10))).Push(o).Pop()
|
||||
defer op.Affine(f32.AffineId().Offset(f32.Pt(10, 10))).Push(o).Pop()
|
||||
defer clip.Rect(image.Rect(0, 0, 80, 80)).Op().Push(o).Pop()
|
||||
paint.ColorOp{Color: color.NRGBA{A: 0x60, B: 0xff}}.Add(o)
|
||||
paint.PaintOp{}.Add(o)
|
||||
}, func(r result) {
|
||||
})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func constSqPath() clip.Op {
|
||||
@@ -143,8 +141,10 @@ func constSqPath() clip.Op {
|
||||
|
||||
func constSqCirc() clip.Op {
|
||||
innerOps := new(op.Ops)
|
||||
return clip.RRect{Rect: image.Rect(0, 0, 40, 40),
|
||||
NW: 20, NE: 20, SW: 20, SE: 20}.Op(innerOps)
|
||||
return clip.RRect{
|
||||
Rect: image.Rect(0, 0, 40, 40),
|
||||
NW: 20, NE: 20, SW: 20, SE: 20,
|
||||
}.Op(innerOps)
|
||||
}
|
||||
|
||||
func drawChild(ops *op.Ops, text clip.Op) op.CallOp {
|
||||
@@ -260,7 +260,7 @@ func TestLinearGradient(t *testing.T) {
|
||||
Color2: g.To,
|
||||
}.Add(ops)
|
||||
cl := clip.RRect{Rect: gr.Round()}.Push(ops)
|
||||
t1 := op.Affine(f32.Affine2D{}.Offset(pixelAligned.Min)).Push(ops)
|
||||
t1 := op.Affine(f32.AffineId().Offset(pixelAligned.Min)).Push(ops)
|
||||
t2 := scale(pixelAligned.Dx()/128, 1).Push(ops)
|
||||
paint.PaintOp{}.Add(ops)
|
||||
t2.Pop()
|
||||
@@ -323,7 +323,7 @@ func TestLinearGradientAngled(t *testing.T) {
|
||||
cl = clip.Rect(image.Rect(0, 64, 64, 128)).Push(ops)
|
||||
paint.PaintOp{}.Add(ops)
|
||||
cl.Pop()
|
||||
}, func(r result) {})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func TestZeroImage(t *testing.T) {
|
||||
@@ -363,7 +363,7 @@ func TestImageRGBA_ScaleLinear(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
w := newWindow(t, 128, 128)
|
||||
defer clip.Rect{Max: image.Pt(128, 128)}.Push(o).Pop()
|
||||
op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(64, 64))).Add(o)
|
||||
op.Affine(f32.AffineId().Scale(f32.Point{}, f32.Pt(64, 64))).Add(o)
|
||||
|
||||
im := image.NewRGBA(image.Rect(0, 0, 2, 2))
|
||||
im.Set(0, 0, colornames.Red)
|
||||
@@ -397,7 +397,7 @@ func TestImageRGBA_ScaleLinear(t *testing.T) {
|
||||
func TestImageRGBA_ScaleNearest(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
w := newWindow(t, 128, 128)
|
||||
op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(64, 64))).Add(o)
|
||||
op.Affine(f32.AffineId().Scale(f32.Point{}, f32.Pt(64, 64))).Add(o)
|
||||
|
||||
im := image.NewRGBA(image.Rect(0, 0, 2, 2))
|
||||
im.Set(0, 0, colornames.Red)
|
||||
@@ -492,8 +492,7 @@ 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()
|
||||
}, func(r result) {
|
||||
})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
// lerp calculates linear interpolation with color b and p.
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestPaintOffset(t *testing.T) {
|
||||
|
||||
func TestPaintRotate(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
a := f32.Affine2D{}.Rotate(f32.Pt(40, 40), -math.Pi/8)
|
||||
a := f32.AffineId().Rotate(f32.Pt(40, 40), -math.Pi/8)
|
||||
defer op.Affine(a).Push(o).Pop()
|
||||
paint.FillShape(o, red, clip.Rect(image.Rect(20, 20, 60, 60)).Op())
|
||||
}, func(r result) {
|
||||
@@ -42,7 +42,7 @@ func TestPaintRotate(t *testing.T) {
|
||||
|
||||
func TestPaintShear(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
a := f32.Affine2D{}.Shear(f32.Point{}, math.Pi/4, 0)
|
||||
a := f32.AffineId().Shear(f32.Point{}, math.Pi/4, 0)
|
||||
defer op.Affine(a).Push(o).Pop()
|
||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 40, 40)).Op())
|
||||
}, func(r result) {
|
||||
@@ -79,7 +79,7 @@ func TestClipOffset(t *testing.T) {
|
||||
|
||||
func TestClipScale(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
a := f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(2, 2)).Offset(f32.Pt(10, 10))
|
||||
a := f32.AffineId().Scale(f32.Point{}, f32.Pt(2, 2)).Offset(f32.Pt(10, 10))
|
||||
defer op.Affine(a).Push(o).Pop()
|
||||
defer clip.RRect{Rect: image.Rect(10, 10, 20, 20)}.Push(o).Pop()
|
||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 1000, 1000)).Op())
|
||||
@@ -93,7 +93,7 @@ func TestClipScale(t *testing.T) {
|
||||
|
||||
func TestClipRotate(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
defer op.Affine(f32.Affine2D{}.Rotate(f32.Pt(40, 40), -math.Pi/4)).Push(o).Pop()
|
||||
defer op.Affine(f32.AffineId().Rotate(f32.Pt(40, 40), -math.Pi/4)).Push(o).Pop()
|
||||
defer clip.RRect{Rect: image.Rect(30, 30, 50, 50)}.Push(o).Pop()
|
||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 40, 100, 100)).Op())
|
||||
}, func(r result) {
|
||||
@@ -121,7 +121,7 @@ func TestOffsetScaleTexture(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
defer op.Offset(image.Pt(15, 15)).Push(o).Pop()
|
||||
squares.Add(o)
|
||||
defer op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(2, 1))).Push(o).Pop()
|
||||
defer op.Affine(f32.AffineId().Scale(f32.Point{}, f32.Pt(2, 1))).Push(o).Pop()
|
||||
defer scale(50.0/512, 50.0/512).Push(o).Pop()
|
||||
paint.PaintOp{}.Add(o)
|
||||
}, func(r result) {
|
||||
@@ -133,7 +133,7 @@ func TestOffsetScaleTexture(t *testing.T) {
|
||||
func TestRotateTexture(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
squares.Add(o)
|
||||
a := f32.Affine2D{}.Offset(f32.Pt(30, 30)).Rotate(f32.Pt(40, 40), math.Pi/4)
|
||||
a := f32.AffineId().Offset(f32.Pt(30, 30)).Rotate(f32.Pt(40, 40), math.Pi/4)
|
||||
defer op.Affine(a).Push(o).Pop()
|
||||
defer scale(20.0/512, 20.0/512).Push(o).Pop()
|
||||
paint.PaintOp{}.Add(o)
|
||||
@@ -146,10 +146,10 @@ func TestRotateTexture(t *testing.T) {
|
||||
func TestRotateClipTexture(t *testing.T) {
|
||||
run(t, func(o *op.Ops) {
|
||||
squares.Add(o)
|
||||
a := f32.Affine2D{}.Rotate(f32.Pt(40, 40), math.Pi/8)
|
||||
a := f32.AffineId().Rotate(f32.Pt(40, 40), math.Pi/8)
|
||||
defer op.Affine(a).Push(o).Pop()
|
||||
defer clip.RRect{Rect: image.Rect(30, 30, 50, 50)}.Push(o).Pop()
|
||||
defer op.Affine(f32.Affine2D{}.Offset(f32.Pt(10, 10))).Push(o).Pop()
|
||||
defer op.Affine(f32.AffineId().Offset(f32.Pt(10, 10))).Push(o).Pop()
|
||||
defer scale(60.0/512, 60.0/512).Push(o).Pop()
|
||||
paint.PaintOp{}.Add(o)
|
||||
}, func(r result) {
|
||||
@@ -168,7 +168,7 @@ func TestComplicatedTransform(t *testing.T) {
|
||||
|
||||
defer clip.RRect{Rect: image.Rect(0, 0, 100, 100), SE: 50, SW: 50, NW: 50, NE: 50}.Push(o).Pop()
|
||||
|
||||
a := f32.Affine2D{}.Shear(f32.Point{}, math.Pi/4, 0)
|
||||
a := f32.AffineId().Shear(f32.Point{}, math.Pi/4, 0)
|
||||
defer op.Affine(a).Push(o).Pop()
|
||||
defer clip.RRect{Rect: image.Rect(0, 0, 50, 40)}.Push(o).Pop()
|
||||
|
||||
@@ -182,13 +182,13 @@ func TestComplicatedTransform(t *testing.T) {
|
||||
func TestTransformOrder(t *testing.T) {
|
||||
// check the ordering of operations bot in affine and in gpu stack.
|
||||
run(t, func(o *op.Ops) {
|
||||
a := f32.Affine2D{}.Offset(f32.Pt(64, 64))
|
||||
a := f32.AffineId().Offset(f32.Pt(64, 64))
|
||||
defer op.Affine(a).Push(o).Pop()
|
||||
|
||||
b := f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(8, 8))
|
||||
b := f32.AffineId().Scale(f32.Point{}, f32.Pt(8, 8))
|
||||
defer op.Affine(b).Push(o).Pop()
|
||||
|
||||
c := f32.Affine2D{}.Offset(f32.Pt(-10, -10)).Scale(f32.Point{}, f32.Pt(0.5, 0.5))
|
||||
c := f32.AffineId().Offset(f32.Pt(-10, -10)).Scale(f32.Point{}, f32.Pt(0.5, 0.5))
|
||||
defer op.Affine(c).Push(o).Pop()
|
||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 20, 20)).Op())
|
||||
}, func(r result) {
|
||||
|
||||
@@ -49,8 +49,8 @@ func buildSquares(size int) paint.ImageOp {
|
||||
sub := size / 4
|
||||
im := image.NewNRGBA(image.Rect(0, 0, size, size))
|
||||
c1, c2 := image.NewUniform(colornames.Green), image.NewUniform(colornames.Blue)
|
||||
for r := 0; r < 4; r++ {
|
||||
for c := 0; c < 4; c++ {
|
||||
for r := range 4 {
|
||||
for c := range 4 {
|
||||
c1, c2 = c2, c1
|
||||
draw.Draw(im, image.Rect(r*sub, c*sub, r*sub+sub, c*sub+sub), c1, image.Point{}, draw.Over)
|
||||
}
|
||||
@@ -78,7 +78,7 @@ func run(t *testing.T, f func(o *op.Ops), c func(r result)) {
|
||||
var img *image.RGBA
|
||||
var err error
|
||||
ops := new(op.Ops)
|
||||
for i := 0; i < 3; i++ {
|
||||
for i := range 3 {
|
||||
ops.Reset()
|
||||
img, err = drawImage(t, 128, ops, f)
|
||||
if err != nil {
|
||||
@@ -90,7 +90,9 @@ func run(t *testing.T, f func(o *op.Ops), c func(r result)) {
|
||||
name := fmt.Sprintf("%s-%d-bad.png", t.Name(), i)
|
||||
saveImage(t, name, img)
|
||||
}
|
||||
c(result{t: t, img: img})
|
||||
if c != nil {
|
||||
c(result{t: t, img: img})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +153,7 @@ func verifyRef(t *testing.T, img *image.RGBA, frame int) (ok bool) {
|
||||
}
|
||||
path = filepath.Join("refs", path+".png")
|
||||
if *dumpImages {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0766); err != nil {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o766); err != nil {
|
||||
if !os.IsExist(err) {
|
||||
t.Error(err)
|
||||
return
|
||||
@@ -285,7 +287,7 @@ func saveImage(t testing.TB, file string, img *image.RGBA) {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if err := os.WriteFile(file, buf.Bytes(), 0666); err != nil {
|
||||
if err := os.WriteFile(file, buf.Bytes(), 0o666); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
@@ -300,5 +302,5 @@ func newWindow(t testing.TB, width, height int) *headless.Window {
|
||||
}
|
||||
|
||||
func scale(sx, sy float32) op.TransformOp {
|
||||
return op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(sx, sy)))
|
||||
return op.Affine(f32.AffineId().Scale(f32.Point{}, f32.Pt(sx, sy)))
|
||||
}
|
||||
|
||||
+2
-2
@@ -10,10 +10,10 @@ import (
|
||||
func BenchmarkPacker(b *testing.B) {
|
||||
var p packer
|
||||
p.maxDims = image.Point{X: 4096, Y: 4096}
|
||||
for i := 0; i < b.N; i++ {
|
||||
for i := 0; b.Loop(); i++ {
|
||||
p.clear()
|
||||
p.newPage()
|
||||
for k := 0; k < 500; k++ {
|
||||
for k := range 500 {
|
||||
_, ok := p.tryAdd(xy(k))
|
||||
if !ok {
|
||||
b.Fatal("add failed", i, k, xy(k))
|
||||
|
||||
+2
-2
@@ -150,7 +150,7 @@ func newCoverer(ctx driver.Device) *coverer {
|
||||
c.texUniforms = new(coverTexUniforms)
|
||||
c.linearGradientUniforms = new(coverLinearGradientUniforms)
|
||||
pipelines, err := createColorPrograms(ctx, gio.Shader_cover_vert, gio.Shader_cover_frag,
|
||||
[3]interface{}{c.colUniforms, c.linearGradientUniforms, c.texUniforms},
|
||||
[3]any{c.colUniforms, c.linearGradientUniforms, c.texUniforms},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -162,7 +162,7 @@ func newCoverer(ctx driver.Device) *coverer {
|
||||
func newStenciler(ctx driver.Device) *stenciler {
|
||||
// Allocate a suitably large index buffer for drawing paths.
|
||||
indices := make([]uint16, pathBatchSize*6)
|
||||
for i := 0; i < pathBatchSize; i++ {
|
||||
for i := range pathBatchSize {
|
||||
i := uint16(i)
|
||||
indices[i*6+0] = i*4 + 0
|
||||
indices[i*6+1] = i*4 + 1
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
// Struct returns a byte slice view of a struct.
|
||||
func Struct(s interface{}) []byte {
|
||||
func Struct(s any) []byte {
|
||||
v := reflect.ValueOf(s)
|
||||
sz := int(v.Elem().Type().Size())
|
||||
return unsafe.Slice((*byte)(unsafe.Pointer(v.Pointer())), sz)
|
||||
@@ -27,7 +27,7 @@ func Uint32(s []uint32) []byte {
|
||||
}
|
||||
|
||||
// Slice returns a byte slice view of a slice.
|
||||
func Slice(s interface{}) []byte {
|
||||
func Slice(s any) []byte {
|
||||
v := reflect.ValueOf(s)
|
||||
first := v.Index(0)
|
||||
sz := int(first.Type().Size())
|
||||
|
||||
@@ -34,7 +34,7 @@ func Parse() {
|
||||
}
|
||||
print := false
|
||||
silent := false
|
||||
for _, part := range strings.Split(val, ",") {
|
||||
for part := range strings.SplitSeq(val, ",") {
|
||||
switch part {
|
||||
case textSubsystem:
|
||||
Text.Store(true)
|
||||
|
||||
+2
-6
@@ -9,6 +9,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"gioui.org/gpu"
|
||||
@@ -154,12 +155,7 @@ func (c *Context) EnableVSync(enable bool) {
|
||||
}
|
||||
|
||||
func hasExtension(exts []string, ext string) bool {
|
||||
for _, e := range exts {
|
||||
if ext == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.Contains(exts, ext)
|
||||
}
|
||||
|
||||
func createContext(disp _EGLDisplay) (*eglContext, error) {
|
||||
|
||||
@@ -41,14 +41,10 @@ var (
|
||||
_eglWaitClient *syscall.Proc
|
||||
)
|
||||
|
||||
var loadOnce sync.Once
|
||||
var loadOnce = sync.OnceValue(loadDLLs)
|
||||
|
||||
func loadEGL() error {
|
||||
var err error
|
||||
loadOnce.Do(func() {
|
||||
err = loadDLLs()
|
||||
})
|
||||
return err
|
||||
return loadOnce()
|
||||
}
|
||||
|
||||
func loadDLLs() error {
|
||||
@@ -186,6 +182,6 @@ func eglWaitClient() bool {
|
||||
|
||||
// issue34474KeepAlive calls runtime.KeepAlive as a
|
||||
// workaround for golang.org/issue/34474.
|
||||
func issue34474KeepAlive(v interface{}) {
|
||||
func issue34474KeepAlive(v any) {
|
||||
runtime.KeepAlive(v)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ type Affine2D = f32.Affine2D
|
||||
|
||||
var NewAffine2D = f32.NewAffine2D
|
||||
|
||||
var AffineId = f32.AffineId
|
||||
|
||||
// A Rectangle contains the points (X, Y) where Min.X <= X < Max.X,
|
||||
// Min.Y <= Y < Max.Y.
|
||||
type Rectangle struct {
|
||||
|
||||
@@ -16,7 +16,7 @@ func main() {
|
||||
flag.Parse()
|
||||
|
||||
var b bytes.Buffer
|
||||
printf := func(content string, args ...interface{}) {
|
||||
printf := func(content string, args ...any) {
|
||||
fmt.Fprintf(&b, content, args...)
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func main() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(*out, data, 0755)
|
||||
err = os.WriteFile(*out, data, 0o755)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -41,17 +41,17 @@ var sink RGBA
|
||||
|
||||
func BenchmarkLinearFromSRGB(b *testing.B) {
|
||||
b.Run("opaque", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for i := 0; b.Loop(); i++ {
|
||||
sink = LinearFromSRGB(color.NRGBA{R: byte(i), G: byte(i >> 8), B: byte(i >> 16), A: 0xFF})
|
||||
}
|
||||
})
|
||||
b.Run("translucent", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for i := 0; b.Loop(); i++ {
|
||||
sink = LinearFromSRGB(color.NRGBA{R: byte(i), G: byte(i >> 8), B: byte(i >> 16), A: 0x50})
|
||||
}
|
||||
})
|
||||
b.Run("transparent", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for i := 0; b.Loop(); i++ {
|
||||
sink = LinearFromSRGB(color.NRGBA{R: byte(i), G: byte(i >> 8), B: byte(i >> 16), A: 0x00})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -90,7 +90,7 @@ func (e *Extrapolation) Estimate() Estimate {
|
||||
first := e.get(0)
|
||||
t := first.t
|
||||
// Walk backwards collecting samples.
|
||||
for i := 0; i < len(e.samples); i++ {
|
||||
for i := range e.samples {
|
||||
p := e.get(-i)
|
||||
age := first.t - p.t
|
||||
if age >= maxAge || t-p.t >= maxSampleGap {
|
||||
@@ -172,9 +172,9 @@ func decomposeQR(A *matrix) (*matrix, *matrix, bool) {
|
||||
// https://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process
|
||||
Q := newMatrix(A.rows, A.cols) // Column-major.
|
||||
Rt := newMatrix(A.rows, A.rows) // R transposed, row-major.
|
||||
for i := 0; i < Q.rows; i++ {
|
||||
for i := range Q.rows {
|
||||
// Copy A column.
|
||||
for j := 0; j < Q.cols; j++ {
|
||||
for j := range Q.cols {
|
||||
Q.set(i, j, A.get(i, j))
|
||||
}
|
||||
// Subtract projections. Note that int the projection
|
||||
@@ -184,9 +184,9 @@ func decomposeQR(A *matrix) (*matrix, *matrix, bool) {
|
||||
// the normalized column e replaces u, where <e, e> = 1:
|
||||
//
|
||||
// proje a = <e, a>/<e, e> e = <e, a> e
|
||||
for j := 0; j < i; j++ {
|
||||
for j := range i {
|
||||
d := dot(Q.col(j), Q.col(i))
|
||||
for k := 0; k < Q.cols; k++ {
|
||||
for k := range Q.cols {
|
||||
Q.set(i, k, Q.get(i, k)-d*Q.get(j, k))
|
||||
}
|
||||
}
|
||||
@@ -197,7 +197,7 @@ func decomposeQR(A *matrix) (*matrix, *matrix, bool) {
|
||||
return nil, nil, false
|
||||
}
|
||||
invNorm := 1 / n
|
||||
for j := 0; j < Q.cols; j++ {
|
||||
for j := range Q.cols {
|
||||
Q.set(i, j, Q.get(i, j)*invNorm)
|
||||
}
|
||||
// Update Rt.
|
||||
@@ -261,8 +261,8 @@ func (m *matrix) approxEqual(m2 *matrix) bool {
|
||||
return false
|
||||
}
|
||||
const epsilon = 0.00001
|
||||
for row := 0; row < m.rows; row++ {
|
||||
for col := 0; col < m.cols; col++ {
|
||||
for row := range m.rows {
|
||||
for col := range m.cols {
|
||||
d := m2.get(row, col) - m.get(row, col)
|
||||
if d < -epsilon || d > epsilon {
|
||||
return false
|
||||
@@ -278,8 +278,8 @@ func (m *matrix) transpose() *matrix {
|
||||
cols: m.rows,
|
||||
data: make([]float32, len(m.data)),
|
||||
}
|
||||
for i := 0; i < m.rows; i++ {
|
||||
for j := 0; j < m.cols; j++ {
|
||||
for i := range m.rows {
|
||||
for j := range m.cols {
|
||||
t.set(j, i, m.get(i, j))
|
||||
}
|
||||
}
|
||||
@@ -295,10 +295,10 @@ func (m *matrix) mul(m2 *matrix) *matrix {
|
||||
cols: m2.cols,
|
||||
data: make([]float32, m.rows*m2.cols),
|
||||
}
|
||||
for i := 0; i < mm.rows; i++ {
|
||||
for j := 0; j < mm.cols; j++ {
|
||||
for i := range mm.rows {
|
||||
for j := range mm.cols {
|
||||
var v float32
|
||||
for k := 0; k < m.rows; k++ {
|
||||
for k := range m.rows {
|
||||
v += m.get(k, j) * m2.get(i, k)
|
||||
}
|
||||
mm.set(i, j, v)
|
||||
@@ -309,8 +309,8 @@ func (m *matrix) mul(m2 *matrix) *matrix {
|
||||
|
||||
func (m *matrix) String() string {
|
||||
var b strings.Builder
|
||||
for i := 0; i < m.rows; i++ {
|
||||
for j := 0; j < m.cols; j++ {
|
||||
for i := range m.rows {
|
||||
for j := range m.cols {
|
||||
v := m.get(i, j)
|
||||
b.WriteString(strconv.FormatFloat(float64(v), 'g', -1, 32))
|
||||
b.WriteString(", ")
|
||||
|
||||
@@ -247,9 +247,11 @@ func (f *Functions) getExtension(name string) js.Value {
|
||||
func (f *Functions) ActiveTexture(t Enum) {
|
||||
f._activeTexture.Invoke(int(t))
|
||||
}
|
||||
|
||||
func (f *Functions) AttachShader(p Program, s Shader) {
|
||||
f._attachShader.Invoke(js.Value(p), js.Value(s))
|
||||
}
|
||||
|
||||
func (f *Functions) BeginQuery(target Enum, query Query) {
|
||||
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
||||
f._beginQuery.Invoke(int(target), js.Value(query))
|
||||
@@ -257,36 +259,47 @@ func (f *Functions) BeginQuery(target Enum, query Query) {
|
||||
f.EXT_disjoint_timer_query.Call("beginQueryEXT", int(target), js.Value(query))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Functions) BindAttribLocation(p Program, a Attrib, name string) {
|
||||
f._bindAttribLocation.Invoke(js.Value(p), int(a), name)
|
||||
}
|
||||
|
||||
func (f *Functions) BindBuffer(target Enum, b Buffer) {
|
||||
f._bindBuffer.Invoke(int(target), js.Value(b))
|
||||
}
|
||||
|
||||
func (f *Functions) BindBufferBase(target Enum, index int, b Buffer) {
|
||||
f._bindBufferBase.Invoke(int(target), index, js.Value(b))
|
||||
}
|
||||
|
||||
func (f *Functions) BindFramebuffer(target Enum, fb Framebuffer) {
|
||||
f._bindFramebuffer.Invoke(int(target), js.Value(fb))
|
||||
}
|
||||
|
||||
func (f *Functions) BindRenderbuffer(target Enum, rb Renderbuffer) {
|
||||
f._bindRenderbuffer.Invoke(int(target), js.Value(rb))
|
||||
}
|
||||
|
||||
func (f *Functions) BindTexture(target Enum, t Texture) {
|
||||
f._bindTexture.Invoke(int(target), js.Value(t))
|
||||
}
|
||||
|
||||
func (f *Functions) BindImageTexture(unit int, t Texture, level int, layered bool, layer int, access, format Enum) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (f *Functions) BindVertexArray(a VertexArray) {
|
||||
panic("not supported")
|
||||
}
|
||||
|
||||
func (f *Functions) BlendEquation(mode Enum) {
|
||||
f._blendEquation.Invoke(int(mode))
|
||||
}
|
||||
|
||||
func (f *Functions) BlendFuncSeparate(srcRGB, dstRGB, srcA, dstA Enum) {
|
||||
f._blendFunc.Invoke(int(srcRGB), int(dstRGB), int(srcA), int(dstA))
|
||||
}
|
||||
|
||||
func (f *Functions) BufferData(target Enum, size int, usage Enum, data []byte) {
|
||||
if data == nil {
|
||||
f._bufferData.Invoke(int(target), size, int(usage))
|
||||
@@ -297,9 +310,11 @@ func (f *Functions) BufferData(target Enum, size int, usage Enum, data []byte) {
|
||||
f._bufferData.Invoke(int(target), f.byteArrayOf(data), int(usage))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
|
||||
f._bufferSubData.Invoke(int(target), offset, f.byteArrayOf(src))
|
||||
}
|
||||
|
||||
func (f *Functions) CheckFramebufferStatus(target Enum) Enum {
|
||||
status := Enum(f._checkFramebufferStatus.Invoke(int(target)).Int())
|
||||
if status != FRAMEBUFFER_COMPLETE && f.Ctx.Call("isContextLost").Bool() {
|
||||
@@ -308,54 +323,71 @@ func (f *Functions) CheckFramebufferStatus(target Enum) Enum {
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
func (f *Functions) Clear(mask Enum) {
|
||||
f._clear.Invoke(int(mask))
|
||||
}
|
||||
|
||||
func (f *Functions) ClearColor(red, green, blue, alpha float32) {
|
||||
f._clearColor.Invoke(red, green, blue, alpha)
|
||||
}
|
||||
|
||||
func (f *Functions) ClearDepthf(d float32) {
|
||||
f._clearDepth.Invoke(d)
|
||||
}
|
||||
|
||||
func (f *Functions) CompileShader(s Shader) {
|
||||
f._compileShader.Invoke(js.Value(s))
|
||||
}
|
||||
|
||||
func (f *Functions) CopyTexSubImage2D(target Enum, level, xoffset, yoffset, x, y, width, height int) {
|
||||
f._copyTexSubImage2D.Invoke(int(target), level, xoffset, yoffset, x, y, width, height)
|
||||
}
|
||||
|
||||
func (f *Functions) CreateBuffer() Buffer {
|
||||
return Buffer(f._createBuffer.Invoke())
|
||||
}
|
||||
|
||||
func (f *Functions) CreateFramebuffer() Framebuffer {
|
||||
return Framebuffer(f._createFramebuffer.Invoke())
|
||||
}
|
||||
|
||||
func (f *Functions) CreateProgram() Program {
|
||||
return Program(f._createProgram.Invoke())
|
||||
}
|
||||
|
||||
func (f *Functions) CreateQuery() Query {
|
||||
return Query(f._createQuery.Invoke())
|
||||
}
|
||||
|
||||
func (f *Functions) CreateRenderbuffer() Renderbuffer {
|
||||
return Renderbuffer(f._createRenderbuffer.Invoke())
|
||||
}
|
||||
|
||||
func (f *Functions) CreateShader(ty Enum) Shader {
|
||||
return Shader(f._createShader.Invoke(int(ty)))
|
||||
}
|
||||
|
||||
func (f *Functions) CreateTexture() Texture {
|
||||
return Texture(f._createTexture.Invoke())
|
||||
}
|
||||
|
||||
func (f *Functions) CreateVertexArray() VertexArray {
|
||||
panic("not supported")
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteBuffer(v Buffer) {
|
||||
f._deleteBuffer.Invoke(js.Value(v))
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteFramebuffer(v Framebuffer) {
|
||||
f._deleteFramebuffer.Invoke(js.Value(v))
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteProgram(p Program) {
|
||||
f._deleteProgram.Invoke(js.Value(p))
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteQuery(query Query) {
|
||||
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
||||
f._deleteQuery.Invoke(js.Value(query))
|
||||
@@ -363,45 +395,59 @@ func (f *Functions) DeleteQuery(query Query) {
|
||||
f.EXT_disjoint_timer_query.Call("deleteQueryEXT", js.Value(query))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteShader(s Shader) {
|
||||
f._deleteShader.Invoke(js.Value(s))
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteRenderbuffer(v Renderbuffer) {
|
||||
f._deleteRenderbuffer.Invoke(js.Value(v))
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteTexture(v Texture) {
|
||||
f._deleteTexture.Invoke(js.Value(v))
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteVertexArray(a VertexArray) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (f *Functions) DepthFunc(fn Enum) {
|
||||
f._depthFunc.Invoke(int(fn))
|
||||
}
|
||||
|
||||
func (f *Functions) DepthMask(mask bool) {
|
||||
f._depthMask.Invoke(mask)
|
||||
}
|
||||
|
||||
func (f *Functions) DisableVertexAttribArray(a Attrib) {
|
||||
f._disableVertexAttribArray.Invoke(int(a))
|
||||
}
|
||||
|
||||
func (f *Functions) Disable(cap Enum) {
|
||||
f._disable.Invoke(int(cap))
|
||||
}
|
||||
|
||||
func (f *Functions) DrawArrays(mode Enum, first, count int) {
|
||||
f._drawArrays.Invoke(int(mode), first, count)
|
||||
}
|
||||
|
||||
func (f *Functions) DrawElements(mode Enum, count int, ty Enum, offset int) {
|
||||
f._drawElements.Invoke(int(mode), count, int(ty), offset)
|
||||
}
|
||||
|
||||
func (f *Functions) DispatchCompute(x, y, z int) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (f *Functions) Enable(cap Enum) {
|
||||
f._enable.Invoke(int(cap))
|
||||
}
|
||||
|
||||
func (f *Functions) EnableVertexAttribArray(a Attrib) {
|
||||
f._enableVertexAttribArray.Invoke(int(a))
|
||||
}
|
||||
|
||||
func (f *Functions) EndQuery(target Enum) {
|
||||
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
||||
f._endQuery.Invoke(int(target))
|
||||
@@ -409,28 +455,36 @@ func (f *Functions) EndQuery(target Enum) {
|
||||
f.EXT_disjoint_timer_query.Call("endQueryEXT", int(target))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Functions) Finish() {
|
||||
f._finish.Invoke()
|
||||
}
|
||||
|
||||
func (f *Functions) Flush() {
|
||||
f._flush.Invoke()
|
||||
}
|
||||
|
||||
func (f *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer) {
|
||||
f._framebufferRenderbuffer.Invoke(int(target), int(attachment), int(renderbuffertarget), js.Value(renderbuffer))
|
||||
}
|
||||
|
||||
func (f *Functions) FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int) {
|
||||
f._framebufferTexture2D.Invoke(int(target), int(attachment), int(texTarget), js.Value(t), level)
|
||||
}
|
||||
|
||||
func (f *Functions) GenerateMipmap(target Enum) {
|
||||
f._generateMipmap.Invoke(int(target))
|
||||
}
|
||||
|
||||
func (f *Functions) GetError() Enum {
|
||||
// Avoid slow getError calls. See gio#179.
|
||||
return 0
|
||||
}
|
||||
|
||||
func (f *Functions) GetRenderbufferParameteri(target, pname Enum) int {
|
||||
return paramVal(f._getRenderbufferParameteri.Invoke(int(pname)))
|
||||
}
|
||||
|
||||
func (f *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int {
|
||||
if !f.isWebGL2 && pname == FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING {
|
||||
// FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING is only available on WebGL 2
|
||||
@@ -438,6 +492,7 @@ func (f *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname
|
||||
}
|
||||
return paramVal(f._getFramebufferAttachmentParameter.Invoke(int(target), int(attachment), int(pname)))
|
||||
}
|
||||
|
||||
func (f *Functions) GetBinding(pname Enum) Object {
|
||||
obj := f._getParameter.Invoke(int(pname))
|
||||
if !obj.Truthy() {
|
||||
@@ -445,6 +500,7 @@ func (f *Functions) GetBinding(pname Enum) Object {
|
||||
}
|
||||
return Object(obj)
|
||||
}
|
||||
|
||||
func (f *Functions) GetBindingi(pname Enum, idx int) Object {
|
||||
obj := f._getIndexedParameter.Invoke(int(pname), idx)
|
||||
if !obj.Truthy() {
|
||||
@@ -452,6 +508,7 @@ func (f *Functions) GetBindingi(pname Enum, idx int) Object {
|
||||
}
|
||||
return Object(obj)
|
||||
}
|
||||
|
||||
func (f *Functions) GetInteger(pname Enum) int {
|
||||
if !f.isWebGL2 {
|
||||
switch pname {
|
||||
@@ -461,9 +518,11 @@ func (f *Functions) GetInteger(pname Enum) int {
|
||||
}
|
||||
return paramVal(f._getParameter.Invoke(int(pname)))
|
||||
}
|
||||
|
||||
func (f *Functions) GetFloat(pname Enum) float32 {
|
||||
return float32(f._getParameter.Invoke(int(pname)).Float())
|
||||
}
|
||||
|
||||
func (f *Functions) GetInteger4(pname Enum) [4]int {
|
||||
arr := f._getParameter.Invoke(int(pname))
|
||||
var res [4]int
|
||||
@@ -472,6 +531,7 @@ func (f *Functions) GetInteger4(pname Enum) [4]int {
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (f *Functions) GetFloat4(pname Enum) [4]float32 {
|
||||
arr := f._getParameter.Invoke(int(pname))
|
||||
var res [4]float32
|
||||
@@ -480,12 +540,15 @@ func (f *Functions) GetFloat4(pname Enum) [4]float32 {
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (f *Functions) GetProgrami(p Program, pname Enum) int {
|
||||
return paramVal(f._getProgramParameter.Invoke(js.Value(p), int(pname)))
|
||||
}
|
||||
|
||||
func (f *Functions) GetProgramInfoLog(p Program) string {
|
||||
return f._getProgramInfoLog.Invoke(js.Value(p)).String()
|
||||
}
|
||||
|
||||
func (f *Functions) GetQueryObjectuiv(query Query, pname Enum) uint {
|
||||
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
||||
return uint(paramVal(f._getQueryParameter.Invoke(js.Value(query), int(pname))))
|
||||
@@ -493,12 +556,15 @@ func (f *Functions) GetQueryObjectuiv(query Query, pname Enum) uint {
|
||||
return uint(paramVal(f.EXT_disjoint_timer_query.Call("getQueryObjectEXT", js.Value(query), int(pname))))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Functions) GetShaderi(s Shader, pname Enum) int {
|
||||
return paramVal(f._getShaderParameter.Invoke(js.Value(s), int(pname)))
|
||||
}
|
||||
|
||||
func (f *Functions) GetShaderInfoLog(s Shader) string {
|
||||
return f._getShaderInfoLog.Invoke(js.Value(s)).String()
|
||||
}
|
||||
|
||||
func (f *Functions) GetString(pname Enum) string {
|
||||
switch pname {
|
||||
case EXTENSIONS:
|
||||
@@ -512,15 +578,19 @@ func (f *Functions) GetString(pname Enum) string {
|
||||
return f._getParameter.Invoke(int(pname)).String()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Functions) GetUniformBlockIndex(p Program, name string) uint {
|
||||
return uint(paramVal(f._getUniformBlockIndex.Invoke(js.Value(p), name)))
|
||||
}
|
||||
|
||||
func (f *Functions) GetUniformLocation(p Program, name string) Uniform {
|
||||
return Uniform(f._getUniformLocation.Invoke(js.Value(p), name))
|
||||
}
|
||||
|
||||
func (f *Functions) GetVertexAttrib(index int, pname Enum) int {
|
||||
return paramVal(f._getVertexAttrib.Invoke(index, int(pname)))
|
||||
}
|
||||
|
||||
func (f *Functions) GetVertexAttribBinding(index int, pname Enum) Object {
|
||||
obj := f._getVertexAttrib.Invoke(index, int(pname))
|
||||
if !obj.Truthy() {
|
||||
@@ -528,9 +598,11 @@ func (f *Functions) GetVertexAttribBinding(index int, pname Enum) Object {
|
||||
}
|
||||
return Object(obj)
|
||||
}
|
||||
|
||||
func (f *Functions) GetVertexAttribPointer(index int, pname Enum) uintptr {
|
||||
return uintptr(f._getVertexAttribOffset.Invoke(index, int(pname)).Int())
|
||||
}
|
||||
|
||||
func (f *Functions) InvalidateFramebuffer(target, attachment Enum) {
|
||||
fn := f.Ctx.Get("invalidateFramebuffer")
|
||||
if !fn.IsUndefined() {
|
||||
@@ -541,74 +613,97 @@ func (f *Functions) InvalidateFramebuffer(target, attachment Enum) {
|
||||
f._invalidateFramebuffer.Invoke(int(target), f.int32Buf)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Functions) IsEnabled(cap Enum) bool {
|
||||
return f._isEnabled.Invoke(int(cap)).Truthy()
|
||||
}
|
||||
|
||||
func (f *Functions) LinkProgram(p Program) {
|
||||
f._linkProgram.Invoke(js.Value(p))
|
||||
}
|
||||
|
||||
func (f *Functions) PixelStorei(pname Enum, param int) {
|
||||
f._pixelStorei.Invoke(int(pname), param)
|
||||
}
|
||||
|
||||
func (f *Functions) MemoryBarrier(barriers Enum) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (f *Functions) MapBufferRange(target Enum, offset, length int, access Enum) []byte {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (f *Functions) RenderbufferStorage(target, internalformat Enum, width, height int) {
|
||||
f._renderbufferStorage.Invoke(int(target), int(internalformat), width, height)
|
||||
}
|
||||
|
||||
func (f *Functions) ReadPixels(x, y, width, height int, format, ty Enum, data []byte) {
|
||||
ba := f.byteArrayOf(data)
|
||||
f._readPixels.Invoke(x, y, width, height, int(format), int(ty), ba)
|
||||
js.CopyBytesToGo(data, ba)
|
||||
}
|
||||
|
||||
func (f *Functions) Scissor(x, y, width, height int32) {
|
||||
f._scissor.Invoke(x, y, width, height)
|
||||
}
|
||||
|
||||
func (f *Functions) ShaderSource(s Shader, src string) {
|
||||
f._shaderSource.Invoke(js.Value(s), src)
|
||||
}
|
||||
|
||||
func (f *Functions) TexImage2D(target Enum, level int, internalFormat Enum, width, height int, format, ty Enum) {
|
||||
f._texImage2D.Invoke(int(target), int(level), int(internalFormat), int(width), int(height), 0, int(format), int(ty), nil)
|
||||
}
|
||||
|
||||
func (f *Functions) TexStorage2D(target Enum, levels int, internalFormat Enum, width, height int) {
|
||||
f._texStorage2D.Invoke(int(target), levels, int(internalFormat), width, height)
|
||||
}
|
||||
|
||||
func (f *Functions) TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte) {
|
||||
f._texSubImage2D.Invoke(int(target), level, x, y, width, height, int(format), int(ty), f.byteArrayOf(data))
|
||||
}
|
||||
|
||||
func (f *Functions) TexParameteri(target, pname Enum, param int) {
|
||||
f._texParameteri.Invoke(int(target), int(pname), int(param))
|
||||
}
|
||||
|
||||
func (f *Functions) UniformBlockBinding(p Program, uniformBlockIndex uint, uniformBlockBinding uint) {
|
||||
f._uniformBlockBinding.Invoke(js.Value(p), int(uniformBlockIndex), int(uniformBlockBinding))
|
||||
}
|
||||
|
||||
func (f *Functions) Uniform1f(dst Uniform, v float32) {
|
||||
f._uniform1f.Invoke(js.Value(dst), v)
|
||||
}
|
||||
|
||||
func (f *Functions) Uniform1i(dst Uniform, v int) {
|
||||
f._uniform1i.Invoke(js.Value(dst), v)
|
||||
}
|
||||
|
||||
func (f *Functions) Uniform2f(dst Uniform, v0, v1 float32) {
|
||||
f._uniform2f.Invoke(js.Value(dst), v0, v1)
|
||||
}
|
||||
|
||||
func (f *Functions) Uniform3f(dst Uniform, v0, v1, v2 float32) {
|
||||
f._uniform3f.Invoke(js.Value(dst), v0, v1, v2)
|
||||
}
|
||||
|
||||
func (f *Functions) Uniform4f(dst Uniform, v0, v1, v2, v3 float32) {
|
||||
f._uniform4f.Invoke(js.Value(dst), v0, v1, v2, v3)
|
||||
}
|
||||
|
||||
func (f *Functions) UseProgram(p Program) {
|
||||
f._useProgram.Invoke(js.Value(p))
|
||||
}
|
||||
|
||||
func (f *Functions) UnmapBuffer(target Enum) bool {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (f *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int) {
|
||||
f._vertexAttribPointer.Invoke(int(dst), size, int(ty), normalized, stride, offset)
|
||||
}
|
||||
|
||||
func (f *Functions) Viewport(x, y, width, height int) {
|
||||
f._viewport.Invoke(x, y, width, height)
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ type Functions struct {
|
||||
uintptrs [100]uintptr
|
||||
}
|
||||
|
||||
type Context interface{}
|
||||
type Context any
|
||||
|
||||
func NewFunctions(ctx Context, forceES bool) (*Functions, error) {
|
||||
if ctx != nil {
|
||||
@@ -239,45 +239,58 @@ func NewFunctions(ctx Context, forceES bool) (*Functions, error) {
|
||||
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 {
|
||||
@@ -285,6 +298,7 @@ func (c *Functions) BufferData(target Enum, size int, usage Enum, data []byte) {
|
||||
}
|
||||
syscall.Syscall6(_glBufferData.Addr(), 4, uintptr(target), uintptr(size), uintptr(p), uintptr(usage), 0, 0)
|
||||
}
|
||||
|
||||
func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
|
||||
if n := len(src); n > 0 {
|
||||
s0 := &src[0]
|
||||
@@ -292,93 +306,118 @@ func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
|
||||
issue34474KeepAlive(s0)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Functions) CheckFramebufferStatus(target Enum) Enum {
|
||||
s, _, _ := syscall.Syscall(_glCheckFramebufferStatus.Addr(), 1, uintptr(target), 0, 0)
|
||||
return Enum(s)
|
||||
}
|
||||
|
||||
func (c *Functions) Clear(mask Enum) {
|
||||
syscall.Syscall(_glClear.Addr(), 1, uintptr(mask), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) ClearColor(red, green, blue, alpha float32) {
|
||||
syscall.Syscall6(_glClearColor.Addr(), 4, uintptr(math.Float32bits(red)), uintptr(math.Float32bits(green)), uintptr(math.Float32bits(blue)), uintptr(math.Float32bits(alpha)), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) ClearDepthf(d float32) {
|
||||
syscall.Syscall(_glClearDepthf.Addr(), 1, uintptr(math.Float32bits(d)), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) CompileShader(s Shader) {
|
||||
syscall.Syscall(_glCompileShader.Addr(), 1, uintptr(s.V), 0, 0)
|
||||
}
|
||||
|
||||
func (f *Functions) CopyTexSubImage2D(target Enum, level, xoffset, yoffset, x, y, width, height int) {
|
||||
syscall.Syscall9(_glCopyTexSubImage2D.Addr(), 8, uintptr(target), uintptr(level), uintptr(xoffset), uintptr(yoffset), uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0)
|
||||
}
|
||||
|
||||
func (f *Functions) GenerateMipmap(target Enum) {
|
||||
syscall.Syscall(_glGenerateMipmap.Addr(), 1, uintptr(target), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) CreateBuffer() Buffer {
|
||||
var buf uintptr
|
||||
syscall.Syscall(_glGenBuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&buf)), 0)
|
||||
return Buffer{uint(buf)}
|
||||
}
|
||||
|
||||
func (c *Functions) CreateFramebuffer() Framebuffer {
|
||||
var fb uintptr
|
||||
syscall.Syscall(_glGenFramebuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&fb)), 0)
|
||||
return Framebuffer{uint(fb)}
|
||||
}
|
||||
|
||||
func (c *Functions) CreateProgram() Program {
|
||||
p, _, _ := syscall.Syscall(_glCreateProgram.Addr(), 0, 0, 0, 0)
|
||||
return Program{uint(p)}
|
||||
}
|
||||
|
||||
func (f *Functions) CreateQuery() Query {
|
||||
var q uintptr
|
||||
syscall.Syscall(_glGenQueries.Addr(), 2, 1, uintptr(unsafe.Pointer(&q)), 0)
|
||||
return Query{uint(q)}
|
||||
}
|
||||
|
||||
func (c *Functions) CreateRenderbuffer() Renderbuffer {
|
||||
var rb uintptr
|
||||
syscall.Syscall(_glGenRenderbuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&rb)), 0)
|
||||
return Renderbuffer{uint(rb)}
|
||||
}
|
||||
|
||||
func (c *Functions) CreateShader(ty Enum) Shader {
|
||||
s, _, _ := syscall.Syscall(_glCreateShader.Addr(), 1, uintptr(ty), 0, 0)
|
||||
return Shader{uint(s)}
|
||||
}
|
||||
|
||||
func (c *Functions) CreateTexture() Texture {
|
||||
var t uintptr
|
||||
syscall.Syscall(_glGenTextures.Addr(), 2, 1, uintptr(unsafe.Pointer(&t)), 0)
|
||||
return Texture{uint(t)}
|
||||
}
|
||||
|
||||
func (c *Functions) CreateVertexArray() VertexArray {
|
||||
var t uintptr
|
||||
syscall.Syscall(_glGenVertexArrays.Addr(), 2, 1, uintptr(unsafe.Pointer(&t)), 0)
|
||||
return VertexArray{uint(t)}
|
||||
}
|
||||
|
||||
func (c *Functions) DeleteBuffer(v Buffer) {
|
||||
syscall.Syscall(_glDeleteBuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v)), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) DeleteFramebuffer(v Framebuffer) {
|
||||
syscall.Syscall(_glDeleteFramebuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) DeleteProgram(p Program) {
|
||||
syscall.Syscall(_glDeleteProgram.Addr(), 1, uintptr(p.V), 0, 0)
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteQuery(query Query) {
|
||||
syscall.Syscall(_glDeleteQueries.Addr(), 2, 1, uintptr(unsafe.Pointer(&query.V)), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) DeleteShader(s Shader) {
|
||||
syscall.Syscall(_glDeleteShader.Addr(), 1, uintptr(s.V), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) DeleteRenderbuffer(v Renderbuffer) {
|
||||
syscall.Syscall(_glDeleteRenderbuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) DeleteTexture(v Texture) {
|
||||
syscall.Syscall(_glDeleteTextures.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteVertexArray(array VertexArray) {
|
||||
syscall.Syscall(_glDeleteVertexArrays.Addr(), 2, 1, uintptr(unsafe.Pointer(&array.V)), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) DepthFunc(f Enum) {
|
||||
syscall.Syscall(_glDepthFunc.Addr(), 1, uintptr(f), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) DepthMask(mask bool) {
|
||||
var m uintptr
|
||||
if mask {
|
||||
@@ -386,42 +425,55 @@ func (c *Functions) DepthMask(mask bool) {
|
||||
}
|
||||
syscall.Syscall(_glDepthMask.Addr(), 1, m, 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) DisableVertexAttribArray(a Attrib) {
|
||||
syscall.Syscall(_glDisableVertexAttribArray.Addr(), 1, uintptr(a), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) Disable(cap Enum) {
|
||||
syscall.Syscall(_glDisable.Addr(), 1, uintptr(cap), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) DrawArrays(mode Enum, first, count int) {
|
||||
syscall.Syscall(_glDrawArrays.Addr(), 3, uintptr(mode), uintptr(first), uintptr(count))
|
||||
}
|
||||
|
||||
func (c *Functions) DrawElements(mode Enum, count int, ty Enum, offset int) {
|
||||
syscall.Syscall6(_glDrawElements.Addr(), 4, uintptr(mode), uintptr(count), uintptr(ty), uintptr(offset), 0, 0)
|
||||
}
|
||||
|
||||
func (f *Functions) DispatchCompute(x, y, z int) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (c *Functions) Enable(cap Enum) {
|
||||
syscall.Syscall(_glEnable.Addr(), 1, uintptr(cap), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) EnableVertexAttribArray(a Attrib) {
|
||||
syscall.Syscall(_glEnableVertexAttribArray.Addr(), 1, uintptr(a), 0, 0)
|
||||
}
|
||||
|
||||
func (f *Functions) EndQuery(target Enum) {
|
||||
syscall.Syscall(_glEndQuery.Addr(), 1, uintptr(target), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) Finish() {
|
||||
syscall.Syscall(_glFinish.Addr(), 0, 0, 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) Flush() {
|
||||
syscall.Syscall(_glFlush.Addr(), 0, 0, 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer) {
|
||||
syscall.Syscall6(_glFramebufferRenderbuffer.Addr(), 4, uintptr(target), uintptr(attachment), uintptr(renderbuffertarget), uintptr(renderbuffer.V), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int) {
|
||||
syscall.Syscall6(_glFramebufferTexture2D.Addr(), 5, uintptr(target), uintptr(attachment), uintptr(texTarget), uintptr(t.V), uintptr(level), 0)
|
||||
}
|
||||
|
||||
func (f *Functions) GetUniformBlockIndex(p Program, name string) uint {
|
||||
cname := cString(name)
|
||||
c0 := &cname[0]
|
||||
@@ -429,24 +481,30 @@ func (f *Functions) GetUniformBlockIndex(p Program, name string) uint {
|
||||
issue34474KeepAlive(c0)
|
||||
return uint(u)
|
||||
}
|
||||
|
||||
func (c *Functions) GetBinding(pname Enum) Object {
|
||||
return Object{uint(c.GetInteger(pname))}
|
||||
}
|
||||
|
||||
func (c *Functions) GetBindingi(pname Enum, idx int) Object {
|
||||
return Object{uint(c.GetIntegeri(pname, idx))}
|
||||
}
|
||||
|
||||
func (c *Functions) GetError() Enum {
|
||||
e, _, _ := syscall.Syscall(_glGetError.Addr(), 0, 0, 0, 0)
|
||||
return Enum(e)
|
||||
}
|
||||
|
||||
func (c *Functions) GetRenderbufferParameteri(target, pname Enum) int {
|
||||
syscall.Syscall(_glGetRenderbufferParameteriv.Addr(), 3, uintptr(target), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
||||
return int(c.int32s[0])
|
||||
}
|
||||
|
||||
func (c *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int {
|
||||
syscall.Syscall6(_glGetFramebufferAttachmentParameteriv.Addr(), 4, uintptr(target), uintptr(attachment), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0, 0)
|
||||
return int(c.int32s[0])
|
||||
}
|
||||
|
||||
func (c *Functions) GetInteger4(pname Enum) [4]int {
|
||||
syscall.Syscall(_glGetIntegerv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0)
|
||||
var r [4]int
|
||||
@@ -455,28 +513,34 @@ 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 {
|
||||
@@ -486,24 +550,29 @@ 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]
|
||||
@@ -511,6 +580,7 @@ func (c *Functions) GetUniformLocation(p Program, name string) Uniform {
|
||||
issue34474KeepAlive(c0)
|
||||
return Uniform{int(u)}
|
||||
}
|
||||
|
||||
func (c *Functions) GetVertexAttrib(index int, pname Enum) int {
|
||||
syscall.Syscall(_glGetVertexAttribiv.Addr(), 3, uintptr(index), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
||||
return int(c.int32s[0])
|
||||
@@ -524,6 +594,7 @@ func (c *Functions) GetVertexAttribPointer(index int, pname Enum) uintptr {
|
||||
syscall.Syscall(_glGetVertexAttribPointerv.Addr(), 3, uintptr(index), uintptr(pname), uintptr(unsafe.Pointer(&c.uintptrs[0])))
|
||||
return c.uintptrs[0]
|
||||
}
|
||||
|
||||
func (c *Functions) InvalidateFramebuffer(target, attachment Enum) {
|
||||
addr := _glInvalidateFramebuffer.Addr()
|
||||
if addr == 0 {
|
||||
@@ -532,77 +603,99 @@ func (c *Functions) InvalidateFramebuffer(target, attachment Enum) {
|
||||
}
|
||||
syscall.Syscall(addr, 3, uintptr(target), 1, uintptr(unsafe.Pointer(&attachment)))
|
||||
}
|
||||
|
||||
func (f *Functions) IsEnabled(cap Enum) bool {
|
||||
u, _, _ := syscall.Syscall(_glIsEnabled.Addr(), 1, uintptr(cap), 0, 0)
|
||||
return u == TRUE
|
||||
}
|
||||
|
||||
func (c *Functions) LinkProgram(p Program) {
|
||||
syscall.Syscall(_glLinkProgram.Addr(), 1, uintptr(p.V), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) PixelStorei(pname Enum, param int) {
|
||||
syscall.Syscall(_glPixelStorei.Addr(), 2, uintptr(pname), uintptr(param), 0)
|
||||
}
|
||||
|
||||
func (f *Functions) MemoryBarrier(barriers Enum) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (f *Functions) MapBufferRange(target Enum, offset, length int, access Enum) []byte {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (f *Functions) ReadPixels(x, y, width, height int, format, ty Enum, data []byte) {
|
||||
d0 := &data[0]
|
||||
syscall.Syscall9(_glReadPixels.Addr(), 7, uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(format), uintptr(ty), uintptr(unsafe.Pointer(d0)), 0, 0)
|
||||
issue34474KeepAlive(d0)
|
||||
}
|
||||
|
||||
func (c *Functions) RenderbufferStorage(target, internalformat Enum, width, height int) {
|
||||
syscall.Syscall6(_glRenderbufferStorage.Addr(), 4, uintptr(target), uintptr(internalformat), uintptr(width), uintptr(height), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) Scissor(x, y, width, height int32) {
|
||||
syscall.Syscall6(_glScissor.Addr(), 4, uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) ShaderSource(s Shader, src string) {
|
||||
var n uintptr = uintptr(len(src))
|
||||
psrc := &src
|
||||
syscall.Syscall6(_glShaderSource.Addr(), 4, uintptr(s.V), 1, uintptr(unsafe.Pointer(psrc)), uintptr(unsafe.Pointer(&n)), 0, 0)
|
||||
issue34474KeepAlive(psrc)
|
||||
}
|
||||
|
||||
func (f *Functions) TexImage2D(target Enum, level int, internalFormat Enum, width int, height int, format Enum, ty Enum) {
|
||||
syscall.Syscall9(_glTexImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(internalFormat), uintptr(width), uintptr(height), 0, uintptr(format), uintptr(ty), 0)
|
||||
}
|
||||
|
||||
func (f *Functions) TexStorage2D(target Enum, levels int, internalFormat Enum, width, height int) {
|
||||
syscall.Syscall6(_glTexStorage2D.Addr(), 5, uintptr(target), uintptr(levels), uintptr(internalFormat), uintptr(width), uintptr(height), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte) {
|
||||
d0 := &data[0]
|
||||
syscall.Syscall9(_glTexSubImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(format), uintptr(ty), uintptr(unsafe.Pointer(d0)))
|
||||
issue34474KeepAlive(d0)
|
||||
}
|
||||
|
||||
func (c *Functions) TexParameteri(target, pname Enum, param int) {
|
||||
syscall.Syscall(_glTexParameteri.Addr(), 3, uintptr(target), uintptr(pname), uintptr(param))
|
||||
}
|
||||
|
||||
func (f *Functions) UniformBlockBinding(p Program, uniformBlockIndex uint, uniformBlockBinding uint) {
|
||||
syscall.Syscall(_glUniformBlockBinding.Addr(), 3, uintptr(p.V), uintptr(uniformBlockIndex), uintptr(uniformBlockBinding))
|
||||
}
|
||||
|
||||
func (c *Functions) Uniform1f(dst Uniform, v float32) {
|
||||
syscall.Syscall(_glUniform1f.Addr(), 2, uintptr(dst.V), uintptr(math.Float32bits(v)), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) Uniform1i(dst Uniform, v int) {
|
||||
syscall.Syscall(_glUniform1i.Addr(), 2, uintptr(dst.V), uintptr(v), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) Uniform2f(dst Uniform, v0, v1 float32) {
|
||||
syscall.Syscall(_glUniform2f.Addr(), 3, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)))
|
||||
}
|
||||
|
||||
func (c *Functions) Uniform3f(dst Uniform, v0, v1, v2 float32) {
|
||||
syscall.Syscall6(_glUniform3f.Addr(), 4, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)), uintptr(math.Float32bits(v2)), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) Uniform4f(dst Uniform, v0, v1, v2, v3 float32) {
|
||||
syscall.Syscall6(_glUniform4f.Addr(), 5, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)), uintptr(math.Float32bits(v2)), uintptr(math.Float32bits(v3)), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) UseProgram(p Program) {
|
||||
syscall.Syscall(_glUseProgram.Addr(), 1, uintptr(p.V), 0, 0)
|
||||
}
|
||||
|
||||
func (f *Functions) UnmapBuffer(target Enum) bool {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (c *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int) {
|
||||
var norm uintptr
|
||||
if normalized {
|
||||
@@ -610,6 +703,7 @@ func (c *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalize
|
||||
}
|
||||
syscall.Syscall6(_glVertexAttribPointer.Addr(), 6, uintptr(dst), uintptr(size), uintptr(ty), norm, uintptr(stride), uintptr(offset))
|
||||
}
|
||||
|
||||
func (c *Functions) Viewport(x, y, width, height int) {
|
||||
syscall.Syscall6(_glViewport.Addr(), 4, uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0, 0)
|
||||
}
|
||||
@@ -622,6 +716,6 @@ func cString(s string) []byte {
|
||||
|
||||
// issue34474KeepAlive calls runtime.KeepAlive as a
|
||||
// workaround for golang.org/issue/34474.
|
||||
func issue34474KeepAlive(v interface{}) {
|
||||
func issue34474KeepAlive(v any) {
|
||||
runtime.KeepAlive(v)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build !js
|
||||
// +build !js
|
||||
|
||||
package gl
|
||||
|
||||
|
||||
+5
-5
@@ -18,7 +18,7 @@ type Ops struct {
|
||||
// data contains the serialized operations.
|
||||
data []byte
|
||||
// refs hold external references for operations.
|
||||
refs []interface{}
|
||||
refs []any
|
||||
// stringRefs provides space for string references, pointers to which will
|
||||
// be stored in refs. Storing a string directly in refs would cause a heap
|
||||
// allocation, to store the string header in an interface value. The backing
|
||||
@@ -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 interface{}) []byte {
|
||||
func Write1(o *Ops, n int, ref1 any) []byte {
|
||||
o.data = append(o.data, make([]byte, n)...)
|
||||
o.refs = append(o.refs, ref1)
|
||||
return o.data[len(o.data)-n:]
|
||||
@@ -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 interface{}) []byte {
|
||||
func Write2(o *Ops, n int, ref1, ref2 any) []byte {
|
||||
o.data = append(o.data, make([]byte, n)...)
|
||||
o.refs = append(o.refs, ref1, ref2)
|
||||
return o.data[len(o.data)-n:]
|
||||
}
|
||||
|
||||
func Write2String(o *Ops, n int, ref1 interface{}, ref2 string) []byte {
|
||||
func Write2String(o *Ops, n int, ref1 any, ref2 string) []byte {
|
||||
o.data = append(o.data, make([]byte, n)...)
|
||||
o.stringRefs = append(o.stringRefs, ref2)
|
||||
o.refs = append(o.refs, ref1, &o.stringRefs[len(o.stringRefs)-1])
|
||||
return o.data[len(o.data)-n:]
|
||||
}
|
||||
|
||||
func Write3(o *Ops, n int, ref1, ref2, ref3 interface{}) []byte {
|
||||
func Write3(o *Ops, n int, ref1, ref2, ref3 any) []byte {
|
||||
o.data = append(o.data, make([]byte, n)...)
|
||||
o.refs = append(o.refs, ref1, ref2, ref3)
|
||||
return o.data[len(o.data)-n:]
|
||||
|
||||
@@ -20,7 +20,7 @@ type Reader struct {
|
||||
type EncodedOp struct {
|
||||
Key Key
|
||||
Data []byte
|
||||
Refs []interface{}
|
||||
Refs []any
|
||||
}
|
||||
|
||||
// Key is a unique key for a given op.
|
||||
@@ -175,7 +175,7 @@ func (op *opMacroDef) decode(data []byte) {
|
||||
op.endpc.refs = bo.Uint32(data[5:])
|
||||
}
|
||||
|
||||
func (m *macroOp) decode(data []byte, refs []interface{}) {
|
||||
func (m *macroOp) decode(data []byte, refs []any) {
|
||||
if len(data) < TypeCallLen || len(refs) < 1 || OpType(data[0]) != TypeCall {
|
||||
panic("invalid op")
|
||||
}
|
||||
|
||||
+21
-12
@@ -87,7 +87,7 @@ func (qs *StrokeQuads) lineTo(pt f32.Point) {
|
||||
func (qs *StrokeQuads) arc(f1, f2 f32.Point, angle float32) {
|
||||
pen := qs.pen()
|
||||
m, segments := ArcTransform(pen, f1.Add(pen), f2.Add(pen), angle)
|
||||
for i := 0; i < segments; i++ {
|
||||
for range segments {
|
||||
p0 := qs.pen()
|
||||
p1 := m.Transform(p0)
|
||||
p2 := m.Transform(p1)
|
||||
@@ -327,13 +327,26 @@ func strokePathNorm(p0, p1, p2 f32.Point, t, d float32) f32.Point {
|
||||
func rot90CW(p f32.Point) f32.Point { return f32.Pt(+p.Y, -p.X) }
|
||||
|
||||
func normPt(p f32.Point, l float32) f32.Point {
|
||||
if (p.X == 0 && p.Y == l) || (p.Y == 0 && p.X == l) {
|
||||
return f32.Point{X: p.X, Y: p.Y}
|
||||
if (p.X == 0 && p.Y == 0) || l == 0 {
|
||||
return f32.Point{}
|
||||
}
|
||||
isVerticalUnit := p.X == 0 && (p.Y == l || p.Y == -l)
|
||||
isHorizontalUnit := p.Y == 0 && (p.X == l || p.X == -l)
|
||||
if isVerticalUnit || isHorizontalUnit {
|
||||
if math.Signbit(float64(l)) {
|
||||
return f32.Point{X: -p.X, Y: -p.Y}
|
||||
} else {
|
||||
return f32.Point{X: p.X, Y: p.Y}
|
||||
}
|
||||
}
|
||||
d := math.Hypot(float64(p.X), float64(p.Y))
|
||||
l64 := float64(l)
|
||||
if math.Abs(d-l64) < 1e-10 {
|
||||
return f32.Point{}
|
||||
if math.Signbit(float64(l)) {
|
||||
return f32.Point{X: -p.X, Y: -p.Y}
|
||||
} else {
|
||||
return f32.Point{X: p.X, Y: p.Y}
|
||||
}
|
||||
}
|
||||
n := float32(l64 / d)
|
||||
return f32.Point{X: p.X * n, Y: p.Y * n}
|
||||
@@ -440,7 +453,6 @@ func flattenQuadBezier(qs StrokeQuads, p0, p1, p2 f32.Point, d, flatness float32
|
||||
}
|
||||
|
||||
func (qs *StrokeQuads) addLine(p0, ctrl, p1 f32.Point, t, d float32) {
|
||||
|
||||
switch i := len(*qs); i {
|
||||
case 0:
|
||||
p0 = p0.Add(strokePathNorm(p0, ctrl, p1, 0, d))
|
||||
@@ -473,7 +485,6 @@ func quadInterp(p, q f32.Point, t float32) f32.Point {
|
||||
// quadBezierSplit returns the pair of triplets (from,ctrl,to) Bézier curve,
|
||||
// split before (resp. after) the provided parametric t value.
|
||||
func quadBezierSplit(p0, p1, p2 f32.Point, t float32) (f32.Point, f32.Point, f32.Point, f32.Point, f32.Point, f32.Point) {
|
||||
|
||||
var (
|
||||
b0 = p0
|
||||
b1 = quadInterp(p0, p1, t)
|
||||
@@ -577,12 +588,10 @@ func ArcTransform(p, f1, f2 f32.Point, angle float32) (transform f32.Affine2D, s
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
θ = angle / float32(segments)
|
||||
ref f32.Affine2D // transform from absolute frame to ellipse-based one
|
||||
rot f32.Affine2D // rotation matrix for each segment
|
||||
inv f32.Affine2D // transform from ellipse-based frame to absolute one
|
||||
)
|
||||
θ := angle / float32(segments)
|
||||
ref := f32.AffineId() // transform from absolute frame to ellipse-based one
|
||||
rot := f32.AffineId() // rotation matrix for each segment
|
||||
inv := f32.AffineId() // transform from ellipse-based frame to absolute one
|
||||
center := f32.Point{
|
||||
X: 0.5 * (f1.X + f2.X),
|
||||
Y: 0.5 * (f1.Y + f2.Y),
|
||||
|
||||
@@ -9,6 +9,111 @@ import (
|
||||
"gioui.org/internal/f32"
|
||||
)
|
||||
|
||||
func TestNormPt(t *testing.T) {
|
||||
type scenario struct {
|
||||
l float32
|
||||
ptIn, ptOut f32.Point
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
// l!=0 && X=Y=0
|
||||
{l: 10, ptIn: f32.Point{X: 0, Y: 0}, ptOut: f32.Point{X: 0, Y: 0}},
|
||||
{l: -10, ptIn: f32.Point{X: 0, Y: 0}, ptOut: f32.Point{X: 0, Y: 0}},
|
||||
|
||||
// l>0 & X
|
||||
{l: +20, ptIn: f32.Point{X: +30, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
|
||||
{l: +20, ptIn: f32.Point{X: +20, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
|
||||
{l: +20, ptIn: f32.Point{X: +10, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
|
||||
{l: +20, ptIn: f32.Point{X: -10, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
|
||||
{l: +20, ptIn: f32.Point{X: -20, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
|
||||
{l: +20, ptIn: f32.Point{X: -30, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
|
||||
|
||||
// l<0 & X
|
||||
{l: -20, ptIn: f32.Point{X: +30, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
|
||||
{l: -20, ptIn: f32.Point{X: +20, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
|
||||
{l: -20, ptIn: f32.Point{X: +10, Y: 0}, ptOut: f32.Point{X: -20, Y: 0}},
|
||||
{l: -20, ptIn: f32.Point{X: -10, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
|
||||
{l: -20, ptIn: f32.Point{X: -20, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
|
||||
{l: -20, ptIn: f32.Point{X: -30, Y: 0}, ptOut: f32.Point{X: +20, Y: 0}},
|
||||
|
||||
// l>0 & Y
|
||||
{l: +20, ptIn: f32.Point{X: 0, Y: +30}, ptOut: f32.Point{X: 0, Y: +20}},
|
||||
{l: +20, ptIn: f32.Point{X: 0, Y: +20}, ptOut: f32.Point{X: 0, Y: +20}},
|
||||
{l: +20, ptIn: f32.Point{X: 0, Y: +10}, ptOut: f32.Point{X: 0, Y: +20}},
|
||||
{l: +20, ptIn: f32.Point{X: 0, Y: -10}, ptOut: f32.Point{X: 0, Y: -20}},
|
||||
{l: +20, ptIn: f32.Point{X: 0, Y: -20}, ptOut: f32.Point{X: 0, Y: -20}},
|
||||
{l: +20, ptIn: f32.Point{X: 0, Y: -30}, ptOut: f32.Point{X: 0, Y: -20}},
|
||||
|
||||
// l<0 & Y
|
||||
{l: -20, ptIn: f32.Point{X: 0, Y: +30}, ptOut: f32.Point{X: 0, Y: -20}},
|
||||
{l: -20, ptIn: f32.Point{X: 0, Y: +20}, ptOut: f32.Point{X: 0, Y: -20}},
|
||||
{l: -20, ptIn: f32.Point{X: 0, Y: +10}, ptOut: f32.Point{X: 0, Y: -20}},
|
||||
{l: -20, ptIn: f32.Point{X: 0, Y: -10}, ptOut: f32.Point{X: 0, Y: +20}},
|
||||
{l: -20, ptIn: f32.Point{X: 0, Y: -20}, ptOut: f32.Point{X: 0, Y: +20}},
|
||||
{l: -20, ptIn: f32.Point{X: 0, Y: -30}, ptOut: f32.Point{X: 0, Y: +20}},
|
||||
|
||||
// l>0 && X=Y
|
||||
{l: +20, ptIn: f32.Point{X: +90, Y: +90}, ptOut: f32.Point{X: +14.142137, Y: +14.142137}},
|
||||
{l: +20, ptIn: f32.Point{X: +30, Y: +30}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
|
||||
{l: +20, ptIn: f32.Point{X: +20, Y: +20}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
|
||||
{l: +20, ptIn: f32.Point{X: +10, Y: +10}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
|
||||
{l: +20, ptIn: f32.Point{X: -10, Y: -10}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
|
||||
{l: +20, ptIn: f32.Point{X: -20, Y: -20}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
|
||||
{l: +20, ptIn: f32.Point{X: -30, Y: -30}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
|
||||
{l: +20, ptIn: f32.Point{X: -90, Y: -90}, ptOut: f32.Point{X: -14.142137, Y: -14.142137}},
|
||||
|
||||
// l>0 && X=-Y
|
||||
{l: +20, ptIn: f32.Point{X: +90, Y: -90}, ptOut: f32.Point{X: +14.142137, Y: -14.142137}},
|
||||
{l: +20, ptIn: f32.Point{X: +30, Y: -30}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
|
||||
{l: +20, ptIn: f32.Point{X: +20, Y: -20}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
|
||||
{l: +20, ptIn: f32.Point{X: +10, Y: -10}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
|
||||
{l: +20, ptIn: f32.Point{X: -10, Y: +10}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
|
||||
{l: +20, ptIn: f32.Point{X: -20, Y: +20}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
|
||||
{l: +20, ptIn: f32.Point{X: -30, Y: +30}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
|
||||
{l: +20, ptIn: f32.Point{X: -90, Y: +90}, ptOut: f32.Point{X: -14.142137, Y: +14.142137}},
|
||||
|
||||
// l<0 && X=Y
|
||||
{l: -20, ptIn: f32.Point{X: +90, Y: +90}, ptOut: f32.Point{X: -14.142137, Y: -14.142137}},
|
||||
{l: -20, ptIn: f32.Point{X: +30, Y: +30}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
|
||||
{l: -20, ptIn: f32.Point{X: +20, Y: +20}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
|
||||
{l: -20, ptIn: f32.Point{X: +10, Y: +10}, ptOut: f32.Point{X: -14.142136, Y: -14.142136}},
|
||||
{l: -20, ptIn: f32.Point{X: -10, Y: -10}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
|
||||
{l: -20, ptIn: f32.Point{X: -20, Y: -20}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
|
||||
{l: -20, ptIn: f32.Point{X: -30, Y: -30}, ptOut: f32.Point{X: +14.142136, Y: +14.142136}},
|
||||
{l: -20, ptIn: f32.Point{X: -90, Y: -90}, ptOut: f32.Point{X: +14.142137, Y: +14.142137}},
|
||||
|
||||
// l<0 && X=-Y
|
||||
{l: -20, ptIn: f32.Point{X: +90, Y: -90}, ptOut: f32.Point{X: -14.142137, Y: +14.142137}},
|
||||
{l: -20, ptIn: f32.Point{X: +30, Y: -30}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
|
||||
{l: -20, ptIn: f32.Point{X: +20, Y: -20}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
|
||||
{l: -20, ptIn: f32.Point{X: +10, Y: -10}, ptOut: f32.Point{X: -14.142136, Y: +14.142136}},
|
||||
{l: -20, ptIn: f32.Point{X: -10, Y: +10}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
|
||||
{l: -20, ptIn: f32.Point{X: -20, Y: +20}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
|
||||
{l: -20, ptIn: f32.Point{X: -30, Y: +30}, ptOut: f32.Point{X: +14.142136, Y: -14.142136}},
|
||||
{l: -20, ptIn: f32.Point{X: -90, Y: +90}, ptOut: f32.Point{X: +14.142137, Y: -14.142137}},
|
||||
|
||||
// l!=0 && Hypot=l
|
||||
{l: 5, ptIn: f32.Point{X: 3, Y: 4}, ptOut: f32.Point{X: 3, Y: 4}},
|
||||
{l: 5, ptIn: f32.Point{X: 3, Y: -4}, ptOut: f32.Point{X: 3, Y: -4}},
|
||||
{l: 5, ptIn: f32.Point{X: -3, Y: -4}, ptOut: f32.Point{X: -3, Y: -4}},
|
||||
{l: 5, ptIn: f32.Point{X: -3, Y: 4}, ptOut: f32.Point{X: -3, Y: 4}},
|
||||
{l: -5, ptIn: f32.Point{X: 3, Y: 4}, ptOut: f32.Point{X: -3, Y: -4}},
|
||||
{l: -5, ptIn: f32.Point{X: 3, Y: -4}, ptOut: f32.Point{X: -3, Y: 4}},
|
||||
{l: -5, ptIn: f32.Point{X: -3, Y: -4}, ptOut: f32.Point{X: 3, Y: 4}},
|
||||
{l: -5, ptIn: f32.Point{X: -3, Y: 4}, ptOut: f32.Point{X: 3, Y: -4}},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
actual := normPt(s.ptIn, s.l)
|
||||
if actual != s.ptOut {
|
||||
t.Errorf("in: %v*%v, expected: %v, actual: %v", s.l, s.ptIn, s.ptOut, actual)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSplitCubic(b *testing.B) {
|
||||
type scenario struct {
|
||||
segments int
|
||||
@@ -47,12 +152,11 @@ 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 i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
quads = SplitCubic(from, ctrl0, ctrl1, to, quads[:0])
|
||||
}
|
||||
if len(quads) != s.segments {
|
||||
|
||||
@@ -385,6 +385,7 @@ static VkResult vkQueuePresentKHR(PFN_vkQueuePresentKHR f, VkQueue queue, const
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -1167,7 +1168,6 @@ func CreateFramebuffer(d Device, rp RenderPass, view ImageView, width, height in
|
||||
return nilFramebuffer, fmt.Errorf("vulkan: vkCreateFramebuffer: %w", err)
|
||||
}
|
||||
return fbo, nil
|
||||
|
||||
}
|
||||
|
||||
func DestroyFramebuffer(d Device, f Framebuffer) {
|
||||
|
||||
@@ -17,6 +17,7 @@ static VkResult vkCreateAndroidSurfaceKHR(PFN_vkCreateAndroidSurfaceKHR f, VkIns
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
@@ -19,6 +19,7 @@ static VkResult vkCreateWaylandSurfaceKHR(PFN_vkCreateWaylandSurfaceKHR f, VkIns
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
@@ -17,6 +17,7 @@ static VkResult vkCreateXlibSurfaceKHR(PFN_vkCreateXlibSurfaceKHR f, VkInstance
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
+1
-1
@@ -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 interface{}
|
||||
type Tag any
|
||||
|
||||
// Event is the marker interface for events.
|
||||
type Event interface {
|
||||
|
||||
@@ -4,6 +4,7 @@ package input
|
||||
|
||||
import (
|
||||
"io"
|
||||
"slices"
|
||||
|
||||
"gioui.org/io/clipboard"
|
||||
"gioui.org/io/event"
|
||||
@@ -60,10 +61,8 @@ func (q *clipboardQueue) ProcessWriteClipboard(req clipboard.WriteCmd) {
|
||||
}
|
||||
|
||||
func (q *clipboardQueue) ProcessReadClipboard(state clipboardState, tag event.Tag) clipboardState {
|
||||
for _, k := range state.receivers {
|
||||
if k == tag {
|
||||
return state
|
||||
}
|
||||
if slices.Contains(state.receivers, tag) {
|
||||
return state
|
||||
}
|
||||
n := len(state.receivers)
|
||||
state.receivers = append(state.receivers[:n:n], tag)
|
||||
|
||||
@@ -52,7 +52,7 @@ func TestQueueProcessReadClipboard(t *testing.T) {
|
||||
assertClipboardReadCmd(t, r, 1)
|
||||
ops.Reset()
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
for range 3 {
|
||||
// No ReadCmd
|
||||
// One receiver must still wait for response
|
||||
|
||||
|
||||
+4
-4
@@ -4,6 +4,7 @@ package input
|
||||
|
||||
import (
|
||||
"image"
|
||||
"slices"
|
||||
"sort"
|
||||
|
||||
"gioui.org/f32"
|
||||
@@ -280,6 +281,7 @@ 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}})
|
||||
@@ -304,10 +306,8 @@ func (s keyState) softKeyboard(show bool) keyState {
|
||||
}
|
||||
|
||||
func (k *keyFilter) Add(f key.Filter) {
|
||||
for _, f2 := range *k {
|
||||
if f == f2 {
|
||||
return
|
||||
}
|
||||
if slices.Contains(*k, f) {
|
||||
return
|
||||
}
|
||||
*k = append(*k, f)
|
||||
}
|
||||
|
||||
+45
-40
@@ -5,6 +5,7 @@ package input
|
||||
import (
|
||||
"image"
|
||||
"io"
|
||||
"slices"
|
||||
|
||||
"gioui.org/f32"
|
||||
f32internal "gioui.org/internal/f32"
|
||||
@@ -143,7 +144,9 @@ const (
|
||||
)
|
||||
|
||||
func (c *pointerCollector) resetState() {
|
||||
c.state = collectState{}
|
||||
c.state = collectState{
|
||||
t: f32.AffineId(),
|
||||
}
|
||||
c.nodeStack = c.nodeStack[:0]
|
||||
// Pop every node except the root.
|
||||
if len(c.q.hitTree) > 0 {
|
||||
@@ -257,6 +260,11 @@ 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 {
|
||||
@@ -282,17 +290,13 @@ func (c *pointerCollector) inputOp(tag event.Tag, state *pointerHandler) {
|
||||
func (p *pointerFilter) Add(f event.Filter) {
|
||||
switch f := f.(type) {
|
||||
case transfer.SourceFilter:
|
||||
for _, m := range p.sourceMimes {
|
||||
if m == f.Type {
|
||||
return
|
||||
}
|
||||
if slices.Contains(p.sourceMimes, f.Type) {
|
||||
return
|
||||
}
|
||||
p.sourceMimes = append(p.sourceMimes, f.Type)
|
||||
case transfer.TargetFilter:
|
||||
for _, m := range p.targetMimes {
|
||||
if m == f.Type {
|
||||
return
|
||||
}
|
||||
if slices.Contains(p.targetMimes, f.Type) {
|
||||
return
|
||||
}
|
||||
p.targetMimes = append(p.targetMimes, f.Type)
|
||||
case pointer.Filter:
|
||||
@@ -309,16 +313,12 @@ 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:
|
||||
for _, t := range p.sourceMimes {
|
||||
if t == e.Type {
|
||||
return true
|
||||
}
|
||||
if slices.Contains(p.sourceMimes, e.Type) {
|
||||
return true
|
||||
}
|
||||
case transfer.DataEvent:
|
||||
for _, t := range p.targetMimes {
|
||||
if t == e.Type {
|
||||
return true
|
||||
}
|
||||
if slices.Contains(p.targetMimes, e.Type) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
@@ -413,7 +413,7 @@ func (q *pointerQueue) offerData(handlers map[event.Tag]*handler, state pointerS
|
||||
},
|
||||
}})
|
||||
}
|
||||
state.pointers = append([]pointerInfo{}, state.pointers...)
|
||||
state.pointers = slices.Clone(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 = append(ids[:i], ids[i+1:]...)
|
||||
ids = slices.Delete(ids, i, 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 = append([]pointerInfo{}, state.pointers...)
|
||||
state.pointers = slices.Clone(state.pointers)
|
||||
state.pointers[i] = p
|
||||
}
|
||||
}
|
||||
@@ -739,6 +739,10 @@ func (q *pointerQueue) Push(handlers map[event.Tag]*handler, state pointerState,
|
||||
state.pointers = nil
|
||||
return state, evts
|
||||
}
|
||||
if e.Kind == pointer.Scroll {
|
||||
// Scroll events are not bound to a pointer; see pointer.Event.PointerID.
|
||||
return state, q.deliverScrollEvent(handlers, evts, e)
|
||||
}
|
||||
state, pidx := state.pointerOf(e)
|
||||
p := state.pointers[pidx]
|
||||
|
||||
@@ -756,14 +760,13 @@ func (q *pointerQueue) Push(handlers map[event.Tag]*handler, state pointerState,
|
||||
if p.pressed {
|
||||
p, evts = q.deliverDragEvent(handlers, p, evts)
|
||||
}
|
||||
case pointer.Leave:
|
||||
p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e)
|
||||
case pointer.Release:
|
||||
evts = q.deliverEvent(handlers, p, evts, e)
|
||||
p.pressed = false
|
||||
p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e)
|
||||
p, evts = q.deliverDropEvent(handlers, p, evts)
|
||||
case pointer.Scroll:
|
||||
p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e)
|
||||
evts = q.deliverEvent(handlers, p, evts, e)
|
||||
default:
|
||||
panic("unsupported pointer event type")
|
||||
}
|
||||
@@ -772,19 +775,29 @@ 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 = append(state.pointers[:pidx:pidx], state.pointers[pidx+1:]...)
|
||||
state.pointers = slices.Concat(state.pointers[:pidx:pidx], state.pointers[pidx+1:])
|
||||
} else {
|
||||
state.pointers = append([]pointerInfo{}, state.pointers...)
|
||||
state.pointers = slices.Clone(state.pointers)
|
||||
state.pointers[pidx] = p
|
||||
}
|
||||
return state, evts
|
||||
}
|
||||
|
||||
// deliverScrollEvent delivers scroll events to the handlers hit by the event coordinate.
|
||||
func (q *pointerQueue) deliverScrollEvent(handlers map[event.Tag]*handler, evts []taggedEvent, e pointer.Event) []taggedEvent {
|
||||
var hits []event.Tag
|
||||
q.hitTest(e.Position, func(n *hitNode) bool {
|
||||
if _, ok := handlers[n.tag]; ok {
|
||||
hits = addHandler(hits, n.tag)
|
||||
}
|
||||
return true
|
||||
})
|
||||
return q.deliverEvent(handlers, pointerInfo{handlers: hits}, evts, e)
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -803,10 +816,6 @@ 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})
|
||||
}
|
||||
@@ -816,7 +825,7 @@ func (q *pointerQueue) deliverEvent(handlers map[event.Tag]*handler, p pointerIn
|
||||
func (q *pointerQueue) deliverEnterLeaveEvents(handlers map[event.Tag]*handler, cursor pointer.Cursor, p pointerInfo, evts []taggedEvent, e pointer.Event) (pointerInfo, []taggedEvent, pointer.Cursor, bool) {
|
||||
changed := false
|
||||
var hits []event.Tag
|
||||
if e.Source != pointer.Mouse && !p.pressed && e.Kind != pointer.Press {
|
||||
if e.Kind == pointer.Leave || e.Source != pointer.Mouse && !p.pressed && e.Kind != pointer.Press {
|
||||
// Consider non-mouse pointers leaving when they're released.
|
||||
} else {
|
||||
var transSrc *pointerFilter
|
||||
@@ -970,10 +979,8 @@ func searchTag(tags []event.Tag, tag event.Tag) (int, bool) {
|
||||
|
||||
// addHandler adds tag to the slice if not present.
|
||||
func addHandler(tags []event.Tag, tag event.Tag) []event.Tag {
|
||||
for _, t := range tags {
|
||||
if t == tag {
|
||||
return tags
|
||||
}
|
||||
if slices.Contains(tags, tag) {
|
||||
return tags
|
||||
}
|
||||
return append(tags, tag)
|
||||
}
|
||||
@@ -981,10 +988,8 @@ 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 {
|
||||
for _, m2 := range src.sourceMimes {
|
||||
if m1 == m2 {
|
||||
return m1, true
|
||||
}
|
||||
if slices.Contains(src.sourceMimes, m1) {
|
||||
return m1, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
|
||||
+131
-14
@@ -97,6 +97,39 @@ 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)
|
||||
@@ -222,6 +255,45 @@ func TestPointerMove(t *testing.T) {
|
||||
assertEventPointerTypeSequence(t, events(&r, -1, filter(handler2)), pointer.Enter, pointer.Move, pointer.Leave, pointer.Cancel)
|
||||
}
|
||||
|
||||
func TestPointerLeave(t *testing.T) {
|
||||
handler := new(int)
|
||||
var ops op.Ops
|
||||
|
||||
filter := pointer.Filter{
|
||||
Target: handler,
|
||||
Kinds: pointer.Move | pointer.Enter | pointer.Leave | pointer.Cancel,
|
||||
}
|
||||
defer clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops).Pop()
|
||||
event.Op(&ops, handler)
|
||||
|
||||
var r Router
|
||||
events(&r, -1, filter)
|
||||
r.Frame(&ops)
|
||||
r.Queue(
|
||||
pointer.Event{
|
||||
Kind: pointer.Move,
|
||||
Source: pointer.Mouse,
|
||||
PointerID: 1,
|
||||
Position: f32.Pt(50, 50),
|
||||
},
|
||||
pointer.Event{
|
||||
Kind: pointer.Leave,
|
||||
Source: pointer.Mouse,
|
||||
PointerID: 1,
|
||||
Position: f32.Pt(50, 50),
|
||||
},
|
||||
)
|
||||
assertEventPointerTypeSequence(t, events(&r, -1, filter), pointer.Enter, pointer.Move, pointer.Leave)
|
||||
|
||||
r.Queue(pointer.Event{
|
||||
Kind: pointer.Move,
|
||||
Source: pointer.Mouse,
|
||||
PointerID: 1,
|
||||
Position: f32.Pt(50, 50),
|
||||
})
|
||||
assertEventPointerTypeSequence(t, events(&r, -1, filter), pointer.Enter, pointer.Move)
|
||||
}
|
||||
|
||||
func TestPointerTypes(t *testing.T) {
|
||||
handler := new(int)
|
||||
var ops op.Ops
|
||||
@@ -367,9 +439,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.Foremost)
|
||||
assertEventPriorities(t, hev2, pointer.Foremost)
|
||||
assertEventPriorities(t, hev3, pointer.Foremost)
|
||||
assertEventPriorities(t, hev1, pointer.Shared, pointer.Shared)
|
||||
assertEventPriorities(t, hev2, pointer.Shared)
|
||||
assertEventPriorities(t, hev3, pointer.Shared)
|
||||
assertScrollEvent(t, hev1[0], f32.Pt(30, 0))
|
||||
assertScrollEvent(t, hev2[0], f32.Pt(20, 0))
|
||||
assertScrollEvent(t, hev1[1], f32.Pt(50, 0))
|
||||
@@ -678,26 +750,31 @@ 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},
|
||||
@@ -705,7 +782,8 @@ 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),
|
||||
@@ -714,7 +792,8 @@ 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),
|
||||
@@ -725,7 +804,8 @@ 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),
|
||||
@@ -1110,7 +1190,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.Foremost})
|
||||
pointer.Event{Kind: pointer.Press, Source: pointer.Mouse, Priority: pointer.Shared})
|
||||
}
|
||||
|
||||
// offer satisfies io.ReadCloser for use in data transfers.
|
||||
@@ -1256,7 +1336,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 := 0; i < handlerCount; i++ {
|
||||
for i := range handlerCount {
|
||||
h := new(int)
|
||||
*h = i
|
||||
handlers[i] = h
|
||||
@@ -1278,7 +1358,7 @@ func BenchmarkRouterAdd(b *testing.B) {
|
||||
r.Frame(&ops)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
r.Queue(
|
||||
pointer.Event{
|
||||
Kind: pointer.Move,
|
||||
@@ -1304,3 +1384,40 @@ func events(r *Router, n int, filters ...event.Filter) []event.Event {
|
||||
}
|
||||
return events
|
||||
}
|
||||
|
||||
// TestPointerScrollDoesNotTrackPointer queues two events over two cursor
|
||||
// regions. The Move puts the live pointer over the button (CursorPointer);
|
||||
// the Scroll happens over the cell (CursorText) and must not update the
|
||||
// cursor.
|
||||
func TestPointerScrollDoesNotTrackPointer(t *testing.T) {
|
||||
var ops op.Ops
|
||||
|
||||
button := clip.Rect(image.Rect(0, 0, 50, 50)).Push(&ops)
|
||||
pointer.CursorPointer.Add(&ops)
|
||||
button.Pop()
|
||||
|
||||
cell := clip.Rect(image.Rect(100, 0, 200, 50)).Push(&ops)
|
||||
pointer.CursorText.Add(&ops)
|
||||
cell.Pop()
|
||||
|
||||
var r Router
|
||||
r.Frame(&ops)
|
||||
r.Queue(
|
||||
pointer.Event{
|
||||
Kind: pointer.Move,
|
||||
Source: pointer.Mouse,
|
||||
Position: f32.Pt(25, 25),
|
||||
},
|
||||
pointer.Event{
|
||||
Kind: pointer.Scroll,
|
||||
Source: pointer.Mouse,
|
||||
Position: f32.Pt(150, 25),
|
||||
Scroll: f32.Pt(0, 1),
|
||||
},
|
||||
)
|
||||
|
||||
if got, want := r.Cursor(), pointer.CursorPointer; got != want {
|
||||
t.Errorf("got %q, want %q (scroll position must not update the cursor; "+
|
||||
"the live pointer's last position is what determines it)", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
+29
-17
@@ -5,6 +5,7 @@ package input
|
||||
import (
|
||||
"image"
|
||||
"io"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -62,7 +63,8 @@ type Router struct {
|
||||
// Source implements the interface between a Router and user interface widgets.
|
||||
// The zero-value Source is disabled.
|
||||
type Source struct {
|
||||
r *Router
|
||||
r *Router
|
||||
disabled bool
|
||||
}
|
||||
|
||||
// Command represents a request such as moving the focus, or initiating a clipboard read.
|
||||
@@ -171,30 +173,38 @@ func (q *Router) Source() Source {
|
||||
|
||||
// Execute a command.
|
||||
func (s Source) Execute(c Command) {
|
||||
if !s.enabled() {
|
||||
if !s.Enabled() {
|
||||
return
|
||||
}
|
||||
s.r.execute(c)
|
||||
}
|
||||
|
||||
// enabled reports whether the source is enabled. Only enabled
|
||||
// Sources deliver events and respond to commands.
|
||||
func (s Source) enabled() bool {
|
||||
return s.r != nil
|
||||
// Disabled returns a copy of this source that don't deliver any events.
|
||||
func (s Source) Disabled() Source {
|
||||
s2 := s
|
||||
s2.disabled = true
|
||||
return s2
|
||||
}
|
||||
|
||||
// Enabled reports whether the source is enabled. Only enabled
|
||||
// Sources deliver events.
|
||||
func (s Source) Enabled() bool {
|
||||
return s.r != nil && !s.disabled
|
||||
}
|
||||
|
||||
// Focused reports whether tag is focused, according to the most recent
|
||||
// [key.FocusEvent] delivered.
|
||||
func (s Source) Focused(tag event.Tag) bool {
|
||||
if !s.enabled() {
|
||||
if !s.Enabled() {
|
||||
return false
|
||||
}
|
||||
return s.r.state().keyState.focus == tag
|
||||
}
|
||||
|
||||
// Event returns the next event that matches at least one of filters.
|
||||
// If the source is disabled, no events will be reported.
|
||||
func (s Source) Event(filters ...event.Filter) (event.Event, bool) {
|
||||
if !s.enabled() {
|
||||
if !s.Enabled() {
|
||||
return nil, false
|
||||
}
|
||||
return s.r.Event(filters...)
|
||||
@@ -293,7 +303,7 @@ func (q *Router) Event(filters ...event.Filter) (event.Event, bool) {
|
||||
}
|
||||
}
|
||||
if match {
|
||||
change.events = append(change.events[:j], change.events[j+1:]...)
|
||||
change.events = slices.Delete(change.events, j, j+1)
|
||||
// Fast forward state to last matched.
|
||||
q.collapseState(i)
|
||||
return evt.event, true
|
||||
@@ -618,11 +628,11 @@ func (q *Router) RevealFocus(viewport image.Rectangle) {
|
||||
viewport = q.pointer.queue.ClipFor(area, viewport)
|
||||
|
||||
topleft := bounds.Min.Sub(viewport.Min)
|
||||
topleft = max(topleft, bounds.Max.Sub(viewport.Max))
|
||||
topleft = min(image.Pt(0, 0), topleft)
|
||||
topleft = maxPoint(topleft, bounds.Max.Sub(viewport.Max))
|
||||
topleft = minPoint(image.Pt(0, 0), topleft)
|
||||
bottomright := bounds.Max.Sub(viewport.Max)
|
||||
bottomright = min(bottomright, bounds.Min.Sub(viewport.Min))
|
||||
bottomright = max(image.Pt(0, 0), bottomright)
|
||||
bottomright = minPoint(bottomright, bounds.Min.Sub(viewport.Min))
|
||||
bottomright = maxPoint(image.Pt(0, 0), bottomright)
|
||||
s := topleft
|
||||
if s.X == 0 {
|
||||
s.X = bottomright.X
|
||||
@@ -649,7 +659,7 @@ func (q *Router) ScrollFocus(dist image.Point) {
|
||||
}))
|
||||
}
|
||||
|
||||
func max(p1, p2 image.Point) image.Point {
|
||||
func maxPoint(p1, p2 image.Point) image.Point {
|
||||
m := p1
|
||||
if p2.X > m.X {
|
||||
m.X = p2.X
|
||||
@@ -660,7 +670,7 @@ func max(p1, p2 image.Point) image.Point {
|
||||
return m
|
||||
}
|
||||
|
||||
func min(p1, p2 image.Point) image.Point {
|
||||
func minPoint(p1, p2 image.Point) image.Point {
|
||||
m := p1
|
||||
if p2.X < m.X {
|
||||
m.X = p2.X
|
||||
@@ -769,13 +779,15 @@ func (q *Router) collect() {
|
||||
pc.Reset()
|
||||
kq := &q.key.queue
|
||||
q.key.queue.Reset()
|
||||
var t f32.Affine2D
|
||||
t := f32.AffineId()
|
||||
for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() {
|
||||
switch ops.OpType(encOp.Data[0]) {
|
||||
case ops.TypeSave:
|
||||
id := ops.DecodeSave(encOp.Data)
|
||||
if extra := id - len(q.savedTrans) + 1; extra > 0 {
|
||||
q.savedTrans = append(q.savedTrans, make([]f32.Affine2D, extra)...)
|
||||
for range extra {
|
||||
q.savedTrans = append(q.savedTrans, f32.AffineId())
|
||||
}
|
||||
}
|
||||
q.savedTrans[id] = t
|
||||
case ops.TypeLoad:
|
||||
|
||||
@@ -15,7 +15,7 @@ func TestNoFilterAllocs(t *testing.T) {
|
||||
s := r.Source()
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
s.Event(pointer.Filter{})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -125,7 +125,7 @@ func verifyTree(t *testing.T, parent SemanticID, n SemanticNode) {
|
||||
}
|
||||
|
||||
func printTree(indent int, n SemanticNode) {
|
||||
for i := 0; i < indent; i++ {
|
||||
for range indent {
|
||||
fmt.Print("\t")
|
||||
}
|
||||
fmt.Printf("%d: %+v\n", n.ID, n.Desc)
|
||||
|
||||
+1
-2
@@ -1,7 +1,6 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build !darwin
|
||||
// +build !darwin
|
||||
//go:build !darwin && !js
|
||||
|
||||
package key
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
-8
@@ -19,7 +19,9 @@ 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 or Cancel.
|
||||
// Release. Populated for Press, Release, Move, Drag,
|
||||
// Enter, Leave, and Cancel; Scroll events are not
|
||||
// bound to a tracked pointer and leave it zero.
|
||||
PointerID ID
|
||||
// Priority is the priority of the receiving handler
|
||||
// for this event.
|
||||
@@ -43,8 +45,7 @@ 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 {
|
||||
@@ -207,9 +208,6 @@ 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
|
||||
)
|
||||
@@ -223,6 +221,12 @@ 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 {
|
||||
@@ -295,8 +299,6 @@ func (p Priority) String() string {
|
||||
switch p {
|
||||
case Shared:
|
||||
return "Shared"
|
||||
case Foremost:
|
||||
return "Foremost"
|
||||
case Grabbed:
|
||||
return "Grabbed"
|
||||
default:
|
||||
@@ -332,6 +334,12 @@ 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,7 +1,6 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build !race
|
||||
// +build !race
|
||||
|
||||
package layout
|
||||
|
||||
|
||||
+5
-16
@@ -5,7 +5,6 @@ package layout
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/input"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/op"
|
||||
@@ -29,7 +28,10 @@ type Context struct {
|
||||
// Interested users must look up and populate these values manually.
|
||||
Locale system.Locale
|
||||
|
||||
disabled bool
|
||||
// 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
|
||||
}
|
||||
@@ -44,21 +46,8 @@ func (c Context) Sp(v unit.Sp) int {
|
||||
return c.Metric.Sp(v)
|
||||
}
|
||||
|
||||
func (c Context) Event(filters ...event.Filter) (event.Event, bool) {
|
||||
if c.disabled {
|
||||
return nil, false
|
||||
}
|
||||
return c.Source.Event(filters...)
|
||||
}
|
||||
|
||||
// Enabled reports whether this context is enabled. Disabled contexts
|
||||
// don't report events.
|
||||
func (c Context) Enabled() bool {
|
||||
return !c.disabled
|
||||
}
|
||||
|
||||
// Disabled returns a copy of this context that don't deliver any events.
|
||||
func (c Context) Disabled() Context {
|
||||
c.disabled = true
|
||||
c.Source = c.Source.Disabled()
|
||||
return c
|
||||
}
|
||||
|
||||
+38
-14
@@ -22,6 +22,8 @@ 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.
|
||||
@@ -30,10 +32,6 @@ type FlexChild struct {
|
||||
weight float32
|
||||
|
||||
widget Widget
|
||||
|
||||
// Scratch space.
|
||||
call op.CallOp
|
||||
dims Dimensions
|
||||
}
|
||||
|
||||
// Spacing determine the spacing mode for a Flex.
|
||||
@@ -86,8 +84,30 @@ 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 {
|
||||
@@ -104,8 +124,8 @@ func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions {
|
||||
if remaining < 0 {
|
||||
remaining = 0
|
||||
}
|
||||
children[i].call = c
|
||||
children[i].dims = dims
|
||||
scratch[i].call = c
|
||||
scratch[i].dims = dims
|
||||
}
|
||||
if w := f.WeightSum; w != 0 {
|
||||
totalWeight = w
|
||||
@@ -139,19 +159,22 @@ func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions {
|
||||
if remaining < 0 {
|
||||
remaining = 0
|
||||
}
|
||||
children[i].call = c
|
||||
children[i].dims = dims
|
||||
scratch[i].call = c
|
||||
scratch[i].dims = dims
|
||||
}
|
||||
maxCross := crossMin
|
||||
var maxBaseline int
|
||||
for _, child := range children {
|
||||
if c := f.Axis.Convert(child.dims.Size).Y; c > maxCross {
|
||||
for _, scratchChild := range scratch {
|
||||
if c := f.Axis.Convert(scratchChild.dims.Size).Y; c > maxCross {
|
||||
maxCross = c
|
||||
}
|
||||
if b := child.dims.Size.Y - child.dims.Baseline; b > maxBaseline {
|
||||
if b := scratchChild.dims.Size.Y - scratchChild.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
|
||||
@@ -169,8 +192,8 @@ func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions {
|
||||
mainSize += space / (len(children) * 2)
|
||||
}
|
||||
}
|
||||
for i, child := range children {
|
||||
dims := child.dims
|
||||
for i, scratchChild := range scratch {
|
||||
dims := scratchChild.dims
|
||||
b := dims.Size.Y - dims.Baseline
|
||||
var cross int
|
||||
switch f.Alignment {
|
||||
@@ -185,10 +208,11 @@ func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions {
|
||||
}
|
||||
pt := f.Axis.Convert(image.Pt(mainSize, cross))
|
||||
trans := op.Offset(pt).Push(gtx.Ops)
|
||||
child.call.Add(gtx.Ops)
|
||||
scratchChild.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))
|
||||
|
||||
@@ -44,6 +44,106 @@ func TestFlex(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlexGap(t *testing.T) {
|
||||
gtx := Context{
|
||||
Ops: new(op.Ops),
|
||||
Constraints: Constraints{
|
||||
Max: image.Pt(100, 100),
|
||||
},
|
||||
}
|
||||
|
||||
// Two 20px children with 10px gap = 50px total.
|
||||
dims := Flex{Gap: 10}.Layout(gtx,
|
||||
Rigid(func(gtx Context) Dimensions {
|
||||
return Dimensions{Size: image.Pt(20, 10)}
|
||||
}),
|
||||
Rigid(func(gtx Context) Dimensions {
|
||||
return Dimensions{Size: image.Pt(20, 10)}
|
||||
}),
|
||||
)
|
||||
if got, exp := dims.Size.X, 50; got != exp {
|
||||
t.Errorf("two rigid children with gap: got width %d, expected %d", got, exp)
|
||||
}
|
||||
|
||||
// Three children: gap added between each pair.
|
||||
dims = Flex{Gap: 5}.Layout(gtx,
|
||||
Rigid(func(gtx Context) Dimensions {
|
||||
return Dimensions{Size: image.Pt(10, 10)}
|
||||
}),
|
||||
Rigid(func(gtx Context) Dimensions {
|
||||
return Dimensions{Size: image.Pt(10, 10)}
|
||||
}),
|
||||
Rigid(func(gtx Context) Dimensions {
|
||||
return Dimensions{Size: image.Pt(10, 10)}
|
||||
}),
|
||||
)
|
||||
if got, exp := dims.Size.X, 40; got != exp {
|
||||
t.Errorf("three rigid children with gap: got width %d, expected %d", got, exp)
|
||||
}
|
||||
|
||||
// Single child: no gap added.
|
||||
dims = Flex{Gap: 10}.Layout(gtx,
|
||||
Rigid(func(gtx Context) Dimensions {
|
||||
return Dimensions{Size: image.Pt(20, 10)}
|
||||
}),
|
||||
)
|
||||
if got, exp := dims.Size.X, 20; got != exp {
|
||||
t.Errorf("single child with gap: got width %d, expected %d", got, exp)
|
||||
}
|
||||
|
||||
// Gap with flexed children: gap is reserved from available space.
|
||||
dims = Flex{Gap: 10}.Layout(gtx,
|
||||
Flexed(1, func(gtx Context) Dimensions {
|
||||
return Dimensions{Size: image.Pt(gtx.Constraints.Max.X, 10)}
|
||||
}),
|
||||
Flexed(1, func(gtx Context) Dimensions {
|
||||
return Dimensions{Size: image.Pt(gtx.Constraints.Max.X, 10)}
|
||||
}),
|
||||
)
|
||||
// 100px max - 10px gap = 90px for flex; 45px each.
|
||||
if got, exp := dims.Size.X, 100; got != exp {
|
||||
t.Errorf("flexed children with gap: got width %d, expected %d", got, exp)
|
||||
}
|
||||
|
||||
// Vertical axis with gap.
|
||||
dims = Flex{Axis: Vertical, Gap: 15}.Layout(gtx,
|
||||
Rigid(func(gtx Context) Dimensions {
|
||||
return Dimensions{Size: image.Pt(10, 20)}
|
||||
}),
|
||||
Rigid(func(gtx Context) Dimensions {
|
||||
return Dimensions{Size: image.Pt(10, 20)}
|
||||
}),
|
||||
)
|
||||
if got, exp := dims.Size.Y, 55; got != exp {
|
||||
t.Errorf("vertical with gap: got height %d, expected %d", got, exp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlexGapConstraints(t *testing.T) {
|
||||
gtx := Context{
|
||||
Ops: new(op.Ops),
|
||||
Constraints: Constraints{
|
||||
Max: image.Pt(100, 100),
|
||||
},
|
||||
}
|
||||
|
||||
// Verify that flexed children receive constraints with gap accounted for.
|
||||
var flexMax int
|
||||
Flex{Gap: 10}.Layout(gtx,
|
||||
Rigid(func(gtx Context) Dimensions {
|
||||
return Dimensions{Size: image.Pt(30, 10)}
|
||||
}),
|
||||
Flexed(1, func(gtx Context) Dimensions {
|
||||
flexMax = gtx.Constraints.Max.X
|
||||
return Dimensions{Size: image.Pt(gtx.Constraints.Max.X, 10)}
|
||||
}),
|
||||
)
|
||||
// 100 - 10 (gap) - 30 (rigid) = 60 remaining for flex.
|
||||
if got, exp := flexMax, 60; got != exp {
|
||||
t.Errorf("flex constraint with gap: got %d, expected %d", got, exp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDirection(t *testing.T) {
|
||||
max := image.Pt(100, 100)
|
||||
for _, tc := range []struct {
|
||||
|
||||
+31
-13
@@ -29,6 +29,10 @@ type List struct {
|
||||
ScrollToEnd bool
|
||||
// Alignment is the cross axis alignment of list elements.
|
||||
Alignment Alignment
|
||||
// ScrollAnyAxis allows any scroll axis to scroll the list, not just the main axis.
|
||||
ScrollAnyAxis bool
|
||||
// Gap is the space in pixels between children.
|
||||
Gap int
|
||||
|
||||
cs Constraints
|
||||
scroll gesture.Scroll
|
||||
@@ -128,7 +132,7 @@ func (l *List) Layout(gtx Context, len int, w ListElement) Dimensions {
|
||||
}
|
||||
|
||||
if numLaidOut > 0 {
|
||||
l.Position.Length = laidOutTotalLength * len / numLaidOut
|
||||
l.Position.Length = laidOutTotalLength*len/numLaidOut + l.Gap*(len-1)
|
||||
} else {
|
||||
l.Position.Length = 0
|
||||
}
|
||||
@@ -159,12 +163,19 @@ func (l *List) update(gtx Context) {
|
||||
max = 0
|
||||
}
|
||||
}
|
||||
|
||||
xrange := pointer.ScrollRange{Min: min, Max: max}
|
||||
yrange := pointer.ScrollRange{}
|
||||
if l.Axis == Vertical {
|
||||
|
||||
axis := gesture.Axis(l.Axis)
|
||||
if l.ScrollAnyAxis {
|
||||
axis = gesture.Both
|
||||
yrange = xrange
|
||||
} else if l.Axis == Vertical {
|
||||
xrange, yrange = yrange, xrange
|
||||
}
|
||||
d := l.scroll.Update(gtx.Metric, gtx.Source, gtx.Now, gesture.Axis(l.Axis), xrange, yrange)
|
||||
d := l.scroll.Update(gtx.Metric, gtx.Source, gtx.Now, axis, xrange, yrange)
|
||||
|
||||
l.scrollDelta = d
|
||||
l.Position.Offset += d
|
||||
}
|
||||
@@ -214,11 +225,11 @@ func (l *List) nextDir() iterationDir {
|
||||
if len(l.children) > 0 {
|
||||
if l.Position.First > 0 {
|
||||
firstChild := l.children[0]
|
||||
firstSize = l.Axis.Convert(firstChild.size).X
|
||||
firstSize = l.Axis.Convert(firstChild.size).X + l.Gap
|
||||
}
|
||||
if last < l.len {
|
||||
lastChild := l.children[len(l.children)-1]
|
||||
lastSize = l.Axis.Convert(lastChild.size).X
|
||||
lastSize = l.Axis.Convert(lastChild.size).X + l.Gap
|
||||
}
|
||||
}
|
||||
switch {
|
||||
@@ -236,6 +247,9 @@ func (l *List) nextDir() iterationDir {
|
||||
func (l *List) end(dims Dimensions, call op.CallOp) {
|
||||
child := scrollChild{dims.Size, call}
|
||||
mainSize := l.Axis.Convert(child.size).X
|
||||
if len(l.children) > 0 {
|
||||
l.maxSize += l.Gap
|
||||
}
|
||||
l.maxSize += mainSize
|
||||
switch l.dir {
|
||||
case iterateForward:
|
||||
@@ -245,7 +259,7 @@ func (l *List) end(dims Dimensions, call op.CallOp) {
|
||||
copy(l.children[1:], l.children)
|
||||
l.children[0] = child
|
||||
l.Position.First--
|
||||
l.Position.Offset += mainSize
|
||||
l.Position.Offset += mainSize + l.Gap
|
||||
default:
|
||||
panic("call Next before End")
|
||||
}
|
||||
@@ -270,7 +284,7 @@ func (l *List) layout(ops *op.Ops, macro op.MacroOp) Dimensions {
|
||||
break
|
||||
}
|
||||
l.Position.First++
|
||||
l.Position.Offset -= mainSize
|
||||
l.Position.Offset -= mainSize + l.Gap
|
||||
first = child
|
||||
children = children[1:]
|
||||
}
|
||||
@@ -282,6 +296,9 @@ func (l *List) layout(ops *op.Ops, macro op.MacroOp) Dimensions {
|
||||
if c := sz.Y; c > maxCross {
|
||||
maxCross = c
|
||||
}
|
||||
if i > 0 {
|
||||
size += l.Gap
|
||||
}
|
||||
size += sz.X
|
||||
if size >= mainMax {
|
||||
if i < len(children)-1 {
|
||||
@@ -308,10 +325,6 @@ func (l *List) layout(ops *op.Ops, macro op.MacroOp) Dimensions {
|
||||
cross = (maxCross - sz.Y) / 2
|
||||
}
|
||||
childSize := sz.X
|
||||
min := pos
|
||||
if min < 0 {
|
||||
min = 0
|
||||
}
|
||||
pt := l.Axis.Convert(image.Pt(pos, cross))
|
||||
trans := op.Offset(pt).Push(ops)
|
||||
child.call.Add(ops)
|
||||
@@ -321,14 +334,19 @@ func (l *List) layout(ops *op.Ops, macro op.MacroOp) Dimensions {
|
||||
// Lay out leading invisible child.
|
||||
if first != (scrollChild{}) {
|
||||
sz := l.Axis.Convert(first.size)
|
||||
pos -= sz.X
|
||||
pos -= sz.X + l.Gap
|
||||
layout(first)
|
||||
pos += l.Gap
|
||||
}
|
||||
for _, child := range children {
|
||||
for i, child := range children {
|
||||
if i > 0 {
|
||||
pos += l.Gap
|
||||
}
|
||||
layout(child)
|
||||
}
|
||||
// Lay out trailing invisible child.
|
||||
if last != (scrollChild{}) {
|
||||
pos += l.Gap
|
||||
layout(last)
|
||||
}
|
||||
atStart := l.Position.First == 0 && l.Position.Offset <= 0
|
||||
|
||||
+99
-6
@@ -88,7 +88,8 @@ func TestListPosition(t *testing.T) {
|
||||
{label: "1 visible 0 hidden", num: 1, count: 1, last: 10},
|
||||
{label: "2 visible 0 hidden", num: 2, count: 2},
|
||||
{label: "2 visible 1 hidden", num: 3, count: 2},
|
||||
{label: "3 visible 0 hidden small scroll", num: 3, count: 3, offset: 5, last: -5,
|
||||
{
|
||||
label: "3 visible 0 hidden small scroll", num: 3, count: 3, offset: 5, last: -5,
|
||||
scroll: _s(
|
||||
pointer.Event{
|
||||
Source: pointer.Mouse,
|
||||
@@ -107,8 +108,10 @@ func TestListPosition(t *testing.T) {
|
||||
Kind: pointer.Release,
|
||||
Position: f32.Pt(5, 0),
|
||||
},
|
||||
)},
|
||||
{label: "3 visible 0 hidden small scroll 2", num: 3, count: 3, offset: 3, last: -7,
|
||||
),
|
||||
},
|
||||
{
|
||||
label: "3 visible 0 hidden small scroll 2", num: 3, count: 3, offset: 3, last: -7,
|
||||
scroll: _s(
|
||||
pointer.Event{
|
||||
Source: pointer.Mouse,
|
||||
@@ -127,8 +130,10 @@ func TestListPosition(t *testing.T) {
|
||||
Kind: pointer.Release,
|
||||
Position: f32.Pt(5, 0),
|
||||
},
|
||||
)},
|
||||
{label: "2 visible 1 hidden large scroll", num: 3, count: 2, first: 1,
|
||||
),
|
||||
},
|
||||
{
|
||||
label: "2 visible 1 hidden large scroll", num: 3, count: 2, first: 1,
|
||||
scroll: _s(
|
||||
pointer.Event{
|
||||
Source: pointer.Mouse,
|
||||
@@ -147,7 +152,8 @@ func TestListPosition(t *testing.T) {
|
||||
Kind: pointer.Release,
|
||||
Position: f32.Pt(15, 0),
|
||||
},
|
||||
)},
|
||||
),
|
||||
},
|
||||
} {
|
||||
t.Run(tc.label, func(t *testing.T) {
|
||||
gtx.Ops.Reset()
|
||||
@@ -178,6 +184,93 @@ func TestListPosition(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestListGap(t *testing.T) {
|
||||
gtx := Context{
|
||||
Ops: new(op.Ops),
|
||||
Constraints: Constraints{
|
||||
Max: image.Pt(100, 20),
|
||||
},
|
||||
}
|
||||
|
||||
// Two 10px children with 5px gap: total 25px.
|
||||
l := List{Gap: 5}
|
||||
dims := l.Layout(gtx, 2, func(gtx Context, idx int) Dimensions {
|
||||
return Dimensions{Size: image.Pt(10, 10)}
|
||||
})
|
||||
if got, exp := dims.Size.X, 25; got != exp {
|
||||
t.Errorf("two children with gap: got width %d, expected %d", got, exp)
|
||||
}
|
||||
|
||||
// Three 10px children with 5px gap: total 40px.
|
||||
l = List{Gap: 5}
|
||||
dims = l.Layout(gtx, 3, func(gtx Context, idx int) Dimensions {
|
||||
return Dimensions{Size: image.Pt(10, 10)}
|
||||
})
|
||||
if got, exp := dims.Size.X, 40; got != exp {
|
||||
t.Errorf("three children with gap: got width %d, expected %d", got, exp)
|
||||
}
|
||||
|
||||
// Single child: no gap.
|
||||
l = List{Gap: 5}
|
||||
dims = l.Layout(gtx, 1, func(gtx Context, idx int) Dimensions {
|
||||
return Dimensions{Size: image.Pt(10, 10)}
|
||||
})
|
||||
if got, exp := dims.Size.X, 10; got != exp {
|
||||
t.Errorf("single child with gap: got width %d, expected %d", got, exp)
|
||||
}
|
||||
|
||||
// Zero children: no gap.
|
||||
l = List{Gap: 5}
|
||||
dims = l.Layout(gtx, 0, nil)
|
||||
if got, exp := dims.Size.X, 0; got != exp {
|
||||
t.Errorf("no children with gap: got width %d, expected %d", got, exp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListGapVertical(t *testing.T) {
|
||||
gtx := Context{
|
||||
Ops: new(op.Ops),
|
||||
Constraints: Constraints{
|
||||
Max: image.Pt(20, 100),
|
||||
},
|
||||
}
|
||||
|
||||
l := List{Axis: Vertical, Gap: 10}
|
||||
dims := l.Layout(gtx, 3, func(gtx Context, idx int) Dimensions {
|
||||
return Dimensions{Size: image.Pt(10, 15)}
|
||||
})
|
||||
// 3*15 + 2*10 = 65.
|
||||
if got, exp := dims.Size.Y, 65; got != exp {
|
||||
t.Errorf("vertical list with gap: got height %d, expected %d", got, exp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListGapPosition(t *testing.T) {
|
||||
gtx := Context{
|
||||
Ops: new(op.Ops),
|
||||
Constraints: Constraints{
|
||||
Max: image.Pt(30, 20),
|
||||
},
|
||||
}
|
||||
|
||||
// Viewport 30px, 5 children of 10px with 5px gap.
|
||||
// Children fill: 10, 10+5+10=25, 25+5+10=40 >= 30, so 3 visible (last partially).
|
||||
l := List{Gap: 5}
|
||||
l.Layout(gtx, 5, func(gtx Context, idx int) Dimensions {
|
||||
return Dimensions{Size: image.Pt(10, 10)}
|
||||
})
|
||||
if got, exp := l.Position.Count, 3; got != exp {
|
||||
t.Errorf("visible count with gap: got %d, expected %d", got, exp)
|
||||
}
|
||||
if got, exp := l.Position.First, 0; got != exp {
|
||||
t.Errorf("first with gap: got %d, expected %d", got, exp)
|
||||
}
|
||||
// OffsetLast = mainMax - size = 30 - 40 = -10.
|
||||
if got, exp := l.Position.OffsetLast, -10; got != exp {
|
||||
t.Errorf("offset last with gap: got %d, expected %d", got, exp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtraChildren(t *testing.T) {
|
||||
var l List
|
||||
l.Position.First = 1
|
||||
|
||||
+22
-12
@@ -20,10 +20,6 @@ type Stack struct {
|
||||
type StackChild struct {
|
||||
expanded bool
|
||||
widget Widget
|
||||
|
||||
// Scratch space.
|
||||
call op.CallOp
|
||||
dims Dimensions
|
||||
}
|
||||
|
||||
// Stacked returns a Stack child that is laid out with no minimum
|
||||
@@ -52,6 +48,20 @@ func (s Stack) Layout(gtx Context, children ...StackChild) Dimensions {
|
||||
// First lay out Stacked children.
|
||||
cgtx := gtx
|
||||
cgtx.Constraints.Min = image.Point{}
|
||||
// Note: previously the scratch space was inside StackChild.
|
||||
// child.call.Add(gtx.Ops) confused the go escape analysis and caused the
|
||||
// entired children slice to be allocated on the heap, including all widgets
|
||||
// in it. This produced a lot of object allocations. Now the scratch space
|
||||
// is separate from children, and for cases len(children) <= 32, we will
|
||||
// allocate the scratch space on the stack. For cases len(children) > 32,
|
||||
// only the scratch space gets allocated from the heap, during append.
|
||||
type scratchSpace struct {
|
||||
call op.CallOp
|
||||
dims Dimensions
|
||||
}
|
||||
var scratchArray [32]scratchSpace
|
||||
scratch := scratchArray[:0]
|
||||
scratch = append(scratch, make([]scratchSpace, len(children))...)
|
||||
for i, w := range children {
|
||||
if w.expanded {
|
||||
continue
|
||||
@@ -65,8 +75,8 @@ func (s Stack) Layout(gtx Context, children ...StackChild) Dimensions {
|
||||
if h := dims.Size.Y; h > maxSZ.Y {
|
||||
maxSZ.Y = h
|
||||
}
|
||||
children[i].call = call
|
||||
children[i].dims = dims
|
||||
scratch[i].call = call
|
||||
scratch[i].dims = dims
|
||||
}
|
||||
// Then lay out Expanded children.
|
||||
for i, w := range children {
|
||||
@@ -83,14 +93,14 @@ func (s Stack) Layout(gtx Context, children ...StackChild) Dimensions {
|
||||
if h := dims.Size.Y; h > maxSZ.Y {
|
||||
maxSZ.Y = h
|
||||
}
|
||||
children[i].call = call
|
||||
children[i].dims = dims
|
||||
scratch[i].call = call
|
||||
scratch[i].dims = dims
|
||||
}
|
||||
|
||||
maxSZ = gtx.Constraints.Constrain(maxSZ)
|
||||
var baseline int
|
||||
for _, ch := range children {
|
||||
sz := ch.dims.Size
|
||||
for _, scratchChild := range scratch {
|
||||
sz := scratchChild.dims.Size
|
||||
var p image.Point
|
||||
switch s.Alignment {
|
||||
case N, S, Center:
|
||||
@@ -105,10 +115,10 @@ func (s Stack) Layout(gtx Context, children ...StackChild) Dimensions {
|
||||
p.Y = maxSZ.Y - sz.Y
|
||||
}
|
||||
trans := op.Offset(p).Push(gtx.Ops)
|
||||
ch.call.Add(gtx.Ops)
|
||||
scratchChild.call.Add(gtx.Ops)
|
||||
trans.Pop()
|
||||
if baseline == 0 {
|
||||
if b := ch.dims.Baseline; b != 0 {
|
||||
if b := scratchChild.dims.Baseline; b != 0 {
|
||||
baseline = b + maxSZ.Y - sz.Y - p.Y
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,8 @@ func BenchmarkStack(b *testing.B) {
|
||||
},
|
||||
}
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
gtx.Ops.Reset()
|
||||
|
||||
Stack{}.Layout(gtx,
|
||||
@@ -41,9 +40,8 @@ func BenchmarkBackground(b *testing.B) {
|
||||
},
|
||||
}
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
gtx.Ops.Reset()
|
||||
|
||||
Background{}.Layout(gtx,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user