mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-03 00:16:15 +00:00
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:
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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() {}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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() {}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
@import UIKit;
|
||||
|
||||
@interface GioAppDelegate : UIResponder <UIApplicationDelegate>
|
||||
@property (strong, nonatomic) UIWindow *window;
|
||||
@end
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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]);
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
`
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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])
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
`
|
||||
@@ -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
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) void nslog(char *str);
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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() {
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -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, ®istry_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);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
// +build darwin,ios
|
||||
|
||||
package app
|
||||
|
||||
import "C"
|
||||
|
||||
//export gio_runMain
|
||||
func gio_runMain() {
|
||||
runMain()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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() {}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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},
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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=
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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:])),
|
||||
})
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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() {}
|
||||
@@ -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
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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)},
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
@@ -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() {}
|
||||
@@ -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() {}
|
||||
@@ -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:])
|
||||
}
|
||||
@@ -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() {}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user