all: rename the gioui.org/ui module to gioui.org

The "ui" is redundant and stutters.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2019-09-30 12:27:55 +02:00
parent ce74bc0cba
commit 22cd88df9f
102 changed files with 93 additions and 93 deletions
+58
View File
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: Unlicense OR MIT
package org.gioui;
import android.app.Activity;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
public class GioActivity extends Activity {
private GioView view;
@Override public void onCreate(Bundle state) {
super.onCreate(state);
Window w = getWindow();
this.view = new GioView(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
this.view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
this.view.setLayoutParams(new WindowManager.LayoutParams(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT));
setContentView(view);
}
@Override public void onDestroy() {
view.destroy();
super.onDestroy();
}
@Override public void onStart() {
super.onStart();
view.start();
}
@Override public void onStop() {
view.stop();
super.onStop();
}
@Override public void onConfigurationChanged(Configuration c) {
super.onConfigurationChanged(c);
view.configurationChanged();
}
@Override public void onLowMemory() {
super.onLowMemory();
view.lowMemory();
}
@Override public void onBackPressed() {
if (!view.backPressed())
super.onBackPressed();
}
}
+238
View File
@@ -0,0 +1,238 @@
// SPDX-License-Identifier: Unlicense OR MIT
package org.gioui;
import android.content.Context;
import android.graphics.Rect;
import android.os.Build;
import android.os.Handler;
import android.util.AttributeSet;
import android.text.Editable;
import android.view.Choreographer;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowInsets;
import android.view.Surface;
import android.view.SurfaceView;
import android.view.SurfaceHolder;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.EditorInfo;
import java.io.UnsupportedEncodingException;
public class GioView extends SurfaceView implements Choreographer.FrameCallback {
private final static Object initLock = new Object();
private static boolean jniLoaded;
private final SurfaceHolder.Callback callbacks;
private final InputMethodManager imm;
private final Handler handler;
private long nhandle;
private static synchronized void initialize(Context appCtx) {
synchronized (initLock) {
if (jniLoaded) {
return;
}
String dataDir = appCtx.getFilesDir().getAbsolutePath();
byte[] dataDirUTF8;
try {
dataDirUTF8 = dataDir.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
System.loadLibrary("gio");
runGoMain(dataDirUTF8);
jniLoaded = true;
}
}
public GioView(Context context) {
this(context, null);
}
public GioView(Context context, AttributeSet attrs) {
super(context, attrs);
// Late initialization of the Go runtime to wait for a valid context.
initialize(context.getApplicationContext());
nhandle = onCreateView(this);
handler = new Handler();
imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
setFocusable(true);
setFocusableInTouchMode(true);
setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override public void onFocusChange(View v, boolean focus) {
GioView.this.onFocusChange(nhandle, focus);
}
});
callbacks = new SurfaceHolder.Callback() {
@Override public void surfaceCreated(SurfaceHolder holder) {
// Ignore; surfaceChanged is guaranteed to be called immediately after this.
}
@Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
onSurfaceChanged(nhandle, getHolder().getSurface());
}
@Override public void surfaceDestroyed(SurfaceHolder holder) {
onSurfaceDestroyed(nhandle);
}
};
getHolder().addCallback(callbacks);
}
@Override public boolean onKeyDown(int keyCode, KeyEvent event) {
onKeyEvent(nhandle, keyCode, event.getUnicodeChar(), event.getEventTime());
return false;
}
@Override public boolean onTouchEvent(MotionEvent event) {
// Ask for unbuffered events. Flutter and Chrome does it
// so I assume its good for us as well.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
requestUnbufferedDispatch(event);
}
for (int j = 0; j < event.getHistorySize(); j++) {
long time = event.getHistoricalEventTime(j);
for (int i = 0; i < event.getPointerCount(); i++) {
onTouchEvent(
nhandle,
event.ACTION_MOVE,
event.getPointerId(i),
event.getToolType(i),
event.getHistoricalX(i, j),
event.getHistoricalY(i, j),
time);
}
}
int act = event.getActionMasked();
int idx = event.getActionIndex();
for (int i = 0; i < event.getPointerCount(); i++) {
int pact = event.ACTION_MOVE;
if (i == idx) {
pact = act;
}
onTouchEvent(
nhandle,
act,
event.getPointerId(i),
event.getToolType(i),
event.getX(i),
event.getY(i),
event.getEventTime());
}
return true;
}
@Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
return new InputConnection(this);
}
void showTextInput() {
post(new Runnable() {
@Override public void run() {
GioView.this.requestFocus();
imm.showSoftInput(GioView.this, 0);
}
});
}
void hideTextInput() {
post(new Runnable() {
@Override public void run() {
imm.hideSoftInputFromWindow(getWindowToken(), 0);
}
});
}
void postFrameCallbackOnMainThread() {
handler.post(new Runnable() {
@Override public void run() {
postFrameCallback();
}
});
}
@Override protected boolean fitSystemWindows(Rect insets) {
onWindowInsets(nhandle, insets.top, insets.right, insets.bottom, insets.left);
return true;
}
void postFrameCallback() {
Choreographer.getInstance().removeFrameCallback(this);
Choreographer.getInstance().postFrameCallback(this);
}
@Override public void doFrame(long nanos) {
onFrameCallback(nhandle, nanos);
}
int getDensity() {
return getResources().getDisplayMetrics().densityDpi;
}
float getFontScale() {
return getResources().getConfiguration().fontScale;
}
void start() {
onStartView(nhandle);
}
void stop() {
onStopView(nhandle);
}
void destroy() {
getHolder().removeCallback(callbacks);
onDestroyView(nhandle);
nhandle = 0;
}
void configurationChanged() {
onConfigurationChanged(nhandle);
}
void lowMemory() {
onLowMemory();
}
boolean backPressed() {
return onBack(nhandle);
}
static private native long onCreateView(GioView view);
static private native void onDestroyView(long handle);
static private native void onStartView(long handle);
static private native void onStopView(long handle);
static private native void onSurfaceDestroyed(long handle);
static private native void onSurfaceChanged(long handle, Surface surface);
static private native void onConfigurationChanged(long handle);
static private native void onWindowInsets(long handle, int top, int right, int bottom, int left);
static private native void onLowMemory();
static private native void onTouchEvent(long handle, int action, int pointerID, int tool, float x, float y, long time);
static private native void onKeyEvent(long handle, int code, int character, long time);
static private native void onFrameCallback(long handle, long nanos);
static private native boolean onBack(long handle);
static private native void onFocusChange(long handle, boolean focus);
static private native void runGoMain(byte[] dataDir);
private static class InputConnection extends BaseInputConnection {
private final Editable editable;
InputConnection(View view) {
// Passing false enables "dummy mode", where the BaseInputConnection
// attempts to convert IME operations to key events.
super(view, false);
editable = Editable.Factory.getInstance().newEditable("");
}
@Override public Editable getEditable() {
return editable;
}
}
}
+206
View File
@@ -0,0 +1,206 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
import (
"errors"
"image"
"math"
"os"
"strings"
"time"
"gioui.org/ui"
)
// An UpdateEvent is generated when a Window's Update
// method must be called.
type UpdateEvent struct {
Config Config
// Size is the dimensions of the window.
Size image.Point
// Insets is the insets to apply.
Insets Insets
// Whether this draw is system generated
// and needs a complete frame before
// proceeding.
sync bool
}
// DestroyEvent is the last event sent through
// a window event channel.
type DestroyEvent struct {
// Err is nil for normal window closures. If a
// window is prematurely closed, Err is the cause.
Err error
}
// Insets is the space taken up by
// system decoration such as translucent
// system bars and software keyboards.
type Insets struct {
Top, Bottom, Left, Right ui.Value
}
// A StageEvent is generated whenever the stage of a
// Window changes.
type StageEvent struct {
Stage Stage
}
// CommandEvent is a system event.
type CommandEvent struct {
Type CommandType
// Suppress the default action of the command.
Cancel bool
}
// Stage of a Window.
type Stage uint8
// CommandType is the type of a CommandEvent.
type CommandType uint8
type windowRendezvous struct {
in chan windowAndOptions
out chan windowAndOptions
errs chan error
}
type windowAndOptions struct {
window *Window
opts *windowOptions
}
const (
// StagePaused is the Stage for inactive Windows.
// Inactive Windows don't receive UpdateEvents.
StagePaused Stage = iota
// StateRunning is for active Windows.
StageRunning
)
const (
// CommandBack is the command for a back action
// such as the Android back button.
CommandBack CommandType = iota
)
const (
inchPrDp = 1.0 / 160
mmPrDp = 25.4 / 160
// monitorScale is the extra scale applied to
// monitor outputs to compensate for the extra
// viewing distance compared to phone and tables.
monitorScale = 1.20
// minDensity is the minimum pixels per dp to
// ensure font and ui legibility on low-dpi
// screens.
minDensity = 1.25
)
// extraArgs contains extra arguments to append to
// os.Args. The arguments are separated with |.
// Useful for running programs on mobiles where the
// command line is not available.
// Set with the go linker flag -X.
var extraArgs string
func (l Stage) String() string {
switch l {
case StagePaused:
return "StagePaused"
case StageRunning:
return "StageRunning"
default:
panic("unexpected Stage value")
}
}
func init() {
if extraArgs != "" {
args := strings.Split(extraArgs, "|")
os.Args = append(os.Args, args...)
}
}
// DataDir returns a path to use for application-specific
// configuration data.
// On desktop systems, DataDir use os.UserConfigDir.
// On iOS NSDocumentDirectory is queried.
// For Android Context.getFilesDir is used.
//
// BUG: DataDir blocks on Android until init functions
// have completed.
func DataDir() (string, error) {
return dataDir()
}
// Main must be called from the a program's main function. It
// blocks until there are no more windows active.
//
// Calling Main is necessary because some operating systems
// require control of the main thread of the program for
// running windows.
func Main() {
main()
}
// Config implements the ui.Config interface.
type Config struct {
// Device pixels per dp.
pxPerDp float32
// Device pixels per sp.
pxPerSp float32
now time.Time
}
func (c *Config) Now() time.Time {
return c.now
}
func (c *Config) Px(v ui.Value) int {
var r float32
switch v.U {
case ui.UnitPx:
r = v.V
case ui.UnitDp:
r = c.pxPerDp * v.V
case ui.UnitSp:
r = c.pxPerSp * v.V
default:
panic("unknown unit")
}
return int(math.Round(float64(r)))
}
func newWindowRendezvous() *windowRendezvous {
wr := &windowRendezvous{
in: make(chan windowAndOptions),
out: make(chan windowAndOptions),
errs: make(chan error),
}
go func() {
var main windowAndOptions
var out chan windowAndOptions
for {
select {
case w := <-wr.in:
var err error
if main.window != nil {
err = errors.New("multiple windows are not supported")
}
wr.errs <- err
main = w
out = wr.out
case out <- main:
}
}
}()
return wr
}
func (_ UpdateEvent) ImplementsEvent() {}
func (_ StageEvent) ImplementsEvent() {}
func (_ *CommandEvent) ImplementsEvent() {}
func (_ DestroyEvent) ImplementsEvent() {}
+11
View File
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build !android,go1.13
package app
import "os"
func dataDir() (string, error) {
return os.UserConfigDir()
}
+25
View File
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build android
package app
import "C"
import "sync"
var (
dataDirOnce sync.Once
dataDirChan = make(chan string, 1)
dataPath string
)
func dataDir() (string, error) {
dataDirOnce.Do(func() {
dataPath = <-dataDirChan
})
return dataPath, nil
}
func setDataDir(dir string) {
dataDirChan <- dir
}
+12
View File
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build !android,!go1.13
package app
import "os"
func dataDir() (string, error) {
// Use a quick workaround until we can require go 1.13.
return os.UserHomeDir()
}
+65
View File
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: Unlicense OR MIT
/*
Package app provides a platform-independent interface to operating system
functionality for running graphical user interfaces.
Windows
Create a new Window by calling NewWindow. On mobile platforms or when Gio
is embedded in another project, NewWindow merely connects with a previously
created window.
A Window is run by receiving events from its Events channel. The most
important event is UpdateEvent that prompts an update of the window
contents and state.
For example:
import "gioui.org/ui"
w := app.NewWindow()
for e := range w.Events() {
if e, ok := e.(app.UpdateEvent); ok {
ops.Reset()
// Add operations to ops.
...
// Completely replace the window contents and state.
w.Update(ops)
}
}
A program must keep receiving events from the event channel until
DestroyEvent is received.
Main
The Main function must be called from a programs main function, to hand over
control of the main thread to operating systems that need it.
Because Main is also blocking, the event loop of a Window must run in a goroutine.
For example, to display a blank but otherwise functional window:
package main
import "gioui.org/app"
func main() {
go func() {
w := app.NewWindow()
for range w.Events() {
}
}()
app.Main()
}
Event queue
A Window's Queue method returns an ui.Queue implementation that distributes
incoming events to the event handlers declared in the latest call to Update.
See the gioui.org/ui package for more information about event handlers.
*/
package app
+281
View File
@@ -0,0 +1,281 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build linux windows
package app
import (
"errors"
"fmt"
"runtime"
"strings"
"gioui.org/app/internal/gl"
)
type context struct {
c *gl.Functions
driver *window
eglCtx *eglContext
nwindow _EGLNativeWindowType
eglWin *eglWindow
eglSurf _EGLSurface
width, height int
// For sRGB emulation.
srgbFBO *gl.SRGBFBO
}
type eglContext struct {
disp _EGLDisplay
config _EGLConfig
ctx _EGLContext
visualID int
srgb bool
}
var (
nilEGLSurface _EGLSurface
nilEGLContext _EGLContext
nilEGLConfig _EGLConfig
nilEGLNativeWindowType _EGLNativeWindowType
)
const (
_EGL_ALPHA_SIZE = 0x3021
_EGL_BLUE_SIZE = 0x3022
_EGL_CONFIG_CAVEAT = 0x3027
_EGL_CONTEXT_CLIENT_VERSION = 0x3098
_EGL_DEPTH_SIZE = 0x3025
_EGL_GL_COLORSPACE_KHR = 0x309d
_EGL_GL_COLORSPACE_SRGB_KHR = 0x3089
_EGL_GREEN_SIZE = 0x3023
_EGL_EXTENSIONS = 0x3055
_EGL_NATIVE_VISUAL_ID = 0x302e
_EGL_NONE = 0x3038
_EGL_OPENGL_ES2_BIT = 0x4
_EGL_RED_SIZE = 0x3024
_EGL_RENDERABLE_TYPE = 0x3040
_EGL_SURFACE_TYPE = 0x3033
_EGL_WINDOW_BIT = 0x4
)
func (c *context) Release() {
if c.srgbFBO != nil {
c.srgbFBO.Release()
}
if c.eglSurf != nilEGLSurface {
eglMakeCurrent(c.eglCtx.disp, nilEGLSurface, nilEGLSurface, nilEGLContext)
eglDestroySurface(c.eglCtx.disp, c.eglSurf)
c.eglSurf = nilEGLSurface
}
if c.eglWin != nil {
c.eglWin.destroy()
c.eglWin = nil
}
if c.eglCtx != nil {
eglDestroyContext(c.eglCtx.disp, c.eglCtx.ctx)
eglTerminate(c.eglCtx.disp)
eglReleaseThread()
c.eglCtx = nil
}
c.driver = nil
}
func (c *context) Present() error {
if c.eglWin == nil {
panic("context is not active")
}
if c.srgbFBO != nil {
c.srgbFBO.Blit()
}
if !eglSwapBuffers(c.eglCtx.disp, c.eglSurf) {
return fmt.Errorf("eglSwapBuffers failed (%x)", eglGetError())
}
if c.srgbFBO != nil {
c.srgbFBO.AfterPresent()
}
return nil
}
func newContext(w *window) (*context, error) {
eglCtx, err := createContext(_EGLNativeDisplayType(w.display()))
if err != nil {
return nil, err
}
c := &context{
driver: w,
eglCtx: eglCtx,
c: new(gl.Functions),
}
return c, nil
}
func (c *context) Functions() *gl.Functions {
return c.c
}
func (c *context) Lock() {}
func (c *context) Unlock() {}
func (c *context) MakeCurrent() error {
w, width, height := c.driver.nativeWindow(int(c.eglCtx.visualID))
win := _EGLNativeWindowType(w)
if c.nwindow == win && width == c.width && height == c.height {
return nil
}
if win == nilEGLNativeWindowType {
if c.srgbFBO != nil {
c.srgbFBO.Release()
c.srgbFBO = nil
}
}
if c.eglSurf != nilEGLSurface {
// Make sure any in-flight GL commands are complete.
c.c.Finish()
eglMakeCurrent(c.eglCtx.disp, nilEGLSurface, nilEGLSurface, nilEGLContext)
eglDestroySurface(c.eglCtx.disp, c.eglSurf)
c.eglSurf = nilEGLSurface
}
c.width, c.height = width, height
c.nwindow = win
if c.nwindow == nilEGLNativeWindowType {
if c.eglWin != nil {
c.eglWin.destroy()
c.eglWin = nil
}
return nil
}
if c.eglWin == nil {
var err error
c.eglWin, err = newEGLWindow(win, width, height)
if err != nil {
return err
}
} else {
c.eglWin.resize(width, height)
}
eglSurf, err := createSurfaceAndMakeCurrent(c.eglCtx, c.eglWin.window())
c.eglSurf = eglSurf
if err != nil {
c.eglWin.destroy()
c.eglWin = nil
c.nwindow = nilEGLNativeWindowType
return err
}
if c.eglCtx.srgb {
return nil
}
if c.srgbFBO == nil {
var err error
c.srgbFBO, err = gl.NewSRGBFBO(c.c)
if err != nil {
c.Release()
return err
}
}
if err := c.srgbFBO.Refresh(c.width, c.height); err != nil {
c.Release()
return err
}
return nil
}
func hasExtension(exts []string, ext string) bool {
for _, e := range exts {
if ext == e {
return true
}
}
return false
}
func createContext(disp _EGLNativeDisplayType) (*eglContext, error) {
eglDisp := eglGetDisplay(disp)
if eglDisp == 0 {
return nil, fmt.Errorf("eglGetDisplay(_EGL_DEFAULT_DISPLAY) failed: 0x%x", eglGetError())
}
major, minor, ret := eglInitialize(eglDisp)
if !ret {
return nil, fmt.Errorf("eglInitialize failed: 0x%x", eglGetError())
}
// sRGB framebuffer support on EGL 1.5 or if EGL_KHR_gl_colorspace is supported.
exts := strings.Split(eglQueryString(eglDisp, _EGL_EXTENSIONS), " ")
srgb := major > 1 || minor >= 5 || hasExtension(exts, "EGL_KHR_gl_colorspace")
attribs := []_EGLint{
_EGL_RENDERABLE_TYPE, _EGL_OPENGL_ES2_BIT,
_EGL_SURFACE_TYPE, _EGL_WINDOW_BIT,
_EGL_BLUE_SIZE, 8,
_EGL_GREEN_SIZE, 8,
_EGL_RED_SIZE, 8,
_EGL_CONFIG_CAVEAT, _EGL_NONE,
}
if srgb {
if runtime.GOOS == "linux" {
// Some Mesa drivers crash if an sRGB framebuffer is requested without alpha.
// https://bugs.freedesktop.org/show_bug.cgi?id=107782.
attribs = append(attribs, _EGL_ALPHA_SIZE, 1)
}
// Only request a depth buffer if we're going to render directly to the framebuffer.
attribs = append(attribs, _EGL_DEPTH_SIZE, 16)
}
attribs = append(attribs, _EGL_NONE)
eglCfg, ret := eglChooseConfig(eglDisp, attribs)
if !ret {
return nil, fmt.Errorf("eglChooseConfig failed: 0x%x", eglGetError())
}
if eglCfg == nilEGLConfig {
return nil, errors.New("eglChooseConfig returned 0 configs")
}
var eglCtx _EGLContext
ctxAttribs := []_EGLint{
_EGL_CONTEXT_CLIENT_VERSION, 3,
_EGL_NONE,
}
eglCtx = eglCreateContext(eglDisp, eglCfg, nilEGLContext, ctxAttribs)
if eglCtx == nilEGLContext {
return nil, fmt.Errorf("eglCreateContext failed: 0x%x", eglGetError())
}
visID, ret := eglGetConfigAttrib(eglDisp, eglCfg, _EGL_NATIVE_VISUAL_ID)
if !ret {
return nil, errors.New("newContext: eglGetConfigAttrib for _EGL_NATIVE_VISUAL_ID failed")
}
return &eglContext{
disp: eglDisp,
config: _EGLConfig(eglCfg),
ctx: _EGLContext(eglCtx),
visualID: int(visID),
srgb: srgb,
}, nil
}
func createSurfaceAndMakeCurrent(eglCtx *eglContext, win _EGLNativeWindowType) (_EGLSurface, error) {
var surfAttribs []_EGLint
if eglCtx.srgb {
surfAttribs = append(surfAttribs, _EGL_GL_COLORSPACE_KHR, _EGL_GL_COLORSPACE_SRGB_KHR)
}
surfAttribs = append(surfAttribs, _EGL_NONE)
eglSurf := eglCreateWindowSurface(eglCtx.disp, eglCtx.config, win, surfAttribs)
if eglSurf == nilEGLSurface && eglCtx.srgb {
// Try again without sRGB
eglCtx.srgb = false
surfAttribs = []_EGLint{_EGL_NONE}
eglSurf = eglCreateWindowSurface(eglCtx.disp, eglCtx.config, win, surfAttribs)
}
if eglSurf == nilEGLSurface {
return nilEGLSurface, fmt.Errorf("newContext: eglCreateWindowSurface failed 0x%x (sRGB=%v)", eglGetError(), eglCtx.srgb)
}
if !eglMakeCurrent(eglCtx.disp, eglSurf, eglSurf, eglCtx.ctx) {
eglDestroySurface(eglCtx.disp, eglSurf)
return nilEGLSurface, fmt.Errorf("eglMakeCurrent error 0x%x", eglGetError())
}
// eglSwapInterval 1 leads to erratic frame rates and unnecessary blocking.
// We rely on platform specific frame rate limiting instead, except on Windows
// where eglSwapInterval is all there is.
if runtime.GOOS != "windows" {
eglSwapInterval(eglCtx.disp, 0)
} else {
eglSwapInterval(eglCtx.disp, 1)
}
return eglSurf, nil
}
+22
View File
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
/*
#include <EGL/egl.h>
*/
import "C"
type (
_EGLNativeDisplayType = C.EGLNativeDisplayType
_EGLNativeWindowType = C.EGLNativeWindowType
)
func eglGetDisplay(disp _EGLNativeDisplayType) _EGLDisplay {
return C.eglGetDisplay(disp)
}
func eglCreateWindowSurface(disp _EGLDisplay, conf _EGLConfig, win _EGLNativeWindowType, attribs []_EGLint) _EGLSurface {
eglSurf := C.eglCreateWindowSurface(disp, conf, win, &attribs[0])
return eglSurf
}
+83
View File
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
/*
#cgo LDFLAGS: -lEGL
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <GLES3/gl3.h>
*/
import "C"
type (
_EGLint = C.EGLint
_EGLDisplay = C.EGLDisplay
_EGLConfig = C.EGLConfig
_EGLContext = C.EGLContext
_EGLSurface = C.EGLSurface
)
func eglChooseConfig(disp _EGLDisplay, attribs []_EGLint) (_EGLConfig, bool) {
var cfg C.EGLConfig
var ncfg C.EGLint
if C.eglChooseConfig(disp, &attribs[0], &cfg, 1, &ncfg) != C.EGL_TRUE {
return nil, false
}
return _EGLConfig(cfg), true
}
func eglCreateContext(disp _EGLDisplay, cfg _EGLConfig, shareCtx _EGLContext, attribs []_EGLint) _EGLContext {
ctx := C.eglCreateContext(disp, cfg, shareCtx, &attribs[0])
return _EGLContext(ctx)
}
func eglDestroySurface(disp _EGLDisplay, surf _EGLSurface) bool {
return C.eglDestroySurface(disp, surf) == C.EGL_TRUE
}
func eglDestroyContext(disp _EGLDisplay, ctx _EGLContext) bool {
return C.eglDestroyContext(disp, ctx) == C.EGL_TRUE
}
func eglGetConfigAttrib(disp _EGLDisplay, cfg _EGLConfig, attr _EGLint) (_EGLint, bool) {
var val _EGLint
ret := C.eglGetConfigAttrib(disp, cfg, attr, &val)
return val, ret == C.EGL_TRUE
}
func eglGetError() _EGLint {
return C.eglGetError()
}
func eglInitialize(disp _EGLDisplay) (_EGLint, _EGLint, bool) {
var maj, min _EGLint
ret := C.eglInitialize(disp, &maj, &min)
return maj, min, ret == C.EGL_TRUE
}
func eglMakeCurrent(disp _EGLDisplay, draw, read _EGLSurface, ctx _EGLContext) bool {
return C.eglMakeCurrent(disp, draw, read, ctx) == C.EGL_TRUE
}
func eglReleaseThread() bool {
return C.eglReleaseThread() == C.EGL_TRUE
}
func eglSwapBuffers(disp _EGLDisplay, surf _EGLSurface) bool {
return C.eglSwapBuffers(disp, surf) == C.EGL_TRUE
}
func eglSwapInterval(disp _EGLDisplay, interval _EGLint) bool {
return C.eglSwapInterval(disp, interval) == C.EGL_TRUE
}
func eglTerminate(disp _EGLDisplay) bool {
return C.eglTerminate(disp) == C.EGL_TRUE
}
func eglQueryString(disp _EGLDisplay, name _EGLint) string {
return C.GoString(C.eglQueryString(disp, name))
}
+59
View File
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build linux,!android
package app
import (
"errors"
"unsafe"
)
/*
#cgo LDFLAGS: -lwayland-egl
#cgo CFLAGS: -DWL_EGL_PLATFORM
#include <wayland-client.h>
#include <wayland-egl.h>
#include <EGL/egl.h>
*/
import "C"
type (
_EGLNativeDisplayType = C.EGLNativeDisplayType
_EGLNativeWindowType = C.EGLNativeWindowType
)
type eglWindow struct {
w *C.struct_wl_egl_window
}
func newEGLWindow(w _EGLNativeWindowType, width, height int) (*eglWindow, error) {
surf := (*C.struct_wl_surface)(unsafe.Pointer(w))
win := C.wl_egl_window_create(surf, C.int(width), C.int(height))
if win == nil {
return nil, errors.New("wl_egl_create_window failed")
}
return &eglWindow{win}, nil
}
func (w *eglWindow) window() _EGLNativeWindowType {
return w.w
}
func (w *eglWindow) resize(width, height int) {
C.wl_egl_window_resize(w.w, C.int(width), C.int(height), 0, 0)
}
func (w *eglWindow) destroy() {
C.wl_egl_window_destroy(w.w)
}
func eglGetDisplay(disp _EGLNativeDisplayType) _EGLDisplay {
return C.eglGetDisplay(disp)
}
func eglCreateWindowSurface(disp _EGLDisplay, conf _EGLConfig, win _EGLNativeWindowType, attribs []_EGLint) _EGLSurface {
eglSurf := C.eglCreateWindowSurface(disp, conf, win, &attribs[0])
return eglSurf
}
+20
View File
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build android windows
package app
type eglWindow struct {
w _EGLNativeWindowType
}
func newEGLWindow(w _EGLNativeWindowType, width, height int) (*eglWindow, error) {
return &eglWindow{w}, nil
}
func (w *eglWindow) window() _EGLNativeWindowType {
return w.w
}
func (w *eglWindow) resize(width, height int) {}
func (w *eglWindow) destroy() {}
+144
View File
@@ -0,0 +1,144 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
import (
"os"
"unsafe"
syscall "golang.org/x/sys/windows"
"gioui.org/app/internal/gl"
)
type (
_EGLint int32
_EGLDisplay uintptr
_EGLConfig uintptr
_EGLContext uintptr
_EGLSurface uintptr
_EGLNativeDisplayType uintptr
_EGLNativeWindowType uintptr
)
var (
libEGL = syscall.NewLazyDLL("libEGL.dll")
_eglChooseConfig = libEGL.NewProc("eglChooseConfig")
_eglCreateContext = libEGL.NewProc("eglCreateContext")
_eglCreateWindowSurface = libEGL.NewProc("eglCreateWindowSurface")
_eglDestroyContext = libEGL.NewProc("eglDestroyContext")
_eglDestroySurface = libEGL.NewProc("eglDestroySurface")
_eglGetConfigAttrib = libEGL.NewProc("eglGetConfigAttrib")
_eglGetDisplay = libEGL.NewProc("eglGetDisplay")
_eglGetError = libEGL.NewProc("eglGetError")
_eglInitialize = libEGL.NewProc("eglInitialize")
_eglMakeCurrent = libEGL.NewProc("eglMakeCurrent")
_eglReleaseThread = libEGL.NewProc("eglReleaseThread")
_eglSwapInterval = libEGL.NewProc("eglSwapInterval")
_eglSwapBuffers = libEGL.NewProc("eglSwapBuffers")
_eglTerminate = libEGL.NewProc("eglTerminate")
_eglQueryString = libEGL.NewProc("eglQueryString")
)
func init() {
mustLoadDLL(libEGL, "libEGL.dll")
mustLoadDLL(gl.LibGLESv2, "libGLESv2.dll")
// d3dcompiler_47.dll is needed internally for shader compilation to function.
mustLoadDLL(syscall.NewLazyDLL("d3dcompiler_47.dll"), "d3dcompiler_47.dll")
}
func mustLoadDLL(dll *syscall.LazyDLL, name string) {
loadErr := dll.Load()
if loadErr == nil {
return
}
pmsg := syscall.StringToUTF16Ptr("Failed to load " + name)
ptitle := syscall.StringToUTF16Ptr("Error")
syscall.MessageBox(0 /* HWND */, pmsg, ptitle, syscall.MB_ICONERROR|syscall.MB_SYSTEMMODAL)
os.Exit(1)
}
func eglChooseConfig(disp _EGLDisplay, attribs []_EGLint) (_EGLConfig, bool) {
var cfg _EGLConfig
var ncfg _EGLint
a := &attribs[0]
r, _, _ := _eglChooseConfig.Call(uintptr(disp), uintptr(unsafe.Pointer(a)), uintptr(unsafe.Pointer(&cfg)), 1, uintptr(unsafe.Pointer(&ncfg)))
issue34474KeepAlive(a)
return cfg, r != 0
}
func eglCreateContext(disp _EGLDisplay, cfg _EGLConfig, shareCtx _EGLContext, attribs []_EGLint) _EGLContext {
a := &attribs[0]
c, _, _ := _eglCreateContext.Call(uintptr(disp), uintptr(cfg), uintptr(shareCtx), uintptr(unsafe.Pointer(a)))
issue34474KeepAlive(a)
return _EGLContext(c)
}
func eglCreateWindowSurface(disp _EGLDisplay, cfg _EGLConfig, win _EGLNativeWindowType, attribs []_EGLint) _EGLSurface {
a := &attribs[0]
s, _, _ := _eglCreateWindowSurface.Call(uintptr(disp), uintptr(cfg), uintptr(win), uintptr(unsafe.Pointer(a)))
issue34474KeepAlive(a)
return _EGLSurface(s)
}
func eglDestroySurface(disp _EGLDisplay, surf _EGLSurface) bool {
r, _, _ := _eglDestroySurface.Call(uintptr(disp), uintptr(surf))
return r != 0
}
func eglDestroyContext(disp _EGLDisplay, ctx _EGLContext) bool {
r, _, _ := _eglDestroyContext.Call(uintptr(disp), uintptr(ctx))
return r != 0
}
func eglGetConfigAttrib(disp _EGLDisplay, cfg _EGLConfig, attr _EGLint) (_EGLint, bool) {
var val uintptr
r, _, _ := _eglGetConfigAttrib.Call(uintptr(disp), uintptr(cfg), uintptr(attr), uintptr(unsafe.Pointer(&val)))
return _EGLint(val), r != 0
}
func eglGetDisplay(disp _EGLNativeDisplayType) _EGLDisplay {
d, _, _ := _eglGetDisplay.Call(uintptr(disp))
return _EGLDisplay(d)
}
func eglGetError() _EGLint {
e, _, _ := _eglGetError.Call()
return _EGLint(e)
}
func eglInitialize(disp _EGLDisplay) (_EGLint, _EGLint, bool) {
var maj, min uintptr
r, _, _ := _eglInitialize.Call(uintptr(disp), uintptr(unsafe.Pointer(&maj)), uintptr(unsafe.Pointer(&min)))
return _EGLint(maj), _EGLint(min), r != 0
}
func eglMakeCurrent(disp _EGLDisplay, draw, read _EGLSurface, ctx _EGLContext) bool {
r, _, _ := _eglMakeCurrent.Call(uintptr(disp), uintptr(draw), uintptr(read), uintptr(ctx))
return r != 0
}
func eglReleaseThread() bool {
r, _, _ := _eglReleaseThread.Call()
return r != 0
}
func eglSwapInterval(disp _EGLDisplay, interval _EGLint) bool {
r, _, _ := _eglSwapInterval.Call(uintptr(disp), uintptr(interval))
return r != 0
}
func eglSwapBuffers(disp _EGLDisplay, surf _EGLSurface) bool {
r, _, _ := _eglSwapBuffers.Call(uintptr(disp), uintptr(surf))
return r != 0
}
func eglTerminate(disp _EGLDisplay) bool {
r, _, _ := _eglTerminate.Call(uintptr(disp))
return r != 0
}
func eglQueryString(disp _EGLDisplay, name _EGLint) string {
r, _, _ := _eglQueryString.Call(uintptr(disp), uintptr(name))
return gl.GoString(gl.SliceOf(r))
}
+8
View File
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: Unlicense OR MIT
@import UIKit;
@interface GioAppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
+124
View File
@@ -0,0 +1,124 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build darwin,ios
package app
/*
#cgo CFLAGS: -fmodules -fobjc-arc -x objective-c
#include <CoreFoundation/CoreFoundation.h>
#include <OpenGLES/ES2/gl.h>
#include <OpenGLES/ES2/glext.h>
#include "gl_ios.h"
*/
import "C"
import (
"errors"
"fmt"
"gioui.org/app/internal/gl"
)
type context struct {
owner *window
c *gl.Functions
ctx C.CFTypeRef
layer C.CFTypeRef
init bool
frameBuffer gl.Framebuffer
colorBuffer, depthBuffer gl.Renderbuffer
}
func init() {
layerFactory = func() uintptr {
return uintptr(C.gio_createGLLayer())
}
}
func newContext(w *window) (*context, error) {
ctx := C.gio_createContext()
if ctx == 0 {
return nil, fmt.Errorf("failed to create EAGLContext")
}
c := &context{
ctx: ctx,
owner: w,
layer: C.CFTypeRef(w.contextLayer()),
c: new(gl.Functions),
}
return c, nil
}
func (c *context) Functions() *gl.Functions {
return c.c
}
func (c *context) Release() {
if c.ctx == 0 {
return
}
C.gio_renderbufferStorage(c.ctx, 0, C.GLenum(gl.RENDERBUFFER))
c.c.DeleteFramebuffer(c.frameBuffer)
c.c.DeleteRenderbuffer(c.colorBuffer)
c.c.DeleteRenderbuffer(c.depthBuffer)
C.gio_makeCurrent(0)
C.CFRelease(c.ctx)
c.ctx = 0
}
func (c *context) Present() error {
if c.layer == 0 {
panic("context is not active")
}
// Discard depth buffer as recommended in
// https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/WorkingwithEAGLContexts/WorkingwithEAGLContexts.html
c.c.BindFramebuffer(gl.FRAMEBUFFER, c.frameBuffer)
c.c.InvalidateFramebuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT)
c.c.BindRenderbuffer(gl.RENDERBUFFER, c.colorBuffer)
if C.gio_presentRenderbuffer(c.ctx, C.GLenum(gl.RENDERBUFFER)) == 0 {
return errors.New("presentRenderBuffer failed")
}
return nil
}
func (c *context) Lock() {}
func (c *context) Unlock() {}
func (c *context) MakeCurrent() error {
if C.gio_makeCurrent(c.ctx) == 0 {
C.CFRelease(c.ctx)
c.ctx = 0
return errors.New("[EAGLContext setCurrentContext] failed")
}
if !c.init {
c.init = true
c.frameBuffer = c.c.CreateFramebuffer()
c.colorBuffer = c.c.CreateRenderbuffer()
c.depthBuffer = c.c.CreateRenderbuffer()
}
if !c.owner.isVisible() {
// Make sure any in-flight GL commands are complete.
c.c.Finish()
return nil
}
currentRB := gl.Renderbuffer{uint(c.c.GetInteger(gl.RENDERBUFFER_BINDING))}
c.c.BindRenderbuffer(gl.RENDERBUFFER, c.colorBuffer)
if C.gio_renderbufferStorage(c.ctx, c.layer, C.GLenum(gl.RENDERBUFFER)) == 0 {
return errors.New("renderbufferStorage failed")
}
w := c.c.GetRenderbufferParameteri(gl.RENDERBUFFER, gl.RENDERBUFFER_WIDTH)
h := c.c.GetRenderbufferParameteri(gl.RENDERBUFFER, gl.RENDERBUFFER_HEIGHT)
c.c.BindRenderbuffer(gl.RENDERBUFFER, c.depthBuffer)
c.c.RenderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, w, h)
c.c.BindRenderbuffer(gl.RENDERBUFFER, currentRB)
c.c.BindFramebuffer(gl.FRAMEBUFFER, c.frameBuffer)
c.c.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, c.colorBuffer)
c.c.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, c.depthBuffer)
if st := c.c.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
return fmt.Errorf("framebuffer incomplete, status: %#x\n", st)
}
return nil
}
+7
View File
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: Unlicense OR MIT
__attribute__ ((visibility ("hidden"))) int gio_renderbufferStorage(CFTypeRef ctx, CFTypeRef layer, GLenum buffer);
__attribute__ ((visibility ("hidden"))) int gio_presentRenderbuffer(CFTypeRef ctx, GLenum buffer);
__attribute__ ((visibility ("hidden"))) int gio_makeCurrent(CFTypeRef ctx);
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createContext(void);
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createGLLayer(void);
+43
View File
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build darwin,ios
@import UIKit;
@import OpenGLES;
#include "gl_ios.h"
int gio_renderbufferStorage(CFTypeRef ctxRef, CFTypeRef layerRef, GLenum buffer) {
EAGLContext *ctx = (__bridge EAGLContext *)ctxRef;
CAEAGLLayer *layer = (__bridge CAEAGLLayer *)layerRef;
return (int)[ctx renderbufferStorage:buffer fromDrawable:layer];
}
int gio_presentRenderbuffer(CFTypeRef ctxRef, GLenum buffer) {
EAGLContext *ctx = (__bridge EAGLContext *)ctxRef;
return (int)[ctx presentRenderbuffer:buffer];
}
int gio_makeCurrent(CFTypeRef ctxRef) {
EAGLContext *ctx = (__bridge EAGLContext *)ctxRef;
return (int)[EAGLContext setCurrentContext:ctx];
}
CFTypeRef gio_createContext(void) {
EAGLContext *ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
if (ctx == nil) {
return nil;
}
return CFBridgingRetain(ctx);
}
CFTypeRef gio_createGLLayer(void) {
CAEAGLLayer *layer = [[CAEAGLLayer layer] init];
if (layer == nil) {
return nil;
}
layer.drawableProperties = @{kEAGLDrawablePropertyColorFormat: kEAGLColorFormatSRGBA8};
layer.opaque = YES;
layer.anchorPoint = CGPointMake(0, 0);
return CFBridgingRetain(layer);
}
+91
View File
@@ -0,0 +1,91 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
import (
"errors"
"syscall/js"
"gioui.org/app/internal/gl"
)
type context struct {
ctx js.Value
cnv js.Value
f *gl.Functions
srgbFBO *gl.SRGBFBO
}
func newContext(w *window) (*context, error) {
args := map[string]interface{}{
// Enable low latency rendering.
// See https://developers.google.com/web/updates/2019/05/desynchronized.
"desynchronized": true,
"preserveDrawingBuffer": true,
}
version := 2
ctx := w.cnv.Call("getContext", "webgl2", args)
if ctx == js.Null() {
version = 1
ctx = w.cnv.Call("getContext", "webgl", args)
}
if ctx == js.Null() {
return nil, errors.New("app: webgl is not supported")
}
f := &gl.Functions{Ctx: ctx}
if err := f.Init(version); err != nil {
return nil, err
}
c := &context{
ctx: ctx,
cnv: w.cnv,
f: f,
}
return c, nil
}
func (c *context) Functions() *gl.Functions {
return c.f
}
func (c *context) Release() {
if c.srgbFBO != nil {
c.srgbFBO.Release()
c.srgbFBO = nil
}
}
func (c *context) Present() error {
if c.srgbFBO != nil {
c.srgbFBO.Blit()
}
if c.srgbFBO != nil {
c.srgbFBO.AfterPresent()
}
if c.ctx.Call("isContextLost").Bool() {
return errors.New("context lost")
}
return nil
}
func (c *context) Lock() {}
func (c *context) Unlock() {}
func (c *context) MakeCurrent() error {
if c.srgbFBO == nil {
var err error
c.srgbFBO, err = gl.NewSRGBFBO(c.f)
if err != nil {
c.Release()
c.srgbFBO = nil
return err
}
}
w, h := c.cnv.Get("width").Int(), c.cnv.Get("height").Int()
if err := c.srgbFBO.Refresh(w, h); err != nil {
c.Release()
return err
}
return nil
}
+74
View File
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build darwin,!ios
package app
import (
"gioui.org/app/internal/gl"
)
/*
#cgo CFLAGS: -DGL_SILENCE_DEPRECATION -fmodules -fobjc-arc -x objective-c
#include <CoreFoundation/CoreFoundation.h>
#include <CoreGraphics/CoreGraphics.h>
#include <AppKit/AppKit.h>
#include <OpenGL/gl3.h>
#include "gl_macos.h"
*/
import "C"
type context struct {
c *gl.Functions
ctx C.CFTypeRef
view C.CFTypeRef
}
func init() {
viewFactory = func() C.CFTypeRef {
return C.gio_createGLView()
}
}
func newContext(w *window) (*context, error) {
view := w.contextView()
ctx := C.gio_contextForView(view)
c := &context{
ctx: ctx,
c: new(gl.Functions),
view: view,
}
return c, nil
}
func (c *context) Functions() *gl.Functions {
return c.c
}
func (c *context) Release() {
c.Lock()
defer c.Unlock()
C.gio_clearCurrentContext()
}
func (c *context) Present() error {
// Assume the caller already locked the context.
C.glFlush()
return nil
}
func (c *context) Lock() {
C.gio_lockContext(c.ctx)
}
func (c *context) Unlock() {
C.gio_unlockContext(c.ctx)
}
func (c *context) MakeCurrent() error {
c.Lock()
defer c.Unlock()
C.gio_makeCurrentContext(c.ctx)
return nil
}
+9
View File
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: Unlicense OR MIT
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createGLView(void);
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_contextForView(CFTypeRef viewRef);
__attribute__ ((visibility ("hidden"))) void gio_makeCurrentContext(CFTypeRef ctx);
__attribute__ ((visibility ("hidden"))) void gio_flushContextBuffer(CFTypeRef ctx);
__attribute__ ((visibility ("hidden"))) void gio_clearCurrentContext(void);
__attribute__ ((visibility ("hidden"))) void gio_lockContext(CFTypeRef ctxRef);
__attribute__ ((visibility ("hidden"))) void gio_unlockContext(CFTypeRef ctxRef);
+166
View File
@@ -0,0 +1,166 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build darwin,!ios
@import AppKit;
#include <CoreFoundation/CoreFoundation.h>
#include <OpenGL/OpenGL.h>
#include <OpenGL/gl3.h>
#include "os_macos.h"
#include "gl_macos.h"
#include "_cgo_export.h"
static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) {
NSPoint p = [view convertPoint:[event locationInWindow] fromView:nil];
if (!event.hasPreciseScrollingDeltas) {
// dx and dy are in rows and columns.
dx *= 10;
dy *= 10;
}
gio_onMouse((__bridge CFTypeRef)view, typ, p.x, p.y, dx, dy, [event timestamp]);
}
static CVReturn displayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) {
CFTypeRef view = (CFTypeRef *)displayLinkContext;
gio_onFrameCallback(view);
return kCVReturnSuccess;
}
@interface GioView : NSOpenGLView
@end
@implementation GioView {
CVDisplayLinkRef displayLink;
}
- (instancetype)initWithFrame:(NSRect)frameRect
pixelFormat:(NSOpenGLPixelFormat *)format {
self = [super initWithFrame:frameRect pixelFormat:format];
if (self) {
CVDisplayLinkCreateWithActiveCGDisplays(&displayLink);
CVDisplayLinkSetOutputCallback(displayLink, displayLinkCallback, (__bridge void*)self);
}
return self;
}
- (void)dealloc {
CVDisplayLinkRelease(displayLink);
}
- (void)setAnimating:(BOOL)anim {
if (anim) {
CVDisplayLinkStart(displayLink);
} else {
CVDisplayLinkStop(displayLink);
}
}
- (void)updateDisplay:(CGDirectDisplayID)dispID {
CVDisplayLinkSetCurrentCGDisplay(displayLink, dispID);
}
- (void)prepareOpenGL {
[super prepareOpenGL];
// Bind a default VBA to emulate OpenGL ES 2.
GLuint defVBA;
glGenVertexArrays(1, &defVBA);
glBindVertexArray(defVBA);
glEnable(GL_FRAMEBUFFER_SRGB);
}
- (BOOL)isFlipped {
return YES;
}
- (void)update {
[super update];
[self setNeedsDisplay:YES];
}
- (void)drawRect:(NSRect)r {
gio_onDraw((__bridge CFTypeRef)self);
}
- (void)mouseDown:(NSEvent *)event {
handleMouse(self, event, GIO_MOUSE_DOWN, 0, 0);
}
- (void)mouseUp:(NSEvent *)event {
handleMouse(self, event, GIO_MOUSE_UP, 0, 0);
}
- (void)mouseMoved:(NSEvent *)event {
handleMouse(self, event, GIO_MOUSE_MOVE, 0, 0);
}
- (void)mouseDragged:(NSEvent *)event {
handleMouse(self, event, GIO_MOUSE_MOVE, 0, 0);
}
- (void)scrollWheel:(NSEvent *)event {
CGFloat dx = -event.scrollingDeltaX;
CGFloat dy = -event.scrollingDeltaY;
handleMouse(self, event, GIO_MOUSE_MOVE, dx, dy);
}
- (void)keyDown:(NSEvent *)event {
NSString *keys = [event charactersIgnoringModifiers];
gio_onKeys((__bridge CFTypeRef)self, (char *)[keys UTF8String], [event timestamp], [event modifierFlags]);
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
}
- (void)insertText:(id)string {
const char *utf8 = [string UTF8String];
gio_onText((__bridge CFTypeRef)self, (char *)utf8);
}
- (void)doCommandBySelector:(SEL)sel {
// Don't pass commands up the responder chain.
// They will end up in a beep.
}
@end
CFTypeRef gio_createGLView(void) {
@autoreleasepool {
NSOpenGLPixelFormatAttribute attr[] = {
NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
NSOpenGLPFAColorSize, 24,
NSOpenGLPFADepthSize, 16,
NSOpenGLPFAAccelerated,
// Opt-in to automatic GPU switching. CGL-only property.
kCGLPFASupportsAutomaticGraphicsSwitching,
NSOpenGLPFAAllowOfflineRenderers,
0
};
id pixFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr];
NSRect frame = NSMakeRect(0, 0, 0, 0);
GioView* view = [[GioView alloc] initWithFrame:frame pixelFormat:pixFormat];
[view setWantsBestResolutionOpenGLSurface:YES];
[view setWantsLayer:YES]; // The default in Mojave.
return CFBridgingRetain(view);
}
}
void gio_updateDisplayLink(CFTypeRef viewRef, CGDirectDisplayID dispID) {
GioView *view = (__bridge GioView *)viewRef;
[view updateDisplay:dispID];
}
void gio_setAnimating(CFTypeRef viewRef, BOOL anim) {
GioView *view = (__bridge GioView *)viewRef;
dispatch_async(dispatch_get_main_queue(), ^{
[view setAnimating:anim];
});
}
CFTypeRef gio_contextForView(CFTypeRef viewRef) {
NSOpenGLView *view = (__bridge NSOpenGLView *)viewRef;
return (__bridge CFTypeRef)view.openGLContext;
}
void gio_clearCurrentContext(void) {
[NSOpenGLContext clearCurrentContext];
}
void gio_makeCurrentContext(CFTypeRef ctxRef) {
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
return [ctx makeCurrentContext];
}
void gio_lockContext(CFTypeRef ctxRef) {
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
CGLLockContext([ctx CGLContextObj]);
}
void gio_unlockContext(CFTypeRef ctxRef) {
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
CGLUnlockContext([ctx CGLContextObj]);
}
+462
View File
@@ -0,0 +1,462 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build darwin linux
package gl
import (
"unsafe"
)
/*
#cgo linux LDFLAGS: -lGLESv2 -ldl
#cgo darwin,!ios LDFLAGS: -framework OpenGL
#include <stdlib.h>
#ifdef __APPLE__
#cgo CFLAGS: -DGL_SILENCE_DEPRECATION
#include "TargetConditionals.h"
#if TARGET_OS_IPHONE
#include <OpenGLES/ES3/gl.h>
#else
#include <OpenGL/gl3.h>
#endif
#else
#define __USE_GNU
#include <dlfcn.h>
#include <GLES2/gl2.h>
#include <GLES3/gl3.h>
#endif
static void (*_glInvalidateFramebuffer)(GLenum target, GLsizei numAttachments, const GLenum *attachments);
static void (*_glBeginQuery)(GLenum target, GLuint id);
static void (*_glDeleteQueries)(GLsizei n, const GLuint *ids);
static void (*_glEndQuery)(GLenum target);
static void (*_glGenQueries)(GLsizei n, GLuint *ids);
static void (*_glGetQueryObjectuiv)(GLuint id, GLenum pname, GLuint *params);
// The pointer-free version of glVertexAttribPointer, to avoid the Cgo pointer checks.
__attribute__ ((visibility ("hidden"))) void gio_glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, uintptr_t offset) {
glVertexAttribPointer(index, size, type, normalized, stride, (const GLvoid *)offset);
}
// The pointer-free version of glDrawElements, to avoid the Cgo pointer checks.
__attribute__ ((visibility ("hidden"))) void gio_glDrawElements(GLenum mode, GLsizei count, GLenum type, const uintptr_t offset) {
glDrawElements(mode, count, type, (const GLvoid *)offset);
}
__attribute__ ((visibility ("hidden"))) void gio_glInvalidateFramebuffer(GLenum target, GLenum attachment) {
// Framebuffer invalidation is just a hint and can safely be ignored.
if (_glInvalidateFramebuffer != NULL) {
_glInvalidateFramebuffer(target, 1, &attachment);
}
}
__attribute__ ((visibility ("hidden"))) void gio_glBeginQuery(GLenum target, GLenum attachment) {
_glBeginQuery(target, attachment);
}
__attribute__ ((visibility ("hidden"))) void gio_glDeleteQueries(GLsizei n, const GLuint *ids) {
_glDeleteQueries(n, ids);
}
__attribute__ ((visibility ("hidden"))) void gio_glEndQuery(GLenum target) {
_glEndQuery(target);
}
__attribute__ ((visibility ("hidden"))) void gio_glGenQueries(GLsizei n, GLuint *ids) {
_glGenQueries(n, ids);
}
__attribute__ ((visibility ("hidden"))) void gio_glGetQueryObjectuiv(GLuint id, GLenum pname, GLuint *params) {
_glGetQueryObjectuiv(id, pname, params);
}
__attribute__((constructor)) static void gio_loadGLFunctions() {
#ifdef __APPLE__
#if TARGET_OS_IPHONE
_glInvalidateFramebuffer = glInvalidateFramebuffer;
_glBeginQuery = glBeginQuery;
_glDeleteQueries = glDeleteQueries;
_glEndQuery = glEndQuery;
_glGenQueries = glGenQueries;
_glGetQueryObjectuiv = glGetQueryObjectuiv;
#endif
#else
// Load libGLESv3 if available.
dlopen("libGLESv3.so", RTLD_NOW | RTLD_GLOBAL);
_glInvalidateFramebuffer = dlsym(RTLD_DEFAULT, "glInvalidateFramebuffer");
// Fall back to EXT_invalidate_framebuffer if available.
if (_glInvalidateFramebuffer == NULL) {
_glInvalidateFramebuffer = dlsym(RTLD_DEFAULT, "glDiscardFramebufferEXT");
}
_glBeginQuery = dlsym(RTLD_DEFAULT, "glBeginQuery");
if (_glBeginQuery == NULL)
_glBeginQuery = dlsym(RTLD_DEFAULT, "glBeginQueryEXT");
_glDeleteQueries = dlsym(RTLD_DEFAULT, "glDeleteQueries");
if (_glDeleteQueries == NULL)
_glDeleteQueries = dlsym(RTLD_DEFAULT, "glDeleteQueriesEXT");
_glEndQuery = dlsym(RTLD_DEFAULT, "glEndQuery");
if (_glEndQuery == NULL)
_glEndQuery = dlsym(RTLD_DEFAULT, "glEndQueryEXT");
_glGenQueries = dlsym(RTLD_DEFAULT, "glGenQueries");
if (_glGenQueries == NULL)
_glGenQueries = dlsym(RTLD_DEFAULT, "glGenQueriesEXT");
_glGetQueryObjectuiv = dlsym(RTLD_DEFAULT, "glGetQueryObjectuiv");
if (_glGetQueryObjectuiv == NULL)
_glGetQueryObjectuiv = dlsym(RTLD_DEFAULT, "glGetQueryObjectuivEXT");
#endif
}
*/
import "C"
type Functions struct {
// Query caches.
uints [100]C.GLuint
ints [100]C.GLint
}
func (f *Functions) ActiveTexture(texture Enum) {
C.glActiveTexture(C.GLenum(texture))
}
func (f *Functions) AttachShader(p Program, s Shader) {
C.glAttachShader(C.GLuint(p.V), C.GLuint(s.V))
}
func (f *Functions) BeginQuery(target Enum, query Query) {
C.gio_glBeginQuery(C.GLenum(target), C.GLenum(query.V))
}
func (f *Functions) BindAttribLocation(p Program, a Attrib, name string) {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
C.glBindAttribLocation(C.GLuint(p.V), C.GLuint(a), cname)
}
func (f *Functions) BindBuffer(target Enum, b Buffer) {
C.glBindBuffer(C.GLenum(target), C.GLuint(b.V))
}
func (f *Functions) BindFramebuffer(target Enum, fb Framebuffer) {
C.glBindFramebuffer(C.GLenum(target), C.GLuint(fb.V))
}
func (f *Functions) BindRenderbuffer(target Enum, fb Renderbuffer) {
C.glBindRenderbuffer(C.GLenum(target), C.GLuint(fb.V))
}
func (f *Functions) BindTexture(target Enum, t Texture) {
C.glBindTexture(C.GLenum(target), C.GLuint(t.V))
}
func (f *Functions) BlendEquation(mode Enum) {
C.glBlendEquation(C.GLenum(mode))
}
func (f *Functions) BlendFunc(sfactor, dfactor Enum) {
C.glBlendFunc(C.GLenum(sfactor), C.GLenum(dfactor))
}
func (f *Functions) BufferData(target Enum, src []byte, usage Enum) {
var p unsafe.Pointer
if len(src) > 0 {
p = unsafe.Pointer(&src[0])
}
C.glBufferData(C.GLenum(target), C.GLsizeiptr(len(src)), p, C.GLenum(usage))
}
func (f *Functions) CheckFramebufferStatus(target Enum) Enum {
return Enum(C.glCheckFramebufferStatus(C.GLenum(target)))
}
func (f *Functions) Clear(mask Enum) {
C.glClear(C.GLbitfield(mask))
}
func (f *Functions) ClearColor(red float32, green float32, blue float32, alpha float32) {
C.glClearColor(C.GLfloat(red), C.GLfloat(green), C.GLfloat(blue), C.GLfloat(alpha))
}
func (f *Functions) ClearDepthf(d float32) {
C.glClearDepthf(C.GLfloat(d))
}
func (f *Functions) CompileShader(s Shader) {
C.glCompileShader(C.GLuint(s.V))
}
func (f *Functions) CreateBuffer() Buffer {
C.glGenBuffers(1, &f.uints[0])
return Buffer{uint(f.uints[0])}
}
func (f *Functions) CreateFramebuffer() Framebuffer {
C.glGenFramebuffers(1, &f.uints[0])
return Framebuffer{uint(f.uints[0])}
}
func (f *Functions) CreateProgram() Program {
return Program{uint(C.glCreateProgram())}
}
func (f *Functions) CreateQuery() Query {
C.gio_glGenQueries(1, &f.uints[0])
return Query{uint(f.uints[0])}
}
func (f *Functions) CreateRenderbuffer() Renderbuffer {
C.glGenRenderbuffers(1, &f.uints[0])
return Renderbuffer{uint(f.uints[0])}
}
func (f *Functions) CreateShader(ty Enum) Shader {
return Shader{uint(C.glCreateShader(C.GLenum(ty)))}
}
func (f *Functions) CreateTexture() Texture {
C.glGenTextures(1, &f.uints[0])
return Texture{uint(f.uints[0])}
}
func (f *Functions) DeleteBuffer(v Buffer) {
f.uints[0] = C.GLuint(v.V)
C.glDeleteBuffers(1, &f.uints[0])
}
func (f *Functions) DeleteFramebuffer(v Framebuffer) {
f.uints[0] = C.GLuint(v.V)
C.glDeleteFramebuffers(1, &f.uints[0])
}
func (f *Functions) DeleteProgram(p Program) {
C.glDeleteProgram(C.GLuint(p.V))
}
func (f *Functions) DeleteQuery(query Query) {
f.uints[0] = C.GLuint(query.V)
C.gio_glDeleteQueries(1, &f.uints[0])
}
func (f *Functions) DeleteRenderbuffer(v Renderbuffer) {
f.uints[0] = C.GLuint(v.V)
C.glDeleteRenderbuffers(1, &f.uints[0])
}
func (f *Functions) DeleteShader(s Shader) {
C.glDeleteShader(C.GLuint(s.V))
}
func (f *Functions) DeleteTexture(v Texture) {
f.uints[0] = C.GLuint(v.V)
C.glDeleteTextures(1, &f.uints[0])
}
func (f *Functions) DepthFunc(v Enum) {
C.glDepthFunc(C.GLenum(v))
}
func (f *Functions) DepthMask(mask bool) {
m := C.GLboolean(C.GL_FALSE)
if mask {
m = C.GLboolean(C.GL_TRUE)
}
C.glDepthMask(m)
}
func (f *Functions) DisableVertexAttribArray(a Attrib) {
C.glDisableVertexAttribArray(C.GLuint(a))
}
func (f *Functions) Disable(cap Enum) {
C.glDisable(C.GLenum(cap))
}
func (f *Functions) DrawArrays(mode Enum, first int, count int) {
C.glDrawArrays(C.GLenum(mode), C.GLint(first), C.GLsizei(count))
}
func (f *Functions) DrawElements(mode Enum, count int, ty Enum, offset int) {
C.gio_glDrawElements(C.GLenum(mode), C.GLsizei(count), C.GLenum(ty), C.uintptr_t(offset))
}
func (f *Functions) Enable(cap Enum) {
C.glEnable(C.GLenum(cap))
}
func (f *Functions) EndQuery(target Enum) {
C.gio_glEndQuery(C.GLenum(target))
}
func (f *Functions) EnableVertexAttribArray(a Attrib) {
C.glEnableVertexAttribArray(C.GLuint(a))
}
func (f *Functions) Finish() {
C.glFinish()
}
func (f *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer) {
C.glFramebufferRenderbuffer(C.GLenum(target), C.GLenum(attachment), C.GLenum(renderbuffertarget), C.GLuint(renderbuffer.V))
}
func (f *Functions) FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int) {
C.glFramebufferTexture2D(C.GLenum(target), C.GLenum(attachment), C.GLenum(texTarget), C.GLuint(t.V), C.GLint(level))
}
func (c *Functions) GetBinding(pname Enum) Object {
return Object{uint(c.GetInteger(pname))}
}
func (f *Functions) GetError() Enum {
return Enum(C.glGetError())
}
func (f *Functions) GetRenderbufferParameteri(target, pname Enum) int {
C.glGetRenderbufferParameteriv(C.GLenum(target), C.GLenum(pname), &f.ints[0])
return int(f.ints[0])
}
func (f *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int {
C.glGetFramebufferAttachmentParameteriv(C.GLenum(target), C.GLenum(attachment), C.GLenum(pname), &f.ints[0])
return int(f.ints[0])
}
func (f *Functions) GetInteger(pname Enum) int {
C.glGetIntegerv(C.GLenum(pname), &f.ints[0])
return int(f.ints[0])
}
func (f *Functions) GetProgrami(p Program, pname Enum) int {
C.glGetProgramiv(C.GLuint(p.V), C.GLenum(pname), &f.ints[0])
return int(f.ints[0])
}
func (f *Functions) GetProgramInfoLog(p Program) string {
n := f.GetProgrami(p, INFO_LOG_LENGTH)
buf := make([]byte, n)
C.glGetProgramInfoLog(C.GLuint(p.V), C.GLsizei(len(buf)), nil, (*C.GLchar)(unsafe.Pointer(&buf[0])))
return string(buf)
}
func (f *Functions) GetQueryObjectuiv(query Query, pname Enum) uint {
C.gio_glGetQueryObjectuiv(C.GLuint(query.V), C.GLenum(pname), &f.uints[0])
return uint(f.uints[0])
}
func (f *Functions) GetShaderi(s Shader, pname Enum) int {
C.glGetShaderiv(C.GLuint(s.V), C.GLenum(pname), &f.ints[0])
return int(f.ints[0])
}
func (f *Functions) GetShaderInfoLog(s Shader) string {
n := f.GetShaderi(s, INFO_LOG_LENGTH)
buf := make([]byte, n)
C.glGetShaderInfoLog(C.GLuint(s.V), C.GLsizei(len(buf)), nil, (*C.GLchar)(unsafe.Pointer(&buf[0])))
return string(buf)
}
func (f *Functions) GetString(pname Enum) string {
str := C.glGetString(C.GLenum(pname))
return C.GoString((*C.char)(unsafe.Pointer(str)))
}
func (f *Functions) GetUniformLocation(p Program, name string) Uniform {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
return Uniform{int(C.glGetUniformLocation(C.GLuint(p.V), cname))}
}
func (f *Functions) InvalidateFramebuffer(target, attachment Enum) {
C.gio_glInvalidateFramebuffer(C.GLenum(target), C.GLenum(attachment))
}
func (f *Functions) LinkProgram(p Program) {
C.glLinkProgram(C.GLuint(p.V))
}
func (f *Functions) PixelStorei(pname Enum, param int32) {
C.glPixelStorei(C.GLenum(pname), C.GLint(param))
}
func (f *Functions) Scissor(x, y, width, height int32) {
C.glScissor(C.GLint(x), C.GLint(y), C.GLsizei(width), C.GLsizei(height))
}
func (f *Functions) ReadPixels(x, y, width, height int, format, ty Enum, data []byte) {
var p unsafe.Pointer
if len(data) > 0 {
p = unsafe.Pointer(&data[0])
}
C.glReadPixels(C.GLint(x), C.GLint(y), C.GLsizei(width), C.GLsizei(height), C.GLenum(format), C.GLenum(ty), p)
}
func (f *Functions) RenderbufferStorage(target, internalformat Enum, width, height int) {
C.glRenderbufferStorage(C.GLenum(target), C.GLenum(internalformat), C.GLsizei(width), C.GLsizei(height))
}
func (f *Functions) ShaderSource(s Shader, src string) {
csrc := C.CString(src)
defer C.free(unsafe.Pointer(csrc))
strlen := C.GLint(len(src))
C.glShaderSource(C.GLuint(s.V), 1, &csrc, &strlen)
}
func (f *Functions) TexImage2D(target Enum, level int, internalFormat int, width int, height int, format Enum, ty Enum, data []byte) {
var p unsafe.Pointer
if len(data) > 0 {
p = unsafe.Pointer(&data[0])
}
C.glTexImage2D(C.GLenum(target), C.GLint(level), C.GLint(internalFormat), C.GLsizei(width), C.GLsizei(height), 0, C.GLenum(format), C.GLenum(ty), p)
}
func (f *Functions) TexSubImage2D(target Enum, level int, x int, y int, width int, height int, format Enum, ty Enum, data []byte) {
var p unsafe.Pointer
if len(data) > 0 {
p = unsafe.Pointer(&data[0])
}
C.glTexSubImage2D(C.GLenum(target), C.GLint(level), C.GLint(x), C.GLint(y), C.GLsizei(width), C.GLsizei(height), C.GLenum(format), C.GLenum(ty), p)
}
func (f *Functions) TexParameteri(target, pname Enum, param int) {
C.glTexParameteri(C.GLenum(target), C.GLenum(pname), C.GLint(param))
}
func (f *Functions) Uniform1f(dst Uniform, v float32) {
C.glUniform1f(C.GLint(dst.V), C.GLfloat(v))
}
func (f *Functions) Uniform1i(dst Uniform, v int) {
C.glUniform1i(C.GLint(dst.V), C.GLint(v))
}
func (f *Functions) Uniform2f(dst Uniform, v0 float32, v1 float32) {
C.glUniform2f(C.GLint(dst.V), C.GLfloat(v0), C.GLfloat(v1))
}
func (f *Functions) Uniform3f(dst Uniform, v0 float32, v1 float32, v2 float32) {
C.glUniform3f(C.GLint(dst.V), C.GLfloat(v0), C.GLfloat(v1), C.GLfloat(v2))
}
func (f *Functions) Uniform4f(dst Uniform, v0 float32, v1 float32, v2 float32, v3 float32) {
C.glUniform4f(C.GLint(dst.V), C.GLfloat(v0), C.GLfloat(v1), C.GLfloat(v2), C.GLfloat(v3))
}
func (f *Functions) UseProgram(p Program) {
C.glUseProgram(C.GLuint(p.V))
}
func (f *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride int, offset int) {
var n C.GLboolean = C.GL_FALSE
if normalized {
n = C.GL_TRUE
}
C.gio_glVertexAttribPointer(C.GLuint(dst), C.GLint(size), C.GLenum(ty), n, C.GLsizei(stride), C.uintptr_t(offset))
}
func (f *Functions) Viewport(x int, y int, width int, height int) {
C.glViewport(C.GLint(x), C.GLint(y), C.GLsizei(width), C.GLsizei(height))
}
+164
View File
@@ -0,0 +1,164 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gl
type (
Attrib uint
Enum uint
)
type Context interface {
Functions() *Functions
Present() error
MakeCurrent() error
Release()
Lock()
Unlock()
}
const (
ARRAY_BUFFER = 0x8892
BLEND = 0xbe2
CLAMP_TO_EDGE = 0x812f
COLOR_ATTACHMENT0 = 0x8ce0
COLOR_BUFFER_BIT = 0x4000
COMPILE_STATUS = 0x8b81
DEPTH_BUFFER_BIT = 0x100
DEPTH_ATTACHMENT = 0x8d00
DEPTH_COMPONENT16 = 0x81a5
DEPTH_TEST = 0xb71
DST_COLOR = 0x306
ELEMENT_ARRAY_BUFFER = 0x8893
EXTENSIONS = 0x1f03
FLOAT = 0x1406
FRAGMENT_SHADER = 0x8b30
FRAMEBUFFER = 0x8d40
FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING = 0x8210
FRAMEBUFFER_BINDING = 0x8ca6
FRAMEBUFFER_COMPLETE = 0x8cd5
HALF_FLOAT = 0x140b
HALF_FLOAT_OES = 0x8d61
INFO_LOG_LENGTH = 0x8B84
GREATER = 0x204
LINEAR = 0x2601
LINK_STATUS = 0x8b82
LUMINANCE = 0x1909
MAX_TEXTURE_SIZE = 0xd33
NEAREST = 0x2600
ONE = 0x1
ONE_MINUS_SRC_ALPHA = 0x303
QUERY_RESULT = 0x8866
QUERY_RESULT_AVAILABLE = 0x8867
R16F = 0x822d
R8 = 0x8229
READ_FRAMEBUFFER = 0x8ca8
RED = 0x1903
RENDERER = 0x1F01
RENDERBUFFER = 0x8d41
RENDERBUFFER_BINDING = 0x8ca7
RENDERBUFFER_HEIGHT = 0x8d43
RENDERBUFFER_WIDTH = 0x8d42
RGB = 0x1907
RGBA = 0x1908
RGBA8 = 0x8058
SHORT = 0x1402
SRGB = 0x8c40
SRGB_ALPHA_EXT = 0x8c42
SRGB8 = 0x8c41
SRGB8_ALPHA8 = 0x8c43
STATIC_DRAW = 0x88e4
TEXTURE_2D = 0xde1
TEXTURE_MAG_FILTER = 0x2800
TEXTURE_MIN_FILTER = 0x2801
TEXTURE_WRAP_S = 0x2802
TEXTURE_WRAP_T = 0x2803
TEXTURE0 = 0x84c0
TEXTURE1 = 0x84c1
TRIANGLE_STRIP = 0x5
TRIANGLES = 0x4
UNPACK_ALIGNMENT = 0xcf5
UNSIGNED_BYTE = 0x1401
UNSIGNED_SHORT = 0x1403
VERSION = 0x1f02
VERTEX_SHADER = 0x8b31
ZERO = 0x0
// EXT_disjoint_timer_query
TIME_ELAPSED_EXT = 0x88BF
GPU_DISJOINT_EXT = 0x8FBB
)
// Enforce Functions interface.
var _ interface {
ActiveTexture(texture Enum)
AttachShader(p Program, s Shader)
BeginQuery(target Enum, query Query)
BindAttribLocation(p Program, a Attrib, name string)
BindBuffer(target Enum, b Buffer)
BindFramebuffer(target Enum, fb Framebuffer)
BindRenderbuffer(target Enum, rb Renderbuffer)
BindTexture(target Enum, t Texture)
BlendEquation(mode Enum)
BlendFunc(sfactor, dfactor Enum)
BufferData(target Enum, src []byte, usage Enum)
CheckFramebufferStatus(target Enum) Enum
Clear(mask Enum)
ClearColor(red, green, blue, alpha float32)
ClearDepthf(d float32)
CompileShader(s Shader)
CreateBuffer() Buffer
CreateFramebuffer() Framebuffer
CreateProgram() Program
CreateQuery() Query
CreateRenderbuffer() Renderbuffer
CreateShader(ty Enum) Shader
CreateTexture() Texture
DeleteBuffer(v Buffer)
DeleteFramebuffer(v Framebuffer)
DeleteProgram(p Program)
DeleteQuery(query Query)
DeleteRenderbuffer(v Renderbuffer)
DeleteShader(s Shader)
DeleteTexture(v Texture)
DepthFunc(f Enum)
DepthMask(mask bool)
DisableVertexAttribArray(a Attrib)
Disable(cap Enum)
DrawArrays(mode Enum, first, count int)
DrawElements(mode Enum, count int, ty Enum, offset int)
Enable(cap Enum)
EnableVertexAttribArray(a Attrib)
EndQuery(target Enum)
Finish()
FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer)
FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int)
GetBinding(pname Enum) Object
GetError() Enum
GetRenderbufferParameteri(target, pname Enum) int
GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int
GetInteger(pname Enum) int
GetProgrami(p Program, pname Enum) int
GetProgramInfoLog(p Program) string
GetQueryObjectuiv(query Query, pname Enum) uint
GetShaderi(s Shader, pname Enum) int
GetShaderInfoLog(s Shader) string
GetString(pname Enum) string
GetUniformLocation(p Program, name string) Uniform
InvalidateFramebuffer(target, attachment Enum)
LinkProgram(p Program)
PixelStorei(pname Enum, param int32)
RenderbufferStorage(target, internalformat Enum, width, height int)
Scissor(x, y, width, height int32)
ShaderSource(s Shader, src string)
TexImage2D(target Enum, level int, internalFormat int, width, height int, format, ty Enum, data []byte)
TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte)
TexParameteri(target, pname Enum, param int)
Uniform1f(dst Uniform, v float32)
Uniform1i(dst Uniform, v int)
Uniform2f(dst Uniform, v0, v1 float32)
Uniform3f(dst Uniform, v0, v1, v2 float32)
Uniform4f(dst Uniform, v0, v1, v2, v3 float32)
UseProgram(p Program)
VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int)
Viewport(x, y, width, height int)
} = (*Functions)(nil)
+328
View File
@@ -0,0 +1,328 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gl
import (
"errors"
"strings"
"syscall/js"
)
type Functions struct {
Ctx js.Value
EXT_disjoint_timer_query js.Value
EXT_disjoint_timer_query_webgl2 js.Value
// Cached JS arrays.
byteBuf js.Value
int32Buf js.Value
}
func (f *Functions) Init(version int) error {
if version < 2 {
f.EXT_disjoint_timer_query = f.getExtension("EXT_disjoint_timer_query")
if f.getExtension("OES_texture_half_float") == js.Null() && f.getExtension("OES_texture_float") == js.Null() {
return errors.New("gl: no support for neither OES_texture_half_float nor OES_texture_float")
}
if f.getExtension("EXT_sRGB") == js.Null() {
return errors.New("gl: EXT_sRGB not supported")
}
} else {
// WebGL2 extensions.
f.EXT_disjoint_timer_query_webgl2 = f.getExtension("EXT_disjoint_timer_query_webgl2")
if f.getExtension("EXT_color_buffer_half_float") == js.Null() && f.getExtension("EXT_color_buffer_float") == js.Null() {
return errors.New("gl: no support for neither EXT_color_buffer_half_float nor EXT_color_buffer_float")
}
}
return nil
}
func (f *Functions) getExtension(name string) js.Value {
return f.Ctx.Call("getExtension", name)
}
func (f *Functions) ActiveTexture(t Enum) {
f.Ctx.Call("activeTexture", int(t))
}
func (f *Functions) AttachShader(p Program, s Shader) {
f.Ctx.Call("attachShader", js.Value(p), js.Value(s))
}
func (f *Functions) BeginQuery(target Enum, query Query) {
if f.EXT_disjoint_timer_query_webgl2 != js.Null() {
f.Ctx.Call("beginQuery", int(target), js.Value(query))
} else {
f.EXT_disjoint_timer_query.Call("beginQueryEXT", int(target), js.Value(query))
}
}
func (f *Functions) BindAttribLocation(p Program, a Attrib, name string) {
f.Ctx.Call("bindAttribLocation", js.Value(p), int(a), name)
}
func (f *Functions) BindBuffer(target Enum, b Buffer) {
f.Ctx.Call("bindBuffer", int(target), js.Value(b))
}
func (f *Functions) BindFramebuffer(target Enum, fb Framebuffer) {
f.Ctx.Call("bindFramebuffer", int(target), js.Value(fb))
}
func (f *Functions) BindRenderbuffer(target Enum, rb Renderbuffer) {
f.Ctx.Call("bindRenderbuffer", int(target), js.Value(rb))
}
func (f *Functions) BindTexture(target Enum, t Texture) {
f.Ctx.Call("bindTexture", int(target), js.Value(t))
}
func (f *Functions) BlendEquation(mode Enum) {
f.Ctx.Call("blendEquation", int(mode))
}
func (f *Functions) BlendFunc(sfactor, dfactor Enum) {
f.Ctx.Call("blendFunc", int(sfactor), int(dfactor))
}
func (f *Functions) BufferData(target Enum, src []byte, usage Enum) {
f.Ctx.Call("bufferData", int(target), f.byteArrayOf(src), int(usage))
}
func (f *Functions) CheckFramebufferStatus(target Enum) Enum {
return Enum(f.Ctx.Call("checkFramebufferStatus", int(target)).Int())
}
func (f *Functions) Clear(mask Enum) {
f.Ctx.Call("clear", int(mask))
}
func (f *Functions) ClearColor(red, green, blue, alpha float32) {
f.Ctx.Call("clearColor", red, green, blue, alpha)
}
func (f *Functions) ClearDepthf(d float32) {
f.Ctx.Call("clearDepth", d)
}
func (f *Functions) CompileShader(s Shader) {
f.Ctx.Call("compileShader", js.Value(s))
}
func (f *Functions) CreateBuffer() Buffer {
return Buffer(f.Ctx.Call("createBuffer"))
}
func (f *Functions) CreateFramebuffer() Framebuffer {
return Framebuffer(f.Ctx.Call("createFramebuffer"))
}
func (f *Functions) CreateProgram() Program {
return Program(f.Ctx.Call("createProgram"))
}
func (f *Functions) CreateQuery() Query {
return Query(f.Ctx.Call("createQuery"))
}
func (f *Functions) CreateRenderbuffer() Renderbuffer {
return Renderbuffer(f.Ctx.Call("createRenderbuffer"))
}
func (f *Functions) CreateShader(ty Enum) Shader {
return Shader(f.Ctx.Call("createShader", int(ty)))
}
func (f *Functions) CreateTexture() Texture {
return Texture(f.Ctx.Call("createTexture"))
}
func (f *Functions) DeleteBuffer(v Buffer) {
f.Ctx.Call("deleteBuffer", js.Value(v))
}
func (f *Functions) DeleteFramebuffer(v Framebuffer) {
f.Ctx.Call("deleteFramebuffer", js.Value(v))
}
func (f *Functions) DeleteProgram(p Program) {
f.Ctx.Call("deleteProgram", js.Value(p))
}
func (f *Functions) DeleteQuery(query Query) {
if f.EXT_disjoint_timer_query_webgl2 != js.Null() {
f.Ctx.Call("deleteQuery", js.Value(query))
} else {
f.EXT_disjoint_timer_query.Call("deleteQueryEXT", js.Value(query))
}
}
func (f *Functions) DeleteShader(s Shader) {
f.Ctx.Call("deleteShader", js.Value(s))
}
func (f *Functions) DeleteRenderbuffer(v Renderbuffer) {
f.Ctx.Call("deleteRenderbuffer", js.Value(v))
}
func (f *Functions) DeleteTexture(v Texture) {
f.Ctx.Call("deleteTexture", js.Value(v))
}
func (f *Functions) DepthFunc(fn Enum) {
f.Ctx.Call("depthFunc", int(fn))
}
func (f *Functions) DepthMask(mask bool) {
f.Ctx.Call("depthMask", mask)
}
func (f *Functions) DisableVertexAttribArray(a Attrib) {
f.Ctx.Call("disableVertexAttribArray", int(a))
}
func (f *Functions) Disable(cap Enum) {
f.Ctx.Call("disable", int(cap))
}
func (f *Functions) DrawArrays(mode Enum, first, count int) {
f.Ctx.Call("drawArrays", int(mode), first, count)
}
func (f *Functions) DrawElements(mode Enum, count int, ty Enum, offset int) {
f.Ctx.Call("drawElements", int(mode), count, int(ty), offset)
}
func (f *Functions) Enable(cap Enum) {
f.Ctx.Call("enable", int(cap))
}
func (f *Functions) EnableVertexAttribArray(a Attrib) {
f.Ctx.Call("enableVertexAttribArray", int(a))
}
func (f *Functions) EndQuery(target Enum) {
if f.EXT_disjoint_timer_query_webgl2 != js.Null() {
f.Ctx.Call("endQuery", int(target))
} else {
f.EXT_disjoint_timer_query.Call("endQueryEXT", int(target))
}
}
func (f *Functions) Finish() {
f.Ctx.Call("finish")
}
func (f *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer) {
f.Ctx.Call("framebufferRenderbuffer", int(target), int(attachment), int(renderbuffertarget), js.Value(renderbuffer))
}
func (f *Functions) FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int) {
f.Ctx.Call("framebufferTexture2D", int(target), int(attachment), int(texTarget), js.Value(t), level)
}
func (f *Functions) GetError() Enum {
return Enum(f.Ctx.Call("getError").Int())
}
func (f *Functions) GetRenderbufferParameteri(target, pname Enum) int {
return paramVal(f.Ctx.Call("getRenderbufferParameteri", int(pname)))
}
func (f *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int {
return paramVal(f.Ctx.Call("getFramebufferAttachmentParameter", int(target), int(attachment), int(pname)))
}
func (f *Functions) GetBinding(pname Enum) Object {
return Object(f.Ctx.Call("getParameter", int(pname)))
}
func (f *Functions) GetInteger(pname Enum) int {
return paramVal(f.Ctx.Call("getParameter", int(pname)))
}
func (f *Functions) GetProgrami(p Program, pname Enum) int {
return paramVal(f.Ctx.Call("getProgramParameter", js.Value(p), int(pname)))
}
func (f *Functions) GetProgramInfoLog(p Program) string {
return f.Ctx.Call("getProgramInfoLog", js.Value(p)).String()
}
func (f *Functions) GetQueryObjectuiv(query Query, pname Enum) uint {
if f.EXT_disjoint_timer_query_webgl2 != js.Null() {
return uint(paramVal(f.Ctx.Call("getQueryParameter", js.Value(query), int(pname))))
} else {
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.Ctx.Call("getShaderParameter", js.Value(s), int(pname)))
}
func (f *Functions) GetShaderInfoLog(s Shader) string {
return f.Ctx.Call("getShaderInfoLog", js.Value(s)).String()
}
func (f *Functions) GetString(pname Enum) string {
switch pname {
case EXTENSIONS:
extsjs := f.Ctx.Call("getSupportedExtensions")
var exts []string
for i := 0; i < extsjs.Length(); i++ {
exts = append(exts, "GL_"+extsjs.Index(i).String())
}
return strings.Join(exts, " ")
default:
return f.Ctx.Call("getParameter", int(pname)).String()
}
}
func (f *Functions) GetUniformLocation(p Program, name string) Uniform {
return Uniform(f.Ctx.Call("getUniformLocation", js.Value(p), name))
}
func (f *Functions) InvalidateFramebuffer(target, attachment Enum) {
fn := f.Ctx.Get("invalidateFramebuffer")
if fn != js.Undefined() {
if f.int32Buf == (js.Value{}) {
f.int32Buf = js.Global().Get("Int32Array").New(1)
}
f.int32Buf.SetIndex(0, int32(attachment))
f.Ctx.Call("invalidateFramebuffer", int(target), f.int32Buf)
}
}
func (f *Functions) LinkProgram(p Program) {
f.Ctx.Call("linkProgram", js.Value(p))
}
func (f *Functions) PixelStorei(pname Enum, param int32) {
f.Ctx.Call("pixelStorei", int(pname), param)
}
func (f *Functions) RenderbufferStorage(target, internalformat Enum, width, height int) {
f.Ctx.Call("renderbufferStorage", int(target), int(internalformat), width, height)
}
func (f *Functions) ReadPixels(x, y, width, height int, format, ty Enum, data []byte) {
f.resizeByteBuffer(len(data))
f.Ctx.Call("readPixels", x, y, width, height, int(format), int(ty), f.byteBuf)
js.CopyBytesToGo(data, f.byteBuf)
}
func (f *Functions) Scissor(x, y, width, height int32) {
f.Ctx.Call("scissor", x, y, width, height)
}
func (f *Functions) ShaderSource(s Shader, src string) {
f.Ctx.Call("shaderSource", js.Value(s), src)
}
func (f *Functions) TexImage2D(target Enum, level int, internalFormat int, width, height int, format, ty Enum, data []byte) {
f.Ctx.Call("texImage2D", int(target), int(level), int(internalFormat), int(width), int(height), 0, int(format), int(ty), f.byteArrayOf(data))
}
func (f *Functions) TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte) {
f.Ctx.Call("texSubImage2D", int(target), level, x, y, width, height, int(format), int(ty), f.byteArrayOf(data))
}
func (f *Functions) TexParameteri(target, pname Enum, param int) {
f.Ctx.Call("texParameteri", int(target), int(pname), int(param))
}
func (f *Functions) Uniform1f(dst Uniform, v float32) {
f.Ctx.Call("uniform1f", js.Value(dst), v)
}
func (f *Functions) Uniform1i(dst Uniform, v int) {
f.Ctx.Call("uniform1i", js.Value(dst), v)
}
func (f *Functions) Uniform2f(dst Uniform, v0, v1 float32) {
f.Ctx.Call("uniform2f", js.Value(dst), v0, v1)
}
func (f *Functions) Uniform3f(dst Uniform, v0, v1, v2 float32) {
f.Ctx.Call("uniform3f", js.Value(dst), v0, v1, v2)
}
func (f *Functions) Uniform4f(dst Uniform, v0, v1, v2, v3 float32) {
f.Ctx.Call("uniform4f", js.Value(dst), v0, v1, v2, v3)
}
func (f *Functions) UseProgram(p Program) {
f.Ctx.Call("useProgram", js.Value(p))
}
func (f *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int) {
f.Ctx.Call("vertexAttribPointer", int(dst), size, int(ty), normalized, stride, offset)
}
func (f *Functions) Viewport(x, y, width, height int) {
f.Ctx.Call("viewport", x, y, width, height)
}
func (f *Functions) byteArrayOf(data []byte) js.Value {
if len(data) == 0 {
return js.Null()
}
f.resizeByteBuffer(len(data))
js.CopyBytesToJS(f.byteBuf, data)
return f.byteBuf
}
func (f *Functions) resizeByteBuffer(n int) {
if n == 0 {
return
}
if f.byteBuf != (js.Value{}) && f.byteBuf.Length() >= n {
return
}
f.byteBuf = js.Global().Get("Uint8Array").New(n)
}
func paramVal(v js.Value) int {
switch v.Type() {
case js.TypeBoolean:
if b := v.Bool(); b {
return 1
} else {
return 0
}
case js.TypeNumber:
return v.Int()
default:
panic("unknown parameter type")
}
}
+387
View File
@@ -0,0 +1,387 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gl
import (
"math"
"runtime"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
var (
LibGLESv2 = windows.NewLazyDLL("libGLESv2.dll")
_glActiveTexture = LibGLESv2.NewProc("glActiveTexture")
_glAttachShader = LibGLESv2.NewProc("glAttachShader")
_glBeginQuery = LibGLESv2.NewProc("glBeginQuery")
_glBindAttribLocation = LibGLESv2.NewProc("glBindAttribLocation")
_glBindBuffer = LibGLESv2.NewProc("glBindBuffer")
_glBindFramebuffer = LibGLESv2.NewProc("glBindFramebuffer")
_glBindRenderbuffer = LibGLESv2.NewProc("glBindRenderbuffer")
_glBindTexture = LibGLESv2.NewProc("glBindTexture")
_glBlendEquation = LibGLESv2.NewProc("glBlendEquation")
_glBlendFunc = LibGLESv2.NewProc("glBlendFunc")
_glBufferData = LibGLESv2.NewProc("glBufferData")
_glCheckFramebufferStatus = LibGLESv2.NewProc("glCheckFramebufferStatus")
_glClear = LibGLESv2.NewProc("glClear")
_glClearColor = LibGLESv2.NewProc("glClearColor")
_glClearDepthf = LibGLESv2.NewProc("glClearDepthf")
_glDeleteQueries = LibGLESv2.NewProc("glDeleteQueries")
_glCompileShader = LibGLESv2.NewProc("glCompileShader")
_glGenBuffers = LibGLESv2.NewProc("glGenBuffers")
_glGenFramebuffers = LibGLESv2.NewProc("glGenFramebuffers")
_glCreateProgram = LibGLESv2.NewProc("glCreateProgram")
_glGenRenderbuffers = LibGLESv2.NewProc("glGenRenderbuffers")
_glCreateShader = LibGLESv2.NewProc("glCreateShader")
_glGenTextures = LibGLESv2.NewProc("glGenTextures")
_glDeleteBuffers = LibGLESv2.NewProc("glDeleteBuffers")
_glDeleteFramebuffers = LibGLESv2.NewProc("glDeleteFramebuffers")
_glDeleteProgram = LibGLESv2.NewProc("glDeleteProgram")
_glDeleteShader = LibGLESv2.NewProc("glDeleteShader")
_glDeleteRenderbuffers = LibGLESv2.NewProc("glDeleteRenderbuffers")
_glDeleteTextures = LibGLESv2.NewProc("glDeleteTextures")
_glDepthFunc = LibGLESv2.NewProc("glDepthFunc")
_glDepthMask = LibGLESv2.NewProc("glDepthMask")
_glDisableVertexAttribArray = LibGLESv2.NewProc("glDisableVertexAttribArray")
_glDisable = LibGLESv2.NewProc("glDisable")
_glDrawArrays = LibGLESv2.NewProc("glDrawArrays")
_glDrawElements = LibGLESv2.NewProc("glDrawElements")
_glEnable = LibGLESv2.NewProc("glEnable")
_glEnableVertexAttribArray = LibGLESv2.NewProc("glEnableVertexAttribArray")
_glEndQuery = LibGLESv2.NewProc("glEndQuery")
_glFinish = LibGLESv2.NewProc("glFinish")
_glFramebufferRenderbuffer = LibGLESv2.NewProc("glFramebufferRenderbuffer")
_glFramebufferTexture2D = LibGLESv2.NewProc("glFramebufferTexture2D")
_glGenQueries = LibGLESv2.NewProc("glGenQueries")
_glGetError = LibGLESv2.NewProc("glGetError")
_glGetRenderbufferParameteri = LibGLESv2.NewProc("glGetRenderbufferParameteri")
_glGetFramebufferAttachmentParameteri = LibGLESv2.NewProc("glGetFramebufferAttachmentParameteri")
_glGetIntegerv = LibGLESv2.NewProc("glGetIntegerv")
_glGetProgramiv = LibGLESv2.NewProc("glGetProgramiv")
_glGetProgramInfoLog = LibGLESv2.NewProc("glGetProgramInfoLog")
_glGetQueryObjectuiv = LibGLESv2.NewProc("glGetQueryObjectuiv")
_glGetShaderiv = LibGLESv2.NewProc("glGetShaderiv")
_glGetShaderInfoLog = LibGLESv2.NewProc("glGetShaderInfoLog")
_glGetString = LibGLESv2.NewProc("glGetString")
_glGetUniformLocation = LibGLESv2.NewProc("glGetUniformLocation")
_glInvalidateFramebuffer = LibGLESv2.NewProc("glInvalidateFramebuffer")
_glLinkProgram = LibGLESv2.NewProc("glLinkProgram")
_glPixelStorei = LibGLESv2.NewProc("glPixelStorei")
_glReadPixels = LibGLESv2.NewProc("glReadPixels")
_glRenderbufferStorage = LibGLESv2.NewProc("glRenderbufferStorage")
_glScissor = LibGLESv2.NewProc("glScissor")
_glShaderSource = LibGLESv2.NewProc("glShaderSource")
_glTexImage2D = LibGLESv2.NewProc("glTexImage2D")
_glTexSubImage2D = LibGLESv2.NewProc("glTexSubImage2D")
_glTexParameteri = LibGLESv2.NewProc("glTexParameteri")
_glUniform1f = LibGLESv2.NewProc("glUniform1f")
_glUniform1i = LibGLESv2.NewProc("glUniform1i")
_glUniform2f = LibGLESv2.NewProc("glUniform2f")
_glUniform3f = LibGLESv2.NewProc("glUniform3f")
_glUniform4f = LibGLESv2.NewProc("glUniform4f")
_glUseProgram = LibGLESv2.NewProc("glUseProgram")
_glVertexAttribPointer = LibGLESv2.NewProc("glVertexAttribPointer")
_glViewport = LibGLESv2.NewProc("glViewport")
)
type Functions struct {
// Query caches.
int32s [100]int32
}
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) 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 (c *Functions) BindTexture(target Enum, t Texture) {
syscall.Syscall(_glBindTexture.Addr(), 2, uintptr(target), uintptr(t.V), 0)
}
func (c *Functions) BlendEquation(mode Enum) {
syscall.Syscall(_glBlendEquation.Addr(), 1, uintptr(mode), 0, 0)
}
func (c *Functions) BlendFunc(sfactor, dfactor Enum) {
syscall.Syscall(_glBlendFunc.Addr(), 2, uintptr(sfactor), uintptr(dfactor), 0)
}
func (c *Functions) BufferData(target Enum, src []byte, usage Enum) {
if n := len(src); n == 0 {
syscall.Syscall6(_glBufferData.Addr(), 4, uintptr(target), 0, 0, uintptr(usage), 0, 0)
} else {
s0 := &src[0]
syscall.Syscall6(_glBufferData.Addr(), 4, uintptr(target), uintptr(n), uintptr(unsafe.Pointer(s0)), uintptr(usage), 0, 0)
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 (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) 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 (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 {
m = 1
}
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 (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) 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 (c *Functions) GetBinding(pname Enum) Object {
return Object{uint(c.GetInteger(pname))}
}
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 {
p, _, _ := syscall.Syscall(_glGetRenderbufferParameteri.Addr(), 2, uintptr(target), uintptr(pname), 0)
return int(p)
}
func (c *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int {
p, _, _ := syscall.Syscall(_glGetFramebufferAttachmentParameteri.Addr(), 3, uintptr(target), uintptr(attachment), uintptr(pname))
return int(p)
}
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) 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)
buf := make([]byte, n)
syscall.Syscall6(_glGetProgramInfoLog.Addr(), 4, uintptr(p.V), uintptr(len(buf)), 0, uintptr(unsafe.Pointer(&buf[0])), 0, 0)
return string(buf)
}
func (c *Functions) GetQueryObjectuiv(query Query, pname Enum) uint {
syscall.Syscall(_glGetQueryObjectuiv.Addr(), 3, uintptr(query.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
return uint(c.int32s[0])
}
func (c *Functions) GetShaderi(s Shader, pname Enum) int {
syscall.Syscall(_glGetShaderiv.Addr(), 3, uintptr(s.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
return int(c.int32s[0])
}
func (c *Functions) GetShaderInfoLog(s Shader) string {
n := c.GetShaderi(s, INFO_LOG_LENGTH)
buf := make([]byte, n)
syscall.Syscall6(_glGetShaderInfoLog.Addr(), 4, uintptr(s.V), uintptr(len(buf)), 0, uintptr(unsafe.Pointer(&buf[0])), 0, 0)
return string(buf)
}
func (c *Functions) GetString(pname Enum) string {
s, _, _ := syscall.Syscall(_glGetString.Addr(), 1, uintptr(pname), 0, 0)
return GoString(SliceOf(s))
}
func (c *Functions) GetUniformLocation(p Program, name string) Uniform {
cname := cString(name)
c0 := &cname[0]
u, _, _ := syscall.Syscall(_glGetUniformLocation.Addr(), 2, uintptr(p.V), uintptr(unsafe.Pointer(c0)), 0)
issue34474KeepAlive(c)
return Uniform{int(u)}
}
func (c *Functions) InvalidateFramebuffer(target, attachment Enum) {
addr := _glInvalidateFramebuffer.Addr()
if addr == 0 {
// InvalidateFramebuffer is just a hint. Skip it if not supported.
return
}
syscall.Syscall(addr, 3, uintptr(target), 1, uintptr(unsafe.Pointer(&attachment)))
}
func (c *Functions) LinkProgram(p Program) {
syscall.Syscall(_glLinkProgram.Addr(), 1, uintptr(p.V), 0, 0)
}
func (c *Functions) PixelStorei(pname Enum, param int32) {
syscall.Syscall(_glPixelStorei.Addr(), 2, uintptr(pname), uintptr(param), 0)
}
func (f *Functions) ReadPixels(x, y, width, height int, format, ty Enum, data []byte) {
d0 := &data[0]
syscall.Syscall6(_glReadPixels.Addr(), uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(format), uintptr(ty), uintptr(unsafe.Pointer(d0)))
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 (c *Functions) TexImage2D(target Enum, level int, internalFormat int, width, height int, format, ty Enum, data []byte) {
if len(data) == 0 {
syscall.Syscall9(_glTexImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(internalFormat), uintptr(width), uintptr(height), 0, uintptr(format), uintptr(ty), 0)
} else {
d0 := &data[0]
syscall.Syscall9(_glTexImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(internalFormat), uintptr(width), uintptr(height), 0, uintptr(format), uintptr(ty), uintptr(unsafe.Pointer(d0)))
issue34474KeepAlive(d0)
}
}
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 (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 (c *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int) {
var norm uintptr
if normalized {
norm = 1
}
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)
}
func cString(s string) []byte {
b := make([]byte, len(s)+1)
copy(b, s)
return b
}
// issue34474KeepAlive calls runtime.KeepAlive as a
// workaround for golang.org/issue/34474.
func issue34474KeepAlive(v interface{}) {
runtime.KeepAlive(v)
}
+184
View File
@@ -0,0 +1,184 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gl
import (
"fmt"
"runtime"
"strings"
)
// SRGBFBO implements an intermediate sRGB FBO
// for gamma-correct rendering on platforms without
// sRGB enabled native framebuffers.
type SRGBFBO struct {
c *Functions
width, height int
frameBuffer Framebuffer
depthBuffer Renderbuffer
colorTex Texture
quad Buffer
prog Program
es3 bool
}
func NewSRGBFBO(f *Functions) (*SRGBFBO, error) {
var es3 bool
glVer := f.GetString(VERSION)
ver, err := ParseGLVersion(glVer)
if err != nil {
return nil, err
}
if ver[0] >= 3 {
es3 = true
} else {
exts := f.GetString(EXTENSIONS)
if !strings.Contains(exts, "EXT_sRGB") {
return nil, fmt.Errorf("no support for OpenGL ES 3 nor EXT_sRGB")
}
}
prog, err := CreateProgram(f, blitVSrc, blitFSrc, []string{"pos", "uv"})
if err != nil {
return nil, err
}
f.UseProgram(prog)
f.Uniform1i(GetUniformLocation(f, prog, "tex"), 0)
s := &SRGBFBO{
c: f,
es3: es3,
prog: prog,
frameBuffer: f.CreateFramebuffer(),
colorTex: f.CreateTexture(),
depthBuffer: f.CreateRenderbuffer(),
}
s.quad = f.CreateBuffer()
f.BindBuffer(ARRAY_BUFFER, s.quad)
f.BufferData(ARRAY_BUFFER,
BytesView([]float32{
-1, +1, 0, 1,
+1, +1, 1, 1,
-1, -1, 0, 0,
+1, -1, 1, 0,
}),
STATIC_DRAW)
f.BindTexture(TEXTURE_2D, s.colorTex)
f.TexParameteri(TEXTURE_2D, TEXTURE_WRAP_S, CLAMP_TO_EDGE)
f.TexParameteri(TEXTURE_2D, TEXTURE_WRAP_T, CLAMP_TO_EDGE)
f.TexParameteri(TEXTURE_2D, TEXTURE_MAG_FILTER, NEAREST)
f.TexParameteri(TEXTURE_2D, TEXTURE_MIN_FILTER, NEAREST)
return s, nil
}
func (s *SRGBFBO) Blit() {
s.c.BindFramebuffer(FRAMEBUFFER, Framebuffer{})
s.c.ClearColor(1, 0, 1, 1)
s.c.Clear(COLOR_BUFFER_BIT)
s.c.UseProgram(s.prog)
s.c.BindTexture(TEXTURE_2D, s.colorTex)
s.c.BindBuffer(ARRAY_BUFFER, s.quad)
s.c.VertexAttribPointer(0 /* pos */, 2, FLOAT, false, 4*4, 0)
s.c.VertexAttribPointer(1 /* uv */, 2, FLOAT, false, 4*4, 4*2)
s.c.EnableVertexAttribArray(0)
s.c.EnableVertexAttribArray(1)
s.c.DrawArrays(TRIANGLE_STRIP, 0, 4)
s.c.BindTexture(TEXTURE_2D, Texture{})
s.c.DisableVertexAttribArray(0)
s.c.DisableVertexAttribArray(1)
s.c.BindFramebuffer(FRAMEBUFFER, s.frameBuffer)
s.c.InvalidateFramebuffer(FRAMEBUFFER, COLOR_ATTACHMENT0)
s.c.InvalidateFramebuffer(FRAMEBUFFER, DEPTH_ATTACHMENT)
// The Android emulator requires framebuffer 0 bound at eglSwapBuffer time.
// Bind the sRGB framebuffer again in afterPresent.
s.c.BindFramebuffer(FRAMEBUFFER, Framebuffer{})
}
func (s *SRGBFBO) AfterPresent() {
s.c.BindFramebuffer(FRAMEBUFFER, s.frameBuffer)
}
func (s *SRGBFBO) Refresh(w, h int) error {
s.width, s.height = w, h
if w == 0 || h == 0 {
return nil
}
s.c.BindTexture(TEXTURE_2D, s.colorTex)
if s.es3 {
s.c.TexImage2D(TEXTURE_2D, 0, SRGB8_ALPHA8, w, h, RGBA, UNSIGNED_BYTE, nil)
} else /* EXT_sRGB */ {
s.c.TexImage2D(TEXTURE_2D, 0, SRGB_ALPHA_EXT, w, h, SRGB_ALPHA_EXT, UNSIGNED_BYTE, nil)
}
currentRB := Renderbuffer(s.c.GetBinding(RENDERBUFFER_BINDING))
s.c.BindRenderbuffer(RENDERBUFFER, s.depthBuffer)
s.c.RenderbufferStorage(RENDERBUFFER, DEPTH_COMPONENT16, w, h)
s.c.BindRenderbuffer(RENDERBUFFER, currentRB)
s.c.BindFramebuffer(FRAMEBUFFER, s.frameBuffer)
s.c.FramebufferTexture2D(FRAMEBUFFER, COLOR_ATTACHMENT0, TEXTURE_2D, s.colorTex, 0)
s.c.FramebufferRenderbuffer(FRAMEBUFFER, DEPTH_ATTACHMENT, RENDERBUFFER, s.depthBuffer)
if st := s.c.CheckFramebufferStatus(FRAMEBUFFER); st != FRAMEBUFFER_COMPLETE {
return fmt.Errorf("sRGB framebuffer incomplete (%dx%d), status: %#x error: %x", s.width, s.height, st, s.c.GetError())
}
if runtime.GOOS == "js" {
// With macOS Safari, rendering to and then reading from a SRGB8_ALPHA8
// texture result in twice gamma corrected colors. Using a plain RGBA
// texture seems to work.
s.c.ClearColor(.5, .5, .5, 1.0)
s.c.Clear(COLOR_BUFFER_BIT)
var pixel [4]byte
s.c.ReadPixels(0, 0, 1, 1, RGBA, UNSIGNED_BYTE, pixel[:])
if pixel[0] == 128 { // Correct sRGB color value is ~188
s.c.TexImage2D(TEXTURE_2D, 0, RGBA, w, h, RGBA, UNSIGNED_BYTE, nil)
if st := s.c.CheckFramebufferStatus(FRAMEBUFFER); st != FRAMEBUFFER_COMPLETE {
return fmt.Errorf("fallback RGBA framebuffer incomplete (%dx%d), status: %#x error: %x", s.width, s.height, st, s.c.GetError())
}
}
}
return nil
}
func (s *SRGBFBO) Release() {
s.c.DeleteFramebuffer(s.frameBuffer)
s.c.DeleteTexture(s.colorTex)
s.c.DeleteRenderbuffer(s.depthBuffer)
}
const (
blitVSrc = `
#version 100
precision highp float;
attribute vec2 pos;
attribute vec2 uv;
varying vec2 vUV;
void main() {
gl_Position = vec4(pos, 0, 1);
vUV = uv;
}
`
blitFSrc = `
#version 100
precision mediump float;
uniform sampler2D tex;
varying vec2 vUV;
vec3 gamma(vec3 rgb) {
vec3 exp = vec3(1.055)*pow(rgb, vec3(0.41666)) - vec3(0.055);
vec3 lin = rgb * vec3(12.92);
bvec3 cut = lessThan(rgb, vec3(0.0031308));
return vec3(cut.r ? lin.r : exp.r, cut.g ? lin.g : exp.g, cut.b ? lin.b : exp.b);
}
void main() {
vec4 col = texture2D(tex, vUV);
vec3 rgb = col.rgb;
rgb = gamma(rgb);
gl_FragColor = vec4(rgb, col.a);
}
`
)
+19
View File
@@ -0,0 +1,19 @@
// +build !js
package gl
type (
Buffer struct{ V uint }
Framebuffer struct{ V uint }
Program struct{ V uint }
Renderbuffer struct{ V uint }
Shader struct{ V uint }
Texture struct{ V uint }
Query struct{ V uint }
Uniform struct{ V int }
Object struct{ V uint }
)
func (u Uniform) Valid() bool {
return u.V != -1
}
+21
View File
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gl
import "syscall/js"
type (
Buffer js.Value
Framebuffer js.Value
Program js.Value
Renderbuffer js.Value
Shader js.Value
Texture js.Value
Query js.Value
Uniform js.Value
Object js.Value
)
func (u Uniform) Valid() bool {
return js.Value(u) != js.Null()
}
+114
View File
@@ -0,0 +1,114 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gl
import (
"errors"
"fmt"
"reflect"
"strings"
"unsafe"
)
func CreateProgram(ctx *Functions, vsSrc, fsSrc string, attribs []string) (Program, error) {
vs, err := createShader(ctx, VERTEX_SHADER, vsSrc)
if err != nil {
return Program{}, err
}
defer ctx.DeleteShader(vs)
fs, err := createShader(ctx, FRAGMENT_SHADER, fsSrc)
if err != nil {
return Program{}, err
}
defer ctx.DeleteShader(fs)
prog := ctx.CreateProgram()
if prog == (Program{}) {
return Program{}, errors.New("glCreateProgram failed")
}
ctx.AttachShader(prog, vs)
ctx.AttachShader(prog, fs)
for i, a := range attribs {
ctx.BindAttribLocation(prog, Attrib(i), a)
}
ctx.LinkProgram(prog)
if ctx.GetProgrami(prog, LINK_STATUS) == 0 {
log := ctx.GetProgramInfoLog(prog)
ctx.DeleteProgram(prog)
return Program{}, fmt.Errorf("program link failed: %s", strings.TrimSpace(log))
}
return prog, nil
}
func GetUniformLocation(ctx *Functions, prog Program, name string) Uniform {
loc := ctx.GetUniformLocation(prog, name)
if !loc.Valid() {
panic(fmt.Errorf("uniform %s not found", name))
}
return loc
}
func createShader(ctx *Functions, typ Enum, src string) (Shader, error) {
sh := ctx.CreateShader(typ)
if sh == (Shader{}) {
return Shader{}, errors.New("glCreateShader failed")
}
ctx.ShaderSource(sh, src)
ctx.CompileShader(sh)
if ctx.GetShaderi(sh, COMPILE_STATUS) == 0 {
log := ctx.GetShaderInfoLog(sh)
ctx.DeleteShader(sh)
return Shader{}, fmt.Errorf("shader compilation failed: %s", strings.TrimSpace(log))
}
return sh, nil
}
// BytesView returns a byte slice view of a slice.
func BytesView(s interface{}) []byte {
v := reflect.ValueOf(s)
first := v.Index(0)
sz := int(first.Type().Size())
return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
Data: uintptr(unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(first.UnsafeAddr())))),
Len: v.Len() * sz,
Cap: v.Cap() * sz,
}))
}
func ParseGLVersion(glVer string) ([2]int, error) {
var ver [2]int
if _, err := fmt.Sscanf(glVer, "OpenGL ES %d.%d", &ver[0], &ver[1]); err == nil {
return ver, nil
} else if _, err := fmt.Sscanf(glVer, "WebGL %d.%d", &ver[0], &ver[1]); err == nil {
// WebGL major version v corresponds to OpenGL ES version v + 1
ver[0]++
return ver, nil
} else if _, err := fmt.Sscanf(glVer, "%d.%d", &ver[0], &ver[1]); err == nil {
return ver, nil
}
return ver, fmt.Errorf("failed to parse OpenGL ES version (%s)", glVer)
}
func SliceOf(s uintptr) []byte {
if s == 0 {
return nil
}
sh := reflect.SliceHeader{
Data: s,
Len: 1 << 30,
Cap: 1 << 30,
}
return *(*[]byte)(unsafe.Pointer(&sh))
}
// GoString convert a NUL-terminated C string
// to a Go string.
func GoString(s []byte) string {
i := 0
for {
if s[i] == 0 {
break
}
i++
}
return string(s[:i])
}
+18
View File
@@ -0,0 +1,18 @@
package gl
import (
"testing"
)
func TestGoString(t *testing.T) {
tests := [][2]string{
{"Hello\x00", "Hello"},
{"\x00", ""},
}
for _, test := range tests {
got := GoString([]byte(test[0]))
if exp := test[1]; exp != got {
t.Errorf("expected %q got %q", exp, got)
}
}
}
+109
View File
@@ -0,0 +1,109 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
import (
"fmt"
"gioui.org/internal/ops"
)
type resourceCache struct {
res map[interface{}]resource
newRes map[interface{}]resource
}
// opCache is like a resourceCache using the concrete Key
// key type to avoid allocations.
type opCache struct {
res map[ops.Key]resource
newRes map[ops.Key]resource
}
func newResourceCache() *resourceCache {
return &resourceCache{
res: make(map[interface{}]resource),
newRes: make(map[interface{}]resource),
}
}
func (r *resourceCache) get(key interface{}) (resource, bool) {
v, exists := r.res[key]
if exists {
r.newRes[key] = v
}
return v, exists
}
func (r *resourceCache) put(key interface{}, val resource) {
if _, exists := r.newRes[key]; exists {
panic(fmt.Errorf("key exists, %p", key))
}
r.res[key] = val
r.newRes[key] = val
}
func (r *resourceCache) frame(ctx *context) {
for k, v := range r.res {
if _, exists := r.newRes[k]; !exists {
delete(r.res, k)
v.release(ctx)
}
}
for k, v := range r.newRes {
delete(r.newRes, k)
r.res[k] = v
}
}
func (r *resourceCache) release(ctx *context) {
for _, v := range r.newRes {
v.release(ctx)
}
r.newRes = nil
r.res = nil
}
func newOpCache() *opCache {
return &opCache{
res: make(map[ops.Key]resource),
newRes: make(map[ops.Key]resource),
}
}
func (r *opCache) get(key ops.Key) (resource, bool) {
v, exists := r.res[key]
if exists {
r.newRes[key] = v
}
return v, exists
}
func (r *opCache) put(key ops.Key, val resource) {
if _, exists := r.newRes[key]; exists {
panic(fmt.Errorf("key exists, %#v", key))
}
r.res[key] = val
r.newRes[key] = val
}
func (r *opCache) frame(ctx *context) {
for k, v := range r.res {
if _, exists := r.newRes[k]; !exists {
delete(r.res, k)
v.release(ctx)
}
}
for k, v := range r.newRes {
delete(r.newRes, k)
r.res[k] = v
}
}
func (r *opCache) release(ctx *context) {
for _, v := range r.newRes {
v.release(ctx)
}
r.newRes = nil
r.res = nil
}
+125
View File
@@ -0,0 +1,125 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
import (
"errors"
"strings"
"gioui.org/app/internal/gl"
)
type context struct {
caps caps
*gl.Functions
}
type caps struct {
EXT_disjoint_timer_query bool
// floatTriple holds the settings for floating point
// textures.
floatTriple textureTriple
// Single channel alpha textures.
alphaTriple textureTriple
srgbaTriple textureTriple
}
// textureTriple holds the type settings for
// a TexImage2D call.
type textureTriple struct {
internalFormat int
format gl.Enum
typ gl.Enum
}
func newContext(glctx gl.Context) (*context, error) {
ctx := &context{
Functions: glctx.Functions(),
}
exts := strings.Split(ctx.GetString(gl.EXTENSIONS), " ")
glVer := ctx.GetString(gl.VERSION)
ver, err := gl.ParseGLVersion(glVer)
if err != nil {
return nil, err
}
floatTriple, err := floatTripleFor(ctx, ver, exts)
if err != nil {
return nil, err
}
srgbaTriple, err := srgbaTripleFor(ver, exts)
if err != nil {
return nil, err
}
hasTimers := hasExtension(exts, "GL_EXT_disjoint_timer_query_webgl2") || hasExtension(exts, "GL_EXT_disjoint_timer_query")
ctx.caps = caps{
EXT_disjoint_timer_query: hasTimers,
floatTriple: floatTriple,
alphaTriple: alphaTripleFor(ver),
srgbaTriple: srgbaTriple,
}
return ctx, nil
}
// floatTripleFor determines the best texture triple for floating point FBOs.
func floatTripleFor(ctx *context, ver [2]int, exts []string) (textureTriple, error) {
var triples []textureTriple
if ver[0] >= 3 {
triples = append(triples, textureTriple{gl.R16F, gl.Enum(gl.RED), gl.Enum(gl.HALF_FLOAT)})
}
if hasExtension(exts, "GL_OES_texture_half_float") || hasExtension(exts, "EXT_color_buffer_half_float") {
triples = append(triples, textureTriple{gl.RGBA, gl.Enum(gl.RGBA), gl.Enum(gl.HALF_FLOAT_OES)})
}
if hasExtension(exts, "GL_OES_texture_float") || hasExtension(exts, "GL_EXT_color_buffer_float") {
triples = append(triples, textureTriple{gl.RGBA, gl.Enum(gl.RGBA), gl.Enum(gl.FLOAT)})
}
tex := ctx.CreateTexture()
defer ctx.DeleteTexture(tex)
ctx.BindTexture(gl.TEXTURE_2D, tex)
ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
fbo := ctx.CreateFramebuffer()
defer ctx.DeleteFramebuffer(fbo)
defFBO := gl.Framebuffer(ctx.GetBinding(gl.FRAMEBUFFER_BINDING))
ctx.BindFramebuffer(gl.FRAMEBUFFER, fbo)
defer ctx.BindFramebuffer(gl.FRAMEBUFFER, defFBO)
for _, tt := range triples {
const size = 256
ctx.TexImage2D(gl.TEXTURE_2D, 0, tt.internalFormat, size, size, tt.format, tt.typ, nil)
ctx.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0)
if st := ctx.CheckFramebufferStatus(gl.FRAMEBUFFER); st == gl.FRAMEBUFFER_COMPLETE {
return tt, nil
}
}
return textureTriple{}, errors.New("floating point fbos not supported")
}
func srgbaTripleFor(ver [2]int, exts []string) (textureTriple, error) {
switch {
case ver[0] >= 3:
return textureTriple{gl.SRGB8_ALPHA8, gl.Enum(gl.RGBA), gl.Enum(gl.UNSIGNED_BYTE)}, nil
case hasExtension(exts, "GL_EXT_sRGB"):
return textureTriple{gl.SRGB_ALPHA_EXT, gl.Enum(gl.SRGB_ALPHA_EXT), gl.Enum(gl.UNSIGNED_BYTE)}, nil
default:
return textureTriple{}, errors.New("no sRGB texture formats found")
}
}
func alphaTripleFor(ver [2]int) textureTriple {
intf, f := gl.R8, gl.Enum(gl.RED)
if ver[0] < 3 {
// R8, RED not supported on OpenGL ES 2.0.
intf, f = gl.LUMINANCE, gl.Enum(gl.LUMINANCE)
}
return textureTriple{intf, f, gl.UNSIGNED_BYTE}
}
func hasExtension(exts []string, ext string) bool {
for _, e := range exts {
if ext == e {
return true
}
}
return false
}
File diff suppressed because it is too large Load Diff
+85
View File
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
import (
"image"
)
// packer packs a set of many smaller rectangles into
// much fewer larger atlases.
type packer struct {
maxDim int
spaces []image.Rectangle
sizes []image.Point
pos image.Point
}
type placement struct {
Idx int
Pos image.Point
}
// add adds the given rectangle to the atlases and
// return the allocated position.
func (p *packer) add(s image.Point) (placement, bool) {
if place, ok := p.tryAdd(s); ok {
return place, true
}
p.newPage()
return p.tryAdd(s)
}
func (p *packer) clear() {
p.sizes = p.sizes[:0]
p.spaces = p.spaces[:0]
}
func (p *packer) newPage() {
p.pos = image.Point{}
p.sizes = append(p.sizes, image.Point{})
p.spaces = p.spaces[:0]
p.spaces = append(p.spaces, image.Rectangle{
Max: image.Point{X: p.maxDim, Y: p.maxDim},
})
}
func (p *packer) tryAdd(s image.Point) (placement, bool) {
// Go backwards to prioritize smaller spaces first.
for i := len(p.spaces) - 1; i >= 0; i-- {
space := p.spaces[i]
rightSpace := space.Dx() - s.X
bottomSpace := space.Dy() - s.Y
if rightSpace >= 0 && bottomSpace >= 0 {
// Remove space.
p.spaces[i] = p.spaces[len(p.spaces)-1]
p.spaces = p.spaces[:len(p.spaces)-1]
// Put s in the top left corner and add the (at most)
// two smaller spaces.
pos := space.Min
if bottomSpace > 0 {
p.spaces = append(p.spaces, image.Rectangle{
Min: image.Point{X: pos.X, Y: pos.Y + s.Y},
Max: image.Point{X: space.Max.X, Y: space.Max.Y},
})
}
if rightSpace > 0 {
p.spaces = append(p.spaces, image.Rectangle{
Min: image.Point{X: pos.X + s.X, Y: pos.Y},
Max: image.Point{X: space.Max.X, Y: pos.Y + s.Y},
})
}
idx := len(p.sizes) - 1
size := &p.sizes[idx]
if x := pos.X + s.X; x > size.X {
size.X = x
}
if y := pos.Y + s.Y; y > size.Y {
size.Y = y
}
return placement{Idx: idx, Pos: pos}, true
}
}
return placement{}, false
}
+573
View File
@@ -0,0 +1,573 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
// GPU accelerated path drawing using the algorithms from
// Pathfinder (https://github.com/pcwalton/pathfinder).
import (
"image"
"unsafe"
"gioui.org/app/internal/gl"
"gioui.org/f32"
"gioui.org/internal/path"
)
type pather struct {
ctx *context
viewport image.Point
stenciler *stenciler
coverer *coverer
}
type coverer struct {
ctx *context
prog [2]gl.Program
vars [2]struct {
z gl.Uniform
uScale, uOffset gl.Uniform
uUVScale, uUVOffset gl.Uniform
uCoverUVScale, uCoverUVOffset gl.Uniform
uColor gl.Uniform
}
}
type stenciler struct {
ctx *context
defFBO gl.Framebuffer
indexBufQuads int
prog gl.Program
iprog gl.Program
fbos fboSet
intersections fboSet
uScale, uOffset gl.Uniform
uPathOffset gl.Uniform
uIntersectUVOffset gl.Uniform
uIntersectUVScale gl.Uniform
indexBuf gl.Buffer
}
type fboSet struct {
fbos []stencilFBO
}
type stencilFBO struct {
size image.Point
fbo gl.Framebuffer
tex gl.Texture
}
type pathData struct {
ncurves int
data gl.Buffer
}
var (
pathAttribs = []string{"corner", "maxy", "from", "ctrl", "to"}
attribPathCorner gl.Attrib = 0
attribPathMaxY gl.Attrib = 1
attribPathFrom gl.Attrib = 2
attribPathCtrl gl.Attrib = 3
attribPathTo gl.Attrib = 4
intersectAttribs = []string{"pos", "uv"}
)
func newPather(ctx *context) *pather {
return &pather{
ctx: ctx,
stenciler: newStenciler(ctx),
coverer: newCoverer(ctx),
}
}
func newCoverer(ctx *context) *coverer {
prog, err := createColorPrograms(ctx, coverVSrc, coverFSrc)
if err != nil {
panic(err)
}
c := &coverer{
ctx: ctx,
prog: prog,
}
for i, prog := range prog {
ctx.UseProgram(prog)
switch materialType(i) {
case materialTexture:
uTex := gl.GetUniformLocation(ctx.Functions, prog, "tex")
ctx.Uniform1i(uTex, 0)
c.vars[i].uUVScale = gl.GetUniformLocation(ctx.Functions, prog, "uvScale")
c.vars[i].uUVOffset = gl.GetUniformLocation(ctx.Functions, prog, "uvOffset")
case materialColor:
c.vars[i].uColor = gl.GetUniformLocation(ctx.Functions, prog, "color")
}
uCover := gl.GetUniformLocation(ctx.Functions, prog, "cover")
ctx.Uniform1i(uCover, 1)
c.vars[i].z = gl.GetUniformLocation(ctx.Functions, prog, "z")
c.vars[i].uScale = gl.GetUniformLocation(ctx.Functions, prog, "scale")
c.vars[i].uOffset = gl.GetUniformLocation(ctx.Functions, prog, "offset")
c.vars[i].uCoverUVScale = gl.GetUniformLocation(ctx.Functions, prog, "uvCoverScale")
c.vars[i].uCoverUVOffset = gl.GetUniformLocation(ctx.Functions, prog, "uvCoverOffset")
}
return c
}
func newStenciler(ctx *context) *stenciler {
defFBO := gl.Framebuffer(ctx.GetBinding(gl.FRAMEBUFFER_BINDING))
prog, err := gl.CreateProgram(ctx.Functions, stencilVSrc, stencilFSrc, pathAttribs)
if err != nil {
panic(err)
}
ctx.UseProgram(prog)
iprog, err := gl.CreateProgram(ctx.Functions, intersectVSrc, intersectFSrc, intersectAttribs)
if err != nil {
panic(err)
}
coverLoc := gl.GetUniformLocation(ctx.Functions, iprog, "cover")
ctx.UseProgram(iprog)
ctx.Uniform1i(coverLoc, 0)
return &stenciler{
ctx: ctx,
defFBO: defFBO,
prog: prog,
iprog: iprog,
uScale: gl.GetUniformLocation(ctx.Functions, prog, "scale"),
uOffset: gl.GetUniformLocation(ctx.Functions, prog, "offset"),
uPathOffset: gl.GetUniformLocation(ctx.Functions, prog, "pathOffset"),
uIntersectUVScale: gl.GetUniformLocation(ctx.Functions, iprog, "uvScale"),
uIntersectUVOffset: gl.GetUniformLocation(ctx.Functions, iprog, "uvOffset"),
indexBuf: ctx.CreateBuffer(),
}
}
func (s *fboSet) resize(ctx *context, sizes []image.Point) {
// Add fbos.
for i := len(s.fbos); i < len(sizes); i++ {
tex := ctx.CreateTexture()
ctx.BindTexture(gl.TEXTURE_2D, tex)
ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
fbo := ctx.CreateFramebuffer()
s.fbos = append(s.fbos, stencilFBO{
fbo: fbo,
tex: tex,
})
}
// Resize fbos.
for i, sz := range sizes {
f := &s.fbos[i]
// Resizing or recreating FBOs can introduce rendering stalls.
// Avoid if the space waste is not too high.
resize := sz.X > f.size.X || sz.Y > f.size.Y
waste := float32(sz.X*sz.Y) / float32(f.size.X*f.size.Y)
resize = resize || waste > 1.2
if resize {
f.size = sz
ctx.BindTexture(gl.TEXTURE_2D, f.tex)
tt := ctx.caps.floatTriple
ctx.TexImage2D(gl.TEXTURE_2D, 0, tt.internalFormat, sz.X, sz.Y, tt.format, tt.typ, nil)
ctx.BindFramebuffer(gl.FRAMEBUFFER, f.fbo)
ctx.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, f.tex, 0)
}
}
// Delete extra fbos.
s.delete(ctx, len(sizes))
}
func (s *fboSet) invalidate(ctx *context) {
for _, f := range s.fbos {
ctx.BindFramebuffer(gl.FRAMEBUFFER, f.fbo)
ctx.InvalidateFramebuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0)
}
}
func (s *fboSet) delete(ctx *context, idx int) {
for i := idx; i < len(s.fbos); i++ {
f := s.fbos[i]
ctx.DeleteFramebuffer(f.fbo)
ctx.DeleteTexture(f.tex)
}
s.fbos = s.fbos[:idx]
}
func (s *stenciler) release() {
s.fbos.delete(s.ctx, 0)
s.ctx.DeleteProgram(s.prog)
s.ctx.DeleteBuffer(s.indexBuf)
}
func (p *pather) release() {
p.stenciler.release()
p.coverer.release()
}
func (c *coverer) release() {
for _, p := range c.prog {
c.ctx.DeleteProgram(p)
}
}
func buildPath(ctx *context, p []byte) *pathData {
buf := ctx.CreateBuffer()
ctx.BindBuffer(gl.ARRAY_BUFFER, buf)
ctx.BufferData(gl.ARRAY_BUFFER, p, gl.STATIC_DRAW)
return &pathData{
ncurves: len(p) / path.VertStride,
data: buf,
}
}
func (p *pathData) release(ctx *context) {
ctx.DeleteBuffer(p.data)
}
func (p *pather) begin(sizes []image.Point) {
p.stenciler.begin(sizes)
}
func (p *pather) end() {
p.stenciler.end()
}
func (p *pather) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data *pathData) {
p.stenciler.stencilPath(bounds, offset, uv, data)
}
func (s *stenciler) beginIntersect(sizes []image.Point) {
s.ctx.ActiveTexture(gl.TEXTURE1)
s.ctx.BindTexture(gl.TEXTURE_2D, gl.Texture{})
s.ctx.ActiveTexture(gl.TEXTURE0)
s.ctx.BlendFunc(gl.DST_COLOR, gl.ZERO)
// 8 bit coverage is enough, but OpenGL ES only supports single channel
// floating point formats. Replace with GL_RGB+GL_UNSIGNED_BYTE if
// no floating point support is available.
s.intersections.resize(s.ctx, sizes)
s.ctx.ClearColor(1.0, 0.0, 0.0, 0.0)
s.ctx.UseProgram(s.iprog)
}
func (s *stenciler) endIntersect() {
s.ctx.BindFramebuffer(gl.FRAMEBUFFER, s.defFBO)
}
func (s *stenciler) invalidateFBO() {
s.intersections.invalidate(s.ctx)
s.fbos.invalidate(s.ctx)
s.ctx.BindFramebuffer(gl.FRAMEBUFFER, s.defFBO)
}
func (s *stenciler) cover(idx int) stencilFBO {
return s.fbos.fbos[idx]
}
func (s *stenciler) begin(sizes []image.Point) {
s.ctx.ActiveTexture(gl.TEXTURE1)
s.ctx.BindTexture(gl.TEXTURE_2D, gl.Texture{})
s.ctx.ActiveTexture(gl.TEXTURE0)
s.ctx.BlendFunc(gl.ONE, gl.ONE)
s.fbos.resize(s.ctx, sizes)
s.ctx.ClearColor(0.0, 0.0, 0.0, 0.0)
s.ctx.UseProgram(s.prog)
s.ctx.EnableVertexAttribArray(attribPathCorner)
s.ctx.EnableVertexAttribArray(attribPathMaxY)
s.ctx.EnableVertexAttribArray(attribPathFrom)
s.ctx.EnableVertexAttribArray(attribPathCtrl)
s.ctx.EnableVertexAttribArray(attribPathTo)
s.ctx.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, s.indexBuf)
}
func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data *pathData) {
s.ctx.BindBuffer(gl.ARRAY_BUFFER, data.data)
s.ctx.Viewport(uv.X, uv.Y, bounds.Dx(), bounds.Dy())
// Transform UI coordinates to OpenGL coordinates.
texSize := f32.Point{X: float32(bounds.Dx()), Y: float32(bounds.Dy())}
scale := f32.Point{X: 2 / texSize.X, Y: 2 / texSize.Y}
orig := f32.Point{X: -1 - float32(bounds.Min.X)*2/texSize.X, Y: -1 - float32(bounds.Min.Y)*2/texSize.Y}
s.ctx.Uniform2f(s.uScale, scale.X, scale.Y)
s.ctx.Uniform2f(s.uOffset, orig.X, orig.Y)
s.ctx.Uniform2f(s.uPathOffset, offset.X, offset.Y)
// Draw in batches that fit in uint16 indices.
start := 0
nquads := data.ncurves / 4
for start < nquads {
batch := nquads - start
if max := int(^uint16(0)) / 6; batch > max {
batch = max
}
// Enlarge VBO if necessary.
if batch > s.indexBufQuads {
indices := make([]uint16, batch*6)
for i := 0; i < batch; i++ {
i := uint16(i)
indices[i*6+0] = i*4 + 0
indices[i*6+1] = i*4 + 1
indices[i*6+2] = i*4 + 2
indices[i*6+3] = i*4 + 2
indices[i*6+4] = i*4 + 1
indices[i*6+5] = i*4 + 3
}
s.ctx.BufferData(gl.ELEMENT_ARRAY_BUFFER, gl.BytesView(indices), gl.STATIC_DRAW)
s.indexBufQuads = batch
}
off := path.VertStride * start * 4
s.ctx.VertexAttribPointer(attribPathCorner, 2, gl.SHORT, false, path.VertStride, off+int(unsafe.Offsetof((*(*path.Vertex)(nil)).CornerX)))
s.ctx.VertexAttribPointer(attribPathMaxY, 1, gl.FLOAT, false, path.VertStride, off+int(unsafe.Offsetof((*(*path.Vertex)(nil)).MaxY)))
s.ctx.VertexAttribPointer(attribPathFrom, 2, gl.FLOAT, false, path.VertStride, off+int(unsafe.Offsetof((*(*path.Vertex)(nil)).FromX)))
s.ctx.VertexAttribPointer(attribPathCtrl, 2, gl.FLOAT, false, path.VertStride, off+int(unsafe.Offsetof((*(*path.Vertex)(nil)).CtrlX)))
s.ctx.VertexAttribPointer(attribPathTo, 2, gl.FLOAT, false, path.VertStride, off+int(unsafe.Offsetof((*(*path.Vertex)(nil)).ToX)))
s.ctx.DrawElements(gl.TRIANGLES, batch*6, gl.UNSIGNED_SHORT, 0)
start += batch
}
}
func (s *stenciler) end() {
s.ctx.DisableVertexAttribArray(attribPathCorner)
s.ctx.DisableVertexAttribArray(attribPathMaxY)
s.ctx.DisableVertexAttribArray(attribPathFrom)
s.ctx.DisableVertexAttribArray(attribPathCtrl)
s.ctx.DisableVertexAttribArray(attribPathTo)
s.ctx.BindFramebuffer(gl.FRAMEBUFFER, s.defFBO)
}
func (p *pather) cover(z float32, mat materialType, col [4]float32, scale, off, uvScale, uvOff, coverScale, coverOff f32.Point) {
p.coverer.cover(z, mat, col, scale, off, uvScale, uvOff, coverScale, coverOff)
}
func (c *coverer) cover(z float32, mat materialType, col [4]float32, scale, off, uvScale, uvOff, coverScale, coverOff f32.Point) {
c.ctx.UseProgram(c.prog[mat])
switch mat {
case materialColor:
c.ctx.Uniform4f(c.vars[mat].uColor, col[0], col[1], col[2], col[3])
case materialTexture:
c.ctx.Uniform2f(c.vars[mat].uUVScale, uvScale.X, uvScale.Y)
c.ctx.Uniform2f(c.vars[mat].uUVOffset, uvOff.X, uvOff.Y)
}
c.ctx.Uniform1f(c.vars[mat].z, z)
c.ctx.Uniform2f(c.vars[mat].uScale, scale.X, scale.Y)
c.ctx.Uniform2f(c.vars[mat].uOffset, off.X, off.Y)
c.ctx.Uniform2f(c.vars[mat].uCoverUVScale, coverScale.X, coverScale.Y)
c.ctx.Uniform2f(c.vars[mat].uCoverUVOffset, coverOff.X, coverOff.Y)
c.ctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
}
const stencilVSrc = `
#version 100
precision highp float;
uniform vec2 scale;
uniform vec2 offset;
uniform vec2 pathOffset;
attribute vec2 corner;
attribute float maxy;
attribute vec2 from;
attribute vec2 ctrl;
attribute vec2 to;
varying vec2 vFrom;
varying vec2 vCtrl;
varying vec2 vTo;
void main() {
// Add a one pixel overlap so curve quads cover their
// entire curves. Could use conservative rasterization
// if available.
vec2 from = from + pathOffset;
vec2 ctrl = ctrl + pathOffset;
vec2 to = to + pathOffset;
float maxy = maxy + pathOffset.y;
vec2 pos;
if (corner.x > 0.0) {
// East.
pos.x = max(max(from.x, ctrl.x), to.x)+1.0;
} else {
// West.
pos.x = min(min(from.x, ctrl.x), to.x)-1.0;
}
if (corner.y > 0.0) {
// North.
pos.y = maxy + 1.0;
} else {
// South.
pos.y = min(min(from.y, ctrl.y), to.y) - 1.0;
}
vFrom = from-pos;
vCtrl = ctrl-pos;
vTo = to-pos;
pos *= scale;
pos += offset;
gl_Position = vec4(pos, 1, 1);
}
`
const stencilFSrc = `
#version 100
precision mediump float;
varying vec2 vFrom;
varying vec2 vCtrl;
varying vec2 vTo;
uniform sampler2D areaLUT;
void main() {
float dx = vTo.x - vFrom.x;
// Sort from and to in increasing order so the root below
// is always the positive square root, if any.
// We need the direction of the curve below, so this can't be
// done from the vertex shader.
bool increasing = vTo.x >= vFrom.x;
vec2 left = increasing ? vFrom : vTo;
vec2 right = increasing ? vTo : vFrom;
// The signed horizontal extent of the fragment.
vec2 extent = clamp(vec2(vFrom.x, vTo.x), -0.5, 0.5);
// Find the t where the curve crosses the middle of the
// extent, x₀.
// Given the Bézier curve with x coordinates P₀, P₁, P₂
// where P₀ is at the origin, its x coordinate in t
// is given by:
//
// x(t) = 2(1-t)tP₁ + t²P₂
//
// Rearranging:
//
// x(t) = (P₂ - 2P₁)t² + 2P₁t
//
// Setting x(t) = x₀ and using Muller's quadratic formula ("Citardauq")
// for robustnesss,
//
// t = 2x₀/(2P₁±√(4P₁²+4(P₂-2P₁)x₀))
//
// which simplifies to
//
// t = x₀/(P₁±√(P₁²+(P₂-2P₁)x₀))
//
// Setting v = P₂-P₁,
//
// t = x₀/(P₁±√(P₁²+(v-P₁)x₀))
//
// t lie in [0; 1]; P₂ ≥ P₁ and P₁ ≥ 0 since we split curves where
// the control point lies before the start point or after the end point.
// It can then be shown that only the positive square root is valid.
float midx = mix(extent.x, extent.y, 0.5);
float x0 = midx - left.x;
vec2 p1 = vCtrl - left;
vec2 v = right - vCtrl;
float t = x0/(p1.x+sqrt(p1.x*p1.x+(v.x-p1.x)*x0));
// Find y(t) on the curve.
float y = mix(mix(left.y, vCtrl.y, t), mix(vCtrl.y, right.y, t), t);
// And the slope.
vec2 d_half = mix(p1, v, t);
float dy = d_half.y/d_half.x;
// Together, y and dy form a line approximation.
// Compute the fragment area above the line.
// The area is symmetric around dy = 0. Scale slope with extent width.
float width = extent.y - extent.x;
dy = abs(dy*width);
vec4 sides = vec4(dy*+0.5 + y, dy*-0.5 + y, (+0.5-y)/dy, (-0.5-y)/dy);
sides = clamp(sides+0.5, 0.0, 1.0);
float area = 0.5*(sides.z - sides.z*sides.y + 1.0 - sides.x+sides.x*sides.w);
area *= width;
// Work around issue #13.
if (width == 0.0)
area = 0.0;
gl_FragColor.r = area;
}
`
const coverVSrc = `
#version 100
precision highp float;
uniform float z;
uniform vec2 scale;
uniform vec2 offset;
uniform vec2 uvScale;
uniform vec2 uvOffset;
uniform vec2 uvCoverScale;
uniform vec2 uvCoverOffset;
attribute vec2 pos;
varying vec2 vCoverUV;
attribute vec2 uv;
varying vec2 vUV;
void main() {
gl_Position = vec4(pos*scale + offset, z, 1);
vUV = uv*uvScale + uvOffset;
vCoverUV = uv*uvCoverScale+uvCoverOffset;
}
`
const coverFSrc = `
#version 100
precision mediump float;
// Use high precision to be pixel accurate for
// large cover atlases.
varying highp vec2 vCoverUV;
uniform sampler2D cover;
varying vec2 vUV;
HEADER
void main() {
gl_FragColor = GET_COLOR;
float cover = abs(texture2D(cover, vCoverUV).r);
gl_FragColor *= cover;
}
`
const intersectVSrc = `
#version 100
precision highp float;
attribute vec2 pos;
attribute vec2 uv;
uniform vec2 uvScale;
uniform vec2 uvOffset;
varying vec2 vUV;
void main() {
vec2 p = pos;
p.y = -p.y;
gl_Position = vec4(p, 0, 1);
vUV = uv*uvScale + uvOffset;
}
`
const intersectFSrc = `
#version 100
precision mediump float;
// Use high precision to be pixel accurate for
// large cover atlases.
varying highp vec2 vUV;
uniform sampler2D cover;
void main() {
float cover = abs(texture2D(cover, vUV).r);
gl_FragColor.r = cover;
}
`
+93
View File
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
import (
"time"
"gioui.org/app/internal/gl"
)
type timers struct {
ctx *context
timers []*timer
}
type timer struct {
Elapsed time.Duration
ctx *context
obj gl.Query
state timerState
}
type timerState uint8
const (
timerIdle timerState = iota
timerRunning
timerWaiting
)
func newTimers(ctx *context) *timers {
return &timers{
ctx: ctx,
}
}
func (t *timers) newTimer() *timer {
if t == nil {
return nil
}
tt := &timer{
ctx: t.ctx,
obj: t.ctx.CreateQuery(),
}
t.timers = append(t.timers, tt)
return tt
}
func (t *timer) begin() {
if t == nil || t.state != timerIdle {
return
}
t.ctx.BeginQuery(gl.TIME_ELAPSED_EXT, t.obj)
t.state = timerRunning
}
func (t *timer) end() {
if t == nil || t.state != timerRunning {
return
}
t.ctx.EndQuery(gl.TIME_ELAPSED_EXT)
t.state = timerWaiting
}
func (t *timers) ready() bool {
if t == nil {
return false
}
for _, tt := range t.timers {
if tt.state != timerWaiting {
return false
}
if t.ctx.GetQueryObjectuiv(tt.obj, gl.QUERY_RESULT_AVAILABLE) == 0 {
return false
}
}
for _, tt := range t.timers {
tt.state = timerIdle
nanos := t.ctx.GetQueryObjectuiv(tt.obj, gl.QUERY_RESULT)
tt.Elapsed = time.Duration(nanos)
}
return t.ctx.GetInteger(gl.GPU_DISJOINT_EXT) == 0
}
func (t *timers) release() {
if t == nil {
return
}
for _, tt := range t.timers {
t.ctx.DeleteQuery(tt.obj)
}
t.timers = nil
}
+149
View File
@@ -0,0 +1,149 @@
// SPDX-License-Identifier: Unlicense OR MIT
package input
import (
"gioui.org/ui"
"gioui.org/internal/opconst"
"gioui.org/internal/ops"
"gioui.org/key"
)
type TextInputState uint8
type keyQueue struct {
focus ui.Key
handlers map[ui.Key]*keyHandler
reader ops.Reader
state TextInputState
}
type keyHandler struct {
active bool
}
type listenerPriority uint8
const (
priNone listenerPriority = iota
priDefault
priCurrentFocus
priNewFocus
)
const (
TextInputKeep TextInputState = iota
TextInputClose
TextInputOpen
)
// InputState returns the last text input state as
// determined in Frame.
func (q *keyQueue) InputState() TextInputState {
return q.state
}
func (q *keyQueue) Frame(root *ui.Ops, events *handlerEvents) {
if q.handlers == nil {
q.handlers = make(map[ui.Key]*keyHandler)
}
for _, h := range q.handlers {
h.active = false
}
q.reader.Reset(root)
focus, pri, hide := q.resolveFocus(events)
for k, h := range q.handlers {
if !h.active {
delete(q.handlers, k)
if q.focus == k {
q.focus = nil
hide = true
}
}
}
if focus != q.focus {
if q.focus != nil {
events.Add(q.focus, key.FocusEvent{Focus: false})
}
q.focus = focus
if q.focus != nil {
events.Add(q.focus, key.FocusEvent{Focus: true})
} else {
hide = true
}
}
switch {
case pri == priNewFocus:
q.state = TextInputOpen
case hide:
q.state = TextInputClose
default:
q.state = TextInputKeep
}
}
func (q *keyQueue) Push(e ui.Event, events *handlerEvents) {
if q.focus != nil {
events.Add(q.focus, e)
}
}
func (q *keyQueue) resolveFocus(events *handlerEvents) (ui.Key, listenerPriority, bool) {
var k ui.Key
var pri listenerPriority
var hide bool
loop:
for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() {
switch opconst.OpType(encOp.Data[0]) {
case opconst.TypeKeyInput:
op := decodeKeyInputOp(encOp.Data, encOp.Refs)
var newPri listenerPriority
switch {
case op.Focus:
newPri = priNewFocus
case op.Key == q.focus:
newPri = priCurrentFocus
default:
newPri = priDefault
}
// Switch focus if higher priority or if focus requested.
if newPri.replaces(pri) {
k, pri = op.Key, newPri
}
h, ok := q.handlers[op.Key]
if !ok {
h = new(keyHandler)
q.handlers[op.Key] = h
// Reset the handler on (each) first appearance.
events.Set(op.Key, []ui.Event{key.FocusEvent{Focus: false}})
}
h.active = true
case opconst.TypeHideInput:
hide = true
case opconst.TypePush:
newK, newPri, h := q.resolveFocus(events)
hide = hide || h
if newPri.replaces(pri) {
k, pri = newK, newPri
}
case opconst.TypePop:
break loop
}
}
return k, pri, hide
}
func (p listenerPriority) replaces(p2 listenerPriority) bool {
// Favor earliest default focus or latest requested focus.
return p > p2 || p == p2 && p == priNewFocus
}
func decodeKeyInputOp(d []byte, refs []interface{}) key.InputOp {
if opconst.OpType(d[0]) != opconst.TypeKeyInput {
panic("invalid op")
}
return key.InputOp{
Focus: d[1] != 0,
Key: refs[0].(ui.Key),
}
}
+333
View File
@@ -0,0 +1,333 @@
// SPDX-License-Identifier: Unlicense OR MIT
package input
import (
"encoding/binary"
"image"
"gioui.org/ui"
"gioui.org/f32"
"gioui.org/internal/opconst"
"gioui.org/internal/ops"
"gioui.org/pointer"
)
type pointerQueue struct {
hitTree []hitNode
areas []areaNode
handlers map[ui.Key]*pointerHandler
pointers []pointerInfo
reader ops.Reader
scratch []ui.Key
}
type hitNode struct {
next int
area int
// Pass tracks the most recent PassOp mode.
pass bool
// For handler nodes.
key ui.Key
}
type pointerInfo struct {
id pointer.ID
pressed bool
handlers []ui.Key
}
type pointerHandler struct {
area int
active bool
transform ui.TransformOp
wantsGrab bool
}
type areaOp struct {
kind areaKind
rect image.Rectangle
}
type areaNode struct {
trans ui.TransformOp
next int
area areaOp
}
type areaKind uint8
const (
areaRect areaKind = iota
areaEllipse
)
func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents, t ui.TransformOp, area, node int, pass bool) {
for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
switch opconst.OpType(encOp.Data[0]) {
case opconst.TypePush:
q.collectHandlers(r, events, t, area, node, pass)
case opconst.TypePop:
return
case opconst.TypePass:
op := decodePassOp(encOp.Data)
pass = op.Pass
case opconst.TypeArea:
var op areaOp
op.Decode(encOp.Data)
q.areas = append(q.areas, areaNode{trans: t, next: area, area: op})
area = len(q.areas) - 1
q.hitTree = append(q.hitTree, hitNode{
next: node,
area: area,
pass: pass,
})
node = len(q.hitTree) - 1
case opconst.TypeTransform:
op := ops.DecodeTransformOp(encOp.Data)
t = t.Multiply(ui.TransformOp(op))
case opconst.TypePointerInput:
op := decodePointerInputOp(encOp.Data, encOp.Refs)
q.hitTree = append(q.hitTree, hitNode{
next: node,
area: area,
pass: pass,
key: op.Key,
})
node = len(q.hitTree) - 1
h, ok := q.handlers[op.Key]
if !ok {
h = new(pointerHandler)
q.handlers[op.Key] = h
events.Set(op.Key, []ui.Event{pointer.Event{Type: pointer.Cancel}})
}
h.active = true
h.area = area
h.transform = t
h.wantsGrab = h.wantsGrab || op.Grab
}
}
}
func (q *pointerQueue) opHit(handlers *[]ui.Key, pos f32.Point) {
// Track whether we're passing through hits.
pass := true
idx := len(q.hitTree) - 1
for idx >= 0 {
n := &q.hitTree[idx]
if !q.hit(n.area, pos) {
idx--
continue
}
pass = pass && n.pass
if pass {
idx--
} else {
idx = n.next
}
if n.key != nil {
if _, exists := q.handlers[n.key]; exists {
*handlers = append(*handlers, n.key)
}
}
}
}
func (q *pointerQueue) hit(areaIdx int, p f32.Point) bool {
for areaIdx != -1 {
a := &q.areas[areaIdx]
if !a.hit(p) {
return false
}
areaIdx = a.next
}
return true
}
func (a *areaNode) hit(p f32.Point) bool {
p = a.trans.Invert().Transform(p)
return a.area.Hit(p)
}
func (q *pointerQueue) init() {
if q.handlers == nil {
q.handlers = make(map[ui.Key]*pointerHandler)
}
}
func (q *pointerQueue) Frame(root *ui.Ops, events *handlerEvents) {
q.init()
for _, h := range q.handlers {
// Reset handler.
h.active = false
}
q.hitTree = q.hitTree[:0]
q.areas = q.areas[:0]
q.reader.Reset(root)
q.collectHandlers(&q.reader, events, ui.TransformOp{}, -1, -1, false)
for k, h := range q.handlers {
if !h.active {
q.dropHandler(k)
delete(q.handlers, k)
}
}
}
func (q *pointerQueue) dropHandler(k ui.Key) {
for i := range q.pointers {
p := &q.pointers[i]
for i := len(p.handlers) - 1; i >= 0; i-- {
if p.handlers[i] == k {
p.handlers = append(p.handlers[:i], p.handlers[i+1:]...)
}
}
}
}
func (q *pointerQueue) Push(e pointer.Event, events *handlerEvents) {
q.init()
if e.Type == pointer.Cancel {
q.pointers = q.pointers[:0]
for k := range q.handlers {
q.dropHandler(k)
}
return
}
pidx := -1
for i, p := range q.pointers {
if p.id == e.PointerID {
pidx = i
break
}
}
if pidx == -1 {
q.pointers = append(q.pointers, pointerInfo{id: e.PointerID})
pidx = len(q.pointers) - 1
}
p := &q.pointers[pidx]
if !p.pressed && (e.Type == pointer.Move || e.Type == pointer.Press) {
p.handlers, q.scratch = q.scratch[:0], p.handlers
q.opHit(&p.handlers, e.Position)
if e.Type == pointer.Press {
p.pressed = true
}
}
if p.pressed {
// Resolve grabs.
q.scratch = q.scratch[:0]
for i, k := range p.handlers {
h := q.handlers[k]
if h.wantsGrab {
q.scratch = append(q.scratch, p.handlers[:i]...)
q.scratch = append(q.scratch, p.handlers[i+1:]...)
break
}
}
// Drop handlers that lost their grab.
for _, k := range q.scratch {
q.dropHandler(k)
}
}
if e.Type == pointer.Release {
q.pointers = append(q.pointers[:pidx], q.pointers[pidx+1:]...)
}
for i, k := range p.handlers {
h := q.handlers[k]
e := e
switch {
case p.pressed && len(p.handlers) == 1:
e.Priority = pointer.Grabbed
case i == 0:
e.Priority = pointer.Foremost
}
e.Hit = q.hit(h.area, e.Position)
e.Position = h.transform.Invert().Transform(e.Position)
events.Add(k, e)
if e.Type == pointer.Release {
// Release grab when the number of grabs reaches zero.
grabs := 0
for _, p := range q.pointers {
if p.pressed && len(p.handlers) == 1 && p.handlers[0] == k {
grabs++
}
}
if grabs == 0 {
h.wantsGrab = false
}
}
}
}
func (op *areaOp) Decode(d []byte) {
if opconst.OpType(d[0]) != opconst.TypeArea {
panic("invalid op")
}
bo := binary.LittleEndian
rect := image.Rectangle{
Min: image.Point{
X: int(int32(bo.Uint32(d[2:]))),
Y: int(int32(bo.Uint32(d[6:]))),
},
Max: image.Point{
X: int(int32(bo.Uint32(d[10:]))),
Y: int(int32(bo.Uint32(d[14:]))),
},
}
*op = areaOp{
kind: areaKind(d[1]),
rect: rect,
}
}
func (op *areaOp) Hit(pos f32.Point) bool {
min := f32.Point{
X: float32(op.rect.Min.X),
Y: float32(op.rect.Min.Y),
}
pos = pos.Sub(min)
size := op.rect.Size()
switch op.kind {
case areaRect:
if 0 <= pos.X && pos.X < float32(size.X) &&
0 <= pos.Y && pos.Y < float32(size.Y) {
return true
} else {
return false
}
case areaEllipse:
rx := float32(size.X) / 2
ry := float32(size.Y) / 2
rx2 := rx * rx
ry2 := ry * ry
xh := pos.X - rx
yk := pos.Y - ry
if xh*xh*ry2+yk*yk*rx2 <= rx2*ry2 {
return true
} else {
return false
}
default:
panic("invalid area kind")
}
}
func decodePointerInputOp(d []byte, refs []interface{}) pointer.InputOp {
if opconst.OpType(d[0]) != opconst.TypePointerInput {
panic("invalid op")
}
return pointer.InputOp{
Grab: d[1] != 0,
Key: refs[0].(ui.Key),
}
}
func decodePassOp(d []byte) pointer.PassOp {
if opconst.OpType(d[0]) != opconst.TypePass {
panic("invalid op")
}
return pointer.PassOp{
Pass: d[1] != 0,
}
}
+172
View File
@@ -0,0 +1,172 @@
// SPDX-License-Identifier: Unlicense OR MIT
package input
import (
"encoding/binary"
"time"
"gioui.org/ui"
"gioui.org/internal/opconst"
"gioui.org/internal/ops"
"gioui.org/key"
"gioui.org/pointer"
"gioui.org/system"
)
// Router is a Queue implementation that routes events from
// all available input sources to registered handlers.
type Router struct {
pqueue pointerQueue
kqueue keyQueue
handlers handlerEvents
reader ops.Reader
// deliveredEvents tracks whether events has been returned to the
// user from Events. If so, another frame is scheduled to flush
// half-updated state. This is important when a an event changes
// UI state that has already been laid out. In the worst case, we
// waste a frame, increasing power usage.
// Gio is expected to grow the ability to construct frame-to-frame
// differences and only render to changed areas. In that case, the
// waste of a spurious frame should be minimal.
deliveredEvents bool
// InvalidateOp summary.
wakeup bool
wakeupTime time.Time
// ProfileOp summary.
profHandlers []ui.Key
}
type handlerEvents struct {
handlers map[ui.Key][]ui.Event
hadEvents bool
}
func (q *Router) Events(k ui.Key) []ui.Event {
events := q.handlers.Events(k)
q.deliveredEvents = q.deliveredEvents || len(events) > 0
return events
}
func (q *Router) Frame(ops *ui.Ops) {
q.handlers.Clear()
q.wakeup = false
q.profHandlers = q.profHandlers[:0]
q.reader.Reset(ops)
q.collect()
q.pqueue.Frame(ops, &q.handlers)
q.kqueue.Frame(ops, &q.handlers)
if q.deliveredEvents || q.handlers.HadEvents() {
q.deliveredEvents = false
q.wakeup = true
q.wakeupTime = time.Time{}
}
}
func (q *Router) Add(e ui.Event) bool {
switch e := e.(type) {
case pointer.Event:
q.pqueue.Push(e, &q.handlers)
case key.EditEvent, key.Event, key.FocusEvent:
q.kqueue.Push(e, &q.handlers)
}
return q.handlers.HadEvents()
}
func (q *Router) TextInputState() TextInputState {
return q.kqueue.InputState()
}
func (q *Router) collect() {
for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() {
switch opconst.OpType(encOp.Data[0]) {
case opconst.TypeInvalidate:
op := decodeInvalidateOp(encOp.Data)
if !q.wakeup || op.At.Before(q.wakeupTime) {
q.wakeup = true
q.wakeupTime = op.At
}
case opconst.TypeProfile:
op := decodeProfileOp(encOp.Data, encOp.Refs)
q.profHandlers = append(q.profHandlers, op.Key)
}
}
}
func (q *Router) AddProfile(e system.ProfileEvent) {
for _, h := range q.profHandlers {
q.handlers.Add(h, e)
}
}
func (q *Router) Profiling() bool {
return len(q.profHandlers) > 0
}
func (q *Router) WakeupTime() (time.Time, bool) {
return q.wakeupTime, q.wakeup
}
func (h *handlerEvents) init() {
if h.handlers == nil {
h.handlers = make(map[ui.Key][]ui.Event)
}
}
func (h *handlerEvents) Set(k ui.Key, evts []ui.Event) {
h.init()
h.handlers[k] = evts
h.hadEvents = true
}
func (h *handlerEvents) Add(k ui.Key, e ui.Event) {
h.init()
h.handlers[k] = append(h.handlers[k], e)
h.hadEvents = true
}
func (h *handlerEvents) HadEvents() bool {
u := h.hadEvents
h.hadEvents = false
return u
}
func (h *handlerEvents) Events(k ui.Key) []ui.Event {
if events, ok := h.handlers[k]; ok {
h.handlers[k] = h.handlers[k][:0]
return events
}
return nil
}
func (h *handlerEvents) Clear() {
for k := range h.handlers {
delete(h.handlers, k)
}
}
func decodeProfileOp(d []byte, refs []interface{}) system.ProfileOp {
if opconst.OpType(d[0]) != opconst.TypeProfile {
panic("invalid op")
}
return system.ProfileOp{
Key: refs[0].(ui.Key),
}
}
func decodeInvalidateOp(d []byte) ui.InvalidateOp {
bo := binary.LittleEndian
if opconst.OpType(d[0]) != opconst.TypeInvalidate {
panic("invalid op")
}
var o ui.InvalidateOp
if nanos := bo.Uint64(d[1:]); nanos > 0 {
o.At = time.Unix(0, int64(nanos))
}
return o
}
+58
View File
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
/*
#cgo LDFLAGS: -llog
#include <stdlib.h>
#include <android/log.h>
*/
import "C"
import (
"bufio"
"log"
"os"
"runtime"
"syscall"
"unsafe"
)
func init() {
// Android's logcat already includes timestamps.
log.SetFlags(log.Flags() &^ log.LstdFlags)
logFd(os.Stdout.Fd())
logFd(os.Stderr.Fd())
}
func logFd(fd uintptr) {
r, w, err := os.Pipe()
if err != nil {
panic(err)
}
if err := syscall.Dup3(int(w.Fd()), int(fd), syscall.O_CLOEXEC); err != nil {
panic(err)
}
go func() {
tag := C.CString("gio")
defer C.free(unsafe.Pointer(tag))
// 1024 is the truncation limit from android/log.h, plus a \n.
lineBuf := bufio.NewReaderSize(r, 1024)
// The buffer to pass to C, including the terminating '\0'.
buf := make([]byte, lineBuf.Size()+1)
cbuf := (*C.char)(unsafe.Pointer(&buf[0]))
for {
line, _, err := lineBuf.ReadLine()
if err != nil {
break
}
copy(buf, line)
buf[len(line)] = 0
C.__android_log_write(C.ANDROID_LOG_INFO, tag, cbuf)
}
// The garbage collector doesn't know that w's fd was dup'ed.
// Avoid finalizing w, and thereby avoid its finalizer closing its fd.
runtime.KeepAlive(w)
}()
}
+45
View File
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build darwin,ios
package app
/*
#include "log_ios.h"
*/
import "C"
import (
"bufio"
"io"
"log"
"unsafe"
)
func init() {
// macOS Console already includes timstamps.
log.SetFlags(log.Flags() &^ log.LstdFlags)
log.SetOutput(newNSLogWriter())
}
func newNSLogWriter() io.Writer {
r, w := io.Pipe()
go func() {
// 1024 is an arbitrary truncation limit, taken from Android's
// log buffer size.
lineBuf := bufio.NewReaderSize(r, 1024)
// The buffer to pass to C, including the terminating '\0'.
buf := make([]byte, lineBuf.Size()+1)
cbuf := (*C.char)(unsafe.Pointer(&buf[0]))
for {
line, _, err := lineBuf.ReadLine()
if err != nil {
break
}
copy(buf, line)
buf[len(line)] = 0
C.nslog(cbuf)
}
}()
return w
}
+3
View File
@@ -0,0 +1,3 @@
// SPDX-License-Identifier: Unlicense OR MIT
__attribute__ ((visibility ("hidden"))) void nslog(char *str);
+11
View File
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build darwin,ios
@import Foundation;
#include "log_ios.h"
void nslog(char *str) {
NSLog(@"%@", @(str));
}
+168
View File
@@ -0,0 +1,168 @@
// SPDX-License-Identifier: Unlicense OR MIT
#include <jni.h>
#include <dlfcn.h>
#include <android/log.h>
#include "os_android.h"
#include "_cgo_export.h"
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) {
return -1;
}
setJVM(vm);
jclass viewClass = (*env)->FindClass(env, "org/gioui/GioView");
if (viewClass == NULL) {
return -1;
}
static const JNINativeMethod methods[] = {
{
.name = "runGoMain",
.signature = "([B)V",
.fnPtr = runGoMain
},
{
.name = "onCreateView",
.signature = "(Lorg/gioui/GioView;)J",
.fnPtr = onCreateView
},
{
.name = "onDestroyView",
.signature = "(J)V",
.fnPtr = onDestroyView
},
{
.name = "onStartView",
.signature = "(J)V",
.fnPtr = onStartView
},
{
.name = "onStopView",
.signature = "(J)V",
.fnPtr = onStopView
},
{
.name = "onSurfaceDestroyed",
.signature = "(J)V",
.fnPtr = onSurfaceDestroyed
},
{
.name = "onSurfaceChanged",
.signature = "(JLandroid/view/Surface;)V",
.fnPtr = onSurfaceChanged
},
{
.name = "onConfigurationChanged",
.signature = "(J)V",
.fnPtr = onConfigurationChanged
},
{
.name = "onWindowInsets",
.signature = "(JIIII)V",
.fnPtr = onWindowInsets
},
{
.name = "onLowMemory",
.signature = "()V",
.fnPtr = onLowMemory
},
{
.name = "onTouchEvent",
.signature = "(JIIIFFJ)V",
.fnPtr = onTouchEvent
},
{
.name = "onKeyEvent",
.signature = "(JIIJ)V",
.fnPtr = onKeyEvent
},
{
.name = "onFrameCallback",
.signature = "(JJ)V",
.fnPtr = onFrameCallback
},
{
.name = "onBack",
.signature = "(J)Z",
.fnPtr = onBack
},
{
.name = "onFocusChange",
.signature = "(JZ)V",
.fnPtr = onFocusChange
}
};
if ((*env)->RegisterNatives(env, viewClass, methods, sizeof(methods)/sizeof(methods[0])) != 0) {
return -1;
}
return JNI_VERSION_1_6;
}
jint gio_jni_GetEnv(JavaVM *vm, JNIEnv **env, jint version) {
return (*vm)->GetEnv(vm, (void **)env, version);
}
jint gio_jni_AttachCurrentThread(JavaVM *vm, JNIEnv **p_env, void *thr_args) {
return (*vm)->AttachCurrentThread(vm, p_env, thr_args);
}
jint gio_jni_DetachCurrentThread(JavaVM *vm) {
return (*vm)->DetachCurrentThread(vm);
}
jobject gio_jni_NewGlobalRef(JNIEnv *env, jobject obj) {
return (*env)->NewGlobalRef(env, obj);
}
void gio_jni_DeleteGlobalRef(JNIEnv *env, jobject obj) {
(*env)->DeleteGlobalRef(env, obj);
}
jclass gio_jni_GetObjectClass(JNIEnv *env, jobject obj) {
return (*env)->GetObjectClass(env, obj);
}
jmethodID gio_jni_GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
return (*env)->GetMethodID(env, clazz, name, sig);
}
jmethodID gio_jni_GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
return (*env)->GetStaticMethodID(env, clazz, name, sig);
}
jint gio_jni_CallStaticIntMethodII(JNIEnv *env, jclass clazz, jmethodID methodID, jint a1, jint a2) {
return (*env)->CallStaticIntMethod(env, clazz, methodID, a1, a2);
}
jfloat gio_jni_CallFloatMethod(JNIEnv *env, jobject obj, jmethodID methodID) {
return (*env)->CallFloatMethod(env, obj, methodID);
}
jint gio_jni_CallIntMethod(JNIEnv *env, jobject obj, jmethodID methodID) {
return (*env)->CallIntMethod(env, obj, methodID);
}
void gio_jni_CallVoidMethod(JNIEnv *env, jobject obj, jmethodID methodID) {
(*env)->CallVoidMethod(env, obj, methodID);
}
void gio_jni_CallVoidMethod_J(JNIEnv *env, jobject obj, jmethodID methodID, jlong a1) {
(*env)->CallVoidMethod(env, obj, methodID, a1);
}
jbyte *gio_jni_GetByteArrayElements(JNIEnv *env, jbyteArray arr) {
return (*env)->GetByteArrayElements(env, arr, NULL);
}
void gio_jni_ReleaseByteArrayElements(JNIEnv *env, jbyteArray arr, jbyte *bytes) {
(*env)->ReleaseByteArrayElements(env, arr, bytes, JNI_ABORT);
}
jsize gio_jni_GetArrayLength(JNIEnv *env, jbyteArray arr) {
return (*env)->GetArrayLength(env, arr);
}
+428
View File
@@ -0,0 +1,428 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
/*
#cgo LDFLAGS: -landroid
#include <android/native_window_jni.h>
#include <android/configuration.h>
#include <android/keycodes.h>
#include <android/input.h>
#include <stdlib.h>
#include "os_android.h"
*/
import "C"
import (
"errors"
"fmt"
"image"
"runtime"
"runtime/debug"
"sync"
"time"
"unsafe"
"gioui.org/ui"
"gioui.org/f32"
"gioui.org/key"
"gioui.org/pointer"
)
type window struct {
*Window
view C.jobject
dpi int
fontScale float32
insets Insets
stage Stage
started bool
mu sync.Mutex
win *C.ANativeWindow
animating bool
mgetDensity C.jmethodID
mgetFontScale C.jmethodID
mshowTextInput C.jmethodID
mhideTextInput C.jmethodID
mpostFrameCallback C.jmethodID
mpostFrameCallbackOnMainThread C.jmethodID
}
var theJVM *C.JavaVM
var views = make(map[C.jlong]*window)
var mainWindow = newWindowRendezvous()
func jniGetMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID {
m := C.CString(method)
defer C.free(unsafe.Pointer(m))
s := C.CString(sig)
defer C.free(unsafe.Pointer(s))
return C.gio_jni_GetMethodID(env, class, m, s)
}
func jniGetStaticMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID {
m := C.CString(method)
defer C.free(unsafe.Pointer(m))
s := C.CString(sig)
defer C.free(unsafe.Pointer(s))
return C.gio_jni_GetStaticMethodID(env, class, m, s)
}
//export runGoMain
func runGoMain(env *C.JNIEnv, class C.jclass, jdataDir C.jbyteArray) {
dirBytes := C.gio_jni_GetByteArrayElements(env, jdataDir)
if dirBytes == nil {
panic("runGoMain: GetByteArrayElements failed")
}
n := C.gio_jni_GetArrayLength(env, jdataDir)
dataDir := C.GoStringN((*C.char)(unsafe.Pointer(dirBytes)), n)
setDataDir(dataDir)
C.gio_jni_ReleaseByteArrayElements(env, jdataDir, dirBytes)
runMain()
}
//export setJVM
func setJVM(vm *C.JavaVM) {
theJVM = vm
}
//export onCreateView
func onCreateView(env *C.JNIEnv, class C.jclass, view C.jobject) C.jlong {
view = C.gio_jni_NewGlobalRef(env, view)
w := &window{
view: view,
mgetDensity: jniGetMethodID(env, class, "getDensity", "()I"),
mgetFontScale: jniGetMethodID(env, class, "getFontScale", "()F"),
mshowTextInput: jniGetMethodID(env, class, "showTextInput", "()V"),
mhideTextInput: jniGetMethodID(env, class, "hideTextInput", "()V"),
mpostFrameCallback: jniGetMethodID(env, class, "postFrameCallback", "()V"),
mpostFrameCallbackOnMainThread: jniGetMethodID(env, class, "postFrameCallbackOnMainThread", "()V"),
}
wopts := <-mainWindow.out
w.Window = wopts.window
w.Window.setDriver(w)
handle := C.jlong(view)
views[handle] = w
w.loadConfig(env, class)
w.setStage(StagePaused)
return handle
}
//export onDestroyView
func onDestroyView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
w := views[handle]
w.setDriver(nil)
delete(views, handle)
C.gio_jni_DeleteGlobalRef(env, w.view)
w.view = 0
}
//export onStopView
func onStopView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
w := views[handle]
w.started = false
w.setStage(StagePaused)
}
//export onStartView
func onStartView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
w := views[handle]
w.started = true
if w.aNativeWindow() != nil {
w.setVisible()
}
}
//export onSurfaceDestroyed
func onSurfaceDestroyed(env *C.JNIEnv, class C.jclass, handle C.jlong) {
w := views[handle]
w.mu.Lock()
w.win = nil
w.mu.Unlock()
w.setStage(StagePaused)
}
//export onSurfaceChanged
func onSurfaceChanged(env *C.JNIEnv, class C.jclass, handle C.jlong, surf C.jobject) {
w := views[handle]
w.mu.Lock()
w.win = C.ANativeWindow_fromSurface(env, surf)
w.mu.Unlock()
if w.started {
w.setVisible()
}
}
//export onLowMemory
func onLowMemory() {
runtime.GC()
debug.FreeOSMemory()
}
//export onConfigurationChanged
func onConfigurationChanged(env *C.JNIEnv, class C.jclass, view C.jlong) {
w := views[view]
w.loadConfig(env, class)
if w.stage >= StageRunning {
w.draw(true)
}
}
//export onFrameCallback
func onFrameCallback(env *C.JNIEnv, class C.jclass, view C.jlong, nanos C.jlong) {
w, exist := views[view]
if !exist {
return
}
if w.stage < StageRunning {
return
}
w.mu.Lock()
anim := w.animating
w.mu.Unlock()
if anim {
runInJVM(func(env *C.JNIEnv) {
C.gio_jni_CallVoidMethod(env, w.view, w.mpostFrameCallback)
})
w.draw(false)
}
}
//export onBack
func onBack(env *C.JNIEnv, class C.jclass, view C.jlong) C.jboolean {
w := views[view]
ev := &CommandEvent{Type: CommandBack}
w.event(ev)
if ev.Cancel {
return C.JNI_TRUE
}
return C.JNI_FALSE
}
//export onFocusChange
func onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) {
w := views[view]
w.event(key.FocusEvent{Focus: focus == C.JNI_TRUE})
}
//export onWindowInsets
func onWindowInsets(env *C.JNIEnv, class C.jclass, view C.jlong, top, right, bottom, left C.jint) {
w := views[view]
w.insets = Insets{
Top: ui.Px(float32(top)),
Right: ui.Px(float32(right)),
Bottom: ui.Px(float32(bottom)),
Left: ui.Px(float32(left)),
}
if w.stage >= StageRunning {
w.draw(true)
}
}
func (w *window) setVisible() {
win := w.aNativeWindow()
width, height := C.ANativeWindow_getWidth(win), C.ANativeWindow_getHeight(win)
if width == 0 || height == 0 {
return
}
w.setStage(StageRunning)
w.draw(true)
}
func (w *window) setStage(stage Stage) {
if stage == w.stage {
return
}
w.stage = stage
w.event(StageEvent{stage})
}
func (w *window) display() unsafe.Pointer {
return nil
}
func (w *window) nativeWindow(visID int) (unsafe.Pointer, int, int) {
win := w.aNativeWindow()
var width, height int
if win != nil {
if C.ANativeWindow_setBuffersGeometry(win, 0, 0, C.int32_t(visID)) != 0 {
panic(errors.New("ANativeWindow_setBuffersGeometry failed"))
}
w, h := C.ANativeWindow_getWidth(win), C.ANativeWindow_getHeight(win)
width, height = int(w), int(h)
}
return unsafe.Pointer(win), width, height
}
func (w *window) aNativeWindow() *C.ANativeWindow {
w.mu.Lock()
defer w.mu.Unlock()
return w.win
}
func (w *window) loadConfig(env *C.JNIEnv, class C.jclass) {
dpi := int(C.gio_jni_CallIntMethod(env, w.view, w.mgetDensity))
w.fontScale = float32(C.gio_jni_CallFloatMethod(env, w.view, w.mgetFontScale))
switch dpi {
case C.ACONFIGURATION_DENSITY_NONE,
C.ACONFIGURATION_DENSITY_DEFAULT,
C.ACONFIGURATION_DENSITY_ANY:
// Assume standard density.
w.dpi = C.ACONFIGURATION_DENSITY_MEDIUM
default:
w.dpi = int(dpi)
}
}
func (w *window) setAnimating(anim bool) {
w.mu.Lock()
w.animating = anim
w.mu.Unlock()
if anim {
runInJVM(func(env *C.JNIEnv) {
C.gio_jni_CallVoidMethod(env, w.view, w.mpostFrameCallbackOnMainThread)
})
}
}
func (w *window) draw(sync bool) {
win := w.aNativeWindow()
width, height := C.ANativeWindow_getWidth(win), C.ANativeWindow_getHeight(win)
if width == 0 || height == 0 {
return
}
ppdp := float32(w.dpi) * inchPrDp
w.event(UpdateEvent{
Size: image.Point{
X: int(width),
Y: int(height),
},
Insets: w.insets,
Config: Config{
pxPerDp: ppdp,
pxPerSp: w.fontScale * ppdp,
now: time.Now(),
},
sync: sync,
})
}
type keyMapper func(devId, keyCode C.int32_t) rune
func runInJVM(f func(env *C.JNIEnv)) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var env *C.JNIEnv
var detach bool
if res := C.gio_jni_GetEnv(theJVM, &env, C.JNI_VERSION_1_6); res != C.JNI_OK {
if res != C.JNI_EDETACHED {
panic(fmt.Errorf("JNI GetEnv failed with error %d", res))
}
if C.gio_jni_AttachCurrentThread(theJVM, &env, nil) != C.JNI_OK {
panic(errors.New("runInJVM: AttachCurrentThread failed"))
}
detach = true
}
if detach {
defer func() {
C.gio_jni_DetachCurrentThread(theJVM)
}()
}
f(env)
}
func convertKeyCode(code C.jint) (rune, bool) {
var n rune
switch code {
case C.AKEYCODE_DPAD_UP:
n = key.NameUpArrow
case C.AKEYCODE_DPAD_DOWN:
n = key.NameDownArrow
case C.AKEYCODE_DPAD_LEFT:
n = key.NameLeftArrow
case C.AKEYCODE_DPAD_RIGHT:
n = key.NameRightArrow
case C.AKEYCODE_FORWARD_DEL:
n = key.NameDeleteForward
case C.AKEYCODE_DEL:
n = key.NameDeleteBackward
default:
return 0, false
}
return n, true
}
//export onKeyEvent
func onKeyEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, keyCode, r C.jint, t C.jlong) {
w := views[handle]
if n, ok := convertKeyCode(keyCode); ok {
w.event(key.Event{Name: n})
}
if r != 0 {
w.event(key.EditEvent{Text: string(rune(r))})
}
}
//export onTouchEvent
func onTouchEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, action, pointerID, tool C.jint, x, y C.jfloat, t C.jlong) {
w := views[handle]
var typ pointer.Type
switch action {
case C.AMOTION_EVENT_ACTION_DOWN, C.AMOTION_EVENT_ACTION_POINTER_DOWN:
typ = pointer.Press
case C.AMOTION_EVENT_ACTION_UP, C.AMOTION_EVENT_ACTION_POINTER_UP:
typ = pointer.Release
case C.AMOTION_EVENT_ACTION_CANCEL:
typ = pointer.Cancel
case C.AMOTION_EVENT_ACTION_MOVE:
typ = pointer.Move
default:
return
}
var src pointer.Source
switch tool {
case C.AMOTION_EVENT_TOOL_TYPE_FINGER:
src = pointer.Touch
case C.AMOTION_EVENT_TOOL_TYPE_MOUSE:
src = pointer.Mouse
default:
return
}
w.event(pointer.Event{
Type: typ,
Source: src,
PointerID: pointer.ID(pointerID),
Time: time.Duration(t) * time.Millisecond,
Position: f32.Point{X: float32(x), Y: float32(y)},
})
}
func (w *window) showTextInput(show bool) {
if w.view == 0 {
return
}
runInJVM(func(env *C.JNIEnv) {
if show {
C.gio_jni_CallVoidMethod(env, w.view, w.mshowTextInput)
} else {
C.gio_jni_CallVoidMethod(env, w.view, w.mhideTextInput)
}
})
}
func main() {
}
func createWindow(window *Window, opts *windowOptions) error {
mainWindow.in <- windowAndOptions{window, opts}
return <-mainWindow.errs
}
+19
View File
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: Unlicense OR MIT
__attribute__ ((visibility ("hidden"))) jint gio_jni_GetEnv(JavaVM *vm, JNIEnv **env, jint version);
__attribute__ ((visibility ("hidden"))) jint gio_jni_AttachCurrentThread(JavaVM *vm, JNIEnv **p_env, void *thr_args);
__attribute__ ((visibility ("hidden"))) jint gio_jni_DetachCurrentThread(JavaVM *vm);
__attribute__ ((visibility ("hidden"))) jobject gio_jni_NewGlobalRef(JNIEnv *env, jobject obj);
__attribute__ ((visibility ("hidden"))) void gio_jni_DeleteGlobalRef(JNIEnv *env, jobject obj);
__attribute__ ((visibility ("hidden"))) jclass gio_jni_GetObjectClass(JNIEnv *env, jobject obj);
__attribute__ ((visibility ("hidden"))) jmethodID gio_jni_GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
__attribute__ ((visibility ("hidden"))) jmethodID gio_jni_GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
__attribute__ ((visibility ("hidden"))) jint gio_jni_CallStaticIntMethodII(JNIEnv *env, jclass clazz, jmethodID methodID, jint a1, jint a2);
__attribute__ ((visibility ("hidden"))) jfloat gio_jni_CallFloatMethod(JNIEnv *env, jobject obj, jmethodID methodID);
__attribute__ ((visibility ("hidden"))) jint gio_jni_CallIntMethod(JNIEnv *env, jobject obj, jmethodID methodID);
__attribute__ ((visibility ("hidden"))) void gio_jni_CallVoidMethod(JNIEnv *env, jobject obj, jmethodID methodID);
__attribute__ ((visibility ("hidden"))) void gio_jni_CallVoidMethod_J(JNIEnv *env, jobject obj, jmethodID methodID, jlong a1);
__attribute__ ((visibility ("hidden"))) jbyte *gio_jni_GetByteArrayElements(JNIEnv *env, jbyteArray arr);
__attribute__ ((visibility ("hidden"))) void gio_jni_ReleaseByteArrayElements(JNIEnv *env, jbyteArray arr, jbyte *bytes);
__attribute__ ((visibility ("hidden"))) jsize gio_jni_GetArrayLength(JNIEnv *env, jbyteArray arr);
+255
View File
@@ -0,0 +1,255 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build darwin,ios
package app
/*
#cgo CFLAGS: -fmodules -fobjc-arc -x objective-c
#include <CoreGraphics/CoreGraphics.h>
#include <UIKit/UIKit.h>
#include <stdint.h>
#include "os_ios.h"
*/
import "C"
import (
"image"
"runtime"
"runtime/debug"
"sync/atomic"
"time"
"gioui.org/ui"
"gioui.org/f32"
"gioui.org/key"
"gioui.org/pointer"
)
type window struct {
view C.CFTypeRef
w *Window
layer C.CFTypeRef
visible atomic.Value
pointerMap []C.CFTypeRef
}
var mainWindow = newWindowRendezvous()
var layerFactory func() uintptr
var views = make(map[C.CFTypeRef]*window)
func init() {
// Darwin requires UI operations happen on the main thread only.
runtime.LockOSThread()
}
//export onCreate
func onCreate(view C.CFTypeRef) {
w := &window{
view: view,
}
wopts := <-mainWindow.out
w.w = wopts.window
w.w.setDriver(w)
w.visible.Store(false)
w.layer = C.CFTypeRef(layerFactory())
C.gio_addLayerToView(view, w.layer)
views[view] = w
w.w.event(StageEvent{StagePaused})
}
//export onDraw
func onDraw(view C.CFTypeRef, dpi, sdpi, width, height C.CGFloat, sync C.int, top, right, bottom, left C.CGFloat) {
if width == 0 || height == 0 {
return
}
w := views[view]
wasVisible := w.isVisible()
w.visible.Store(true)
C.gio_updateView(view, w.layer)
if !wasVisible {
w.w.event(StageEvent{StageRunning})
}
isSync := false
if sync != 0 {
isSync = true
}
w.w.event(UpdateEvent{
Size: image.Point{
X: int(width + .5),
Y: int(height + .5),
},
Insets: Insets{
Top: ui.Px(float32(top)),
Right: ui.Px(float32(right)),
Bottom: ui.Px(float32(bottom)),
Left: ui.Px(float32(left)),
},
Config: Config{
pxPerDp: float32(dpi) * inchPrDp,
pxPerSp: float32(sdpi) * inchPrDp,
now: time.Now(),
},
sync: isSync,
})
}
//export onStop
func onStop(view C.CFTypeRef) {
w := views[view]
w.visible.Store(false)
w.w.event(StageEvent{StagePaused})
}
//export onDestroy
func onDestroy(view C.CFTypeRef) {
w := views[view]
delete(views, view)
w.w.event(DestroyEvent{})
C.gio_removeLayer(w.layer)
C.CFRelease(w.layer)
w.layer = 0
w.view = 0
}
//export onFocus
func onFocus(view C.CFTypeRef, focus int) {
w := views[view]
w.w.event(key.FocusEvent{Focus: focus != 0})
}
//export onLowMemory
func onLowMemory() {
runtime.GC()
debug.FreeOSMemory()
}
//export onUpArrow
func onUpArrow(view C.CFTypeRef) {
views[view].onKeyCommand(key.NameUpArrow)
}
//export onDownArrow
func onDownArrow(view C.CFTypeRef) {
views[view].onKeyCommand(key.NameDownArrow)
}
//export onLeftArrow
func onLeftArrow(view C.CFTypeRef) {
views[view].onKeyCommand(key.NameLeftArrow)
}
//export onRightArrow
func onRightArrow(view C.CFTypeRef) {
views[view].onKeyCommand(key.NameRightArrow)
}
//export onDeleteBackward
func onDeleteBackward(view C.CFTypeRef) {
views[view].onKeyCommand(key.NameDeleteBackward)
}
//export onText
func onText(view C.CFTypeRef, str *C.char) {
w := views[view]
w.w.event(key.EditEvent{
Text: C.GoString(str),
})
}
//export onTouch
func onTouch(last C.int, view, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) {
var typ pointer.Type
switch phase {
case C.UITouchPhaseBegan:
typ = pointer.Press
case C.UITouchPhaseMoved:
typ = pointer.Move
case C.UITouchPhaseEnded:
typ = pointer.Release
case C.UITouchPhaseCancelled:
typ = pointer.Cancel
default:
return
}
w := views[view]
t := time.Duration(float64(ti) * float64(time.Second))
p := f32.Point{X: float32(x), Y: float32(y)}
w.w.event(pointer.Event{
Type: typ,
Source: pointer.Touch,
PointerID: w.lookupTouch(last != 0, touchRef),
Position: p,
Time: t,
})
}
func (w *window) setAnimating(anim bool) {
if w.view == 0 {
return
}
var animi C.int
if anim {
animi = 1
}
C.gio_setAnimating(w.view, animi)
}
func (w *window) onKeyCommand(name rune) {
w.w.event(key.Event{
Name: name,
})
}
// lookupTouch maps an UITouch pointer value to an index. If
// last is set, the map is cleared.
func (w *window) lookupTouch(last bool, touch C.CFTypeRef) pointer.ID {
id := -1
for i, ref := range w.pointerMap {
if ref == touch {
id = i
break
}
}
if id == -1 {
id = len(w.pointerMap)
w.pointerMap = append(w.pointerMap, touch)
}
if last {
w.pointerMap = w.pointerMap[:0]
}
return pointer.ID(id)
}
func (w *window) contextLayer() uintptr {
return uintptr(w.layer)
}
func (w *window) isVisible() bool {
return w.visible.Load().(bool)
}
func (w *window) showTextInput(show bool) {
if w.view == 0 {
return
}
if show {
C.gio_showTextInput(w.view)
} else {
C.gio_hideTextInput(w.view)
}
}
func createWindow(win *Window, opts *windowOptions) error {
mainWindow.in <- windowAndOptions{win, opts}
return <-mainWindow.errs
}
func main() {
}
+8
View File
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: Unlicense OR MIT
__attribute__ ((visibility ("hidden"))) void gio_showTextInput(CFTypeRef viewRef);
__attribute__ ((visibility ("hidden"))) void gio_hideTextInput(CFTypeRef viewRef);
__attribute__ ((visibility ("hidden"))) void gio_addLayerToView(CFTypeRef viewRef, CFTypeRef layerRef);
__attribute__ ((visibility ("hidden"))) void gio_updateView(CFTypeRef viewRef, CFTypeRef layerRef);
__attribute__ ((visibility ("hidden"))) void gio_removeLayer(CFTypeRef layerRef);
__attribute__ ((visibility ("hidden"))) void gio_setAnimating(CFTypeRef viewRef, int anim);
+320
View File
@@ -0,0 +1,320 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build darwin,ios
@import UIKit;
#include <stdint.h>
#include "_cgo_export.h"
#include "os_ios.h"
#include "framework_ios.h"
@interface GioView: UIView <UIKeyInput>
- (void)setAnimating:(BOOL)anim;
@end
@interface GioViewController : UIViewController
@property(weak) UIScreen *screen;
@end
static void redraw(CFTypeRef viewRef, BOOL sync) {
UIView *v = (__bridge UIView *)viewRef;
CGFloat scale = v.layer.contentsScale;
// Use 163 as the standard ppi on iOS.
CGFloat dpi = 163*scale;
CGFloat sdpi = dpi;
UIEdgeInsets insets = v.layoutMargins;
if (@available(iOS 11.0, tvOS 11.0, *)) {
UIFontMetrics *metrics = [UIFontMetrics defaultMetrics];
sdpi = [metrics scaledValueForValue:sdpi];
insets = v.safeAreaInsets;
}
onDraw(viewRef, dpi, sdpi, v.bounds.size.width*scale, v.bounds.size.height*scale, sync,
insets.top*scale, insets.right*scale, insets.bottom*scale, insets.left*scale);
}
@implementation GioAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
gio_runMain();
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
GioViewController *controller = [[GioViewController alloc] initWithNibName:nil bundle:nil];
controller.screen = self.window.screen;
self.window.rootViewController = controller;
[self.window makeKeyAndVisible];
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application {
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
GioViewController *vc = (GioViewController *)self.window.rootViewController;
UIView *drawView = vc.view.subviews[0];
if (drawView != nil) {
onStop((__bridge CFTypeRef)drawView);
}
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
GioViewController *c = (GioViewController*)self.window.rootViewController;
UIView *drawView = c.view.subviews[0];
if (drawView != nil) {
CFTypeRef viewRef = (__bridge CFTypeRef)drawView;
redraw(viewRef, YES);
}
}
@end
@implementation GioViewController
CGFloat _keyboardHeight;
- (void)loadView {
CGRect zeroFrame = CGRectMake(0, 0, 0, 0);
self.view = [[UIView alloc] initWithFrame:zeroFrame];
self.view.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
UIView *drawView = [[GioView alloc] initWithFrame:zeroFrame];
[self.view addSubview: drawView];
#ifndef TARGET_OS_TV
drawView.multipleTouchEnabled = YES;
#endif
drawView.contentScaleFactor = self.screen.nativeScale;
drawView.preservesSuperviewLayoutMargins = YES;
drawView.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
onCreate((__bridge CFTypeRef)drawView);
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillChange:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillChange:)
name:UIKeyboardWillChangeFrameNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
CFTypeRef viewRef = (__bridge CFTypeRef)self.view.subviews[0];
onDestroy(viewRef);
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
UIView *view = self.view.subviews[0];
CGRect frame = self.view.bounds;
// Adjust view bounds to make room for the keyboard.
frame.size.height -= _keyboardHeight;
view.frame = frame;
redraw((__bridge CFTypeRef)view, YES);
}
- (void)didReceiveMemoryWarning {
onLowMemory();
[super didReceiveMemoryWarning];
}
- (void)keyboardWillChange:(NSNotification *)note {
NSDictionary *userInfo = note.userInfo;
CGRect f = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
_keyboardHeight = f.size.height;
[self.view setNeedsLayout];
}
- (void)keyboardWillHide:(NSNotification *)note {
_keyboardHeight = 0.0;
[self.view setNeedsLayout];
}
@end
static void handleTouches(int last, UIView *view, NSSet<UITouch *> *touches, UIEvent *event) {
CGFloat scale = view.contentScaleFactor;
NSUInteger i = 0;
NSUInteger n = [touches count];
CFTypeRef viewRef = (__bridge CFTypeRef)view;
for (UITouch *touch in touches) {
CFTypeRef touchRef = (__bridge CFTypeRef)touch;
i++;
NSArray<UITouch *> *coalescedTouches = [event coalescedTouchesForTouch:touch];
NSUInteger j = 0;
NSUInteger m = [coalescedTouches count];
for (UITouch *coalescedTouch in [event coalescedTouchesForTouch:touch]) {
CGPoint loc = [coalescedTouch locationInView:view];
j++;
int lastTouch = last && i == n && j == m;
onTouch(lastTouch, viewRef, touchRef, touch.phase, loc.x*scale, loc.y*scale, [coalescedTouch timestamp]);
}
}
}
@implementation GioView
CADisplayLink *displayLink;
NSArray<UIKeyCommand *> *_keyCommands;
- (void)onFrameCallback:(CADisplayLink *)link {
redraw((__bridge CFTypeRef)self, NO);
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
__weak id weakSelf = self;
displayLink = [CADisplayLink displayLinkWithTarget:weakSelf selector:@selector(onFrameCallback:)];
}
return self;
}
- (void)willMoveToWindow:(UIWindow *)newWindow {
if (self.window != nil) {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIWindowDidBecomeKeyNotification
object:self.window];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIWindowDidResignKeyNotification
object:self.window];
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onWindowDidBecomeKey:)
name:UIWindowDidBecomeKeyNotification
object:newWindow];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onWindowDidResignKey:)
name:UIWindowDidResignKeyNotification
object:newWindow];
}
- (void)onWindowDidBecomeKey:(NSNotification *)note {
if (self.isFirstResponder) {
onFocus((__bridge CFTypeRef)self, YES);
}
}
- (void)onWindowDidResignKey:(NSNotification *)note {
if (self.isFirstResponder) {
onFocus((__bridge CFTypeRef)self, NO);
}
}
- (void)dealloc {
[displayLink invalidate];
}
- (void)setAnimating:(BOOL)anim {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
if (anim) {
[displayLink addToRunLoop:runLoop forMode:[runLoop currentMode]];
} else {
[displayLink removeFromRunLoop:runLoop forMode:[runLoop currentMode]];
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
handleTouches(0, self, touches, event);
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
handleTouches(0, self, touches, event);
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
handleTouches(1, self, touches, event);
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
handleTouches(1, self, touches, event);
}
- (void)insertText:(NSString *)text {
onText((__bridge CFTypeRef)self, (char *)text.UTF8String);
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (BOOL)hasText {
return YES;
}
- (void)deleteBackward {
onDeleteBackward((__bridge CFTypeRef)self);
}
- (void)onUpArrow {
onUpArrow((__bridge CFTypeRef)self);
}
- (void)onDownArrow {
onDownArrow((__bridge CFTypeRef)self);
}
- (void)onLeftArrow {
onLeftArrow((__bridge CFTypeRef)self);
}
- (void)onRightArrow {
onRightArrow((__bridge CFTypeRef)self);
}
- (NSArray<UIKeyCommand *> *)keyCommands {
if (_keyCommands == nil) {
_keyCommands = @[
[UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow
modifierFlags:0
action:@selector(onUpArrow)],
[UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow
modifierFlags:0
action:@selector(onDownArrow)],
[UIKeyCommand keyCommandWithInput:UIKeyInputLeftArrow
modifierFlags:0
action:@selector(onLeftArrow)],
[UIKeyCommand keyCommandWithInput:UIKeyInputRightArrow
modifierFlags:0
action:@selector(onRightArrow)]
];
}
return _keyCommands;
}
@end
void gio_setAnimating(CFTypeRef viewRef, int anim) {
GioView *view = (__bridge GioView *)viewRef;
dispatch_async(dispatch_get_main_queue(), ^{
[view setAnimating:(anim ? YES : NO)];
});
}
void gio_showTextInput(CFTypeRef viewRef) {
UIView *view = (__bridge UIView *)viewRef;
dispatch_async(dispatch_get_main_queue(), ^{
[view becomeFirstResponder];
});
}
void gio_hideTextInput(CFTypeRef viewRef) {
UIView *view = (__bridge UIView *)viewRef;
dispatch_async(dispatch_get_main_queue(), ^{
[view resignFirstResponder];
});
}
void gio_addLayerToView(CFTypeRef viewRef, CFTypeRef layerRef) {
UIView *view = (__bridge UIView *)viewRef;
CALayer *layer = (__bridge CALayer *)layerRef;
[view.layer addSublayer:layer];
}
void gio_updateView(CFTypeRef viewRef, CFTypeRef layerRef) {
UIView *view = (__bridge UIView *)viewRef;
CAEAGLLayer *layer = (__bridge CAEAGLLayer *)layerRef;
layer.contentsScale = view.contentScaleFactor;
layer.bounds = view.bounds;
}
void gio_removeLayer(CFTypeRef layerRef) {
CALayer *layer = (__bridge CALayer *)layerRef;
[layer removeFromSuperlayer];
}
+421
View File
@@ -0,0 +1,421 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
import (
"image"
"sync"
"syscall/js"
"time"
"gioui.org/f32"
"gioui.org/key"
"gioui.org/pointer"
)
type window struct {
window js.Value
cnv js.Value
tarea js.Value
w *Window
redraw js.Func
requestAnimationFrame js.Value
cleanfuncs []func()
touches []js.Value
composing bool
mu sync.Mutex
scale float32
animating bool
}
var mainDone = make(chan struct{})
func createWindow(win *Window, opts *windowOptions) error {
doc := js.Global().Get("document")
cont := getContainer(doc)
cnv := createCanvas(doc)
cont.Call("appendChild", cnv)
tarea := createTextArea(doc)
cont.Call("appendChild", tarea)
w := &window{
cnv: cnv,
tarea: tarea,
window: js.Global().Get("window"),
}
w.requestAnimationFrame = w.window.Get("requestAnimationFrame")
w.redraw = w.funcOf(func(this js.Value, args []js.Value) interface{} {
w.animCallback()
return nil
})
w.addEventListeners()
w.w = win
go func() {
w.w.setDriver(w)
w.focus()
w.w.event(StageEvent{StageRunning})
w.draw(true)
select {}
w.cleanup()
close(mainDone)
}()
return nil
}
func getContainer(doc js.Value) js.Value {
cont := doc.Call("getElementById", "giowindow")
if cont != js.Null() {
return cont
}
cont = doc.Call("createElement", "DIV")
doc.Get("body").Call("appendChild", cont)
return cont
}
func createTextArea(doc js.Value) js.Value {
tarea := doc.Call("createElement", "input")
style := tarea.Get("style")
style.Set("width", "1px")
style.Set("height", "1px")
style.Set("opacity", "0")
style.Set("border", "0")
style.Set("padding", "0")
tarea.Set("autocomplete", "off")
tarea.Set("autocorrect", "off")
tarea.Set("autocapitalize", "off")
tarea.Set("spellcheck", false)
return tarea
}
func createCanvas(doc js.Value) js.Value {
cnv := doc.Call("createElement", "canvas")
style := cnv.Get("style")
style.Set("position", "fixed")
style.Set("width", "100%")
style.Set("height", "100%")
return cnv
}
func (w *window) cleanup() {
// Cleanup in the opposite order of
// construction.
for i := len(w.cleanfuncs) - 1; i >= 0; i-- {
w.cleanfuncs[i]()
}
w.cleanfuncs = nil
}
func (w *window) addEventListeners() {
w.addEventListener(w.window, "resize", func(this js.Value, args []js.Value) interface{} {
w.draw(true)
return nil
})
w.addEventListener(w.cnv, "mousemove", func(this js.Value, args []js.Value) interface{} {
w.pointerEvent(pointer.Move, 0, 0, args[0])
return nil
})
w.addEventListener(w.cnv, "mousedown", func(this js.Value, args []js.Value) interface{} {
w.pointerEvent(pointer.Press, 0, 0, args[0])
return nil
})
w.addEventListener(w.cnv, "mouseup", func(this js.Value, args []js.Value) interface{} {
w.pointerEvent(pointer.Release, 0, 0, args[0])
return nil
})
w.addEventListener(w.cnv, "wheel", func(this js.Value, args []js.Value) interface{} {
e := args[0]
dx, dy := e.Get("deltaX").Float(), e.Get("deltaY").Float()
mode := e.Get("deltaMode").Int()
switch mode {
case 0x01: // DOM_DELTA_LINE
dx *= 10
dy *= 10
case 0x02: // DOM_DELTA_PAGE
dx *= 120
dy *= 120
}
w.pointerEvent(pointer.Move, float32(dx), float32(dy), e)
return nil
})
w.addEventListener(w.cnv, "touchstart", func(this js.Value, args []js.Value) interface{} {
w.touchEvent(pointer.Press, args[0])
return nil
})
w.addEventListener(w.cnv, "touchend", func(this js.Value, args []js.Value) interface{} {
w.touchEvent(pointer.Release, args[0])
return nil
})
w.addEventListener(w.cnv, "touchmove", func(this js.Value, args []js.Value) interface{} {
w.touchEvent(pointer.Move, args[0])
return nil
})
w.addEventListener(w.cnv, "touchcancel", func(this js.Value, args []js.Value) interface{} {
// Cancel all touches even if only one touch was cancelled.
for i := range w.touches {
w.touches[i] = js.Null()
}
w.touches = w.touches[:0]
w.w.event(pointer.Event{
Type: pointer.Cancel,
Source: pointer.Touch,
})
return nil
})
w.addEventListener(w.tarea, "focus", func(this js.Value, args []js.Value) interface{} {
w.w.event(key.FocusEvent{Focus: true})
return nil
})
w.addEventListener(w.tarea, "blur", func(this js.Value, args []js.Value) interface{} {
w.w.event(key.FocusEvent{Focus: false})
return nil
})
w.addEventListener(w.tarea, "keydown", func(this js.Value, args []js.Value) interface{} {
w.keyEvent(args[0])
return nil
})
w.addEventListener(w.tarea, "compositionstart", func(this js.Value, args []js.Value) interface{} {
w.composing = true
return nil
})
w.addEventListener(w.tarea, "compositionend", func(this js.Value, args []js.Value) interface{} {
w.composing = false
w.flushInput()
return nil
})
w.addEventListener(w.tarea, "input", func(this js.Value, args []js.Value) interface{} {
if w.composing {
return nil
}
w.flushInput()
return nil
})
}
func (w *window) flushInput() {
val := w.tarea.Get("value").String()
w.tarea.Set("value", "")
w.w.event(key.EditEvent{Text: string(val)})
}
func (w *window) blur() {
w.tarea.Call("blur")
}
func (w *window) focus() {
w.tarea.Call("focus")
}
func (w *window) keyEvent(e js.Value) {
k := e.Get("key").String()
if n, ok := translateKey(k); ok {
cmd := key.Event{Name: n}
if e.Call("getModifierState", "Control").Bool() {
cmd.Modifiers |= key.ModCommand
}
if e.Call("getModifierState", "Shift").Bool() {
cmd.Modifiers |= key.ModShift
}
w.w.event(cmd)
}
}
func (w *window) touchEvent(typ pointer.Type, e js.Value) {
e.Call("preventDefault")
t := time.Duration(e.Get("timeStamp").Int()) * time.Millisecond
changedTouches := e.Get("changedTouches")
n := changedTouches.Length()
rect := w.cnv.Call("getBoundingClientRect")
w.mu.Lock()
scale := w.scale
w.mu.Unlock()
for i := 0; i < n; i++ {
touch := changedTouches.Index(i)
pid := w.touchIDFor(touch)
x, y := touch.Get("clientX").Float(), touch.Get("clientY").Float()
x -= rect.Get("left").Float()
y -= rect.Get("top").Float()
pos := f32.Point{
X: float32(x) * scale,
Y: float32(y) * scale,
}
w.w.event(pointer.Event{
Type: typ,
Source: pointer.Touch,
Position: pos,
PointerID: pid,
Time: t,
})
}
}
func (w *window) touchIDFor(touch js.Value) pointer.ID {
id := touch.Get("identifier")
for i, id2 := range w.touches {
if id2 == id {
return pointer.ID(i)
}
}
pid := pointer.ID(len(w.touches))
w.touches = append(w.touches, id)
return pid
}
func (w *window) pointerEvent(typ pointer.Type, dx, dy float32, e js.Value) {
e.Call("preventDefault")
x, y := e.Get("clientX").Float(), e.Get("clientY").Float()
rect := w.cnv.Call("getBoundingClientRect")
x -= rect.Get("left").Float()
y -= rect.Get("top").Float()
w.mu.Lock()
scale := w.scale
w.mu.Unlock()
pos := f32.Point{
X: float32(x) * scale,
Y: float32(y) * scale,
}
scroll := f32.Point{
X: dx * scale,
Y: dy * scale,
}
t := time.Duration(e.Get("timeStamp").Int()) * time.Millisecond
w.w.event(pointer.Event{
Type: typ,
Source: pointer.Mouse,
Position: pos,
Scroll: scroll,
Time: t,
})
}
func (w *window) addEventListener(this js.Value, event string, f func(this js.Value, args []js.Value) interface{}) {
jsf := w.funcOf(f)
this.Call("addEventListener", event, jsf)
w.cleanfuncs = append(w.cleanfuncs, func() {
this.Call("removeEventListener", event, jsf)
})
}
// funcOf is like js.FuncOf but adds the js.Func to a list of
// functions to be released up.
func (w *window) funcOf(f func(this js.Value, args []js.Value) interface{}) js.Func {
jsf := js.FuncOf(f)
w.cleanfuncs = append(w.cleanfuncs, jsf.Release)
return jsf
}
func (w *window) animCallback() {
w.mu.Lock()
anim := w.animating
if anim {
w.requestAnimationFrame.Invoke(w.redraw)
}
w.mu.Unlock()
if anim {
w.draw(false)
}
}
func (w *window) setAnimating(anim bool) {
w.mu.Lock()
defer w.mu.Unlock()
if anim && !w.animating {
w.requestAnimationFrame.Invoke(w.redraw)
}
w.animating = anim
}
func (w *window) showTextInput(show bool) {
// Run in a goroutine to avoid a deadlock if the
// focus change result in an event.
go func() {
if show {
w.focus()
} else {
w.blur()
}
}()
}
func (w *window) draw(sync bool) {
width, height, scale, cfg := w.config()
if cfg == (Config{}) {
return
}
w.mu.Lock()
w.scale = float32(scale)
w.mu.Unlock()
cfg.now = time.Now()
w.w.event(UpdateEvent{
Size: image.Point{
X: width,
Y: height,
},
Config: cfg,
sync: sync,
})
}
func (w *window) config() (int, int, float32, Config) {
rect := w.cnv.Call("getBoundingClientRect")
width, height := rect.Get("width").Float(), rect.Get("height").Float()
scale := w.window.Get("devicePixelRatio").Float()
width *= scale
height *= scale
iw, ih := int(width+.5), int(height+.5)
// Adjust internal size of canvas if necessary.
if cw, ch := w.cnv.Get("width").Int(), w.cnv.Get("height").Int(); iw != cw || ih != ch {
w.cnv.Set("width", iw)
w.cnv.Set("height", ih)
}
const ppdp = 96 * inchPrDp * monitorScale
return iw, ih, float32(scale), Config{
pxPerDp: ppdp * float32(scale),
pxPerSp: ppdp * float32(scale),
}
}
func main() {
<-mainDone
}
func translateKey(k string) (rune, bool) {
if len(k) == 1 {
c := k[0]
if '0' <= c && c <= '9' || 'A' <= c && c <= 'Z' {
return rune(c), true
}
if 'a' <= c && c <= 'z' {
return rune(c - 0x20), true
}
}
var n rune
switch k {
case "ArrowUp":
n = key.NameUpArrow
case "ArrowDown":
n = key.NameDownArrow
case "ArrowLeft":
n = key.NameLeftArrow
case "ArrowRight":
n = key.NameRightArrow
case "Escape":
n = key.NameEscape
case "Enter":
n = key.NameReturn
case "Backspace":
n = key.NameDeleteBackward
case "Delete":
n = key.NameDeleteForward
case "Home":
n = key.NameHome
case "End":
n = key.NameEnd
case "PageUp":
n = key.NamePageUp
case "PageDown":
n = key.NamePageDown
default:
return 0, false
}
return n, true
}
+332
View File
@@ -0,0 +1,332 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build darwin,!ios
package app
/*
#cgo CFLAGS: -DGL_SILENCE_DEPRECATION -Werror -fmodules -fobjc-arc -x objective-c
#include <AppKit/AppKit.h>
#include "os_macos.h"
*/
import "C"
import (
"errors"
"image"
"runtime"
"sync"
"time"
"unsafe"
"gioui.org/f32"
"gioui.org/key"
"gioui.org/pointer"
)
func init() {
// Darwin requires that UI operations happen on the main thread only.
runtime.LockOSThread()
}
type window struct {
view C.CFTypeRef
w *Window
stage Stage
ppdp float32
scale float32
}
type viewCmd struct {
view C.CFTypeRef
f viewFunc
}
type viewFunc func(views viewMap, view C.CFTypeRef)
type viewMap map[C.CFTypeRef]*window
var (
viewOnce sync.Once
viewCmds = make(chan viewCmd)
viewAcks = make(chan struct{})
)
var mainWindow = newWindowRendezvous()
var viewFactory func() C.CFTypeRef
func viewDo(view C.CFTypeRef, f viewFunc) {
viewOnce.Do(func() {
go runViewCmdLoop()
})
viewCmds <- viewCmd{view, f}
<-viewAcks
}
func runViewCmdLoop() {
views := make(viewMap)
for {
select {
case cmd := <-viewCmds:
cmd.f(views, cmd.view)
viewAcks <- struct{}{}
}
}
}
func (w *window) contextView() C.CFTypeRef {
return w.view
}
func (w *window) showTextInput(show bool) {}
func (w *window) setAnimating(anim bool) {
var animb C.BOOL
if anim {
animb = 1
}
C.gio_setAnimating(w.view, animb)
}
func (w *window) setStage(stage Stage) {
if stage == w.stage {
return
}
w.stage = stage
w.w.event(StageEvent{stage})
}
// Use a top level func for onFrameCallback to avoid
// garbage from viewDo.
func onFrameCmd(views viewMap, view C.CFTypeRef) {
// CVDisplayLink does not run on the main thread,
// so we have to ignore requests to windows being
// deleted.
if w, exists := views[view]; exists {
w.draw(false)
}
}
//export gio_onFrameCallback
func gio_onFrameCallback(view C.CFTypeRef) {
viewDo(view, onFrameCmd)
}
//export gio_onKeys
func gio_onKeys(view C.CFTypeRef, cstr *C.char, ti C.double, mods C.NSUInteger) {
str := C.GoString(cstr)
var kmods key.Modifiers
if mods&C.NSEventModifierFlagCommand != 0 {
kmods |= key.ModCommand
}
if mods&C.NSEventModifierFlagShift != 0 {
kmods |= key.ModShift
}
viewDo(view, func(views viewMap, view C.CFTypeRef) {
w := views[view]
for _, k := range str {
if n, ok := convertKey(k); ok {
w.w.event(key.Event{Name: n, Modifiers: kmods})
}
}
})
}
//export gio_onText
func gio_onText(view C.CFTypeRef, cstr *C.char) {
str := C.GoString(cstr)
viewDo(view, func(views viewMap, view C.CFTypeRef) {
w := views[view]
w.w.event(key.EditEvent{Text: str})
})
}
//export gio_onMouse
func gio_onMouse(view C.CFTypeRef, cdir C.int, x, y, dx, dy C.CGFloat, ti C.double) {
var typ pointer.Type
switch cdir {
case C.GIO_MOUSE_MOVE:
typ = pointer.Move
case C.GIO_MOUSE_UP:
typ = pointer.Release
case C.GIO_MOUSE_DOWN:
typ = pointer.Press
default:
panic("invalid direction")
}
t := time.Duration(float64(ti)*float64(time.Second) + .5)
viewDo(view, func(views viewMap, view C.CFTypeRef) {
w := views[view]
x, y := float32(x)*w.scale, float32(y)*w.scale
dx, dy := float32(dx)*w.scale, float32(dy)*w.scale
w.w.event(pointer.Event{
Type: typ,
Source: pointer.Mouse,
Time: t,
Position: f32.Point{X: x, Y: y},
Scroll: f32.Point{X: dx, Y: dy},
})
})
}
//export gio_onDraw
func gio_onDraw(view C.CFTypeRef) {
viewDo(view, func(views viewMap, view C.CFTypeRef) {
if w, exists := views[view]; exists {
w.draw(true)
}
})
}
//export gio_onFocus
func gio_onFocus(view C.CFTypeRef, focus C.BOOL) {
viewDo(view, func(views viewMap, view C.CFTypeRef) {
w := views[view]
w.w.event(key.FocusEvent{Focus: focus == C.YES})
})
}
func (w *window) draw(sync bool) {
w.scale = float32(C.gio_getViewBackingScale(w.view))
wf, hf := float32(C.gio_viewWidth(w.view)), float32(C.gio_viewHeight(w.view))
if wf == 0 || hf == 0 {
return
}
width := int(wf*w.scale + .5)
height := int(hf*w.scale + .5)
cfg := configFor(w.ppdp, w.scale)
cfg.now = time.Now()
w.setStage(StageRunning)
w.w.event(UpdateEvent{
Size: image.Point{
X: width,
Y: height,
},
Config: cfg,
sync: sync,
})
}
func getPixelsPerDp(scale float32) float32 {
ppdp := float32(C.gio_getPixelsPerDP())
ppdp = ppdp * scale * monitorScale
if ppdp < minDensity {
ppdp = minDensity
}
return ppdp / scale
}
func configFor(ppdp, scale float32) Config {
ppdp = ppdp * scale
return Config{
pxPerDp: ppdp,
pxPerSp: ppdp,
}
}
//export gio_onTerminate
func gio_onTerminate(view C.CFTypeRef) {
viewDo(view, func(views viewMap, view C.CFTypeRef) {
w := views[view]
delete(views, view)
w.w.event(DestroyEvent{})
})
}
//export gio_onHide
func gio_onHide(view C.CFTypeRef) {
viewDo(view, func(views viewMap, view C.CFTypeRef) {
w := views[view]
w.setStage(StagePaused)
})
}
//export gio_onShow
func gio_onShow(view C.CFTypeRef) {
viewDo(view, func(views viewMap, view C.CFTypeRef) {
w := views[view]
w.setStage(StageRunning)
})
}
//export gio_onCreate
func gio_onCreate(view C.CFTypeRef) {
viewDo(view, func(views viewMap, view C.CFTypeRef) {
scale := float32(C.gio_getBackingScale())
w := &window{
view: view,
ppdp: getPixelsPerDp(scale),
scale: scale,
}
wopts := <-mainWindow.out
w.w = wopts.window
w.w.setDriver(w)
views[view] = w
})
}
func createWindow(win *Window, opts *windowOptions) error {
mainWindow.in <- windowAndOptions{win, opts}
return <-mainWindow.errs
}
func main() {
wopts := <-mainWindow.out
view := viewFactory()
if view == 0 {
// TODO: return this error from CreateWindow.
panic(errors.New("CreateWindow: failed to create view"))
}
scale := float32(C.gio_getBackingScale())
ppdp := getPixelsPerDp(scale)
cfg := configFor(ppdp, scale)
opts := wopts.opts
w := cfg.Px(opts.Width)
h := cfg.Px(opts.Height)
// Window sizes is on screen coordinates, not device pixels.
w = int(float32(w) / scale)
h = int(float32(h) / scale)
title := C.CString(opts.Title)
defer C.free(unsafe.Pointer(title))
C.gio_main(view, title, C.CGFloat(w), C.CGFloat(h))
}
func convertKey(k rune) (rune, bool) {
if '0' <= k && k <= '9' || 'A' <= k && k <= 'Z' {
return k, true
}
if 'a' <= k && k <= 'z' {
return k - 0x20, true
}
var n rune
switch k {
case 0x1b:
n = key.NameEscape
case C.NSLeftArrowFunctionKey:
n = key.NameLeftArrow
case C.NSRightArrowFunctionKey:
n = key.NameRightArrow
case C.NSUpArrowFunctionKey:
n = key.NameUpArrow
case C.NSDownArrowFunctionKey:
n = key.NameDownArrow
case 0xd:
n = key.NameReturn
case C.NSHomeFunctionKey:
n = key.NameHome
case C.NSEndFunctionKey:
n = key.NameEnd
case 0x7f:
n = key.NameDeleteBackward
case C.NSDeleteFunctionKey:
n = key.NameDeleteForward
case C.NSPageUpFunctionKey:
n = key.NamePageUp
case C.NSPageDownFunctionKey:
n = key.NamePageDown
default:
return 0, false
}
return n, true
}
+19
View File
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: Unlicense OR MIT
#ifndef _OS_MACOS_H
#define _OS_MACOS_H
#define GIO_MOUSE_MOVE 1
#define GIO_MOUSE_UP 2
#define GIO_MOUSE_DOWN 3
__attribute__ ((visibility ("hidden"))) void gio_main(CFTypeRef viewRef, const char *title, CGFloat width, CGFloat height);
__attribute__ ((visibility ("hidden"))) CGFloat gio_viewWidth(CFTypeRef viewRef);
__attribute__ ((visibility ("hidden"))) CGFloat gio_viewHeight(CFTypeRef viewRef);
__attribute__ ((visibility ("hidden"))) void gio_setAnimating(CFTypeRef viewRef, BOOL anim);
__attribute__ ((visibility ("hidden"))) void gio_updateDisplayLink(CFTypeRef viewRef, CGDirectDisplayID dispID);
__attribute__ ((visibility ("hidden"))) CGFloat gio_getPixelsPerDP(void);
__attribute__ ((visibility ("hidden"))) CGFloat gio_getBackingScale(void);
__attribute__ ((visibility ("hidden"))) CGFloat gio_getViewBackingScale(CFTypeRef viewRef);
#endif
+130
View File
@@ -0,0 +1,130 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build darwin,!ios
@import AppKit;
#include "os_macos.h"
#include "_cgo_export.h"
@interface GioDelegate : NSObject<NSApplicationDelegate, NSWindowDelegate>
@property (strong,nonatomic) NSWindow *window;
@end
@implementation GioDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[[NSRunningApplication currentApplication] activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)];
[self.window makeKeyAndOrderFront:self];
gio_onShow((__bridge CFTypeRef)self.window.contentView);
}
- (void)applicationDidHide:(NSNotification *)aNotification {
gio_onHide((__bridge CFTypeRef)self.window.contentView);
}
- (void)applicationWillUnhide:(NSNotification *)notification {
gio_onShow((__bridge CFTypeRef)self.window.contentView);
}
- (void)windowWillMiniaturize:(NSNotification *)notification {
gio_onHide((__bridge CFTypeRef)self.window.contentView);
}
- (void)windowDidDeminiaturize:(NSNotification *)notification {
gio_onShow((__bridge CFTypeRef)self.window.contentView);
}
- (void)windowDidChangeScreen:(NSNotification *)notification {
CGDirectDisplayID dispID = [[[self.window screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue];
CFTypeRef view = (__bridge CFTypeRef)self.window.contentView;
gio_updateDisplayLink(view, dispID);
}
- (void)windowDidBecomeKey:(NSNotification *)notification {
gio_onFocus((__bridge CFTypeRef)self.window.contentView, YES);
}
- (void)windowDidResignKey:(NSNotification *)notification {
gio_onFocus((__bridge CFTypeRef)self.window.contentView, NO);
}
- (void)windowWillClose:(NSNotification *)notification {
gio_onTerminate((__bridge CFTypeRef)self.window.contentView);
self.window.delegate = nil;
[NSApp terminate:nil];
}
@end
CGFloat gio_viewHeight(CFTypeRef viewRef) {
NSView *view = (__bridge NSView *)viewRef;
return [view bounds].size.height;
}
CGFloat gio_viewWidth(CFTypeRef viewRef) {
NSView *view = (__bridge NSView *)viewRef;
return [view bounds].size.width;
}
// Points pr. dp.
static CGFloat getPointsPerDP(NSScreen *screen) {
NSDictionary *description = [screen deviceDescription];
NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue];
CGSize displayPhysicalSize = CGDisplayScreenSize([[description objectForKey:@"NSScreenNumber"] unsignedIntValue]);
return (25.4/160)*displayPixelSize.width / displayPhysicalSize.width;
}
// Pixels pr dp.
CGFloat gio_getPixelsPerDP(void) {
NSScreen *screen = [NSScreen mainScreen];
return getPointsPerDP(screen);
}
CGFloat gio_getBackingScale() {
NSScreen *screen = [NSScreen mainScreen];
return [screen backingScaleFactor];
}
CGFloat gio_getViewBackingScale(CFTypeRef viewRef) {
NSView *view = (__bridge NSView *)viewRef;
return [view.window backingScaleFactor];
}
void gio_main(CFTypeRef viewRef, const char *title, CGFloat width, CGFloat height) {
@autoreleasepool {
NSView *view = (NSView *)CFBridgingRelease(viewRef);
[NSApplication sharedApplication];
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
NSMenuItem *mainMenu = [NSMenuItem new];
NSMenu *menu = [NSMenu new];
NSMenuItem *hideMenuItem = [[NSMenuItem alloc] initWithTitle:@"Hide"
action:@selector(hide:)
keyEquivalent:@"h"];
[menu addItem:hideMenuItem];
NSMenuItem *quitMenuItem = [[NSMenuItem alloc] initWithTitle:@"Quit"
action:@selector(terminate:)
keyEquivalent:@"q"];
[menu addItem:quitMenuItem];
[mainMenu setSubmenu:menu];
NSMenu *menuBar = [NSMenu new];
[menuBar addItem:mainMenu];
[NSApp setMainMenu:menuBar];
NSRect rect = NSMakeRect(0, 0, width, height);
NSWindowStyleMask styleMask = NSWindowStyleMaskTitled |
NSWindowStyleMaskResizable |
NSWindowStyleMaskMiniaturizable |
NSWindowStyleMaskClosable;
NSWindow* window = [[NSWindow alloc] initWithContentRect:rect
styleMask:styleMask
backing:NSBackingStoreBuffered
defer:NO];
window.title = [NSString stringWithUTF8String: title];
[window cascadeTopLeftFromPoint:NSMakePoint(20,20)];
[window setAcceptsMouseMovedEvents:YES];
gio_onCreate((__bridge CFTypeRef)view);
GioDelegate *del = [[GioDelegate alloc] init];
del.window = window;
[window setDelegate:del];
[NSApp setDelegate:del];
[window setContentView:view];
[window makeFirstResponder:view];
[NSApp run];
}
}
+138
View File
@@ -0,0 +1,138 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build linux,!android
#include <wayland-client.h>
#include "wayland_xdg_shell.h"
#include "wayland_text_input.h"
#include "os_wayland.h"
#include "_cgo_export.h"
static const struct wl_registry_listener registry_listener = {
// Cast away const parameter.
.global = (void (*)(void *, struct wl_registry *, uint32_t, const char *, uint32_t))gio_onRegistryGlobal,
.global_remove = gio_onRegistryGlobalRemove
};
void gio_wl_registry_add_listener(struct wl_registry *reg) {
wl_registry_add_listener(reg, &registry_listener, NULL);
}
static struct wl_surface_listener surface_listener = {.enter = gio_onSurfaceEnter, .leave = gio_onSurfaceLeave};
void gio_wl_surface_add_listener(struct wl_surface *surface) {
wl_surface_add_listener(surface, &surface_listener, NULL);
}
static const struct xdg_surface_listener xdg_surface_listener = {
.configure = gio_onXdgSurfaceConfigure,
};
void gio_xdg_surface_add_listener(struct xdg_surface *surface) {
xdg_surface_add_listener(surface, &xdg_surface_listener, NULL);
}
static const struct xdg_toplevel_listener xdg_toplevel_listener = {
.configure = gio_onToplevelConfigure,
.close = gio_onToplevelClose,
};
void gio_xdg_toplevel_add_listener(struct xdg_toplevel *toplevel) {
xdg_toplevel_add_listener(toplevel, &xdg_toplevel_listener, NULL);
}
static void xdg_wm_base_handle_ping(void *data, struct xdg_wm_base *wm, uint32_t serial) {
xdg_wm_base_pong(wm, serial);
}
static const struct xdg_wm_base_listener xdg_wm_base_listener = {
.ping = xdg_wm_base_handle_ping,
};
void gio_xdg_wm_base_add_listener(struct xdg_wm_base *wm) {
xdg_wm_base_add_listener(wm, &xdg_wm_base_listener, NULL);
}
static const struct wl_callback_listener wl_callback_listener = {
.done = gio_onFrameDone,
};
void gio_wl_callback_add_listener(struct wl_callback *callback, void *data) {
wl_callback_add_listener(callback, &wl_callback_listener, data);
}
static const struct wl_output_listener wl_output_listener = {
// Cast away const parameter.
.geometry = (void (*)(void *, struct wl_output *, int32_t, int32_t, int32_t, int32_t, int32_t, const char *, const char *, int32_t))gio_onOutputGeometry,
.mode = gio_onOutputMode,
.done = gio_onOutputDone,
.scale = gio_onOutputScale,
};
void gio_wl_output_add_listener(struct wl_output *output) {
wl_output_add_listener(output, &wl_output_listener, NULL);
}
static const struct wl_seat_listener wl_seat_listener = {
.capabilities = gio_onSeatCapabilities,
// Cast away const parameter.
.name = (void (*)(void *, struct wl_seat *, const char *))gio_onSeatName,
};
void gio_wl_seat_add_listener(struct wl_seat *seat) {
wl_seat_add_listener(seat, &wl_seat_listener, NULL);
}
static const struct wl_pointer_listener wl_pointer_listener = {
.enter = gio_onPointerEnter,
.leave = gio_onPointerLeave,
.motion = gio_onPointerMotion,
.button = gio_onPointerButton,
.axis = gio_onPointerAxis,
.frame = gio_onPointerFrame,
.axis_source = gio_onPointerAxisSource,
.axis_stop = gio_onPointerAxisStop,
.axis_discrete = gio_onPointerAxisDiscrete,
};
void gio_wl_pointer_add_listener(struct wl_pointer *pointer) {
wl_pointer_add_listener(pointer, &wl_pointer_listener, NULL);
}
static const struct wl_touch_listener wl_touch_listener = {
.down = gio_onTouchDown,
.up = gio_onTouchUp,
.motion = gio_onTouchMotion,
.frame = gio_onTouchFrame,
.cancel = gio_onTouchCancel,
};
void gio_wl_touch_add_listener(struct wl_touch *touch) {
wl_touch_add_listener(touch, &wl_touch_listener, NULL);
}
static const struct wl_keyboard_listener wl_keyboard_listener = {
.keymap = gio_onKeyboardKeymap,
.enter = gio_onKeyboardEnter,
.leave = gio_onKeyboardLeave,
.key = gio_onKeyboardKey,
.modifiers = gio_onKeyboardModifiers,
.repeat_info = gio_onKeyboardRepeatInfo
};
void gio_wl_keyboard_add_listener(struct wl_keyboard *keyboard) {
wl_keyboard_add_listener(keyboard, &wl_keyboard_listener, NULL);
}
static const struct zwp_text_input_v3_listener zwp_text_input_v3_listener = {
.enter = gio_onTextInputEnter,
.leave = gio_onTextInputLeave,
// Cast away const parameter.
.preedit_string = (void (*)(void *, struct zwp_text_input_v3 *, const char *, int32_t, int32_t))gio_onTextInputPreeditString,
.commit_string = (void (*)(void *, struct zwp_text_input_v3 *, const char *))gio_onTextInputCommitString,
.delete_surrounding_text = gio_onTextInputDeleteSurroundingText,
.done = gio_onTextInputDone
};
void gio_zwp_text_input_v3_add_listener(struct zwp_text_input_v3 *im) {
zwp_text_input_v3_add_listener(im, &zwp_text_input_v3_listener, NULL);
}
+1190
View File
File diff suppressed because it is too large Load Diff
+14
View File
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: Unlicense OR MIT
__attribute__ ((visibility ("hidden"))) void gio_wl_registry_add_listener(struct wl_registry *reg);
__attribute__ ((visibility ("hidden"))) void gio_wl_surface_add_listener(struct wl_surface *surface);
__attribute__ ((visibility ("hidden"))) void gio_xdg_surface_add_listener(struct xdg_surface *surface);
__attribute__ ((visibility ("hidden"))) void gio_xdg_toplevel_add_listener(struct xdg_toplevel *toplevel);
__attribute__ ((visibility ("hidden"))) void gio_xdg_wm_base_add_listener(struct xdg_wm_base *wm);
__attribute__ ((visibility ("hidden"))) void gio_wl_callback_add_listener(struct wl_callback *callback, void *data);
__attribute__ ((visibility ("hidden"))) void gio_wl_output_add_listener(struct wl_output *output);
__attribute__ ((visibility ("hidden"))) void gio_wl_seat_add_listener(struct wl_seat *seat);
__attribute__ ((visibility ("hidden"))) void gio_wl_pointer_add_listener(struct wl_pointer *pointer);
__attribute__ ((visibility ("hidden"))) void gio_wl_touch_add_listener(struct wl_touch *touch);
__attribute__ ((visibility ("hidden"))) void gio_wl_keyboard_add_listener(struct wl_keyboard *keyboard);
__attribute__ ((visibility ("hidden"))) void gio_zwp_text_input_v3_add_listener(struct zwp_text_input_v3 *im);
+732
View File
@@ -0,0 +1,732 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
import (
"errors"
"fmt"
"image"
"runtime"
"sync"
"time"
"unicode"
"unsafe"
syscall "golang.org/x/sys/windows"
"gioui.org/f32"
"gioui.org/key"
"gioui.org/pointer"
)
var winMap = make(map[syscall.Handle]*window)
type rect struct {
left, top, right, bottom int32
}
type wndClassEx struct {
cbSize uint32
style uint32
lpfnWndProc uintptr
cnClsExtra int32
cbWndExtra int32
hInstance syscall.Handle
hIcon syscall.Handle
hCursor syscall.Handle
hbrBackground syscall.Handle
lpszMenuName *uint16
lpszClassName *uint16
hIconSm syscall.Handle
}
type msg struct {
hwnd syscall.Handle
message uint32
wParam uintptr
lParam uintptr
time uint32
pt point
lPrivate uint32
}
type point struct {
x, y int32
}
type window struct {
hwnd syscall.Handle
hdc syscall.Handle
w *Window
width int
height int
stage Stage
dead bool
mu sync.Mutex
animating bool
}
const (
_CS_HREDRAW = 0x0002
_CS_VREDRAW = 0x0001
_CS_OWNDC = 0x0020
_CW_USEDEFAULT = -2147483648
_IDC_ARROW = 32512
_INFINITE = 0xFFFFFFFF
_LOGPIXELSX = 88
_SIZE_MAXIMIZED = 2
_SIZE_MINIMIZED = 1
_SIZE_RESTORED = 0
_SW_SHOWDEFAULT = 10
_USER_TIMER_MINIMUM = 0x0000000A
_VK_CONTROL = 0x11
_VK_SHIFT = 0x10
_VK_BACK = 0x08
_VK_DELETE = 0x2e
_VK_DOWN = 0x28
_VK_END = 0x23
_VK_ESCAPE = 0x1b
_VK_HOME = 0x24
_VK_LEFT = 0x25
_VK_NEXT = 0x22
_VK_PRIOR = 0x21
_VK_RIGHT = 0x27
_VK_RETURN = 0x0d
_VK_UP = 0x26
_UNICODE_NOCHAR = 65535
_WM_CANCELMODE = 0x001F
_WM_CHAR = 0x0102
_WM_CREATE = 0x0001
_WM_DESTROY = 0x0002
_WM_KEYDOWN = 0x0100
_WM_KEYUP = 0x0101
_WM_LBUTTONDOWN = 0x0201
_WM_LBUTTONUP = 0x0202
_WM_MOUSEMOVE = 0x0200
_WM_MOUSEWHEEL = 0x020A
_WM_PAINT = 0x000F
_WM_QUIT = 0x0012
_WM_SETFOCUS = 0x0007
_WM_KILLFOCUS = 0x0008
_WM_SHOWWINDOW = 0x0018
_WM_SIZE = 0x0005
_WM_SYSKEYDOWN = 0x0104
_WM_TIMER = 0x0113
_WM_UNICHAR = 0x0109
_WM_USER = 0x0400
_WS_CLIPCHILDREN = 0x00010000
_WS_CLIPSIBLINGS = 0x04000000
_WS_VISIBLE = 0x10000000
_WS_OVERLAPPED = 0x00000000
_WS_OVERLAPPEDWINDOW = _WS_OVERLAPPED | _WS_CAPTION | _WS_SYSMENU | _WS_THICKFRAME |
_WS_MINIMIZEBOX | _WS_MAXIMIZEBOX
_WS_CAPTION = 0x00C00000
_WS_SYSMENU = 0x00080000
_WS_THICKFRAME = 0x00040000
_WS_MINIMIZEBOX = 0x00020000
_WS_MAXIMIZEBOX = 0x00010000
_WS_EX_APPWINDOW = 0x00040000
_WS_EX_WINDOWEDGE = 0x00000100
_QS_ALLINPUT = 0x04FF
_MWMO_WAITALL = 0x0001
_MWMO_INPUTAVAILABLE = 0x0004
_WAIT_OBJECT_0 = 0
_PM_REMOVE = 0x0001
_PM_NOREMOVE = 0x0000
)
const _WM_REDRAW = _WM_USER + 0
var onceMu sync.Mutex
var mainDone = make(chan struct{})
func main() {
<-mainDone
}
func createWindow(window *Window, opts *windowOptions) error {
onceMu.Lock()
defer onceMu.Unlock()
if len(winMap) > 0 {
return errors.New("multiple windows are not supported")
}
cerr := make(chan error)
go func() {
// Call win32 API from a single OS thread.
runtime.LockOSThread()
w, err := createNativeWindow(opts)
if err != nil {
cerr <- err
return
}
defer w.destroy()
cerr <- nil
winMap[w.hwnd] = w
defer delete(winMap, w.hwnd)
w.w = window
w.w.setDriver(w)
defer w.w.event(DestroyEvent{})
showWindow(w.hwnd, _SW_SHOWDEFAULT)
setForegroundWindow(w.hwnd)
setFocus(w.hwnd)
if err := w.loop(); err != nil {
panic(err)
}
close(mainDone)
}()
return <-cerr
}
func createNativeWindow(opts *windowOptions) (*window, error) {
setProcessDPIAware()
screenDC, err := getDC(0)
if err != nil {
return nil, err
}
cfg := configForDC(screenDC)
releaseDC(screenDC)
hInst, err := getModuleHandle()
if err != nil {
return nil, err
}
curs, err := loadCursor(_IDC_ARROW)
if err != nil {
return nil, err
}
wcls := wndClassEx{
cbSize: uint32(unsafe.Sizeof(wndClassEx{})),
style: _CS_HREDRAW | _CS_VREDRAW | _CS_OWNDC,
lpfnWndProc: syscall.NewCallback(windowProc),
hInstance: hInst,
hCursor: curs,
lpszClassName: syscall.StringToUTF16Ptr("GioWindow"),
}
cls, err := registerClassEx(&wcls)
if err != nil {
return nil, err
}
wr := rect{
right: int32(cfg.Px(opts.Width)),
bottom: int32(cfg.Px(opts.Height)),
}
dwStyle := uint32(_WS_OVERLAPPEDWINDOW)
dwExStyle := uint32(_WS_EX_APPWINDOW | _WS_EX_WINDOWEDGE)
adjustWindowRectEx(&wr, dwStyle, 0, dwExStyle)
hwnd, err := createWindowEx(dwExStyle,
cls,
opts.Title,
dwStyle|_WS_CLIPSIBLINGS|_WS_CLIPCHILDREN,
_CW_USEDEFAULT, _CW_USEDEFAULT,
wr.right-wr.left,
wr.bottom-wr.top,
0,
0,
hInst,
0)
if err != nil {
return nil, err
}
w := &window{
hwnd: hwnd,
}
w.hdc, err = getDC(hwnd)
if err != nil {
return nil, err
}
return w, nil
}
func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
w := winMap[hwnd]
switch msg {
case _WM_UNICHAR:
if wParam == _UNICODE_NOCHAR {
// Tell the system that we accept WM_UNICHAR messages.
return 1
}
fallthrough
case _WM_CHAR:
if r := rune(wParam); unicode.IsPrint(r) {
w.w.event(key.EditEvent{Text: string(r)})
}
// The message is processed.
return 1
case _WM_KEYDOWN, _WM_SYSKEYDOWN:
if n, ok := convertKeyCode(wParam); ok {
cmd := key.Event{Name: n}
if getKeyState(_VK_CONTROL)&0x1000 != 0 {
cmd.Modifiers |= key.ModCommand
}
if getKeyState(_VK_SHIFT)&0x1000 != 0 {
cmd.Modifiers |= key.ModShift
}
w.w.event(cmd)
}
case _WM_LBUTTONDOWN:
setCapture(w.hwnd)
x, y := coordsFromlParam(lParam)
p := f32.Point{X: float32(x), Y: float32(y)}
w.w.event(pointer.Event{
Type: pointer.Press,
Source: pointer.Mouse,
Position: p,
Time: getMessageTime(),
})
case _WM_CANCELMODE:
w.w.event(pointer.Event{
Type: pointer.Cancel,
})
case _WM_SETFOCUS:
w.w.event(key.FocusEvent{Focus: true})
case _WM_KILLFOCUS:
w.w.event(key.FocusEvent{Focus: false})
case _WM_LBUTTONUP:
releaseCapture()
x, y := coordsFromlParam(lParam)
p := f32.Point{X: float32(x), Y: float32(y)}
w.w.event(pointer.Event{
Type: pointer.Release,
Source: pointer.Mouse,
Position: p,
Time: getMessageTime(),
})
case _WM_MOUSEMOVE:
x, y := coordsFromlParam(lParam)
p := f32.Point{X: float32(x), Y: float32(y)}
w.w.event(pointer.Event{
Type: pointer.Move,
Source: pointer.Mouse,
Position: p,
Time: getMessageTime(),
})
case _WM_MOUSEWHEEL:
w.scrollEvent(wParam, lParam)
case _WM_DESTROY:
w.dead = true
case _WM_PAINT:
w.draw(true)
case _WM_SIZE:
switch wParam {
case _SIZE_MINIMIZED:
w.setStage(StagePaused)
case _SIZE_MAXIMIZED, _SIZE_RESTORED:
w.setStage(StageRunning)
w.draw(true)
}
}
return defWindowProc(hwnd, msg, wParam, lParam)
}
func coordsFromlParam(lParam uintptr) (int, int) {
x := int(int16(lParam & 0xffff))
y := int(int16((lParam >> 16) & 0xffff))
return x, y
}
func (w *window) scrollEvent(wParam, lParam uintptr) {
x, y := coordsFromlParam(lParam)
// The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast
// to other mouse events.
np := point{x: int32(x), y: int32(y)}
screenToClient(w.hwnd, &np)
p := f32.Point{X: float32(np.x), Y: float32(np.y)}
dist := float32(int16(wParam >> 16))
w.w.event(pointer.Event{
Type: pointer.Move,
Source: pointer.Mouse,
Position: p,
Scroll: f32.Point{Y: -dist},
Time: getMessageTime(),
})
}
// Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/
func (w *window) loop() error {
msg := new(msg)
for !w.dead {
w.mu.Lock()
anim := w.animating
w.mu.Unlock()
if anim && !peekMessage(msg, w.hwnd, 0, 0, _PM_NOREMOVE) {
w.draw(false)
continue
}
getMessage(msg, w.hwnd, 0, 0)
if msg.message == _WM_QUIT {
postQuitMessage(msg.wParam)
break
}
translateMessage(msg)
dispatchMessage(msg)
}
return nil
}
func (w *window) setAnimating(anim bool) {
w.mu.Lock()
w.animating = anim
w.mu.Unlock()
if anim {
w.postRedraw()
}
}
func (w *window) postRedraw() {
if err := postMessage(w.hwnd, _WM_REDRAW, 0, 0); err != nil {
panic(err)
}
}
func (w *window) setStage(s Stage) {
w.stage = s
w.w.event(StageEvent{s})
}
func (w *window) draw(sync bool) {
var r rect
getClientRect(w.hwnd, &r)
w.width = int(r.right - r.left)
w.height = int(r.bottom - r.top)
cfg := configForDC(w.hdc)
cfg.now = time.Now()
w.w.event(UpdateEvent{
Size: image.Point{
X: w.width,
Y: w.height,
},
Config: cfg,
sync: sync,
})
}
func (w *window) destroy() {
if w.hdc != 0 {
releaseDC(w.hdc)
w.hdc = 0
}
if w.hwnd != 0 {
destroyWindow(w.hwnd)
w.hwnd = 0
}
}
func (w *window) showTextInput(show bool) {}
func (w *window) display() uintptr {
return uintptr(w.hdc)
}
func (w *window) nativeWindow(visID int) (uintptr, int, int) {
return uintptr(w.hwnd), w.width, w.height
}
func convertKeyCode(code uintptr) (rune, bool) {
if '0' <= code && code <= '9' || 'A' <= code && code <= 'Z' {
return rune(code), true
}
var r rune
switch code {
case _VK_ESCAPE:
r = key.NameEscape
case _VK_LEFT:
r = key.NameLeftArrow
case _VK_RIGHT:
r = key.NameRightArrow
case _VK_RETURN:
r = key.NameReturn
case _VK_UP:
r = key.NameUpArrow
case _VK_DOWN:
r = key.NameDownArrow
case _VK_HOME:
r = key.NameHome
case _VK_END:
r = key.NameEnd
case _VK_BACK:
r = key.NameDeleteBackward
case _VK_DELETE:
r = key.NameDeleteForward
case _VK_PRIOR:
r = key.NamePageUp
case _VK_NEXT:
r = key.NamePageDown
default:
return 0, false
}
return r, true
}
func configForDC(hdc syscall.Handle) Config {
dpi := getDeviceCaps(hdc, _LOGPIXELSX)
ppdp := float32(dpi) * inchPrDp * monitorScale
// Force a minimum density to keep text legible and to handle bogus output geometry.
if ppdp < minDensity {
ppdp = minDensity
}
return Config{
pxPerDp: ppdp,
pxPerSp: ppdp,
}
}
var (
kernel32 = syscall.NewLazySystemDLL("kernel32.dll")
_GetModuleHandleW = kernel32.NewProc("GetModuleHandleW")
user32 = syscall.NewLazySystemDLL("user32.dll")
_AdjustWindowRectEx = user32.NewProc("AdjustWindowRectEx")
_CallMsgFilter = user32.NewProc("CallMsgFilterW")
_CreateWindowEx = user32.NewProc("CreateWindowExW")
_DefWindowProc = user32.NewProc("DefWindowProcW")
_DestroyWindow = user32.NewProc("DestroyWindow")
_DispatchMessage = user32.NewProc("DispatchMessageW")
_GetClientRect = user32.NewProc("GetClientRect")
_GetDC = user32.NewProc("GetDC")
_GetKeyState = user32.NewProc("GetKeyState")
_GetMessage = user32.NewProc("GetMessageW")
_GetMessageTime = user32.NewProc("GetMessageTime")
_KillTimer = user32.NewProc("KillTimer")
_LoadCursor = user32.NewProc("LoadCursorW")
_MsgWaitForMultipleObjectsEx = user32.NewProc("MsgWaitForMultipleObjectsEx")
_PeekMessage = user32.NewProc("PeekMessageW")
_PostMessage = user32.NewProc("PostMessageW")
_PostQuitMessage = user32.NewProc("PostQuitMessage")
_ReleaseCapture = user32.NewProc("ReleaseCapture")
_RegisterClassExW = user32.NewProc("RegisterClassExW")
_ReleaseDC = user32.NewProc("ReleaseDC")
_ScreenToClient = user32.NewProc("ScreenToClient")
_ShowWindow = user32.NewProc("ShowWindow")
_SetCapture = user32.NewProc("SetCapture")
_SetForegroundWindow = user32.NewProc("SetForegroundWindow")
_SetFocus = user32.NewProc("SetFocus")
_SetProcessDPIAware = user32.NewProc("SetProcessDPIAware")
_SetTimer = user32.NewProc("SetTimer")
_TranslateMessage = user32.NewProc("TranslateMessage")
_UnregisterClass = user32.NewProc("UnregisterClassW")
_UpdateWindow = user32.NewProc("UpdateWindow")
gdi32 = syscall.NewLazySystemDLL("gdi32")
_GetDeviceCaps = gdi32.NewProc("GetDeviceCaps")
)
func getModuleHandle() (syscall.Handle, error) {
h, _, err := _GetModuleHandleW.Call(uintptr(0))
if h == 0 {
return 0, fmt.Errorf("GetModuleHandleW failed: %v", err)
}
return syscall.Handle(h), nil
}
func adjustWindowRectEx(r *rect, dwStyle uint32, bMenu int, dwExStyle uint32) {
_AdjustWindowRectEx.Call(uintptr(unsafe.Pointer(r)), uintptr(dwStyle), uintptr(bMenu), uintptr(dwExStyle))
issue34474KeepAlive(r)
}
func callMsgFilter(m *msg, nCode uintptr) bool {
r, _, _ := _CallMsgFilter.Call(uintptr(unsafe.Pointer(m)), nCode)
issue34474KeepAlive(m)
return r != 0
}
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)
hwnd, _, err := _CreateWindowEx.Call(
uintptr(dwExStyle),
uintptr(lpClassName),
uintptr(unsafe.Pointer(wname)),
uintptr(dwStyle),
uintptr(x), uintptr(y),
uintptr(w), uintptr(h),
uintptr(hWndParent),
uintptr(hMenu),
uintptr(hInstance),
uintptr(lpParam))
issue34474KeepAlive(wname)
if hwnd == 0 {
return 0, fmt.Errorf("CreateWindowEx failed: %v", err)
}
return syscall.Handle(hwnd), nil
}
func defWindowProc(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
r, _, _ := _DefWindowProc.Call(uintptr(hwnd), uintptr(msg), wparam, lparam)
return r
}
func destroyWindow(hwnd syscall.Handle) {
_DestroyWindow.Call(uintptr(hwnd))
}
func dispatchMessage(m *msg) {
_DispatchMessage.Call(uintptr(unsafe.Pointer(m)))
issue34474KeepAlive(m)
}
func getClientRect(hwnd syscall.Handle, r *rect) {
_GetClientRect.Call(uintptr(hwnd), uintptr(unsafe.Pointer(r)))
issue34474KeepAlive(r)
}
func getDC(hwnd syscall.Handle) (syscall.Handle, error) {
hdc, _, err := _GetDC.Call(uintptr(hwnd))
if hdc == 0 {
return 0, fmt.Errorf("GetDC failed: %v", err)
}
return syscall.Handle(hdc), nil
}
func getDeviceCaps(hdc syscall.Handle, index int32) int {
c, _, _ := _GetDeviceCaps.Call(uintptr(hdc), uintptr(index))
return int(c)
}
func getKeyState(nVirtKey int32) int16 {
c, _, _ := _GetKeyState.Call(uintptr(nVirtKey))
return int16(c)
}
func getMessage(m *msg, hwnd syscall.Handle, wMsgFilterMin, wMsgFilterMax uint32) int32 {
r, _, _ := _GetMessage.Call(uintptr(unsafe.Pointer(m)),
uintptr(hwnd),
uintptr(wMsgFilterMin),
uintptr(wMsgFilterMax))
issue34474KeepAlive(m)
return int32(r)
}
func getMessageTime() time.Duration {
r, _, _ := _GetMessageTime.Call()
return time.Duration(r) * time.Millisecond
}
func killTimer(hwnd syscall.Handle, nIDEvent uintptr) error {
r, _, err := _SetTimer.Call(uintptr(hwnd), uintptr(nIDEvent), 0, 0)
if r == 0 {
return fmt.Errorf("KillTimer failed: %v", err)
}
return nil
}
func loadCursor(curID uint16) (syscall.Handle, error) {
h, _, err := _LoadCursor.Call(0, uintptr(curID))
if h == 0 {
return 0, fmt.Errorf("LoadCursorW failed: %v", err)
}
return syscall.Handle(h), nil
}
func msgWaitForMultipleObjectsEx(nCount uint32, pHandles uintptr, millis, mask, flags uint32) (uint32, error) {
r, _, err := _MsgWaitForMultipleObjectsEx.Call(uintptr(nCount), pHandles, uintptr(millis), uintptr(mask), uintptr(flags))
res := uint32(r)
if res == 0xFFFFFFFF {
return 0, fmt.Errorf("MsgWaitForMultipleObjectsEx failed: %v", err)
}
return res, nil
}
func peekMessage(m *msg, hwnd syscall.Handle, wMsgFilterMin, wMsgFilterMax, wRemoveMsg uint32) bool {
r, _, _ := _PeekMessage.Call(uintptr(unsafe.Pointer(m)), uintptr(hwnd), uintptr(wMsgFilterMin), uintptr(wMsgFilterMax), uintptr(wRemoveMsg))
issue34474KeepAlive(m)
return r != 0
}
func postQuitMessage(exitCode uintptr) {
_PostQuitMessage.Call(exitCode)
}
func postMessage(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) error {
r, _, err := _PostMessage.Call(uintptr(hwnd), uintptr(msg), wParam, lParam)
if r == 0 {
return fmt.Errorf("PostMessage failed: %v", err)
}
return nil
}
func releaseCapture() bool {
r, _, _ := _ReleaseCapture.Call()
return r != 0
}
func registerClassEx(cls *wndClassEx) (uint16, error) {
a, _, err := _RegisterClassExW.Call(uintptr(unsafe.Pointer(cls)))
issue34474KeepAlive(cls)
if a == 0 {
return 0, fmt.Errorf("RegisterClassExW failed: %v", err)
}
return uint16(a), nil
}
func releaseDC(hdc syscall.Handle) {
_ReleaseDC.Call(uintptr(hdc))
}
func setForegroundWindow(hwnd syscall.Handle) {
_SetForegroundWindow.Call(uintptr(hwnd))
}
func setFocus(hwnd syscall.Handle) {
_SetFocus.Call(uintptr(hwnd))
}
func setProcessDPIAware() {
_SetProcessDPIAware.Call()
}
func setCapture(hwnd syscall.Handle) syscall.Handle {
r, _, _ := _SetCapture.Call(uintptr(hwnd))
return syscall.Handle(r)
}
func setTimer(hwnd syscall.Handle, nIDEvent uintptr, uElapse uint32, timerProc uintptr) error {
r, _, err := _SetTimer.Call(uintptr(hwnd), uintptr(nIDEvent), uintptr(uElapse), timerProc)
if r == 0 {
return fmt.Errorf("SetTimer failed: %v", err)
}
return nil
}
func screenToClient(hwnd syscall.Handle, p *point) {
_ScreenToClient.Call(uintptr(hwnd), uintptr(unsafe.Pointer(p)))
issue34474KeepAlive(p)
}
func showWindow(hwnd syscall.Handle, nCmdShow int32) {
_ShowWindow.Call(uintptr(hwnd), uintptr(nCmdShow))
}
func translateMessage(m *msg) {
_TranslateMessage.Call(uintptr(unsafe.Pointer(m)))
issue34474KeepAlive(m)
}
func unregisterClass(cls uint16, hInst syscall.Handle) {
_UnregisterClass.Call(uintptr(cls), uintptr(hInst))
}
func updateWindow(hwnd syscall.Handle) {
_UpdateWindow.Call(uintptr(hwnd))
}
// issue34474KeepAlive calls runtime.KeepAlive as a
// workaround for golang.org/issue/34474.
func issue34474KeepAlive(v interface{}) {
runtime.KeepAlive(v)
}
+27
View File
@@ -0,0 +1,27 @@
// +build android darwin,ios
package app
// Android only supports non-Java programs as c-shared libraries.
// Unfortunately, Go does not run a program's main function in
// library mode. To make Gio programs simpler and uniform, we'll
// link to the main function here and call it from Java.
import (
"sync"
_ "unsafe" // for go:linkname
)
//go:linkname mainMain main.main
func mainMain()
var runMainOnce sync.Once
func runMain() {
runMainOnce.Do(func() {
// Indirect call, since the linker does not know the address of main when
// laying down this package.
fn := mainMain
go fn()
})
}
+10
View File
@@ -0,0 +1,10 @@
// +build darwin,ios
package app
import "C"
//export gio_runMain
func gio_runMain() {
runMain()
}
+14
View File
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
import (
"os"
"os/signal"
"syscall"
)
func init() {
// Work around golang.org/issue/33384
signal.Notify(make(chan os.Signal), syscall.SIGPIPE)
}
+98
View File
@@ -0,0 +1,98 @@
// +build linux,!android
/* Generated by wayland-scanner 1.16.0 */
/*
* Copyright © 2012, 2013 Intel Corporation
* Copyright © 2015, 2016 Jan Arne Petersen
* Copyright © 2017, 2018 Red Hat, Inc.
* Copyright © 2018 Purism SPC
*
* Permission to use, copy, modify, distribute, and sell this
* software and its documentation for any purpose is hereby granted
* without fee, provided that the above copyright notice appear in
* all copies and that both that copyright notice and this permission
* notice appear in supporting documentation, and that the name of
* the copyright holders not be used in advertising or publicity
* pertaining to distribution of the software without specific,
* written prior permission. The copyright holders make no
* representations about the suitability of this software for any
* purpose. It is provided "as is" without express or implied
* warranty.
*
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
* SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
* SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
* ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
* THIS SOFTWARE.
*/
#include <stdlib.h>
#include <stdint.h>
#include "wayland-util.h"
#ifndef __has_attribute
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
#endif
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
#else
#define WL_PRIVATE
#endif
extern const struct wl_interface wl_seat_interface;
extern const struct wl_interface wl_surface_interface;
extern const struct wl_interface zwp_text_input_v3_interface;
static const struct wl_interface *types[] = {
NULL,
NULL,
NULL,
NULL,
&wl_surface_interface,
&wl_surface_interface,
&zwp_text_input_v3_interface,
&wl_seat_interface,
};
static const struct wl_message zwp_text_input_v3_requests[] = {
{ "destroy", "", types + 0 },
{ "enable", "", types + 0 },
{ "disable", "", types + 0 },
{ "set_surrounding_text", "sii", types + 0 },
{ "set_text_change_cause", "u", types + 0 },
{ "set_content_type", "uu", types + 0 },
{ "set_cursor_rectangle", "iiii", types + 0 },
{ "commit", "", types + 0 },
};
static const struct wl_message zwp_text_input_v3_events[] = {
{ "enter", "o", types + 4 },
{ "leave", "o", types + 5 },
{ "preedit_string", "?sii", types + 0 },
{ "commit_string", "?s", types + 0 },
{ "delete_surrounding_text", "uu", types + 0 },
{ "done", "u", types + 0 },
};
WL_PRIVATE const struct wl_interface zwp_text_input_v3_interface = {
"zwp_text_input_v3", 1,
8, zwp_text_input_v3_requests,
6, zwp_text_input_v3_events,
};
static const struct wl_message zwp_text_input_manager_v3_requests[] = {
{ "destroy", "", types + 0 },
{ "get_text_input", "no", types + 6 },
};
WL_PRIVATE const struct wl_interface zwp_text_input_manager_v3_interface = {
"zwp_text_input_manager_v3", 1,
2, zwp_text_input_manager_v3_requests,
0, NULL,
};
+819
View File
@@ -0,0 +1,819 @@
/* Generated by wayland-scanner 1.16.0 */
#ifndef TEXT_INPUT_UNSTABLE_V3_CLIENT_PROTOCOL_H
#define TEXT_INPUT_UNSTABLE_V3_CLIENT_PROTOCOL_H
#include <stdint.h>
#include <stddef.h>
#include "wayland-client.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @page page_text_input_unstable_v3 The text_input_unstable_v3 protocol
* Protocol for composing text
*
* @section page_desc_text_input_unstable_v3 Description
*
* This protocol allows compositors to act as input methods and to send text
* to applications. A text input object is used to manage state of what are
* typically text entry fields in the application.
*
* This document adheres to the RFC 2119 when using words like "must",
* "should", "may", etc.
*
* Warning! The protocol described in this file is experimental and
* backward incompatible changes may be made. Backward compatible changes
* may be added together with the corresponding interface version bump.
* Backward incompatible changes are done by bumping the version number in
* the protocol and interface names and resetting the interface version.
* Once the protocol is to be declared stable, the 'z' prefix and the
* version number in the protocol and interface names are removed and the
* interface version number is reset.
*
* @section page_ifaces_text_input_unstable_v3 Interfaces
* - @subpage page_iface_zwp_text_input_v3 - text input
* - @subpage page_iface_zwp_text_input_manager_v3 - text input manager
* @section page_copyright_text_input_unstable_v3 Copyright
* <pre>
*
* Copyright © 2012, 2013 Intel Corporation
* Copyright © 2015, 2016 Jan Arne Petersen
* Copyright © 2017, 2018 Red Hat, Inc.
* Copyright © 2018 Purism SPC
*
* Permission to use, copy, modify, distribute, and sell this
* software and its documentation for any purpose is hereby granted
* without fee, provided that the above copyright notice appear in
* all copies and that both that copyright notice and this permission
* notice appear in supporting documentation, and that the name of
* the copyright holders not be used in advertising or publicity
* pertaining to distribution of the software without specific,
* written prior permission. The copyright holders make no
* representations about the suitability of this software for any
* purpose. It is provided "as is" without express or implied
* warranty.
*
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
* SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
* SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
* ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
* THIS SOFTWARE.
* </pre>
*/
struct wl_seat;
struct wl_surface;
struct zwp_text_input_manager_v3;
struct zwp_text_input_v3;
/**
* @page page_iface_zwp_text_input_v3 zwp_text_input_v3
* @section page_iface_zwp_text_input_v3_desc Description
*
* The zwp_text_input_v3 interface represents text input and input methods
* associated with a seat. It provides enter/leave events to follow the
* text input focus for a seat.
*
* Requests are used to enable/disable the text-input object and set
* state information like surrounding and selected text or the content type.
* The information about the entered text is sent to the text-input object
* via the preedit_string and commit_string events.
*
* Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices
* must not point to middle bytes inside a code point: they must either
* point to the first byte of a code point or to the end of the buffer.
* Lengths must be measured between two valid indices.
*
* Focus moving throughout surfaces will result in the emission of
* zwp_text_input_v3.enter and zwp_text_input_v3.leave events. The focused
* surface must commit zwp_text_input_v3.enable and
* zwp_text_input_v3.disable requests as the keyboard focus moves across
* editable and non-editable elements of the UI. Those two requests are not
* expected to be paired with each other, the compositor must be able to
* handle consecutive series of the same request.
*
* State is sent by the state requests (set_surrounding_text,
* set_content_type and set_cursor_rectangle) and a commit request. After an
* enter event or disable request all state information is invalidated and
* needs to be resent by the client.
* @section page_iface_zwp_text_input_v3_api API
* See @ref iface_zwp_text_input_v3.
*/
/**
* @defgroup iface_zwp_text_input_v3 The zwp_text_input_v3 interface
*
* The zwp_text_input_v3 interface represents text input and input methods
* associated with a seat. It provides enter/leave events to follow the
* text input focus for a seat.
*
* Requests are used to enable/disable the text-input object and set
* state information like surrounding and selected text or the content type.
* The information about the entered text is sent to the text-input object
* via the preedit_string and commit_string events.
*
* Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices
* must not point to middle bytes inside a code point: they must either
* point to the first byte of a code point or to the end of the buffer.
* Lengths must be measured between two valid indices.
*
* Focus moving throughout surfaces will result in the emission of
* zwp_text_input_v3.enter and zwp_text_input_v3.leave events. The focused
* surface must commit zwp_text_input_v3.enable and
* zwp_text_input_v3.disable requests as the keyboard focus moves across
* editable and non-editable elements of the UI. Those two requests are not
* expected to be paired with each other, the compositor must be able to
* handle consecutive series of the same request.
*
* State is sent by the state requests (set_surrounding_text,
* set_content_type and set_cursor_rectangle) and a commit request. After an
* enter event or disable request all state information is invalidated and
* needs to be resent by the client.
*/
extern const struct wl_interface zwp_text_input_v3_interface;
/**
* @page page_iface_zwp_text_input_manager_v3 zwp_text_input_manager_v3
* @section page_iface_zwp_text_input_manager_v3_desc Description
*
* A factory for text-input objects. This object is a global singleton.
* @section page_iface_zwp_text_input_manager_v3_api API
* See @ref iface_zwp_text_input_manager_v3.
*/
/**
* @defgroup iface_zwp_text_input_manager_v3 The zwp_text_input_manager_v3 interface
*
* A factory for text-input objects. This object is a global singleton.
*/
extern const struct wl_interface zwp_text_input_manager_v3_interface;
#ifndef ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_ENUM
#define ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_ENUM
/**
* @ingroup iface_zwp_text_input_v3
* text change reason
*
* Reason for the change of surrounding text or cursor posision.
*/
enum zwp_text_input_v3_change_cause {
/**
* input method caused the change
*/
ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_INPUT_METHOD = 0,
/**
* something else than the input method caused the change
*/
ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_OTHER = 1,
};
#endif /* ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_ENUM */
#ifndef ZWP_TEXT_INPUT_V3_CONTENT_HINT_ENUM
#define ZWP_TEXT_INPUT_V3_CONTENT_HINT_ENUM
/**
* @ingroup iface_zwp_text_input_v3
* content hint
*
* Content hint is a bitmask to allow to modify the behavior of the text
* input.
*/
enum zwp_text_input_v3_content_hint {
/**
* no special behavior
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE = 0x0,
/**
* suggest word completions
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_COMPLETION = 0x1,
/**
* suggest word corrections
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_SPELLCHECK = 0x2,
/**
* switch to uppercase letters at the start of a sentence
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_AUTO_CAPITALIZATION = 0x4,
/**
* prefer lowercase letters
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_LOWERCASE = 0x8,
/**
* prefer uppercase letters
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_UPPERCASE = 0x10,
/**
* prefer casing for titles and headings (can be language dependent)
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_TITLECASE = 0x20,
/**
* characters should be hidden
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_HIDDEN_TEXT = 0x40,
/**
* typed text should not be stored
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA = 0x80,
/**
* just Latin characters should be entered
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_LATIN = 0x100,
/**
* the text input is multiline
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_MULTILINE = 0x200,
};
#endif /* ZWP_TEXT_INPUT_V3_CONTENT_HINT_ENUM */
#ifndef ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ENUM
#define ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ENUM
/**
* @ingroup iface_zwp_text_input_v3
* content purpose
*
* The content purpose allows to specify the primary purpose of a text
* input.
*
* This allows an input method to show special purpose input panels with
* extra characters or to disallow some characters.
*/
enum zwp_text_input_v3_content_purpose {
/**
* default input, allowing all characters
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL = 0,
/**
* allow only alphabetic characters
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ALPHA = 1,
/**
* allow only digits
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DIGITS = 2,
/**
* input a number (including decimal separator and sign)
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NUMBER = 3,
/**
* input a phone number
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PHONE = 4,
/**
* input an URL
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_URL = 5,
/**
* input an email address
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_EMAIL = 6,
/**
* input a name of a person
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NAME = 7,
/**
* input a password (combine with sensitive_data hint)
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PASSWORD = 8,
/**
* input is a numeric password (combine with sensitive_data hint)
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PIN = 9,
/**
* input a date
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATE = 10,
/**
* input a time
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TIME = 11,
/**
* input a date and time
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATETIME = 12,
/**
* input for a terminal
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL = 13,
};
#endif /* ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ENUM */
/**
* @ingroup iface_zwp_text_input_v3
* @struct zwp_text_input_v3_listener
*/
struct zwp_text_input_v3_listener {
/**
* enter event
*
* Notification that this seat's text-input focus is on a certain
* surface.
*
* When the seat has the keyboard capability the text-input focus
* follows the keyboard focus. This event sets the current surface
* for the text-input object.
*/
void (*enter)(void *data,
struct zwp_text_input_v3 *zwp_text_input_v3,
struct wl_surface *surface);
/**
* leave event
*
* Notification that this seat's text-input focus is no longer on
* a certain surface. The client should reset any preedit string
* previously set.
*
* The leave notification clears the current surface. It is sent
* before the enter notification for the new focus.
*
* When the seat has the keyboard capability the text-input focus
* follows the keyboard focus.
*/
void (*leave)(void *data,
struct zwp_text_input_v3 *zwp_text_input_v3,
struct wl_surface *surface);
/**
* pre-edit
*
* Notify when a new composing text (pre-edit) should be set at
* the current cursor position. Any previously set composing text
* must be removed. Any previously existing selected text must be
* removed.
*
* The argument text contains the pre-edit string buffer.
*
* The parameters cursor_begin and cursor_end are counted in bytes
* relative to the beginning of the submitted text buffer. Cursor
* should be hidden when both are equal to -1.
*
* They could be represented by the client as a line if both values
* are the same, or as a text highlight otherwise.
*
* Values set with this event are double-buffered. They must be
* applied and reset to initial on the next zwp_text_input_v3.done
* event.
*
* The initial value of text is an empty string, and cursor_begin,
* cursor_end and cursor_hidden are all 0.
*/
void (*preedit_string)(void *data,
struct zwp_text_input_v3 *zwp_text_input_v3,
const char *text,
int32_t cursor_begin,
int32_t cursor_end);
/**
* text commit
*
* Notify when text should be inserted into the editor widget.
* The text to commit could be either just a single character after
* a key press or the result of some composing (pre-edit).
*
* Values set with this event are double-buffered. They must be
* applied and reset to initial on the next zwp_text_input_v3.done
* event.
*
* The initial value of text is an empty string.
*/
void (*commit_string)(void *data,
struct zwp_text_input_v3 *zwp_text_input_v3,
const char *text);
/**
* delete surrounding text
*
* Notify when the text around the current cursor position should
* be deleted.
*
* Before_length and after_length are the number of bytes before
* and after the current cursor index (excluding the selection) to
* delete.
*
* If a preedit text is present, in effect before_length is counted
* from the beginning of it, and after_length from its end (see
* done event sequence).
*
* Values set with this event are double-buffered. They must be
* applied and reset to initial on the next zwp_text_input_v3.done
* event.
*
* The initial values of both before_length and after_length are 0.
* @param before_length length of text before current cursor position
* @param after_length length of text after current cursor position
*/
void (*delete_surrounding_text)(void *data,
struct zwp_text_input_v3 *zwp_text_input_v3,
uint32_t before_length,
uint32_t after_length);
/**
* apply changes
*
* Instruct the application to apply changes to state requested
* by the preedit_string, commit_string and delete_surrounding_text
* events. The state relating to these events is double-buffered,
* and each one modifies the pending state. This event replaces the
* current state with the pending state.
*
* The application must proceed by evaluating the changes in the
* following order:
*
* 1. Replace existing preedit string with the cursor. 2. Delete
* requested surrounding text. 3. Insert commit string with the
* cursor at its end. 4. Calculate surrounding text to send. 5.
* Insert new preedit text in cursor position. 6. Place cursor
* inside preedit text.
*
* The serial number reflects the last state of the
* zwp_text_input_v3 object known to the compositor. The value of
* the serial argument must be equal to the number of commit
* requests already issued on that object. When the client receives
* a done event with a serial different than the number of past
* commit requests, it must proceed as normal, except it should not
* change the current state of the zwp_text_input_v3 object.
*/
void (*done)(void *data,
struct zwp_text_input_v3 *zwp_text_input_v3,
uint32_t serial);
};
/**
* @ingroup iface_zwp_text_input_v3
*/
static inline int
zwp_text_input_v3_add_listener(struct zwp_text_input_v3 *zwp_text_input_v3,
const struct zwp_text_input_v3_listener *listener, void *data)
{
return wl_proxy_add_listener((struct wl_proxy *) zwp_text_input_v3,
(void (**)(void)) listener, data);
}
#define ZWP_TEXT_INPUT_V3_DESTROY 0
#define ZWP_TEXT_INPUT_V3_ENABLE 1
#define ZWP_TEXT_INPUT_V3_DISABLE 2
#define ZWP_TEXT_INPUT_V3_SET_SURROUNDING_TEXT 3
#define ZWP_TEXT_INPUT_V3_SET_TEXT_CHANGE_CAUSE 4
#define ZWP_TEXT_INPUT_V3_SET_CONTENT_TYPE 5
#define ZWP_TEXT_INPUT_V3_SET_CURSOR_RECTANGLE 6
#define ZWP_TEXT_INPUT_V3_COMMIT 7
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_ENTER_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_LEAVE_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_PREEDIT_STRING_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_COMMIT_STRING_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_DELETE_SURROUNDING_TEXT_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_DONE_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_ENABLE_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_DISABLE_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_SET_SURROUNDING_TEXT_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_SET_TEXT_CHANGE_CAUSE_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_SET_CONTENT_TYPE_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_SET_CURSOR_RECTANGLE_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_COMMIT_SINCE_VERSION 1
/** @ingroup iface_zwp_text_input_v3 */
static inline void
zwp_text_input_v3_set_user_data(struct zwp_text_input_v3 *zwp_text_input_v3, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) zwp_text_input_v3, user_data);
}
/** @ingroup iface_zwp_text_input_v3 */
static inline void *
zwp_text_input_v3_get_user_data(struct zwp_text_input_v3 *zwp_text_input_v3)
{
return wl_proxy_get_user_data((struct wl_proxy *) zwp_text_input_v3);
}
static inline uint32_t
zwp_text_input_v3_get_version(struct zwp_text_input_v3 *zwp_text_input_v3)
{
return wl_proxy_get_version((struct wl_proxy *) zwp_text_input_v3);
}
/**
* @ingroup iface_zwp_text_input_v3
*
* Destroy the wp_text_input object. Also disables all surfaces enabled
* through this wp_text_input object.
*/
static inline void
zwp_text_input_v3_destroy(struct zwp_text_input_v3 *zwp_text_input_v3)
{
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
ZWP_TEXT_INPUT_V3_DESTROY);
wl_proxy_destroy((struct wl_proxy *) zwp_text_input_v3);
}
/**
* @ingroup iface_zwp_text_input_v3
*
* Requests text input on the surface previously obtained from the enter
* event.
*
* This request must be issued every time the active text input changes
* to a new one, including within the current surface. Use
* zwp_text_input_v3.disable when there is no longer any input focus on
* the current surface.
*
* This request resets all state associated with previous enable, disable,
* set_surrounding_text, set_text_change_cause, set_content_type, and
* set_cursor_rectangle requests, as well as the state associated with
* preedit_string, commit_string, and delete_surrounding_text events.
*
* The set_surrounding_text, set_content_type and set_cursor_rectangle
* requests must follow if the text input supports the necessary
* functionality.
*
* State set with this request is double-buffered. It will get applied on
* the next zwp_text_input_v3.commit request, and stay valid until the
* next committed enable or disable request.
*
* The changes must be applied by the compositor after issuing a
* zwp_text_input_v3.commit request.
*/
static inline void
zwp_text_input_v3_enable(struct zwp_text_input_v3 *zwp_text_input_v3)
{
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
ZWP_TEXT_INPUT_V3_ENABLE);
}
/**
* @ingroup iface_zwp_text_input_v3
*
* Explicitly disable text input on the current surface (typically when
* there is no focus on any text entry inside the surface).
*
* State set with this request is double-buffered. It will get applied on
* the next zwp_text_input_v3.commit request.
*/
static inline void
zwp_text_input_v3_disable(struct zwp_text_input_v3 *zwp_text_input_v3)
{
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
ZWP_TEXT_INPUT_V3_DISABLE);
}
/**
* @ingroup iface_zwp_text_input_v3
*
* Sets the surrounding plain text around the input, excluding the preedit
* text.
*
* The client should notify the compositor of any changes in any of the
* values carried with this request, including changes caused by handling
* incoming text-input events as well as changes caused by other
* mechanisms like keyboard typing.
*
* If the client is unaware of the text around the cursor, it should not
* issue this request, to signify lack of support to the compositor.
*
* Text is UTF-8 encoded, and should include the cursor position, the
* complete selection and additional characters before and after them.
* There is a maximum length of wayland messages, so text can not be
* longer than 4000 bytes.
*
* Cursor is the byte offset of the cursor within text buffer.
*
* Anchor is the byte offset of the selection anchor within text buffer.
* If there is no selected text, anchor is the same as cursor.
*
* If any preedit text is present, it is replaced with a cursor for the
* purpose of this event.
*
* Values set with this request are double-buffered. They will get applied
* on the next zwp_text_input_v3.commit request, and stay valid until the
* next committed enable or disable request.
*
* The initial state for affected fields is empty, meaning that the text
* input does not support sending surrounding text. If the empty values
* get applied, subsequent attempts to change them may have no effect.
*/
static inline void
zwp_text_input_v3_set_surrounding_text(struct zwp_text_input_v3 *zwp_text_input_v3, const char *text, int32_t cursor, int32_t anchor)
{
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
ZWP_TEXT_INPUT_V3_SET_SURROUNDING_TEXT, text, cursor, anchor);
}
/**
* @ingroup iface_zwp_text_input_v3
*
* Tells the compositor why the text surrounding the cursor changed.
*
* Whenever the client detects an external change in text, cursor, or
* anchor posision, it must issue this request to the compositor. This
* request is intended to give the input method a chance to update the
* preedit text in an appropriate way, e.g. by removing it when the user
* starts typing with a keyboard.
*
* cause describes the source of the change.
*
* The value set with this request is double-buffered. It must be applied
* and reset to initial at the next zwp_text_input_v3.commit request.
*
* The initial value of cause is input_method.
*/
static inline void
zwp_text_input_v3_set_text_change_cause(struct zwp_text_input_v3 *zwp_text_input_v3, uint32_t cause)
{
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
ZWP_TEXT_INPUT_V3_SET_TEXT_CHANGE_CAUSE, cause);
}
/**
* @ingroup iface_zwp_text_input_v3
*
* Sets the content purpose and content hint. While the purpose is the
* basic purpose of an input field, the hint flags allow to modify some of
* the behavior.
*
* Values set with this request are double-buffered. They will get applied
* on the next zwp_text_input_v3.commit request.
* Subsequent attempts to update them may have no effect. The values
* remain valid until the next committed enable or disable request.
*
* The initial value for hint is none, and the initial value for purpose
* is normal.
*/
static inline void
zwp_text_input_v3_set_content_type(struct zwp_text_input_v3 *zwp_text_input_v3, uint32_t hint, uint32_t purpose)
{
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
ZWP_TEXT_INPUT_V3_SET_CONTENT_TYPE, hint, purpose);
}
/**
* @ingroup iface_zwp_text_input_v3
*
* Marks an area around the cursor as a x, y, width, height rectangle in
* surface local coordinates.
*
* Allows the compositor to put a window with word suggestions near the
* cursor, without obstructing the text being input.
*
* If the client is unaware of the position of edited text, it should not
* issue this request, to signify lack of support to the compositor.
*
* Values set with this request are double-buffered. They will get applied
* on the next zwp_text_input_v3.commit request, and stay valid until the
* next committed enable or disable request.
*
* The initial values describing a cursor rectangle are empty. That means
* the text input does not support describing the cursor area. If the
* empty values get applied, subsequent attempts to change them may have
* no effect.
*/
static inline void
zwp_text_input_v3_set_cursor_rectangle(struct zwp_text_input_v3 *zwp_text_input_v3, int32_t x, int32_t y, int32_t width, int32_t height)
{
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
ZWP_TEXT_INPUT_V3_SET_CURSOR_RECTANGLE, x, y, width, height);
}
/**
* @ingroup iface_zwp_text_input_v3
*
* Atomically applies state changes recently sent to the compositor.
*
* The commit request establishes and updates the state of the client, and
* must be issued after any changes to apply them.
*
* Text input state (enabled status, content purpose, content hint,
* surrounding text and change cause, cursor rectangle) is conceptually
* double-buffered within the context of a text input, i.e. between a
* committed enable request and the following committed enable or disable
* request.
*
* Protocol requests modify the pending state, as opposed to the current
* state in use by the input method. A commit request atomically applies
* all pending state, replacing the current state. After commit, the new
* pending state is as documented for each related request.
*
* Requests are applied in the order of arrival.
*
* Neither current nor pending state are modified unless noted otherwise.
*
* The compositor must count the number of commit requests coming from
* each zwp_text_input_v3 object and use the count as the serial in done
* events.
*/
static inline void
zwp_text_input_v3_commit(struct zwp_text_input_v3 *zwp_text_input_v3)
{
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
ZWP_TEXT_INPUT_V3_COMMIT);
}
#define ZWP_TEXT_INPUT_MANAGER_V3_DESTROY 0
#define ZWP_TEXT_INPUT_MANAGER_V3_GET_TEXT_INPUT 1
/**
* @ingroup iface_zwp_text_input_manager_v3
*/
#define ZWP_TEXT_INPUT_MANAGER_V3_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_manager_v3
*/
#define ZWP_TEXT_INPUT_MANAGER_V3_GET_TEXT_INPUT_SINCE_VERSION 1
/** @ingroup iface_zwp_text_input_manager_v3 */
static inline void
zwp_text_input_manager_v3_set_user_data(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) zwp_text_input_manager_v3, user_data);
}
/** @ingroup iface_zwp_text_input_manager_v3 */
static inline void *
zwp_text_input_manager_v3_get_user_data(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3)
{
return wl_proxy_get_user_data((struct wl_proxy *) zwp_text_input_manager_v3);
}
static inline uint32_t
zwp_text_input_manager_v3_get_version(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3)
{
return wl_proxy_get_version((struct wl_proxy *) zwp_text_input_manager_v3);
}
/**
* @ingroup iface_zwp_text_input_manager_v3
*
* Destroy the wp_text_input_manager object.
*/
static inline void
zwp_text_input_manager_v3_destroy(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3)
{
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_manager_v3,
ZWP_TEXT_INPUT_MANAGER_V3_DESTROY);
wl_proxy_destroy((struct wl_proxy *) zwp_text_input_manager_v3);
}
/**
* @ingroup iface_zwp_text_input_manager_v3
*
* Creates a new text-input object for a given seat.
*/
static inline struct zwp_text_input_v3 *
zwp_text_input_manager_v3_get_text_input(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3, struct wl_seat *seat)
{
struct wl_proxy *id;
id = wl_proxy_marshal_constructor((struct wl_proxy *) zwp_text_input_manager_v3,
ZWP_TEXT_INPUT_MANAGER_V3_GET_TEXT_INPUT, &zwp_text_input_v3_interface, NULL, seat);
return (struct zwp_text_input_v3 *) id;
}
#ifdef __cplusplus
}
#endif
#endif
+77
View File
@@ -0,0 +1,77 @@
// +build linux,!android
/* Generated by wayland-scanner 1.16.0 */
/*
* Copyright © 2018 Simon Ser
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <stdlib.h>
#include <stdint.h>
#include "wayland-util.h"
#ifndef __has_attribute
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
#endif
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
#else
#define WL_PRIVATE
#endif
extern const struct wl_interface xdg_toplevel_interface;
extern const struct wl_interface zxdg_toplevel_decoration_v1_interface;
static const struct wl_interface *types[] = {
NULL,
&zxdg_toplevel_decoration_v1_interface,
&xdg_toplevel_interface,
};
static const struct wl_message zxdg_decoration_manager_v1_requests[] = {
{ "destroy", "", types + 0 },
{ "get_toplevel_decoration", "no", types + 1 },
};
WL_PRIVATE const struct wl_interface zxdg_decoration_manager_v1_interface = {
"zxdg_decoration_manager_v1", 1,
2, zxdg_decoration_manager_v1_requests,
0, NULL,
};
static const struct wl_message zxdg_toplevel_decoration_v1_requests[] = {
{ "destroy", "", types + 0 },
{ "set_mode", "u", types + 0 },
{ "unset_mode", "", types + 0 },
};
static const struct wl_message zxdg_toplevel_decoration_v1_events[] = {
{ "configure", "u", types + 0 },
};
WL_PRIVATE const struct wl_interface zxdg_toplevel_decoration_v1_interface = {
"zxdg_toplevel_decoration_v1", 1,
3, zxdg_toplevel_decoration_v1_requests,
1, zxdg_toplevel_decoration_v1_events,
};
+376
View File
@@ -0,0 +1,376 @@
/* Generated by wayland-scanner 1.16.0 */
#ifndef XDG_DECORATION_UNSTABLE_V1_CLIENT_PROTOCOL_H
#define XDG_DECORATION_UNSTABLE_V1_CLIENT_PROTOCOL_H
#include <stdint.h>
#include <stddef.h>
#include "wayland-client.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @page page_xdg_decoration_unstable_v1 The xdg_decoration_unstable_v1 protocol
* @section page_ifaces_xdg_decoration_unstable_v1 Interfaces
* - @subpage page_iface_zxdg_decoration_manager_v1 - window decoration manager
* - @subpage page_iface_zxdg_toplevel_decoration_v1 - decoration object for a toplevel surface
* @section page_copyright_xdg_decoration_unstable_v1 Copyright
* <pre>
*
* Copyright © 2018 Simon Ser
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
* </pre>
*/
struct xdg_toplevel;
struct zxdg_decoration_manager_v1;
struct zxdg_toplevel_decoration_v1;
/**
* @page page_iface_zxdg_decoration_manager_v1 zxdg_decoration_manager_v1
* @section page_iface_zxdg_decoration_manager_v1_desc Description
*
* This interface allows a compositor to announce support for server-side
* decorations.
*
* A window decoration is a set of window controls as deemed appropriate by
* the party managing them, such as user interface components used to move,
* resize and change a window's state.
*
* A client can use this protocol to request being decorated by a supporting
* compositor.
*
* If compositor and client do not negotiate the use of a server-side
* decoration using this protocol, clients continue to self-decorate as they
* see fit.
*
* Warning! The protocol described in this file is experimental and
* backward incompatible changes may be made. Backward compatible changes
* may be added together with the corresponding interface version bump.
* Backward incompatible changes are done by bumping the version number in
* the protocol and interface names and resetting the interface version.
* Once the protocol is to be declared stable, the 'z' prefix and the
* version number in the protocol and interface names are removed and the
* interface version number is reset.
* @section page_iface_zxdg_decoration_manager_v1_api API
* See @ref iface_zxdg_decoration_manager_v1.
*/
/**
* @defgroup iface_zxdg_decoration_manager_v1 The zxdg_decoration_manager_v1 interface
*
* This interface allows a compositor to announce support for server-side
* decorations.
*
* A window decoration is a set of window controls as deemed appropriate by
* the party managing them, such as user interface components used to move,
* resize and change a window's state.
*
* A client can use this protocol to request being decorated by a supporting
* compositor.
*
* If compositor and client do not negotiate the use of a server-side
* decoration using this protocol, clients continue to self-decorate as they
* see fit.
*
* Warning! The protocol described in this file is experimental and
* backward incompatible changes may be made. Backward compatible changes
* may be added together with the corresponding interface version bump.
* Backward incompatible changes are done by bumping the version number in
* the protocol and interface names and resetting the interface version.
* Once the protocol is to be declared stable, the 'z' prefix and the
* version number in the protocol and interface names are removed and the
* interface version number is reset.
*/
extern const struct wl_interface zxdg_decoration_manager_v1_interface;
/**
* @page page_iface_zxdg_toplevel_decoration_v1 zxdg_toplevel_decoration_v1
* @section page_iface_zxdg_toplevel_decoration_v1_desc Description
*
* The decoration object allows the compositor to toggle server-side window
* decorations for a toplevel surface. The client can request to switch to
* another mode.
*
* The xdg_toplevel_decoration object must be destroyed before its
* xdg_toplevel.
* @section page_iface_zxdg_toplevel_decoration_v1_api API
* See @ref iface_zxdg_toplevel_decoration_v1.
*/
/**
* @defgroup iface_zxdg_toplevel_decoration_v1 The zxdg_toplevel_decoration_v1 interface
*
* The decoration object allows the compositor to toggle server-side window
* decorations for a toplevel surface. The client can request to switch to
* another mode.
*
* The xdg_toplevel_decoration object must be destroyed before its
* xdg_toplevel.
*/
extern const struct wl_interface zxdg_toplevel_decoration_v1_interface;
#define ZXDG_DECORATION_MANAGER_V1_DESTROY 0
#define ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION 1
/**
* @ingroup iface_zxdg_decoration_manager_v1
*/
#define ZXDG_DECORATION_MANAGER_V1_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_zxdg_decoration_manager_v1
*/
#define ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION_SINCE_VERSION 1
/** @ingroup iface_zxdg_decoration_manager_v1 */
static inline void
zxdg_decoration_manager_v1_set_user_data(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) zxdg_decoration_manager_v1, user_data);
}
/** @ingroup iface_zxdg_decoration_manager_v1 */
static inline void *
zxdg_decoration_manager_v1_get_user_data(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1)
{
return wl_proxy_get_user_data((struct wl_proxy *) zxdg_decoration_manager_v1);
}
static inline uint32_t
zxdg_decoration_manager_v1_get_version(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1)
{
return wl_proxy_get_version((struct wl_proxy *) zxdg_decoration_manager_v1);
}
/**
* @ingroup iface_zxdg_decoration_manager_v1
*
* Destroy the decoration manager. This doesn't destroy objects created
* with the manager.
*/
static inline void
zxdg_decoration_manager_v1_destroy(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1)
{
wl_proxy_marshal((struct wl_proxy *) zxdg_decoration_manager_v1,
ZXDG_DECORATION_MANAGER_V1_DESTROY);
wl_proxy_destroy((struct wl_proxy *) zxdg_decoration_manager_v1);
}
/**
* @ingroup iface_zxdg_decoration_manager_v1
*
* Create a new decoration object associated with the given toplevel.
*
* Creating an xdg_toplevel_decoration from an xdg_toplevel which has a
* buffer attached or committed is a client error, and any attempts by a
* client to attach or manipulate a buffer prior to the first
* xdg_toplevel_decoration.configure event must also be treated as
* errors.
*/
static inline struct zxdg_toplevel_decoration_v1 *
zxdg_decoration_manager_v1_get_toplevel_decoration(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1, struct xdg_toplevel *toplevel)
{
struct wl_proxy *id;
id = wl_proxy_marshal_constructor((struct wl_proxy *) zxdg_decoration_manager_v1,
ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION, &zxdg_toplevel_decoration_v1_interface, NULL, toplevel);
return (struct zxdg_toplevel_decoration_v1 *) id;
}
#ifndef ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM
#define ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM
enum zxdg_toplevel_decoration_v1_error {
/**
* xdg_toplevel has a buffer attached before configure
*/
ZXDG_TOPLEVEL_DECORATION_V1_ERROR_UNCONFIGURED_BUFFER = 0,
/**
* xdg_toplevel already has a decoration object
*/
ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ALREADY_CONSTRUCTED = 1,
/**
* xdg_toplevel destroyed before the decoration object
*/
ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ORPHANED = 2,
};
#endif /* ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM */
#ifndef ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM
#define ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
* window decoration modes
*
* These values describe window decoration modes.
*/
enum zxdg_toplevel_decoration_v1_mode {
/**
* no server-side window decoration
*/
ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE = 1,
/**
* server-side window decoration
*/
ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE = 2,
};
#endif /* ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM */
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
* @struct zxdg_toplevel_decoration_v1_listener
*/
struct zxdg_toplevel_decoration_v1_listener {
/**
* suggest a surface change
*
* The configure event asks the client to change its decoration
* mode. The configured state should not be applied immediately.
* Clients must send an ack_configure in response to this event.
* See xdg_surface.configure and xdg_surface.ack_configure for
* details.
*
* A configure event can be sent at any time. The specified mode
* must be obeyed by the client.
* @param mode the decoration mode
*/
void (*configure)(void *data,
struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1,
uint32_t mode);
};
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
*/
static inline int
zxdg_toplevel_decoration_v1_add_listener(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1,
const struct zxdg_toplevel_decoration_v1_listener *listener, void *data)
{
return wl_proxy_add_listener((struct wl_proxy *) zxdg_toplevel_decoration_v1,
(void (**)(void)) listener, data);
}
#define ZXDG_TOPLEVEL_DECORATION_V1_DESTROY 0
#define ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE 1
#define ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE 2
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
*/
#define ZXDG_TOPLEVEL_DECORATION_V1_CONFIGURE_SINCE_VERSION 1
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
*/
#define ZXDG_TOPLEVEL_DECORATION_V1_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
*/
#define ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE_SINCE_VERSION 1
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
*/
#define ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE_SINCE_VERSION 1
/** @ingroup iface_zxdg_toplevel_decoration_v1 */
static inline void
zxdg_toplevel_decoration_v1_set_user_data(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) zxdg_toplevel_decoration_v1, user_data);
}
/** @ingroup iface_zxdg_toplevel_decoration_v1 */
static inline void *
zxdg_toplevel_decoration_v1_get_user_data(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
{
return wl_proxy_get_user_data((struct wl_proxy *) zxdg_toplevel_decoration_v1);
}
static inline uint32_t
zxdg_toplevel_decoration_v1_get_version(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
{
return wl_proxy_get_version((struct wl_proxy *) zxdg_toplevel_decoration_v1);
}
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
*
* Switch back to a mode without any server-side decorations at the next
* commit.
*/
static inline void
zxdg_toplevel_decoration_v1_destroy(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
{
wl_proxy_marshal((struct wl_proxy *) zxdg_toplevel_decoration_v1,
ZXDG_TOPLEVEL_DECORATION_V1_DESTROY);
wl_proxy_destroy((struct wl_proxy *) zxdg_toplevel_decoration_v1);
}
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
*
* Set the toplevel surface decoration mode. This informs the compositor
* that the client prefers the provided decoration mode.
*
* After requesting a decoration mode, the compositor will respond by
* emitting a xdg_surface.configure event. The client should then update
* its content, drawing it without decorations if the received mode is
* server-side decorations. The client must also acknowledge the configure
* when committing the new content (see xdg_surface.ack_configure).
*
* The compositor can decide not to use the client's mode and enforce a
* different mode instead.
*
* Clients whose decoration mode depend on the xdg_toplevel state may send
* a set_mode request in response to a xdg_surface.configure event and wait
* for the next xdg_surface.configure event to prevent unwanted state.
* Such clients are responsible for preventing configure loops and must
* make sure not to send multiple successive set_mode requests with the
* same decoration mode.
*/
static inline void
zxdg_toplevel_decoration_v1_set_mode(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, uint32_t mode)
{
wl_proxy_marshal((struct wl_proxy *) zxdg_toplevel_decoration_v1,
ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE, mode);
}
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
*
* Unset the toplevel surface decoration mode. This informs the compositor
* that the client doesn't prefer a particular decoration mode.
*
* This request has the same semantics as set_mode.
*/
static inline void
zxdg_toplevel_decoration_v1_unset_mode(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
{
wl_proxy_marshal((struct wl_proxy *) zxdg_toplevel_decoration_v1,
ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE);
}
#ifdef __cplusplus
}
#endif
#endif
+176
View File
@@ -0,0 +1,176 @@
// +build linux,!android
/* Generated by wayland-scanner 1.16.0 */
/*
* Copyright © 2008-2013 Kristian Høgsberg
* Copyright © 2013 Rafael Antognolli
* Copyright © 2013 Jasper St. Pierre
* Copyright © 2010-2013 Intel Corporation
* Copyright © 2015-2017 Samsung Electronics Co., Ltd
* Copyright © 2015-2017 Red Hat Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <stdlib.h>
#include <stdint.h>
#include "wayland-util.h"
#ifndef __has_attribute
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
#endif
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
#else
#define WL_PRIVATE
#endif
extern const struct wl_interface wl_output_interface;
extern const struct wl_interface wl_seat_interface;
extern const struct wl_interface wl_surface_interface;
extern const struct wl_interface xdg_popup_interface;
extern const struct wl_interface xdg_positioner_interface;
extern const struct wl_interface xdg_surface_interface;
extern const struct wl_interface xdg_toplevel_interface;
static const struct wl_interface *types[] = {
NULL,
NULL,
NULL,
NULL,
&xdg_positioner_interface,
&xdg_surface_interface,
&wl_surface_interface,
&xdg_toplevel_interface,
&xdg_popup_interface,
&xdg_surface_interface,
&xdg_positioner_interface,
&xdg_toplevel_interface,
&wl_seat_interface,
NULL,
NULL,
NULL,
&wl_seat_interface,
NULL,
&wl_seat_interface,
NULL,
NULL,
&wl_output_interface,
&wl_seat_interface,
NULL,
};
static const struct wl_message xdg_wm_base_requests[] = {
{ "destroy", "", types + 0 },
{ "create_positioner", "n", types + 4 },
{ "get_xdg_surface", "no", types + 5 },
{ "pong", "u", types + 0 },
};
static const struct wl_message xdg_wm_base_events[] = {
{ "ping", "u", types + 0 },
};
WL_PRIVATE const struct wl_interface xdg_wm_base_interface = {
"xdg_wm_base", 2,
4, xdg_wm_base_requests,
1, xdg_wm_base_events,
};
static const struct wl_message xdg_positioner_requests[] = {
{ "destroy", "", types + 0 },
{ "set_size", "ii", types + 0 },
{ "set_anchor_rect", "iiii", types + 0 },
{ "set_anchor", "u", types + 0 },
{ "set_gravity", "u", types + 0 },
{ "set_constraint_adjustment", "u", types + 0 },
{ "set_offset", "ii", types + 0 },
};
WL_PRIVATE const struct wl_interface xdg_positioner_interface = {
"xdg_positioner", 2,
7, xdg_positioner_requests,
0, NULL,
};
static const struct wl_message xdg_surface_requests[] = {
{ "destroy", "", types + 0 },
{ "get_toplevel", "n", types + 7 },
{ "get_popup", "n?oo", types + 8 },
{ "set_window_geometry", "iiii", types + 0 },
{ "ack_configure", "u", types + 0 },
};
static const struct wl_message xdg_surface_events[] = {
{ "configure", "u", types + 0 },
};
WL_PRIVATE const struct wl_interface xdg_surface_interface = {
"xdg_surface", 2,
5, xdg_surface_requests,
1, xdg_surface_events,
};
static const struct wl_message xdg_toplevel_requests[] = {
{ "destroy", "", types + 0 },
{ "set_parent", "?o", types + 11 },
{ "set_title", "s", types + 0 },
{ "set_app_id", "s", types + 0 },
{ "show_window_menu", "ouii", types + 12 },
{ "move", "ou", types + 16 },
{ "resize", "ouu", types + 18 },
{ "set_max_size", "ii", types + 0 },
{ "set_min_size", "ii", types + 0 },
{ "set_maximized", "", types + 0 },
{ "unset_maximized", "", types + 0 },
{ "set_fullscreen", "?o", types + 21 },
{ "unset_fullscreen", "", types + 0 },
{ "set_minimized", "", types + 0 },
};
static const struct wl_message xdg_toplevel_events[] = {
{ "configure", "iia", types + 0 },
{ "close", "", types + 0 },
};
WL_PRIVATE const struct wl_interface xdg_toplevel_interface = {
"xdg_toplevel", 2,
14, xdg_toplevel_requests,
2, xdg_toplevel_events,
};
static const struct wl_message xdg_popup_requests[] = {
{ "destroy", "", types + 0 },
{ "grab", "ou", types + 22 },
};
static const struct wl_message xdg_popup_events[] = {
{ "configure", "iiii", types + 0 },
{ "popup_done", "", types + 0 },
};
WL_PRIVATE const struct wl_interface xdg_popup_interface = {
"xdg_popup", 2,
2, xdg_popup_requests,
2, xdg_popup_events,
};
File diff suppressed because it is too large Load Diff
+363
View File
@@ -0,0 +1,363 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
import (
"errors"
"fmt"
"image"
"time"
"gioui.org/ui"
"gioui.org/app/internal/gpu"
"gioui.org/app/internal/input"
"gioui.org/system"
)
// WindowOption configures a Window.
type WindowOption struct {
apply func(opts *windowOptions)
}
type windowOptions struct {
Width, Height ui.Value
Title string
}
// Window represents an operating system window.
type Window struct {
driver *window
lastFrame time.Time
drawStart time.Time
gpu *gpu.GPU
out chan ui.Event
in chan ui.Event
ack chan struct{}
invalidates chan struct{}
frames chan *ui.Ops
stage Stage
animating bool
hasNextFrame bool
nextFrame time.Time
delayedDraw *time.Timer
queue Queue
}
// Queue is an ui.Queue implementation that distributes system events
// to the input handlers declared in the most recent call to Update.
type Queue struct {
q input.Router
}
// driverEvent is sent when a new native driver
// is available for the Window.
type driverEvent struct {
driver *window
}
// driver is the interface for the platform implementation
// of a Window.
var _ interface {
// setAnimating sets the animation flag. When the window is animating,
// UpdateEvents are delivered as fast as the display can handle them.
setAnimating(anim bool)
// showTextInput updates the virtual keyboard state.
showTextInput(show bool)
} = (*window)(nil)
// Pre-allocate the ack event to avoid garbage.
var ackEvent ui.Event
// NewWindow creates a new window for a set of window
// options. The options are hints; the platform is free to
// ignore or adjust them.
//
// If opts are nil, a set of sensible defaults are used.
//
// If the current program is running on iOS and Android,
// NewWindow returns the window previously created by the
// platform.
//
// BUG: Calling NewWindow more than once is not yet supported.
func NewWindow(options ...WindowOption) *Window {
opts := &windowOptions{
Width: ui.Dp(800),
Height: ui.Dp(600),
Title: "Gio",
}
for _, o := range options {
o.apply(opts)
}
w := &Window{
in: make(chan ui.Event),
out: make(chan ui.Event),
ack: make(chan struct{}),
invalidates: make(chan struct{}, 1),
frames: make(chan *ui.Ops),
}
go w.run(opts)
return w
}
// Events returns the channel where events are delivered.
func (w *Window) Events() <-chan ui.Event {
return w.out
}
// Queue returns the Window's event queue. The queue contains
// the events received since the last UpdateEvent.
func (w *Window) Queue() *Queue {
return &w.queue
}
// Update updates the Window. Paint operations updates the
// window contents, input operations declare input handlers,
// and so on. The supplied operations list completely replaces
// the window state from previous calls.
func (w *Window) Update(frame *ui.Ops) {
w.frames <- frame
}
func (w *Window) draw(size image.Point, frame *ui.Ops) {
var drawDur time.Duration
if !w.drawStart.IsZero() {
drawDur = time.Since(w.drawStart)
w.drawStart = time.Time{}
}
w.gpu.Draw(w.queue.q.Profiling(), size, frame)
w.queue.q.Frame(frame)
now := time.Now()
switch w.queue.q.TextInputState() {
case input.TextInputOpen:
w.driver.showTextInput(true)
case input.TextInputClose:
w.driver.showTextInput(false)
}
frameDur := now.Sub(w.lastFrame)
frameDur = frameDur.Truncate(100 * time.Microsecond)
w.lastFrame = now
if w.queue.q.Profiling() {
q := 100 * time.Microsecond
timings := fmt.Sprintf("tot:%7s cpu:%7s %s", frameDur.Round(q), drawDur.Round(q), w.gpu.Timings())
w.queue.q.AddProfile(system.ProfileEvent{Timings: timings})
w.setNextFrame(time.Time{})
}
if t, ok := w.queue.q.WakeupTime(); ok {
w.setNextFrame(t)
}
w.updateAnimation()
}
// Invalidate the window such that a UpdateEvent will be generated
// immediately. If the window is inactive, the event is sent when the
// window becomes active.
// Invalidate is safe for concurrent use.
func (w *Window) Invalidate() {
select {
case w.invalidates <- struct{}{}:
default:
}
}
func (w *Window) updateAnimation() {
animate := false
if w.delayedDraw != nil {
w.delayedDraw.Stop()
w.delayedDraw = nil
}
if w.stage >= StageRunning && w.hasNextFrame {
if dt := time.Until(w.nextFrame); dt <= 0 {
animate = true
} else {
w.delayedDraw = time.NewTimer(dt)
}
}
if animate != w.animating {
w.animating = animate
w.driver.setAnimating(animate)
}
}
func (w *Window) setNextFrame(at time.Time) {
if !w.hasNextFrame || at.Before(w.nextFrame) {
w.hasNextFrame = true
w.nextFrame = at
}
}
func (w *Window) setDriver(d *window) {
w.event(driverEvent{d})
}
func (w *Window) event(e ui.Event) {
w.in <- e
<-w.ack
}
func (w *Window) waitAck() {
// Send a dummy event; when it gets through we
// know the application has processed the previous event.
w.out <- ackEvent
}
// Prematurely destroy the window and wait for the native window
// destroy event.
func (w *Window) destroy(err error) {
// Ack the current event.
w.ack <- struct{}{}
w.out <- DestroyEvent{err}
for e := range w.in {
w.ack <- struct{}{}
if _, ok := e.(DestroyEvent); ok {
return
}
}
}
func (w *Window) run(opts *windowOptions) {
defer close(w.in)
defer close(w.out)
if err := createWindow(w, opts); err != nil {
w.out <- DestroyEvent{err}
return
}
for {
var timer <-chan time.Time
if w.delayedDraw != nil {
timer = w.delayedDraw.C
}
select {
case <-timer:
w.setNextFrame(time.Time{})
w.updateAnimation()
case <-w.invalidates:
w.setNextFrame(time.Time{})
w.updateAnimation()
case e := <-w.in:
switch e2 := e.(type) {
case StageEvent:
if w.gpu != nil {
if e2.Stage < StageRunning {
w.gpu.Release()
w.gpu = nil
} else {
w.gpu.Refresh()
}
}
w.stage = e2.Stage
w.updateAnimation()
w.out <- e
w.waitAck()
case UpdateEvent:
if e2.Size == (image.Point{}) {
panic(errors.New("internal error: zero-sized Draw"))
}
if w.stage < StageRunning {
// No drawing if not visible.
break
}
w.drawStart = time.Now()
w.hasNextFrame = false
w.out <- e
var frame *ui.Ops
// Wait for either a frame or the ack event,
// which meant that the client didn't draw.
select {
case frame = <-w.frames:
case w.out <- ackEvent:
}
if w.gpu != nil {
if e2.sync {
w.gpu.Refresh()
}
if err := w.gpu.Flush(); err != nil {
w.gpu.Release()
w.gpu = nil
w.destroy(err)
return
}
} else {
ctx, err := newContext(w.driver)
if err != nil {
w.destroy(err)
return
}
w.gpu, err = gpu.NewGPU(ctx)
if err != nil {
w.destroy(err)
return
}
}
w.draw(e2.Size, frame)
if e2.sync {
if err := w.gpu.Flush(); err != nil {
w.gpu.Release()
w.gpu = nil
w.destroy(err)
return
}
}
case *CommandEvent:
w.out <- e
w.waitAck()
case driverEvent:
w.driver = e2.driver
case DestroyEvent:
w.out <- e2
w.ack <- struct{}{}
return
case ui.Event:
if w.queue.q.Add(e2) {
w.setNextFrame(time.Time{})
w.updateAnimation()
}
w.out <- e
}
w.ack <- struct{}{}
}
}
}
func (q *Queue) Events(k ui.Key) []ui.Event {
return q.q.Events(k)
}
// WithTitle returns an option that sets the window title.
func WithTitle(t string) WindowOption {
return WindowOption{
apply: func(opts *windowOptions) {
opts.Title = t
},
}
}
// WithWidth returns an option that sets the window width.
func WithWidth(w ui.Value) WindowOption {
if w.V <= 0 {
panic("width must be larger than or equal to 0")
}
return WindowOption{
apply: func(opts *windowOptions) {
opts.Width = w
},
}
}
// WithHeight returns an option that sets the window height.
func WithHeight(h ui.Value) WindowOption {
if h.V <= 0 {
panic("height must be larger than or equal to 0")
}
return WindowOption{
apply: func(opts *windowOptions) {
opts.Height = h
},
}
}
func (driverEvent) ImplementsEvent() {}
+219
View File
@@ -0,0 +1,219 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build !android
package app
/*
#cgo LDFLAGS: -lxkbcommon
#include <stdlib.h>
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-compose.h>
*/
import "C"
import (
"errors"
"fmt"
"os"
"syscall"
"unicode"
"unicode/utf8"
"unsafe"
"gioui.org/key"
)
type xkb struct {
ctx *C.struct_xkb_context
keyMap *C.struct_xkb_keymap
state *C.struct_xkb_state
compTable *C.struct_xkb_compose_table
compState *C.struct_xkb_compose_state
utf8Buf []byte
}
var (
_XKB_MOD_NAME_CTRL = []byte("Control\x00")
_XKB_MOD_NAME_SHIFT = []byte("Shift\x00")
)
func (x *xkb) Destroy() {
if x.state != nil {
C.xkb_compose_state_unref(x.compState)
x.compState = nil
}
if x.compTable != nil {
C.xkb_compose_table_unref(x.compTable)
x.compTable = nil
}
if x.state != nil {
C.xkb_state_unref(x.state)
x.state = nil
}
if x.keyMap != nil {
C.xkb_keymap_unref(x.keyMap)
x.keyMap = nil
}
if x.ctx != nil {
C.xkb_context_unref(x.ctx)
x.ctx = nil
}
}
func newXKB(format C.uint32_t, fd C.int32_t, size C.uint32_t) (*xkb, error) {
xkb := &xkb{
ctx: C.xkb_context_new(C.XKB_CONTEXT_NO_FLAGS),
}
if xkb.ctx == nil {
return nil, errors.New("newXKB: xkb_context_new failed")
}
locale := os.Getenv("LC_ALL")
if locale == "" {
locale = os.Getenv("LC_CTYPE")
}
if locale == "" {
locale = os.Getenv("LANG")
}
if locale == "" {
locale = "C"
}
cloc := C.CString(locale)
defer C.free(unsafe.Pointer(cloc))
xkb.compTable = C.xkb_compose_table_new_from_locale(xkb.ctx, cloc, C.XKB_COMPOSE_COMPILE_NO_FLAGS)
if xkb.compTable == nil {
xkb.Destroy()
return nil, errors.New("newXKB: xkb_compose_table_new_from_locale failed")
}
xkb.compState = C.xkb_compose_state_new(xkb.compTable, C.XKB_COMPOSE_STATE_NO_FLAGS)
if xkb.compState == nil {
xkb.Destroy()
return nil, errors.New("newXKB: xkb_compose_state_new failed")
}
mapData, err := syscall.Mmap(int(fd), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED)
if err != nil {
xkb.Destroy()
return nil, fmt.Errorf("newXKB: mmap of keymap failed: %v", err)
}
defer syscall.Munmap(mapData)
xkb.keyMap = C.xkb_keymap_new_from_buffer(xkb.ctx, (*C.char)(unsafe.Pointer(&mapData[0])), C.size_t(size-1), C.XKB_KEYMAP_FORMAT_TEXT_V1, C.XKB_KEYMAP_COMPILE_NO_FLAGS)
if xkb.keyMap == nil {
xkb.Destroy()
return nil, errors.New("newXKB: xkb_keymap_new_from_buffer failed")
}
xkb.state = C.xkb_state_new(xkb.keyMap)
if xkb.state == nil {
xkb.Destroy()
return nil, errors.New("newXKB: xkb_state_new failed")
}
return xkb, nil
}
func (x *xkb) dispatchKey(w *Window, keyCode C.uint32_t) {
keyCode = mapXKBKeyCode(keyCode)
if len(x.utf8Buf) == 0 {
x.utf8Buf = make([]byte, 1)
}
sym := C.xkb_state_key_get_one_sym(x.state, C.xkb_keycode_t(keyCode))
if n, ok := convertKeysym(sym); ok {
cmd := key.Event{Name: n}
if C.xkb_state_mod_name_is_active(x.state, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_CTRL[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 {
cmd.Modifiers |= key.ModCommand
}
if C.xkb_state_mod_name_is_active(x.state, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_SHIFT[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 {
cmd.Modifiers |= key.ModShift
}
w.event(cmd)
}
C.xkb_compose_state_feed(x.compState, sym)
var size C.int
switch C.xkb_compose_state_get_status(x.compState) {
case C.XKB_COMPOSE_CANCELLED, C.XKB_COMPOSE_COMPOSING:
return
case C.XKB_COMPOSE_COMPOSED:
size = C.xkb_compose_state_get_utf8(x.compState, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
if int(size) >= len(x.utf8Buf) {
x.utf8Buf = make([]byte, size+1)
size = C.xkb_compose_state_get_utf8(x.compState, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
}
C.xkb_compose_state_reset(x.compState)
case C.XKB_COMPOSE_NOTHING:
size = C.xkb_state_key_get_utf8(x.state, C.xkb_keycode_t(keyCode), (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
if int(size) >= len(x.utf8Buf) {
x.utf8Buf = make([]byte, size+1)
size = C.xkb_state_key_get_utf8(x.state, C.xkb_keycode_t(keyCode), (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
}
}
// Report only printable runes.
str := x.utf8Buf[:size]
var n int
for n < len(str) {
r, s := utf8.DecodeRune(str)
if unicode.IsPrint(r) {
n += s
} else {
copy(str[n:], str[n+s:])
str = str[:len(str)-s]
}
}
if len(str) > 0 {
w.event(key.EditEvent{Text: string(str)})
}
}
func (x *xkb) isRepeatKey(keyCode C.uint32_t) bool {
keyCode = mapXKBKeyCode(keyCode)
return C.xkb_keymap_key_repeats(conn.xkb.keyMap, C.xkb_keycode_t(keyCode)) == 1
}
func (x *xkb) updateMask(depressed, latched, locked, group C.uint32_t) {
xkbGrp := C.xkb_layout_index_t(group)
C.xkb_state_update_mask(conn.xkb.state, C.xkb_mod_mask_t(depressed), C.xkb_mod_mask_t(latched), C.xkb_mod_mask_t(locked), xkbGrp, xkbGrp, xkbGrp)
}
func mapXKBKeyCode(keyCode C.uint32_t) C.uint32_t {
// According to the xkb_v1 spec: "to determine the xkb keycode, clients must add 8 to the key event keycode."
return keyCode + 8
}
func convertKeysym(s C.xkb_keysym_t) (rune, bool) {
if '0' <= s && s <= '9' || 'A' <= s && s <= 'Z' {
return rune(s), true
}
if 'a' <= s && s <= 'z' {
return rune(s - 0x20), true
}
var n rune
switch s {
case C.XKB_KEY_Escape:
n = key.NameEscape
case C.XKB_KEY_Left:
n = key.NameLeftArrow
case C.XKB_KEY_Right:
n = key.NameRightArrow
case C.XKB_KEY_Return:
n = key.NameReturn
case C.XKB_KEY_KP_Enter:
n = key.NameEnter
case C.XKB_KEY_Up:
n = key.NameUpArrow
case C.XKB_KEY_Down:
n = key.NameDownArrow
case C.XKB_KEY_Home:
n = key.NameHome
case C.XKB_KEY_End:
n = key.NameEnd
case C.XKB_KEY_BackSpace:
n = key.NameDeleteBackward
case C.XKB_KEY_Delete:
n = key.NameDeleteForward
case C.XKB_KEY_Page_Up:
n = key.NamePageUp
case C.XKB_KEY_Page_Down:
n = key.NamePageDown
default:
return 0, false
}
return n, true
}