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
@@ -1,58 +0,0 @@
// 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
@@ -1,238 +0,0 @@
// 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
@@ -1,206 +0,0 @@
// 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
@@ -1,11 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build !android,go1.13
package app
import "os"
func dataDir() (string, error) {
return os.UserConfigDir()
}
-25
View File
@@ -1,25 +0,0 @@
// 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
@@ -1,12 +0,0 @@
// 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
@@ -1,65 +0,0 @@
// 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/ui/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
@@ -1,281 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build linux windows
package app
import (
"errors"
"fmt"
"runtime"
"strings"
"gioui.org/ui/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
@@ -1,22 +0,0 @@
// 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
@@ -1,83 +0,0 @@
// 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
@@ -1,59 +0,0 @@
// 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
@@ -1,20 +0,0 @@
// 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
@@ -1,144 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
import (
"os"
"unsafe"
syscall "golang.org/x/sys/windows"
"gioui.org/ui/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
@@ -1,8 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
@import UIKit;
@interface GioAppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
-124
View File
@@ -1,124 +0,0 @@
// 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/ui/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
@@ -1,7 +0,0 @@
// 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
@@ -1,43 +0,0 @@
// 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
@@ -1,91 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
import (
"errors"
"syscall/js"
"gioui.org/ui/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
@@ -1,74 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build darwin,!ios
package app
import (
"gioui.org/ui/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
@@ -1,9 +0,0 @@
// 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
@@ -1,166 +0,0 @@
// 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
@@ -1,462 +0,0 @@
// 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
@@ -1,164 +0,0 @@
// 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
@@ -1,328 +0,0 @@
// 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
@@ -1,387 +0,0 @@
// 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
@@ -1,184 +0,0 @@
// 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
@@ -1,19 +0,0 @@
// +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
@@ -1,21 +0,0 @@
// 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
@@ -1,114 +0,0 @@
// 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
@@ -1,18 +0,0 @@
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
@@ -1,109 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
import (
"fmt"
"gioui.org/ui/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
@@ -1,125 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
import (
"errors"
"strings"
"gioui.org/ui/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
@@ -1,85 +0,0 @@
// 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
@@ -1,573 +0,0 @@
// 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/ui/app/internal/gl"
"gioui.org/ui/f32"
"gioui.org/ui/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
@@ -1,93 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
import (
"time"
"gioui.org/ui/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
@@ -1,149 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package input
import (
"gioui.org/ui"
"gioui.org/ui/internal/opconst"
"gioui.org/ui/internal/ops"
"gioui.org/ui/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
@@ -1,333 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package input
import (
"encoding/binary"
"image"
"gioui.org/ui"
"gioui.org/ui/f32"
"gioui.org/ui/internal/opconst"
"gioui.org/ui/internal/ops"
"gioui.org/ui/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
@@ -1,172 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package input
import (
"encoding/binary"
"time"
"gioui.org/ui"
"gioui.org/ui/internal/opconst"
"gioui.org/ui/internal/ops"
"gioui.org/ui/key"
"gioui.org/ui/pointer"
"gioui.org/ui/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
@@ -1,58 +0,0 @@
// 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
@@ -1,45 +0,0 @@
// 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
@@ -1,3 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
__attribute__ ((visibility ("hidden"))) void nslog(char *str);
-11
View File
@@ -1,11 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build darwin,ios
@import Foundation;
#include "log_ios.h"
void nslog(char *str) {
NSLog(@"%@", @(str));
}
-168
View File
@@ -1,168 +0,0 @@
// 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
@@ -1,428 +0,0 @@
// 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/ui/f32"
"gioui.org/ui/key"
"gioui.org/ui/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
@@ -1,19 +0,0 @@
// 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
@@ -1,255 +0,0 @@
// 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/ui/f32"
"gioui.org/ui/key"
"gioui.org/ui/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
@@ -1,8 +0,0 @@
// 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
@@ -1,320 +0,0 @@
// 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
@@ -1,421 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
import (
"image"
"sync"
"syscall/js"
"time"
"gioui.org/ui/f32"
"gioui.org/ui/key"
"gioui.org/ui/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
@@ -1,332 +0,0 @@
// 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/ui/f32"
"gioui.org/ui/key"
"gioui.org/ui/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
@@ -1,19 +0,0 @@
// 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
@@ -1,130 +0,0 @@
// 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
@@ -1,138 +0,0 @@
// 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
@@ -1,14 +0,0 @@
// 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
@@ -1,732 +0,0 @@
// 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/ui/f32"
"gioui.org/ui/key"
"gioui.org/ui/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
@@ -1,27 +0,0 @@
// +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
@@ -1,10 +0,0 @@
// +build darwin,ios
package app
import "C"
//export gio_runMain
func gio_runMain() {
runMain()
}
-14
View File
@@ -1,14 +0,0 @@
// 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
@@ -1,98 +0,0 @@
// +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
@@ -1,819 +0,0 @@
/* 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
@@ -1,77 +0,0 @@
// +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
@@ -1,376 +0,0 @@
/* 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
@@ -1,176 +0,0 @@
// +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
@@ -1,363 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
import (
"errors"
"fmt"
"image"
"time"
"gioui.org/ui"
"gioui.org/ui/app/internal/gpu"
"gioui.org/ui/app/internal/input"
"gioui.org/ui/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
@@ -1,219 +0,0 @@
// 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/ui/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
}
+2 -2
View File
@@ -20,8 +20,8 @@ to a ui/app.Window's Update method.
Drawing a colored square:
import "gioui.org/ui"
import "gioui.org/ui/app"
import "gioui.org/ui/paint"
import "gioui.org/app"
import "gioui.org/paint"
var w app.Window
ops := new(ui.Ops)
-118
View File
@@ -1,118 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
/*
Package f32 is a float32 implementation of package image's
Point and Rectangle.
The coordinate space has the origin in the top left
corner with the axes extending right and down.
*/
package f32
// A Point is a two dimensional point.
type Point struct {
X, Y float32
}
// A Rectangle contains the points (X, Y) where Min.X <= X < Max.X,
// Min.Y <= Y < Max.Y.
type Rectangle struct {
Min, Max Point
}
// Add return the point p+p2.
func (p Point) Add(p2 Point) Point {
return Point{X: p.X + p2.X, Y: p.Y + p2.Y}
}
// Sub returns the vector p-p2.
func (p Point) Sub(p2 Point) Point {
return Point{X: p.X - p2.X, Y: p.Y - p2.Y}
}
// Mul returns p scaled by s.
func (p Point) Mul(s float32) Point {
return Point{X: p.X * s, Y: p.Y * s}
}
// Size returns r's width and height.
func (r Rectangle) Size() Point {
return Point{X: r.Dx(), Y: r.Dy()}
}
// Dx returns r's width.
func (r Rectangle) Dx() float32 {
return r.Max.X - r.Min.X
}
// Dy returns r's Height.
func (r Rectangle) Dy() float32 {
return r.Max.Y - r.Min.Y
}
// Intersect returns the intersection of r and s.
func (r Rectangle) Intersect(s Rectangle) Rectangle {
if r.Min.X < s.Min.X {
r.Min.X = s.Min.X
}
if r.Min.Y < s.Min.Y {
r.Min.Y = s.Min.Y
}
if r.Max.X > s.Max.X {
r.Max.X = s.Max.X
}
if r.Max.Y > s.Max.Y {
r.Max.Y = s.Max.Y
}
return r
}
// Union returns the union of r and s.
func (r Rectangle) Union(s Rectangle) Rectangle {
if r.Min.X > s.Min.X {
r.Min.X = s.Min.X
}
if r.Min.Y > s.Min.Y {
r.Min.Y = s.Min.Y
}
if r.Max.X < s.Max.X {
r.Max.X = s.Max.X
}
if r.Max.Y < s.Max.Y {
r.Max.Y = s.Max.Y
}
return r
}
// Canon returns the canonical version of r, where Min is to
// the upper left of Max.
func (r Rectangle) Canon() Rectangle {
if r.Max.X < r.Min.X {
r.Min.X, r.Max.X = r.Max.X, r.Min.X
}
if r.Max.Y < r.Min.Y {
r.Min.Y, r.Max.Y = r.Max.Y, r.Min.Y
}
return r
}
// Empty reports whether r represents the empty area.
func (r Rectangle) Empty() bool {
return r.Min.X >= r.Max.X || r.Min.Y >= r.Max.Y
}
// Add offsets r with the vector p.
func (r Rectangle) Add(p Point) Rectangle {
return Rectangle{
Point{r.Min.X + p.X, r.Min.Y + p.Y},
Point{r.Max.X + p.X, r.Max.Y + p.Y},
}
}
// Sub offsets r with the vector -p.
func (r Rectangle) Sub(p Point) Rectangle {
return Rectangle{
Point{r.Min.X - p.X, r.Min.Y - p.Y},
Point{r.Max.X - p.X, r.Max.Y - p.Y},
}
}
-292
View File
@@ -1,292 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
/*
Package gesture implements common pointer gestures.
Gestures accept low level pointer Events from an event
Queue and detect higher level actions such as clicks
and scrolling.
*/
package gesture
import (
"math"
"gioui.org/ui"
"gioui.org/ui/f32"
"gioui.org/ui/internal/fling"
"gioui.org/ui/pointer"
)
// Click detects click gestures in the form
// of ClickEvents.
type Click struct {
// state tracks the gesture state.
state ClickState
}
type ClickState uint8
// ClickEvent represent a click action, either a
// TypePress for the beginning of a click or a
// TypeClick for a completed click.
type ClickEvent struct {
Type ClickType
Position f32.Point
Source pointer.Source
}
type ClickType uint8
// Scroll detects scroll gestures and reduces them to
// scroll distances. Scroll recognizes mouse wheel
// movements as well as drag and fling touch gestures.
type Scroll struct {
dragging bool
axis Axis
estimator fling.Extrapolation
flinger fling.Animation
pid pointer.ID
grab bool
last int
// Leftover scroll.
scroll float32
}
type ScrollState uint8
type Axis uint8
const (
Horizontal Axis = iota
Vertical
)
const (
// StateNormal is the default click state.
StateNormal ClickState = iota
// StateFocused is reported when a pointer
// is hovering over the handler.
StateFocused
// StatePressed is then a pointer is pressed.
StatePressed
)
const (
// TypePress is reported for the first pointer
// press.
TypePress ClickType = iota
// TypeClick is reporoted when a click action
// is complete.
TypeClick
)
const (
// StateIdle is the default scroll state.
StateIdle ScrollState = iota
// StateDrag is reported during drag gestures.
StateDragging
// StateFlinging is reported when a fling is
// in progress.
StateFlinging
)
var touchSlop = ui.Dp(3)
// Add the handler to the operation list to receive click events.
func (c *Click) Add(ops *ui.Ops) {
op := pointer.InputOp{Key: c}
op.Add(ops)
}
// State reports the click state.
func (c *Click) State() ClickState {
return c.state
}
// Events returns the next click event, if any.
func (c *Click) Events(q ui.Queue) []ClickEvent {
var events []ClickEvent
for _, evt := range q.Events(c) {
e, ok := evt.(pointer.Event)
if !ok {
continue
}
switch e.Type {
case pointer.Release:
wasPressed := c.state == StatePressed
c.state = StateNormal
if wasPressed {
events = append(events, ClickEvent{Type: TypeClick, Position: e.Position, Source: e.Source})
}
case pointer.Cancel:
c.state = StateNormal
case pointer.Press:
if c.state == StatePressed || !e.Hit {
break
}
c.state = StatePressed
events = append(events, ClickEvent{Type: TypePress, Position: e.Position, Source: e.Source})
case pointer.Move:
if c.state == StatePressed && !e.Hit {
c.state = StateNormal
} else if c.state < StateFocused {
c.state = StateFocused
}
}
}
return events
}
// Add the handler to the operation list to receive scroll events.
func (s *Scroll) Add(ops *ui.Ops) {
oph := pointer.InputOp{Key: s, Grab: s.grab}
oph.Add(ops)
if s.flinger.Active() {
ui.InvalidateOp{}.Add(ops)
}
}
// Stop any remaining fling movement.
func (s *Scroll) Stop() {
s.flinger = fling.Animation{}
}
// Scroll detects the scrolling distance from the available events and
// ongoing fling gestures.
func (s *Scroll) Scroll(cfg ui.Config, q ui.Queue, axis Axis) int {
if s.axis != axis {
s.axis = axis
return 0
}
total := 0
for _, evt := range q.Events(s) {
e, ok := evt.(pointer.Event)
if !ok {
continue
}
switch e.Type {
case pointer.Press:
if s.dragging || e.Source != pointer.Touch {
break
}
s.Stop()
s.estimator = fling.Extrapolation{}
v := s.val(e.Position)
s.last = int(math.Round(float64(v)))
s.estimator.Sample(e.Time, v)
s.dragging = true
s.pid = e.PointerID
case pointer.Release:
if s.pid != e.PointerID {
break
}
fling := s.estimator.Estimate()
if slop, d := float32(cfg.Px(touchSlop)), fling.Distance; d < -slop || d > slop {
s.flinger.Start(cfg, fling.Velocity)
}
fallthrough
case pointer.Cancel:
s.dragging = false
s.grab = false
case pointer.Move:
// Scroll
switch s.axis {
case Horizontal:
s.scroll += e.Scroll.X
case Vertical:
s.scroll += e.Scroll.Y
}
iscroll := int(math.Round(float64(s.scroll)))
s.scroll -= float32(iscroll)
total += iscroll
if !s.dragging || s.pid != e.PointerID {
continue
}
// Drag
val := s.val(e.Position)
s.estimator.Sample(e.Time, val)
v := int(math.Round(float64(val)))
dist := s.last - v
if e.Priority < pointer.Grabbed {
slop := cfg.Px(touchSlop)
if dist := dist; dist >= slop || -slop >= dist {
s.grab = true
}
} else {
s.last = v
total += dist
}
}
}
total += s.flinger.Tick(cfg.Now())
return total
}
func (s *Scroll) val(p f32.Point) float32 {
if s.axis == Horizontal {
return p.X
} else {
return p.Y
}
}
// State reports the scroll state.
func (s *Scroll) State() ScrollState {
switch {
case s.flinger.Active():
return StateFlinging
case s.dragging:
return StateDragging
default:
return StateIdle
}
}
func (a Axis) String() string {
switch a {
case Horizontal:
return "Horizontal"
case Vertical:
return "Vertical"
default:
panic("invalid Axis")
}
}
func (ct ClickType) String() string {
switch ct {
case TypePress:
return "TypePress"
case TypeClick:
return "TypeClick"
default:
panic("invalid ClickType")
}
}
func (cs ClickState) String() string {
switch cs {
case StateNormal:
return "StateNormal"
case StateFocused:
return "StateFocused"
case StatePressed:
return "StatePressed"
default:
panic("invalid ClickState")
}
}
func (s ScrollState) String() string {
switch s {
case StateIdle:
return "StateIdle"
case StateDragging:
return "StateDragging"
case StateFlinging:
return "StateFlinging"
default:
panic("unreachable")
}
}
-8
View File
@@ -1,8 +0,0 @@
module gioui.org/ui
go 1.13
require (
golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a
)
-6
View File
@@ -1,6 +0,0 @@
golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9 h1:uc17S921SPw5F2gJo7slQ3aqvr2RwpL7eb3+DZncu3s=
golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-98
View File
@@ -1,98 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package fling
import (
"math"
"runtime"
"time"
"gioui.org/ui"
)
type Animation struct {
// Current offset in pixels.
x float32
// Initial time.
t0 time.Time
// Initial velocity in pixels pr second.
v0 float32
}
var (
// Pixels/second.
minFlingVelocity = ui.Dp(50)
maxFlingVelocity = ui.Dp(8000)
)
const (
thresholdVelocity = 1
)
// Start a fling given a starting velocity. Returns whether a
// fling was started.
func (f *Animation) Start(c ui.Config, velocity float32) bool {
min := float32(c.Px(minFlingVelocity))
v := velocity
if -min <= v && v <= min {
return false
}
max := float32(c.Px(maxFlingVelocity))
if v > max {
v = max
} else if v < -max {
v = -max
}
f.init(c.Now(), v)
return true
}
func (f *Animation) init(now time.Time, v0 float32) {
f.t0 = now
f.v0 = v0
f.x = 0
}
func (f *Animation) Active() bool {
return f.v0 != 0
}
// Tick computes and returns a fling distance since
// the last time Tick was called.
func (f *Animation) Tick(now time.Time) int {
if !f.Active() {
return 0
}
var k float32
if runtime.GOOS == "darwin" {
k = -2 // iOS
} else {
k = -4.2 // Android and default
}
t := now.Sub(f.t0)
// The acceleration x''(t) of a point mass with a drag
// force, f, proportional with velocity, x'(t), is
// governed by the equation
//
// x''(t) = kx'(t)
//
// Given the starting position x(0) = 0, the starting
// velocity x'(0) = v0, the position is then
// given by
//
// x(t) = v0*e^(k*t)/k - v0/k
//
ekt := float32(math.Exp(float64(k) * t.Seconds()))
x := f.v0*ekt/k - f.v0/k
dist := x - f.x
idist := int(math.Round(float64(dist)))
f.x += float32(idist)
// Solving for the velocity x'(t) gives us
//
// x'(t) = v0*e^(k*t)
v := f.v0 * ekt
if -thresholdVelocity < v && v < thresholdVelocity {
f.v0 = 0
}
return idist
}
-332
View File
@@ -1,332 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package fling
import (
"math"
"strconv"
"strings"
"time"
)
// Extrapolation computes a 1-dimensional velocity estimate
// for a set of timestamped points using the least squares
// fit of a 2nd order polynomial. The same method is used
// by Android.
type Extrapolation struct {
// Index into points.
idx int
// Circular buffer of samples.
samples []sample
lastValue float32
// Pre-allocated cache for samples.
cache [historySize]sample
// Filtered values and times
values [historySize]float32
times [historySize]float32
}
type sample struct {
t time.Duration
v float32
}
type matrix struct {
rows, cols int
data []float32
}
type Estimate struct {
Velocity float32
Distance float32
}
type coefficients [degree + 1]float32
const (
degree = 2
historySize = 20
maxAge = 100 * time.Millisecond
maxSampleGap = 40 * time.Millisecond
)
// SampleDelta adds a relative sample to the estimation.
func (e *Extrapolation) SampleDelta(t time.Duration, delta float32) {
val := delta + e.lastValue
e.Sample(t, val)
}
// Sample adds an absolute sample to the estimation.
func (e *Extrapolation) Sample(t time.Duration, val float32) {
e.lastValue = val
if e.samples == nil {
e.samples = e.cache[:0]
}
s := sample{
t: t,
v: val,
}
if e.idx == len(e.samples) && e.idx < cap(e.samples) {
e.samples = append(e.samples, s)
} else {
e.samples[e.idx] = s
}
e.idx++
if e.idx == cap(e.samples) {
e.idx = 0
}
}
// Velocity returns an estimate of the implied velocity and
// distance for the points sampled, or zero if the estimation method
// failed.
func (e *Extrapolation) Estimate() Estimate {
if len(e.samples) == 0 {
return Estimate{}
}
values := e.values[:0]
times := e.times[:0]
first := e.get(0)
t := first.t
// Walk backwards collecting samples.
for i := 0; i < len(e.samples); i++ {
p := e.get(-i)
age := first.t - p.t
if age >= maxAge || t-p.t >= maxSampleGap {
// If the samples are too old or
// too much time passed between samples
// assume they're not part of the fling.
break
}
t = p.t
values = append(values, first.v-p.v)
times = append(times, float32((-age).Seconds()))
}
coef, ok := polyFit(times, values)
if !ok {
return Estimate{}
}
dist := values[len(values)-1] - values[0]
return Estimate{
Velocity: coef[1],
Distance: dist,
}
}
func (e *Extrapolation) get(i int) sample {
idx := (e.idx + i - 1 + len(e.samples)) % len(e.samples)
return e.samples[idx]
}
// fit computes the least squares polynomial fit for
// the set of points in X, Y. If the fitting fails
// because of contradicting or insufficient data,
// fit returns false.
func polyFit(X, Y []float32) (coefficients, bool) {
if len(X) != len(Y) {
panic("X and Y lengths differ")
}
if len(X) <= degree {
// Not enough points to fit a curve.
return coefficients{}, false
}
// Use a method similar to Android's VelocityTracker.cpp:
// https://android.googlesource.com/platform/frameworks/base/+/56a2301/libs/androidfw/VelocityTracker.cpp
// where all weights are 1.
// First, expand the X vector to the matrix A in column-major order.
A := newMatrix(degree+1, len(X))
for i, x := range X {
A.set(0, i, 1)
for j := 1; j < A.rows; j++ {
A.set(j, i, A.get(j-1, i)*x)
}
}
Q, Rt, ok := decomposeQR(A)
if !ok {
return coefficients{}, false
}
// Solve R*B = Qt*Y for B, which is then the polynomial coefficients.
// Since R is upper triangular, we can proceed from bottom right to
// upper left.
// https://en.wikipedia.org/wiki/Non-linear_least_squares
var B coefficients
for i := Q.rows - 1; i >= 0; i-- {
B[i] = dot(Q.col(i), Y)
for j := Q.rows - 1; j > i; j-- {
B[i] -= Rt.get(i, j) * B[j]
}
B[i] /= Rt.get(i, i)
}
return B, true
}
// decomposeQR computes and returns Q, Rt where Q*transpose(Rt) = A, if
// possible. R is guaranteed to be upper triangular and only the square
// part of Rt is returned.
func decomposeQR(A *matrix) (*matrix, *matrix, bool) {
// Gram-Schmidt QR decompose A where Q*R = A.
// https://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process
Q := newMatrix(A.rows, A.cols) // Column-major.
Rt := newMatrix(A.rows, A.rows) // R transposed, row-major.
for i := 0; i < Q.rows; i++ {
// Copy A column.
for j := 0; j < Q.cols; j++ {
Q.set(i, j, A.get(i, j))
}
// Subtract projections. Note that int the projection
//
// proju a = <u, a>/<u, u> u
//
// the normalized column e replaces u, where <e, e> = 1:
//
// proje a = <e, a>/<e, e> e = <e, a> e
for j := 0; j < i; j++ {
d := dot(Q.col(j), Q.col(i))
for k := 0; k < Q.cols; k++ {
Q.set(i, k, Q.get(i, k)-d*Q.get(j, k))
}
}
// Normalize Q columns.
n := norm(Q.col(i))
if n < 0.000001 {
// Degenerate data, no solution.
return nil, nil, false
}
invNorm := 1 / n
for j := 0; j < Q.cols; j++ {
Q.set(i, j, Q.get(i, j)*invNorm)
}
// Update Rt.
for j := i; j < Rt.cols; j++ {
Rt.set(i, j, dot(Q.col(i), A.col(j)))
}
}
return Q, Rt, true
}
func norm(V []float32) float32 {
var n float32
for _, v := range V {
n += v * v
}
return float32(math.Sqrt(float64(n)))
}
func dot(V1, V2 []float32) float32 {
var d float32
for i, v1 := range V1 {
d += v1 * V2[i]
}
return d
}
func newMatrix(rows, cols int) *matrix {
return &matrix{
rows: rows,
cols: cols,
data: make([]float32, rows*cols),
}
}
func (m *matrix) set(row, col int, v float32) {
if row < 0 || row >= m.rows {
panic("row out of range")
}
if col < 0 || col >= m.cols {
panic("col out of range")
}
m.data[row*m.cols+col] = v
}
func (m *matrix) get(row, col int) float32 {
if row < 0 || row >= m.rows {
panic("row out of range")
}
if col < 0 || col >= m.cols {
panic("col out of range")
}
return m.data[row*m.cols+col]
}
func (m *matrix) col(c int) []float32 {
return m.data[c*m.cols : (c+1)*m.cols]
}
func (m *matrix) approxEqual(m2 *matrix) bool {
if m.rows != m2.rows || m.cols != m2.cols {
return false
}
const epsilon = 0.00001
for row := 0; row < m.rows; row++ {
for col := 0; col < m.cols; col++ {
d := m2.get(row, col) - m.get(row, col)
if d < -epsilon || d > epsilon {
return false
}
}
}
return true
}
func (m *matrix) transpose() *matrix {
t := &matrix{
rows: m.cols,
cols: m.rows,
data: make([]float32, len(m.data)),
}
for i := 0; i < m.rows; i++ {
for j := 0; j < m.cols; j++ {
t.set(j, i, m.get(i, j))
}
}
return t
}
func (m *matrix) mul(m2 *matrix) *matrix {
if m.rows != m2.cols {
panic("mismatched matrices")
}
mm := &matrix{
rows: m.rows,
cols: m2.cols,
data: make([]float32, m.rows*m2.cols),
}
for i := 0; i < mm.rows; i++ {
for j := 0; j < mm.cols; j++ {
var v float32
for k := 0; k < m.rows; k++ {
v += m.get(k, j) * m2.get(i, k)
}
mm.set(i, j, v)
}
}
return mm
}
func (m *matrix) String() string {
var b strings.Builder
for i := 0; i < m.rows; i++ {
for j := 0; j < m.cols; j++ {
v := m.get(i, j)
b.WriteString(strconv.FormatFloat(float64(v), 'g', -1, 32))
b.WriteString(", ")
}
b.WriteString("\n")
}
return b.String()
}
func (c coefficients) approxEqual(c2 coefficients) bool {
const epsilon = 0.00001
for i, v := range c {
d := v - c2[i]
if d < -epsilon || d > epsilon {
return false
}
}
return true
}
-43
View File
@@ -1,43 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package fling
import "testing"
func TestDecomposeQR(t *testing.T) {
A := &matrix{
rows: 3, cols: 3,
data: []float32{
12, 6, -4,
-51, 167, 24,
4, -68, -41,
},
}
Q, Rt, ok := decomposeQR(A)
if !ok {
t.Fatal("decomposeQR failed")
}
R := Rt.transpose()
QR := Q.mul(R)
if !A.approxEqual(QR) {
t.Log("A\n", A)
t.Log("Q\n", Q)
t.Log("R\n", R)
t.Log("QR\n", QR)
t.Fatal("Q*R not approximately equal to A")
}
}
func TestFit(t *testing.T) {
X := []float32{-1, 0, 1}
Y := []float32{2, 0, 2}
got, ok := polyFit(X, Y)
if !ok {
t.Fatal("polyFit failed")
}
want := coefficients{0, 0, 2}
if !got.approxEqual(want) {
t.Fatalf("polyFit: got %v want %v", got, want)
}
}
-82
View File
@@ -1,82 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package opconst
type OpType byte
// Start at a high number for easier debugging.
const firstOpIndex = 200
const (
TypeMacroDef OpType = iota + firstOpIndex
TypeMacro
TypeTransform
TypeLayer
TypeInvalidate
TypeImage
TypePaint
TypeColor
TypeArea
TypePointerInput
TypePass
TypeKeyInput
TypeHideInput
TypePush
TypePop
TypeAux
TypeClip
TypeProfile
)
const (
TypeMacroDefLen = 1 + 4 + 4
TypeMacroLen = 1 + 4 + 4 + 4
TypeTransformLen = 1 + 4*2
TypeLayerLen = 1
TypeRedrawLen = 1 + 8
TypeImageLen = 1 + 4*4
TypePaintLen = 1 + 4*4
TypeColorLen = 1 + 4
TypeAreaLen = 1 + 1 + 4*4
TypePointerInputLen = 1 + 1
TypePassLen = 1 + 1
TypeKeyInputLen = 1 + 1
TypeHideInputLen = 1
TypePushLen = 1
TypePopLen = 1
TypeAuxLen = 1 + 4
TypeClipLen = 1 + 4*4
TypeProfileLen = 1
)
func (t OpType) Size() int {
return [...]int{
TypeMacroDefLen,
TypeMacroLen,
TypeTransformLen,
TypeLayerLen,
TypeRedrawLen,
TypeImageLen,
TypePaintLen,
TypeColorLen,
TypeAreaLen,
TypePointerInputLen,
TypePassLen,
TypeKeyInputLen,
TypeHideInputLen,
TypePushLen,
TypePopLen,
TypeAuxLen,
TypeClipLen,
TypeProfileLen,
}[t-firstOpIndex]
}
func (t OpType) NumRefs() int {
switch t {
case TypeMacro, TypeImage, TypeKeyInput, TypePointerInput, TypeProfile:
return 1
default:
return 0
}
}
-23
View File
@@ -1,23 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package ops
import (
"encoding/binary"
"math"
"gioui.org/ui"
"gioui.org/ui/f32"
"gioui.org/ui/internal/opconst"
)
func DecodeTransformOp(d []byte) ui.TransformOp {
bo := binary.LittleEndian
if opconst.OpType(d[0]) != opconst.TypeTransform {
panic("invalid op")
}
return ui.TransformOp{}.Offset(f32.Point{
X: math.Float32frombits(bo.Uint32(d[1:])),
Y: math.Float32frombits(bo.Uint32(d[5:])),
})
}
-180
View File
@@ -1,180 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package ops
import (
"encoding/binary"
"gioui.org/ui"
"gioui.org/ui/internal/opconst"
)
// Reader parses an ops list.
type Reader struct {
pc pc
stack []macro
ops *ui.Ops
}
// EncodedOp represents an encoded op returned by
// Reader.
type EncodedOp struct {
Key Key
Data []byte
Refs []interface{}
}
// Key is a unique key for a given op.
type Key struct {
ops *ui.Ops
pc int
version int
}
// Shadow of ui.MacroOp.
type macroOp struct {
ops *ui.Ops
version int
pc pc
}
type pc struct {
data int
refs int
}
type macro struct {
ops *ui.Ops
retPC pc
endPC pc
}
type opMacroDef struct {
endpc pc
}
type opAux struct {
len int
}
// Reset start reading from the op list.
func (r *Reader) Reset(ops *ui.Ops) {
r.stack = r.stack[:0]
r.pc = pc{}
r.ops = ops
}
func (r *Reader) Decode() (EncodedOp, bool) {
if r.ops == nil {
return EncodedOp{}, false
}
for {
if len(r.stack) > 0 {
b := r.stack[len(r.stack)-1]
if r.pc == b.endPC {
r.ops = b.ops
r.pc = b.retPC
r.stack = r.stack[:len(r.stack)-1]
continue
}
}
data := r.ops.Data()
data = data[r.pc.data:]
if len(data) == 0 {
return EncodedOp{}, false
}
key := Key{ops: r.ops, pc: r.pc.data, version: r.ops.Version()}
t := opconst.OpType(data[0])
n := t.Size()
nrefs := t.NumRefs()
data = data[:n]
refs := r.ops.Refs()
refs = refs[r.pc.refs:]
refs = refs[:nrefs]
switch t {
case opconst.TypeAux:
var op opAux
op.decode(data)
n += op.len
data = data[:n]
case opconst.TypeMacro:
var op macroOp
op.decode(data, refs)
macroOps := op.ops
macroData := macroOps.Data()
macroData = macroData[op.pc.data:]
if opconst.OpType(macroData[0]) != opconst.TypeMacroDef {
panic("invalid macro reference")
}
if op.version != op.ops.Version() {
panic("invalid MacroOp reference to reset Ops")
}
var opDef opMacroDef
opDef.decode(macroData[:opconst.TypeMacroDef.Size()])
retPC := r.pc
retPC.data += n
retPC.refs += nrefs
r.stack = append(r.stack, macro{
ops: r.ops,
retPC: retPC,
endPC: opDef.endpc,
})
r.ops = macroOps
r.pc = op.pc
r.pc.data += opconst.TypeMacroDef.Size()
r.pc.refs += opconst.TypeMacroDef.NumRefs()
continue
case opconst.TypeMacroDef:
var op opMacroDef
op.decode(data)
r.pc = op.endpc
continue
}
r.pc.data += n
r.pc.refs += nrefs
return EncodedOp{Key: key, Data: data, Refs: refs}, true
}
}
func (op *opMacroDef) decode(data []byte) {
if opconst.OpType(data[0]) != opconst.TypeMacroDef {
panic("invalid op")
}
bo := binary.LittleEndian
dataIdx := int(int32(bo.Uint32(data[1:])))
refsIdx := int(int32(bo.Uint32(data[5:])))
*op = opMacroDef{
endpc: pc{
data: dataIdx,
refs: refsIdx,
},
}
}
func (op *opAux) decode(data []byte) {
if opconst.OpType(data[0]) != opconst.TypeAux {
panic("invalid op")
}
bo := binary.LittleEndian
*op = opAux{
len: int(int32(bo.Uint32(data[1:]))),
}
}
func (m *macroOp) decode(data []byte, refs []interface{}) {
if opconst.OpType(data[0]) != opconst.TypeMacro {
panic("invalid op")
}
bo := binary.LittleEndian
dataIdx := int(int32(bo.Uint32(data[1:])))
refsIdx := int(int32(bo.Uint32(data[5:])))
version := int(int32(bo.Uint32(data[9:])))
*m = macroOp{
ops: refs[0].(*ui.Ops),
pc: pc{
data: dataIdx,
refs: refsIdx,
},
version: version,
}
}
-26
View File
@@ -1,26 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package path
import (
"unsafe"
)
// The vertex data suitable for passing to vertex programs.
type Vertex struct {
CornerX, CornerY int16
MaxY float32
FromX, FromY float32
CtrlX, CtrlY float32
ToX, ToY float32
}
const VertStride = 7*4 + 2*2
func init() {
// Check that struct vertex has the expected size and
// that it contains no padding.
if unsafe.Sizeof(*(*Vertex)(nil)) != VertStride {
panic("unexpected struct size")
}
}
-105
View File
@@ -1,105 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
/*
Package key implements key and text events and operations.
The InputOp operations is used for declaring key input handlers. Use
an implementation of the Queue interface from package ui to receive
events.
*/
package key
import (
"gioui.org/ui"
"gioui.org/ui/internal/opconst"
)
// InputOp declares a handler ready for key events.
// Key events are in general only delivered to the
// focused key handler. Set the Focus flag to request
// the focus.
type InputOp struct {
Key ui.Key
Focus bool
}
// HideInputOp request that any on screen text input
// be hidden.
type HideInputOp struct{}
// A FocusEvent is generated when a handler gains or loses
// focus.
type FocusEvent struct {
Focus bool
}
// An Event is generated when a key is pressed. For text input
// use EditEvent.
type Event struct {
// Name is the rune character that most closely
// match the key. For letters, the upper case form
// is used.
Name rune
// Modifiers is the set of active modifiers when
// the key was pressed.
Modifiers Modifiers
}
// An EditEvent is generated when text is input.
type EditEvent struct {
Text string
}
// Modifiers
type Modifiers uint32
const (
// ModCommand is the command modifier. On macOS
// it is the Cmd key, on other platforms the Ctrl
// key.
ModCommand Modifiers = 1 << iota
// THe shift key.
ModShift
)
const (
// Runes for special keys.
NameLeftArrow = '←'
NameRightArrow = '→'
NameUpArrow = '↑'
NameDownArrow = '↓'
NameReturn = '⏎'
NameEnter = '⌤'
NameEscape = '⎋'
NameHome = '⇱'
NameEnd = '⇲'
NameDeleteBackward = '⌫'
NameDeleteForward = '⌦'
NamePageUp = '⇞'
NamePageDown = '⇟'
)
// Contain reports whether m contains all modifiers
// in m2.
func (m Modifiers) Contain(m2 Modifiers) bool {
return m&m2 == m2
}
func (h InputOp) Add(o *ui.Ops) {
data := make([]byte, opconst.TypeKeyInputLen)
data[0] = byte(opconst.TypeKeyInput)
if h.Focus {
data[1] = 1
}
o.Write(data, h.Key)
}
func (h HideInputOp) Add(o *ui.Ops) {
data := make([]byte, opconst.TypeHideInputLen)
data[0] = byte(opconst.TypeHideInput)
o.Write(data)
}
func (EditEvent) ImplementsEvent() {}
func (Event) ImplementsEvent() {}
func (FocusEvent) ImplementsEvent() {}
-51
View File
@@ -1,51 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
/*
Package layout implements layouts common to GUI programs.
Constraints and dimensions
Constraints and dimensions form the interface between layouts and
interface child elements. This package operates on Widgets, functions
that compute Dimensions from a a set of constraints for acceptable
widths and heights. Both the constraints and dimensions are maintained
in an implicit Context to keep the Widget declaration short.
For example, to add space above a widget:
gtx := &layout.Context{...}
gtx.Reset(...)
// Configure a top inset.
inset := layout.Inset{Top: ui.Dp(8), ...}
// Use the inset to lay out a widget.
inset.Layout(gtx, func() {
// Lay out widget and determine its size given the constraints.
...
dims := layout.Dimensions{...}
gtx.Dimensions = dims
})
Note that the example does not generate any garbage even though the
Inset is transient. Layouts that don't accept user input are designed
to not escape to the heap during their use.
Layout operations are recursive: a child in a layout operation can
itself be another layout. That way, complex user interfaces can
be created from a few generic layouts.
This example both aligns and insets a child:
inset := layout.Inset{...}
inset.Layout(gtx, func() {
align := layout.Align(...)
align.Layout(gtx, func() {
widget.Layout(gtx, ...)
})
})
More complex layouts such as Stack and Flex lay out multiple children,
and stateful layouts such as List accept user input.
*/
package layout
-298
View File
@@ -1,298 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package layout
import (
"image"
"gioui.org/ui"
"gioui.org/ui/f32"
)
// Flex lays out child elements along an axis,
// according to alignment and weights.
type Flex struct {
// Axis is the main axis, either Horizontal or Vertical.
Axis Axis
// Spacing controls the distribution of space left after
// layout.
Spacing Spacing
// Alignment is the alignment in the cross axis.
Alignment Alignment
ctx *Context
macro ui.MacroOp
mode flexMode
size int
rigidSize int
// fraction is the rounding error from a Flexible weighting.
fraction float32
maxCross int
maxBaseline int
}
// FlexChild is the layout result of a call End.
type FlexChild struct {
macro ui.MacroOp
dims Dimensions
}
// Spacing determine the spacing mode for a Flex.
type Spacing uint8
type flexMode uint8
const (
// SpaceEnd leaves space at the end.
SpaceEnd Spacing = iota
// SpaceStart leaves space at the start.
SpaceStart
// SpaceSides shares space between the start and end.
SpaceSides
// SpaceAround distributes space evenly between children,
// with half as much space at the start and end.
SpaceAround
// SpaceBetween distributes space evenly between children,
// leaving no space at the start and end.
SpaceBetween
// SpaceEvenly distributes space evenly between children and
// at the start and end.
SpaceEvenly
)
const (
modeNone flexMode = iota
modeBegun
modeRigid
modeFlex
)
// Init must be called before Rigid or Flexible.
func (f *Flex) Init(gtx *Context) *Flex {
if f.mode > modeBegun {
panic("must End the current child before calling Init again")
}
f.mode = modeBegun
f.ctx = gtx
f.size = 0
f.rigidSize = 0
f.maxCross = 0
f.maxBaseline = 0
return f
}
func (f *Flex) begin(mode flexMode) {
switch {
case f.mode == modeNone:
panic("must Init before adding a child")
case f.mode > modeBegun:
panic("must End before adding a child")
}
f.mode = mode
f.macro.Record(f.ctx.Ops)
}
// Rigid lays out a widget with the main axis constrained to the range
// from 0 to the remaining space.
func (f *Flex) Rigid(w Widget) FlexChild {
f.begin(modeRigid)
cs := f.ctx.Constraints
mainc := axisMainConstraint(f.Axis, cs)
mainMax := mainc.Max - f.size
if mainMax < 0 {
mainMax = 0
}
cs = axisConstraints(f.Axis, Constraint{Max: mainMax}, axisCrossConstraint(f.Axis, cs))
dims := f.ctx.Layout(cs, w)
return f.end(dims)
}
// Flexible is like Rigid, where the main axis size is also constrained to a
// fraction of the space not taken up by Rigid children.
func (f *Flex) Flexible(weight float32, w Widget) FlexChild {
f.begin(modeFlex)
cs := f.ctx.Constraints
mainc := axisMainConstraint(f.Axis, cs)
var flexSize int
if mainc.Max > f.size {
flexSize = mainc.Max - f.rigidSize
// Apply weight and add any leftover fraction from a
// previous Flexible.
size := float32(flexSize)*weight + f.fraction
flexSize = int(size + .5)
f.fraction = size - float32(flexSize)
if max := mainc.Max - f.size; flexSize > max {
flexSize = max
}
}
submainc := Constraint{Max: flexSize}
cs = axisConstraints(f.Axis, submainc, axisCrossConstraint(f.Axis, cs))
dims := f.ctx.Layout(cs, w)
return f.end(dims)
}
// End a child by specifying its dimensions. Pass the returned layout result
// to Layout.
func (f *Flex) end(dims Dimensions) FlexChild {
if f.mode <= modeBegun {
panic("End called without an active child")
}
f.macro.Stop()
sz := axisMain(f.Axis, dims.Size)
f.size += sz
if f.mode == modeRigid {
f.rigidSize += sz
}
f.mode = modeBegun
if c := axisCross(f.Axis, dims.Size); c > f.maxCross {
f.maxCross = c
}
if b := dims.Baseline; b > f.maxBaseline {
f.maxBaseline = b
}
return FlexChild{f.macro, dims}
}
// Layout a list of children. The order of the children determines their laid
// out order.
func (f *Flex) Layout(children ...FlexChild) {
cs := f.ctx.Constraints
mainc := axisMainConstraint(f.Axis, cs)
crossSize := axisCrossConstraint(f.Axis, cs).Constrain(f.maxCross)
var space int
if mainc.Min > f.size {
space = mainc.Min - f.size
}
var mainSize int
var baseline int
switch f.Spacing {
case SpaceSides:
mainSize += space / 2
case SpaceStart:
mainSize += space
case SpaceEvenly:
mainSize += space / (1 + len(children))
case SpaceAround:
mainSize += space / (len(children) * 2)
}
for i, child := range children {
dims := child.dims
b := dims.Baseline
var cross int
switch f.Alignment {
case End:
cross = crossSize - axisCross(f.Axis, dims.Size)
case Middle:
cross = (crossSize - axisCross(f.Axis, dims.Size)) / 2
case Baseline:
if f.Axis == Horizontal {
cross = f.maxBaseline - b
}
}
var stack ui.StackOp
stack.Push(f.ctx.Ops)
ui.TransformOp{}.Offset(toPointF(axisPoint(f.Axis, mainSize, cross))).Add(f.ctx.Ops)
child.macro.Add(f.ctx.Ops)
stack.Pop()
mainSize += axisMain(f.Axis, dims.Size)
if i < len(children)-1 {
switch f.Spacing {
case SpaceEvenly:
mainSize += space / (1 + len(children))
case SpaceAround:
mainSize += space / len(children)
case SpaceBetween:
mainSize += space / (len(children) - 1)
}
}
if b != dims.Size.Y {
baseline = b
}
}
switch f.Spacing {
case SpaceSides:
mainSize += space / 2
case SpaceEnd:
mainSize += space
case SpaceEvenly:
mainSize += space / (1 + len(children))
case SpaceAround:
mainSize += space / (len(children) * 2)
}
sz := axisPoint(f.Axis, mainSize, crossSize)
if baseline == 0 {
baseline = sz.Y
}
f.ctx.Dimensions = Dimensions{Size: sz, Baseline: baseline}
}
func axisPoint(a Axis, main, cross int) image.Point {
if a == Horizontal {
return image.Point{main, cross}
} else {
return image.Point{cross, main}
}
}
func axisMain(a Axis, sz image.Point) int {
if a == Horizontal {
return sz.X
} else {
return sz.Y
}
}
func axisCross(a Axis, sz image.Point) int {
if a == Horizontal {
return sz.Y
} else {
return sz.X
}
}
func axisMainConstraint(a Axis, cs Constraints) Constraint {
if a == Horizontal {
return cs.Width
} else {
return cs.Height
}
}
func axisCrossConstraint(a Axis, cs Constraints) Constraint {
if a == Horizontal {
return cs.Height
} else {
return cs.Width
}
}
func axisConstraints(a Axis, mainc, crossc Constraint) Constraints {
if a == Horizontal {
return Constraints{Width: mainc, Height: crossc}
} else {
return Constraints{Width: crossc, Height: mainc}
}
}
func toPointF(p image.Point) f32.Point {
return f32.Point{X: float32(p.X), Y: float32(p.Y)}
}
func (s Spacing) String() string {
switch s {
case SpaceEnd:
return "SpaceEnd"
case SpaceStart:
return "SpaceStart"
case SpaceSides:
return "SpaceSides"
case SpaceAround:
return "SpaceAround"
case SpaceBetween:
return "SpaceAround"
case SpaceEvenly:
return "SpaceEvenly"
default:
panic("unreachable")
}
}
-266
View File
@@ -1,266 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package layout
import (
"image"
"gioui.org/ui"
)
// Constraints represent a set of acceptable ranges for
// a widget's width and height.
type Constraints struct {
Width Constraint
Height Constraint
}
// Constraint is a range of acceptable sizes in a single
// dimension.
type Constraint struct {
Min, Max int
}
// Dimensions are the resolved size and baseline for a widget.
type Dimensions struct {
Size image.Point
Baseline int
}
// Axis is the Horizontal or Vertical direction.
type Axis uint8
// Alignment is the mutual alignment of a list of widgets.
type Alignment uint8
// Direction is the alignment of widgets relative to a containing
// space.
type Direction uint8
// Widget is a function scope for drawing, processing events and
// computing dimensions for a user interface element.
type Widget func()
// Context carry the state needed by almost all layouts and widgets.
type Context struct {
// Constraints track the constraints for the active widget or
// layout.
Constraints Constraints
// Dimensions track the result of the most recent layout
// operation.
Dimensions Dimensions
ui.Config
ui.Queue
*ui.Ops
}
const (
Start Alignment = iota
End
Middle
Baseline
)
const (
NW Direction = iota
N
NE
E
SE
S
SW
W
Center
)
const (
Horizontal Axis = iota
Vertical
)
// Layout a widget with a set of constraints and return its
// dimensions. The previous constraints are restored after layout.
func (s *Context) Layout(cs Constraints, w Widget) Dimensions {
saved := s.Constraints
s.Constraints = cs
s.Dimensions = Dimensions{}
w()
s.Constraints = saved
return s.Dimensions
}
// Reset the context.
func (c *Context) Reset(cfg ui.Config, cs Constraints) {
c.Constraints = cs
c.Dimensions = Dimensions{}
c.Config = cfg
if c.Ops == nil {
c.Ops = new(ui.Ops)
}
c.Ops.Reset()
}
// Constrain a value to the range [Min; Max].
func (c Constraint) Constrain(v int) int {
if v < c.Min {
return c.Min
} else if v > c.Max {
return c.Max
}
return v
}
// Constrain a size to the Width and Height ranges.
func (c Constraints) Constrain(size image.Point) image.Point {
return image.Point{X: c.Width.Constrain(size.X), Y: c.Height.Constrain(size.Y)}
}
// RigidConstraints returns the constraints that can only be
// satisfied by the given dimensions.
func RigidConstraints(size image.Point) Constraints {
return Constraints{
Width: Constraint{Min: size.X, Max: size.X},
Height: Constraint{Min: size.Y, Max: size.Y},
}
}
// Inset adds space around a widget.
type Inset struct {
Top, Right, Bottom, Left ui.Value
}
// Align aligns a widget in the available space.
type Align Direction
// Layout a widget.
func (in Inset) Layout(gtx *Context, w Widget) {
top := gtx.Px(in.Top)
right := gtx.Px(in.Right)
bottom := gtx.Px(in.Bottom)
left := gtx.Px(in.Left)
mcs := gtx.Constraints
mcs.Width.Min -= left + right
mcs.Width.Max -= left + right
if mcs.Width.Min < 0 {
mcs.Width.Min = 0
}
if mcs.Width.Max < mcs.Width.Min {
mcs.Width.Max = mcs.Width.Min
}
mcs.Height.Min -= top + bottom
mcs.Height.Max -= top + bottom
if mcs.Height.Min < 0 {
mcs.Height.Min = 0
}
if mcs.Height.Max < mcs.Height.Min {
mcs.Height.Max = mcs.Height.Min
}
var stack ui.StackOp
stack.Push(gtx.Ops)
ui.TransformOp{}.Offset(toPointF(image.Point{X: left, Y: top})).Add(gtx.Ops)
dims := gtx.Layout(mcs, w)
stack.Pop()
gtx.Dimensions = Dimensions{
Size: gtx.Constraints.Constrain(dims.Size.Add(image.Point{X: right + left, Y: top + bottom})),
Baseline: dims.Baseline + top,
}
}
// UniformInset returns an Inset with a single inset applied to all
// edges.
func UniformInset(v ui.Value) Inset {
return Inset{Top: v, Right: v, Bottom: v, Left: v}
}
// Layout a widget.
func (a Align) Layout(gtx *Context, w Widget) {
var macro ui.MacroOp
macro.Record(gtx.Ops)
cs := gtx.Constraints
mcs := cs
mcs.Width.Min = 0
mcs.Height.Min = 0
dims := gtx.Layout(mcs, w)
macro.Stop()
sz := dims.Size
if sz.X < cs.Width.Min {
sz.X = cs.Width.Min
}
if sz.Y < cs.Height.Min {
sz.Y = cs.Height.Min
}
var p image.Point
switch Direction(a) {
case N, S, Center:
p.X = (sz.X - dims.Size.X) / 2
case NE, SE, E:
p.X = sz.X - dims.Size.X
}
switch Direction(a) {
case W, Center, E:
p.Y = (sz.Y - dims.Size.Y) / 2
case SW, S, SE:
p.Y = sz.Y - dims.Size.Y
}
var stack ui.StackOp
stack.Push(gtx.Ops)
ui.TransformOp{}.Offset(toPointF(p)).Add(gtx.Ops)
macro.Add(gtx.Ops)
stack.Pop()
gtx.Dimensions = Dimensions{
Size: sz,
Baseline: dims.Baseline,
}
}
func (a Alignment) String() string {
switch a {
case Start:
return "Start"
case End:
return "End"
case Middle:
return "Middle"
case Baseline:
return "Baseline"
default:
panic("unreachable")
}
}
func (a Axis) String() string {
switch a {
case Horizontal:
return "Horizontal"
case Vertical:
return "Vertical"
default:
panic("unreachable")
}
}
func (d Direction) String() string {
switch d {
case NW:
return "NW"
case N:
return "N"
case NE:
return "NE"
case E:
return "E"
case SE:
return "SE"
case S:
return "S"
case SW:
return "SW"
case W:
return "W"
case Center:
return "Center"
default:
panic("unreachable")
}
}
-154
View File
@@ -1,154 +0,0 @@
package layout_test
import (
"fmt"
"image"
"time"
"gioui.org/ui"
"gioui.org/ui/layout"
)
type queue struct{}
type config struct{}
var q queue
var cfg = new(config)
func ExampleInset() {
gtx := &layout.Context{Queue: q}
// Loose constraints with no minimal size.
var cs layout.Constraints
cs.Width.Max = 100
cs.Height.Max = 100
gtx.Reset(cfg, cs)
// Inset all edges by 10.
inset := layout.UniformInset(ui.Dp(10))
inset.Layout(gtx, func() {
// Lay out a 50x50 sized widget.
layoutWidget(gtx, 50, 50)
fmt.Println(gtx.Dimensions.Size)
})
fmt.Println(gtx.Dimensions.Size)
// Output:
// (50,50)
// (70,70)
}
func ExampleAlign() {
gtx := &layout.Context{Queue: q}
// Rigid constraints with both minimum and maximum set.
cs := layout.RigidConstraints(image.Point{X: 100, Y: 100})
gtx.Reset(cfg, cs)
align := layout.Align(layout.Center)
align.Layout(gtx, func() {
// Lay out a 50x50 sized widget.
layoutWidget(gtx, 50, 50)
fmt.Println(gtx.Dimensions.Size)
})
fmt.Println(gtx.Dimensions.Size)
// Output:
// (50,50)
// (100,100)
}
func ExampleFlex() {
gtx := &layout.Context{Queue: q}
cs := layout.RigidConstraints(image.Point{X: 100, Y: 100})
gtx.Reset(cfg, cs)
flex := layout.Flex{}
flex.Init(gtx)
// Rigid 10x10 widget.
child1 := flex.Rigid(func() {
fmt.Printf("Rigid: %v\n", gtx.Constraints.Width)
layoutWidget(gtx, 10, 10)
})
// Child with 50% space allowance.
child2 := flex.Flexible(0.5, func() {
fmt.Printf("50%%: %v\n", gtx.Constraints.Width)
layoutWidget(gtx, 10, 10)
})
flex.Layout(child1, child2)
// Output:
// Rigid: {0 100}
// 50%: {0 45}
}
func ExampleStack() {
gtx := &layout.Context{Queue: q}
cs := layout.RigidConstraints(image.Point{X: 100, Y: 100})
gtx.Reset(cfg, cs)
stack := layout.Stack{}
stack.Init(gtx)
// Rigid 50x50 widget.
child1 := stack.Rigid(func() {
layoutWidget(gtx, 50, 50)
})
// Force widget to the same size as the first.
child2 := stack.Expand(func() {
fmt.Printf("Expand: %v\n", gtx.Constraints)
layoutWidget(gtx, 10, 10)
})
stack.Layout(child1, child2)
// Output:
// Expand: {{50 50} {50 50}}
}
func ExampleList() {
gtx := &layout.Context{Queue: q}
cs := layout.RigidConstraints(image.Point{X: 100, Y: 100})
gtx.Reset(cfg, cs)
// The list is 1e6 elements, but only 5 fit the constraints.
const listLen = 1e6
var list layout.List
count := 0
list.Layout(gtx, listLen, func(i int) {
count++
layoutWidget(gtx, 20, 20)
})
fmt.Println(count)
// Output:
// 5
}
func layoutWidget(ctx *layout.Context, width, height int) {
ctx.Dimensions = layout.Dimensions{
Size: image.Point{
X: width,
Y: height,
},
}
}
func (config) Now() time.Time {
return time.Now()
}
func (config) Px(v ui.Value) int {
return int(v.V + .5)
}
func (queue) Events(k ui.Key) []ui.Event {
return nil
}
-269
View File
@@ -1,269 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package layout
import (
"image"
"gioui.org/ui"
"gioui.org/ui/gesture"
"gioui.org/ui/paint"
"gioui.org/ui/pointer"
)
type scrollChild struct {
size image.Point
macro ui.MacroOp
}
// List displays a subsection of a potentially infinitely
// large underlying list. List accepts user input to scroll
// the subsection.
type List struct {
Axis Axis
// ScrollToEnd instructs the list to stay scrolled to the far end position
// once reahed. A List with ScrollToEnd enabled also align its content to
// the end.
ScrollToEnd bool
// Alignment is the cross axis alignment of list elements.
Alignment Alignment
// beforeEnd tracks whether the List position is before
// the very end.
beforeEnd bool
ctx *Context
macro ui.MacroOp
child ui.MacroOp
scroll gesture.Scroll
scrollDelta int
// first is the index of the first visible child.
first int
// offset is the signed distance from the top edge
// to the child with index first.
offset int
len int
// maxSize is the total size of visible children.
maxSize int
children []scrollChild
dir iterationDir
}
// ListElement is a function that computes the dimensions of
// a list element.
type ListElement func(index int)
type iterationDir uint8
const (
iterateNone iterationDir = iota
iterateForward
iterateBackward
)
const inf = 1e6
// init prepares the list for iterating through its children with next.
func (l *List) init(gtx *Context, len int) {
if l.more() {
panic("unfinished child")
}
l.ctx = gtx
l.maxSize = 0
l.children = l.children[:0]
l.len = len
l.update()
if l.scrollToEnd() {
l.offset = 0
l.first = len
}
if l.first > len {
l.offset = 0
l.first = len
}
l.macro.Record(gtx.Ops)
l.next()
}
// Layout the List and return its dimensions.
func (l *List) Layout(gtx *Context, len int, w ListElement) {
for l.init(gtx, len); l.more(); l.next() {
cs := axisConstraints(l.Axis, Constraint{Max: inf}, axisCrossConstraint(l.Axis, l.ctx.Constraints))
i := l.index()
l.end(gtx.Layout(cs, func() {
w(i)
}))
}
gtx.Dimensions = l.layout()
}
func (l *List) scrollToEnd() bool {
return l.ScrollToEnd && !l.beforeEnd
}
// Dragging reports whether the List is being dragged.
func (l *List) Dragging() bool {
return l.scroll.State() == gesture.StateDragging
}
func (l *List) update() {
d := l.scroll.Scroll(l.ctx.Config, l.ctx.Queue, gesture.Axis(l.Axis))
l.scrollDelta = d
l.offset += d
}
// next advances to the next child.
func (l *List) next() {
l.dir = l.nextDir()
// The user scroll offset is applied after scrolling to
// list end.
if l.scrollToEnd() && !l.more() && l.scrollDelta < 0 {
l.beforeEnd = true
l.offset += l.scrollDelta
l.dir = l.nextDir()
}
if l.more() {
l.child.Record(l.ctx.Ops)
}
}
// index is current child's position in the underlying list.
func (l *List) index() int {
switch l.dir {
case iterateBackward:
return l.first - 1
case iterateForward:
return l.first + len(l.children)
default:
panic("Index called before Next")
}
}
// more reports whether more children are needed.
func (l *List) more() bool {
return l.dir != iterateNone
}
func (l *List) nextDir() iterationDir {
vsize := axisMainConstraint(l.Axis, l.ctx.Constraints).Max
last := l.first + len(l.children)
// Clamp offset.
if l.maxSize-l.offset < vsize && last == l.len {
l.offset = l.maxSize - vsize
}
if l.offset < 0 && l.first == 0 {
l.offset = 0
}
switch {
case len(l.children) == l.len:
return iterateNone
case l.maxSize-l.offset < vsize:
return iterateForward
case l.offset < 0:
return iterateBackward
}
return iterateNone
}
// End the current child by specifying its dimensions.
func (l *List) end(dims Dimensions) {
l.child.Stop()
child := scrollChild{dims.Size, l.child}
mainSize := axisMain(l.Axis, child.size)
l.maxSize += mainSize
switch l.dir {
case iterateForward:
l.children = append(l.children, child)
case iterateBackward:
l.children = append([]scrollChild{child}, l.children...)
l.first--
l.offset += mainSize
default:
panic("call Next before End")
}
l.dir = iterateNone
}
// Layout the List and return its dimensions.
func (l *List) layout() Dimensions {
if l.more() {
panic("unfinished child")
}
mainc := axisMainConstraint(l.Axis, l.ctx.Constraints)
children := l.children
// Skip invisible children
for len(children) > 0 {
sz := children[0].size
mainSize := axisMain(l.Axis, sz)
if l.offset <= mainSize {
break
}
l.first++
l.offset -= mainSize
children = children[1:]
}
size := -l.offset
var maxCross int
for i, child := range children {
sz := child.size
if c := axisCross(l.Axis, sz); c > maxCross {
maxCross = c
}
size += axisMain(l.Axis, sz)
if size >= mainc.Max {
children = children[:i+1]
break
}
}
ops := l.ctx.Ops
pos := -l.offset
// ScrollToEnd lists lists are end aligned.
if space := mainc.Max - size; l.ScrollToEnd && space > 0 {
pos += space
}
for _, child := range children {
sz := child.size
var cross int
switch l.Alignment {
case End:
cross = maxCross - axisCross(l.Axis, sz)
case Middle:
cross = (maxCross - axisCross(l.Axis, sz)) / 2
}
childSize := axisMain(l.Axis, sz)
max := childSize + pos
if max > mainc.Max {
max = mainc.Max
}
min := pos
if min < 0 {
min = 0
}
r := image.Rectangle{
Min: axisPoint(l.Axis, min, -inf),
Max: axisPoint(l.Axis, max, inf),
}
var stack ui.StackOp
stack.Push(ops)
paint.RectClip(r).Add(ops)
ui.TransformOp{}.Offset(toPointF(axisPoint(l.Axis, pos, cross))).Add(ops)
child.macro.Add(ops)
stack.Pop()
pos += childSize
}
atStart := l.first == 0 && l.offset <= 0
atEnd := l.first+len(children) == l.len && mainc.Max >= pos
if atStart && l.scrollDelta < 0 || atEnd && l.scrollDelta > 0 {
l.scroll.Stop()
}
l.beforeEnd = !atEnd
dims := axisPoint(l.Axis, mainc.Constrain(pos), maxCross)
l.macro.Stop()
pointer.RectAreaOp{Rect: image.Rectangle{Max: dims}}.Add(ops)
l.scroll.Add(ops)
l.macro.Add(ops)
return Dimensions{Size: dims}
}
-116
View File
@@ -1,116 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package layout
import (
"image"
"gioui.org/ui"
)
// Stack lays out child elements on top of each other,
// according to an alignment direction.
type Stack struct {
// Alignment is the direction to align children
// smaller than the available space.
Alignment Direction
macro ui.MacroOp
constrained bool
ctx *Context
maxSZ image.Point
baseline int
}
// StackChild is the layout result of a call to End.
type StackChild struct {
macro ui.MacroOp
dims Dimensions
}
// Init a stack before calling Rigid or Expand.
func (s *Stack) Init(gtx *Context) *Stack {
s.ctx = gtx
s.constrained = true
s.maxSZ = image.Point{}
s.baseline = 0
return s
}
func (s *Stack) begin() {
if !s.constrained {
panic("must Init before adding a child")
}
s.macro.Record(s.ctx.Ops)
}
// Rigid lays out a widget with the same constraints that were
// passed to Init.
func (s *Stack) Rigid(w Widget) StackChild {
s.begin()
dims := s.ctx.Layout(s.ctx.Constraints, w)
return s.end(dims)
}
// Expand lays out a widget with constraints that exactly match
// the biggest child previously added.
func (s *Stack) Expand(w Widget) StackChild {
s.begin()
cs := Constraints{
Width: Constraint{Min: s.maxSZ.X, Max: s.maxSZ.X},
Height: Constraint{Min: s.maxSZ.Y, Max: s.maxSZ.Y},
}
dims := s.ctx.Layout(cs, w)
return s.end(dims)
}
// End a child by specifying its dimensions.
func (s *Stack) end(dims Dimensions) StackChild {
s.macro.Stop()
if w := dims.Size.X; w > s.maxSZ.X {
s.maxSZ.X = w
}
if h := dims.Size.Y; h > s.maxSZ.Y {
s.maxSZ.Y = h
}
if s.baseline == 0 {
if b := dims.Baseline; b != dims.Size.Y {
s.baseline = b
}
}
return StackChild{s.macro, dims}
}
// Layout a list of children. The order of the children determines their laid
// out order.
func (s *Stack) Layout(children ...StackChild) {
for _, ch := range children {
sz := ch.dims.Size
var p image.Point
switch s.Alignment {
case N, S, Center:
p.X = (s.maxSZ.X - sz.X) / 2
case NE, SE, E:
p.X = s.maxSZ.X - sz.X
}
switch s.Alignment {
case W, Center, E:
p.Y = (s.maxSZ.Y - sz.Y) / 2
case SW, S, SE:
p.Y = s.maxSZ.Y - sz.Y
}
var stack ui.StackOp
stack.Push(s.ctx.Ops)
ui.TransformOp{}.Offset(toPointF(p)).Add(s.ctx.Ops)
ch.macro.Add(s.ctx.Ops)
stack.Pop()
}
b := s.baseline
if b == 0 {
b = s.maxSZ.Y
}
s.ctx.Dimensions = Dimensions{
Size: s.maxSZ,
Baseline: b,
}
}
-300
View File
@@ -1,300 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
/*
Package measure implements text layout and shaping.
*/
package measure
import (
"math"
"unicode"
"unicode/utf8"
"gioui.org/ui"
"gioui.org/ui/f32"
"gioui.org/ui/paint"
"gioui.org/ui/text"
"golang.org/x/image/font"
"golang.org/x/image/font/sfnt"
"golang.org/x/image/math/fixed"
)
// Faces is a cache of text layouts and paths.
type Faces struct {
config ui.Config
faceCache map[faceKey]*Face
layoutCache map[layoutKey]cachedLayout
pathCache map[pathKey]cachedPath
}
type cachedLayout struct {
active bool
layout *text.Layout
}
type cachedPath struct {
active bool
path ui.MacroOp
}
type layoutKey struct {
f *sfnt.Font
ppem fixed.Int26_6
str string
opts text.LayoutOptions
}
type pathKey struct {
f *sfnt.Font
ppem fixed.Int26_6
str string
}
type faceKey struct {
font *sfnt.Font
size ui.Value
}
// Face is a cached implementation of text.Face.
type Face struct {
faces *Faces
size ui.Value
font *opentype
}
// Reset the cache, discarding any measures or paths that
// haven't been used since the last call to Reset.
func (f *Faces) Reset(c ui.Config) {
f.config = c
f.init()
for pk, p := range f.pathCache {
if !p.active {
delete(f.pathCache, pk)
continue
}
p.active = false
f.pathCache[pk] = p
}
for lk, l := range f.layoutCache {
if !l.active {
delete(f.layoutCache, lk)
continue
}
l.active = false
f.layoutCache[lk] = l
}
}
// For returns a Face for the given font and size.
func (f *Faces) For(fnt *sfnt.Font, size ui.Value) *Face {
f.init()
fk := faceKey{fnt, size}
if f, exist := f.faceCache[fk]; exist {
return f
}
face := &Face{
faces: f,
size: size,
font: &opentype{Font: fnt, Hinting: font.HintingFull},
}
f.faceCache[fk] = face
return face
}
func (f *Faces) init() {
if f.faceCache != nil {
return
}
f.faceCache = make(map[faceKey]*Face)
f.pathCache = make(map[pathKey]cachedPath)
f.layoutCache = make(map[layoutKey]cachedLayout)
}
func (f *Face) Layout(str string, opts text.LayoutOptions) *text.Layout {
ppem := fixed.Int26_6(f.faces.config.Px(f.size) * 64)
lk := layoutKey{
f: f.font.Font,
ppem: ppem,
str: str,
opts: opts,
}
if l, ok := f.faces.layoutCache[lk]; ok {
l.active = true
f.faces.layoutCache[lk] = l
return l.layout
}
l := layoutText(ppem, str, f.font, opts)
f.faces.layoutCache[lk] = cachedLayout{active: true, layout: l}
return l
}
func (f *Face) Path(str text.String) ui.MacroOp {
ppem := fixed.Int26_6(f.faces.config.Px(f.size) * 64)
pk := pathKey{
f: f.font.Font,
ppem: ppem,
str: str.String,
}
if p, ok := f.faces.pathCache[pk]; ok {
p.active = true
f.faces.pathCache[pk] = p
return p.path
}
p := textPath(ppem, f.font, str)
f.faces.pathCache[pk] = cachedPath{active: true, path: p}
return p
}
func layoutText(ppem fixed.Int26_6, str string, f *opentype, opts text.LayoutOptions) *text.Layout {
m := f.Metrics(ppem)
lineTmpl := text.Line{
Ascent: m.Ascent,
// m.Height is equal to m.Ascent + m.Descent + linegap.
// Compute the descent including the linegap.
Descent: m.Height - m.Ascent,
Bounds: f.Bounds(ppem),
}
var lines []text.Line
maxDotX := fixed.Int26_6(math.MaxInt32)
maxDotX = fixed.I(opts.MaxWidth)
type state struct {
r rune
advs []fixed.Int26_6
adv fixed.Int26_6
x fixed.Int26_6
idx int
valid bool
}
var prev, word state
endLine := func() {
line := lineTmpl
line.Text.Advances = prev.advs
line.Text.String = str[:prev.idx]
line.Width = prev.x + prev.adv
line.Bounds.Max.X += prev.x
lines = append(lines, line)
str = str[prev.idx:]
prev = state{}
word = state{}
}
for prev.idx < len(str) {
c, s := utf8.DecodeRuneInString(str[prev.idx:])
nl := c == '\n'
if opts.SingleLine && nl {
nl = false
c = ' '
s = 1
}
a, ok := f.GlyphAdvance(ppem, c)
if !ok {
prev.idx += s
continue
}
next := state{
r: c,
advs: prev.advs,
idx: prev.idx + s,
x: prev.x + prev.adv,
valid: true,
}
if nl {
// The newline is zero width; use the previous
// character for line measurements.
prev.advs = append(prev.advs, 0)
prev.idx = next.idx
endLine()
continue
}
next.adv = a
var k fixed.Int26_6
if prev.valid {
k = f.Kern(ppem, prev.r, next.r)
}
// Break the line if we're out of space.
if prev.idx > 0 && next.x+next.adv+k >= maxDotX {
// If the line contains no word breaks, break off the last rune.
if word.idx == 0 {
word = prev
}
next.x -= word.x + word.adv
next.idx -= word.idx
next.advs = next.advs[len(word.advs):]
prev = word
endLine()
} else {
next.adv += k
}
next.advs = append(next.advs, next.adv)
if unicode.IsSpace(next.r) {
word = next
}
prev = next
}
endLine()
return &text.Layout{Lines: lines}
}
func textPath(ppem fixed.Int26_6, f *opentype, str text.String) ui.MacroOp {
var lastPos f32.Point
var builder paint.PathBuilder
ops := new(ui.Ops)
builder.Init(ops)
var x fixed.Int26_6
var advIdx int
var m ui.MacroOp
m.Record(ops)
for _, r := range str.String {
if !unicode.IsSpace(r) {
segs, ok := f.LoadGlyph(ppem, r)
if !ok {
continue
}
// Move to glyph position.
pos := f32.Point{
X: float32(x) / 64,
}
builder.Move(pos.Sub(lastPos))
lastPos = pos
var lastArg f32.Point
// Convert sfnt.Segments to relative segments.
for _, fseg := range segs {
nargs := 1
switch fseg.Op {
case sfnt.SegmentOpQuadTo:
nargs = 2
case sfnt.SegmentOpCubeTo:
nargs = 3
}
var args [3]f32.Point
for i := 0; i < nargs; i++ {
a := f32.Point{
X: float32(fseg.Args[i].X) / 64,
Y: float32(fseg.Args[i].Y) / 64,
}
args[i] = a.Sub(lastArg)
if i == nargs-1 {
lastArg = a
}
}
switch fseg.Op {
case sfnt.SegmentOpMoveTo:
builder.Move(args[0])
case sfnt.SegmentOpLineTo:
builder.Line(args[0])
case sfnt.SegmentOpQuadTo:
builder.Quad(args[0], args[1])
case sfnt.SegmentOpCubeTo:
builder.Cube(args[0], args[1], args[2])
default:
panic("unsupported segment op")
}
}
lastPos = lastPos.Add(lastArg)
}
x += str.Advances[advIdx]
advIdx++
}
builder.End()
m.Stop()
return m
}
-63
View File
@@ -1,63 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package measure
import (
"golang.org/x/image/font"
"golang.org/x/image/font/sfnt"
"golang.org/x/image/math/fixed"
)
type opentype struct {
Font *sfnt.Font
Hinting font.Hinting
buf sfnt.Buffer
}
func (f *opentype) GlyphAdvance(ppem fixed.Int26_6, r rune) (advance fixed.Int26_6, ok bool) {
g, err := f.Font.GlyphIndex(&f.buf, r)
if err != nil {
return 0, false
}
adv, err := f.Font.GlyphAdvance(&f.buf, g, ppem, f.Hinting)
return adv, err == nil
}
func (f *opentype) Kern(ppem fixed.Int26_6, r0, r1 rune) fixed.Int26_6 {
g0, err := f.Font.GlyphIndex(&f.buf, r0)
if err != nil {
return 0
}
g1, err := f.Font.GlyphIndex(&f.buf, r1)
if err != nil {
return 0
}
adv, err := f.Font.Kern(&f.buf, g0, g1, ppem, f.Hinting)
if err != nil {
return 0
}
return adv
}
func (f *opentype) Metrics(ppem fixed.Int26_6) font.Metrics {
m, _ := f.Font.Metrics(&f.buf, ppem, f.Hinting)
return m
}
func (f *opentype) Bounds(ppem fixed.Int26_6) fixed.Rectangle26_6 {
r, _ := f.Font.Bounds(&f.buf, ppem, f.Hinting)
return r
}
func (f *opentype) LoadGlyph(ppem fixed.Int26_6, r rune) ([]sfnt.Segment, bool) {
g, err := f.Font.GlyphIndex(&f.buf, r)
if err != nil {
return nil, false
}
segs, err := f.Font.LoadGlyph(&f.buf, g, ppem, nil)
if err != nil {
return nil, false
}
return segs, true
}
+1 -1
View File
@@ -5,7 +5,7 @@ package ui
import (
"encoding/binary"
"gioui.org/ui/internal/opconst"
"gioui.org/internal/opconst"
)
// Ops holds a list of operations. Operations are stored in
-15
View File
@@ -1,15 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
/*
Package paint provides operations for 2D graphics.
The PaintOp operation draws the current material into a rectangular
area, taking the current clip path and transformation into account.
The material is set by either a ColorOp for a constant color, or
ImageOp for an image.
The ClipOp operation sets the clip path. Drawing outside the clip
path is ignored. A path is a closed shape of lines or curves.
*/
package paint
-79
View File
@@ -1,79 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package paint
import (
"encoding/binary"
"image"
"image/color"
"math"
"gioui.org/ui"
"gioui.org/ui/f32"
"gioui.org/ui/internal/opconst"
)
// ImageOp sets the material to a section of an
// image.
type ImageOp struct {
// Src is the image.
Src image.Image
// Rect defines the section of Src to use.
Rect image.Rectangle
}
// ColorOp sets the material to a constant color.
type ColorOp struct {
Color color.RGBA
}
// PaintOp draws the current material, respecting the
// clip path and transformation.
type PaintOp struct {
Rect f32.Rectangle
}
func (i ImageOp) Add(o *ui.Ops) {
data := make([]byte, opconst.TypeImageLen)
data[0] = byte(opconst.TypeImage)
bo := binary.LittleEndian
bo.PutUint32(data[1:], uint32(i.Rect.Min.X))
bo.PutUint32(data[5:], uint32(i.Rect.Min.Y))
bo.PutUint32(data[9:], uint32(i.Rect.Max.X))
bo.PutUint32(data[13:], uint32(i.Rect.Max.Y))
o.Write(data, i.Src)
}
func (c ColorOp) Add(o *ui.Ops) {
data := make([]byte, opconst.TypeColorLen)
data[0] = byte(opconst.TypeColor)
data[1] = c.Color.R
data[2] = c.Color.G
data[3] = c.Color.B
data[4] = c.Color.A
o.Write(data)
}
func (d PaintOp) Add(o *ui.Ops) {
data := make([]byte, opconst.TypePaintLen)
data[0] = byte(opconst.TypePaint)
bo := binary.LittleEndian
bo.PutUint32(data[1:], math.Float32bits(d.Rect.Min.X))
bo.PutUint32(data[5:], math.Float32bits(d.Rect.Min.Y))
bo.PutUint32(data[9:], math.Float32bits(d.Rect.Max.X))
bo.PutUint32(data[13:], math.Float32bits(d.Rect.Max.Y))
o.Write(data)
}
// RectClip returns a ClipOp corresponding to a pixel aligned
// rectangular area.
func RectClip(r image.Rectangle) ClipOp {
return ClipOp{bounds: toRectF(r)}
}
func toRectF(r image.Rectangle) f32.Rectangle {
return f32.Rectangle{
Min: f32.Point{X: float32(r.Min.X), Y: float32(r.Min.Y)},
Max: f32.Point{X: float32(r.Max.X), Y: float32(r.Max.Y)},
}
}
-292
View File
@@ -1,292 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package paint
import (
"encoding/binary"
"math"
"unsafe"
"gioui.org/ui"
"gioui.org/ui/f32"
"gioui.org/ui/internal/opconst"
"gioui.org/ui/internal/path"
)
// PathBuilder builds and adds a general ClipOp clip path
// from lines and curves.
// PathBuilder generates no garbage and can be used for
// dynamic paths; path data is stored directly in the Ops
// list supplied to Init.
type PathBuilder struct {
ops *ui.Ops
firstVert int
nverts int
maxy float32
pen f32.Point
bounds f32.Rectangle
hasBounds bool
}
// ClipOp sets the current clip path.
type ClipOp struct {
bounds f32.Rectangle
}
func (p ClipOp) Add(o *ui.Ops) {
data := make([]byte, opconst.TypeClipLen)
data[0] = byte(opconst.TypeClip)
bo := binary.LittleEndian
bo.PutUint32(data[1:], math.Float32bits(p.bounds.Min.X))
bo.PutUint32(data[5:], math.Float32bits(p.bounds.Min.Y))
bo.PutUint32(data[9:], math.Float32bits(p.bounds.Max.X))
bo.PutUint32(data[13:], math.Float32bits(p.bounds.Max.Y))
o.Write(data)
}
// Init the builder and specify the operations list for
// storing the path data and final ClipOp.
func (p *PathBuilder) Init(ops *ui.Ops) {
p.ops = ops
}
// MoveTo moves the pen to the given position.
func (p *PathBuilder) Move(to f32.Point) {
p.end()
to = to.Add(p.pen)
p.maxy = to.Y
p.pen = to
}
// end completes the current contour.
func (p *PathBuilder) end() {
aux := p.ops.Aux()
bo := binary.LittleEndian
// Fill in maximal Y coordinates of the NW and NE corners.
for i := p.firstVert; i < p.nverts; i++ {
off := path.VertStride*i + int(unsafe.Offsetof(((*path.Vertex)(nil)).MaxY))
bo.PutUint32(aux[off:], math.Float32bits(p.maxy))
}
p.firstVert = p.nverts
}
// Line records a line from the pen to end.
func (p *PathBuilder) Line(to f32.Point) {
to = to.Add(p.pen)
p.lineTo(to)
}
func (p *PathBuilder) lineTo(to f32.Point) {
// Model lines as degenerate quadratic Béziers.
p.quadTo(to.Add(p.pen).Mul(.5), to)
}
// Quad records a quadratic Bézier from the pen to end
// with the control point ctrl.
func (p *PathBuilder) Quad(ctrl, to f32.Point) {
ctrl = ctrl.Add(p.pen)
to = to.Add(p.pen)
p.quadTo(ctrl, to)
}
func (p *PathBuilder) quadTo(ctrl, to f32.Point) {
// Zero width curves don't contribute to stenciling.
if p.pen.X == to.X && p.pen.X == ctrl.X {
p.pen = to
return
}
bounds := f32.Rectangle{
Min: p.pen,
Max: to,
}.Canon()
// If the curve contain areas where a vertical line
// intersects it twice, split the curve in two x monotone
// lower and upper curves. The stencil fragment program
// expects only one intersection per curve.
// Find the t where the derivative in x is 0.
v0 := ctrl.Sub(p.pen)
v1 := to.Sub(ctrl)
d := v0.X - v1.X
// t = v0 / d. Split if t is in ]0;1[.
if v0.X > 0 && d > v0.X || v0.X < 0 && d < v0.X {
t := v0.X / d
ctrl0 := p.pen.Mul(1 - t).Add(ctrl.Mul(t))
ctrl1 := ctrl.Mul(1 - t).Add(to.Mul(t))
mid := ctrl0.Mul(1 - t).Add(ctrl1.Mul(t))
p.simpleQuadTo(ctrl0, mid)
p.simpleQuadTo(ctrl1, to)
if mid.X > bounds.Max.X {
bounds.Max.X = mid.X
}
if mid.X < bounds.Min.X {
bounds.Min.X = mid.X
}
} else {
p.simpleQuadTo(ctrl, to)
}
// Find the y extremum, if any.
d = v0.Y - v1.Y
if v0.Y > 0 && d > v0.Y || v0.Y < 0 && d < v0.Y {
t := v0.Y / d
y := (1-t)*(1-t)*p.pen.Y + 2*(1-t)*t*ctrl.Y + t*t*to.Y
if y > bounds.Max.Y {
bounds.Max.Y = y
}
if y < bounds.Min.Y {
bounds.Min.Y = y
}
}
p.expand(bounds)
}
// Cube records a cubic Bézier from the pen through
// two control points ending in to.
func (p *PathBuilder) Cube(ctrl0, ctrl1, to f32.Point) {
ctrl0 = ctrl0.Add(p.pen)
ctrl1 = ctrl1.Add(p.pen)
to = to.Add(p.pen)
// Set the maximum distance proportionally to the longest side
// of the bounding rectangle.
hull := f32.Rectangle{
Min: p.pen,
Max: ctrl0,
}.Canon().Add(ctrl1).Add(to)
l := hull.Dx()
if h := hull.Dy(); h > l {
l = h
}
p.approxCubeTo(0, l*0.001, ctrl0, ctrl1, to)
}
// approxCube approximates a cubic Bézier by a series of quadratic
// curves.
func (p *PathBuilder) approxCubeTo(splits int, maxDist float32, ctrl0, ctrl1, to f32.Point) int {
// The idea is from
// https://caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html
// where a quadratic approximates a cubic by eliminating its t³ term
// from its polynomial expression anchored at the starting point:
//
// P(t) = pen + 3t(ctrl0 - pen) + 3t²(ctrl1 - 2ctrl0 + pen) + t³(to - 3ctrl1 + 3ctrl0 - pen)
//
// The control point for the new quadratic Q1 that shares starting point, pen, with P is
//
// C1 = (3ctrl0 - pen)/2
//
// The reverse cubic anchored at the end point has the polynomial
//
// P'(t) = to + 3t(ctrl1 - to) + 3t²(ctrl0 - 2ctrl1 + to) + t³(pen - 3ctrl0 + 3ctrl1 - to)
//
// The corresponding quadratic Q2 that shares the end point, to, with P has control
// point
//
// C2 = (3ctrl1 - to)/2
//
// The combined quadratic Bézier, Q, shares both start and end points with its cubic
// and use the midpoint between the two curves Q1 and Q2 as control point:
//
// C = (3ctrl0 - pen + 3ctrl1 - to)/4
c := ctrl0.Mul(3).Sub(p.pen).Add(ctrl1.Mul(3)).Sub(to).Mul(1.0 / 4.0)
const maxSplits = 32
if splits >= maxSplits {
p.quadTo(c, to)
return splits
}
// The maximum distance between the cubic P and its approximation Q given t
// can be shown to be
//
// d = sqrt(3)/36*|to - 3ctrl1 + 3ctrl0 - pen|
//
// To save a square root, compare d² with the squared tolerance.
v := to.Sub(ctrl1.Mul(3)).Add(ctrl0.Mul(3)).Sub(p.pen)
d2 := (v.X*v.X + v.Y*v.Y) * 3 / (36 * 36)
if d2 <= maxDist*maxDist {
p.quadTo(c, to)
return splits
}
// De Casteljau split the curve and approximate the halves.
t := float32(0.5)
c0 := p.pen.Add(ctrl0.Sub(p.pen).Mul(t))
c1 := ctrl0.Add(ctrl1.Sub(ctrl0).Mul(t))
c2 := ctrl1.Add(to.Sub(ctrl1).Mul(t))
c01 := c0.Add(c1.Sub(c0).Mul(t))
c12 := c1.Add(c2.Sub(c1).Mul(t))
c0112 := c01.Add(c12.Sub(c01).Mul(t))
splits++
splits = p.approxCubeTo(splits, maxDist, c0, c01, c0112)
splits = p.approxCubeTo(splits, maxDist, c12, c2, to)
return splits
}
func (p *PathBuilder) expand(b f32.Rectangle) {
if !p.hasBounds {
p.hasBounds = true
inf := float32(math.Inf(+1))
p.bounds = f32.Rectangle{
Min: f32.Point{X: inf, Y: inf},
Max: f32.Point{X: -inf, Y: -inf},
}
}
p.bounds = p.bounds.Union(b)
}
func (p *PathBuilder) vertex(cornerx, cornery int16, ctrl, to f32.Point) {
p.nverts++
v := path.Vertex{
CornerX: cornerx,
CornerY: cornery,
FromX: p.pen.X,
FromY: p.pen.Y,
CtrlX: ctrl.X,
CtrlY: ctrl.Y,
ToX: to.X,
ToY: to.Y,
}
data := make([]byte, path.VertStride+1)
data[0] = byte(opconst.TypeAux)
bo := binary.LittleEndian
data[1] = byte(uint16(v.CornerX))
data[2] = byte(uint16(v.CornerX) >> 8)
data[3] = byte(uint16(v.CornerY))
data[4] = byte(uint16(v.CornerY) >> 8)
bo.PutUint32(data[5:], math.Float32bits(v.MaxY))
bo.PutUint32(data[9:], math.Float32bits(v.FromX))
bo.PutUint32(data[13:], math.Float32bits(v.FromY))
bo.PutUint32(data[17:], math.Float32bits(v.CtrlX))
bo.PutUint32(data[21:], math.Float32bits(v.CtrlY))
bo.PutUint32(data[25:], math.Float32bits(v.ToX))
bo.PutUint32(data[29:], math.Float32bits(v.ToY))
p.ops.Write(data)
}
func (p *PathBuilder) simpleQuadTo(ctrl, to f32.Point) {
if p.pen.Y > p.maxy {
p.maxy = p.pen.Y
}
if ctrl.Y > p.maxy {
p.maxy = ctrl.Y
}
if to.Y > p.maxy {
p.maxy = to.Y
}
// NW.
p.vertex(-1, 1, ctrl, to)
// NE.
p.vertex(1, 1, ctrl, to)
// SW.
p.vertex(-1, -1, ctrl, to)
// SE.
p.vertex(1, -1, ctrl, to)
p.pen = to
}
// End the path and add the resulting ClipOp to
// the operation list passed to Init.
func (p *PathBuilder) End() {
p.end()
ClipOp{
bounds: p.bounds,
}.Add(p.ops)
}
-117
View File
@@ -1,117 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
/*
Package pointer implements pointer events and operations.
A pointer is either a mouse controlled cursor or a touch
object such as a finger.
The InputOp operation is used to declare a handler ready for pointer
events. Use a ui.Queue to receive events.
Areas
The area operations are used for specifying the area where
subsequent InputOp are active.
For example, to set up a rectangular hit area:
var ops ui.Ops
var h *Handler = ...
r := image.Rectangle{...}
pointer.RectAreaOp{Rect: r}.Add(ops)
pointer.InputOp{Key: h}.Add(ops)
Note that areas compound: the effective area of multiple area
operations is the intersection of the areas.
Matching events
StackOp operations and input handlers form an implicit tree.
Each stack operation is a node, and each input handler is associated
with the most recent node.
For example:
ops := new(ui.Ops)
var stack ui.StackOp
var h1, h2 *Handler
stack.Push(ops)
pointer.InputOp{Key: h1}.Add(Ops)
stack.Pop()
stack.Push(ops)
pointer.InputOp{Key: h2}.Add(ops)
stack.Pop()
implies a tree of two inner nodes, each with one pointer handler.
When determining which handlers match an Event, only handlers whose
areas contain the event position are considered. The matching
proceeds as follows.
First, the foremost matching handler is included. If the handler
has pass-through enabled, this step is repeated.
Then, all matching handlers from the current node and all parent
nodes are included.
In the example above, all events will go to h2 only even though both
handlers have the same area (the entire screen).
Pass-through
The PassOp operations controls the pass-through setting. A handler's
pass-through setting is recorded along with the InputOp.
Pass-through handlers are useful for overlay widgets such as a hidden
side drawer. When the user touches the side, both the (transparent)
drawer handle and the interface below should receive pointer events.
Disambiguation
When more than one handler matches a pointer event, the event queue
follows a set of rules for distributing the event.
As long as the pointer has not received a Press event, all
matching handlers receive all events.
When a pointer is pressed, the set of matching handlers is
recorded. The set is not updated according to the pointer position
and hit areas. Rather, handlers stay in the matching set until they
no longer appear in a InputOp or when another handler in the set
grabs the pointer.
A handler can exclude all other handler from its matching sets
by setting the Grab flag in its InputOp. The Grab flag is sticky
and stays in effect until the handler no longer appears in any
matching sets.
The losing handlers are notified by a Cancel event.
For multiple grabbing handlers, the foremost handler wins.
Priorities
Handlers know their position in a matching set of a pointer through
event priorities. The Shared and Foremost priorities are for matching sets
with multiple handlers; the Grabbed priority indicate exclusive access.
Priorities are useful for deferred gesture matching.
Consider a scrollable list of clickable elements. When the user touches an
element, it is unknown whether the gesture is a click on the element
or a drag (scroll) of the list. While the click handler might light up
the element in anticipation of a click, the scrolling handler does not
scroll on finger movements with lower than Grabbed priority.
Should the user release the finger, the click handler registers a click.
However, if the finger moves beyond a threshold, the scrolling handler
determines that the gesture is a drag and sets its Grab flag. The
click handler receives a Cancel (removing the highlight) and further
movements for the scroll handler has priority Grabbed, scrolling the
list.
*/
package pointer
-209
View File
@@ -1,209 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package pointer
import (
"encoding/binary"
"image"
"time"
"gioui.org/ui"
"gioui.org/ui/f32"
"gioui.org/ui/internal/opconst"
)
// Event is a pointer event.
type Event struct {
Type Type
Source Source
// PointerID is the id for the pointer and can be used
// to track a particular pointer from Press to
// Release or Cancel.
PointerID ID
// Priority is the priority of the receiving handler
// for this event.
Priority Priority
// Time is when the event was received. The
// timestamp is relative to an undefined base.
Time time.Duration
// Hit is set when the event was within the registered
// area for the handler. Hit can be false when a pointer
// was pressed within the hit area, and then dragged
// outside it.
Hit bool
// Position is the position of the event, relative to
// the current transformation, as set by ui.TransformOp.
Position f32.Point
// Scroll is the scroll amount, if any.
Scroll f32.Point
}
// RectAreaOp updates the hit area to the intersection
// of the current hit area with a rectangular area.
type RectAreaOp struct {
// Rect defines the rectangle. The current transform
// is applied to it.
Rect image.Rectangle
}
// EllipseAreaOp updates the hit area to the intersection
// of the current hit area with an elliptical area.
type EllipseAreaOp struct {
// Rect is the bounds for the ellipse. The current transform
// is applied to the rectangle.
Rect image.Rectangle
}
// Must match the structure in input.areaOp
type areaOp struct {
kind areaKind
rect image.Rectangle
}
// InputOp declares an input handler ready for pointer
// events.
type InputOp struct {
Key ui.Key
// Grab, if set, request that the handler get
// Grabbed priority.
Grab bool
}
// PassOp sets the pass-through mode.
type PassOp struct {
Pass bool
}
type ID uint16
// Type of an Event.
type Type uint8
// Priority of an Event.
type Priority uint8
// Source of an Event.
type Source uint8
// Must match app/internal/input.areaKind
type areaKind uint8
const (
// A Cancel event is generated when the current gesture is
// interrupted by other handlers or the system.
Cancel Type = iota
// Press of a pointer.
Press
// Release of a pointer.
Release
// Move of a pointer.
Move
)
const (
// Mouse generated event.
Mouse Source = iota
// Touch generated event.
Touch
)
const (
// Shared priority is for handlers that
// are part of a matching set larger than 1.
Shared Priority = iota
// Foremost is like Shared, but the handler is
// the foremost in the matching set.
Foremost
// Grabbed is used for matching sets of size 1.
Grabbed
)
const (
areaRect areaKind = iota
areaEllipse
)
func (op RectAreaOp) Add(ops *ui.Ops) {
areaOp{
kind: areaRect,
rect: op.Rect,
}.add(ops)
}
func (op EllipseAreaOp) Add(ops *ui.Ops) {
areaOp{
kind: areaEllipse,
rect: op.Rect,
}.add(ops)
}
func (op areaOp) add(o *ui.Ops) {
data := make([]byte, opconst.TypeAreaLen)
data[0] = byte(opconst.TypeArea)
data[1] = byte(op.kind)
bo := binary.LittleEndian
bo.PutUint32(data[2:], uint32(op.rect.Min.X))
bo.PutUint32(data[6:], uint32(op.rect.Min.Y))
bo.PutUint32(data[10:], uint32(op.rect.Max.X))
bo.PutUint32(data[14:], uint32(op.rect.Max.Y))
o.Write(data)
}
func (h InputOp) Add(o *ui.Ops) {
data := make([]byte, opconst.TypePointerInputLen)
data[0] = byte(opconst.TypePointerInput)
if h.Grab {
data[1] = 1
}
o.Write(data, h.Key)
}
func (op PassOp) Add(o *ui.Ops) {
data := make([]byte, opconst.TypePassLen)
data[0] = byte(opconst.TypePass)
if op.Pass {
data[1] = 1
}
o.Write(data)
}
func (t Type) String() string {
switch t {
case Press:
return "Press"
case Release:
return "Release"
case Cancel:
return "Cancel"
case Move:
return "Move"
default:
panic("unknown Type")
}
}
func (p Priority) String() string {
switch p {
case Shared:
return "Shared"
case Foremost:
return "Foremost"
case Grabbed:
return "Grabbed"
default:
panic("unknown priority")
}
}
func (s Source) String() string {
switch s {
case Mouse:
return "Mouse"
case Touch:
return "Touch"
default:
panic("unknown source")
}
}
func (Event) ImplementsEvent() {}
-31
View File
@@ -1,31 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
// Package system contain ops and types for
// system events.
package system
import (
"gioui.org/ui"
"gioui.org/ui/internal/opconst"
)
// ProfileOp registers a handler for receiving
// ProfileEvents.
type ProfileOp struct {
Key ui.Key
}
// ProfileEvent contain profile data from a single
// rendered frame.
type ProfileEvent struct {
// String with timings. Very likely to change.
Timings string
}
func (p ProfileOp) Add(o *ui.Ops) {
data := make([]byte, opconst.TypeProfileLen)
data[0] = byte(opconst.TypeProfile)
o.Write(data, p.Key)
}
func (p ProfileEvent) ImplementsEvent() {}
-168
View File
@@ -1,168 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package text
import (
"fmt"
"io"
"strings"
"unicode/utf8"
)
const bufferDebug = false
// editBuffer implements a gap buffer for text editing.
type editBuffer struct {
// caret is the caret position in bytes.
caret int
// pos is the byte position for Read and ReadRune.
pos int
// The gap start and end in bytes.
gapstart, gapend int
text []byte
// changed tracks whether the buffer content
// has changed since the last call to Changed.
changed bool
}
const minSpace = 5
func (e *editBuffer) Changed() bool {
c := e.changed
e.changed = false
return c
}
func (e *editBuffer) deleteRuneForward() {
e.moveGap(0)
_, s := utf8.DecodeRune(e.text[e.gapend:])
e.gapend += s
e.changed = e.changed || s > 0
e.dump()
}
func (e *editBuffer) deleteRune() {
e.moveGap(0)
_, s := utf8.DecodeLastRune(e.text[:e.gapstart])
e.gapstart -= s
e.caret -= s
e.changed = e.changed || s > 0
e.dump()
}
// moveGap moves the gap to the caret position. After returning,
// the gap is guaranteed to be at least space bytes long.
func (e *editBuffer) moveGap(space int) {
if e.gapLen() < space {
if space < minSpace {
space = minSpace
}
txt := make([]byte, e.len()+space)
// Expand to capacity.
txt = txt[:cap(txt)]
gaplen := len(txt) - e.len()
if e.caret > e.gapstart {
copy(txt, e.text[:e.gapstart])
copy(txt[e.caret+gaplen:], e.text[e.caret:])
copy(txt[e.gapstart:], e.text[e.gapend:e.caret+e.gapLen()])
} else {
copy(txt, e.text[:e.caret])
copy(txt[e.gapstart+gaplen:], e.text[e.gapend:])
copy(txt[e.caret+gaplen:], e.text[e.caret:e.gapstart])
}
e.text = txt
e.gapstart = e.caret
e.gapend = e.gapstart + gaplen
} else {
if e.caret > e.gapstart {
copy(e.text[e.gapstart:], e.text[e.gapend:e.caret+e.gapLen()])
} else {
copy(e.text[e.caret+e.gapLen():], e.text[e.caret:e.gapstart])
}
l := e.gapLen()
e.gapstart = e.caret
e.gapend = e.gapstart + l
}
e.dump()
}
func (e *editBuffer) len() int {
return len(e.text) - e.gapLen()
}
func (e *editBuffer) gapLen() int {
return e.gapend - e.gapstart
}
func (e *editBuffer) Read(p []byte) (int, error) {
if e.pos == e.len() {
return 0, io.EOF
}
var n int
if e.pos < e.gapstart {
n += copy(p, e.text[e.pos:e.gapstart])
p = p[n:]
}
n += copy(p, e.text[e.gapend:])
e.pos += n
return n, nil
}
func (e *editBuffer) ReadRune() (rune, int, error) {
if e.pos == e.len() {
return 0, 0, io.EOF
}
r, s := e.runeAt(e.pos)
e.pos += s
return r, s, nil
}
func (e *editBuffer) String() string {
var b strings.Builder
b.Grow(e.len())
b.Write(e.text[:e.gapstart])
b.Write(e.text[e.gapend:])
return b.String()
}
func (e *editBuffer) prepend(s string) {
e.moveGap(len(s))
copy(e.text[e.caret:], s)
e.gapstart += len(s)
e.changed = e.changed || len(s) > 0
e.dump()
}
func (e *editBuffer) dump() {
if bufferDebug {
fmt.Printf("len(e.text) %d e.len() %d e.gapstart %d e.gapend %d e.caret %d txt:\n'%+x'<-%d->'%+x'\n", len(e.text), e.len(), e.gapstart, e.gapend, e.caret, e.text[:e.gapstart], e.gapLen(), e.text[e.gapend:])
}
}
func (e *editBuffer) moveLeft() {
_, s := e.runeBefore(e.caret)
e.caret -= s
e.dump()
}
func (e *editBuffer) moveRight() {
_, s := e.runeAt(e.caret)
e.caret += s
e.dump()
}
func (e *editBuffer) runeBefore(idx int) (rune, int) {
if idx >= e.gapstart {
idx += e.gapLen()
}
return utf8.DecodeLastRune(e.text[:idx])
}
func (e *editBuffer) runeAt(idx int) (rune, int) {
if idx >= e.gapstart {
idx += e.gapLen()
}
return utf8.DecodeRune(e.text[idx:])
}
-622
View File
@@ -1,622 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package text
import (
"image"
"image/color"
"math"
"time"
"unicode/utf8"
"gioui.org/ui"
"gioui.org/ui/gesture"
"gioui.org/ui/key"
"gioui.org/ui/layout"
"gioui.org/ui/paint"
"gioui.org/ui/pointer"
"golang.org/x/image/math/fixed"
)
// Editor implements an editable and scrollable text area.
type Editor struct {
Face Face
Alignment Alignment
// SingleLine force the text to stay on a single line.
// SingleLine also sets the scrolling direction to
// horizontal.
SingleLine bool
// Submit enabled translation of carriage return keys to SubmitEvents.
// If not enabled, carriage returns are inserted as newlines in the text.
Submit bool
// Material for drawing the text.
Material ui.MacroOp
// Hint contains the text displayed to the user when the
// Editor is empty.
Hint string
// Mmaterial is used to draw the hint.
HintMaterial ui.MacroOp
oldScale int
blinkStart time.Time
focused bool
rr editBuffer
maxWidth int
viewSize image.Point
valid bool
lines []Line
dims layout.Dimensions
padTop, padBottom int
padLeft, padRight int
requestFocus bool
it lineIterator
// carXOff is the offset to the current caret
// position when moving between lines.
carXOff fixed.Int26_6
scroller gesture.Scroll
scrollOff image.Point
clicker gesture.Click
// events is the list of events not yet processed.
events []ui.Event
}
type EditorEvent interface {
isEditorEvent()
}
// A ChangeEvent is generated for every user change to the text.
type ChangeEvent struct{}
// A SubmitEvent is generated when Submit is set
// and a carriage return key is pressed.
type SubmitEvent struct{}
const (
blinksPerSecond = 1
maxBlinkDuration = 10 * time.Second
)
// Event returns the next available editor event, or false if none are available.
func (e *Editor) Event(gtx *layout.Context) (EditorEvent, bool) {
// Crude configuration change detection.
if scale := gtx.Px(ui.Sp(100)); scale != e.oldScale {
e.invalidate()
e.oldScale = scale
}
sbounds := e.scrollBounds()
var smin, smax int
var axis gesture.Axis
if e.SingleLine {
axis = gesture.Horizontal
smin, smax = sbounds.Min.X, sbounds.Max.X
} else {
axis = gesture.Vertical
smin, smax = sbounds.Min.Y, sbounds.Max.Y
}
sdist := e.scroller.Scroll(gtx.Config, gtx.Queue, axis)
var soff int
if e.SingleLine {
e.scrollOff.X += sdist
soff = e.scrollOff.X
} else {
e.scrollOff.Y += sdist
soff = e.scrollOff.Y
}
for _, evt := range e.clicker.Events(gtx.Queue) {
switch {
case evt.Type == gesture.TypePress && evt.Source == pointer.Mouse,
evt.Type == gesture.TypeClick && evt.Source == pointer.Touch:
e.blinkStart = gtx.Now()
e.moveCoord(image.Point{
X: int(math.Round(float64(evt.Position.X))),
Y: int(math.Round(float64(evt.Position.Y))),
})
e.requestFocus = true
if e.scroller.State() != gesture.StateFlinging {
e.scrollToCaret(gtx)
}
}
}
if (sdist > 0 && soff >= smax) || (sdist < 0 && soff <= smin) {
e.scroller.Stop()
}
e.events = append(e.events, gtx.Queue.Events(e)...)
return e.editorEvent(gtx)
}
func (e *Editor) editorEvent(gtx *layout.Context) (EditorEvent, bool) {
for len(e.events) > 0 {
ke := e.events[0]
copy(e.events, e.events[1:])
e.events = e.events[:len(e.events)-1]
e.blinkStart = gtx.Now()
switch ke := ke.(type) {
case key.FocusEvent:
e.focused = ke.Focus
case key.Event:
if !e.focused {
break
}
if e.Submit && ke.Name == key.NameReturn || ke.Name == key.NameEnter {
if !ke.Modifiers.Contain(key.ModShift) {
return SubmitEvent{}, true
}
}
if e.command(ke) {
e.scrollToCaret(gtx.Config)
e.scroller.Stop()
}
case key.EditEvent:
e.scrollToCaret(gtx)
e.scroller.Stop()
e.append(ke.Text)
}
if e.rr.Changed() {
return ChangeEvent{}, true
}
}
return nil, false
}
func (e *Editor) caretWidth(c ui.Config) fixed.Int26_6 {
oneDp := c.Px(ui.Dp(1))
return fixed.Int26_6(oneDp * 64)
}
// Focus requests the input focus for the Editor.
func (e *Editor) Focus() {
e.requestFocus = true
}
// Layout flushes any remaining events and lays out the editor.
func (e *Editor) Layout(gtx *layout.Context) {
cs := gtx.Constraints
for _, ok := e.Event(gtx); ok; _, ok = e.Event(gtx) {
}
twoDp := gtx.Px(ui.Dp(2))
e.padLeft, e.padRight = twoDp, twoDp
maxWidth := cs.Width.Max
if e.SingleLine {
maxWidth = inf
}
if maxWidth != inf {
maxWidth -= e.padLeft + e.padRight
}
if maxWidth != e.maxWidth {
e.maxWidth = maxWidth
e.invalidate()
}
e.layout()
lines, size := e.lines, e.dims.Size
e.viewSize = cs.Constrain(size)
carLine, _, carX, carY := e.layoutCaret()
off := image.Point{
X: -e.scrollOff.X + e.padLeft,
Y: -e.scrollOff.Y + e.padTop,
}
clip := image.Rectangle{
Min: image.Point{X: 0, Y: 0},
Max: image.Point{X: e.viewSize.X, Y: e.viewSize.Y},
}
key.InputOp{Key: e, Focus: e.requestFocus}.Add(gtx.Ops)
e.requestFocus = false
e.it = lineIterator{
Lines: lines,
Clip: clip,
Alignment: e.Alignment,
Width: e.viewWidth(),
Offset: off,
}
var stack ui.StackOp
stack.Push(gtx.Ops)
// Apply material. Set a default color in case the material is empty.
if e.rr.len() > 0 {
paint.ColorOp{Color: color.RGBA{A: 0xff}}.Add(gtx.Ops)
e.Material.Add(gtx.Ops)
} else {
paint.ColorOp{Color: color.RGBA{A: 0xaa}}.Add(gtx.Ops)
e.HintMaterial.Add(gtx.Ops)
}
for {
str, lineOff, ok := e.it.Next()
if !ok {
break
}
var stack ui.StackOp
stack.Push(gtx.Ops)
ui.TransformOp{}.Offset(lineOff).Add(gtx.Ops)
e.Face.Path(str).Add(gtx.Ops)
paint.PaintOp{Rect: toRectF(clip).Sub(lineOff)}.Add(gtx.Ops)
stack.Pop()
}
if e.focused {
now := gtx.Now()
dt := now.Sub(e.blinkStart)
blinking := dt < maxBlinkDuration
const timePerBlink = time.Second / blinksPerSecond
nextBlink := now.Add(timePerBlink/2 - dt%(timePerBlink/2))
on := !blinking || dt%timePerBlink < timePerBlink/2
if on {
carWidth := e.caretWidth(gtx)
carX -= carWidth / 2
carAsc, carDesc := -lines[carLine].Bounds.Min.Y, lines[carLine].Bounds.Max.Y
carRect := image.Rectangle{
Min: image.Point{X: carX.Ceil(), Y: carY - carAsc.Ceil()},
Max: image.Point{X: carX.Ceil() + carWidth.Ceil(), Y: carY + carDesc.Ceil()},
}
carRect = carRect.Add(image.Point{
X: -e.scrollOff.X + e.padLeft,
Y: -e.scrollOff.Y + e.padTop,
})
carRect = clip.Intersect(carRect)
if !carRect.Empty() {
paint.ColorOp{Color: color.RGBA{A: 0xff}}.Add(gtx.Ops)
e.Material.Add(gtx.Ops)
paint.PaintOp{Rect: toRectF(carRect)}.Add(gtx.Ops)
}
}
if blinking {
redraw := ui.InvalidateOp{At: nextBlink}
redraw.Add(gtx.Ops)
}
}
stack.Pop()
baseline := e.padTop + e.dims.Baseline
pointerPadding := gtx.Px(ui.Dp(4))
r := image.Rectangle{Max: e.viewSize}
r.Min.X -= pointerPadding
r.Min.Y -= pointerPadding
r.Max.X += pointerPadding
r.Max.X += pointerPadding
pointer.RectAreaOp{Rect: r}.Add(gtx.Ops)
e.scroller.Add(gtx.Ops)
e.clicker.Add(gtx.Ops)
gtx.Dimensions = layout.Dimensions{Size: e.viewSize, Baseline: baseline}
}
// Text returns the contents of the editor.
func (e *Editor) Text() string {
return e.rr.String()
}
// SetText replaces the contents of the editor.
func (e *Editor) SetText(s string) {
e.rr = editBuffer{}
e.carXOff = 0
e.prepend(s)
}
func (e *Editor) layout() {
e.adjustScroll()
if e.valid {
return
}
e.layoutText()
e.valid = true
}
func (e *Editor) scrollBounds() image.Rectangle {
var b image.Rectangle
if e.SingleLine {
if len(e.lines) > 0 {
b.Min.X = align(e.Alignment, e.lines[0].Width, e.viewWidth()).Floor()
if b.Min.X > 0 {
b.Min.X = 0
}
}
b.Max.X = e.dims.Size.X + b.Min.X - e.viewSize.X
} else {
b.Max.Y = e.dims.Size.Y - e.viewSize.Y
}
return b
}
func (e *Editor) adjustScroll() {
b := e.scrollBounds()
if e.scrollOff.X > b.Max.X {
e.scrollOff.X = b.Max.X
}
if e.scrollOff.X < b.Min.X {
e.scrollOff.X = b.Min.X
}
if e.scrollOff.Y > b.Max.Y {
e.scrollOff.Y = b.Max.Y
}
if e.scrollOff.Y < b.Min.Y {
e.scrollOff.Y = b.Min.Y
}
}
func (e *Editor) moveCoord(pos image.Point) {
e.layout()
var (
prevDesc fixed.Int26_6
carLine int
y int
)
for _, l := range e.lines {
y += (prevDesc + l.Ascent).Ceil()
prevDesc = l.Descent
if y+prevDesc.Ceil() >= pos.Y+e.scrollOff.Y-e.padTop {
break
}
carLine++
}
x := fixed.I(pos.X + e.scrollOff.X - e.padLeft)
e.moveToLine(x, carLine)
}
func (e *Editor) layoutText() {
s := e.rr.String()
if s == "" {
s = e.Hint
}
textLayout := e.Face.Layout(s, LayoutOptions{SingleLine: e.SingleLine, MaxWidth: e.maxWidth})
lines := textLayout.Lines
dims := linesDimens(lines)
for i := 0; i < len(lines)-1; i++ {
s := lines[i].Text.String
// To avoid layout flickering while editing, assume a soft newline takes
// up all available space.
if len(s) > 0 {
r, _ := utf8.DecodeLastRuneInString(s)
if r != '\n' {
dims.Size.X = e.maxWidth
break
}
}
}
padTop, padBottom := textPadding(lines)
dims.Size.Y += padTop + padBottom
dims.Size.X += e.padLeft + e.padRight
e.padTop = padTop
e.padBottom = padBottom
e.lines, e.dims = lines, dims
}
func (e *Editor) viewWidth() int {
return e.viewSize.X - e.padLeft - e.padRight
}
func (e *Editor) layoutCaret() (carLine, carCol int, x fixed.Int26_6, y int) {
e.layout()
var idx int
var prevDesc fixed.Int26_6
loop:
for carLine = 0; carLine < len(e.lines); carLine++ {
l := e.lines[carLine]
y += (prevDesc + l.Ascent).Ceil()
prevDesc = l.Descent
if carLine == len(e.lines)-1 || idx+len(l.Text.String) > e.rr.caret {
str := l.Text.String
for _, adv := range l.Text.Advances {
if idx == e.rr.caret {
break loop
}
x += adv
_, s := utf8.DecodeRuneInString(str)
idx += s
str = str[s:]
carCol++
}
break
}
idx += len(l.Text.String)
}
x += align(e.Alignment, e.lines[carLine].Width, e.viewWidth())
return
}
func (e *Editor) invalidate() {
e.valid = false
}
func (e *Editor) deleteRune() {
e.rr.deleteRune()
e.carXOff = 0
e.invalidate()
}
func (e *Editor) deleteRuneForward() {
e.rr.deleteRuneForward()
e.carXOff = 0
e.invalidate()
}
func (e *Editor) append(s string) {
if e.SingleLine && s == "\n" {
return
}
e.prepend(s)
e.rr.caret += len(s)
}
func (e *Editor) prepend(s string) {
e.rr.prepend(s)
e.carXOff = 0
e.invalidate()
}
func (e *Editor) movePages(pages int) {
e.layout()
_, _, carX, carY := e.layoutCaret()
y := carY + pages*e.viewSize.Y
var (
prevDesc fixed.Int26_6
carLine2 int
)
y2 := e.lines[0].Ascent.Ceil()
for i := 1; i < len(e.lines); i++ {
if y2 >= y {
break
}
l := e.lines[i]
h := (prevDesc + l.Ascent).Ceil()
prevDesc = l.Descent
if y2+h-y >= y-y2 {
break
}
y2 += h
carLine2++
}
e.carXOff = e.moveToLine(carX+e.carXOff, carLine2)
}
func (e *Editor) moveToLine(carX fixed.Int26_6, carLine2 int) fixed.Int26_6 {
e.layout()
carLine, carCol, _, _ := e.layoutCaret()
if carLine2 < 0 {
carLine2 = 0
}
if carLine2 >= len(e.lines) {
carLine2 = len(e.lines) - 1
}
// Move to start of line.
for i := carCol - 1; i >= 0; i-- {
_, s := e.rr.runeBefore(e.rr.caret)
e.rr.caret -= s
}
if carLine2 != carLine {
// Move to start of line2.
if carLine2 > carLine {
for i := carLine; i < carLine2; i++ {
e.rr.caret += len(e.lines[i].Text.String)
}
} else {
for i := carLine - 1; i >= carLine2; i-- {
e.rr.caret -= len(e.lines[i].Text.String)
}
}
}
l2 := e.lines[carLine2]
carX2 := align(e.Alignment, l2.Width, e.viewWidth())
// Only move past the end of the last line
end := 0
if carLine2 < len(e.lines)-1 {
end = 1
}
// Move to rune closest to previous horizontal position.
for i := 0; i < len(l2.Text.Advances)-end; i++ {
adv := l2.Text.Advances[i]
if carX2 >= carX {
break
}
if carX2+adv-carX >= carX-carX2 {
break
}
carX2 += adv
_, s := e.rr.runeAt(e.rr.caret)
e.rr.caret += s
}
return carX - carX2
}
func (e *Editor) moveLeft() {
e.rr.moveLeft()
e.carXOff = 0
}
func (e *Editor) moveRight() {
e.rr.moveRight()
e.carXOff = 0
}
func (e *Editor) moveStart() {
carLine, carCol, x, _ := e.layoutCaret()
advances := e.lines[carLine].Text.Advances
for i := carCol - 1; i >= 0; i-- {
_, s := e.rr.runeBefore(e.rr.caret)
e.rr.caret -= s
x -= advances[i]
}
e.carXOff = -x
}
func (e *Editor) moveEnd() {
carLine, carCol, x, _ := e.layoutCaret()
l := e.lines[carLine]
// Only move past the end of the last line
end := 0
if carLine < len(e.lines)-1 {
end = 1
}
for i := carCol; i < len(l.Text.Advances)-end; i++ {
adv := l.Text.Advances[i]
_, s := e.rr.runeAt(e.rr.caret)
e.rr.caret += s
x += adv
}
a := align(e.Alignment, l.Width, e.viewWidth())
e.carXOff = l.Width + a - x
}
func (e *Editor) scrollToCaret(c ui.Config) {
carWidth := e.caretWidth(c)
carLine, _, x, y := e.layoutCaret()
l := e.lines[carLine]
if e.SingleLine {
minx := (x - carWidth/2).Ceil()
if d := minx - e.scrollOff.X + e.padLeft; d < 0 {
e.scrollOff.X += d
}
maxx := (x + carWidth/2).Ceil()
if d := maxx - (e.scrollOff.X + e.viewSize.X - e.padRight); d > 0 {
e.scrollOff.X += d
}
} else {
miny := y + l.Bounds.Min.Y.Floor()
if d := miny - e.scrollOff.Y + e.padTop; d < 0 {
e.scrollOff.Y += d
}
maxy := y + l.Bounds.Max.Y.Ceil()
if d := maxy - (e.scrollOff.Y + e.viewSize.Y - e.padBottom); d > 0 {
e.scrollOff.Y += d
}
}
}
func (e *Editor) command(k key.Event) bool {
switch k.Name {
case key.NameReturn, key.NameEnter:
e.append("\n")
case key.NameDeleteBackward:
e.deleteRune()
case key.NameDeleteForward:
e.deleteRuneForward()
case key.NameUpArrow:
line, _, carX, _ := e.layoutCaret()
e.carXOff = e.moveToLine(carX+e.carXOff, line-1)
case key.NameDownArrow:
line, _, carX, _ := e.layoutCaret()
e.carXOff = e.moveToLine(carX+e.carXOff, line+1)
case key.NameLeftArrow:
e.moveLeft()
case key.NameRightArrow:
e.moveRight()
case key.NamePageUp:
e.movePages(-1)
case key.NamePageDown:
e.movePages(+1)
case key.NameHome:
e.moveStart()
case key.NameEnd:
e.moveEnd()
default:
return false
}
return true
}
func (s ChangeEvent) isEditorEvent() {}
func (s SubmitEvent) isEditorEvent() {}
-153
View File
@@ -1,153 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
// Package text implements text widgets.
package text
import (
"image"
"image/color"
"unicode/utf8"
"gioui.org/ui"
"gioui.org/ui/f32"
"gioui.org/ui/layout"
"gioui.org/ui/paint"
"golang.org/x/image/math/fixed"
)
// Label is a widget for laying out and drawing text.
type Label struct {
// Face define the font and style of the text.
Face Face
// Material is a macro recording the material to draw the
// text. Use a ColorOp for colored text.
Material ui.MacroOp
// Alignment specify the text alignment.
Alignment Alignment
// Text is the string to draw.
Text string
// MaxLines specify the maximum number of lines the text
// may fill.
MaxLines int
it lineIterator
}
type lineIterator struct {
Lines []Line
Clip image.Rectangle
Alignment Alignment
Width int
Offset image.Point
y, prevDesc fixed.Int26_6
}
const inf = 1e6
func (l *lineIterator) Next() (String, f32.Point, bool) {
for len(l.Lines) > 0 {
line := l.Lines[0]
l.Lines = l.Lines[1:]
x := align(l.Alignment, line.Width, l.Width) + fixed.I(l.Offset.X)
l.y += l.prevDesc + line.Ascent
l.prevDesc = line.Descent
// Align baseline and line start to the pixel grid.
off := fixed.Point26_6{X: fixed.I(x.Floor()), Y: fixed.I(l.y.Ceil())}
l.y = off.Y
off.Y += fixed.I(l.Offset.Y)
if (off.Y + line.Bounds.Min.Y).Floor() > l.Clip.Max.Y {
break
}
if (off.Y + line.Bounds.Max.Y).Ceil() < l.Clip.Min.Y {
continue
}
str := line.Text
for len(str.Advances) > 0 {
adv := str.Advances[0]
if (off.X + adv + line.Bounds.Max.X - line.Width).Ceil() >= l.Clip.Min.X {
break
}
off.X += adv
_, s := utf8.DecodeRuneInString(str.String)
str.String = str.String[s:]
str.Advances = str.Advances[1:]
}
n := 0
endx := off.X
for i, adv := range str.Advances {
if (endx + line.Bounds.Min.X).Floor() > l.Clip.Max.X {
str.String = str.String[:n]
str.Advances = str.Advances[:i]
break
}
_, s := utf8.DecodeRuneInString(str.String[n:])
n += s
endx += adv
}
offf := f32.Point{X: float32(off.X) / 64, Y: float32(off.Y) / 64}
return str, offf, true
}
return String{}, f32.Point{}, false
}
func (l Label) Layout(gtx *layout.Context) {
cs := gtx.Constraints
textLayout := l.Face.Layout(l.Text, LayoutOptions{MaxWidth: cs.Width.Max})
lines := textLayout.Lines
if max := l.MaxLines; max > 0 && len(lines) > max {
lines = lines[:max]
}
dims := linesDimens(lines)
dims.Size = cs.Constrain(dims.Size)
padTop, padBottom := textPadding(lines)
clip := image.Rectangle{
Min: image.Point{X: -inf, Y: -padTop},
Max: image.Point{X: inf, Y: dims.Size.Y + padBottom},
}
l.it = lineIterator{
Lines: lines,
Clip: clip,
Alignment: l.Alignment,
Width: dims.Size.X,
}
for {
str, off, ok := l.it.Next()
if !ok {
break
}
lclip := toRectF(clip).Sub(off)
var stack ui.StackOp
stack.Push(gtx.Ops)
ui.TransformOp{}.Offset(off).Add(gtx.Ops)
l.Face.Path(str).Add(gtx.Ops)
// Set a default color in case the material is empty.
paint.ColorOp{Color: color.RGBA{A: 0xff}}.Add(gtx.Ops)
l.Material.Add(gtx.Ops)
paint.PaintOp{Rect: lclip}.Add(gtx.Ops)
stack.Pop()
}
gtx.Dimensions = dims
}
func toRectF(r image.Rectangle) f32.Rectangle {
return f32.Rectangle{
Min: f32.Point{X: float32(r.Min.X), Y: float32(r.Min.Y)},
Max: f32.Point{X: float32(r.Max.X), Y: float32(r.Max.Y)},
}
}
func textPadding(lines []Line) (padTop int, padBottom int) {
if len(lines) > 0 {
first := lines[0]
if d := -first.Bounds.Min.Y - first.Ascent; d > 0 {
padTop = d.Ceil()
}
last := lines[len(lines)-1]
if d := last.Bounds.Max.Y - last.Descent; d > 0 {
padBottom = d.Ceil()
}
}
return
}
-115
View File
@@ -1,115 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package text
import (
"fmt"
"image"
"gioui.org/ui"
"gioui.org/ui/layout"
"golang.org/x/image/math/fixed"
)
// A Line contains the measurements of a line of text.
type Line struct {
Text String
// Width is the width of the line.
Width fixed.Int26_6
// Ascent is the height above the baseline.
Ascent fixed.Int26_6
// Descent is the height below the baseline, including
// the line gap.
Descent fixed.Int26_6
// Bounds is the visible bounds of the line.
Bounds fixed.Rectangle26_6
}
type String struct {
String string
// Advances contain the advance of each rune in String.
Advances []fixed.Int26_6
}
// A Layout contains the measurements of a body of text as
// a list of Lines.
type Layout struct {
Lines []Line
}
// LayoutOptions specify the constraints of a text layout.
type LayoutOptions struct {
// MaxWidth set the maximum width of the layout.
MaxWidth int
// SingleLine specify that line breaks are ignored.
SingleLine bool
}
type Face interface {
// Layout returns the text layout for a string given a set of
// options.
Layout(s string, opts LayoutOptions) *Layout
// Path returns the ClipOp outline of a text recorded in a macro.
Path(s String) ui.MacroOp
}
type Alignment uint8
const (
Start Alignment = iota
End
Middle
)
func linesDimens(lines []Line) layout.Dimensions {
var width fixed.Int26_6
var h int
var baseline int
if len(lines) > 0 {
baseline = lines[0].Ascent.Ceil()
var prevDesc fixed.Int26_6
for _, l := range lines {
h += (prevDesc + l.Ascent).Ceil()
prevDesc = l.Descent
if l.Width > width {
width = l.Width
}
}
h += lines[len(lines)-1].Descent.Ceil()
}
w := width.Ceil()
return layout.Dimensions{
Size: image.Point{
X: w,
Y: h,
},
Baseline: baseline,
}
}
func align(align Alignment, width fixed.Int26_6, maxWidth int) fixed.Int26_6 {
mw := fixed.I(maxWidth)
switch align {
case Middle:
return fixed.I(((mw - width) / 2).Floor())
case End:
return fixed.I((mw - width).Floor())
case Start:
return 0
default:
panic(fmt.Errorf("unknown alignment %v", align))
}
}
func (a Alignment) String() string {
switch a {
case Start:
return "Start"
case End:
return "End"
case Middle:
return "Middle"
default:
panic("unreachable")
}
}

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