chore(v2): vendor dependencies for offline/China builds
go mod vendor pins onnxruntime_go v1.12.1, Gio and the rest into v2/vendor so go run/build work without hitting proxy.golang.org (blocked/slow in China). Verified: CGO_ENABLED=1 go build -mod=vendor ./internal/spike and GOOS=windows go build -mod=vendor ./internal/ui both pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+63
@@ -0,0 +1,63 @@
|
||||
This project is provided under the terms of the UNLICENSE or
|
||||
the MIT license denoted by the following SPDX identifier:
|
||||
|
||||
SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
You may use the project under the terms of either license.
|
||||
|
||||
Both licenses are reproduced below.
|
||||
|
||||
----
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019 The Gio authors
|
||||
|
||||
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 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.
|
||||
---
|
||||
|
||||
|
||||
|
||||
---
|
||||
The UNLICENSE
|
||||
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
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 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.
|
||||
|
||||
For more information, please refer to <https://unlicense.org/>
|
||||
---
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package org.gioui;
|
||||
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
public final class Gio {
|
||||
private static final Object initLock = new Object();
|
||||
private static boolean jniLoaded;
|
||||
private static final Handler handler = new Handler(Looper.getMainLooper());
|
||||
|
||||
/**
|
||||
* init loads and initializes the Go native library and runs
|
||||
* the Go main function.
|
||||
*
|
||||
* It is exported for use by Android apps that need to run Go code
|
||||
* outside the lifecycle of the Gio activity.
|
||||
*/
|
||||
public static synchronized void init(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, appCtx);
|
||||
jniLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
static private native void runGoMain(byte[] dataDir, Context context);
|
||||
|
||||
static void writeClipboard(Context ctx, String s) {
|
||||
ClipboardManager m = (ClipboardManager)ctx.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
m.setPrimaryClip(ClipData.newPlainText(null, s));
|
||||
}
|
||||
|
||||
static String readClipboard(Context ctx) {
|
||||
ClipboardManager m = (ClipboardManager)ctx.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData c = m.getPrimaryClip();
|
||||
if (c == null || c.getItemCount() < 1) {
|
||||
return null;
|
||||
}
|
||||
return c.getItemAt(0).coerceToText(ctx).toString();
|
||||
}
|
||||
|
||||
static void wakeupMainThread() {
|
||||
handler.post(new Runnable() {
|
||||
@Override public void run() {
|
||||
scheduleMainFuncs();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static private native void scheduleMainFuncs();
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package org.gioui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
public final class GioActivity extends Activity {
|
||||
private GioView view;
|
||||
public FrameLayout layer;
|
||||
|
||||
@Override public void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
|
||||
layer = new FrameLayout(this);
|
||||
view = new GioView(this);
|
||||
|
||||
view.setLayoutParams(new FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.MATCH_PARENT
|
||||
));
|
||||
view.setFocusable(true);
|
||||
view.setFocusableInTouchMode(true);
|
||||
|
||||
layer.addView(view);
|
||||
setContentView(layer);
|
||||
onNewIntent(this.getIntent());
|
||||
}
|
||||
|
||||
@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 onPause() {
|
||||
super.onPause();
|
||||
view.pause();
|
||||
}
|
||||
|
||||
@Override public void onResume() {
|
||||
super.onResume();
|
||||
view.resume();
|
||||
}
|
||||
|
||||
@Override public void onConfigurationChanged(Configuration c) {
|
||||
super.onConfigurationChanged(c);
|
||||
view.configurationChanged();
|
||||
}
|
||||
|
||||
@Override public void onLowMemory() {
|
||||
super.onLowMemory();
|
||||
GioView.onLowMemory();
|
||||
}
|
||||
|
||||
@Override public void onBackPressed() {
|
||||
if (!view.backPressed())
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
view.onIntentEvent(intent);
|
||||
}
|
||||
}
|
||||
+858
@@ -0,0 +1,858 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package org.gioui;
|
||||
|
||||
import java.lang.Class;
|
||||
import java.lang.IllegalAccessException;
|
||||
import java.lang.InstantiationException;
|
||||
import java.lang.ExceptionInInitializerError;
|
||||
import java.lang.SecurityException;
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.app.FragmentManager;
|
||||
import android.app.FragmentTransaction;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.text.TextUtils;
|
||||
import android.text.Selection;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Choreographer;
|
||||
import android.view.Display;
|
||||
import android.view.KeyCharacterMap;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.PointerIcon;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.Window;
|
||||
import android.view.WindowInsetsController;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.CorrectionInfo;
|
||||
import android.view.inputmethod.CompletionInfo;
|
||||
import android.view.inputmethod.CursorAnchorInfo;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.ExtractedText;
|
||||
import android.view.inputmethod.ExtractedTextRequest;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.inputmethod.InputContentInfo;
|
||||
import android.view.inputmethod.SurroundingText;
|
||||
import android.view.accessibility.AccessibilityNodeProvider;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
public final class GioView extends SurfaceView implements Choreographer.FrameCallback {
|
||||
private static boolean jniLoaded;
|
||||
|
||||
private final SurfaceHolder.Callback surfCallbacks;
|
||||
private final InputMethodManager imm;
|
||||
private final float scrollXScale;
|
||||
private final float scrollYScale;
|
||||
private final AccessibilityManager accessManager;
|
||||
private int keyboardHint;
|
||||
|
||||
private long nhandle;
|
||||
|
||||
public GioView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public GioView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
|
||||
}
|
||||
setLayoutParams(new WindowManager.LayoutParams(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT));
|
||||
|
||||
// Late initialization of the Go runtime to wait for a valid context.
|
||||
Gio.init(context.getApplicationContext());
|
||||
|
||||
// Set background color to transparent to avoid a flickering
|
||||
// issue on ChromeOS.
|
||||
setBackgroundColor(Color.argb(0, 0, 0, 0));
|
||||
|
||||
ViewConfiguration conf = ViewConfiguration.get(context);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
scrollXScale = conf.getScaledHorizontalScrollFactor();
|
||||
scrollYScale = conf.getScaledVerticalScrollFactor();
|
||||
|
||||
// The platform focus highlight is not aware of Gio's widgets.
|
||||
setDefaultFocusHighlightEnabled(false);
|
||||
} else {
|
||||
float listItemHeight = 48; // dp
|
||||
float px = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
listItemHeight,
|
||||
getResources().getDisplayMetrics()
|
||||
);
|
||||
scrollXScale = px;
|
||||
scrollYScale = px;
|
||||
}
|
||||
|
||||
setHighRefreshRate();
|
||||
|
||||
accessManager = (AccessibilityManager)context.getSystemService(Context.ACCESSIBILITY_SERVICE);
|
||||
imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
nhandle = onCreateView(this);
|
||||
setFocusable(true);
|
||||
setFocusableInTouchMode(true);
|
||||
surfCallbacks = 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(surfCallbacks);
|
||||
}
|
||||
|
||||
@Override public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (nhandle != 0) {
|
||||
onKeyEvent(nhandle, keyCode, event.getUnicodeChar(), true, event.getEventTime());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
if (nhandle != 0) {
|
||||
onKeyEvent(nhandle, keyCode, event.getUnicodeChar(), false, event.getEventTime());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean onGenericMotionEvent(MotionEvent event) {
|
||||
dispatchMotionEvent(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public boolean onTouchEvent(MotionEvent event) {
|
||||
// Ask for unbuffered events. Flutter and Chrome do it
|
||||
// so assume it's good for us as well.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
requestUnbufferedDispatch(event);
|
||||
}
|
||||
|
||||
dispatchMotionEvent(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setCursor(int id) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
return;
|
||||
}
|
||||
PointerIcon pointerIcon = PointerIcon.getSystemIcon(getContext(), id);
|
||||
setPointerIcon(pointerIcon);
|
||||
}
|
||||
|
||||
private void setOrientation(int id, int fallback) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
id = fallback;
|
||||
}
|
||||
((Activity) this.getContext()).setRequestedOrientation(id);
|
||||
}
|
||||
|
||||
private void setFullscreen(boolean enabled) {
|
||||
int flags = this.getSystemUiVisibility();
|
||||
if (enabled) {
|
||||
flags |= SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||||
flags |= SYSTEM_UI_FLAG_HIDE_NAVIGATION;
|
||||
flags |= SYSTEM_UI_FLAG_FULLSCREEN;
|
||||
flags |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
|
||||
} else {
|
||||
flags &= ~SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||||
flags &= ~SYSTEM_UI_FLAG_HIDE_NAVIGATION;
|
||||
flags &= ~SYSTEM_UI_FLAG_FULLSCREEN;
|
||||
flags &= ~SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
|
||||
}
|
||||
this.setSystemUiVisibility(flags);
|
||||
}
|
||||
|
||||
private enum Bar {
|
||||
NAVIGATION,
|
||||
STATUS,
|
||||
}
|
||||
|
||||
private void setBarColor(Bar t, int color, int luminance) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
return;
|
||||
}
|
||||
|
||||
Window window = ((Activity) this.getContext()).getWindow();
|
||||
|
||||
int insetsMask;
|
||||
int viewMask;
|
||||
|
||||
switch (t) {
|
||||
case STATUS:
|
||||
insetsMask = WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
|
||||
viewMask = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
|
||||
window.setStatusBarColor(color);
|
||||
break;
|
||||
case NAVIGATION:
|
||||
insetsMask = WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
|
||||
viewMask = View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
|
||||
window.setNavigationBarColor(color);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("invalid bar type");
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
int flags = this.getSystemUiVisibility();
|
||||
if (luminance > 128) {
|
||||
flags |= viewMask;
|
||||
} else {
|
||||
flags &= ~viewMask;
|
||||
}
|
||||
this.setSystemUiVisibility(flags);
|
||||
return;
|
||||
}
|
||||
|
||||
WindowInsetsController insetsController = window.getInsetsController();
|
||||
if (insetsController == null) {
|
||||
return;
|
||||
}
|
||||
if (luminance > 128) {
|
||||
insetsController.setSystemBarsAppearance(insetsMask, insetsMask);
|
||||
} else {
|
||||
insetsController.setSystemBarsAppearance(0, insetsMask);
|
||||
}
|
||||
}
|
||||
|
||||
private void setStatusColor(int color, int luminance) {
|
||||
this.setBarColor(Bar.STATUS, color, luminance);
|
||||
}
|
||||
|
||||
private void setNavigationColor(int color, int luminance) {
|
||||
this.setBarColor(Bar.NAVIGATION, color, luminance);
|
||||
}
|
||||
|
||||
private void setHighRefreshRate() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
return;
|
||||
}
|
||||
|
||||
Context context = getContext();
|
||||
Display display = context.getDisplay();
|
||||
Display.Mode[] supportedModes = display.getSupportedModes();
|
||||
if (supportedModes.length <= 1) {
|
||||
// Nothing to set
|
||||
return;
|
||||
}
|
||||
|
||||
Display.Mode currentMode = display.getMode();
|
||||
int currentWidth = currentMode.getPhysicalWidth();
|
||||
int currentHeight = currentMode.getPhysicalHeight();
|
||||
|
||||
float minRefreshRate = -1;
|
||||
float maxRefreshRate = -1;
|
||||
float bestRefreshRate = -1;
|
||||
int bestModeId = -1;
|
||||
for (Display.Mode mode : supportedModes) {
|
||||
float refreshRate = mode.getRefreshRate();
|
||||
float width = mode.getPhysicalWidth();
|
||||
float height = mode.getPhysicalHeight();
|
||||
|
||||
if (minRefreshRate == -1 || refreshRate < minRefreshRate) {
|
||||
minRefreshRate = refreshRate;
|
||||
}
|
||||
if (maxRefreshRate == -1 || refreshRate > maxRefreshRate) {
|
||||
maxRefreshRate = refreshRate;
|
||||
}
|
||||
|
||||
boolean refreshRateIsBetter = bestRefreshRate == -1 || refreshRate > bestRefreshRate;
|
||||
if (width == currentWidth && height == currentHeight && refreshRateIsBetter) {
|
||||
int modeId = mode.getModeId();
|
||||
bestRefreshRate = refreshRate;
|
||||
bestModeId = modeId;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestModeId == -1) {
|
||||
// Not expecting this but just in case
|
||||
return;
|
||||
}
|
||||
|
||||
if (minRefreshRate == maxRefreshRate) {
|
||||
// Can't improve the refresh rate
|
||||
return;
|
||||
}
|
||||
|
||||
Window window = ((Activity) context).getWindow();
|
||||
WindowManager.LayoutParams layoutParams = window.getAttributes();
|
||||
layoutParams.preferredDisplayModeId = bestModeId;
|
||||
window.setAttributes(layoutParams);
|
||||
}
|
||||
|
||||
protected void onIntentEvent(Intent intent) {
|
||||
if (intent == null) {
|
||||
return;
|
||||
}
|
||||
if (intent.getData() != null) {
|
||||
this.onOpenURI(nhandle, intent.getData().toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected boolean dispatchHoverEvent(MotionEvent event) {
|
||||
if (!accessManager.isTouchExplorationEnabled()) {
|
||||
return super.dispatchHoverEvent(event);
|
||||
}
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_HOVER_ENTER:
|
||||
// Fall through.
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
onTouchExploration(nhandle, event.getX(), event.getY());
|
||||
break;
|
||||
case MotionEvent.ACTION_HOVER_EXIT:
|
||||
onExitTouchExploration(nhandle);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void sendA11yEvent(int eventType, int viewId) {
|
||||
if (!accessManager.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
AccessibilityEvent event = obtainA11yEvent(eventType, viewId);
|
||||
getParent().requestSendAccessibilityEvent(this, event);
|
||||
}
|
||||
|
||||
AccessibilityEvent obtainA11yEvent(int eventType, int viewId) {
|
||||
AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
|
||||
event.setPackageName(getContext().getPackageName());
|
||||
event.setSource(this, viewId);
|
||||
return event;
|
||||
}
|
||||
|
||||
boolean isA11yActive() {
|
||||
return accessManager.isEnabled();
|
||||
}
|
||||
|
||||
void sendA11yChange(int viewId) {
|
||||
if (!accessManager.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
AccessibilityEvent event = obtainA11yEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED, viewId);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
|
||||
}
|
||||
getParent().requestSendAccessibilityEvent(this, event);
|
||||
}
|
||||
|
||||
private void dispatchMotionEvent(MotionEvent event) {
|
||||
if (nhandle == 0) {
|
||||
return;
|
||||
}
|
||||
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),
|
||||
scrollXScale*event.getHistoricalAxisValue(MotionEvent.AXIS_HSCROLL, i, j),
|
||||
scrollYScale*event.getHistoricalAxisValue(MotionEvent.AXIS_VSCROLL, i, j),
|
||||
event.getButtonState(),
|
||||
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,
|
||||
pact,
|
||||
event.getPointerId(i),
|
||||
event.getToolType(i),
|
||||
event.getX(i), event.getY(i),
|
||||
scrollXScale*event.getAxisValue(MotionEvent.AXIS_HSCROLL, i),
|
||||
scrollYScale*event.getAxisValue(MotionEvent.AXIS_VSCROLL, i),
|
||||
event.getButtonState(),
|
||||
event.getEventTime());
|
||||
}
|
||||
}
|
||||
|
||||
@Override public InputConnection onCreateInputConnection(EditorInfo editor) {
|
||||
Snippet snip = getSnippet();
|
||||
editor.inputType = this.keyboardHint;
|
||||
editor.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
|
||||
editor.initialSelStart = imeToUTF16(nhandle, imeSelectionStart(nhandle));
|
||||
editor.initialSelEnd = imeToUTF16(nhandle, imeSelectionEnd(nhandle));
|
||||
int selStart = editor.initialSelStart - snip.offset;
|
||||
editor.initialCapsMode = TextUtils.getCapsMode(snip.snippet, selStart, this.keyboardHint);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
editor.setInitialSurroundingSubText(snip.snippet, imeToUTF16(nhandle, snip.offset));
|
||||
}
|
||||
imeSetComposingRegion(nhandle, -1, -1);
|
||||
return new GioInputConnection();
|
||||
}
|
||||
|
||||
void setInputHint(int hint) {
|
||||
if (hint == this.keyboardHint) {
|
||||
return;
|
||||
}
|
||||
this.keyboardHint = hint;
|
||||
restartInput();
|
||||
}
|
||||
|
||||
void showTextInput() {
|
||||
GioView.this.requestFocus();
|
||||
imm.showSoftInput(GioView.this, 0);
|
||||
}
|
||||
|
||||
void hideTextInput() {
|
||||
imm.hideSoftInputFromWindow(getWindowToken(), 0);
|
||||
}
|
||||
|
||||
@Override protected boolean fitSystemWindows(Rect insets) {
|
||||
if (nhandle != 0) {
|
||||
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) {
|
||||
if (nhandle != 0) {
|
||||
onFrameCallback(nhandle);
|
||||
}
|
||||
}
|
||||
|
||||
int getDensity() {
|
||||
return getResources().getDisplayMetrics().densityDpi;
|
||||
}
|
||||
|
||||
float getFontScale() {
|
||||
return getResources().getConfiguration().fontScale;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (nhandle != 0) {
|
||||
onStartView(nhandle);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (nhandle != 0) {
|
||||
onStopView(nhandle);
|
||||
}
|
||||
}
|
||||
|
||||
public void pause() {
|
||||
if (nhandle != 0) {
|
||||
onFocusChange(nhandle, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void resume() {
|
||||
if (nhandle != 0) {
|
||||
onFocusChange(nhandle, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
if (nhandle != 0) {
|
||||
onDestroyView(nhandle);
|
||||
}
|
||||
}
|
||||
|
||||
protected void unregister() {
|
||||
setOnFocusChangeListener(null);
|
||||
getHolder().removeCallback(surfCallbacks);
|
||||
nhandle = 0;
|
||||
}
|
||||
|
||||
public void configurationChanged() {
|
||||
if (nhandle != 0) {
|
||||
onConfigurationChanged(nhandle);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean backPressed() {
|
||||
if (nhandle == 0) {
|
||||
return false;
|
||||
}
|
||||
return onBack(nhandle);
|
||||
}
|
||||
|
||||
void restartInput() {
|
||||
imm.restartInput(this);
|
||||
}
|
||||
|
||||
void updateSelection() {
|
||||
int selStart = imeToUTF16(nhandle, imeSelectionStart(nhandle));
|
||||
int selEnd = imeToUTF16(nhandle, imeSelectionEnd(nhandle));
|
||||
int compStart = imeToUTF16(nhandle, imeComposingStart(nhandle));
|
||||
int compEnd = imeToUTF16(nhandle, imeComposingEnd(nhandle));
|
||||
imm.updateSelection(this, selStart, selEnd, compStart, compEnd);
|
||||
}
|
||||
|
||||
void updateCaret(float m00, float m01, float m02, float m10, float m11, float m12, float caretX, float caretTop, float caretBase, float caretBottom) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
return;
|
||||
}
|
||||
Matrix m = new Matrix();
|
||||
m.setValues(new float[]{m00, m01, m02, m10, m11, m12, 0.0f, 0.0f, 1.0f});
|
||||
m.setConcat(getMatrix(), m);
|
||||
int selStart = imeSelectionStart(nhandle);
|
||||
int selEnd = imeSelectionEnd(nhandle);
|
||||
int compStart = imeComposingStart(nhandle);
|
||||
int compEnd = imeComposingEnd(nhandle);
|
||||
Snippet snip = getSnippet();
|
||||
String composing = "";
|
||||
if (compStart != -1) {
|
||||
composing = snip.substringRunes(compStart, compEnd);
|
||||
}
|
||||
CursorAnchorInfo inf = new CursorAnchorInfo.Builder()
|
||||
.setMatrix(m)
|
||||
.setComposingText(imeToUTF16(nhandle, compStart), composing)
|
||||
.setSelectionRange(imeToUTF16(nhandle, selStart), imeToUTF16(nhandle, selEnd))
|
||||
.setInsertionMarkerLocation(caretX, caretTop, caretBase, caretBottom, 0)
|
||||
.build();
|
||||
imm.updateCursorAnchorInfo(this, inf);
|
||||
}
|
||||
|
||||
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 public native void onLowMemory();
|
||||
static private native void onTouchEvent(long handle, int action, int pointerID, int tool, float x, float y, float scrollX, float scrollY, int buttons, long time);
|
||||
static private native void onKeyEvent(long handle, int code, int character, boolean pressed, long time);
|
||||
static private native void onFrameCallback(long handle);
|
||||
static private native boolean onBack(long handle);
|
||||
static private native void onFocusChange(long handle, boolean focus);
|
||||
static private native AccessibilityNodeInfo initializeAccessibilityNodeInfo(long handle, int viewId, int screenX, int screenY, AccessibilityNodeInfo info);
|
||||
static private native void onTouchExploration(long handle, float x, float y);
|
||||
static private native void onExitTouchExploration(long handle);
|
||||
static private native void onA11yFocus(long handle, int viewId);
|
||||
static private native void onClearA11yFocus(long handle, int viewId);
|
||||
static private native void onOpenURI(long handle, String uri);
|
||||
static private native void imeSetSnippet(long handle, int start, int end);
|
||||
static private native String imeSnippet(long handle);
|
||||
static private native int imeSnippetStart(long handle);
|
||||
static private native int imeSelectionStart(long handle);
|
||||
static private native int imeSelectionEnd(long handle);
|
||||
static private native int imeComposingStart(long handle);
|
||||
static private native int imeComposingEnd(long handle);
|
||||
static private native int imeReplace(long handle, int start, int end, String text);
|
||||
static private native int imeSetSelection(long handle, int start, int end);
|
||||
static private native int imeSetComposingRegion(long handle, int start, int end);
|
||||
// imeToRunes converts the Java character index into runes (Java code points).
|
||||
static private native int imeToRunes(long handle, int chars);
|
||||
// imeToUTF16 converts the rune index into Java characters.
|
||||
static private native int imeToUTF16(long handle, int runes);
|
||||
|
||||
private class GioInputConnection implements InputConnection {
|
||||
private int batchDepth;
|
||||
|
||||
@Override public boolean beginBatchEdit() {
|
||||
batchDepth++;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public boolean endBatchEdit() {
|
||||
batchDepth--;
|
||||
return batchDepth > 0;
|
||||
}
|
||||
|
||||
@Override public boolean clearMetaKeyStates(int states) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean commitCompletion(CompletionInfo text) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean commitCorrection(CorrectionInfo info) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean commitText(CharSequence text, int cursor) {
|
||||
setComposingText(text, cursor);
|
||||
return finishComposingText();
|
||||
}
|
||||
|
||||
@Override public boolean deleteSurroundingText(int beforeChars, int afterChars) {
|
||||
// translate before and after to runes.
|
||||
int selStart = imeSelectionStart(nhandle);
|
||||
int selEnd = imeSelectionEnd(nhandle);
|
||||
int before = selStart - imeToRunes(nhandle, imeToUTF16(nhandle, selStart) - beforeChars);
|
||||
int after = selEnd - imeToRunes(nhandle, imeToUTF16(nhandle, selEnd) - afterChars);
|
||||
return deleteSurroundingTextInCodePoints(before, after);
|
||||
}
|
||||
|
||||
@Override public boolean finishComposingText() {
|
||||
imeSetComposingRegion(nhandle, -1, -1);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public int getCursorCapsMode(int reqModes) {
|
||||
Snippet snip = getSnippet();
|
||||
int selStart = imeSelectionStart(nhandle);
|
||||
int off = imeToUTF16(nhandle, selStart - snip.offset);
|
||||
if (off < 0 || off > snip.snippet.length()) {
|
||||
return 0;
|
||||
}
|
||||
return TextUtils.getCapsMode(snip.snippet, off, reqModes);
|
||||
}
|
||||
|
||||
@Override public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override public CharSequence getSelectedText(int flags) {
|
||||
Snippet snip = getSnippet();
|
||||
int selStart = imeSelectionStart(nhandle);
|
||||
int selEnd = imeSelectionEnd(nhandle);
|
||||
String sub = snip.substringRunes(selStart, selEnd);
|
||||
return sub;
|
||||
}
|
||||
|
||||
@Override public CharSequence getTextAfterCursor(int n, int flags) {
|
||||
Snippet snip = getSnippet();
|
||||
int selStart = imeSelectionStart(nhandle);
|
||||
int selEnd = imeSelectionEnd(nhandle);
|
||||
// n are in Java characters, but in worst case we'll just ask for more runes
|
||||
// than wanted.
|
||||
imeSetSnippet(nhandle, selStart - n, selEnd + n);
|
||||
int start = selEnd;
|
||||
int end = imeToRunes(nhandle, imeToUTF16(nhandle, selEnd) + n);
|
||||
String ret = snip.substringRunes(start, end);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override public CharSequence getTextBeforeCursor(int n, int flags) {
|
||||
Snippet snip = getSnippet();
|
||||
int selStart = imeSelectionStart(nhandle);
|
||||
int selEnd = imeSelectionEnd(nhandle);
|
||||
// n are in Java characters, but in worst case we'll just ask for more runes
|
||||
// than wanted.
|
||||
imeSetSnippet(nhandle, selStart - n, selEnd + n);
|
||||
int start = imeToRunes(nhandle, imeToUTF16(nhandle, selStart) - n);
|
||||
int end = selStart;
|
||||
String ret = snip.substringRunes(start, end);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override public boolean performContextMenuAction(int id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean performEditorAction(int editorAction) {
|
||||
long eventTime = SystemClock.uptimeMillis();
|
||||
// Translate to enter key.
|
||||
onKeyEvent(nhandle, KeyEvent.KEYCODE_ENTER, '\n', true, eventTime);
|
||||
onKeyEvent(nhandle, KeyEvent.KEYCODE_ENTER, '\n', false, eventTime);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public boolean performPrivateCommand(String action, Bundle data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean reportFullscreenMode(boolean enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean sendKeyEvent(KeyEvent event) {
|
||||
boolean pressed = event.getAction() == KeyEvent.ACTION_DOWN;
|
||||
onKeyEvent(nhandle, event.getKeyCode(), event.getUnicodeChar(), pressed, event.getEventTime());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public boolean setComposingRegion(int startChars, int endChars) {
|
||||
int compStart = imeToRunes(nhandle, startChars);
|
||||
int compEnd = imeToRunes(nhandle, endChars);
|
||||
imeSetComposingRegion(nhandle, compStart, compEnd);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public boolean setComposingText(CharSequence text, int relCursor) {
|
||||
int start = imeComposingStart(nhandle);
|
||||
int end = imeComposingEnd(nhandle);
|
||||
if (start == -1 || end == -1) {
|
||||
start = imeSelectionStart(nhandle);
|
||||
end = imeSelectionEnd(nhandle);
|
||||
}
|
||||
String str = text.toString();
|
||||
imeReplace(nhandle, start, end, str);
|
||||
int cursor = start;
|
||||
int runes = str.codePointCount(0, str.length());
|
||||
if (relCursor > 0) {
|
||||
cursor += runes;
|
||||
relCursor--;
|
||||
}
|
||||
imeSetComposingRegion(nhandle, start, start + runes);
|
||||
|
||||
// Move cursor.
|
||||
Snippet snip = getSnippet();
|
||||
cursor = imeToRunes(nhandle, imeToUTF16(nhandle, cursor) + relCursor);
|
||||
imeSetSelection(nhandle, cursor, cursor);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public boolean setSelection(int startChars, int endChars) {
|
||||
int start = imeToRunes(nhandle, startChars);
|
||||
int end = imeToRunes(nhandle, endChars);
|
||||
imeSetSelection(nhandle, start, end);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*@Override*/ public boolean requestCursorUpdates(int cursorUpdateMode) {
|
||||
// We always provide cursor updates.
|
||||
return true;
|
||||
}
|
||||
|
||||
/*@Override*/ public void closeConnection() {
|
||||
}
|
||||
|
||||
/*@Override*/ public Handler getHandler() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*@Override*/ public boolean commitContent(InputContentInfo info, int flags, Bundle opts) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*@Override*/ public boolean deleteSurroundingTextInCodePoints(int before, int after) {
|
||||
if (after > 0) {
|
||||
int selEnd = imeSelectionEnd(nhandle);
|
||||
imeReplace(nhandle, selEnd, selEnd + after, "");
|
||||
}
|
||||
if (before > 0) {
|
||||
int selStart = imeSelectionStart(nhandle);
|
||||
imeReplace(nhandle, selStart - before, selStart, "");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*@Override*/ public SurroundingText getSurroundingText(int beforeChars, int afterChars, int flags) {
|
||||
Snippet snip = getSnippet();
|
||||
int selStart = imeSelectionStart(nhandle);
|
||||
int selEnd = imeSelectionEnd(nhandle);
|
||||
// Expanding in Java characters is ok.
|
||||
imeSetSnippet(nhandle, selStart - beforeChars, selEnd + afterChars);
|
||||
return new SurroundingText(snip.snippet, imeToUTF16(nhandle, selStart), imeToUTF16(nhandle, selEnd), imeToUTF16(nhandle, snip.offset));
|
||||
}
|
||||
}
|
||||
|
||||
private Snippet getSnippet() {
|
||||
Snippet snip = new Snippet();
|
||||
snip.snippet = imeSnippet(nhandle);
|
||||
snip.offset = imeSnippetStart(nhandle);
|
||||
return snip;
|
||||
}
|
||||
|
||||
// Snippet is like android.view.inputmethod.SurroundingText but available for Android < 31.
|
||||
private static class Snippet {
|
||||
String snippet;
|
||||
// offset of snippet into the entire editor content. It is in runes because we won't require
|
||||
// Gio editors to keep track of UTF-16 offsets. The distinction won't matter in practice because IMEs only
|
||||
// ever see snippets.
|
||||
int offset;
|
||||
|
||||
// substringRunes returns the substring from start to end in runes. The resuls is
|
||||
// truncated to the snippet.
|
||||
String substringRunes(int start, int end) {
|
||||
start -= this.offset;
|
||||
end -= this.offset;
|
||||
int runes = snippet.codePointCount(0, snippet.length());
|
||||
if (start < 0) {
|
||||
start = 0;
|
||||
}
|
||||
if (end < 0) {
|
||||
end = 0;
|
||||
}
|
||||
if (start > runes) {
|
||||
start = runes;
|
||||
}
|
||||
if (end > runes) {
|
||||
end = runes;
|
||||
}
|
||||
return snippet.substring(
|
||||
snippet.offsetByCodePoints(0, start),
|
||||
snippet.offsetByCodePoints(0, end)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public AccessibilityNodeProvider getAccessibilityNodeProvider() {
|
||||
return new AccessibilityNodeProvider() {
|
||||
private final int[] screenOff = new int[2];
|
||||
|
||||
@Override public AccessibilityNodeInfo createAccessibilityNodeInfo(int viewId) {
|
||||
AccessibilityNodeInfo info = null;
|
||||
if (viewId == View.NO_ID) {
|
||||
info = AccessibilityNodeInfo.obtain(GioView.this);
|
||||
GioView.this.onInitializeAccessibilityNodeInfo(info);
|
||||
} else {
|
||||
info = AccessibilityNodeInfo.obtain(GioView.this, viewId);
|
||||
info.setPackageName(getContext().getPackageName());
|
||||
info.setVisibleToUser(true);
|
||||
}
|
||||
GioView.this.getLocationOnScreen(screenOff);
|
||||
info = GioView.this.initializeAccessibilityNodeInfo(nhandle, viewId, screenOff[0], screenOff[1], info);
|
||||
return info;
|
||||
}
|
||||
|
||||
@Override public boolean performAction(int viewId, int action, Bundle arguments) {
|
||||
if (viewId == View.NO_ID) {
|
||||
return GioView.this.performAccessibilityAction(action, arguments);
|
||||
}
|
||||
switch (action) {
|
||||
case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
|
||||
GioView.this.onA11yFocus(nhandle, viewId);
|
||||
GioView.this.sendA11yEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, viewId);
|
||||
return true;
|
||||
case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
|
||||
GioView.this.onClearA11yFocus(nhandle, viewId);
|
||||
GioView.this.sendA11yEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, viewId);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"gioui.org/io/event"
|
||||
"golang.org/x/net/idna"
|
||||
"image"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gioui.org/io/input"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
// ID is the app id exposed to the platform.
|
||||
//
|
||||
// On Android ID is the package property of AndroidManifest.xml,
|
||||
// on iOS ID is the CFBundleIdentifier of the app Info.plist,
|
||||
// on Wayland it is the toplevel app_id,
|
||||
// on X11 it is the X11 XClassHint.
|
||||
//
|
||||
// ID is set by the [gioui.org/cmd/gogio] tool or manually with the -X linker flag. For example,
|
||||
//
|
||||
// go build -ldflags="-X 'gioui.org/app.ID=org.gioui.example.Kitchen'" .
|
||||
//
|
||||
// Note that ID is treated as a constant, and that changing it at runtime
|
||||
// is not supported. The default value of ID is filepath.Base(os.Args[0]).
|
||||
var ID = ""
|
||||
|
||||
// A FrameEvent requests a new frame in the form of a list of
|
||||
// operations that describes the window content.
|
||||
type FrameEvent struct {
|
||||
// Now is the current animation. Use Now instead of time.Now to
|
||||
// synchronize animation and to avoid the time.Now call overhead.
|
||||
Now time.Time
|
||||
// Metric converts device independent dp and sp to device pixels.
|
||||
Metric unit.Metric
|
||||
// Size is the dimensions of the window.
|
||||
Size image.Point
|
||||
// Insets represent the space occupied by system decorations and controls.
|
||||
Insets Insets
|
||||
// Frame completes the FrameEvent by drawing the graphical operations
|
||||
// from ops into the window.
|
||||
Frame func(frame *op.Ops)
|
||||
// Source is the interface between the window and widgets.
|
||||
Source input.Source
|
||||
}
|
||||
|
||||
// URLEvent is generated for external requests to open a URL. Unlike window specific events,
|
||||
// it is delivered through the [Events] iterator.
|
||||
//
|
||||
// In order to receive URLEvents the program must register one or more URL schemes. A scheme can
|
||||
// be registered using gogio, with the `-schemes` flag.
|
||||
type URLEvent struct {
|
||||
URL *url.URL
|
||||
}
|
||||
|
||||
// ViewEvent provides handles to the underlying window objects for the
|
||||
// current display protocol.
|
||||
type ViewEvent interface {
|
||||
implementsViewEvent()
|
||||
ImplementsEvent()
|
||||
// Valid will return true when the ViewEvent does contains valid handles.
|
||||
// If a window receives an invalid ViewEvent, it should deinitialize any
|
||||
// state referring to handles from a previous ViewEvent.
|
||||
Valid() bool
|
||||
}
|
||||
|
||||
// Insets is the space taken up by
|
||||
// system decoration such as translucent
|
||||
// system bars and software keyboards.
|
||||
type Insets struct {
|
||||
// Values are in pixels.
|
||||
Top, Bottom, Left, Right unit.Dp
|
||||
}
|
||||
|
||||
// NewContext is shorthand for
|
||||
//
|
||||
// layout.Context{
|
||||
// Ops: ops,
|
||||
// Now: e.Now,
|
||||
// Source: e.Source,
|
||||
// Metric: e.Metric,
|
||||
// Constraints: layout.Exact(e.Size),
|
||||
// }
|
||||
//
|
||||
// NewContext calls ops.Reset and adjusts ops for e.Insets.
|
||||
func NewContext(ops *op.Ops, e FrameEvent) layout.Context {
|
||||
ops.Reset()
|
||||
|
||||
size := e.Size
|
||||
|
||||
if e.Insets != (Insets{}) {
|
||||
left := e.Metric.Dp(e.Insets.Left)
|
||||
top := e.Metric.Dp(e.Insets.Top)
|
||||
op.Offset(image.Point{
|
||||
X: left,
|
||||
Y: top,
|
||||
}).Add(ops)
|
||||
|
||||
size.X -= left + e.Metric.Dp(e.Insets.Right)
|
||||
size.Y -= top + e.Metric.Dp(e.Insets.Bottom)
|
||||
}
|
||||
|
||||
return layout.Context{
|
||||
Ops: ops,
|
||||
Now: e.Now,
|
||||
Source: e.Source,
|
||||
Metric: e.Metric,
|
||||
Constraints: layout.Exact(size),
|
||||
}
|
||||
}
|
||||
|
||||
// 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: On Android, DataDir panics if called before main.
|
||||
func DataDir() (string, error) {
|
||||
return dataDir()
|
||||
}
|
||||
|
||||
// Main must be called last from the program main function.
|
||||
// On most platforms Main blocks forever, for Android and
|
||||
// iOS it returns immediately to give control of the main
|
||||
// thread back to the system.
|
||||
//
|
||||
// Calling Main is necessary because some operating systems
|
||||
// require control of the main thread of the program for
|
||||
// running windows.
|
||||
func Main() {
|
||||
osMain()
|
||||
}
|
||||
|
||||
// Events is an iterator that yields events that are not specific to any window,
|
||||
// such as [URLEvent]. It never returns.
|
||||
//
|
||||
// Events must be called by the main goroutine, and replaces the
|
||||
// call to [Main].
|
||||
func Events(yield func(event.Event) bool) {
|
||||
yieldGlobalEvent = yield
|
||||
osMain()
|
||||
}
|
||||
|
||||
var yieldGlobalEvent func(evt event.Event) bool
|
||||
|
||||
func processGlobalEvent(evt event.Event) {
|
||||
if yieldGlobalEvent == nil {
|
||||
return
|
||||
}
|
||||
if !yieldGlobalEvent(evt) {
|
||||
yieldGlobalEvent = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (FrameEvent) ImplementsEvent() {}
|
||||
func (URLEvent) ImplementsEvent() {}
|
||||
|
||||
func init() {
|
||||
if extraArgs != "" {
|
||||
args := strings.Split(extraArgs, "|")
|
||||
os.Args = append(os.Args, args...)
|
||||
}
|
||||
if ID == "" {
|
||||
ID = filepath.Base(os.Args[0])
|
||||
}
|
||||
}
|
||||
|
||||
// newURLEvent creates a URLEvent from a raw URL string, handling Punycode decoding.
|
||||
func newURLEvent(rawurl string) (URLEvent, error) {
|
||||
u, err := url.Parse(rawurl)
|
||||
if err != nil {
|
||||
return URLEvent{}, err
|
||||
}
|
||||
u.Host, err = idna.Punycode.ToUnicode(u.Hostname())
|
||||
if err != nil {
|
||||
return URLEvent{}, err
|
||||
}
|
||||
u, err = url.Parse(u.String())
|
||||
if err != nil {
|
||||
return URLEvent{}, err
|
||||
}
|
||||
return URLEvent{URL: u}, nil
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/internal/d3d11"
|
||||
)
|
||||
|
||||
type d3d11Context struct {
|
||||
win *window
|
||||
dev *d3d11.Device
|
||||
ctx *d3d11.DeviceContext
|
||||
|
||||
swchain *d3d11.IDXGISwapChain
|
||||
renderTarget *d3d11.RenderTargetView
|
||||
width, height int
|
||||
}
|
||||
|
||||
const debugDirectX = false
|
||||
|
||||
func init() {
|
||||
drivers = append(drivers, gpuAPI{
|
||||
priority: 1,
|
||||
initializer: func(w *window) (context, error) {
|
||||
hwnd, _, _ := w.HWND()
|
||||
var flags uint32
|
||||
if debugDirectX {
|
||||
flags |= d3d11.CREATE_DEVICE_DEBUG
|
||||
}
|
||||
dev, ctx, _, err := d3d11.CreateDevice(
|
||||
d3d11.DRIVER_TYPE_HARDWARE,
|
||||
flags,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewContext: %v", err)
|
||||
}
|
||||
swchain, err := d3d11.CreateSwapChain(dev, hwnd)
|
||||
if err != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(ctx), ctx.Vtbl.Release)
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(dev), dev.Vtbl.Release)
|
||||
return nil, err
|
||||
}
|
||||
return &d3d11Context{win: w, dev: dev, ctx: ctx, swchain: swchain}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (c *d3d11Context) API() gpu.API {
|
||||
return gpu.Direct3D11{Device: unsafe.Pointer(c.dev)}
|
||||
}
|
||||
|
||||
func (c *d3d11Context) RenderTarget() (gpu.RenderTarget, error) {
|
||||
return gpu.Direct3D11RenderTarget{
|
||||
RenderTarget: unsafe.Pointer(c.renderTarget),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *d3d11Context) Present() error {
|
||||
return wrapErr(c.swchain.Present(1, 0))
|
||||
}
|
||||
|
||||
func wrapErr(err error) error {
|
||||
if err, ok := err.(d3d11.ErrorCode); ok {
|
||||
switch err.Code {
|
||||
case d3d11.DXGI_STATUS_OCCLUDED:
|
||||
// Ignore
|
||||
return nil
|
||||
case d3d11.DXGI_ERROR_DEVICE_RESET, d3d11.DXGI_ERROR_DEVICE_REMOVED, d3d11.D3DDDIERR_DEVICEREMOVED:
|
||||
return gpu.ErrDeviceLost
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *d3d11Context) Refresh() error {
|
||||
var width, height int
|
||||
_, width, height = c.win.HWND()
|
||||
if c.renderTarget != nil && width == c.width && height == c.height {
|
||||
return nil
|
||||
}
|
||||
c.releaseFBO()
|
||||
if err := c.swchain.ResizeBuffers(0, 0, 0, d3d11.DXGI_FORMAT_UNKNOWN, 0); err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
c.width = width
|
||||
c.height = height
|
||||
|
||||
backBuffer, err := c.swchain.GetBuffer(0, &d3d11.IID_Texture2D)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
texture := (*d3d11.Resource)(unsafe.Pointer(backBuffer))
|
||||
renderTarget, err := c.dev.CreateRenderTargetView(texture)
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(backBuffer), backBuffer.Vtbl.Release)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.renderTarget = renderTarget
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *d3d11Context) Lock() error {
|
||||
c.ctx.OMSetRenderTargets(c.renderTarget, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *d3d11Context) Unlock() {}
|
||||
|
||||
func (c *d3d11Context) Release() {
|
||||
c.releaseFBO()
|
||||
if c.swchain != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(c.swchain), c.swchain.Vtbl.Release)
|
||||
}
|
||||
if c.ctx != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(c.ctx), c.ctx.Vtbl.Release)
|
||||
}
|
||||
if c.dev != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(c.dev), c.dev.Vtbl.Release)
|
||||
}
|
||||
*c = d3d11Context{}
|
||||
if debugDirectX {
|
||||
d3d11.ReportLiveObjects()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *d3d11Context) releaseFBO() {
|
||||
if c.renderTarget != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(c.renderTarget), c.renderTarget.Vtbl.Release)
|
||||
c.renderTarget = nil
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build !android
|
||||
|
||||
package app
|
||||
|
||||
import "os"
|
||||
|
||||
func dataDir() (string, error) {
|
||||
return os.UserConfigDir()
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
/*
|
||||
Package app provides a platform-independent interface to operating system
|
||||
functionality for running graphical user interfaces.
|
||||
|
||||
See https://gioui.org for instructions to set up and run Gio programs.
|
||||
|
||||
# Windows
|
||||
|
||||
A Window is run by calling its Event method in a loop. The first time a
|
||||
method on Window is called, a new GUI window is created and shown. On mobile
|
||||
platforms or when Gio is embedded in another project, Window merely connects
|
||||
with a previously created GUI window.
|
||||
|
||||
The most important event is [FrameEvent] that prompts an update of the window
|
||||
contents.
|
||||
|
||||
For example:
|
||||
|
||||
w := new(app.Window)
|
||||
for {
|
||||
e := w.Event()
|
||||
if e, ok := e.(app.FrameEvent); ok {
|
||||
ops.Reset()
|
||||
// Add operations to ops.
|
||||
...
|
||||
// Completely replace the window contents and state.
|
||||
e.Frame(ops)
|
||||
}
|
||||
}
|
||||
|
||||
A program must keep receiving events from the event channel until
|
||||
[DestroyEvent] is received.
|
||||
|
||||
# Main
|
||||
|
||||
The Main function must be called from a program's main function, to hand over
|
||||
control of the main thread to operating systems that need it.
|
||||
|
||||
Because Main is also blocking on some platforms, the event loop of a Window must run in a goroutine.
|
||||
|
||||
For example, to display a blank but otherwise functional window:
|
||||
|
||||
package main
|
||||
|
||||
import "gioui.org/app"
|
||||
|
||||
func main() {
|
||||
go func() {
|
||||
w := new(app.Window)
|
||||
for {
|
||||
w.Event()
|
||||
}
|
||||
}()
|
||||
app.Main()
|
||||
}
|
||||
|
||||
# Events
|
||||
|
||||
The [Events] iterator yields app-specific events such as [URLEvent]. [Window.Event]
|
||||
yields events that target a particular window.
|
||||
|
||||
# Permissions
|
||||
|
||||
The packages under gioui.org/app/permission should be imported
|
||||
by a Gio program or by one of its dependencies to indicate that specific
|
||||
operating-system permissions are required. Please see documentation for
|
||||
package gioui.org/app/permission for more information.
|
||||
*/
|
||||
package app
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build !noopengl
|
||||
|
||||
package app
|
||||
|
||||
/*
|
||||
#include <android/native_window_jni.h>
|
||||
#include <EGL/egl.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/internal/egl"
|
||||
)
|
||||
|
||||
type androidContext struct {
|
||||
win *window
|
||||
eglSurf egl.NativeWindowType
|
||||
*egl.Context
|
||||
}
|
||||
|
||||
func init() {
|
||||
newAndroidGLESContext = func(w *window) (context, error) {
|
||||
ctx, err := egl.NewContext(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &androidContext{win: w, Context: ctx}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *androidContext) Release() {
|
||||
if c.Context != nil {
|
||||
c.Context.Release()
|
||||
c.Context = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *androidContext) Refresh() error {
|
||||
c.Context.ReleaseSurface()
|
||||
if err := c.win.setVisual(c.Context.VisualID()); err != nil {
|
||||
return err
|
||||
}
|
||||
win, _, _ := c.win.nativeWindow()
|
||||
c.eglSurf = egl.NativeWindowType(unsafe.Pointer(win))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *androidContext) Lock() error {
|
||||
// The Android emulator creates a broken surface if it is not
|
||||
// created on the same thread as the context is made current.
|
||||
if c.eglSurf != nil {
|
||||
if err := c.Context.CreateSurface(c.eglSurf); err != nil {
|
||||
return err
|
||||
}
|
||||
c.eglSurf = nil
|
||||
}
|
||||
return c.Context.MakeCurrent()
|
||||
}
|
||||
|
||||
func (c *androidContext) Unlock() {
|
||||
c.Context.ReleaseCurrent()
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build ((linux && !android) || freebsd) && !nowayland && !noopengl
|
||||
// +build linux,!android freebsd
|
||||
// +build !nowayland
|
||||
// +build !noopengl
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/internal/egl"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: egl wayland-egl
|
||||
#cgo freebsd openbsd LDFLAGS: -lwayland-egl
|
||||
#cgo CFLAGS: -DEGL_NO_X11
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <wayland-client.h>
|
||||
#include <wayland-egl.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type wlContext struct {
|
||||
win *window
|
||||
*egl.Context
|
||||
eglWin *C.struct_wl_egl_window
|
||||
}
|
||||
|
||||
func init() {
|
||||
newWaylandEGLContext = func(w *window) (context, error) {
|
||||
disp := egl.NativeDisplayType(unsafe.Pointer(w.display()))
|
||||
ctx, err := egl.NewContext(disp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
surf, width, height := w.surface()
|
||||
if surf == nil {
|
||||
return nil, errors.New("wayland: no surface")
|
||||
}
|
||||
eglWin := C.wl_egl_window_create(surf, C.int(width), C.int(height))
|
||||
if eglWin == nil {
|
||||
return nil, errors.New("wayland: wl_egl_window_create failed")
|
||||
}
|
||||
eglSurf := egl.NativeWindowType(uintptr(unsafe.Pointer(eglWin)))
|
||||
if err := ctx.CreateSurface(eglSurf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// We're in charge of the frame callbacks, don't let eglSwapBuffers
|
||||
// wait for callbacks that may never arrive.
|
||||
ctx.EnableVSync(false)
|
||||
|
||||
return &wlContext{Context: ctx, win: w, eglWin: eglWin}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *wlContext) Release() {
|
||||
if c.Context != nil {
|
||||
c.Context.Release()
|
||||
c.Context = nil
|
||||
}
|
||||
if c.eglWin != nil {
|
||||
C.wl_egl_window_destroy(c.eglWin)
|
||||
c.eglWin = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *wlContext) Refresh() error {
|
||||
surf, width, height := c.win.surface()
|
||||
if surf == nil {
|
||||
return errors.New("wayland: no surface")
|
||||
}
|
||||
C.wl_egl_window_resize(c.eglWin, C.int(width), C.int(height), 0, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *wlContext) Lock() error {
|
||||
return c.Context.MakeCurrent()
|
||||
}
|
||||
|
||||
func (c *wlContext) Unlock() {
|
||||
c.Context.ReleaseCurrent()
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build !noopengl
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"gioui.org/internal/egl"
|
||||
)
|
||||
|
||||
type glContext struct {
|
||||
win *window
|
||||
*egl.Context
|
||||
}
|
||||
|
||||
func init() {
|
||||
drivers = append(drivers, gpuAPI{
|
||||
priority: 2,
|
||||
initializer: func(w *window) (context, error) {
|
||||
disp := egl.NativeDisplayType(w.HDC())
|
||||
ctx, err := egl.NewContext(disp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
win, _, _ := w.HWND()
|
||||
eglSurf := egl.NativeWindowType(win)
|
||||
if err := ctx.CreateSurface(eglSurf); err != nil {
|
||||
ctx.Release()
|
||||
return nil, err
|
||||
}
|
||||
if err := ctx.MakeCurrent(); err != nil {
|
||||
ctx.Release()
|
||||
return nil, err
|
||||
}
|
||||
defer ctx.ReleaseCurrent()
|
||||
ctx.EnableVSync(true)
|
||||
return &glContext{win: w, Context: ctx}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (c *glContext) Release() {
|
||||
if c.Context != nil {
|
||||
c.Context.Release()
|
||||
c.Context = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *glContext) Refresh() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *glContext) Lock() error {
|
||||
return c.Context.MakeCurrent()
|
||||
}
|
||||
|
||||
func (c *glContext) Unlock() {
|
||||
c.Context.ReleaseCurrent()
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build ((linux && !android) || freebsd || openbsd) && !nox11 && !noopengl
|
||||
// +build linux,!android freebsd openbsd
|
||||
// +build !nox11
|
||||
// +build !noopengl
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/internal/egl"
|
||||
)
|
||||
|
||||
type x11Context struct {
|
||||
win *x11Window
|
||||
*egl.Context
|
||||
}
|
||||
|
||||
func init() {
|
||||
newX11EGLContext = func(w *x11Window) (context, error) {
|
||||
disp := egl.NativeDisplayType(unsafe.Pointer(w.display()))
|
||||
ctx, err := egl.NewContext(disp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
win, _, _ := w.window()
|
||||
eglSurf := egl.NativeWindowType(uintptr(win))
|
||||
if err := ctx.CreateSurface(eglSurf); err != nil {
|
||||
ctx.Release()
|
||||
return nil, err
|
||||
}
|
||||
if err := ctx.MakeCurrent(); err != nil {
|
||||
ctx.Release()
|
||||
return nil, err
|
||||
}
|
||||
defer ctx.ReleaseCurrent()
|
||||
ctx.EnableVSync(true)
|
||||
return &x11Context{win: w, Context: ctx}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *x11Context) Release() {
|
||||
if c.Context != nil {
|
||||
c.Context.Release()
|
||||
c.Context = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *x11Context) Refresh() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *x11Context) Lock() error {
|
||||
return c.Context.MakeCurrent()
|
||||
}
|
||||
|
||||
func (c *x11Context) Unlock() {
|
||||
c.Context.ReleaseCurrent()
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
#include <UIKit/UIKit.h>
|
||||
|
||||
@interface GioViewController : UIViewController
|
||||
@end
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build darwin && ios && nometal
|
||||
|
||||
package app
|
||||
|
||||
/*
|
||||
@import UIKit;
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <OpenGLES/ES2/gl.h>
|
||||
#include <OpenGLES/ES2/glext.h>
|
||||
|
||||
__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);
|
||||
|
||||
static CFTypeRef getViewLayer(CFTypeRef viewRef) {
|
||||
@autoreleasepool {
|
||||
UIView *view = (__bridge UIView *)viewRef;
|
||||
return CFBridgingRetain(view.layer);
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/internal/gl"
|
||||
)
|
||||
|
||||
type context struct {
|
||||
owner *window
|
||||
c *gl.Functions
|
||||
ctx C.CFTypeRef
|
||||
layer C.CFTypeRef
|
||||
init bool
|
||||
frameBuffer gl.Framebuffer
|
||||
colorBuffer gl.Renderbuffer
|
||||
}
|
||||
|
||||
func newContext(w *window) (*context, error) {
|
||||
ctx := C.gio_createContext()
|
||||
if ctx == 0 {
|
||||
return nil, fmt.Errorf("failed to create EAGLContext")
|
||||
}
|
||||
api := contextAPI()
|
||||
f, err := gl.NewFunctions(api.Context, api.ES)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &context{
|
||||
ctx: ctx,
|
||||
owner: w,
|
||||
layer: C.getViewLayer(w.contextView()),
|
||||
c: f,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func contextAPI() gpu.OpenGL {
|
||||
return gpu.OpenGL{}
|
||||
}
|
||||
|
||||
func (c *context) RenderTarget() gpu.RenderTarget {
|
||||
return gpu.OpenGLRenderTarget(c.frameBuffer)
|
||||
}
|
||||
|
||||
func (c *context) API() gpu.API {
|
||||
return contextAPI()
|
||||
}
|
||||
|
||||
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.gio_makeCurrent(0)
|
||||
C.CFRelease(c.ctx)
|
||||
c.ctx = 0
|
||||
}
|
||||
|
||||
func (c *context) Present() error {
|
||||
if c.layer == 0 {
|
||||
panic("context is not active")
|
||||
}
|
||||
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() error {
|
||||
// OpenGL contexts are implicit and thread-local. Lock the OS thread.
|
||||
runtime.LockOSThread()
|
||||
|
||||
if C.gio_makeCurrent(c.ctx) == 0 {
|
||||
return errors.New("[EAGLContext setCurrentContext] failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) Unlock() {
|
||||
C.gio_makeCurrent(0)
|
||||
}
|
||||
|
||||
func (c *context) Refresh() error {
|
||||
if C.gio_makeCurrent(c.ctx) == 0 {
|
||||
return errors.New("[EAGLContext setCurrentContext] failed")
|
||||
}
|
||||
if !c.init {
|
||||
c.init = true
|
||||
c.frameBuffer = c.c.CreateFramebuffer()
|
||||
c.colorBuffer = c.c.CreateRenderbuffer()
|
||||
}
|
||||
if !c.owner.visible {
|
||||
// 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")
|
||||
}
|
||||
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)
|
||||
if st := c.c.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
|
||||
return fmt.Errorf("framebuffer incomplete, status: %#x\n", st)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (Context, error) {
|
||||
return newContext(w)
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,ios,nometal
|
||||
|
||||
@import UIKit;
|
||||
@import OpenGLES;
|
||||
|
||||
#include "_cgo_export.h"
|
||||
|
||||
Class gio_layerClass(void) {
|
||||
return [CAEAGLLayer class];
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall/js"
|
||||
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/internal/gl"
|
||||
)
|
||||
|
||||
type glContext struct {
|
||||
ctx js.Value
|
||||
cnv js.Value
|
||||
w *window
|
||||
}
|
||||
|
||||
func newContext(w *window) (*glContext, error) {
|
||||
args := map[string]interface{}{
|
||||
// Enable low latency rendering.
|
||||
// See https://developers.google.com/web/updates/2019/05/desynchronized.
|
||||
"desynchronized": true,
|
||||
"preserveDrawingBuffer": true,
|
||||
}
|
||||
ctx := w.cnv.Call("getContext", "webgl2", args)
|
||||
if ctx.IsNull() {
|
||||
ctx = w.cnv.Call("getContext", "webgl", args)
|
||||
}
|
||||
if ctx.IsNull() {
|
||||
return nil, errors.New("app: webgl is not supported")
|
||||
}
|
||||
c := &glContext{
|
||||
ctx: ctx,
|
||||
cnv: w.cnv,
|
||||
w: w,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *glContext) RenderTarget() (gpu.RenderTarget, error) {
|
||||
if c.w.contextStatus != contextStatusOkay {
|
||||
return nil, gpu.ErrDeviceLost
|
||||
}
|
||||
return gpu.OpenGLRenderTarget{}, nil
|
||||
}
|
||||
|
||||
func (c *glContext) API() gpu.API {
|
||||
return gpu.OpenGL{Context: gl.Context(c.ctx)}
|
||||
}
|
||||
|
||||
func (c *glContext) Release() {
|
||||
}
|
||||
|
||||
func (c *glContext) Present() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *glContext) Lock() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *glContext) Unlock() {}
|
||||
|
||||
func (c *glContext) Refresh() error {
|
||||
switch c.w.contextStatus {
|
||||
case contextStatusLost:
|
||||
return errOutOfDate
|
||||
case contextStatusRestored:
|
||||
c.w.contextStatus = contextStatusOkay
|
||||
return gpu.ErrDeviceLost
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (context, error) {
|
||||
return newContext(w)
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build darwin && !ios && nometal
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/internal/gl"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -DGL_SILENCE_DEPRECATION -xobjective-c -fobjc-arc
|
||||
#cgo LDFLAGS: -framework OpenGL
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
#include <AppKit/AppKit.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createGLContext(void);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_setContextView(CFTypeRef ctx, CFTypeRef view);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_makeCurrentContext(CFTypeRef ctx);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_updateContext(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);
|
||||
|
||||
typedef void (*PFN_glFlush)(void);
|
||||
|
||||
static void glFlush(PFN_glFlush f) {
|
||||
f();
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type glContext struct {
|
||||
c *gl.Functions
|
||||
ctx C.CFTypeRef
|
||||
view C.CFTypeRef
|
||||
|
||||
glFlush C.PFN_glFlush
|
||||
}
|
||||
|
||||
func newContext(w *window) (*glContext, error) {
|
||||
clib := C.CString("/System/Library/Frameworks/OpenGL.framework/OpenGL")
|
||||
defer C.free(unsafe.Pointer(clib))
|
||||
lib, err := C.dlopen(clib, C.RTLD_NOW|C.RTLD_LOCAL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
csym := C.CString("glFlush")
|
||||
defer C.free(unsafe.Pointer(csym))
|
||||
glFlush := C.PFN_glFlush(C.dlsym(lib, csym))
|
||||
if glFlush == nil {
|
||||
return nil, errors.New("gl: missing symbol glFlush in the OpenGL framework")
|
||||
}
|
||||
view := w.contextView()
|
||||
ctx := C.gio_createGLContext()
|
||||
if ctx == 0 {
|
||||
return nil, errors.New("gl: failed to create NSOpenGLContext")
|
||||
}
|
||||
C.gio_setContextView(ctx, view)
|
||||
c := &glContext{
|
||||
ctx: ctx,
|
||||
view: view,
|
||||
glFlush: glFlush,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *glContext) RenderTarget() (gpu.RenderTarget, error) {
|
||||
return gpu.OpenGLRenderTarget{}, nil
|
||||
}
|
||||
|
||||
func (c *glContext) API() gpu.API {
|
||||
return gpu.OpenGL{}
|
||||
}
|
||||
|
||||
func (c *glContext) Release() {
|
||||
if c.ctx != 0 {
|
||||
C.gio_clearCurrentContext()
|
||||
C.CFRelease(c.ctx)
|
||||
c.ctx = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (c *glContext) Present() error {
|
||||
// Assume the caller already locked the context.
|
||||
C.glFlush(c.glFlush)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *glContext) Lock() error {
|
||||
// OpenGL contexts are implicit and thread-local. Lock the OS thread.
|
||||
runtime.LockOSThread()
|
||||
|
||||
C.gio_lockContext(c.ctx)
|
||||
C.gio_makeCurrentContext(c.ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *glContext) Unlock() {
|
||||
C.gio_clearCurrentContext()
|
||||
C.gio_unlockContext(c.ctx)
|
||||
}
|
||||
|
||||
func (c *glContext) Refresh() error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
C.gio_updateContext(c.ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (context, error) {
|
||||
return newContext(w)
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,!ios,nometal
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <OpenGL/OpenGL.h>
|
||||
#include "_cgo_export.h"
|
||||
|
||||
CALayer *gio_layerFactory(BOOL presentWithTrans) {
|
||||
@autoreleasepool {
|
||||
return [CALayer layer];
|
||||
}
|
||||
}
|
||||
|
||||
CFTypeRef gio_createGLContext(void) {
|
||||
@autoreleasepool {
|
||||
NSOpenGLPixelFormatAttribute attr[] = {
|
||||
NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
|
||||
NSOpenGLPFAColorSize, 24,
|
||||
NSOpenGLPFAAccelerated,
|
||||
// Opt-in to automatic GPU switching. CGL-only property.
|
||||
kCGLPFASupportsAutomaticGraphicsSwitching,
|
||||
NSOpenGLPFAAllowOfflineRenderers,
|
||||
0
|
||||
};
|
||||
NSOpenGLPixelFormat *pixFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr];
|
||||
|
||||
NSOpenGLContext *ctx = [[NSOpenGLContext alloc] initWithFormat:pixFormat shareContext: nil];
|
||||
return CFBridgingRetain(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
void gio_setContextView(CFTypeRef ctxRef, CFTypeRef viewRef) {
|
||||
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
[view setWantsBestResolutionOpenGLSurface:YES];
|
||||
[ctx setView:view];
|
||||
}
|
||||
|
||||
void gio_clearCurrentContext(void) {
|
||||
@autoreleasepool {
|
||||
[NSOpenGLContext clearCurrentContext];
|
||||
}
|
||||
}
|
||||
|
||||
void gio_updateContext(CFTypeRef ctxRef) {
|
||||
@autoreleasepool {
|
||||
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
|
||||
[ctx update];
|
||||
}
|
||||
}
|
||||
|
||||
void gio_makeCurrentContext(CFTypeRef ctxRef) {
|
||||
@autoreleasepool {
|
||||
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
|
||||
[ctx makeCurrentContext];
|
||||
}
|
||||
}
|
||||
|
||||
void gio_lockContext(CFTypeRef ctxRef) {
|
||||
@autoreleasepool {
|
||||
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
|
||||
CGLLockContext([ctx CGLContextObj]);
|
||||
}
|
||||
}
|
||||
|
||||
void gio_unlockContext(CFTypeRef ctxRef) {
|
||||
@autoreleasepool {
|
||||
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
|
||||
CGLUnlockContext([ctx CGLContextObj]);
|
||||
}
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
|
||||
"gioui.org/io/input"
|
||||
"gioui.org/io/key"
|
||||
)
|
||||
|
||||
type editorState struct {
|
||||
input.EditorState
|
||||
compose key.Range
|
||||
}
|
||||
|
||||
func (e *editorState) Replace(r key.Range, text string) {
|
||||
if r.Start > r.End {
|
||||
r.Start, r.End = r.End, r.Start
|
||||
}
|
||||
runes := []rune(text)
|
||||
newEnd := r.Start + len(runes)
|
||||
adjust := func(pos int) int {
|
||||
switch {
|
||||
case newEnd < pos && pos <= r.End:
|
||||
return newEnd
|
||||
case r.End < pos:
|
||||
diff := newEnd - r.End
|
||||
return pos + diff
|
||||
}
|
||||
return pos
|
||||
}
|
||||
e.Selection.Start = adjust(e.Selection.Start)
|
||||
e.Selection.End = adjust(e.Selection.End)
|
||||
if e.compose.Start != -1 {
|
||||
e.compose.Start = adjust(e.compose.Start)
|
||||
e.compose.End = adjust(e.compose.End)
|
||||
}
|
||||
s := e.Snippet
|
||||
if r.End < s.Start || r.Start > s.End {
|
||||
// Discard snippet if it doesn't overlap with replacement.
|
||||
s = key.Snippet{
|
||||
Range: key.Range{
|
||||
Start: r.Start,
|
||||
End: r.Start,
|
||||
},
|
||||
}
|
||||
}
|
||||
var newSnippet []rune
|
||||
snippet := []rune(s.Text)
|
||||
// Append first part of existing snippet.
|
||||
if end := r.Start - s.Start; end > 0 {
|
||||
newSnippet = append(newSnippet, snippet[:end]...)
|
||||
}
|
||||
// Append replacement.
|
||||
newSnippet = append(newSnippet, runes...)
|
||||
// Append last part of existing snippet.
|
||||
if start := r.End; start < s.End {
|
||||
newSnippet = append(newSnippet, snippet[start-s.Start:]...)
|
||||
}
|
||||
// Adjust snippet range to include replacement.
|
||||
if r.Start < s.Start {
|
||||
s.Start = r.Start
|
||||
}
|
||||
s.End = s.Start + len(newSnippet)
|
||||
s.Text = string(newSnippet)
|
||||
e.Snippet = s
|
||||
}
|
||||
|
||||
// UTF16Index converts the given index in runes into an index in utf16 characters.
|
||||
func (e *editorState) UTF16Index(runes int) int {
|
||||
if runes == -1 {
|
||||
return -1
|
||||
}
|
||||
if runes < e.Snippet.Start {
|
||||
// Assume runes before sippet are one UTF-16 character each.
|
||||
return runes
|
||||
}
|
||||
chars := e.Snippet.Start
|
||||
runes -= e.Snippet.Start
|
||||
for _, r := range e.Snippet.Text {
|
||||
if runes == 0 {
|
||||
break
|
||||
}
|
||||
runes--
|
||||
chars++
|
||||
if r1, _ := utf16.EncodeRune(r); r1 != unicode.ReplacementChar {
|
||||
chars++
|
||||
}
|
||||
}
|
||||
// Assume runes after snippets are one UTF-16 character each.
|
||||
return chars + runes
|
||||
}
|
||||
|
||||
// RunesIndex converts the given index in utf16 characters to an index in runes.
|
||||
func (e *editorState) RunesIndex(chars int) int {
|
||||
if chars == -1 {
|
||||
return -1
|
||||
}
|
||||
if chars < e.Snippet.Start {
|
||||
// Assume runes before offset are one UTF-16 character each.
|
||||
return chars
|
||||
}
|
||||
runes := e.Snippet.Start
|
||||
chars -= e.Snippet.Start
|
||||
for _, r := range e.Snippet.Text {
|
||||
if chars == 0 {
|
||||
break
|
||||
}
|
||||
chars--
|
||||
runes++
|
||||
if r1, _ := utf16.EncodeRune(r); r1 != unicode.ReplacementChar {
|
||||
chars--
|
||||
}
|
||||
}
|
||||
// Assume runes after snippets are one UTF-16 character each.
|
||||
return runes + chars
|
||||
}
|
||||
|
||||
// areSnippetsConsistent reports whether the content of the old snippet is
|
||||
// consistent with the content of the new.
|
||||
func areSnippetsConsistent(old, new key.Snippet) bool {
|
||||
// Compute the overlapping range.
|
||||
r := old.Range
|
||||
r.Start = max(r.Start, new.Start)
|
||||
r.End = max(r.End, r.Start)
|
||||
r.End = min(r.End, new.End)
|
||||
return snippetSubstring(old, r) == snippetSubstring(new, r)
|
||||
}
|
||||
|
||||
func snippetSubstring(s key.Snippet, r key.Range) string {
|
||||
for r.Start > s.Start && r.Start < s.End {
|
||||
_, n := utf8.DecodeRuneInString(s.Text)
|
||||
s.Text = s.Text[n:]
|
||||
s.Start++
|
||||
}
|
||||
for r.End < s.End && r.End > s.Start {
|
||||
_, n := utf8.DecodeLastRuneInString(s.Text)
|
||||
s.Text = s.Text[:len(s.Text)-n]
|
||||
s.End--
|
||||
}
|
||||
return s.Text
|
||||
}
|
||||
+997
@@ -0,0 +1,997 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
syscall "golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type CompositionForm struct {
|
||||
dwStyle uint32
|
||||
ptCurrentPos Point
|
||||
rcArea Rect
|
||||
}
|
||||
|
||||
type CandidateForm struct {
|
||||
dwIndex uint32
|
||||
dwStyle uint32
|
||||
ptCurrentPos Point
|
||||
rcArea Rect
|
||||
}
|
||||
|
||||
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 Margins struct {
|
||||
CxLeftWidth int32
|
||||
CxRightWidth int32
|
||||
CyTopHeight int32
|
||||
CyBottomHeight int32
|
||||
}
|
||||
|
||||
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 MinMaxInfo struct {
|
||||
PtReserved Point
|
||||
PtMaxSize Point
|
||||
PtMaxPosition Point
|
||||
PtMinTrackSize Point
|
||||
PtMaxTrackSize Point
|
||||
}
|
||||
|
||||
type NCCalcSizeParams struct {
|
||||
Rgrc [3]Rect
|
||||
LpPos *WindowPos
|
||||
}
|
||||
|
||||
type WindowPos struct {
|
||||
HWND syscall.Handle
|
||||
HWNDInsertAfter syscall.Handle
|
||||
x int32
|
||||
y int32
|
||||
cx int32
|
||||
cy int32
|
||||
flags uint32
|
||||
}
|
||||
|
||||
type WindowPlacement struct {
|
||||
length uint32
|
||||
flags uint32
|
||||
showCmd uint32
|
||||
ptMinPosition Point
|
||||
ptMaxPosition Point
|
||||
rcNormalPosition Rect
|
||||
rcDevice Rect
|
||||
}
|
||||
|
||||
type MonitorInfo struct {
|
||||
cbSize uint32
|
||||
Monitor Rect
|
||||
WorkArea Rect
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
type CopyDataStruct struct {
|
||||
DwData uintptr
|
||||
CbData uint32
|
||||
LpData uintptr
|
||||
}
|
||||
|
||||
type POINTER_INPUT_TYPE int32
|
||||
|
||||
const (
|
||||
PT_POINTER POINTER_INPUT_TYPE = 1
|
||||
PT_TOUCH POINTER_INPUT_TYPE = 2
|
||||
PT_PEN POINTER_INPUT_TYPE = 3
|
||||
PT_MOUSE POINTER_INPUT_TYPE = 4
|
||||
PT_TOUCHPAD POINTER_INPUT_TYPE = 5
|
||||
)
|
||||
|
||||
type POINTER_INFO_POINTER_FLAGS int32
|
||||
|
||||
const (
|
||||
POINTER_FLAG_NEW POINTER_INFO_POINTER_FLAGS = 0x00000001
|
||||
POINTER_FLAG_INRANGE POINTER_INFO_POINTER_FLAGS = 0x00000002
|
||||
POINTER_FLAG_INCONTACT POINTER_INFO_POINTER_FLAGS = 0x00000004
|
||||
POINTER_FLAG_FIRSTBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000010
|
||||
POINTER_FLAG_SECONDBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000020
|
||||
POINTER_FLAG_THIRDBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000040
|
||||
POINTER_FLAG_FOURTHBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000080
|
||||
POINTER_FLAG_FIFTHBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000100
|
||||
POINTER_FLAG_PRIMARY POINTER_INFO_POINTER_FLAGS = 0x00002000
|
||||
POINTER_FLAG_CONFIDENCE POINTER_INFO_POINTER_FLAGS = 0x00004000
|
||||
POINTER_FLAG_CANCELED POINTER_INFO_POINTER_FLAGS = 0x00008000
|
||||
POINTER_FLAG_DOWN POINTER_INFO_POINTER_FLAGS = 0x00010000
|
||||
POINTER_FLAG_UPDATE POINTER_INFO_POINTER_FLAGS = 0x00020000
|
||||
POINTER_FLAG_UP POINTER_INFO_POINTER_FLAGS = 0x00040000
|
||||
POINTER_FLAG_WHEEL POINTER_INFO_POINTER_FLAGS = 0x00080000
|
||||
POINTER_FLAG_HWHEEL POINTER_INFO_POINTER_FLAGS = 0x00100000
|
||||
POINTER_FLAG_CAPTURECHANGED POINTER_INFO_POINTER_FLAGS = 0x00200000
|
||||
POINTER_FLAG_HASTRANSFORM POINTER_INFO_POINTER_FLAGS = 0x00400000
|
||||
)
|
||||
|
||||
type POINTER_BUTTON_CHANGE_TYPE int32
|
||||
|
||||
const (
|
||||
POINTER_CHANGE_NONE POINTER_BUTTON_CHANGE_TYPE = 0
|
||||
POINTER_CHANGE_FIRSTBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 1
|
||||
POINTER_CHANGE_FIRSTBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 2
|
||||
POINTER_CHANGE_SECONDBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 3
|
||||
POINTER_CHANGE_SECONDBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 4
|
||||
POINTER_CHANGE_THIRDBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 5
|
||||
POINTER_CHANGE_THIRDBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 6
|
||||
POINTER_CHANGE_FOURTHBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 7
|
||||
POINTER_CHANGE_FOURTHBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 8
|
||||
POINTER_CHANGE_FIFTHBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 9
|
||||
POINTER_CHANGE_FIFTHBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 10
|
||||
)
|
||||
|
||||
type PointerInfo struct {
|
||||
PointerType POINTER_INPUT_TYPE
|
||||
PointerId uint32
|
||||
FrameId uint32
|
||||
PointerFlags POINTER_INFO_POINTER_FLAGS
|
||||
SourceDevice syscall.Handle
|
||||
HwndTarget syscall.Handle
|
||||
PtPixelLocation Point
|
||||
PtHimetricLocation Point
|
||||
PtPixelLocationRaw Point
|
||||
PtHimetricLocationRaw Point
|
||||
DwTime uint32
|
||||
HistoryCount uint32
|
||||
InputData int32
|
||||
DwKeyStates uint32
|
||||
PerformanceCount uint64
|
||||
ButtonChangeType POINTER_BUTTON_CHANGE_TYPE
|
||||
}
|
||||
|
||||
const (
|
||||
TRUE = 1
|
||||
|
||||
CPS_CANCEL = 0x0004
|
||||
|
||||
CS_HREDRAW = 0x0002
|
||||
CS_INSERTCHAR = 0x2000
|
||||
CS_NOMOVECARET = 0x4000
|
||||
CS_VREDRAW = 0x0001
|
||||
CS_OWNDC = 0x0020
|
||||
|
||||
CW_USEDEFAULT = -2147483648
|
||||
|
||||
GWL_STYLE = ^(uintptr(16) - 1) // -16
|
||||
|
||||
GCS_COMPSTR = 0x0008
|
||||
GCS_COMPREADSTR = 0x0001
|
||||
GCS_CURSORPOS = 0x0080
|
||||
GCS_DELTASTART = 0x0100
|
||||
GCS_RESULTREADSTR = 0x0200
|
||||
GCS_RESULTSTR = 0x0800
|
||||
|
||||
CFS_POINT = 0x0002
|
||||
CFS_CANDIDATEPOS = 0x0040
|
||||
|
||||
HWND_TOP = syscall.Handle(0)
|
||||
HWND_TOPMOST = ^(syscall.Handle(1) - 1) // -1
|
||||
HWND_NOTOPMOST = ^(syscall.Handle(2) - 1) // -2
|
||||
|
||||
HTCAPTION = 2
|
||||
HTCLIENT = 1
|
||||
HTLEFT = 10
|
||||
HTRIGHT = 11
|
||||
HTTOP = 12
|
||||
HTTOPLEFT = 13
|
||||
HTTOPRIGHT = 14
|
||||
HTBOTTOM = 15
|
||||
HTBOTTOMLEFT = 16
|
||||
HTBOTTOMRIGHT = 17
|
||||
|
||||
IDC_APPSTARTING = 32650 // Standard arrow and small hourglass
|
||||
IDC_ARROW = 32512 // Standard arrow
|
||||
IDC_CROSS = 32515 // Crosshair
|
||||
IDC_HAND = 32649 // Hand
|
||||
IDC_HELP = 32651 // Arrow and question mark
|
||||
IDC_IBEAM = 32513 // I-beam
|
||||
IDC_NO = 32648 // Slashed circle
|
||||
IDC_SIZEALL = 32646 // Four-pointed arrow pointing north, south, east, and west
|
||||
IDC_SIZENESW = 32643 // Double-pointed arrow pointing northeast and southwest
|
||||
IDC_SIZENS = 32645 // Double-pointed arrow pointing north and south
|
||||
IDC_SIZENWSE = 32642 // Double-pointed arrow pointing northwest and southeast
|
||||
IDC_SIZEWE = 32644 // Double-pointed arrow pointing west and east
|
||||
IDC_UPARROW = 32516 // Vertical arrow
|
||||
IDC_WAIT = 32514 // Hour
|
||||
|
||||
INFINITE = 0xFFFFFFFF
|
||||
|
||||
LOGPIXELSX = 88
|
||||
|
||||
MDT_EFFECTIVE_DPI = 0
|
||||
|
||||
MONITOR_DEFAULTTOPRIMARY = 1
|
||||
|
||||
NI_COMPOSITIONSTR = 0x0015
|
||||
|
||||
SIZE_MAXIMIZED = 2
|
||||
SIZE_MINIMIZED = 1
|
||||
SIZE_RESTORED = 0
|
||||
|
||||
SCS_SETSTR = GCS_COMPREADSTR | GCS_COMPSTR
|
||||
|
||||
SM_CXSIZEFRAME = 32
|
||||
SM_CYSIZEFRAME = 33
|
||||
|
||||
SW_SHOWDEFAULT = 10
|
||||
SW_SHOWMINIMIZED = 2
|
||||
SW_SHOWMAXIMIZED = 3
|
||||
SW_SHOWNORMAL = 1
|
||||
SW_SHOW = 5
|
||||
|
||||
SWP_FRAMECHANGED = 0x0020
|
||||
SWP_NOMOVE = 0x0002
|
||||
SWP_NOOWNERZORDER = 0x0200
|
||||
SWP_NOSIZE = 0x0001
|
||||
SWP_NOZORDER = 0x0004
|
||||
SWP_SHOWWINDOW = 0x0040
|
||||
|
||||
USER_TIMER_MINIMUM = 0x0000000A
|
||||
|
||||
VK_CONTROL = 0x11
|
||||
VK_LWIN = 0x5B
|
||||
VK_MENU = 0x12
|
||||
VK_RWIN = 0x5C
|
||||
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_SPACE = 0x20
|
||||
VK_TAB = 0x09
|
||||
VK_UP = 0x26
|
||||
|
||||
VK_F1 = 0x70
|
||||
VK_F2 = 0x71
|
||||
VK_F3 = 0x72
|
||||
VK_F4 = 0x73
|
||||
VK_F5 = 0x74
|
||||
VK_F6 = 0x75
|
||||
VK_F7 = 0x76
|
||||
VK_F8 = 0x77
|
||||
VK_F9 = 0x78
|
||||
VK_F10 = 0x79
|
||||
VK_F11 = 0x7A
|
||||
VK_F12 = 0x7B
|
||||
|
||||
VK_OEM_1 = 0xba
|
||||
VK_OEM_PLUS = 0xbb
|
||||
VK_OEM_COMMA = 0xbc
|
||||
VK_OEM_MINUS = 0xbd
|
||||
VK_OEM_PERIOD = 0xbe
|
||||
VK_OEM_2 = 0xbf
|
||||
VK_OEM_3 = 0xc0
|
||||
VK_OEM_4 = 0xdb
|
||||
VK_OEM_5 = 0xdc
|
||||
VK_OEM_6 = 0xdd
|
||||
VK_OEM_7 = 0xde
|
||||
VK_OEM_102 = 0xe2
|
||||
|
||||
UNICODE_NOCHAR = 65535
|
||||
|
||||
WM_CANCELMODE = 0x001F
|
||||
WM_CHAR = 0x0102
|
||||
WM_CLOSE = 0x0010
|
||||
WM_COPYDATA = 0x004A
|
||||
WM_CREATE = 0x0001
|
||||
WM_DPICHANGED = 0x02E0
|
||||
WM_DESTROY = 0x0002
|
||||
WM_ERASEBKGND = 0x0014
|
||||
WM_GETMINMAXINFO = 0x0024
|
||||
WM_IME_COMPOSITION = 0x010F
|
||||
WM_IME_ENDCOMPOSITION = 0x010E
|
||||
WM_IME_STARTCOMPOSITION = 0x010D
|
||||
WM_KEYDOWN = 0x0100
|
||||
WM_KEYUP = 0x0101
|
||||
WM_KILLFOCUS = 0x0008
|
||||
WM_LBUTTONDOWN = 0x0201
|
||||
WM_LBUTTONUP = 0x0202
|
||||
WM_MBUTTONDOWN = 0x0207
|
||||
WM_MBUTTONUP = 0x0208
|
||||
WM_MOUSEMOVE = 0x0200
|
||||
WM_MOUSEWHEEL = 0x020A
|
||||
WM_MOUSEHWHEEL = 0x020E
|
||||
WM_NCACTIVATE = 0x0086
|
||||
WM_NCHITTEST = 0x0084
|
||||
WM_NCCALCSIZE = 0x0083
|
||||
WM_PAINT = 0x000F
|
||||
WM_POINTERCAPTURECHANGED = 0x024C
|
||||
WM_POINTERDOWN = 0x0246
|
||||
WM_POINTERUP = 0x0247
|
||||
WM_POINTERUPDATE = 0x0245
|
||||
WM_POINTERWHEEL = 0x024E
|
||||
WM_POINTERHWHEEL = 0x024F
|
||||
WM_QUIT = 0x0012
|
||||
WM_RBUTTONDOWN = 0x0204
|
||||
WM_RBUTTONUP = 0x0205
|
||||
WM_SETCURSOR = 0x0020
|
||||
WM_SETFOCUS = 0x0007
|
||||
WM_SHOWWINDOW = 0x0018
|
||||
WM_SIZE = 0x0005
|
||||
WM_STYLECHANGED = 0x007D
|
||||
WM_SYSKEYDOWN = 0x0104
|
||||
WM_SYSKEYUP = 0x0105
|
||||
WM_TIMER = 0x0113
|
||||
WM_UNICHAR = 0x0109
|
||||
WM_USER = 0x0400
|
||||
WM_WINDOWPOSCHANGED = 0x0047
|
||||
|
||||
WS_CLIPCHILDREN = 0x02000000
|
||||
WS_CLIPSIBLINGS = 0x04000000
|
||||
WS_MAXIMIZE = 0x01000000
|
||||
WS_ICONIC = 0x20000000
|
||||
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
|
||||
|
||||
GHND = 0x0042
|
||||
|
||||
CF_UNICODETEXT = 13
|
||||
IMAGE_BITMAP = 0
|
||||
IMAGE_ICON = 1
|
||||
IMAGE_CURSOR = 2
|
||||
|
||||
LR_CREATEDIBSECTION = 0x00002000
|
||||
LR_DEFAULTCOLOR = 0x00000000
|
||||
LR_DEFAULTSIZE = 0x00000040
|
||||
LR_LOADFROMFILE = 0x00000010
|
||||
LR_LOADMAP3DCOLORS = 0x00001000
|
||||
LR_LOADTRANSPARENT = 0x00000020
|
||||
LR_MONOCHROME = 0x00000001
|
||||
LR_SHARED = 0x00008000
|
||||
LR_VGACOLOR = 0x00000080
|
||||
)
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazySystemDLL("kernel32.dll")
|
||||
_GetModuleHandleW = kernel32.NewProc("GetModuleHandleW")
|
||||
_GlobalAlloc = kernel32.NewProc("GlobalAlloc")
|
||||
_GlobalFree = kernel32.NewProc("GlobalFree")
|
||||
_GlobalLock = kernel32.NewProc("GlobalLock")
|
||||
_GlobalUnlock = kernel32.NewProc("GlobalUnlock")
|
||||
|
||||
user32 = syscall.NewLazySystemDLL("user32.dll")
|
||||
_AdjustWindowRectEx = user32.NewProc("AdjustWindowRectEx")
|
||||
_CallMsgFilter = user32.NewProc("CallMsgFilterW")
|
||||
_CloseClipboard = user32.NewProc("CloseClipboard")
|
||||
_CreateWindowEx = user32.NewProc("CreateWindowExW")
|
||||
_DefWindowProc = user32.NewProc("DefWindowProcW")
|
||||
_DestroyWindow = user32.NewProc("DestroyWindow")
|
||||
_DispatchMessage = user32.NewProc("DispatchMessageW")
|
||||
_FindWindow = user32.NewProc("FindWindowW")
|
||||
_EmptyClipboard = user32.NewProc("EmptyClipboard")
|
||||
_EnableMouseInPointer = user32.NewProc("EnableMouseInPointer")
|
||||
_GetWindowRect = user32.NewProc("GetWindowRect")
|
||||
_GetClientRect = user32.NewProc("GetClientRect")
|
||||
_GetClipboardData = user32.NewProc("GetClipboardData")
|
||||
_GetDC = user32.NewProc("GetDC")
|
||||
_GetDpiForWindow = user32.NewProc("GetDpiForWindow")
|
||||
_GetKeyState = user32.NewProc("GetKeyState")
|
||||
_GetMessage = user32.NewProc("GetMessageW")
|
||||
_GetMessageTime = user32.NewProc("GetMessageTime")
|
||||
_GetMonitorInfo = user32.NewProc("GetMonitorInfoW")
|
||||
_GetPointerInfo = user32.NewProc("GetPointerInfo")
|
||||
_GetSystemMetrics = user32.NewProc("GetSystemMetrics")
|
||||
_GetWindowLong = user32.NewProc("GetWindowLongPtrW")
|
||||
_GetWindowLong32 = user32.NewProc("GetWindowLongW")
|
||||
_GetWindowPlacement = user32.NewProc("GetWindowPlacement")
|
||||
_KillTimer = user32.NewProc("KillTimer")
|
||||
_LoadCursor = user32.NewProc("LoadCursorW")
|
||||
_LoadImage = user32.NewProc("LoadImageW")
|
||||
_MonitorFromPoint = user32.NewProc("MonitorFromPoint")
|
||||
_MonitorFromWindow = user32.NewProc("MonitorFromWindow")
|
||||
_MoveWindow = user32.NewProc("MoveWindow")
|
||||
_MsgWaitForMultipleObjectsEx = user32.NewProc("MsgWaitForMultipleObjectsEx")
|
||||
_OpenClipboard = user32.NewProc("OpenClipboard")
|
||||
_PeekMessage = user32.NewProc("PeekMessageW")
|
||||
_PostMessage = user32.NewProc("PostMessageW")
|
||||
_PostQuitMessage = user32.NewProc("PostQuitMessage")
|
||||
_ReleaseCapture = user32.NewProc("ReleaseCapture")
|
||||
_RegisterClassExW = user32.NewProc("RegisterClassExW")
|
||||
_RegisterTouchWindow = user32.NewProc("RegisterTouchWindow")
|
||||
_ReleaseDC = user32.NewProc("ReleaseDC")
|
||||
_ScreenToClient = user32.NewProc("ScreenToClient")
|
||||
_ShowWindow = user32.NewProc("ShowWindow")
|
||||
_SendMessage = user32.NewProc("SendMessageW")
|
||||
_SetCapture = user32.NewProc("SetCapture")
|
||||
_SetCursor = user32.NewProc("SetCursor")
|
||||
_SetClipboardData = user32.NewProc("SetClipboardData")
|
||||
_SetForegroundWindow = user32.NewProc("SetForegroundWindow")
|
||||
_SetFocus = user32.NewProc("SetFocus")
|
||||
_SetProcessDPIAware = user32.NewProc("SetProcessDPIAware")
|
||||
_SetTimer = user32.NewProc("SetTimer")
|
||||
_SetWindowLong = user32.NewProc("SetWindowLongPtrW")
|
||||
_SetWindowLong32 = user32.NewProc("SetWindowLongW")
|
||||
_SetWindowPlacement = user32.NewProc("SetWindowPlacement")
|
||||
_SetWindowPos = user32.NewProc("SetWindowPos")
|
||||
_SetWindowText = user32.NewProc("SetWindowTextW")
|
||||
_TranslateMessage = user32.NewProc("TranslateMessage")
|
||||
_UnregisterClass = user32.NewProc("UnregisterClassW")
|
||||
_UpdateWindow = user32.NewProc("UpdateWindow")
|
||||
|
||||
shcore = syscall.NewLazySystemDLL("shcore")
|
||||
_GetDpiForMonitor = shcore.NewProc("GetDpiForMonitor")
|
||||
|
||||
gdi32 = syscall.NewLazySystemDLL("gdi32")
|
||||
_GetDeviceCaps = gdi32.NewProc("GetDeviceCaps")
|
||||
|
||||
imm32 = syscall.NewLazySystemDLL("imm32")
|
||||
_ImmGetContext = imm32.NewProc("ImmGetContext")
|
||||
_ImmGetCompositionString = imm32.NewProc("ImmGetCompositionStringW")
|
||||
_ImmNotifyIME = imm32.NewProc("ImmNotifyIME")
|
||||
_ImmReleaseContext = imm32.NewProc("ImmReleaseContext")
|
||||
_ImmSetCandidateWindow = imm32.NewProc("ImmSetCandidateWindow")
|
||||
_ImmSetCompositionWindow = imm32.NewProc("ImmSetCompositionWindow")
|
||||
|
||||
dwmapi = syscall.NewLazySystemDLL("dwmapi")
|
||||
_DwmExtendFrameIntoClientArea = dwmapi.NewProc("DwmExtendFrameIntoClientArea")
|
||||
)
|
||||
|
||||
func AdjustWindowRectEx(r *Rect, dwStyle uint32, bMenu int, dwExStyle uint32) {
|
||||
_AdjustWindowRectEx.Call(uintptr(unsafe.Pointer(r)), uintptr(dwStyle), uintptr(bMenu), uintptr(dwExStyle))
|
||||
}
|
||||
|
||||
func CallMsgFilter(m *Msg, nCode uintptr) bool {
|
||||
r, _, _ := _CallMsgFilter.Call(uintptr(unsafe.Pointer(m)), nCode)
|
||||
return r != 0
|
||||
}
|
||||
|
||||
func CloseClipboard() error {
|
||||
r, _, err := _CloseClipboard.Call()
|
||||
if r == 0 {
|
||||
return fmt.Errorf("CloseClipboard: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateWindowEx(dwExStyle uint32, lpClassName uint16, lpWindowName string, dwStyle uint32, x, y, w, h int32, hWndParent, hMenu, hInstance syscall.Handle, lpParam uintptr) (syscall.Handle, error) {
|
||||
wname, err := syscall.UTF16PtrFromString(lpWindowName)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("CreateWindowEx failed: %v", err)
|
||||
}
|
||||
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))
|
||||
if hwnd == 0 {
|
||||
return 0, fmt.Errorf("CreateWindowEx failed: %v", err)
|
||||
}
|
||||
return syscall.Handle(hwnd), nil
|
||||
}
|
||||
|
||||
func GetPointerInfo(pointerId uint32) (PointerInfo, error) {
|
||||
var info PointerInfo
|
||||
r1, _, err := _GetPointerInfo.Call(uintptr(pointerId), uintptr(unsafe.Pointer(&info)))
|
||||
if r1 == 0 {
|
||||
return PointerInfo{}, fmt.Errorf("GetPointerInfo failed: %v", err)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func RegisterTouchWindow(hwnd syscall.Handle, flags uint32) error {
|
||||
r1, _, err := _RegisterTouchWindow.Call(uintptr(hwnd), uintptr(flags))
|
||||
if r1 == 0 {
|
||||
return fmt.Errorf("RegisterTouchWindow failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func EnableMouseInPointer(enable uint) error {
|
||||
r1, _, err := _EnableMouseInPointer.Call(uintptr(enable))
|
||||
if r1 == 0 {
|
||||
return fmt.Errorf("EnableMouseInPointer failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DefWindowProc(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
|
||||
r, _, _ := _DefWindowProc.Call(uintptr(hwnd), uintptr(msg), wparam, lparam)
|
||||
return r
|
||||
}
|
||||
|
||||
func DestroyWindow(hwnd syscall.Handle) {
|
||||
_DestroyWindow.Call(uintptr(hwnd))
|
||||
}
|
||||
|
||||
func DispatchMessage(m *Msg) {
|
||||
_DispatchMessage.Call(uintptr(unsafe.Pointer(m)))
|
||||
}
|
||||
|
||||
func DwmExtendFrameIntoClientArea(hwnd syscall.Handle, margins Margins) error {
|
||||
r, _, _ := _DwmExtendFrameIntoClientArea.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&margins)))
|
||||
if r != 0 {
|
||||
return fmt.Errorf("DwmExtendFrameIntoClientArea: %#x", r)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func EmptyClipboard() error {
|
||||
r, _, err := _EmptyClipboard.Call()
|
||||
if r == 0 {
|
||||
return fmt.Errorf("EmptyClipboard: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func FindWindow(lpClassName string) (syscall.Handle, error) {
|
||||
className, err := syscall.UTF16PtrFromString(lpClassName)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("FindWindow failed: %v", err)
|
||||
}
|
||||
hwnd, _, err := _FindWindow.Call(uintptr(unsafe.Pointer(className)), 0)
|
||||
if hwnd == 0 {
|
||||
return 0, fmt.Errorf("FindWindow failed: %v", err)
|
||||
}
|
||||
return syscall.Handle(hwnd), nil
|
||||
}
|
||||
|
||||
func GetWindowRect(hwnd syscall.Handle) Rect {
|
||||
var r Rect
|
||||
_GetWindowRect.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&r)))
|
||||
return r
|
||||
}
|
||||
|
||||
func GetClientRect(hwnd syscall.Handle) Rect {
|
||||
var r Rect
|
||||
_GetClientRect.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&r)))
|
||||
return r
|
||||
}
|
||||
|
||||
func GetClipboardData(format uint32) (syscall.Handle, error) {
|
||||
r, _, err := _GetClipboardData.Call(uintptr(format))
|
||||
if r == 0 {
|
||||
return 0, fmt.Errorf("GetClipboardData: %v", err)
|
||||
}
|
||||
return syscall.Handle(r), nil
|
||||
}
|
||||
|
||||
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 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 getDeviceCaps(hdc syscall.Handle, index int32) int {
|
||||
c, _, _ := _GetDeviceCaps.Call(uintptr(hdc), uintptr(index))
|
||||
return int(c)
|
||||
}
|
||||
|
||||
func getDpiForMonitor(hmonitor syscall.Handle, dpiType uint32) int {
|
||||
var dpiX, dpiY uintptr
|
||||
_GetDpiForMonitor.Call(uintptr(hmonitor), uintptr(dpiType), uintptr(unsafe.Pointer(&dpiX)), uintptr(unsafe.Pointer(&dpiY)))
|
||||
return int(dpiX)
|
||||
}
|
||||
|
||||
// GetSystemDPI returns the effective DPI of the system.
|
||||
func GetSystemDPI() int {
|
||||
// Check for GetDpiForMonitor, introduced in Windows 8.1.
|
||||
if _GetDpiForMonitor.Find() == nil {
|
||||
hmon := monitorFromPoint(Point{}, MONITOR_DEFAULTTOPRIMARY)
|
||||
return getDpiForMonitor(hmon, MDT_EFFECTIVE_DPI)
|
||||
} else {
|
||||
// Fall back to the physical device DPI.
|
||||
screenDC, err := GetDC(0)
|
||||
if err != nil {
|
||||
return 96
|
||||
}
|
||||
defer ReleaseDC(screenDC)
|
||||
return getDeviceCaps(screenDC, LOGPIXELSX)
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
return int32(r)
|
||||
}
|
||||
|
||||
func GetMessageTime() time.Duration {
|
||||
r, _, _ := _GetMessageTime.Call()
|
||||
return time.Duration(r) * time.Millisecond
|
||||
}
|
||||
|
||||
func GetSystemMetrics(nIndex int) int {
|
||||
r, _, _ := _GetSystemMetrics.Call(uintptr(nIndex))
|
||||
return int(r)
|
||||
}
|
||||
|
||||
// GetWindowDPI returns the effective DPI of the window.
|
||||
func GetWindowDPI(hwnd syscall.Handle) int {
|
||||
// Check for GetDpiForWindow, introduced in Windows 10.
|
||||
if _GetDpiForWindow.Find() == nil {
|
||||
dpi, _, _ := _GetDpiForWindow.Call(uintptr(hwnd))
|
||||
return int(dpi)
|
||||
} else {
|
||||
return GetSystemDPI()
|
||||
}
|
||||
}
|
||||
|
||||
func GetWindowPlacement(hwnd syscall.Handle) *WindowPlacement {
|
||||
var wp WindowPlacement
|
||||
wp.length = uint32(unsafe.Sizeof(wp))
|
||||
_GetWindowPlacement.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&wp)))
|
||||
return &wp
|
||||
}
|
||||
|
||||
func GetMonitorInfo(hwnd syscall.Handle) MonitorInfo {
|
||||
var mi MonitorInfo
|
||||
mi.cbSize = uint32(unsafe.Sizeof(mi))
|
||||
v, _, _ := _MonitorFromWindow.Call(uintptr(hwnd), MONITOR_DEFAULTTOPRIMARY)
|
||||
_GetMonitorInfo.Call(v, uintptr(unsafe.Pointer(&mi)))
|
||||
return mi
|
||||
}
|
||||
|
||||
func GetWindowLong(hwnd syscall.Handle, index uintptr) (val uintptr) {
|
||||
if runtime.GOARCH == "386" {
|
||||
val, _, _ = _GetWindowLong32.Call(uintptr(hwnd), index)
|
||||
} else {
|
||||
val, _, _ = _GetWindowLong.Call(uintptr(hwnd), index)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ImmGetContext(hwnd syscall.Handle) syscall.Handle {
|
||||
h, _, _ := _ImmGetContext.Call(uintptr(hwnd))
|
||||
return syscall.Handle(h)
|
||||
}
|
||||
|
||||
func ImmReleaseContext(hwnd, imc syscall.Handle) {
|
||||
_ImmReleaseContext.Call(uintptr(hwnd), uintptr(imc))
|
||||
}
|
||||
|
||||
func ImmNotifyIME(imc syscall.Handle, action, index, value int) {
|
||||
_ImmNotifyIME.Call(uintptr(imc), uintptr(action), uintptr(index), uintptr(value))
|
||||
}
|
||||
|
||||
func ImmGetCompositionString(imc syscall.Handle, key int) string {
|
||||
size, _, _ := _ImmGetCompositionString.Call(uintptr(imc), uintptr(key), 0, 0)
|
||||
if int32(size) <= 0 {
|
||||
return ""
|
||||
}
|
||||
u16 := make([]uint16, size/unsafe.Sizeof(uint16(0)))
|
||||
_ImmGetCompositionString.Call(uintptr(imc), uintptr(key), uintptr(unsafe.Pointer(&u16[0])), size)
|
||||
return string(utf16.Decode(u16))
|
||||
}
|
||||
|
||||
func ImmGetCompositionValue(imc syscall.Handle, key int) int {
|
||||
val, _, _ := _ImmGetCompositionString.Call(uintptr(imc), uintptr(key), 0, 0)
|
||||
return int(int32(val))
|
||||
}
|
||||
|
||||
func ImmSetCompositionWindow(imc syscall.Handle, x, y int) {
|
||||
f := CompositionForm{
|
||||
dwStyle: CFS_POINT,
|
||||
ptCurrentPos: Point{
|
||||
X: int32(x), Y: int32(y),
|
||||
},
|
||||
}
|
||||
_ImmSetCompositionWindow.Call(uintptr(imc), uintptr(unsafe.Pointer(&f)))
|
||||
}
|
||||
|
||||
func ImmSetCandidateWindow(imc syscall.Handle, x, y int) {
|
||||
f := CandidateForm{
|
||||
dwStyle: CFS_CANDIDATEPOS,
|
||||
ptCurrentPos: Point{
|
||||
X: int32(x), Y: int32(y),
|
||||
},
|
||||
}
|
||||
_ImmSetCandidateWindow.Call(uintptr(imc), uintptr(unsafe.Pointer(&f)))
|
||||
}
|
||||
|
||||
func SetWindowLong(hwnd syscall.Handle, idx uintptr, style uintptr) {
|
||||
if runtime.GOARCH == "386" {
|
||||
_SetWindowLong32.Call(uintptr(hwnd), idx, style)
|
||||
} else {
|
||||
_SetWindowLong.Call(uintptr(hwnd), idx, style)
|
||||
}
|
||||
}
|
||||
|
||||
func SetWindowPlacement(hwnd syscall.Handle, wp *WindowPlacement) {
|
||||
_SetWindowPlacement.Call(uintptr(hwnd), uintptr(unsafe.Pointer(wp)))
|
||||
}
|
||||
|
||||
func SetWindowPos(hwnd, hwndInsertAfter syscall.Handle, x, y, dx, dy int32, style uintptr) {
|
||||
_SetWindowPos.Call(uintptr(hwnd), uintptr(hwndInsertAfter),
|
||||
uintptr(x), uintptr(y),
|
||||
uintptr(dx), uintptr(dy),
|
||||
style,
|
||||
)
|
||||
}
|
||||
|
||||
func SetWindowText(hwnd syscall.Handle, title string) {
|
||||
wname, err := syscall.UTF16PtrFromString(title)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_SetWindowText.Call(uintptr(hwnd), uintptr(unsafe.Pointer(wname)))
|
||||
}
|
||||
|
||||
func GlobalAlloc(size int) (syscall.Handle, error) {
|
||||
r, _, err := _GlobalAlloc.Call(GHND, uintptr(size))
|
||||
if r == 0 {
|
||||
return 0, fmt.Errorf("GlobalAlloc: %v", err)
|
||||
}
|
||||
return syscall.Handle(r), nil
|
||||
}
|
||||
|
||||
func GlobalFree(h syscall.Handle) {
|
||||
_GlobalFree.Call(uintptr(h))
|
||||
}
|
||||
|
||||
func GlobalLock(h syscall.Handle) (unsafe.Pointer, error) {
|
||||
r, _, err := _GlobalLock.Call(uintptr(h))
|
||||
if r == 0 {
|
||||
return nil, fmt.Errorf("GlobalLock: %v", err)
|
||||
}
|
||||
return unsafe.Pointer(r), nil
|
||||
}
|
||||
|
||||
func GlobalUnlock(h syscall.Handle) {
|
||||
_GlobalUnlock.Call(uintptr(h))
|
||||
}
|
||||
|
||||
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 LoadImage(hInst syscall.Handle, res uint32, typ uint32, cx, cy int, fuload uint32) (syscall.Handle, error) {
|
||||
h, _, err := _LoadImage.Call(uintptr(hInst), uintptr(res), uintptr(typ), uintptr(cx), uintptr(cy), uintptr(fuload))
|
||||
if h == 0 {
|
||||
return 0, fmt.Errorf("LoadImageW failed: %v", err)
|
||||
}
|
||||
return syscall.Handle(h), nil
|
||||
}
|
||||
|
||||
func MoveWindow(hwnd syscall.Handle, x, y, width, height int32, repaint bool) {
|
||||
var paint uintptr
|
||||
if repaint {
|
||||
paint = TRUE
|
||||
}
|
||||
_MoveWindow.Call(uintptr(hwnd), uintptr(x), uintptr(y), uintptr(width), uintptr(height), paint)
|
||||
}
|
||||
|
||||
func monitorFromPoint(pt Point, flags uint32) syscall.Handle {
|
||||
r, _, _ := _MonitorFromPoint.Call(uintptr(pt.X), uintptr(pt.Y), uintptr(flags))
|
||||
return syscall.Handle(r)
|
||||
}
|
||||
|
||||
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 OpenClipboard(hwnd syscall.Handle) error {
|
||||
r, _, err := _OpenClipboard.Call(uintptr(hwnd))
|
||||
if r == 0 {
|
||||
return fmt.Errorf("OpenClipboard: %v", err)
|
||||
}
|
||||
return 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))
|
||||
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)))
|
||||
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 SendMessage(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) error {
|
||||
r, _, err := _SendMessage.Call(uintptr(hwnd), uintptr(msg), wParam, lParam)
|
||||
if r == 0 {
|
||||
return fmt.Errorf("SendMessage failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetForegroundWindow(hwnd syscall.Handle) {
|
||||
_SetForegroundWindow.Call(uintptr(hwnd))
|
||||
}
|
||||
|
||||
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 SetClipboardData(format uint32, mem syscall.Handle) error {
|
||||
r, _, err := _SetClipboardData.Call(uintptr(format), uintptr(mem))
|
||||
if r == 0 {
|
||||
return fmt.Errorf("SetClipboardData: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetCursor(h syscall.Handle) {
|
||||
_SetCursor.Call(uintptr(h))
|
||||
}
|
||||
|
||||
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)))
|
||||
}
|
||||
|
||||
func ShowWindow(hwnd syscall.Handle, nCmdShow int32) {
|
||||
_ShowWindow.Call(uintptr(hwnd), uintptr(nCmdShow))
|
||||
}
|
||||
|
||||
func TranslateMessage(m *Msg) {
|
||||
_TranslateMessage.Call(uintptr(unsafe.Pointer(m)))
|
||||
}
|
||||
|
||||
func UnregisterClass(cls uint16, hInst syscall.Handle) {
|
||||
_UnregisterClass.Call(uintptr(cls), uintptr(hInst))
|
||||
}
|
||||
|
||||
func UpdateWindow(hwnd syscall.Handle) {
|
||||
_UpdateWindow.Call(uintptr(hwnd))
|
||||
}
|
||||
|
||||
func (p WindowPlacement) Rect() Rect {
|
||||
return p.rcNormalPosition
|
||||
}
|
||||
|
||||
func (p WindowPlacement) IsMinimized() bool {
|
||||
return p.showCmd == SW_SHOWMINIMIZED
|
||||
}
|
||||
|
||||
func (p WindowPlacement) IsMaximized() bool {
|
||||
return p.showCmd == SW_SHOWMAXIMIZED
|
||||
}
|
||||
|
||||
func (p *WindowPlacement) Set(Left, Top, Right, Bottom int) {
|
||||
p.rcNormalPosition.Left = int32(Left)
|
||||
p.rcNormalPosition.Top = int32(Top)
|
||||
p.rcNormalPosition.Right = int32(Right)
|
||||
p.rcNormalPosition.Bottom = int32(Bottom)
|
||||
}
|
||||
+375
@@ -0,0 +1,375 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build (linux && !android) || freebsd || openbsd
|
||||
// +build linux,!android freebsd openbsd
|
||||
|
||||
// Package xkb implements a Go interface for the X Keyboard Extension library.
|
||||
package xkb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: xkbcommon
|
||||
#cgo freebsd openbsd CFLAGS: -I/usr/local/include
|
||||
#cgo freebsd openbsd LDFLAGS: -L/usr/local/lib -lxkbcommon
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#include <xkbcommon/xkbcommon-compose.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type Context 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")
|
||||
_XKB_MOD_NAME_ALT = []byte("Mod1\x00")
|
||||
_XKB_MOD_NAME_LOGO = []byte("Mod4\x00")
|
||||
)
|
||||
|
||||
func (x *Context) Destroy() {
|
||||
if x.compState != 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
|
||||
}
|
||||
x.DestroyKeymapState()
|
||||
if x.Ctx != nil {
|
||||
C.xkb_context_unref(x.Ctx)
|
||||
x.Ctx = nil
|
||||
}
|
||||
}
|
||||
|
||||
func New() (*Context, error) {
|
||||
ctx := &Context{
|
||||
Ctx: C.xkb_context_new(C.XKB_CONTEXT_NO_FLAGS),
|
||||
}
|
||||
if ctx.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))
|
||||
ctx.compTable = C.xkb_compose_table_new_from_locale(ctx.Ctx, cloc, C.XKB_COMPOSE_COMPILE_NO_FLAGS)
|
||||
if ctx.compTable == nil {
|
||||
ctx.Destroy()
|
||||
return nil, errors.New("newXKB: xkb_compose_table_new_from_locale failed")
|
||||
}
|
||||
ctx.compState = C.xkb_compose_state_new(ctx.compTable, C.XKB_COMPOSE_STATE_NO_FLAGS)
|
||||
if ctx.compState == nil {
|
||||
ctx.Destroy()
|
||||
return nil, errors.New("newXKB: xkb_compose_state_new failed")
|
||||
}
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func (x *Context) DestroyKeymapState() {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// SetKeymap sets the keymap and state. The context takes ownership of the
|
||||
// keymap and state and frees them in Destroy.
|
||||
func (x *Context) SetKeymap(xkbKeyMap, xkbState unsafe.Pointer) {
|
||||
x.DestroyKeymapState()
|
||||
x.keyMap = (*C.struct_xkb_keymap)(xkbKeyMap)
|
||||
x.state = (*C.struct_xkb_state)(xkbState)
|
||||
}
|
||||
|
||||
func (x *Context) LoadKeymap(format int, fd int, size int) error {
|
||||
x.DestroyKeymapState()
|
||||
mapData, err := syscall.Mmap(int(fd), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED)
|
||||
if err != nil {
|
||||
return fmt.Errorf("newXKB: mmap of keymap failed: %v", err)
|
||||
}
|
||||
defer syscall.Munmap(mapData)
|
||||
keyMap := C.xkb_keymap_new_from_buffer(x.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 keyMap == nil {
|
||||
return errors.New("newXKB: xkb_keymap_new_from_buffer failed")
|
||||
}
|
||||
state := C.xkb_state_new(keyMap)
|
||||
if state == nil {
|
||||
C.xkb_keymap_unref(keyMap)
|
||||
return errors.New("newXKB: xkb_state_new failed")
|
||||
}
|
||||
x.keyMap = keyMap
|
||||
x.state = state
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Context) Modifiers() key.Modifiers {
|
||||
var mods key.Modifiers
|
||||
if x.state == nil {
|
||||
return mods
|
||||
}
|
||||
|
||||
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 {
|
||||
mods |= key.ModCtrl
|
||||
}
|
||||
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 {
|
||||
mods |= key.ModShift
|
||||
}
|
||||
if C.xkb_state_mod_name_is_active(x.state, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_ALT[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 {
|
||||
mods |= key.ModAlt
|
||||
}
|
||||
if C.xkb_state_mod_name_is_active(x.state, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_LOGO[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 {
|
||||
mods |= key.ModSuper
|
||||
}
|
||||
return mods
|
||||
}
|
||||
|
||||
func (x *Context) DispatchKey(keyCode uint32, state key.State) (events []event.Event) {
|
||||
if x.state == nil {
|
||||
return
|
||||
}
|
||||
kc := C.xkb_keycode_t(keyCode)
|
||||
if len(x.utf8Buf) == 0 {
|
||||
x.utf8Buf = make([]byte, 1)
|
||||
}
|
||||
sym := C.xkb_state_key_get_one_sym(x.state, kc)
|
||||
if name, ok := convertKeysym(sym); ok {
|
||||
cmd := key.Event{
|
||||
Name: name,
|
||||
Modifiers: x.Modifiers(),
|
||||
State: state,
|
||||
}
|
||||
// Ensure that a physical backtab key is translated to
|
||||
// Shift-Tab.
|
||||
if sym == C.XKB_KEY_ISO_Left_Tab {
|
||||
cmd.Modifiers |= key.ModShift
|
||||
}
|
||||
events = append(events, cmd)
|
||||
}
|
||||
C.xkb_compose_state_feed(x.compState, sym)
|
||||
var str []byte
|
||||
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)
|
||||
str = x.utf8Buf[:size]
|
||||
case C.XKB_COMPOSE_NOTHING:
|
||||
mod := x.Modifiers()
|
||||
if mod&(key.ModCtrl|key.ModAlt|key.ModSuper) == 0 {
|
||||
str = x.charsForKeycode(kc)
|
||||
}
|
||||
}
|
||||
// Report only printable runes.
|
||||
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 state == key.Press && len(str) > 0 {
|
||||
events = append(events, key.EditEvent{Text: string(str)})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (x *Context) charsForKeycode(keyCode C.xkb_keycode_t) []byte {
|
||||
size := C.xkb_state_key_get_utf8(x.state, 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, keyCode, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
|
||||
}
|
||||
return x.utf8Buf[:size]
|
||||
}
|
||||
|
||||
func (x *Context) IsRepeatKey(keyCode uint32) bool {
|
||||
if x.state == nil {
|
||||
return false
|
||||
}
|
||||
kc := C.xkb_keycode_t(keyCode)
|
||||
return C.xkb_keymap_key_repeats(x.keyMap, kc) == 1
|
||||
}
|
||||
|
||||
func (x *Context) UpdateMask(depressed, latched, locked, depressedGroup, latchedGroup, lockedGroup uint32) {
|
||||
if x.state == nil {
|
||||
return
|
||||
}
|
||||
C.xkb_state_update_mask(x.state, C.xkb_mod_mask_t(depressed), C.xkb_mod_mask_t(latched), C.xkb_mod_mask_t(locked),
|
||||
C.xkb_layout_index_t(depressedGroup), C.xkb_layout_index_t(latchedGroup), C.xkb_layout_index_t(lockedGroup))
|
||||
}
|
||||
|
||||
func convertKeysym(s C.xkb_keysym_t) (key.Name, bool) {
|
||||
if 'a' <= s && s <= 'z' {
|
||||
return key.Name(rune(s - 'a' + 'A')), true
|
||||
}
|
||||
if C.XKB_KEY_KP_0 <= s && s <= C.XKB_KEY_KP_9 {
|
||||
return key.Name(rune(s - C.XKB_KEY_KP_0 + '0')), true
|
||||
}
|
||||
if ' ' < s && s <= '~' {
|
||||
return key.Name(rune(s)), true
|
||||
}
|
||||
var n key.Name
|
||||
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_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
|
||||
case C.XKB_KEY_F1:
|
||||
n = key.NameF1
|
||||
case C.XKB_KEY_F2:
|
||||
n = key.NameF2
|
||||
case C.XKB_KEY_F3:
|
||||
n = key.NameF3
|
||||
case C.XKB_KEY_F4:
|
||||
n = key.NameF4
|
||||
case C.XKB_KEY_F5:
|
||||
n = key.NameF5
|
||||
case C.XKB_KEY_F6:
|
||||
n = key.NameF6
|
||||
case C.XKB_KEY_F7:
|
||||
n = key.NameF7
|
||||
case C.XKB_KEY_F8:
|
||||
n = key.NameF8
|
||||
case C.XKB_KEY_F9:
|
||||
n = key.NameF9
|
||||
case C.XKB_KEY_F10:
|
||||
n = key.NameF10
|
||||
case C.XKB_KEY_F11:
|
||||
n = key.NameF11
|
||||
case C.XKB_KEY_F12:
|
||||
n = key.NameF12
|
||||
case C.XKB_KEY_Tab, C.XKB_KEY_ISO_Left_Tab:
|
||||
n = key.NameTab
|
||||
case 0x20:
|
||||
n = key.NameSpace
|
||||
case C.XKB_KEY_Control_L, C.XKB_KEY_Control_R:
|
||||
n = key.NameCtrl
|
||||
case C.XKB_KEY_Shift_L, C.XKB_KEY_Shift_R:
|
||||
n = key.NameShift
|
||||
case C.XKB_KEY_Alt_L, C.XKB_KEY_Alt_R:
|
||||
n = key.NameAlt
|
||||
case C.XKB_KEY_Super_L, C.XKB_KEY_Super_R:
|
||||
n = key.NameSuper
|
||||
|
||||
case C.XKB_KEY_KP_Space:
|
||||
n = key.NameSpace
|
||||
case C.XKB_KEY_KP_Tab:
|
||||
n = key.NameTab
|
||||
case C.XKB_KEY_KP_Enter:
|
||||
n = key.NameEnter
|
||||
case C.XKB_KEY_KP_F1:
|
||||
n = key.NameF1
|
||||
case C.XKB_KEY_KP_F2:
|
||||
n = key.NameF2
|
||||
case C.XKB_KEY_KP_F3:
|
||||
n = key.NameF3
|
||||
case C.XKB_KEY_KP_F4:
|
||||
n = key.NameF4
|
||||
case C.XKB_KEY_KP_Home:
|
||||
n = key.NameHome
|
||||
case C.XKB_KEY_KP_Left:
|
||||
n = key.NameLeftArrow
|
||||
case C.XKB_KEY_KP_Up:
|
||||
n = key.NameUpArrow
|
||||
case C.XKB_KEY_KP_Right:
|
||||
n = key.NameRightArrow
|
||||
case C.XKB_KEY_KP_Down:
|
||||
n = key.NameDownArrow
|
||||
case C.XKB_KEY_KP_Prior:
|
||||
// not supported
|
||||
return "", false
|
||||
case C.XKB_KEY_KP_Next:
|
||||
// not supported
|
||||
return "", false
|
||||
case C.XKB_KEY_KP_End:
|
||||
n = key.NameEnd
|
||||
case C.XKB_KEY_KP_Begin:
|
||||
n = key.NameHome
|
||||
case C.XKB_KEY_KP_Insert:
|
||||
// not supported
|
||||
return "", false
|
||||
case C.XKB_KEY_KP_Delete:
|
||||
n = key.NameDeleteForward
|
||||
case C.XKB_KEY_KP_Multiply:
|
||||
n = "*"
|
||||
case C.XKB_KEY_KP_Add:
|
||||
n = "+"
|
||||
case C.XKB_KEY_KP_Separator:
|
||||
// not supported
|
||||
return "", false
|
||||
case C.XKB_KEY_KP_Subtract:
|
||||
n = "-"
|
||||
case C.XKB_KEY_KP_Decimal:
|
||||
// TODO(dh): does a German keyboard layout also translate the numpad key to XKB_KEY_KP_DECIMAL? Because in
|
||||
// German, the decimal is a comma, not a period.
|
||||
n = "."
|
||||
case C.XKB_KEY_KP_Divide:
|
||||
n = "/"
|
||||
case C.XKB_KEY_KP_Equal:
|
||||
n = "="
|
||||
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
return n, true
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// 1024 is the truncation limit from android/log.h, plus a \n.
|
||||
const logLineLimit = 1024
|
||||
|
||||
var logTag = C.CString(ID)
|
||||
|
||||
func init() {
|
||||
// Android's logcat already includes timestamps.
|
||||
log.SetFlags(log.Flags() &^ log.LstdFlags)
|
||||
log.SetOutput(new(androidLogWriter))
|
||||
|
||||
// Redirect stdout and stderr to the Android logger.
|
||||
logFd(os.Stdout.Fd())
|
||||
logFd(os.Stderr.Fd())
|
||||
}
|
||||
|
||||
type androidLogWriter struct {
|
||||
// buf has room for the maximum log line, plus a terminating '\0'.
|
||||
buf [logLineLimit + 1]byte
|
||||
}
|
||||
|
||||
func (w *androidLogWriter) Write(data []byte) (int, error) {
|
||||
n := 0
|
||||
for len(data) > 0 {
|
||||
msg := data
|
||||
// Truncate the buffer, leaving space for the '\0'.
|
||||
if max := len(w.buf) - 1; len(msg) > max {
|
||||
msg = msg[:max]
|
||||
}
|
||||
buf := w.buf[:len(msg)+1]
|
||||
copy(buf, msg)
|
||||
// Terminating '\0'.
|
||||
buf[len(msg)] = 0
|
||||
C.__android_log_write(C.ANDROID_LOG_INFO, logTag, (*C.char)(unsafe.Pointer(&buf[0])))
|
||||
n += len(msg)
|
||||
data = data[len(msg):]
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
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() {
|
||||
lineBuf := bufio.NewReaderSize(r, logLineLimit)
|
||||
// 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, logTag, 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)
|
||||
}()
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build darwin && ios
|
||||
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -Werror -fmodules -fobjc-arc -x objective-c
|
||||
|
||||
@import Foundation;
|
||||
|
||||
static void nslog(char *str) {
|
||||
NSLog(@"%@", @(str));
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"log"
|
||||
"unsafe"
|
||||
|
||||
_ "gioui.org/internal/cocoainit"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// macOS Console already includes timestamps.
|
||||
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
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"log"
|
||||
"unsafe"
|
||||
|
||||
syscall "golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type logger struct{}
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazySystemDLL("kernel32")
|
||||
outputDebugStringW = kernel32.NewProc("OutputDebugStringW")
|
||||
debugView *logger
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Windows DebugView already includes timestamps.
|
||||
if syscall.Stderr == 0 {
|
||||
log.SetFlags(log.Flags() &^ log.LstdFlags)
|
||||
log.SetOutput(debugView)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *logger) Write(buf []byte) (int, error) {
|
||||
p, err := syscall.UTF16PtrFromString(string(buf))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
outputDebugStringW.Call(uintptr(unsafe.Pointer(p)))
|
||||
return len(buf), nil
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build !nometal
|
||||
// +build !nometal
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"gioui.org/gpu"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -Werror -xobjective-c -fobjc-arc
|
||||
#cgo LDFLAGS: -framework QuartzCore -framework Metal
|
||||
|
||||
#import <Metal/Metal.h>
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
static CFTypeRef createMetalDevice(void) {
|
||||
@autoreleasepool {
|
||||
id<MTLDevice> dev = MTLCreateSystemDefaultDevice();
|
||||
return CFBridgingRetain(dev);
|
||||
}
|
||||
}
|
||||
|
||||
static void setupLayer(CFTypeRef layerRef, CFTypeRef devRef) {
|
||||
@autoreleasepool {
|
||||
CAMetalLayer *layer = (__bridge CAMetalLayer *)layerRef;
|
||||
id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef;
|
||||
layer.device = dev;
|
||||
// Package gpu assumes an sRGB-encoded framebuffer.
|
||||
layer.pixelFormat = MTLPixelFormatBGRA8Unorm_sRGB;
|
||||
if (@available(iOS 11.0, *)) {
|
||||
// Never let nextDrawable time out and return nil.
|
||||
layer.allowsNextDrawableTimeout = NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static CFTypeRef nextDrawable(CFTypeRef layerRef) {
|
||||
@autoreleasepool {
|
||||
CAMetalLayer *layer = (__bridge CAMetalLayer *)layerRef;
|
||||
return CFBridgingRetain([layer nextDrawable]);
|
||||
}
|
||||
}
|
||||
|
||||
static CFTypeRef drawableTexture(CFTypeRef drawableRef) {
|
||||
@autoreleasepool {
|
||||
id<CAMetalDrawable> drawable = (__bridge id<CAMetalDrawable>)drawableRef;
|
||||
return CFBridgingRetain(drawable.texture);
|
||||
}
|
||||
}
|
||||
|
||||
static void presentDrawable(CFTypeRef queueRef, CFTypeRef drawableRef) {
|
||||
@autoreleasepool {
|
||||
id<MTLDrawable> drawable = (__bridge id<MTLDrawable>)drawableRef;
|
||||
id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)queueRef;
|
||||
id<MTLCommandBuffer> cmdBuffer = [queue commandBuffer];
|
||||
[cmdBuffer commit];
|
||||
[cmdBuffer waitUntilScheduled];
|
||||
[drawable present];
|
||||
}
|
||||
}
|
||||
|
||||
static CFTypeRef newCommandQueue(CFTypeRef devRef) {
|
||||
@autoreleasepool {
|
||||
id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef;
|
||||
return CFBridgingRetain([dev newCommandQueue]);
|
||||
}
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type mtlContext struct {
|
||||
dev C.CFTypeRef
|
||||
view C.CFTypeRef
|
||||
layer C.CFTypeRef
|
||||
queue C.CFTypeRef
|
||||
drawable C.CFTypeRef
|
||||
texture C.CFTypeRef
|
||||
}
|
||||
|
||||
func newMtlContext(w *window) (*mtlContext, error) {
|
||||
dev := C.createMetalDevice()
|
||||
if dev == 0 {
|
||||
return nil, errors.New("metal: MTLCreateSystemDefaultDevice failed")
|
||||
}
|
||||
view := w.contextView()
|
||||
layer := getMetalLayer(view)
|
||||
if layer == 0 {
|
||||
C.CFRelease(dev)
|
||||
return nil, errors.New("metal: CAMetalLayer construction failed")
|
||||
}
|
||||
queue := C.newCommandQueue(dev)
|
||||
if queue == 0 {
|
||||
C.CFRelease(dev)
|
||||
C.CFRelease(layer)
|
||||
return nil, errors.New("metal: [MTLDevice newCommandQueue] failed")
|
||||
}
|
||||
C.setupLayer(layer, dev)
|
||||
c := &mtlContext{
|
||||
dev: dev,
|
||||
view: view,
|
||||
layer: layer,
|
||||
queue: queue,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *mtlContext) RenderTarget() (gpu.RenderTarget, error) {
|
||||
if c.drawable != 0 || c.texture != 0 {
|
||||
return nil, errors.New("metal:a previous RenderTarget wasn't Presented")
|
||||
}
|
||||
c.drawable = C.nextDrawable(c.layer)
|
||||
if c.drawable == 0 {
|
||||
return nil, errors.New("metal: [CAMetalLayer nextDrawable] failed")
|
||||
}
|
||||
c.texture = C.drawableTexture(c.drawable)
|
||||
if c.texture == 0 {
|
||||
return nil, errors.New("metal: CADrawable.texture is nil")
|
||||
}
|
||||
return gpu.MetalRenderTarget{
|
||||
Texture: uintptr(c.texture),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *mtlContext) API() gpu.API {
|
||||
return gpu.Metal{
|
||||
Device: uintptr(c.dev),
|
||||
Queue: uintptr(c.queue),
|
||||
PixelFormat: int(C.MTLPixelFormatBGRA8Unorm_sRGB),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *mtlContext) Release() {
|
||||
C.CFRelease(c.queue)
|
||||
C.CFRelease(c.dev)
|
||||
C.CFRelease(c.layer)
|
||||
if c.drawable != 0 {
|
||||
C.CFRelease(c.drawable)
|
||||
}
|
||||
if c.texture != 0 {
|
||||
C.CFRelease(c.texture)
|
||||
}
|
||||
*c = mtlContext{}
|
||||
}
|
||||
|
||||
func (c *mtlContext) Present() error {
|
||||
C.CFRelease(c.texture)
|
||||
c.texture = 0
|
||||
C.presentDrawable(c.queue, c.drawable)
|
||||
C.CFRelease(c.drawable)
|
||||
c.drawable = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *mtlContext) Lock() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *mtlContext) Unlock() {}
|
||||
|
||||
func (c *mtlContext) Refresh() error {
|
||||
resizeDrawable(c.view, c.layer)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (context, error) {
|
||||
return newMtlContext(w)
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build !nometal
|
||||
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -Werror -xobjective-c -fmodules -fobjc-arc
|
||||
|
||||
@import UIKit;
|
||||
|
||||
@import QuartzCore.CAMetalLayer;
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
Class gio_layerClass(void) {
|
||||
return [CAMetalLayer class];
|
||||
}
|
||||
|
||||
static CFTypeRef getMetalLayer(CFTypeRef viewRef) {
|
||||
@autoreleasepool {
|
||||
UIView *view = (__bridge UIView *)viewRef;
|
||||
CAMetalLayer *l = (CAMetalLayer *)view.layer;
|
||||
l.needsDisplayOnBoundsChange = YES;
|
||||
l.presentsWithTransaction = YES;
|
||||
return CFBridgingRetain(l);
|
||||
}
|
||||
}
|
||||
|
||||
static void resizeDrawable(CFTypeRef viewRef, CFTypeRef layerRef) {
|
||||
@autoreleasepool {
|
||||
UIView *view = (__bridge UIView *)viewRef;
|
||||
CAMetalLayer *layer = (__bridge CAMetalLayer *)layerRef;
|
||||
layer.contentsScale = view.contentScaleFactor;
|
||||
CGSize size = layer.bounds.size;
|
||||
size.width *= layer.contentsScale;
|
||||
size.height *= layer.contentsScale;
|
||||
layer.drawableSize = size;
|
||||
}
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func getMetalLayer(view C.CFTypeRef) C.CFTypeRef {
|
||||
return C.getMetalLayer(view)
|
||||
}
|
||||
|
||||
func resizeDrawable(view, layer C.CFTypeRef) {
|
||||
C.resizeDrawable(view, layer)
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build darwin && !ios && !nometal
|
||||
// +build darwin,!ios,!nometal
|
||||
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -Werror -xobjective-c -fobjc-arc
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
CALayer *gio_layerFactory(BOOL presentWithTrans) {
|
||||
@autoreleasepool {
|
||||
CAMetalLayer *l = [CAMetalLayer layer];
|
||||
l.autoresizingMask = kCALayerHeightSizable|kCALayerWidthSizable;
|
||||
l.needsDisplayOnBoundsChange = YES;
|
||||
l.presentsWithTransaction = presentWithTrans;
|
||||
return l;
|
||||
}
|
||||
}
|
||||
|
||||
static CFTypeRef getMetalLayer(CFTypeRef viewRef) {
|
||||
@autoreleasepool {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return CFBridgingRetain(view.layer);
|
||||
}
|
||||
}
|
||||
|
||||
static void resizeDrawable(CFTypeRef viewRef, CFTypeRef layerRef) {
|
||||
@autoreleasepool {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
CAMetalLayer *layer = (__bridge CAMetalLayer *)layerRef;
|
||||
CGSize size = layer.bounds.size;
|
||||
size.width *= layer.contentsScale;
|
||||
size.height *= layer.contentsScale;
|
||||
layer.drawableSize = size;
|
||||
}
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func getMetalLayer(view C.CFTypeRef) C.CFTypeRef {
|
||||
return C.getMetalLayer(view)
|
||||
}
|
||||
|
||||
func resizeDrawable(view, layer C.CFTypeRef) {
|
||||
C.resizeDrawable(view, layer)
|
||||
}
|
||||
+367
@@ -0,0 +1,367 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/op"
|
||||
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
// errOutOfDate is reported when the GPU surface dimensions or properties no
|
||||
// longer match the window.
|
||||
var errOutOfDate = errors.New("app: GPU surface out of date")
|
||||
|
||||
// Config describes a Window configuration.
|
||||
type Config struct {
|
||||
// Size is the window dimensions (Width, Height).
|
||||
Size image.Point
|
||||
// MaxSize is the window maximum allowed dimensions.
|
||||
MaxSize image.Point
|
||||
// MinSize is the window minimum allowed dimensions.
|
||||
MinSize image.Point
|
||||
// Title is the window title displayed in its decoration bar.
|
||||
Title string
|
||||
// WindowMode is the window mode.
|
||||
Mode WindowMode
|
||||
// StatusColor is the color of the Android status bar.
|
||||
StatusColor color.NRGBA
|
||||
// NavigationColor is the color of the navigation bar
|
||||
// on Android, or the address bar in browsers.
|
||||
NavigationColor color.NRGBA
|
||||
// Orientation is the current window orientation.
|
||||
Orientation Orientation
|
||||
// CustomRenderer is true when the window content is rendered by the
|
||||
// client.
|
||||
CustomRenderer bool
|
||||
// Decorated reports whether window decorations are provided automatically.
|
||||
Decorated bool
|
||||
// TopMost windows render above all other non-top-most windows.
|
||||
TopMost bool
|
||||
// Focused reports whether the window is focused.
|
||||
Focused bool
|
||||
// decoHeight is the height of the fallback decoration for platforms such
|
||||
// as Wayland that may need fallback client-side decorations.
|
||||
decoHeight unit.Dp
|
||||
}
|
||||
|
||||
// ConfigEvent is sent whenever the configuration of a Window changes.
|
||||
type ConfigEvent struct {
|
||||
Config Config
|
||||
}
|
||||
|
||||
func (c *Config) apply(m unit.Metric, options []Option) {
|
||||
for _, o := range options {
|
||||
o(m, c)
|
||||
}
|
||||
}
|
||||
|
||||
type wakeupEvent struct{}
|
||||
|
||||
// WindowMode is the window mode (WindowMode.Option sets it).
|
||||
// Note that mode can be changed programatically as well as by the user
|
||||
// clicking on the minimize/maximize buttons on the window's title bar.
|
||||
type WindowMode uint8
|
||||
|
||||
const (
|
||||
// Windowed is the normal window mode with OS specific window decorations.
|
||||
Windowed WindowMode = iota
|
||||
// Fullscreen is the full screen window mode.
|
||||
Fullscreen
|
||||
// Minimized is for systems where the window can be minimized to an icon.
|
||||
Minimized
|
||||
// Maximized is for systems where the window can be made to fill the available monitor area.
|
||||
Maximized
|
||||
)
|
||||
|
||||
// Option changes the mode of a Window.
|
||||
func (m WindowMode) Option() Option {
|
||||
return func(_ unit.Metric, cnf *Config) {
|
||||
cnf.Mode = m
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the mode name.
|
||||
func (m WindowMode) String() string {
|
||||
switch m {
|
||||
case Windowed:
|
||||
return "windowed"
|
||||
case Fullscreen:
|
||||
return "fullscreen"
|
||||
case Minimized:
|
||||
return "minimized"
|
||||
case Maximized:
|
||||
return "maximized"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Orientation is the orientation of the app (Orientation.Option sets it).
|
||||
//
|
||||
// Supported platforms are Android and JS.
|
||||
type Orientation uint8
|
||||
|
||||
const (
|
||||
// AnyOrientation allows the window to be freely orientated.
|
||||
AnyOrientation Orientation = iota
|
||||
// LandscapeOrientation constrains the window to landscape orientations.
|
||||
LandscapeOrientation
|
||||
// PortraitOrientation constrains the window to portrait orientations.
|
||||
PortraitOrientation
|
||||
)
|
||||
|
||||
func (o Orientation) Option() Option {
|
||||
return func(_ unit.Metric, cnf *Config) {
|
||||
cnf.Orientation = o
|
||||
}
|
||||
}
|
||||
|
||||
func (o Orientation) String() string {
|
||||
switch o {
|
||||
case AnyOrientation:
|
||||
return "any"
|
||||
case LandscapeOrientation:
|
||||
return "landscape"
|
||||
case PortraitOrientation:
|
||||
return "portrait"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// eventLoop implements the functionality required for drivers where
|
||||
// window event loops must run on a separate thread.
|
||||
type eventLoop struct {
|
||||
win *callbacks
|
||||
// wakeup is the callback to wake up the event loop.
|
||||
wakeup func()
|
||||
// driverFuncs is a channel of functions to run the next
|
||||
// time the window loop waits for events.
|
||||
driverFuncs chan func()
|
||||
// invalidates is notified when an invalidate is requested by the client.
|
||||
invalidates chan struct{}
|
||||
// immediateInvalidates is an optimistic invalidates that doesn't require a wakeup.
|
||||
immediateInvalidates chan struct{}
|
||||
// events is where the platform backend delivers events bound for the
|
||||
// user program.
|
||||
events chan event.Event
|
||||
frames chan *op.Ops
|
||||
frameAck chan struct{}
|
||||
// delivering avoids re-entrant event delivery.
|
||||
delivering bool
|
||||
}
|
||||
|
||||
type frameEvent struct {
|
||||
FrameEvent
|
||||
|
||||
Sync bool
|
||||
}
|
||||
|
||||
type context interface {
|
||||
API() gpu.API
|
||||
RenderTarget() (gpu.RenderTarget, error)
|
||||
Present() error
|
||||
Refresh() error
|
||||
Release()
|
||||
Lock() error
|
||||
Unlock()
|
||||
}
|
||||
|
||||
// driver is the interface for the platform implementation
|
||||
// of a window.
|
||||
type driver interface {
|
||||
// Event blocks until an event is available and returns it.
|
||||
Event() event.Event
|
||||
// Invalidate requests a FrameEvent.
|
||||
Invalidate()
|
||||
// SetAnimating sets the animation flag. When the window is animating,
|
||||
// FrameEvents are delivered as fast as the display can handle them.
|
||||
SetAnimating(anim bool)
|
||||
// ShowTextInput updates the virtual keyboard state.
|
||||
ShowTextInput(show bool)
|
||||
SetInputHint(mode key.InputHint)
|
||||
NewContext() (context, error)
|
||||
// ReadClipboard requests the clipboard content.
|
||||
ReadClipboard()
|
||||
// WriteClipboard requests a clipboard write.
|
||||
WriteClipboard(mime string, s []byte)
|
||||
// Configure the window.
|
||||
Configure([]Option)
|
||||
// SetCursor updates the current cursor to name.
|
||||
SetCursor(cursor pointer.Cursor)
|
||||
// Perform actions on the window.
|
||||
Perform(system.Action)
|
||||
// EditorStateChanged notifies the driver that the editor state changed.
|
||||
EditorStateChanged(old, new editorState)
|
||||
// Run a function on the window thread.
|
||||
Run(f func())
|
||||
// Frame receives a frame.
|
||||
Frame(frame *op.Ops)
|
||||
// ProcessEvent processes an event.
|
||||
ProcessEvent(e event.Event)
|
||||
}
|
||||
|
||||
type windowRendezvous struct {
|
||||
in chan windowAndConfig
|
||||
out chan windowAndConfig
|
||||
windows chan struct{}
|
||||
}
|
||||
|
||||
type windowAndConfig struct {
|
||||
window *callbacks
|
||||
options []Option
|
||||
}
|
||||
|
||||
func newWindowRendezvous() *windowRendezvous {
|
||||
wr := &windowRendezvous{
|
||||
in: make(chan windowAndConfig),
|
||||
out: make(chan windowAndConfig),
|
||||
windows: make(chan struct{}),
|
||||
}
|
||||
go func() {
|
||||
in := wr.in
|
||||
var window windowAndConfig
|
||||
var out chan windowAndConfig
|
||||
for {
|
||||
select {
|
||||
case w := <-in:
|
||||
window = w
|
||||
out = wr.out
|
||||
case out <- window:
|
||||
}
|
||||
}
|
||||
}()
|
||||
return wr
|
||||
}
|
||||
|
||||
func newEventLoop(w *callbacks, wakeup func()) *eventLoop {
|
||||
return &eventLoop{
|
||||
win: w,
|
||||
wakeup: wakeup,
|
||||
events: make(chan event.Event),
|
||||
invalidates: make(chan struct{}, 1),
|
||||
immediateInvalidates: make(chan struct{}),
|
||||
frames: make(chan *op.Ops),
|
||||
frameAck: make(chan struct{}),
|
||||
driverFuncs: make(chan func(), 1),
|
||||
}
|
||||
}
|
||||
|
||||
// Frame receives a frame and waits for its processing. It is called by
|
||||
// the client goroutine.
|
||||
func (e *eventLoop) Frame(frame *op.Ops) {
|
||||
e.frames <- frame
|
||||
<-e.frameAck
|
||||
}
|
||||
|
||||
// Event returns the next available event. It is called by the client
|
||||
// goroutine.
|
||||
func (e *eventLoop) Event() event.Event {
|
||||
for {
|
||||
evt := <-e.events
|
||||
// Receiving a flushEvent indicates to the platform backend that
|
||||
// all previous events have been processed by the user program.
|
||||
if _, ok := evt.(flushEvent); ok {
|
||||
continue
|
||||
}
|
||||
return evt
|
||||
}
|
||||
}
|
||||
|
||||
// Invalidate requests invalidation of the window. It is called by the client
|
||||
// goroutine.
|
||||
func (e *eventLoop) Invalidate() {
|
||||
select {
|
||||
case e.immediateInvalidates <- struct{}{}:
|
||||
// The event loop was waiting, no need for a wakeup.
|
||||
case e.invalidates <- struct{}{}:
|
||||
// The event loop is sleeping, wake it up.
|
||||
e.wakeup()
|
||||
default:
|
||||
// A redraw is pending.
|
||||
}
|
||||
}
|
||||
|
||||
// Run f in the window loop thread. It is called by the client goroutine.
|
||||
func (e *eventLoop) Run(f func()) {
|
||||
e.driverFuncs <- f
|
||||
e.wakeup()
|
||||
}
|
||||
|
||||
// FlushEvents delivers pending events to the client.
|
||||
func (e *eventLoop) FlushEvents() {
|
||||
if e.delivering {
|
||||
return
|
||||
}
|
||||
e.delivering = true
|
||||
defer func() { e.delivering = false }()
|
||||
for {
|
||||
evt, ok := e.win.nextEvent()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
e.deliverEvent(evt)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *eventLoop) deliverEvent(evt event.Event) {
|
||||
var frames <-chan *op.Ops
|
||||
for {
|
||||
select {
|
||||
case f := <-e.driverFuncs:
|
||||
f()
|
||||
case frame := <-frames:
|
||||
// The client called FrameEvent.Frame.
|
||||
frames = nil
|
||||
e.win.ProcessFrame(frame, e.frameAck)
|
||||
case e.events <- evt:
|
||||
switch evt.(type) {
|
||||
case flushEvent, DestroyEvent:
|
||||
// DestroyEvents are not flushed.
|
||||
return
|
||||
case FrameEvent:
|
||||
frames = e.frames
|
||||
}
|
||||
evt = theFlushEvent
|
||||
case <-e.invalidates:
|
||||
e.win.Invalidate()
|
||||
case <-e.immediateInvalidates:
|
||||
e.win.Invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *eventLoop) Wakeup() {
|
||||
for {
|
||||
select {
|
||||
case f := <-e.driverFuncs:
|
||||
f()
|
||||
case <-e.invalidates:
|
||||
e.win.Invalidate()
|
||||
case <-e.immediateInvalidates:
|
||||
e.win.Invalidate()
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func walkActions(actions system.Action, do func(system.Action)) {
|
||||
for a := system.Action(1); actions != 0; a <<= 1 {
|
||||
if actions&a != 0 {
|
||||
actions &^= a
|
||||
do(a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wakeupEvent) ImplementsEvent() {}
|
||||
func (ConfigEvent) ImplementsEvent() {}
|
||||
+1508
File diff suppressed because it is too large
Load Diff
+265
@@ -0,0 +1,265 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
/*
|
||||
#include <Foundation/Foundation.h>
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) void gio_runOnMain(uintptr_t h);
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createDisplayLink(void);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_releaseDisplayLink(CFTypeRef dl);
|
||||
__attribute__ ((visibility ("hidden"))) int gio_startDisplayLink(CFTypeRef dl);
|
||||
__attribute__ ((visibility ("hidden"))) int gio_stopDisplayLink(CFTypeRef dl);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_setDisplayLinkDisplay(CFTypeRef dl, uint64_t did);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_hideCursor();
|
||||
__attribute__ ((visibility ("hidden"))) void gio_showCursor();
|
||||
__attribute__ ((visibility ("hidden"))) void gio_setCursor(NSUInteger curID);
|
||||
|
||||
static bool isMainThread() {
|
||||
return [NSThread isMainThread];
|
||||
}
|
||||
|
||||
static NSUInteger nsstringLength(CFTypeRef cstr) {
|
||||
NSString *str = (__bridge NSString *)cstr;
|
||||
return [str length];
|
||||
}
|
||||
|
||||
static void nsstringGetCharacters(CFTypeRef cstr, unichar *chars, NSUInteger loc, NSUInteger length) {
|
||||
NSString *str = (__bridge NSString *)cstr;
|
||||
[str getCharacters:chars range:NSMakeRange(loc, length)];
|
||||
}
|
||||
|
||||
static CFTypeRef newNSString(unichar *chars, NSUInteger length) {
|
||||
@autoreleasepool {
|
||||
NSString *s = [NSString string];
|
||||
if (length > 0) {
|
||||
s = [NSString stringWithCharacters:chars length:length];
|
||||
}
|
||||
return CFBridgingRetain(s);
|
||||
}
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime/cgo"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/io/pointer"
|
||||
)
|
||||
|
||||
// displayLink is the state for a display link (CVDisplayLinkRef on macOS,
|
||||
// CADisplayLink on iOS). It runs a state-machine goroutine that keeps the
|
||||
// display link running for a while after being stopped to avoid the thread
|
||||
// start/stop overhead and because the CVDisplayLink sometimes fails to
|
||||
// start, stop and start again within a short duration.
|
||||
type displayLink struct {
|
||||
callback func()
|
||||
// states is for starting or stopping the display link.
|
||||
states chan bool
|
||||
// done is closed when the display link is destroyed.
|
||||
done chan struct{}
|
||||
// dids receives the display id when the callback owner is moved
|
||||
// to a different screen.
|
||||
dids chan uint64
|
||||
// running tracks the desired state of the link. running is accessed
|
||||
// with atomic.
|
||||
running uint32
|
||||
}
|
||||
|
||||
// displayLinks maps CFTypeRefs to *displayLinks.
|
||||
var displayLinks sync.Map
|
||||
|
||||
func isMainThread() bool {
|
||||
return bool(C.isMainThread())
|
||||
}
|
||||
|
||||
// runOnMain runs the function on the main thread.
|
||||
func runOnMain(f func()) {
|
||||
if isMainThread() {
|
||||
f()
|
||||
return
|
||||
}
|
||||
C.gio_runOnMain(C.uintptr_t(cgo.NewHandle(f)))
|
||||
}
|
||||
|
||||
//export gio_runFunc
|
||||
func gio_runFunc(h C.uintptr_t) {
|
||||
handle := cgo.Handle(h)
|
||||
defer handle.Delete()
|
||||
f := handle.Value().(func())
|
||||
f()
|
||||
}
|
||||
|
||||
// nsstringToString converts a NSString to a Go string.
|
||||
func nsstringToString(str C.CFTypeRef) string {
|
||||
if str == 0 {
|
||||
return ""
|
||||
}
|
||||
n := C.nsstringLength(str)
|
||||
if n == 0 {
|
||||
return ""
|
||||
}
|
||||
chars := make([]uint16, n)
|
||||
C.nsstringGetCharacters(str, (*C.unichar)(unsafe.Pointer(&chars[0])), 0, n)
|
||||
utf8 := utf16.Decode(chars)
|
||||
return string(utf8)
|
||||
}
|
||||
|
||||
// stringToNSString converts a Go string to a retained NSString.
|
||||
func stringToNSString(str string) C.CFTypeRef {
|
||||
u16 := utf16.Encode([]rune(str))
|
||||
var chars *C.unichar
|
||||
if len(u16) > 0 {
|
||||
chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
|
||||
}
|
||||
return C.newNSString(chars, C.NSUInteger(len(u16)))
|
||||
}
|
||||
|
||||
func newDisplayLink(callback func()) (*displayLink, error) {
|
||||
d := &displayLink{
|
||||
callback: callback,
|
||||
done: make(chan struct{}),
|
||||
states: make(chan bool),
|
||||
dids: make(chan uint64),
|
||||
}
|
||||
dl := C.gio_createDisplayLink()
|
||||
if dl == 0 {
|
||||
return nil, errors.New("app: failed to create display link")
|
||||
}
|
||||
go d.run(dl)
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (d *displayLink) run(dl C.CFTypeRef) {
|
||||
defer C.gio_releaseDisplayLink(dl)
|
||||
displayLinks.Store(dl, d)
|
||||
defer displayLinks.Delete(dl)
|
||||
var stopTimer *time.Timer
|
||||
var tchan <-chan time.Time
|
||||
started := false
|
||||
for {
|
||||
select {
|
||||
case <-tchan:
|
||||
tchan = nil
|
||||
started = false
|
||||
C.gio_stopDisplayLink(dl)
|
||||
case start := <-d.states:
|
||||
switch {
|
||||
case !start && tchan == nil:
|
||||
// stopTimeout is the delay before stopping the display link to
|
||||
// avoid the overhead of frequently starting and stopping the
|
||||
// link thread.
|
||||
const stopTimeout = 500 * time.Millisecond
|
||||
if stopTimer == nil {
|
||||
stopTimer = time.NewTimer(stopTimeout)
|
||||
} else {
|
||||
// stopTimer is always drained when tchan == nil.
|
||||
stopTimer.Reset(stopTimeout)
|
||||
}
|
||||
tchan = stopTimer.C
|
||||
atomic.StoreUint32(&d.running, 0)
|
||||
case start:
|
||||
if tchan != nil && !stopTimer.Stop() {
|
||||
<-tchan
|
||||
}
|
||||
tchan = nil
|
||||
atomic.StoreUint32(&d.running, 1)
|
||||
if !started {
|
||||
started = true
|
||||
C.gio_startDisplayLink(dl)
|
||||
}
|
||||
}
|
||||
case did := <-d.dids:
|
||||
C.gio_setDisplayLinkDisplay(dl, C.uint64_t(did))
|
||||
case <-d.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *displayLink) Start() {
|
||||
d.states <- true
|
||||
}
|
||||
|
||||
func (d *displayLink) Stop() {
|
||||
d.states <- false
|
||||
}
|
||||
|
||||
func (d *displayLink) Close() {
|
||||
close(d.done)
|
||||
}
|
||||
|
||||
func (d *displayLink) SetDisplayID(did uint64) {
|
||||
d.dids <- did
|
||||
}
|
||||
|
||||
//export gio_onFrameCallback
|
||||
func gio_onFrameCallback(ref C.CFTypeRef) {
|
||||
d, exists := displayLinks.Load(ref)
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
dl := d.(*displayLink)
|
||||
if atomic.LoadUint32(&dl.running) != 0 {
|
||||
dl.callback()
|
||||
}
|
||||
}
|
||||
|
||||
var macosCursorID = [...]byte{
|
||||
pointer.CursorDefault: 0,
|
||||
pointer.CursorNone: 1,
|
||||
pointer.CursorText: 2,
|
||||
pointer.CursorVerticalText: 3,
|
||||
pointer.CursorPointer: 4,
|
||||
pointer.CursorCrosshair: 5,
|
||||
pointer.CursorAllScroll: 6,
|
||||
pointer.CursorColResize: 7,
|
||||
pointer.CursorRowResize: 8,
|
||||
pointer.CursorGrab: 9,
|
||||
pointer.CursorGrabbing: 10,
|
||||
pointer.CursorNotAllowed: 11,
|
||||
pointer.CursorWait: 12,
|
||||
pointer.CursorProgress: 13,
|
||||
pointer.CursorNorthWestResize: 14,
|
||||
pointer.CursorNorthEastResize: 15,
|
||||
pointer.CursorSouthWestResize: 16,
|
||||
pointer.CursorSouthEastResize: 17,
|
||||
pointer.CursorNorthSouthResize: 18,
|
||||
pointer.CursorEastWestResize: 19,
|
||||
pointer.CursorWestResize: 20,
|
||||
pointer.CursorEastResize: 21,
|
||||
pointer.CursorNorthResize: 22,
|
||||
pointer.CursorSouthResize: 23,
|
||||
pointer.CursorNorthEastSouthWestResize: 24,
|
||||
pointer.CursorNorthWestSouthEastResize: 25,
|
||||
}
|
||||
|
||||
// windowSetCursor updates the cursor from the current one to a new one
|
||||
// and returns the new one.
|
||||
func windowSetCursor(from, to pointer.Cursor) pointer.Cursor {
|
||||
if from == to {
|
||||
return to
|
||||
}
|
||||
if to == pointer.CursorNone {
|
||||
C.gio_hideCursor()
|
||||
return to
|
||||
}
|
||||
if from == pointer.CursorNone {
|
||||
C.gio_showCursor()
|
||||
}
|
||||
C.gio_setCursor(C.NSUInteger(macosCursorID[to]))
|
||||
return to
|
||||
}
|
||||
|
||||
func (w *window) wakeup() {
|
||||
runOnMain(func() {
|
||||
w.loop.Wakeup()
|
||||
w.loop.FlushEvents()
|
||||
})
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "_cgo_export.h"
|
||||
|
||||
void gio_runOnMain(uintptr_t h) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
gio_runFunc(h);
|
||||
});
|
||||
}
|
||||
+456
@@ -0,0 +1,456 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build darwin && ios
|
||||
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -DGLES_SILENCE_DEPRECATION -Werror -Wno-deprecated-declarations -fmodules -fobjc-arc -x objective-c
|
||||
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
#include <UIKit/UIKit.h>
|
||||
#include <stdint.h>
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) int gio_applicationMain(int argc, char *argv[]);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle);
|
||||
|
||||
struct drawParams {
|
||||
CGFloat dpi, sdpi;
|
||||
CGFloat width, height;
|
||||
CGFloat top, right, bottom, left;
|
||||
};
|
||||
|
||||
static void writeClipboard(unichar *chars, NSUInteger length) {
|
||||
#if !TARGET_OS_TV
|
||||
@autoreleasepool {
|
||||
NSString *s = [NSString string];
|
||||
if (length > 0) {
|
||||
s = [NSString stringWithCharacters:chars length:length];
|
||||
}
|
||||
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
||||
p.string = s;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static CFTypeRef readClipboard(void) {
|
||||
#if !TARGET_OS_TV
|
||||
@autoreleasepool {
|
||||
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
||||
return (__bridge_retained CFTypeRef)p.string;
|
||||
}
|
||||
#else
|
||||
return nil;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void showTextInput(CFTypeRef viewRef) {
|
||||
UIView *view = (__bridge UIView *)viewRef;
|
||||
[view becomeFirstResponder];
|
||||
}
|
||||
|
||||
static void hideTextInput(CFTypeRef viewRef) {
|
||||
UIView *view = (__bridge UIView *)viewRef;
|
||||
[view resignFirstResponder];
|
||||
}
|
||||
|
||||
static struct drawParams viewDrawParams(CFTypeRef viewRef) {
|
||||
UIView *v = (__bridge UIView *)viewRef;
|
||||
struct drawParams params;
|
||||
CGFloat scale = v.layer.contentsScale;
|
||||
// Use 163 as the standard ppi on iOS.
|
||||
params.dpi = 163*scale;
|
||||
params.sdpi = params.dpi;
|
||||
UIEdgeInsets insets = v.layoutMargins;
|
||||
if (@available(iOS 11.0, tvOS 11.0, *)) {
|
||||
UIFontMetrics *metrics = [UIFontMetrics defaultMetrics];
|
||||
params.sdpi = [metrics scaledValueForValue:params.sdpi];
|
||||
insets = v.safeAreaInsets;
|
||||
}
|
||||
params.width = v.bounds.size.width*scale;
|
||||
params.height = v.bounds.size.height*scale;
|
||||
params.top = insets.top*scale;
|
||||
params.right = insets.right*scale;
|
||||
params.bottom = insets.bottom*scale;
|
||||
params.left = insets.left*scale;
|
||||
return params;
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"image"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/cgo"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/io/transfer"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
type UIKitViewEvent struct {
|
||||
// ViewController is a CFTypeRef for the UIViewController backing a Window.
|
||||
ViewController uintptr
|
||||
}
|
||||
|
||||
type window struct {
|
||||
view C.CFTypeRef
|
||||
w *callbacks
|
||||
displayLink *displayLink
|
||||
loop *eventLoop
|
||||
|
||||
hidden bool
|
||||
cursor pointer.Cursor
|
||||
config Config
|
||||
|
||||
pointerMap []C.CFTypeRef
|
||||
}
|
||||
|
||||
var mainWindow = newWindowRendezvous()
|
||||
|
||||
func init() {
|
||||
// Darwin requires UI operations happen on the main thread only.
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
//export onCreate
|
||||
func onCreate(view, controller C.CFTypeRef) {
|
||||
wopts := <-mainWindow.out
|
||||
w := &window{
|
||||
view: view,
|
||||
w: wopts.window,
|
||||
}
|
||||
w.loop = newEventLoop(w.w, w.wakeup)
|
||||
w.w.SetDriver(w)
|
||||
mainWindow.windows <- struct{}{}
|
||||
dl, err := newDisplayLink(func() {
|
||||
w.draw(false)
|
||||
})
|
||||
if err != nil {
|
||||
w.w.ProcessEvent(DestroyEvent{Err: err})
|
||||
return
|
||||
}
|
||||
w.displayLink = dl
|
||||
C.gio_viewSetHandle(view, C.uintptr_t(cgo.NewHandle(w)))
|
||||
w.Configure(wopts.options)
|
||||
w.ProcessEvent(UIKitViewEvent{ViewController: uintptr(controller)})
|
||||
}
|
||||
|
||||
func viewFor(h C.uintptr_t) *window {
|
||||
return cgo.Handle(h).Value().(*window)
|
||||
}
|
||||
|
||||
//export gio_onDraw
|
||||
func gio_onDraw(h C.uintptr_t) {
|
||||
w := viewFor(h)
|
||||
w.draw(true)
|
||||
}
|
||||
|
||||
func (w *window) draw(sync bool) {
|
||||
if w.hidden {
|
||||
return
|
||||
}
|
||||
params := C.viewDrawParams(w.view)
|
||||
if params.width == 0 || params.height == 0 {
|
||||
return
|
||||
}
|
||||
const inchPrDp = 1.0 / 163
|
||||
m := unit.Metric{
|
||||
PxPerDp: float32(params.dpi) * inchPrDp,
|
||||
PxPerSp: float32(params.sdpi) * inchPrDp,
|
||||
}
|
||||
dppp := unit.Dp(1. / m.PxPerDp)
|
||||
w.ProcessEvent(frameEvent{
|
||||
FrameEvent: FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: image.Point{
|
||||
X: int(params.width + .5),
|
||||
Y: int(params.height + .5),
|
||||
},
|
||||
Insets: Insets{
|
||||
Top: unit.Dp(params.top) * dppp,
|
||||
Bottom: unit.Dp(params.bottom) * dppp,
|
||||
Left: unit.Dp(params.left) * dppp,
|
||||
Right: unit.Dp(params.right) * dppp,
|
||||
},
|
||||
Metric: m,
|
||||
},
|
||||
Sync: sync,
|
||||
})
|
||||
}
|
||||
|
||||
//export onStop
|
||||
func onStop(h C.uintptr_t) {
|
||||
w := viewFor(h)
|
||||
w.hidden = true
|
||||
}
|
||||
|
||||
//export onStart
|
||||
func onStart(h C.uintptr_t) {
|
||||
w := viewFor(h)
|
||||
w.hidden = false
|
||||
w.draw(true)
|
||||
}
|
||||
|
||||
//export onDestroy
|
||||
func onDestroy(h C.uintptr_t) {
|
||||
w := viewFor(h)
|
||||
w.ProcessEvent(UIKitViewEvent{})
|
||||
w.ProcessEvent(DestroyEvent{})
|
||||
w.displayLink.Close()
|
||||
w.displayLink = nil
|
||||
cgo.Handle(h).Delete()
|
||||
w.view = 0
|
||||
}
|
||||
|
||||
//export onFocus
|
||||
func onFocus(h C.uintptr_t, focus int) {
|
||||
w := viewFor(h)
|
||||
w.config.Focused = focus != 0
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
//export onLowMemory
|
||||
func onLowMemory() {
|
||||
runtime.GC()
|
||||
debug.FreeOSMemory()
|
||||
}
|
||||
|
||||
//export onUpArrow
|
||||
func onUpArrow(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameUpArrow)
|
||||
}
|
||||
|
||||
//export onDownArrow
|
||||
func onDownArrow(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameDownArrow)
|
||||
}
|
||||
|
||||
//export onLeftArrow
|
||||
func onLeftArrow(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameLeftArrow)
|
||||
}
|
||||
|
||||
//export onRightArrow
|
||||
func onRightArrow(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameRightArrow)
|
||||
}
|
||||
|
||||
//export onDeleteBackward
|
||||
func onDeleteBackward(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameDeleteBackward)
|
||||
}
|
||||
|
||||
//export onText
|
||||
func onText(h C.uintptr_t, str C.CFTypeRef) {
|
||||
w := viewFor(h)
|
||||
w.w.EditorInsert(nsstringToString(str))
|
||||
}
|
||||
|
||||
//export onTouch
|
||||
func onTouch(h C.uintptr_t, last C.int, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) {
|
||||
var kind pointer.Kind
|
||||
switch phase {
|
||||
case C.UITouchPhaseBegan:
|
||||
kind = pointer.Press
|
||||
case C.UITouchPhaseMoved:
|
||||
kind = pointer.Move
|
||||
case C.UITouchPhaseEnded:
|
||||
kind = pointer.Release
|
||||
case C.UITouchPhaseCancelled:
|
||||
kind = pointer.Cancel
|
||||
default:
|
||||
return
|
||||
}
|
||||
w := viewFor(h)
|
||||
t := time.Duration(float64(ti) * float64(time.Second))
|
||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: kind,
|
||||
Source: pointer.Touch,
|
||||
PointerID: w.lookupTouch(last != 0, touchRef),
|
||||
Position: p,
|
||||
Time: t,
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) ReadClipboard() {
|
||||
cstr := C.readClipboard()
|
||||
defer C.CFRelease(cstr)
|
||||
content := nsstringToString(cstr)
|
||||
w.ProcessEvent(transfer.DataEvent{
|
||||
Type: "application/text",
|
||||
Open: func() io.ReadCloser {
|
||||
return io.NopCloser(strings.NewReader(content))
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(mime string, s []byte) {
|
||||
u16 := utf16.Encode([]rune(string(s)))
|
||||
var chars *C.unichar
|
||||
if len(u16) > 0 {
|
||||
chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
|
||||
}
|
||||
C.writeClipboard(chars, C.NSUInteger(len(u16)))
|
||||
}
|
||||
|
||||
func (w *window) Configure([]Option) {
|
||||
// Decorations are never disabled.
|
||||
w.config.Decorated = true
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
func (w *window) EditorStateChanged(old, new editorState) {}
|
||||
|
||||
func (w *window) Perform(system.Action) {}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
if anim {
|
||||
w.displayLink.Start()
|
||||
} else {
|
||||
w.displayLink.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) SetCursor(cursor pointer.Cursor) {
|
||||
w.cursor = windowSetCursor(w.cursor, cursor)
|
||||
}
|
||||
|
||||
func (w *window) onKeyCommand(name key.Name) {
|
||||
w.ProcessEvent(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) contextView() C.CFTypeRef {
|
||||
return w.view
|
||||
}
|
||||
|
||||
func (w *window) ShowTextInput(show bool) {
|
||||
if show {
|
||||
C.showTextInput(w.view)
|
||||
} else {
|
||||
C.hideTextInput(w.view)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) SetInputHint(_ key.InputHint) {}
|
||||
|
||||
func (w *window) ProcessEvent(e event.Event) {
|
||||
w.w.ProcessEvent(e)
|
||||
w.loop.FlushEvents()
|
||||
}
|
||||
|
||||
func (w *window) Event() event.Event {
|
||||
return w.loop.Event()
|
||||
}
|
||||
|
||||
func (w *window) Invalidate() {
|
||||
w.loop.Invalidate()
|
||||
}
|
||||
|
||||
func (w *window) Run(f func()) {
|
||||
w.loop.Run(f)
|
||||
}
|
||||
|
||||
func (w *window) Frame(frame *op.Ops) {
|
||||
w.loop.Frame(frame)
|
||||
}
|
||||
|
||||
func newWindow(win *callbacks, options []Option) {
|
||||
mainWindow.in <- windowAndConfig{win, options}
|
||||
<-mainWindow.windows
|
||||
}
|
||||
|
||||
var mainMode = mainModeUndefined
|
||||
|
||||
const (
|
||||
mainModeUndefined = iota
|
||||
mainModeExe
|
||||
mainModeLibrary
|
||||
)
|
||||
|
||||
func osMain() {
|
||||
switch mainMode {
|
||||
case mainModeUndefined:
|
||||
if !isMainThread() {
|
||||
panic("app.Main must be run on the main goroutine")
|
||||
}
|
||||
|
||||
mainMode = mainModeExe
|
||||
var argv []*C.char
|
||||
for _, arg := range os.Args {
|
||||
a := C.CString(arg)
|
||||
defer C.free(unsafe.Pointer(a))
|
||||
argv = append(argv, a)
|
||||
}
|
||||
C.gio_applicationMain(C.int(len(argv)), unsafe.SliceData(argv))
|
||||
case mainModeExe:
|
||||
panic("app.Main may be called only once")
|
||||
case mainModeLibrary:
|
||||
// Do nothing, we're embedded as a library.
|
||||
}
|
||||
select {}
|
||||
}
|
||||
|
||||
//export gio_onOpenURI
|
||||
func gio_onOpenURI(uri C.CFTypeRef) {
|
||||
evt, err := newURLEvent(nsstringToString(uri))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
processGlobalEvent(evt)
|
||||
}
|
||||
|
||||
//export gio_runMain
|
||||
func gio_runMain() {
|
||||
if !isMainThread() {
|
||||
panic("app.Main must be run on the main goroutine")
|
||||
}
|
||||
switch mainMode {
|
||||
case mainModeUndefined:
|
||||
mainMode = mainModeLibrary
|
||||
runMain()
|
||||
case mainModeExe:
|
||||
// Do nothing, main has already been called.
|
||||
}
|
||||
}
|
||||
|
||||
func (UIKitViewEvent) implementsViewEvent() {}
|
||||
func (UIKitViewEvent) ImplementsEvent() {}
|
||||
func (u UIKitViewEvent) Valid() bool {
|
||||
return u != (UIKitViewEvent{})
|
||||
}
|
||||
+342
@@ -0,0 +1,342 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,ios
|
||||
|
||||
@import UIKit;
|
||||
|
||||
#include <stdint.h>
|
||||
#include "_cgo_export.h"
|
||||
#include "framework_ios.h"
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) Class gio_layerClass(void);
|
||||
|
||||
@interface GioView: UIView <UIKeyInput>
|
||||
@property uintptr_t handle;
|
||||
@end
|
||||
|
||||
@implementation GioViewController
|
||||
|
||||
CGFloat _keyboardHeight;
|
||||
|
||||
- (void)loadView {
|
||||
gio_runMain();
|
||||
|
||||
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];
|
||||
#if !TARGET_OS_TV
|
||||
drawView.multipleTouchEnabled = YES;
|
||||
#endif
|
||||
drawView.preservesSuperviewLayoutMargins = YES;
|
||||
drawView.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
|
||||
onCreate((__bridge CFTypeRef)drawView, (__bridge CFTypeRef)self);
|
||||
#if !TARGET_OS_TV
|
||||
[[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];
|
||||
#endif
|
||||
[[NSNotificationCenter defaultCenter] addObserver: self
|
||||
selector: @selector(applicationDidEnterBackground:)
|
||||
name: UIApplicationDidEnterBackgroundNotification
|
||||
object: nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver: self
|
||||
selector: @selector(applicationWillEnterForeground:)
|
||||
name: UIApplicationWillEnterForegroundNotification
|
||||
object: nil];
|
||||
}
|
||||
|
||||
- (void)applicationWillEnterForeground:(UIApplication *)application {
|
||||
GioView *view = (GioView *)self.view.subviews[0];
|
||||
if (view != nil) {
|
||||
onStart(view.handle);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
||||
GioView *view = (GioView *)self.view.subviews[0];
|
||||
if (view != nil) {
|
||||
onStop(view.handle);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewDidDisappear:(BOOL)animated {
|
||||
[super viewDidDisappear:animated];
|
||||
GioView *view = (GioView *)self.view.subviews[0];
|
||||
onDestroy(view.handle);
|
||||
}
|
||||
|
||||
- (void)viewDidLayoutSubviews {
|
||||
[super viewDidLayoutSubviews];
|
||||
GioView *view = (GioView *)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;
|
||||
gio_onDraw(view.handle);
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning {
|
||||
onLowMemory();
|
||||
[super didReceiveMemoryWarning];
|
||||
}
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
- (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];
|
||||
}
|
||||
#endif
|
||||
@end
|
||||
|
||||
static void handleTouches(int last, GioView *view, NSSet<UITouch *> *touches, UIEvent *event) {
|
||||
CGFloat scale = view.contentScaleFactor;
|
||||
NSUInteger i = 0;
|
||||
NSUInteger n = [touches count];
|
||||
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(view.handle, lastTouch, touchRef, touch.phase, loc.x*scale, loc.y*scale, [coalescedTouch timestamp]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@implementation GioView
|
||||
NSArray<UIKeyCommand *> *_keyCommands;
|
||||
+ (void)onFrameCallback:(CADisplayLink *)link {
|
||||
gio_onFrameCallback((__bridge CFTypeRef)link);
|
||||
}
|
||||
+ (Class)layerClass {
|
||||
return gio_layerClass();
|
||||
}
|
||||
- (void)willMoveToWindow:(UIWindow *)newWindow {
|
||||
self.contentScaleFactor = newWindow.screen.nativeScale;
|
||||
if (@available(iOS 13.0, *)) {
|
||||
[self registerSceneNotifications:newWindow];
|
||||
}else{
|
||||
[self registerWindowNotifications:newWindow];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)registerSceneNotifications:(UIWindow *)newWindow {
|
||||
if (self.window != nil) {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:UISceneDidActivateNotification
|
||||
object:self.window.windowScene];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:UISceneWillDeactivateNotification
|
||||
object:self.window.windowScene];
|
||||
}
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(onSceneDidActivate:)
|
||||
name:UISceneDidActivateNotification
|
||||
object:newWindow.windowScene];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(onSceneWillDeactivate:)
|
||||
name:UISceneWillDeactivateNotification
|
||||
object:newWindow.windowScene];
|
||||
}
|
||||
|
||||
- (void)onSceneDidActivate:(NSNotification *)note API_AVAILABLE(ios(13.0)){
|
||||
onFocus(self.handle, YES);
|
||||
}
|
||||
|
||||
- (void)onSceneWillDeactivate:(NSNotification *)note API_AVAILABLE(ios(13.0)){
|
||||
onFocus(self.handle, NO);
|
||||
}
|
||||
|
||||
- (void)registerWindowNotifications:(UIWindow *)newWindow {
|
||||
if (self.window != nil) {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:UIWindowDidBecomeKeyNotification
|
||||
object:self.window];
|
||||
[[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(self.handle, YES);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onWindowDidResignKey:(NSNotification *)note {
|
||||
if (self.isFirstResponder) {
|
||||
onFocus(self.handle, NO);
|
||||
}
|
||||
}
|
||||
|
||||
- (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(self.handle, (__bridge CFTypeRef)text);
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)hasText {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)deleteBackward {
|
||||
onDeleteBackward(self.handle);
|
||||
}
|
||||
|
||||
- (void)onUpArrow {
|
||||
onUpArrow(self.handle);
|
||||
}
|
||||
|
||||
- (void)onDownArrow {
|
||||
onDownArrow(self.handle);
|
||||
}
|
||||
|
||||
- (void)onLeftArrow {
|
||||
onLeftArrow(self.handle);
|
||||
}
|
||||
|
||||
- (void)onRightArrow {
|
||||
onRightArrow(self.handle);
|
||||
}
|
||||
|
||||
- (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
|
||||
|
||||
CFTypeRef gio_createDisplayLink(void) {
|
||||
CADisplayLink *dl = [CADisplayLink displayLinkWithTarget:[GioView class] selector:@selector(onFrameCallback:)];
|
||||
dl.paused = YES;
|
||||
NSRunLoop *runLoop = [NSRunLoop mainRunLoop];
|
||||
[dl addToRunLoop:runLoop forMode:[runLoop currentMode]];
|
||||
return (__bridge_retained CFTypeRef)dl;
|
||||
}
|
||||
|
||||
int gio_startDisplayLink(CFTypeRef dlref) {
|
||||
CADisplayLink *dl = (__bridge CADisplayLink *)dlref;
|
||||
dl.paused = NO;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gio_stopDisplayLink(CFTypeRef dlref) {
|
||||
CADisplayLink *dl = (__bridge CADisplayLink *)dlref;
|
||||
dl.paused = YES;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void gio_releaseDisplayLink(CFTypeRef dlref) {
|
||||
CADisplayLink *dl = (__bridge CADisplayLink *)dlref;
|
||||
[dl invalidate];
|
||||
CFRelease(dlref);
|
||||
}
|
||||
|
||||
void gio_setDisplayLinkDisplay(CFTypeRef dl, uint64_t did) {
|
||||
// Nothing to do on iOS.
|
||||
}
|
||||
|
||||
void gio_hideCursor() {
|
||||
// Not supported.
|
||||
}
|
||||
|
||||
void gio_showCursor() {
|
||||
// Not supported.
|
||||
}
|
||||
|
||||
void gio_setCursor(NSUInteger curID) {
|
||||
// Not supported.
|
||||
}
|
||||
|
||||
void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
|
||||
GioView *v = (__bridge GioView *)viewRef;
|
||||
v.handle = handle;
|
||||
}
|
||||
|
||||
@interface _gioAppDelegate : UIResponder <UIApplicationDelegate>
|
||||
@property (strong, nonatomic) UIWindow *window;
|
||||
@end
|
||||
|
||||
@implementation _gioAppDelegate
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||
GioViewController *controller = [[GioViewController alloc] initWithNibName:nil bundle:nil];
|
||||
self.window.rootViewController = controller;
|
||||
[self.window makeKeyAndVisible];
|
||||
return YES;
|
||||
}
|
||||
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
|
||||
gio_onOpenURI((__bridge CFTypeRef)url.absoluteString);
|
||||
return YES;
|
||||
}
|
||||
@end
|
||||
|
||||
int gio_applicationMain(int argc, char *argv[]) {
|
||||
@autoreleasepool {
|
||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass([_gioAppDelegate class]));
|
||||
}
|
||||
}
|
||||
+1161
File diff suppressed because it is too large
Load Diff
+1192
File diff suppressed because it is too large
Load Diff
+479
@@ -0,0 +1,479 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,!ios
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
#include "_cgo_export.h"
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(BOOL presentWithTrans);
|
||||
|
||||
@interface GioAppDelegate : NSObject<NSApplicationDelegate>
|
||||
@end
|
||||
|
||||
@interface GioWindowDelegate : NSObject<NSWindowDelegate>
|
||||
@end
|
||||
|
||||
@interface GioView : NSView <CALayerDelegate,NSTextInputClient>
|
||||
@property uintptr_t handle;
|
||||
@property BOOL presentWithTrans;
|
||||
@end
|
||||
|
||||
@implementation GioWindowDelegate
|
||||
- (void)windowWillMiniaturize:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onDraw(view.handle);
|
||||
}
|
||||
- (void)windowDidDeminiaturize:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onDraw(view.handle);
|
||||
}
|
||||
- (void)windowWillEnterFullScreen:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onDraw(view.handle);
|
||||
}
|
||||
- (void)windowWillExitFullScreen:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onDraw(view.handle);
|
||||
}
|
||||
- (void)windowDidChangeScreen:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
CGDirectDisplayID dispID = [[[window screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue];
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onChangeScreen(view.handle, dispID);
|
||||
}
|
||||
- (void)windowDidBecomeKey:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
if ([window firstResponder] == view) {
|
||||
gio_onFocus(view.handle, 1);
|
||||
}
|
||||
}
|
||||
- (void)windowDidResignKey:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
if ([window firstResponder] == view) {
|
||||
gio_onFocus(view.handle, 0);
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
static void handleMouse(GioView *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;
|
||||
}
|
||||
// Origin is in the lower left corner. Convert to upper left.
|
||||
CGFloat height = view.bounds.size.height;
|
||||
gio_onMouse(view.handle, (__bridge CFTypeRef)event, typ, event.buttonNumber, p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]);
|
||||
}
|
||||
|
||||
@implementation GioView
|
||||
- (void)setFrameSize:(NSSize)newSize {
|
||||
[super setFrameSize:newSize];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
// drawRect is called when OpenGL is used, displayLayer otherwise.
|
||||
// Don't know why.
|
||||
- (void)drawRect:(NSRect)r {
|
||||
gio_onDraw(self.handle);
|
||||
}
|
||||
- (void)displayLayer:(CALayer *)layer {
|
||||
layer.contentsScale = self.window.backingScaleFactor;
|
||||
gio_onDraw(self.handle);
|
||||
}
|
||||
- (CALayer *)makeBackingLayer {
|
||||
CALayer *layer = gio_layerFactory(self.presentWithTrans);
|
||||
layer.delegate = self;
|
||||
return layer;
|
||||
}
|
||||
- (void)viewDidMoveToWindow {
|
||||
gio_onAttached(self.handle, self.window != nil ? 1 : 0);
|
||||
}
|
||||
- (void)mouseDown:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_DOWN, 0, 0);
|
||||
}
|
||||
- (void)mouseUp:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_UP, 0, 0);
|
||||
}
|
||||
- (void)rightMouseDown:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_DOWN, 0, 0);
|
||||
}
|
||||
- (void)rightMouseUp:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_UP, 0, 0);
|
||||
}
|
||||
- (void)otherMouseDown:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_DOWN, 0, 0);
|
||||
}
|
||||
- (void)otherMouseUp:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_UP, 0, 0);
|
||||
}
|
||||
- (void)mouseMoved:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_MOVE, 0, 0);
|
||||
}
|
||||
- (void)mouseDragged:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_MOVE, 0, 0);
|
||||
}
|
||||
- (void)rightMouseDragged:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_MOVE, 0, 0);
|
||||
}
|
||||
- (void)otherMouseDragged:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_MOVE, 0, 0);
|
||||
}
|
||||
- (void)scrollWheel:(NSEvent *)event {
|
||||
CGFloat dx = -event.scrollingDeltaX;
|
||||
CGFloat dy = -event.scrollingDeltaY;
|
||||
handleMouse(self, event, MOUSE_SCROLL, dx, dy);
|
||||
}
|
||||
- (void)keyDown:(NSEvent *)event {
|
||||
NSString *keys = [event charactersIgnoringModifiers];
|
||||
gio_onKeys(self.handle, (__bridge CFTypeRef)event, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true);
|
||||
}
|
||||
- (void)flagsChanged:(NSEvent *)event {
|
||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
||||
gio_onFlagsChanged(self.handle, [event modifierFlags]);
|
||||
}
|
||||
- (void)keyUp:(NSEvent *)event {
|
||||
NSString *keys = [event charactersIgnoringModifiers];
|
||||
gio_onKeys(self.handle, (__bridge CFTypeRef)event, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], false);
|
||||
}
|
||||
- (void)insertText:(id)string {
|
||||
gio_onText(self.handle, (__bridge CFTypeRef)string);
|
||||
}
|
||||
- (void)doCommandBySelector:(SEL)action {
|
||||
if (!gio_onCommandBySelector(self.handle)) {
|
||||
[super doCommandBySelector:action];
|
||||
}
|
||||
}
|
||||
- (BOOL)hasMarkedText {
|
||||
int res = gio_hasMarkedText(self.handle);
|
||||
return res ? YES : NO;
|
||||
}
|
||||
- (NSRange)markedRange {
|
||||
return gio_markedRange(self.handle);
|
||||
}
|
||||
- (NSRange)selectedRange {
|
||||
return gio_selectedRange(self.handle);
|
||||
}
|
||||
- (void)unmarkText {
|
||||
gio_unmarkText(self.handle);
|
||||
}
|
||||
- (void)setMarkedText:(id)string
|
||||
selectedRange:(NSRange)selRange
|
||||
replacementRange:(NSRange)replaceRange {
|
||||
NSString *str;
|
||||
// string is either an NSAttributedString or an NSString.
|
||||
if ([string isKindOfClass:[NSAttributedString class]]) {
|
||||
str = [string string];
|
||||
} else {
|
||||
str = string;
|
||||
}
|
||||
gio_setMarkedText(self.handle, (__bridge CFTypeRef)str, selRange, replaceRange);
|
||||
}
|
||||
- (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText {
|
||||
return nil;
|
||||
}
|
||||
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range
|
||||
actualRange:(NSRangePointer)actualRange {
|
||||
NSString *str = CFBridgingRelease(gio_substringForProposedRange(self.handle, range, actualRange));
|
||||
return [[NSAttributedString alloc] initWithString:str attributes:nil];
|
||||
}
|
||||
- (void)insertText:(id)string
|
||||
replacementRange:(NSRange)replaceRange {
|
||||
NSString *str;
|
||||
// string is either an NSAttributedString or an NSString.
|
||||
if ([string isKindOfClass:[NSAttributedString class]]) {
|
||||
str = [string string];
|
||||
} else {
|
||||
str = string;
|
||||
}
|
||||
gio_insertText(self.handle, (__bridge CFTypeRef)str, replaceRange);
|
||||
}
|
||||
- (NSUInteger)characterIndexForPoint:(NSPoint)p {
|
||||
return gio_characterIndexForPoint(self.handle, p);
|
||||
}
|
||||
- (NSRect)firstRectForCharacterRange:(NSRange)rng
|
||||
actualRange:(NSRangePointer)actual {
|
||||
NSRect r = gio_firstRectForCharacterRange(self.handle, rng, actual);
|
||||
r = [self convertRect:r toView:nil];
|
||||
return [[self window] convertRectToScreen:r];
|
||||
}
|
||||
- (void)applicationWillUnhide:(NSNotification *)notification {
|
||||
gio_onDraw(self.handle);
|
||||
}
|
||||
- (void)applicationDidHide:(NSNotification *)notification {
|
||||
gio_onDraw(self.handle);
|
||||
}
|
||||
- (void)dealloc {
|
||||
gio_onDestroy(self.handle);
|
||||
}
|
||||
- (BOOL) becomeFirstResponder {
|
||||
gio_onFocus(self.handle, 1);
|
||||
return [super becomeFirstResponder];
|
||||
}
|
||||
- (BOOL) resignFirstResponder {
|
||||
gio_onFocus(self.handle, 0);
|
||||
return [super resignFirstResponder];
|
||||
}
|
||||
@end
|
||||
|
||||
// Delegates are weakly referenced from their peers. Nothing
|
||||
// else holds a strong reference to our window delegate, so
|
||||
// keep a single global reference instead.
|
||||
static GioWindowDelegate *globalWindowDel;
|
||||
|
||||
static CVReturn displayLinkCallback(CVDisplayLinkRef dl, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *handle) {
|
||||
gio_onFrameCallback(dl);
|
||||
return kCVReturnSuccess;
|
||||
}
|
||||
|
||||
CFTypeRef gio_createDisplayLink(void) {
|
||||
CVDisplayLinkRef dl;
|
||||
CVDisplayLinkCreateWithActiveCGDisplays(&dl);
|
||||
CVDisplayLinkSetOutputCallback(dl, displayLinkCallback, nil);
|
||||
return dl;
|
||||
}
|
||||
|
||||
int gio_startDisplayLink(CFTypeRef dl) {
|
||||
return CVDisplayLinkStart((CVDisplayLinkRef)dl);
|
||||
}
|
||||
|
||||
int gio_stopDisplayLink(CFTypeRef dl) {
|
||||
return CVDisplayLinkStop((CVDisplayLinkRef)dl);
|
||||
}
|
||||
|
||||
void gio_releaseDisplayLink(CFTypeRef dl) {
|
||||
CVDisplayLinkRelease((CVDisplayLinkRef)dl);
|
||||
}
|
||||
|
||||
void gio_setDisplayLinkDisplay(CFTypeRef dl, uint64_t did) {
|
||||
CVDisplayLinkSetCurrentCGDisplay((CVDisplayLinkRef)dl, (CGDirectDisplayID)did);
|
||||
}
|
||||
|
||||
void gio_hideCursor() {
|
||||
@autoreleasepool {
|
||||
[NSCursor hide];
|
||||
}
|
||||
}
|
||||
|
||||
void gio_showCursor() {
|
||||
@autoreleasepool {
|
||||
[NSCursor unhide];
|
||||
}
|
||||
}
|
||||
|
||||
// some cursors are not public, this tries to use a private cursor
|
||||
// and uses fallback when the use of private cursor fails.
|
||||
static void trySetPrivateCursor(SEL cursorName, NSCursor* fallback) {
|
||||
if ([NSCursor respondsToSelector:cursorName]) {
|
||||
id object = [NSCursor performSelector:cursorName];
|
||||
if ([object isKindOfClass:[NSCursor class]]) {
|
||||
[(NSCursor*)object set];
|
||||
return;
|
||||
}
|
||||
}
|
||||
[fallback set];
|
||||
}
|
||||
|
||||
void gio_setCursor(NSUInteger curID) {
|
||||
@autoreleasepool {
|
||||
switch (curID) {
|
||||
case 0: // pointer.CursorDefault
|
||||
[NSCursor.arrowCursor set];
|
||||
break;
|
||||
// case 1: // pointer.CursorNone
|
||||
case 2: // pointer.CursorText
|
||||
[NSCursor.IBeamCursor set];
|
||||
break;
|
||||
case 3: // pointer.CursorVerticalText
|
||||
[NSCursor.IBeamCursorForVerticalLayout set];
|
||||
break;
|
||||
case 4: // pointer.CursorPointer
|
||||
[NSCursor.pointingHandCursor set];
|
||||
break;
|
||||
case 5: // pointer.CursorCrosshair
|
||||
[NSCursor.crosshairCursor set];
|
||||
break;
|
||||
case 6: // pointer.CursorAllScroll
|
||||
// For some reason, using _moveCursor fails on Monterey.
|
||||
// trySetPrivateCursor(@selector(_moveCursor), NSCursor.arrowCursor);
|
||||
[NSCursor.arrowCursor set];
|
||||
break;
|
||||
case 7: // pointer.CursorColResize
|
||||
[NSCursor.resizeLeftRightCursor set];
|
||||
break;
|
||||
case 8: // pointer.CursorRowResize
|
||||
[NSCursor.resizeUpDownCursor set];
|
||||
break;
|
||||
case 9: // pointer.CursorGrab
|
||||
[NSCursor.openHandCursor set];
|
||||
break;
|
||||
case 10: // pointer.CursorGrabbing
|
||||
[NSCursor.closedHandCursor set];
|
||||
break;
|
||||
case 11: // pointer.CursorNotAllowed
|
||||
[NSCursor.operationNotAllowedCursor set];
|
||||
break;
|
||||
case 12: // pointer.CursorWait
|
||||
trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
||||
break;
|
||||
case 13: // pointer.CursorProgress
|
||||
trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
||||
break;
|
||||
case 14: // pointer.CursorNorthWestResize
|
||||
trySetPrivateCursor(@selector(_windowResizeNorthWestCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 15: // pointer.CursorNorthEastResize
|
||||
trySetPrivateCursor(@selector(_windowResizeNorthEastCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 16: // pointer.CursorSouthWestResize
|
||||
trySetPrivateCursor(@selector(_windowResizeSouthWestCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 17: // pointer.CursorSouthEastResize
|
||||
trySetPrivateCursor(@selector(_windowResizeSouthEastCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 18: // pointer.CursorNorthSouthResize
|
||||
[NSCursor.resizeUpDownCursor set];
|
||||
break;
|
||||
case 19: // pointer.CursorEastWestResize
|
||||
[NSCursor.resizeLeftRightCursor set];
|
||||
break;
|
||||
case 20: // pointer.CursorWestResize
|
||||
[NSCursor.resizeLeftCursor set];
|
||||
break;
|
||||
case 21: // pointer.CursorEastResize
|
||||
[NSCursor.resizeRightCursor set];
|
||||
break;
|
||||
case 22: // pointer.CursorNorthResize
|
||||
[NSCursor.resizeUpCursor set];
|
||||
break;
|
||||
case 23: // pointer.CursorSouthResize
|
||||
[NSCursor.resizeDownCursor set];
|
||||
break;
|
||||
case 24: // pointer.CursorNorthEastSouthWestResize
|
||||
trySetPrivateCursor(@selector(_windowResizeNorthEastSouthWestCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 25: // pointer.CursorNorthWestSouthEastResize
|
||||
trySetPrivateCursor(@selector(_windowResizeNorthWestSouthEastCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
default:
|
||||
[NSCursor.arrowCursor set];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height) {
|
||||
@autoreleasepool {
|
||||
NSRect rect = NSMakeRect(0, 0, width, height);
|
||||
NSUInteger styleMask = NSTitledWindowMask |
|
||||
NSResizableWindowMask |
|
||||
NSMiniaturizableWindowMask |
|
||||
NSClosableWindowMask;
|
||||
|
||||
NSWindow* window = [[NSWindow alloc] initWithContentRect:rect
|
||||
styleMask:styleMask
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:NO];
|
||||
[window setAcceptsMouseMovedEvents:YES];
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
[window setContentView:view];
|
||||
window.delegate = globalWindowDel;
|
||||
return (__bridge_retained CFTypeRef)window;
|
||||
}
|
||||
}
|
||||
|
||||
CFTypeRef gio_createView(int presentWithTrans) {
|
||||
@autoreleasepool {
|
||||
NSRect frame = NSMakeRect(0, 0, 0, 0);
|
||||
GioView* view = [[GioView alloc] initWithFrame:frame];
|
||||
view.presentWithTrans = presentWithTrans ? YES : NO;
|
||||
view.wantsLayer = YES;
|
||||
view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:view
|
||||
selector:@selector(applicationWillUnhide:)
|
||||
name:NSApplicationWillUnhideNotification
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:view
|
||||
selector:@selector(applicationDidHide:)
|
||||
name:NSApplicationDidHideNotification
|
||||
object:nil];
|
||||
return CFBridgingRetain(view);
|
||||
}
|
||||
}
|
||||
|
||||
void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
|
||||
@autoreleasepool {
|
||||
GioView *v = (__bridge GioView *)viewRef;
|
||||
v.handle = handle;
|
||||
}
|
||||
}
|
||||
|
||||
@implementation GioAppDelegate
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
}
|
||||
- (void)application:(NSApplication *)application openURLs:(NSArray<NSURL *> *)urls {
|
||||
for (NSURL *url in urls) {
|
||||
gio_onOpenURI((__bridge CFTypeRef)url.absoluteString);
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
void gio_main() {
|
||||
@autoreleasepool {
|
||||
[NSApplication sharedApplication];
|
||||
GioAppDelegate *del = [[GioAppDelegate alloc] init];
|
||||
[NSApp setDelegate:del];
|
||||
|
||||
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];
|
||||
|
||||
globalWindowDel = [[GioWindowDelegate alloc] init];
|
||||
|
||||
[NSApp run];
|
||||
}
|
||||
}
|
||||
|
||||
@interface AppListener : NSObject
|
||||
@end
|
||||
|
||||
static AppListener *appListener;
|
||||
|
||||
@implementation AppListener
|
||||
- (void)launchFinished:(NSNotification *)notification {
|
||||
appListener = nil;
|
||||
gio_onFinishLaunching();
|
||||
}
|
||||
@end
|
||||
|
||||
void gio_init() {
|
||||
@autoreleasepool {
|
||||
appListener = [[AppListener alloc] init];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:appListener
|
||||
selector:@selector(launchFinished:)
|
||||
name:NSApplicationDidFinishLaunchingNotification
|
||||
object:nil];
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build (linux && !android) || freebsd || openbsd
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/io/pointer"
|
||||
)
|
||||
|
||||
type X11ViewEvent struct {
|
||||
// Display is a pointer to the X11 Display created by XOpenDisplay.
|
||||
Display unsafe.Pointer
|
||||
// Window is the X11 window ID as returned by XCreateWindow.
|
||||
Window uintptr
|
||||
}
|
||||
|
||||
func (X11ViewEvent) implementsViewEvent() {}
|
||||
func (X11ViewEvent) ImplementsEvent() {}
|
||||
func (x X11ViewEvent) Valid() bool {
|
||||
return x != (X11ViewEvent{})
|
||||
}
|
||||
|
||||
type WaylandViewEvent struct {
|
||||
// Display is the *wl_display returned by wl_display_connect.
|
||||
Display unsafe.Pointer
|
||||
// Surface is the *wl_surface returned by wl_compositor_create_surface.
|
||||
Surface unsafe.Pointer
|
||||
}
|
||||
|
||||
func (WaylandViewEvent) implementsViewEvent() {}
|
||||
func (WaylandViewEvent) ImplementsEvent() {}
|
||||
func (w WaylandViewEvent) Valid() bool {
|
||||
return w != (WaylandViewEvent{})
|
||||
}
|
||||
|
||||
func osMain() {
|
||||
select {}
|
||||
}
|
||||
|
||||
type windowDriver func(*callbacks, []Option) error
|
||||
|
||||
// Instead of creating files with build tags for each combination of wayland +/- x11
|
||||
// let each driver initialize these variables with their own version of createWindow.
|
||||
var wlDriver, x11Driver windowDriver
|
||||
|
||||
func newWindow(window *callbacks, options []Option) {
|
||||
var errFirst error
|
||||
for _, d := range []windowDriver{wlDriver, x11Driver} {
|
||||
if d == nil {
|
||||
continue
|
||||
}
|
||||
err := d(window, options)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if errFirst == nil {
|
||||
errFirst = err
|
||||
}
|
||||
}
|
||||
if errFirst == nil {
|
||||
errFirst = errors.New("app: no window driver available")
|
||||
}
|
||||
window.ProcessEvent(DestroyEvent{Err: errFirst})
|
||||
}
|
||||
|
||||
// xCursor contains mapping from pointer.Cursor to XCursor.
|
||||
var xCursor = [...]string{
|
||||
pointer.CursorDefault: "left_ptr",
|
||||
pointer.CursorNone: "",
|
||||
pointer.CursorText: "xterm",
|
||||
pointer.CursorVerticalText: "vertical-text",
|
||||
pointer.CursorPointer: "hand2",
|
||||
pointer.CursorCrosshair: "crosshair",
|
||||
pointer.CursorAllScroll: "fleur",
|
||||
pointer.CursorColResize: "sb_h_double_arrow",
|
||||
pointer.CursorRowResize: "sb_v_double_arrow",
|
||||
pointer.CursorGrab: "hand1",
|
||||
pointer.CursorGrabbing: "move",
|
||||
pointer.CursorNotAllowed: "crossed_circle",
|
||||
pointer.CursorWait: "watch",
|
||||
pointer.CursorProgress: "left_ptr_watch",
|
||||
pointer.CursorNorthWestResize: "top_left_corner",
|
||||
pointer.CursorNorthEastResize: "top_right_corner",
|
||||
pointer.CursorSouthWestResize: "bottom_left_corner",
|
||||
pointer.CursorSouthEastResize: "bottom_right_corner",
|
||||
pointer.CursorNorthSouthResize: "sb_v_double_arrow",
|
||||
pointer.CursorEastWestResize: "sb_h_double_arrow",
|
||||
pointer.CursorWestResize: "left_side",
|
||||
pointer.CursorEastResize: "right_side",
|
||||
pointer.CursorNorthResize: "top_side",
|
||||
pointer.CursorSouthResize: "bottom_side",
|
||||
pointer.CursorNorthEastSouthWestResize: "fd_double_arrow",
|
||||
pointer.CursorNorthWestSouthEastResize: "bd_double_arrow",
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build ((linux && !android) || freebsd) && !nowayland
|
||||
// +build linux,!android freebsd
|
||||
// +build !nowayland
|
||||
|
||||
#include <wayland-client.h>
|
||||
#include "wayland_xdg_shell.h"
|
||||
#include "wayland_xdg_decoration.h"
|
||||
#include "wayland_text_input.h"
|
||||
#include "_cgo_export.h"
|
||||
|
||||
const struct wl_registry_listener gio_registry_listener = {
|
||||
// Cast away const parameter.
|
||||
.global = (void (*)(void *, struct wl_registry *, uint32_t, const char *, uint32_t))gio_onRegistryGlobal,
|
||||
.global_remove = gio_onRegistryGlobalRemove
|
||||
};
|
||||
|
||||
const struct wl_surface_listener gio_surface_listener = {
|
||||
.enter = gio_onSurfaceEnter,
|
||||
.leave = gio_onSurfaceLeave,
|
||||
};
|
||||
|
||||
const struct xdg_surface_listener gio_xdg_surface_listener = {
|
||||
.configure = gio_onXdgSurfaceConfigure,
|
||||
};
|
||||
|
||||
const struct xdg_toplevel_listener gio_xdg_toplevel_listener = {
|
||||
.configure = gio_onToplevelConfigure,
|
||||
.close = gio_onToplevelClose,
|
||||
};
|
||||
|
||||
const struct zxdg_toplevel_decoration_v1_listener gio_zxdg_toplevel_decoration_v1_listener = {
|
||||
.configure = gio_onToplevelDecorationConfigure,
|
||||
};
|
||||
|
||||
static void xdg_wm_base_handle_ping(void *data, struct xdg_wm_base *wm, uint32_t serial) {
|
||||
xdg_wm_base_pong(wm, serial);
|
||||
}
|
||||
|
||||
const struct xdg_wm_base_listener gio_xdg_wm_base_listener = {
|
||||
.ping = xdg_wm_base_handle_ping,
|
||||
};
|
||||
|
||||
const struct wl_callback_listener gio_callback_listener = {
|
||||
.done = gio_onFrameDone,
|
||||
};
|
||||
|
||||
const struct wl_output_listener gio_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,
|
||||
};
|
||||
|
||||
const struct wl_seat_listener gio_seat_listener = {
|
||||
.capabilities = gio_onSeatCapabilities,
|
||||
// Cast away const parameter.
|
||||
.name = (void (*)(void *, struct wl_seat *, const char *))gio_onSeatName,
|
||||
};
|
||||
|
||||
const struct wl_pointer_listener gio_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,
|
||||
};
|
||||
|
||||
const struct wl_touch_listener gio_touch_listener = {
|
||||
.down = gio_onTouchDown,
|
||||
.up = gio_onTouchUp,
|
||||
.motion = gio_onTouchMotion,
|
||||
.frame = gio_onTouchFrame,
|
||||
.cancel = gio_onTouchCancel,
|
||||
};
|
||||
|
||||
const struct wl_keyboard_listener gio_keyboard_listener = {
|
||||
.keymap = gio_onKeyboardKeymap,
|
||||
.enter = gio_onKeyboardEnter,
|
||||
.leave = gio_onKeyboardLeave,
|
||||
.key = gio_onKeyboardKey,
|
||||
.modifiers = gio_onKeyboardModifiers,
|
||||
.repeat_info = gio_onKeyboardRepeatInfo
|
||||
};
|
||||
|
||||
const struct zwp_text_input_v3_listener gio_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
|
||||
};
|
||||
|
||||
const struct wl_data_device_listener gio_data_device_listener = {
|
||||
.data_offer = gio_onDataDeviceOffer,
|
||||
.enter = gio_onDataDeviceEnter,
|
||||
.leave = gio_onDataDeviceLeave,
|
||||
.motion = gio_onDataDeviceMotion,
|
||||
.drop = gio_onDataDeviceDrop,
|
||||
.selection = gio_onDataDeviceSelection,
|
||||
};
|
||||
|
||||
const struct wl_data_offer_listener gio_data_offer_listener = {
|
||||
.offer = (void (*)(void *, struct wl_data_offer *, const char *))gio_onDataOfferOffer,
|
||||
.source_actions = gio_onDataOfferSourceActions,
|
||||
.action = gio_onDataOfferAction,
|
||||
};
|
||||
|
||||
const struct wl_data_source_listener gio_data_source_listener = {
|
||||
.target = (void (*)(void *, struct wl_data_source *, const char *))gio_onDataSourceTarget,
|
||||
.send = (void (*)(void *, struct wl_data_source *, const char *, int32_t))gio_onDataSourceSend,
|
||||
.cancelled = gio_onDataSourceCancelled,
|
||||
.dnd_drop_performed = gio_onDataSourceDNDDropPerformed,
|
||||
.dnd_finished = gio_onDataSourceDNDFinished,
|
||||
.action = gio_onDataSourceAction,
|
||||
};
|
||||
+1945
File diff suppressed because it is too large
Load Diff
+1303
File diff suppressed because it is too large
Load Diff
+928
@@ -0,0 +1,928 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build ((linux && !android) || freebsd || openbsd) && !nox11
|
||||
// +build linux,!android freebsd openbsd
|
||||
// +build !nox11
|
||||
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo freebsd openbsd CFLAGS: -I/usr/X11R6/include -I/usr/local/include
|
||||
#cgo freebsd openbsd LDFLAGS: -L/usr/X11R6/lib -L/usr/local/lib
|
||||
#cgo freebsd openbsd LDFLAGS: -lX11 -lxkbcommon -lxkbcommon-x11 -lX11-xcb -lXcursor -lXfixes
|
||||
#cgo linux pkg-config: x11 xkbcommon xkbcommon-x11 x11-xcb xcursor xfixes
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <locale.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xatom.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/Xresource.h>
|
||||
#include <X11/XKBlib.h>
|
||||
#include <X11/Xlib-xcb.h>
|
||||
#include <X11/extensions/Xfixes.h>
|
||||
#include <X11/Xcursor/Xcursor.h>
|
||||
#include <xkbcommon/xkbcommon-x11.h>
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/io/transfer"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
|
||||
syscall "golang.org/x/sys/unix"
|
||||
|
||||
"gioui.org/app/internal/xkb"
|
||||
)
|
||||
|
||||
const (
|
||||
_NET_WM_STATE_REMOVE = 0
|
||||
_NET_WM_STATE_ADD = 1
|
||||
)
|
||||
|
||||
type x11Window struct {
|
||||
w *callbacks
|
||||
x *C.Display
|
||||
xkb *xkb.Context
|
||||
xkbEventBase C.int
|
||||
xw C.Window
|
||||
|
||||
atoms struct {
|
||||
// "UTF8_STRING".
|
||||
utf8string C.Atom
|
||||
// "text/plain;charset=utf-8".
|
||||
plaintext C.Atom
|
||||
// "TARGETS"
|
||||
targets C.Atom
|
||||
// "CLIPBOARD".
|
||||
clipboard C.Atom
|
||||
// "PRIMARY".
|
||||
primary C.Atom
|
||||
// "CLIPBOARD_CONTENT", the clipboard destination property.
|
||||
clipboardContent C.Atom
|
||||
// "WM_DELETE_WINDOW"
|
||||
evDelWindow C.Atom
|
||||
// "ATOM"
|
||||
atom C.Atom
|
||||
// "GTK_TEXT_BUFFER_CONTENTS"
|
||||
gtk_text_buffer_contents C.Atom
|
||||
// "_NET_WM_NAME"
|
||||
wmName C.Atom
|
||||
// "_NET_WM_STATE"
|
||||
wmState C.Atom
|
||||
// "_NET_WM_STATE_FULLSCREEN"
|
||||
wmStateFullscreen C.Atom
|
||||
// "_NET_ACTIVE_WINDOW"
|
||||
wmActiveWindow C.Atom
|
||||
// _NET_WM_STATE_MAXIMIZED_HORZ
|
||||
wmStateMaximizedHorz C.Atom
|
||||
// _NET_WM_STATE_MAXIMIZED_VERT
|
||||
wmStateMaximizedVert C.Atom
|
||||
}
|
||||
metric unit.Metric
|
||||
notify struct {
|
||||
read, write int
|
||||
}
|
||||
|
||||
animating bool
|
||||
|
||||
pointerBtns pointer.Buttons
|
||||
|
||||
clipboard struct {
|
||||
content []byte
|
||||
}
|
||||
cursor pointer.Cursor
|
||||
config Config
|
||||
|
||||
wakeups chan struct{}
|
||||
handler x11EventHandler
|
||||
buf [100]byte
|
||||
}
|
||||
|
||||
var (
|
||||
newX11EGLContext func(w *x11Window) (context, error)
|
||||
newX11VulkanContext func(w *x11Window) (context, error)
|
||||
)
|
||||
|
||||
// X11 and Vulkan doesn't work reliably on NVIDIA systems.
|
||||
// See https://gioui.org/issue/347.
|
||||
const vulkanBuggy = true
|
||||
|
||||
func (w *x11Window) NewContext() (context, error) {
|
||||
var firstErr error
|
||||
if f := newX11VulkanContext; f != nil && !vulkanBuggy {
|
||||
c, err := f(w)
|
||||
if err == nil {
|
||||
return c, nil
|
||||
}
|
||||
firstErr = err
|
||||
}
|
||||
if f := newX11EGLContext; f != nil {
|
||||
c, err := f(w)
|
||||
if err == nil {
|
||||
return c, nil
|
||||
}
|
||||
firstErr = err
|
||||
}
|
||||
if firstErr != nil {
|
||||
return nil, firstErr
|
||||
}
|
||||
return nil, errors.New("x11: no available GPU backends")
|
||||
}
|
||||
|
||||
func (w *x11Window) SetAnimating(anim bool) {
|
||||
w.animating = anim
|
||||
}
|
||||
|
||||
func (w *x11Window) ReadClipboard() {
|
||||
C.XDeleteProperty(w.x, w.xw, w.atoms.clipboardContent)
|
||||
C.XConvertSelection(w.x, w.atoms.clipboard, w.atoms.utf8string, w.atoms.clipboardContent, w.xw, C.CurrentTime)
|
||||
}
|
||||
|
||||
func (w *x11Window) WriteClipboard(mime string, s []byte) {
|
||||
w.clipboard.content = s
|
||||
C.XSetSelectionOwner(w.x, w.atoms.clipboard, w.xw, C.CurrentTime)
|
||||
C.XSetSelectionOwner(w.x, w.atoms.primary, w.xw, C.CurrentTime)
|
||||
}
|
||||
|
||||
func (w *x11Window) Configure(options []Option) {
|
||||
var shints C.XSizeHints
|
||||
prev := w.config
|
||||
cnf := w.config
|
||||
cnf.apply(w.metric, options)
|
||||
// Decorations are never disabled.
|
||||
cnf.Decorated = true
|
||||
|
||||
switch cnf.Mode {
|
||||
case Fullscreen:
|
||||
switch prev.Mode {
|
||||
case Fullscreen:
|
||||
case Minimized:
|
||||
w.raise()
|
||||
fallthrough
|
||||
default:
|
||||
w.config.Mode = Fullscreen
|
||||
w.sendWMStateEvent(_NET_WM_STATE_ADD, w.atoms.wmStateFullscreen, 0)
|
||||
}
|
||||
case Minimized:
|
||||
switch prev.Mode {
|
||||
case Minimized, Fullscreen:
|
||||
default:
|
||||
w.config.Mode = Minimized
|
||||
screen := C.XDefaultScreen(w.x)
|
||||
C.XIconifyWindow(w.x, w.xw, screen)
|
||||
}
|
||||
case Maximized:
|
||||
switch prev.Mode {
|
||||
case Fullscreen:
|
||||
case Minimized:
|
||||
w.raise()
|
||||
fallthrough
|
||||
default:
|
||||
w.config.Mode = Maximized
|
||||
w.sendWMStateEvent(_NET_WM_STATE_ADD, w.atoms.wmStateMaximizedHorz, w.atoms.wmStateMaximizedVert)
|
||||
w.setTitle(prev, cnf)
|
||||
}
|
||||
case Windowed:
|
||||
switch prev.Mode {
|
||||
case Fullscreen:
|
||||
w.config.Mode = Windowed
|
||||
w.sendWMStateEvent(_NET_WM_STATE_REMOVE, w.atoms.wmStateFullscreen, 0)
|
||||
C.XResizeWindow(w.x, w.xw, C.uint(cnf.Size.X), C.uint(cnf.Size.Y))
|
||||
case Minimized:
|
||||
w.config.Mode = Windowed
|
||||
w.raise()
|
||||
case Maximized:
|
||||
w.config.Mode = Windowed
|
||||
w.sendWMStateEvent(_NET_WM_STATE_REMOVE, w.atoms.wmStateMaximizedHorz, w.atoms.wmStateMaximizedVert)
|
||||
}
|
||||
w.setTitle(prev, cnf)
|
||||
if prev.Size != cnf.Size {
|
||||
w.config.Size = cnf.Size
|
||||
C.XResizeWindow(w.x, w.xw, C.uint(cnf.Size.X), C.uint(cnf.Size.Y))
|
||||
}
|
||||
if prev.MinSize != cnf.MinSize {
|
||||
w.config.MinSize = cnf.MinSize
|
||||
shints.min_width = C.int(cnf.MinSize.X)
|
||||
shints.min_height = C.int(cnf.MinSize.Y)
|
||||
shints.flags = C.PMinSize
|
||||
}
|
||||
if prev.MaxSize != cnf.MaxSize {
|
||||
w.config.MaxSize = cnf.MaxSize
|
||||
shints.max_width = C.int(cnf.MaxSize.X)
|
||||
shints.max_height = C.int(cnf.MaxSize.Y)
|
||||
shints.flags = shints.flags | C.PMaxSize
|
||||
}
|
||||
if shints.flags != 0 {
|
||||
C.XSetWMNormalHints(w.x, w.xw, &shints)
|
||||
}
|
||||
}
|
||||
if cnf.Decorated != prev.Decorated {
|
||||
w.config.Decorated = cnf.Decorated
|
||||
}
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
func (w *x11Window) setTitle(prev, cnf Config) {
|
||||
if prev.Title != cnf.Title {
|
||||
title := cnf.Title
|
||||
ctitle := C.CString(title)
|
||||
defer C.free(unsafe.Pointer(ctitle))
|
||||
C.XStoreName(w.x, w.xw, ctitle)
|
||||
// set _NET_WM_NAME as well for UTF-8 support in window title.
|
||||
C.XSetTextProperty(w.x, w.xw,
|
||||
&C.XTextProperty{
|
||||
value: (*C.uchar)(unsafe.Pointer(ctitle)),
|
||||
encoding: w.atoms.utf8string,
|
||||
format: 8,
|
||||
nitems: C.ulong(len(title)),
|
||||
},
|
||||
w.atoms.wmName)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *x11Window) Perform(acts system.Action) {
|
||||
walkActions(acts, func(a system.Action) {
|
||||
switch a {
|
||||
case system.ActionCenter:
|
||||
w.center()
|
||||
case system.ActionRaise:
|
||||
w.raise()
|
||||
}
|
||||
})
|
||||
if acts&system.ActionClose != 0 {
|
||||
w.close()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *x11Window) center() {
|
||||
screen := C.XDefaultScreen(w.x)
|
||||
width := C.XDisplayWidth(w.x, screen)
|
||||
height := C.XDisplayHeight(w.x, screen)
|
||||
|
||||
var attrs C.XWindowAttributes
|
||||
C.XGetWindowAttributes(w.x, w.xw, &attrs)
|
||||
width -= attrs.border_width
|
||||
height -= attrs.border_width
|
||||
|
||||
sz := w.config.Size
|
||||
x := (int(width) - sz.X) / 2
|
||||
y := (int(height) - sz.Y) / 2
|
||||
|
||||
C.XMoveResizeWindow(w.x, w.xw, C.int(x), C.int(y), C.uint(sz.X), C.uint(sz.Y))
|
||||
}
|
||||
|
||||
func (w *x11Window) raise() {
|
||||
var xev C.XEvent
|
||||
ev := (*C.XClientMessageEvent)(unsafe.Pointer(&xev))
|
||||
*ev = C.XClientMessageEvent{
|
||||
_type: C.ClientMessage,
|
||||
display: w.x,
|
||||
window: w.xw,
|
||||
message_type: w.atoms.wmActiveWindow,
|
||||
format: 32,
|
||||
}
|
||||
C.XSendEvent(
|
||||
w.x,
|
||||
C.XDefaultRootWindow(w.x), // MUST be the root window
|
||||
C.False,
|
||||
C.SubstructureNotifyMask|C.SubstructureRedirectMask,
|
||||
&xev,
|
||||
)
|
||||
C.XMapRaised(w.display(), w.xw)
|
||||
}
|
||||
|
||||
func (w *x11Window) SetCursor(cursor pointer.Cursor) {
|
||||
if cursor == pointer.CursorNone {
|
||||
w.cursor = cursor
|
||||
C.XFixesHideCursor(w.x, w.xw)
|
||||
return
|
||||
}
|
||||
|
||||
xcursor := xCursor[cursor]
|
||||
cname := C.CString(xcursor)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
c := C.XcursorLibraryLoadCursor(w.x, cname)
|
||||
if c == 0 {
|
||||
cursor = pointer.CursorDefault
|
||||
}
|
||||
w.cursor = cursor
|
||||
// If c if null (i.e. cursor was not found),
|
||||
// XDefineCursor will use the default cursor.
|
||||
C.XDefineCursor(w.x, w.xw, c)
|
||||
}
|
||||
|
||||
func (w *x11Window) ShowTextInput(show bool) {}
|
||||
|
||||
func (w *x11Window) SetInputHint(_ key.InputHint) {}
|
||||
|
||||
func (w *x11Window) EditorStateChanged(old, new editorState) {}
|
||||
|
||||
// close the window.
|
||||
func (w *x11Window) close() {
|
||||
var xev C.XEvent
|
||||
ev := (*C.XClientMessageEvent)(unsafe.Pointer(&xev))
|
||||
*ev = C.XClientMessageEvent{
|
||||
_type: C.ClientMessage,
|
||||
display: w.x,
|
||||
window: w.xw,
|
||||
message_type: w.atom("WM_PROTOCOLS", true),
|
||||
format: 32,
|
||||
}
|
||||
arr := (*[5]C.long)(unsafe.Pointer(&ev.data))
|
||||
arr[0] = C.long(w.atoms.evDelWindow)
|
||||
arr[1] = C.CurrentTime
|
||||
C.XSendEvent(w.x, w.xw, C.False, C.NoEventMask, &xev)
|
||||
}
|
||||
|
||||
// action is one of _NET_WM_STATE_REMOVE, _NET_WM_STATE_ADD.
|
||||
func (w *x11Window) sendWMStateEvent(action C.long, atom1, atom2 C.ulong) {
|
||||
var xev C.XEvent
|
||||
ev := (*C.XClientMessageEvent)(unsafe.Pointer(&xev))
|
||||
*ev = C.XClientMessageEvent{
|
||||
_type: C.ClientMessage,
|
||||
display: w.x,
|
||||
window: w.xw,
|
||||
message_type: w.atoms.wmState,
|
||||
format: 32,
|
||||
}
|
||||
data := (*[5]C.long)(unsafe.Pointer(&ev.data))
|
||||
data[0] = C.long(action)
|
||||
data[1] = C.long(atom1)
|
||||
data[2] = C.long(atom2)
|
||||
data[3] = 1 // application
|
||||
|
||||
C.XSendEvent(
|
||||
w.x,
|
||||
C.XDefaultRootWindow(w.x), // MUST be the root window
|
||||
C.False,
|
||||
C.SubstructureNotifyMask|C.SubstructureRedirectMask,
|
||||
&xev,
|
||||
)
|
||||
}
|
||||
|
||||
var x11OneByte = make([]byte, 1)
|
||||
|
||||
func (w *x11Window) ProcessEvent(e event.Event) {
|
||||
w.w.ProcessEvent(e)
|
||||
}
|
||||
|
||||
func (w *x11Window) shutdown(err error) {
|
||||
w.ProcessEvent(X11ViewEvent{})
|
||||
w.ProcessEvent(DestroyEvent{Err: err})
|
||||
w.destroy()
|
||||
}
|
||||
|
||||
func (w *x11Window) Event() event.Event {
|
||||
for {
|
||||
evt, ok := w.w.nextEvent()
|
||||
if !ok {
|
||||
w.dispatch()
|
||||
continue
|
||||
}
|
||||
return evt
|
||||
}
|
||||
}
|
||||
|
||||
func (w *x11Window) Run(f func()) {
|
||||
f()
|
||||
}
|
||||
|
||||
func (w *x11Window) Frame(frame *op.Ops) {
|
||||
w.w.ProcessFrame(frame, nil)
|
||||
}
|
||||
|
||||
func (w *x11Window) Invalidate() {
|
||||
select {
|
||||
case w.wakeups <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
if _, err := syscall.Write(w.notify.write, x11OneByte); err != nil && err != syscall.EAGAIN {
|
||||
panic(fmt.Errorf("failed to write to pipe: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
func (w *x11Window) display() *C.Display {
|
||||
return w.x
|
||||
}
|
||||
|
||||
func (w *x11Window) window() (C.Window, int, int) {
|
||||
return w.xw, w.config.Size.X, w.config.Size.Y
|
||||
}
|
||||
|
||||
func (w *x11Window) dispatch() {
|
||||
if w.x == nil {
|
||||
// Only Invalidate can wake us up.
|
||||
<-w.wakeups
|
||||
w.w.Invalidate()
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case <-w.wakeups:
|
||||
w.w.Invalidate()
|
||||
default:
|
||||
}
|
||||
|
||||
xfd := C.XConnectionNumber(w.x)
|
||||
|
||||
// Poll for events and notifications.
|
||||
pollfds := []syscall.PollFd{
|
||||
{Fd: int32(xfd), Events: syscall.POLLIN | syscall.POLLERR},
|
||||
{Fd: int32(w.notify.read), Events: syscall.POLLIN | syscall.POLLERR},
|
||||
}
|
||||
xEvents := &pollfds[0].Revents
|
||||
// Plenty of room for a backlog of notifications.
|
||||
|
||||
var syn, anim bool
|
||||
// Check for pending draw events before checking animation or blocking.
|
||||
// This fixes an issue on Xephyr where on startup XPending() > 0 but
|
||||
// poll will still block. This also prevents no-op calls to poll.
|
||||
syn = w.handler.handleEvents()
|
||||
if w.x == nil {
|
||||
// handleEvents received a close request and destroyed the window.
|
||||
return
|
||||
}
|
||||
if !syn {
|
||||
anim = w.animating
|
||||
if !anim {
|
||||
// Clear poll events.
|
||||
*xEvents = 0
|
||||
// Wait for X event or gio notification.
|
||||
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
||||
panic(fmt.Errorf("x11 loop: poll failed: %w", err))
|
||||
}
|
||||
switch {
|
||||
case *xEvents&syscall.POLLIN != 0:
|
||||
syn = w.handler.handleEvents()
|
||||
if w.x == nil {
|
||||
return
|
||||
}
|
||||
case *xEvents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
||||
}
|
||||
}
|
||||
}
|
||||
// Clear notifications.
|
||||
for {
|
||||
_, err := syscall.Read(w.notify.read, w.buf[:])
|
||||
if err == syscall.EAGAIN {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("x11 loop: read from notify pipe failed: %w", err))
|
||||
}
|
||||
}
|
||||
if (anim || syn) && w.config.Size.X != 0 && w.config.Size.Y != 0 {
|
||||
w.ProcessEvent(frameEvent{
|
||||
FrameEvent: FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: w.config.Size,
|
||||
Metric: w.metric,
|
||||
},
|
||||
Sync: syn,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (w *x11Window) destroy() {
|
||||
if w.notify.write != 0 {
|
||||
syscall.Close(w.notify.write)
|
||||
w.notify.write = 0
|
||||
}
|
||||
if w.notify.read != 0 {
|
||||
syscall.Close(w.notify.read)
|
||||
w.notify.read = 0
|
||||
}
|
||||
if w.xkb != nil {
|
||||
w.xkb.Destroy()
|
||||
w.xkb = nil
|
||||
}
|
||||
C.XDestroyWindow(w.x, w.xw)
|
||||
C.XCloseDisplay(w.x)
|
||||
w.x = nil
|
||||
}
|
||||
|
||||
// atom is a wrapper around XInternAtom. Callers should cache the result
|
||||
// in order to limit round-trips to the X server.
|
||||
func (w *x11Window) atom(name string, onlyIfExists bool) C.Atom {
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
flag := C.Bool(C.False)
|
||||
if onlyIfExists {
|
||||
flag = C.True
|
||||
}
|
||||
return C.XInternAtom(w.x, cname, flag)
|
||||
}
|
||||
|
||||
// x11EventHandler wraps static variables for the main event loop.
|
||||
// Its sole purpose is to prevent heap allocation and reduce clutter
|
||||
// in x11window.loop.
|
||||
type x11EventHandler struct {
|
||||
w *x11Window
|
||||
text []byte
|
||||
xev *C.XEvent
|
||||
}
|
||||
|
||||
// handleEvents returns true if the window needs to be redrawn.
|
||||
func (h *x11EventHandler) handleEvents() bool {
|
||||
w := h.w
|
||||
xev := h.xev
|
||||
redraw := false
|
||||
for C.XPending(w.x) != 0 {
|
||||
C.XNextEvent(w.x, xev)
|
||||
if C.XFilterEvent(xev, C.None) == C.True {
|
||||
continue
|
||||
}
|
||||
switch _type := (*C.XAnyEvent)(unsafe.Pointer(xev))._type; _type {
|
||||
case h.w.xkbEventBase:
|
||||
xkbEvent := (*C.XkbAnyEvent)(unsafe.Pointer(xev))
|
||||
switch xkbEvent.xkb_type {
|
||||
case C.XkbNewKeyboardNotify, C.XkbMapNotify:
|
||||
if err := h.w.updateXkbKeymap(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case C.XkbStateNotify:
|
||||
state := (*C.XkbStateNotifyEvent)(unsafe.Pointer(xev))
|
||||
h.w.xkb.UpdateMask(uint32(state.base_mods), uint32(state.latched_mods), uint32(state.locked_mods),
|
||||
uint32(state.base_group), uint32(state.latched_group), uint32(state.locked_group))
|
||||
}
|
||||
case C.KeyPress, C.KeyRelease:
|
||||
ks := key.Press
|
||||
if _type == C.KeyRelease {
|
||||
ks = key.Release
|
||||
}
|
||||
kevt := (*C.XKeyPressedEvent)(unsafe.Pointer(xev))
|
||||
for _, e := range h.w.xkb.DispatchKey(uint32(kevt.keycode), ks) {
|
||||
if ee, ok := e.(key.EditEvent); ok {
|
||||
// There's no support for IME yet.
|
||||
w.w.EditorInsert(ee.Text)
|
||||
} else {
|
||||
w.ProcessEvent(e)
|
||||
}
|
||||
}
|
||||
case C.ButtonPress, C.ButtonRelease:
|
||||
bevt := (*C.XButtonEvent)(unsafe.Pointer(xev))
|
||||
ev := pointer.Event{
|
||||
Kind: pointer.Press,
|
||||
Source: pointer.Mouse,
|
||||
Position: f32.Point{
|
||||
X: float32(bevt.x),
|
||||
Y: float32(bevt.y),
|
||||
},
|
||||
Time: time.Duration(bevt.time) * time.Millisecond,
|
||||
Modifiers: w.xkb.Modifiers(),
|
||||
}
|
||||
if bevt._type == C.ButtonRelease {
|
||||
ev.Kind = pointer.Release
|
||||
}
|
||||
var btn pointer.Buttons
|
||||
const scrollScale = 10
|
||||
switch bevt.button {
|
||||
case C.Button1:
|
||||
btn = pointer.ButtonPrimary
|
||||
case C.Button2:
|
||||
btn = pointer.ButtonTertiary
|
||||
case C.Button3:
|
||||
btn = pointer.ButtonSecondary
|
||||
case C.Button4:
|
||||
ev.Kind = pointer.Scroll
|
||||
// scroll up or left (if shift is pressed).
|
||||
if ev.Modifiers == key.ModShift {
|
||||
ev.Scroll.X = -scrollScale
|
||||
} else {
|
||||
ev.Scroll.Y = -scrollScale
|
||||
}
|
||||
case C.Button5:
|
||||
// scroll down or right (if shift is pressed).
|
||||
ev.Kind = pointer.Scroll
|
||||
if ev.Modifiers == key.ModShift {
|
||||
ev.Scroll.X = +scrollScale
|
||||
} else {
|
||||
ev.Scroll.Y = +scrollScale
|
||||
}
|
||||
case 6:
|
||||
// http://xahlee.info/linux/linux_x11_mouse_button_number.html
|
||||
// scroll left.
|
||||
ev.Kind = pointer.Scroll
|
||||
ev.Scroll.X = -scrollScale * 2
|
||||
case 7:
|
||||
// scroll right
|
||||
ev.Kind = pointer.Scroll
|
||||
ev.Scroll.X = +scrollScale * 2
|
||||
default:
|
||||
continue
|
||||
}
|
||||
switch _type {
|
||||
case C.ButtonPress:
|
||||
w.pointerBtns |= btn
|
||||
case C.ButtonRelease:
|
||||
w.pointerBtns &^= btn
|
||||
}
|
||||
ev.Buttons = w.pointerBtns
|
||||
w.ProcessEvent(ev)
|
||||
case C.MotionNotify:
|
||||
mevt := (*C.XMotionEvent)(unsafe.Pointer(xev))
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Move,
|
||||
Source: pointer.Mouse,
|
||||
Buttons: w.pointerBtns,
|
||||
Position: f32.Point{
|
||||
X: float32(mevt.x),
|
||||
Y: float32(mevt.y),
|
||||
},
|
||||
Time: time.Duration(mevt.time) * time.Millisecond,
|
||||
Modifiers: w.xkb.Modifiers(),
|
||||
})
|
||||
case C.Expose: // update
|
||||
// redraw only on the last expose event
|
||||
redraw = (*C.XExposeEvent)(unsafe.Pointer(xev)).count == 0
|
||||
case C.FocusIn:
|
||||
w.config.Focused = true
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
case C.FocusOut:
|
||||
w.config.Focused = false
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
case C.ConfigureNotify: // window configuration change
|
||||
cevt := (*C.XConfigureEvent)(unsafe.Pointer(xev))
|
||||
if sz := image.Pt(int(cevt.width), int(cevt.height)); sz != w.config.Size {
|
||||
w.config.Size = sz
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
// redraw will be done by a later expose event
|
||||
case C.SelectionNotify:
|
||||
cevt := (*C.XSelectionEvent)(unsafe.Pointer(xev))
|
||||
prop := w.atoms.clipboardContent
|
||||
if cevt.property != prop {
|
||||
break
|
||||
}
|
||||
if cevt.selection != w.atoms.clipboard {
|
||||
break
|
||||
}
|
||||
var text C.XTextProperty
|
||||
if st := C.XGetTextProperty(w.x, w.xw, &text, prop); st == 0 {
|
||||
// Failed; ignore.
|
||||
break
|
||||
}
|
||||
if text.format != 8 || text.encoding != w.atoms.utf8string {
|
||||
// Ignore non-utf-8 encoded strings.
|
||||
break
|
||||
}
|
||||
str := C.GoStringN((*C.char)(unsafe.Pointer(text.value)), C.int(text.nitems))
|
||||
w.ProcessEvent(transfer.DataEvent{
|
||||
Type: "application/text",
|
||||
Open: func() io.ReadCloser {
|
||||
return io.NopCloser(strings.NewReader(str))
|
||||
},
|
||||
})
|
||||
case C.SelectionRequest:
|
||||
cevt := (*C.XSelectionRequestEvent)(unsafe.Pointer(xev))
|
||||
if (cevt.selection != w.atoms.clipboard && cevt.selection != w.atoms.primary) || cevt.property == C.None {
|
||||
// Unsupported clipboard or obsolete requestor.
|
||||
break
|
||||
}
|
||||
notify := func() {
|
||||
var xev C.XEvent
|
||||
ev := (*C.XSelectionEvent)(unsafe.Pointer(&xev))
|
||||
*ev = C.XSelectionEvent{
|
||||
_type: C.SelectionNotify,
|
||||
display: cevt.display,
|
||||
requestor: cevt.requestor,
|
||||
selection: cevt.selection,
|
||||
target: cevt.target,
|
||||
property: cevt.property,
|
||||
time: cevt.time,
|
||||
}
|
||||
C.XSendEvent(w.x, cevt.requestor, 0, 0, &xev)
|
||||
}
|
||||
switch cevt.target {
|
||||
case w.atoms.targets:
|
||||
// The requestor wants the supported clipboard
|
||||
// formats. First write the targets...
|
||||
formats := [...]C.long{
|
||||
C.long(w.atoms.targets),
|
||||
C.long(w.atoms.utf8string),
|
||||
C.long(w.atoms.plaintext),
|
||||
// GTK clients need this.
|
||||
C.long(w.atoms.gtk_text_buffer_contents),
|
||||
}
|
||||
C.XChangeProperty(w.x, cevt.requestor, cevt.property, w.atoms.atom,
|
||||
32 /* bitwidth of formats */, C.PropModeReplace,
|
||||
(*C.uchar)(unsafe.Pointer(&formats)), C.int(len(formats)),
|
||||
)
|
||||
// ...then notify the requestor.
|
||||
notify()
|
||||
case w.atoms.plaintext, w.atoms.utf8string, w.atoms.gtk_text_buffer_contents:
|
||||
content := w.clipboard.content
|
||||
var ptr *C.uchar
|
||||
if len(content) > 0 {
|
||||
ptr = (*C.uchar)(unsafe.Pointer(&content[0]))
|
||||
}
|
||||
C.XChangeProperty(w.x, cevt.requestor, cevt.property, cevt.target,
|
||||
8 /* bitwidth */, C.PropModeReplace,
|
||||
ptr, C.int(len(content)),
|
||||
)
|
||||
notify()
|
||||
}
|
||||
case C.ClientMessage: // extensions
|
||||
cevt := (*C.XClientMessageEvent)(unsafe.Pointer(xev))
|
||||
switch *(*C.long)(unsafe.Pointer(&cevt.data)) {
|
||||
case C.long(w.atoms.evDelWindow):
|
||||
w.shutdown(nil)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return redraw
|
||||
}
|
||||
|
||||
var x11Threads sync.Once
|
||||
|
||||
func init() {
|
||||
x11Driver = newX11Window
|
||||
}
|
||||
|
||||
func newX11Window(gioWin *callbacks, options []Option) error {
|
||||
var err error
|
||||
|
||||
pipe := make([]int, 2)
|
||||
if err := syscall.Pipe2(pipe, syscall.O_NONBLOCK|syscall.O_CLOEXEC); err != nil {
|
||||
return fmt.Errorf("NewX11Window: failed to create pipe: %w", err)
|
||||
}
|
||||
|
||||
x11Threads.Do(func() {
|
||||
if C.XInitThreads() == 0 {
|
||||
err = errors.New("x11: threads init failed")
|
||||
}
|
||||
C.XrmInitialize()
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dpy := C.XOpenDisplay(nil)
|
||||
if dpy == nil {
|
||||
return errors.New("x11: cannot connect to the X server")
|
||||
}
|
||||
var major, minor C.int = C.XkbMajorVersion, C.XkbMinorVersion
|
||||
var xkbEventBase C.int
|
||||
if C.XkbQueryExtension(dpy, nil, &xkbEventBase, nil, &major, &minor) != C.True {
|
||||
C.XCloseDisplay(dpy)
|
||||
return errors.New("x11: XkbQueryExtension failed")
|
||||
}
|
||||
const bits = C.uint(C.XkbNewKeyboardNotifyMask | C.XkbMapNotifyMask | C.XkbStateNotifyMask)
|
||||
if C.XkbSelectEvents(dpy, C.XkbUseCoreKbd, bits, bits) != C.True {
|
||||
C.XCloseDisplay(dpy)
|
||||
return errors.New("x11: XkbSelectEvents failed")
|
||||
}
|
||||
xkb, err := xkb.New()
|
||||
if err != nil {
|
||||
C.XCloseDisplay(dpy)
|
||||
return fmt.Errorf("x11: %v", err)
|
||||
}
|
||||
|
||||
ppsp := x11DetectUIScale(dpy)
|
||||
cfg := unit.Metric{PxPerDp: ppsp, PxPerSp: ppsp}
|
||||
// Only use cnf for getting the window size.
|
||||
var cnf Config
|
||||
cnf.apply(cfg, options)
|
||||
|
||||
swa := C.XSetWindowAttributes{
|
||||
event_mask: C.ExposureMask | C.FocusChangeMask | // update
|
||||
C.KeyPressMask | C.KeyReleaseMask | // keyboard
|
||||
C.ButtonPressMask | C.ButtonReleaseMask | // mouse clicks
|
||||
C.PointerMotionMask | // mouse movement
|
||||
C.StructureNotifyMask, // resize
|
||||
background_pixmap: C.None,
|
||||
override_redirect: C.False,
|
||||
}
|
||||
win := C.XCreateWindow(dpy, C.XDefaultRootWindow(dpy),
|
||||
0, 0, C.uint(cnf.Size.X), C.uint(cnf.Size.Y),
|
||||
0, C.CopyFromParent, C.InputOutput, nil,
|
||||
C.CWEventMask|C.CWBackPixmap|C.CWOverrideRedirect, &swa)
|
||||
|
||||
w := &x11Window{
|
||||
w: gioWin, x: dpy, xw: win,
|
||||
metric: cfg,
|
||||
xkb: xkb,
|
||||
xkbEventBase: xkbEventBase,
|
||||
wakeups: make(chan struct{}, 1),
|
||||
config: Config{Size: cnf.Size},
|
||||
}
|
||||
w.handler = x11EventHandler{w: w, xev: new(C.XEvent), text: make([]byte, 4)}
|
||||
w.notify.read = pipe[0]
|
||||
w.notify.write = pipe[1]
|
||||
w.w.SetDriver(w)
|
||||
|
||||
if err := w.updateXkbKeymap(); err != nil {
|
||||
w.destroy()
|
||||
return err
|
||||
}
|
||||
|
||||
var hints C.XWMHints
|
||||
hints.input = C.True
|
||||
hints.flags = C.InputHint
|
||||
C.XSetWMHints(dpy, win, &hints)
|
||||
|
||||
name := C.CString(ID)
|
||||
defer C.free(unsafe.Pointer(name))
|
||||
wmhints := C.XClassHint{name, name}
|
||||
C.XSetClassHint(dpy, win, &wmhints)
|
||||
|
||||
w.atoms.utf8string = w.atom("UTF8_STRING", false)
|
||||
w.atoms.plaintext = w.atom("text/plain;charset=utf-8", false)
|
||||
w.atoms.gtk_text_buffer_contents = w.atom("GTK_TEXT_BUFFER_CONTENTS", false)
|
||||
w.atoms.evDelWindow = w.atom("WM_DELETE_WINDOW", false)
|
||||
w.atoms.clipboard = w.atom("CLIPBOARD", false)
|
||||
w.atoms.primary = w.atom("PRIMARY", false)
|
||||
w.atoms.clipboardContent = w.atom("CLIPBOARD_CONTENT", false)
|
||||
w.atoms.atom = w.atom("ATOM", false)
|
||||
w.atoms.targets = w.atom("TARGETS", false)
|
||||
w.atoms.wmName = w.atom("_NET_WM_NAME", false)
|
||||
w.atoms.wmState = w.atom("_NET_WM_STATE", false)
|
||||
w.atoms.wmStateFullscreen = w.atom("_NET_WM_STATE_FULLSCREEN", false)
|
||||
w.atoms.wmActiveWindow = w.atom("_NET_ACTIVE_WINDOW", false)
|
||||
w.atoms.wmStateMaximizedHorz = w.atom("_NET_WM_STATE_MAXIMIZED_HORZ", false)
|
||||
w.atoms.wmStateMaximizedVert = w.atom("_NET_WM_STATE_MAXIMIZED_VERT", false)
|
||||
|
||||
// extensions
|
||||
C.XSetWMProtocols(dpy, win, &w.atoms.evDelWindow, 1)
|
||||
|
||||
// make the window visible on the screen
|
||||
C.XMapWindow(dpy, win)
|
||||
w.Configure(options)
|
||||
w.ProcessEvent(X11ViewEvent{Display: unsafe.Pointer(dpy), Window: uintptr(win)})
|
||||
return nil
|
||||
}
|
||||
|
||||
// detectUIScale reports the system UI scale, or 1.0 if it fails.
|
||||
func x11DetectUIScale(dpy *C.Display) float32 {
|
||||
// default fixed DPI value used in most desktop UI toolkits
|
||||
const defaultDesktopDPI = 96
|
||||
var scale float32 = 1.0
|
||||
|
||||
// Get actual DPI from X resource Xft.dpi (set by GTK and Qt).
|
||||
// This value is entirely based on user preferences and conflates both
|
||||
// screen (UI) scaling and font scale.
|
||||
rms := C.XResourceManagerString(dpy)
|
||||
if rms != nil {
|
||||
db := C.XrmGetStringDatabase(rms)
|
||||
if db != nil {
|
||||
var (
|
||||
t *C.char
|
||||
v C.XrmValue
|
||||
)
|
||||
if C.XrmGetResource(db, (*C.char)(unsafe.Pointer(&[]byte("Xft.dpi\x00")[0])),
|
||||
(*C.char)(unsafe.Pointer(&[]byte("Xft.Dpi\x00")[0])), &t, &v) != C.False {
|
||||
if t != nil && C.GoString(t) == "String" {
|
||||
f, err := strconv.ParseFloat(C.GoString(v.addr), 32)
|
||||
if err == nil {
|
||||
scale = float32(f) / defaultDesktopDPI
|
||||
}
|
||||
}
|
||||
}
|
||||
C.XrmDestroyDatabase(db)
|
||||
}
|
||||
}
|
||||
|
||||
return scale
|
||||
}
|
||||
|
||||
func (w *x11Window) updateXkbKeymap() error {
|
||||
w.xkb.DestroyKeymapState()
|
||||
ctx := (*C.struct_xkb_context)(unsafe.Pointer(w.xkb.Ctx))
|
||||
xcb := C.XGetXCBConnection(w.x)
|
||||
if xcb == nil {
|
||||
return errors.New("x11: XGetXCBConnection failed")
|
||||
}
|
||||
xkbDevID := C.xkb_x11_get_core_keyboard_device_id(xcb)
|
||||
if xkbDevID == -1 {
|
||||
return errors.New("x11: xkb_x11_get_core_keyboard_device_id failed")
|
||||
}
|
||||
keymap := C.xkb_x11_keymap_new_from_device(ctx, xcb, xkbDevID, C.XKB_KEYMAP_COMPILE_NO_FLAGS)
|
||||
if keymap == nil {
|
||||
return errors.New("x11: xkb_x11_keymap_new_from_device failed")
|
||||
}
|
||||
state := C.xkb_x11_state_new_from_device(keymap, xcb, xkbDevID)
|
||||
if state == nil {
|
||||
C.xkb_keymap_unref(keymap)
|
||||
return errors.New("x11: xkb_x11_keymap_new_from_device failed")
|
||||
}
|
||||
w.xkb.SetKeymap(unsafe.Pointer(keymap), unsafe.Pointer(state))
|
||||
return nil
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go: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()
|
||||
})
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func (DestroyEvent) ImplementsEvent() {}
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build (linux || freebsd) && !novulkan
|
||||
// +build linux freebsd
|
||||
// +build !novulkan
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/internal/vk"
|
||||
)
|
||||
|
||||
type vkContext struct {
|
||||
physDev vk.PhysicalDevice
|
||||
inst vk.Instance
|
||||
dev vk.Device
|
||||
queueFam int
|
||||
queue vk.Queue
|
||||
acquireSem vk.Semaphore
|
||||
presentSem vk.Semaphore
|
||||
fence vk.Fence
|
||||
|
||||
swchain vk.Swapchain
|
||||
imgs []vk.Image
|
||||
views []vk.ImageView
|
||||
fbos []vk.Framebuffer
|
||||
format vk.Format
|
||||
presentIdx int
|
||||
}
|
||||
|
||||
func newVulkanContext(inst vk.Instance, surf vk.Surface) (*vkContext, error) {
|
||||
physDev, qFam, err := vk.ChoosePhysicalDevice(inst, surf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dev, err := vk.CreateDeviceAndQueue(physDev, qFam, "VK_KHR_swapchain")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
acquireSem, err := vk.CreateSemaphore(dev)
|
||||
if err != nil {
|
||||
vk.DestroyDevice(dev)
|
||||
return nil, err
|
||||
}
|
||||
presentSem, err := vk.CreateSemaphore(dev)
|
||||
if err != nil {
|
||||
vk.DestroySemaphore(dev, acquireSem)
|
||||
vk.DestroyDevice(dev)
|
||||
return nil, err
|
||||
}
|
||||
fence, err := vk.CreateFence(dev, vk.FENCE_CREATE_SIGNALED_BIT)
|
||||
if err != nil {
|
||||
vk.DestroySemaphore(dev, presentSem)
|
||||
vk.DestroySemaphore(dev, acquireSem)
|
||||
vk.DestroyDevice(dev)
|
||||
return nil, err
|
||||
}
|
||||
c := &vkContext{
|
||||
physDev: physDev,
|
||||
inst: inst,
|
||||
dev: dev,
|
||||
queueFam: qFam,
|
||||
queue: vk.GetDeviceQueue(dev, qFam, 0),
|
||||
acquireSem: acquireSem,
|
||||
presentSem: presentSem,
|
||||
fence: fence,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *vkContext) RenderTarget() (gpu.RenderTarget, error) {
|
||||
vk.WaitForFences(c.dev, c.fence)
|
||||
vk.ResetFences(c.dev, c.fence)
|
||||
|
||||
imgIdx, err := vk.AcquireNextImage(c.dev, c.swchain, c.acquireSem, 0)
|
||||
if err := mapSurfaceErr(err); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.presentIdx = imgIdx
|
||||
return gpu.VulkanRenderTarget{
|
||||
WaitSem: uint64(c.acquireSem),
|
||||
SignalSem: uint64(c.presentSem),
|
||||
Fence: uint64(c.fence),
|
||||
Framebuffer: uint64(c.fbos[imgIdx]),
|
||||
Image: uint64(c.imgs[imgIdx]),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *vkContext) api() gpu.API {
|
||||
return gpu.Vulkan{
|
||||
PhysDevice: unsafe.Pointer(c.physDev),
|
||||
Device: unsafe.Pointer(c.dev),
|
||||
Format: int(c.format),
|
||||
QueueFamily: c.queueFam,
|
||||
QueueIndex: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func mapErr(err error) error {
|
||||
var vkErr vk.Error
|
||||
if errors.As(err, &vkErr) && vkErr == vk.ERROR_DEVICE_LOST {
|
||||
return gpu.ErrDeviceLost
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func mapSurfaceErr(err error) error {
|
||||
var vkErr vk.Error
|
||||
if !errors.As(err, &vkErr) {
|
||||
return err
|
||||
}
|
||||
switch {
|
||||
case vkErr == vk.SUBOPTIMAL_KHR:
|
||||
// Android reports VK_SUBOPTIMAL_KHR when presenting to a rotated
|
||||
// swapchain (preTransform != currentTransform). However, we don't
|
||||
// support transforming the output ourselves, so we'll live with it.
|
||||
return nil
|
||||
case vkErr == vk.ERROR_OUT_OF_DATE_KHR:
|
||||
return errOutOfDate
|
||||
case vkErr == vk.ERROR_SURFACE_LOST_KHR:
|
||||
// Treating a lost surface as a lost device isn't accurate, but
|
||||
// probably not worth optimizing.
|
||||
return gpu.ErrDeviceLost
|
||||
}
|
||||
return mapErr(err)
|
||||
}
|
||||
|
||||
func (c *vkContext) release() {
|
||||
vk.DeviceWaitIdle(c.dev)
|
||||
|
||||
c.destroySwapchain()
|
||||
vk.DestroyFence(c.dev, c.fence)
|
||||
vk.DestroySemaphore(c.dev, c.acquireSem)
|
||||
vk.DestroySemaphore(c.dev, c.presentSem)
|
||||
vk.DestroyDevice(c.dev)
|
||||
*c = vkContext{}
|
||||
}
|
||||
|
||||
func (c *vkContext) present() error {
|
||||
return mapSurfaceErr(vk.PresentQueue(c.queue, c.swchain, c.presentSem, c.presentIdx))
|
||||
}
|
||||
|
||||
func (c *vkContext) destroyImageViews() {
|
||||
for _, f := range c.fbos {
|
||||
vk.DestroyFramebuffer(c.dev, f)
|
||||
}
|
||||
c.fbos = nil
|
||||
for _, view := range c.views {
|
||||
vk.DestroyImageView(c.dev, view)
|
||||
}
|
||||
c.views = nil
|
||||
}
|
||||
|
||||
func (c *vkContext) destroySwapchain() {
|
||||
vk.DeviceWaitIdle(c.dev)
|
||||
|
||||
c.destroyImageViews()
|
||||
if c.swchain != 0 {
|
||||
vk.DestroySwapchain(c.dev, c.swchain)
|
||||
c.swchain = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (c *vkContext) refresh(surf vk.Surface, width, height int) error {
|
||||
vk.DeviceWaitIdle(c.dev)
|
||||
|
||||
c.destroyImageViews()
|
||||
// Check whether size is valid. That's needed on X11, where ConfigureNotify
|
||||
// is not always synchronized with the window extent.
|
||||
caps, err := vk.GetPhysicalDeviceSurfaceCapabilities(c.physDev, surf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
minExt, maxExt := vk.SurfaceCapabilitiesMinExtent(caps), vk.SurfaceCapabilitiesMaxExtent(caps)
|
||||
if width < minExt.X || maxExt.X < width || height < minExt.Y || maxExt.Y < height {
|
||||
return errOutOfDate
|
||||
}
|
||||
swchain, imgs, format, err := vk.CreateSwapchain(c.physDev, c.dev, surf, width, height, c.swchain)
|
||||
if c.swchain != 0 {
|
||||
vk.DestroySwapchain(c.dev, c.swchain)
|
||||
c.swchain = 0
|
||||
}
|
||||
if err := mapSurfaceErr(err); err != nil {
|
||||
return err
|
||||
}
|
||||
c.swchain = swchain
|
||||
c.imgs = imgs
|
||||
c.format = format
|
||||
pass, err := vk.CreateRenderPass(
|
||||
c.dev,
|
||||
format,
|
||||
vk.ATTACHMENT_LOAD_OP_CLEAR,
|
||||
vk.IMAGE_LAYOUT_UNDEFINED,
|
||||
vk.IMAGE_LAYOUT_PRESENT_SRC_KHR,
|
||||
nil,
|
||||
)
|
||||
if err := mapErr(err); err != nil {
|
||||
return err
|
||||
}
|
||||
defer vk.DestroyRenderPass(c.dev, pass)
|
||||
for _, img := range imgs {
|
||||
view, err := vk.CreateImageView(c.dev, img, format)
|
||||
if err := mapErr(err); err != nil {
|
||||
return err
|
||||
}
|
||||
c.views = append(c.views, view)
|
||||
fbo, err := vk.CreateFramebuffer(c.dev, pass, view, width, height)
|
||||
if err := mapErr(err); err != nil {
|
||||
return err
|
||||
}
|
||||
c.fbos = append(c.fbos, fbo)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build !novulkan
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/internal/vk"
|
||||
)
|
||||
|
||||
type wlVkContext struct {
|
||||
win *window
|
||||
inst vk.Instance
|
||||
surf vk.Surface
|
||||
ctx *vkContext
|
||||
}
|
||||
|
||||
func init() {
|
||||
newAndroidVulkanContext = func(w *window) (context, error) {
|
||||
inst, err := vk.CreateInstance("VK_KHR_surface", "VK_KHR_android_surface")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
window, _, _ := w.nativeWindow()
|
||||
surf, err := vk.CreateAndroidSurface(inst, unsafe.Pointer(window))
|
||||
if err != nil {
|
||||
vk.DestroyInstance(inst)
|
||||
return nil, err
|
||||
}
|
||||
ctx, err := newVulkanContext(inst, surf)
|
||||
if err != nil {
|
||||
vk.DestroySurface(inst, surf)
|
||||
vk.DestroyInstance(inst)
|
||||
return nil, err
|
||||
}
|
||||
c := &wlVkContext{
|
||||
win: w,
|
||||
inst: inst,
|
||||
surf: surf,
|
||||
ctx: ctx,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *wlVkContext) RenderTarget() (gpu.RenderTarget, error) {
|
||||
return c.ctx.RenderTarget()
|
||||
}
|
||||
|
||||
func (c *wlVkContext) API() gpu.API {
|
||||
return c.ctx.api()
|
||||
}
|
||||
|
||||
func (c *wlVkContext) Release() {
|
||||
c.ctx.release()
|
||||
if c.surf != 0 {
|
||||
vk.DestroySurface(c.inst, c.surf)
|
||||
}
|
||||
vk.DestroyInstance(c.inst)
|
||||
*c = wlVkContext{}
|
||||
}
|
||||
|
||||
func (c *wlVkContext) Present() error {
|
||||
return c.ctx.present()
|
||||
}
|
||||
|
||||
func (c *wlVkContext) Lock() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *wlVkContext) Unlock() {}
|
||||
|
||||
func (c *wlVkContext) Refresh() error {
|
||||
win, w, h := c.win.nativeWindow()
|
||||
if c.surf != 0 {
|
||||
c.ctx.destroySwapchain()
|
||||
vk.DestroySurface(c.inst, c.surf)
|
||||
c.surf = 0
|
||||
}
|
||||
surf, err := vk.CreateAndroidSurface(c.inst, unsafe.Pointer(win))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.surf = surf
|
||||
return c.ctx.refresh(c.surf, w, h)
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build ((linux && !android) || freebsd) && !nowayland && !novulkan
|
||||
// +build linux,!android freebsd
|
||||
// +build !nowayland
|
||||
// +build !novulkan
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/internal/vk"
|
||||
)
|
||||
|
||||
type wlVkContext struct {
|
||||
win *window
|
||||
inst vk.Instance
|
||||
surf vk.Surface
|
||||
ctx *vkContext
|
||||
}
|
||||
|
||||
func init() {
|
||||
newWaylandVulkanContext = func(w *window) (context, error) {
|
||||
inst, err := vk.CreateInstance("VK_KHR_surface", "VK_KHR_wayland_surface")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
disp := w.display()
|
||||
wlSurf, _, _ := w.surface()
|
||||
surf, err := vk.CreateWaylandSurface(inst, unsafe.Pointer(disp), unsafe.Pointer(wlSurf))
|
||||
if err != nil {
|
||||
vk.DestroyInstance(inst)
|
||||
return nil, err
|
||||
}
|
||||
ctx, err := newVulkanContext(inst, surf)
|
||||
if err != nil {
|
||||
vk.DestroySurface(inst, surf)
|
||||
vk.DestroyInstance(inst)
|
||||
return nil, err
|
||||
}
|
||||
c := &wlVkContext{
|
||||
win: w,
|
||||
inst: inst,
|
||||
surf: surf,
|
||||
ctx: ctx,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *wlVkContext) RenderTarget() (gpu.RenderTarget, error) {
|
||||
return c.ctx.RenderTarget()
|
||||
}
|
||||
|
||||
func (c *wlVkContext) API() gpu.API {
|
||||
return c.ctx.api()
|
||||
}
|
||||
|
||||
func (c *wlVkContext) Release() {
|
||||
c.ctx.release()
|
||||
vk.DestroySurface(c.inst, c.surf)
|
||||
vk.DestroyInstance(c.inst)
|
||||
*c = wlVkContext{}
|
||||
}
|
||||
|
||||
func (c *wlVkContext) Present() error {
|
||||
return c.ctx.present()
|
||||
}
|
||||
|
||||
func (c *wlVkContext) Lock() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *wlVkContext) Unlock() {}
|
||||
|
||||
func (c *wlVkContext) Refresh() error {
|
||||
_, w, h := c.win.surface()
|
||||
return c.ctx.refresh(c.surf, w, h)
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build ((linux && !android) || freebsd) && !nox11 && !novulkan
|
||||
// +build linux,!android freebsd
|
||||
// +build !nox11
|
||||
// +build !novulkan
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/internal/vk"
|
||||
)
|
||||
|
||||
type x11VkContext struct {
|
||||
win *x11Window
|
||||
inst vk.Instance
|
||||
surf vk.Surface
|
||||
ctx *vkContext
|
||||
}
|
||||
|
||||
func init() {
|
||||
newX11VulkanContext = func(w *x11Window) (context, error) {
|
||||
inst, err := vk.CreateInstance("VK_KHR_surface", "VK_KHR_xlib_surface")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
disp := w.display()
|
||||
window, _, _ := w.window()
|
||||
surf, err := vk.CreateXlibSurface(inst, unsafe.Pointer(disp), uintptr(window))
|
||||
if err != nil {
|
||||
vk.DestroyInstance(inst)
|
||||
return nil, err
|
||||
}
|
||||
ctx, err := newVulkanContext(inst, surf)
|
||||
if err != nil {
|
||||
vk.DestroySurface(inst, surf)
|
||||
vk.DestroyInstance(inst)
|
||||
return nil, err
|
||||
}
|
||||
c := &x11VkContext{
|
||||
win: w,
|
||||
inst: inst,
|
||||
surf: surf,
|
||||
ctx: ctx,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *x11VkContext) RenderTarget() (gpu.RenderTarget, error) {
|
||||
return c.ctx.RenderTarget()
|
||||
}
|
||||
|
||||
func (c *x11VkContext) API() gpu.API {
|
||||
return c.ctx.api()
|
||||
}
|
||||
|
||||
func (c *x11VkContext) Release() {
|
||||
c.ctx.release()
|
||||
vk.DestroySurface(c.inst, c.surf)
|
||||
vk.DestroyInstance(c.inst)
|
||||
*c = x11VkContext{}
|
||||
}
|
||||
|
||||
func (c *x11VkContext) Present() error {
|
||||
return c.ctx.present()
|
||||
}
|
||||
|
||||
func (c *x11VkContext) Lock() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *x11VkContext) Unlock() {}
|
||||
|
||||
func (c *x11VkContext) Refresh() error {
|
||||
_, w, h := c.win.window()
|
||||
return c.ctx.refresh(c.surf, w, h)
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
//go:build ((linux && !android) || freebsd) && !nowayland
|
||||
// +build linux,!android freebsd
|
||||
// +build !nowayland
|
||||
|
||||
/* Generated by wayland-scanner 1.19.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 *text_input_unstable_v3_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", "", text_input_unstable_v3_types + 0 },
|
||||
{ "enable", "", text_input_unstable_v3_types + 0 },
|
||||
{ "disable", "", text_input_unstable_v3_types + 0 },
|
||||
{ "set_surrounding_text", "sii", text_input_unstable_v3_types + 0 },
|
||||
{ "set_text_change_cause", "u", text_input_unstable_v3_types + 0 },
|
||||
{ "set_content_type", "uu", text_input_unstable_v3_types + 0 },
|
||||
{ "set_cursor_rectangle", "iiii", text_input_unstable_v3_types + 0 },
|
||||
{ "commit", "", text_input_unstable_v3_types + 0 },
|
||||
};
|
||||
|
||||
static const struct wl_message zwp_text_input_v3_events[] = {
|
||||
{ "enter", "o", text_input_unstable_v3_types + 4 },
|
||||
{ "leave", "o", text_input_unstable_v3_types + 5 },
|
||||
{ "preedit_string", "?sii", text_input_unstable_v3_types + 0 },
|
||||
{ "commit_string", "?s", text_input_unstable_v3_types + 0 },
|
||||
{ "delete_surrounding_text", "uu", text_input_unstable_v3_types + 0 },
|
||||
{ "done", "u", text_input_unstable_v3_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", "", text_input_unstable_v3_types + 0 },
|
||||
{ "get_text_input", "no", text_input_unstable_v3_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,
|
||||
};
|
||||
|
||||
+836
@@ -0,0 +1,836 @@
|
||||
/* Generated by wayland-scanner 1.19.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;
|
||||
|
||||
#ifndef ZWP_TEXT_INPUT_V3_INTERFACE
|
||||
#define ZWP_TEXT_INPUT_V3_INTERFACE
|
||||
/**
|
||||
* @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;
|
||||
#endif
|
||||
#ifndef ZWP_TEXT_INPUT_MANAGER_V3_INTERFACE
|
||||
#define ZWP_TEXT_INPUT_MANAGER_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;
|
||||
#endif
|
||||
|
||||
#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.
|
||||
*
|
||||
* If client has created multiple text input objects, compositor
|
||||
* must send this event to all of them.
|
||||
*
|
||||
* 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. After leave
|
||||
* event, compositor must ignore requests from any text input
|
||||
* instances until next enter event.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Clients must not enable more than one text input on the single seat
|
||||
* and should disable the current text input before enabling the new one.
|
||||
* At most one instance of text input may be in enabled state per instance,
|
||||
* Requests to enable the another text input when some text input is active
|
||||
* must be ignored by compositor.
|
||||
*
|
||||
* 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
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
//go:build ((linux && !android) || freebsd) && !nowayland
|
||||
// +build linux,!android freebsd
|
||||
// +build !nowayland
|
||||
|
||||
/* Generated by wayland-scanner 1.19.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 *xdg_decoration_unstable_v1_types[] = {
|
||||
NULL,
|
||||
&zxdg_toplevel_decoration_v1_interface,
|
||||
&xdg_toplevel_interface,
|
||||
};
|
||||
|
||||
static const struct wl_message zxdg_decoration_manager_v1_requests[] = {
|
||||
{ "destroy", "", xdg_decoration_unstable_v1_types + 0 },
|
||||
{ "get_toplevel_decoration", "no", xdg_decoration_unstable_v1_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", "", xdg_decoration_unstable_v1_types + 0 },
|
||||
{ "set_mode", "u", xdg_decoration_unstable_v1_types + 0 },
|
||||
{ "unset_mode", "", xdg_decoration_unstable_v1_types + 0 },
|
||||
};
|
||||
|
||||
static const struct wl_message zxdg_toplevel_decoration_v1_events[] = {
|
||||
{ "configure", "u", xdg_decoration_unstable_v1_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,
|
||||
};
|
||||
|
||||
+382
@@ -0,0 +1,382 @@
|
||||
/* Generated by wayland-scanner 1.19.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;
|
||||
|
||||
#ifndef ZXDG_DECORATION_MANAGER_V1_INTERFACE
|
||||
#define ZXDG_DECORATION_MANAGER_V1_INTERFACE
|
||||
/**
|
||||
* @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;
|
||||
#endif
|
||||
#ifndef ZXDG_TOPLEVEL_DECORATION_V1_INTERFACE
|
||||
#define ZXDG_TOPLEVEL_DECORATION_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;
|
||||
#endif
|
||||
|
||||
#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 an 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 an 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
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
//go:build ((linux && !android) || freebsd) && !nowayland
|
||||
// +build linux,!android freebsd
|
||||
// +build !nowayland
|
||||
|
||||
/* Generated by wayland-scanner 1.19.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 *xdg_shell_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,
|
||||
&xdg_positioner_interface,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_wm_base_requests[] = {
|
||||
{ "destroy", "", xdg_shell_types + 0 },
|
||||
{ "create_positioner", "n", xdg_shell_types + 4 },
|
||||
{ "get_xdg_surface", "no", xdg_shell_types + 5 },
|
||||
{ "pong", "u", xdg_shell_types + 0 },
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_wm_base_events[] = {
|
||||
{ "ping", "u", xdg_shell_types + 0 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface xdg_wm_base_interface = {
|
||||
"xdg_wm_base", 3,
|
||||
4, xdg_wm_base_requests,
|
||||
1, xdg_wm_base_events,
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_positioner_requests[] = {
|
||||
{ "destroy", "", xdg_shell_types + 0 },
|
||||
{ "set_size", "ii", xdg_shell_types + 0 },
|
||||
{ "set_anchor_rect", "iiii", xdg_shell_types + 0 },
|
||||
{ "set_anchor", "u", xdg_shell_types + 0 },
|
||||
{ "set_gravity", "u", xdg_shell_types + 0 },
|
||||
{ "set_constraint_adjustment", "u", xdg_shell_types + 0 },
|
||||
{ "set_offset", "ii", xdg_shell_types + 0 },
|
||||
{ "set_reactive", "3", xdg_shell_types + 0 },
|
||||
{ "set_parent_size", "3ii", xdg_shell_types + 0 },
|
||||
{ "set_parent_configure", "3u", xdg_shell_types + 0 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface xdg_positioner_interface = {
|
||||
"xdg_positioner", 3,
|
||||
10, xdg_positioner_requests,
|
||||
0, NULL,
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_surface_requests[] = {
|
||||
{ "destroy", "", xdg_shell_types + 0 },
|
||||
{ "get_toplevel", "n", xdg_shell_types + 7 },
|
||||
{ "get_popup", "n?oo", xdg_shell_types + 8 },
|
||||
{ "set_window_geometry", "iiii", xdg_shell_types + 0 },
|
||||
{ "ack_configure", "u", xdg_shell_types + 0 },
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_surface_events[] = {
|
||||
{ "configure", "u", xdg_shell_types + 0 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface xdg_surface_interface = {
|
||||
"xdg_surface", 3,
|
||||
5, xdg_surface_requests,
|
||||
1, xdg_surface_events,
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_toplevel_requests[] = {
|
||||
{ "destroy", "", xdg_shell_types + 0 },
|
||||
{ "set_parent", "?o", xdg_shell_types + 11 },
|
||||
{ "set_title", "s", xdg_shell_types + 0 },
|
||||
{ "set_app_id", "s", xdg_shell_types + 0 },
|
||||
{ "show_window_menu", "ouii", xdg_shell_types + 12 },
|
||||
{ "move", "ou", xdg_shell_types + 16 },
|
||||
{ "resize", "ouu", xdg_shell_types + 18 },
|
||||
{ "set_max_size", "ii", xdg_shell_types + 0 },
|
||||
{ "set_min_size", "ii", xdg_shell_types + 0 },
|
||||
{ "set_maximized", "", xdg_shell_types + 0 },
|
||||
{ "unset_maximized", "", xdg_shell_types + 0 },
|
||||
{ "set_fullscreen", "?o", xdg_shell_types + 21 },
|
||||
{ "unset_fullscreen", "", xdg_shell_types + 0 },
|
||||
{ "set_minimized", "", xdg_shell_types + 0 },
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_toplevel_events[] = {
|
||||
{ "configure", "iia", xdg_shell_types + 0 },
|
||||
{ "close", "", xdg_shell_types + 0 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface xdg_toplevel_interface = {
|
||||
"xdg_toplevel", 3,
|
||||
14, xdg_toplevel_requests,
|
||||
2, xdg_toplevel_events,
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_popup_requests[] = {
|
||||
{ "destroy", "", xdg_shell_types + 0 },
|
||||
{ "grab", "ou", xdg_shell_types + 22 },
|
||||
{ "reposition", "3ou", xdg_shell_types + 24 },
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_popup_events[] = {
|
||||
{ "configure", "iiii", xdg_shell_types + 0 },
|
||||
{ "popup_done", "", xdg_shell_types + 0 },
|
||||
{ "repositioned", "3u", xdg_shell_types + 0 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface xdg_popup_interface = {
|
||||
"xdg_popup", 3,
|
||||
3, xdg_popup_requests,
|
||||
3, xdg_popup_events,
|
||||
};
|
||||
|
||||
+2003
File diff suppressed because it is too large
Load Diff
+991
@@ -0,0 +1,991 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/font/gofont"
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/internal/debug"
|
||||
"gioui.org/internal/ops"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/input"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
"gioui.org/text"
|
||||
"gioui.org/unit"
|
||||
"gioui.org/widget"
|
||||
"gioui.org/widget/material"
|
||||
)
|
||||
|
||||
// Option configures a window.
|
||||
type Option func(unit.Metric, *Config)
|
||||
|
||||
// Window represents an operating system window.
|
||||
//
|
||||
// The zero-value Window is useful; the GUI window is created and shown the first
|
||||
// time the [Event] method is called. On iOS or Android, the first Window represents
|
||||
// the window previously created by the platform.
|
||||
//
|
||||
// More than one Window is not supported on iOS, Android, WebAssembly.
|
||||
type Window struct {
|
||||
initialOpts []Option
|
||||
initialActions []system.Action
|
||||
|
||||
ctx context
|
||||
gpu gpu.GPU
|
||||
// timer tracks the delayed invalidate goroutine.
|
||||
timer struct {
|
||||
// quit is shuts down the goroutine.
|
||||
quit chan struct{}
|
||||
// update the invalidate time.
|
||||
update chan time.Time
|
||||
}
|
||||
|
||||
animating bool
|
||||
hasNextFrame bool
|
||||
nextFrame time.Time
|
||||
// viewport is the latest frame size with insets applied.
|
||||
viewport image.Rectangle
|
||||
// metric is the metric from the most recent frame.
|
||||
metric unit.Metric
|
||||
queue input.Router
|
||||
cursor pointer.Cursor
|
||||
decorations struct {
|
||||
op.Ops
|
||||
// enabled tracks the Decorated option as
|
||||
// given to the Option method. It may differ
|
||||
// from Config.Decorated depending on platform
|
||||
// capability.
|
||||
enabled bool
|
||||
Config
|
||||
height unit.Dp
|
||||
currentHeight int
|
||||
*material.Theme
|
||||
*widget.Decorations
|
||||
}
|
||||
nocontext bool
|
||||
// semantic data, lazily evaluated if requested by a backend to speed up
|
||||
// the cases where semantic data is not needed.
|
||||
semantic struct {
|
||||
// uptodate tracks whether the fields below are up to date.
|
||||
uptodate bool
|
||||
root input.SemanticID
|
||||
prevTree []input.SemanticNode
|
||||
tree []input.SemanticNode
|
||||
ids map[input.SemanticID]input.SemanticNode
|
||||
}
|
||||
imeState editorState
|
||||
driver driver
|
||||
// gpuErr tracks the GPU error that is to be reported when
|
||||
// the window is closed.
|
||||
gpuErr error
|
||||
|
||||
// invMu protects mayInvalidate.
|
||||
invMu sync.Mutex
|
||||
mayInvalidate bool
|
||||
|
||||
// coalesced tracks the most recent events waiting to be delivered
|
||||
// to the client.
|
||||
coalesced eventSummary
|
||||
// frame tracks the most recent frame event.
|
||||
lastFrame struct {
|
||||
sync bool
|
||||
size image.Point
|
||||
off image.Point
|
||||
deco op.CallOp
|
||||
}
|
||||
}
|
||||
|
||||
type eventSummary struct {
|
||||
wakeup bool
|
||||
cfg *ConfigEvent
|
||||
view *ViewEvent
|
||||
frame *frameEvent
|
||||
framePending bool
|
||||
destroy *DestroyEvent
|
||||
}
|
||||
|
||||
type callbacks struct {
|
||||
w *Window
|
||||
}
|
||||
|
||||
func decoHeightOpt(h unit.Dp) Option {
|
||||
return func(m unit.Metric, c *Config) {
|
||||
c.decoHeight = h
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) validateAndProcess(size image.Point, sync bool, frame *op.Ops, sigChan chan<- struct{}) error {
|
||||
signal := func() {
|
||||
if sigChan != nil {
|
||||
// We're done with frame, let the client continue.
|
||||
sigChan <- struct{}{}
|
||||
// Signal at most once.
|
||||
sigChan = nil
|
||||
}
|
||||
}
|
||||
defer signal()
|
||||
for {
|
||||
if w.gpu == nil && !w.nocontext {
|
||||
var err error
|
||||
if w.ctx == nil {
|
||||
w.ctx, err = w.driver.NewContext()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sync = true
|
||||
}
|
||||
}
|
||||
if sync && w.ctx != nil {
|
||||
if err := w.ctx.Refresh(); err != nil {
|
||||
if errors.Is(err, errOutOfDate) {
|
||||
// Surface couldn't be created for transient reasons. Skip
|
||||
// this frame and wait for the next.
|
||||
return nil
|
||||
}
|
||||
w.destroyGPU()
|
||||
if errors.Is(err, gpu.ErrDeviceLost) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
if w.ctx != nil {
|
||||
if err := w.ctx.Lock(); err != nil {
|
||||
w.destroyGPU()
|
||||
return err
|
||||
}
|
||||
}
|
||||
if w.gpu == nil && !w.nocontext {
|
||||
gpu, err := gpu.New(w.ctx.API())
|
||||
if err != nil {
|
||||
w.ctx.Unlock()
|
||||
w.destroyGPU()
|
||||
return err
|
||||
}
|
||||
w.gpu = gpu
|
||||
}
|
||||
if w.gpu != nil {
|
||||
if err := w.frame(frame, size); err != nil {
|
||||
w.ctx.Unlock()
|
||||
if errors.Is(err, errOutOfDate) {
|
||||
// GPU surface needs refreshing.
|
||||
sync = true
|
||||
continue
|
||||
}
|
||||
w.destroyGPU()
|
||||
if errors.Is(err, gpu.ErrDeviceLost) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
w.queue.Frame(frame)
|
||||
// Let the client continue as soon as possible, in particular before
|
||||
// a potentially blocking Present.
|
||||
signal()
|
||||
var err error
|
||||
if w.gpu != nil {
|
||||
err = w.ctx.Present()
|
||||
w.ctx.Unlock()
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) frame(frame *op.Ops, viewport image.Point) error {
|
||||
if runtime.GOOS == "js" {
|
||||
// Use transparent black when Gio is embedded, to allow mixing of Gio and
|
||||
// foreign content below.
|
||||
w.gpu.Clear(color.NRGBA{A: 0x00, R: 0x00, G: 0x00, B: 0x00})
|
||||
} else {
|
||||
w.gpu.Clear(color.NRGBA{A: 0xff, R: 0xff, G: 0xff, B: 0xff})
|
||||
}
|
||||
target, err := w.ctx.RenderTarget()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return w.gpu.Frame(frame, target, viewport)
|
||||
}
|
||||
|
||||
func (w *Window) processFrame(frame *op.Ops, ack chan<- struct{}) {
|
||||
w.coalesced.framePending = false
|
||||
wrapper := &w.decorations.Ops
|
||||
off := op.Offset(w.lastFrame.off).Push(wrapper)
|
||||
ops.AddCall(&wrapper.Internal, &frame.Internal, ops.PC{}, ops.PCFor(&frame.Internal))
|
||||
off.Pop()
|
||||
w.lastFrame.deco.Add(wrapper)
|
||||
if err := w.validateAndProcess(w.lastFrame.size, w.lastFrame.sync, wrapper, ack); err != nil {
|
||||
w.destroyGPU()
|
||||
w.gpuErr = err
|
||||
w.driver.Perform(system.ActionClose)
|
||||
return
|
||||
}
|
||||
w.updateState()
|
||||
w.updateCursor()
|
||||
}
|
||||
|
||||
func (w *Window) updateState() {
|
||||
for k := range w.semantic.ids {
|
||||
delete(w.semantic.ids, k)
|
||||
}
|
||||
w.semantic.uptodate = false
|
||||
q := &w.queue
|
||||
switch q.TextInputState() {
|
||||
case input.TextInputOpen:
|
||||
w.driver.ShowTextInput(true)
|
||||
case input.TextInputClose:
|
||||
w.driver.ShowTextInput(false)
|
||||
}
|
||||
if hint, ok := q.TextInputHint(); ok {
|
||||
w.driver.SetInputHint(hint)
|
||||
}
|
||||
if mime, txt, ok := q.WriteClipboard(); ok {
|
||||
w.driver.WriteClipboard(mime, txt)
|
||||
}
|
||||
if q.ClipboardRequested() {
|
||||
w.driver.ReadClipboard()
|
||||
}
|
||||
oldState := w.imeState
|
||||
newState := oldState
|
||||
newState.EditorState = q.EditorState()
|
||||
if newState != oldState {
|
||||
w.imeState = newState
|
||||
w.driver.EditorStateChanged(oldState, newState)
|
||||
}
|
||||
if t, ok := q.WakeupTime(); ok {
|
||||
w.setNextFrame(t)
|
||||
}
|
||||
w.updateAnimation()
|
||||
}
|
||||
|
||||
// Invalidate the window such that a [FrameEvent] will be generated immediately.
|
||||
// If the window is inactive, an unspecified event is sent instead.
|
||||
//
|
||||
// Note that Invalidate is intended for externally triggered updates, such as a
|
||||
// response from a network request. The [op.InvalidateCmd] command is more efficient
|
||||
// for animation.
|
||||
//
|
||||
// Invalidate is safe for concurrent use.
|
||||
func (w *Window) Invalidate() {
|
||||
w.invMu.Lock()
|
||||
defer w.invMu.Unlock()
|
||||
if w.mayInvalidate {
|
||||
w.mayInvalidate = false
|
||||
w.driver.Invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
// Option applies the options to the window. The options are hints; the platform is
|
||||
// free to ignore or adjust them.
|
||||
func (w *Window) Option(opts ...Option) {
|
||||
if len(opts) == 0 {
|
||||
return
|
||||
}
|
||||
if w.driver == nil {
|
||||
w.initialOpts = append(w.initialOpts, opts...)
|
||||
return
|
||||
}
|
||||
w.Run(func() {
|
||||
cnf := Config{Decorated: w.decorations.enabled}
|
||||
for _, opt := range opts {
|
||||
opt(w.metric, &cnf)
|
||||
}
|
||||
w.decorations.enabled = cnf.Decorated
|
||||
decoHeight := w.decorations.height
|
||||
if !w.decorations.enabled {
|
||||
decoHeight = 0
|
||||
}
|
||||
opts = append(opts, decoHeightOpt(decoHeight))
|
||||
w.driver.Configure(opts)
|
||||
w.setNextFrame(time.Time{})
|
||||
w.updateAnimation()
|
||||
})
|
||||
}
|
||||
|
||||
// Run f in the same thread as the native window event loop, and wait for f to
|
||||
// return or the window to close. If the window has not yet been created,
|
||||
// Run calls f directly.
|
||||
//
|
||||
// Note that most programs should not call Run; configuring a Window with
|
||||
// [CustomRenderer] is a notable exception.
|
||||
func (w *Window) Run(f func()) {
|
||||
if w.driver == nil {
|
||||
f()
|
||||
return
|
||||
}
|
||||
done := make(chan struct{})
|
||||
w.driver.Run(func() {
|
||||
defer close(done)
|
||||
f()
|
||||
})
|
||||
<-done
|
||||
}
|
||||
|
||||
func (w *Window) updateAnimation() {
|
||||
if w.driver == nil {
|
||||
return
|
||||
}
|
||||
animate := false
|
||||
if w.hasNextFrame {
|
||||
if dt := time.Until(w.nextFrame); dt <= 0 {
|
||||
animate = true
|
||||
} else {
|
||||
// Schedule redraw.
|
||||
w.scheduleInvalidate(w.nextFrame)
|
||||
}
|
||||
}
|
||||
if animate != w.animating {
|
||||
w.animating = animate
|
||||
w.driver.SetAnimating(animate)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) scheduleInvalidate(t time.Time) {
|
||||
if w.timer.quit == nil {
|
||||
w.timer.quit = make(chan struct{})
|
||||
w.timer.update = make(chan time.Time)
|
||||
go func() {
|
||||
var timer *time.Timer
|
||||
for {
|
||||
var timeC <-chan time.Time
|
||||
if timer != nil {
|
||||
timeC = timer.C
|
||||
}
|
||||
select {
|
||||
case <-w.timer.quit:
|
||||
w.timer.quit <- struct{}{}
|
||||
return
|
||||
case t := <-w.timer.update:
|
||||
if timer != nil {
|
||||
timer.Stop()
|
||||
}
|
||||
timer = time.NewTimer(time.Until(t))
|
||||
case <-timeC:
|
||||
w.Invalidate()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
w.timer.update <- t
|
||||
}
|
||||
|
||||
func (w *Window) setNextFrame(at time.Time) {
|
||||
if !w.hasNextFrame || at.Before(w.nextFrame) {
|
||||
w.hasNextFrame = true
|
||||
w.nextFrame = at
|
||||
}
|
||||
}
|
||||
|
||||
func (c *callbacks) SetDriver(d driver) {
|
||||
if d == nil {
|
||||
panic("nil driver")
|
||||
}
|
||||
c.w.invMu.Lock()
|
||||
defer c.w.invMu.Unlock()
|
||||
c.w.driver = d
|
||||
}
|
||||
|
||||
func (c *callbacks) ProcessFrame(frame *op.Ops, ack chan<- struct{}) {
|
||||
c.w.processFrame(frame, ack)
|
||||
}
|
||||
|
||||
func (c *callbacks) ProcessEvent(e event.Event) bool {
|
||||
return c.w.processEvent(e)
|
||||
}
|
||||
|
||||
// SemanticRoot returns the ID of the semantic root.
|
||||
func (c *callbacks) SemanticRoot() input.SemanticID {
|
||||
c.w.updateSemantics()
|
||||
return c.w.semantic.root
|
||||
}
|
||||
|
||||
// LookupSemantic looks up a semantic node from an ID. The zero ID denotes the root.
|
||||
func (c *callbacks) LookupSemantic(semID input.SemanticID) (input.SemanticNode, bool) {
|
||||
c.w.updateSemantics()
|
||||
n, found := c.w.semantic.ids[semID]
|
||||
return n, found
|
||||
}
|
||||
|
||||
func (c *callbacks) AppendSemanticDiffs(diffs []input.SemanticID) []input.SemanticID {
|
||||
c.w.updateSemantics()
|
||||
if tree := c.w.semantic.prevTree; len(tree) > 0 {
|
||||
c.w.collectSemanticDiffs(&diffs, c.w.semantic.prevTree[0])
|
||||
}
|
||||
return diffs
|
||||
}
|
||||
|
||||
func (c *callbacks) SemanticAt(pos f32.Point) (input.SemanticID, bool) {
|
||||
c.w.updateSemantics()
|
||||
return c.w.queue.SemanticAt(pos)
|
||||
}
|
||||
|
||||
func (c *callbacks) EditorState() editorState {
|
||||
return c.w.imeState
|
||||
}
|
||||
|
||||
func (c *callbacks) SetComposingRegion(r key.Range) {
|
||||
c.w.imeState.compose = r
|
||||
c.w.driver.ProcessEvent(key.CompositionEvent(r))
|
||||
}
|
||||
|
||||
func (c *callbacks) EditorInsert(text string) {
|
||||
sel := c.w.imeState.Selection.Range
|
||||
c.EditorReplace(sel, text)
|
||||
start := min(sel.End, sel.Start)
|
||||
sel.Start = start + utf8.RuneCountInString(text)
|
||||
sel.End = sel.Start
|
||||
c.SetEditorSelection(sel)
|
||||
}
|
||||
|
||||
func (c *callbacks) EditorReplace(r key.Range, text string) {
|
||||
c.w.imeState.Replace(r, text)
|
||||
c.w.driver.ProcessEvent(key.EditEvent{Range: r, Text: text})
|
||||
c.w.driver.ProcessEvent(key.SnippetEvent(c.w.imeState.Snippet.Range))
|
||||
}
|
||||
|
||||
func (c *callbacks) SetEditorSelection(r key.Range) {
|
||||
c.w.imeState.Selection.Range = r
|
||||
c.w.driver.ProcessEvent(key.SelectionEvent(r))
|
||||
}
|
||||
|
||||
func (c *callbacks) SetEditorSnippet(r key.Range) {
|
||||
if sn := c.EditorState().Snippet.Range; sn == r {
|
||||
// No need to expand.
|
||||
return
|
||||
}
|
||||
c.w.driver.ProcessEvent(key.SnippetEvent(r))
|
||||
}
|
||||
|
||||
func (w *Window) moveFocus(dir key.FocusDirection) {
|
||||
w.queue.MoveFocus(dir)
|
||||
if _, handled := w.queue.WakeupTime(); handled {
|
||||
w.queue.RevealFocus(w.viewport)
|
||||
} else {
|
||||
var v image.Point
|
||||
switch dir {
|
||||
case key.FocusRight:
|
||||
v = image.Pt(+1, 0)
|
||||
case key.FocusLeft:
|
||||
v = image.Pt(-1, 0)
|
||||
case key.FocusDown:
|
||||
v = image.Pt(0, +1)
|
||||
case key.FocusUp:
|
||||
v = image.Pt(0, -1)
|
||||
default:
|
||||
return
|
||||
}
|
||||
const scrollABit = unit.Dp(50)
|
||||
dist := v.Mul(int(w.metric.Dp(scrollABit)))
|
||||
w.queue.ScrollFocus(dist)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *callbacks) ClickFocus() {
|
||||
c.w.queue.ClickFocus()
|
||||
c.w.setNextFrame(time.Time{})
|
||||
c.w.updateAnimation()
|
||||
}
|
||||
|
||||
func (c *callbacks) ActionAt(p f32.Point) (system.Action, bool) {
|
||||
return c.w.queue.ActionAt(p)
|
||||
}
|
||||
|
||||
func (w *Window) destroyGPU() {
|
||||
if w.gpu != nil {
|
||||
w.ctx.Lock()
|
||||
w.gpu.Release()
|
||||
w.ctx.Unlock()
|
||||
w.gpu = nil
|
||||
}
|
||||
if w.ctx != nil {
|
||||
w.ctx.Release()
|
||||
w.ctx = nil
|
||||
}
|
||||
}
|
||||
|
||||
// updateSemantics refreshes the semantics tree, the id to node map and the ids of
|
||||
// updated nodes.
|
||||
func (w *Window) updateSemantics() {
|
||||
if w.semantic.uptodate {
|
||||
return
|
||||
}
|
||||
w.semantic.uptodate = true
|
||||
w.semantic.prevTree, w.semantic.tree = w.semantic.tree, w.semantic.prevTree
|
||||
w.semantic.tree = w.queue.AppendSemantics(w.semantic.tree[:0])
|
||||
w.semantic.root = w.semantic.tree[0].ID
|
||||
for _, n := range w.semantic.tree {
|
||||
w.semantic.ids[n.ID] = n
|
||||
}
|
||||
}
|
||||
|
||||
// collectSemanticDiffs traverses the previous semantic tree, noting changed nodes.
|
||||
func (w *Window) collectSemanticDiffs(diffs *[]input.SemanticID, n input.SemanticNode) {
|
||||
newNode, exists := w.semantic.ids[n.ID]
|
||||
// Ignore deleted nodes, as their disappearance will be reported through an
|
||||
// ancestor node.
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
diff := newNode.Desc != n.Desc || len(n.Children) != len(newNode.Children)
|
||||
for i, ch := range n.Children {
|
||||
if !diff {
|
||||
newCh := newNode.Children[i]
|
||||
diff = ch.ID != newCh.ID
|
||||
}
|
||||
w.collectSemanticDiffs(diffs, ch)
|
||||
}
|
||||
if diff {
|
||||
*diffs = append(*diffs, n.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *callbacks) Invalidate() {
|
||||
c.w.setNextFrame(time.Time{})
|
||||
c.w.updateAnimation()
|
||||
// Guarantee a wakeup, even when not animating.
|
||||
c.w.processEvent(wakeupEvent{})
|
||||
}
|
||||
|
||||
func (c *callbacks) nextEvent() (event.Event, bool) {
|
||||
return c.w.nextEvent()
|
||||
}
|
||||
|
||||
func (w *Window) nextEvent() (event.Event, bool) {
|
||||
s := &w.coalesced
|
||||
defer func() {
|
||||
// Every event counts as a wakeup.
|
||||
s.wakeup = false
|
||||
}()
|
||||
switch {
|
||||
case s.framePending:
|
||||
// If the user didn't call FrameEvent.Event, process
|
||||
// an empty frame.
|
||||
w.processFrame(new(op.Ops), nil)
|
||||
case s.view != nil:
|
||||
e := *s.view
|
||||
s.view = nil
|
||||
return e, true
|
||||
case s.destroy != nil:
|
||||
e := *s.destroy
|
||||
// Clear pending events after DestroyEvent is delivered.
|
||||
*s = eventSummary{}
|
||||
return e, true
|
||||
case s.cfg != nil:
|
||||
e := *s.cfg
|
||||
s.cfg = nil
|
||||
return e, true
|
||||
case s.frame != nil:
|
||||
e := *s.frame
|
||||
s.frame = nil
|
||||
s.framePending = true
|
||||
return e.FrameEvent, true
|
||||
case s.wakeup:
|
||||
return wakeupEvent{}, true
|
||||
}
|
||||
w.invMu.Lock()
|
||||
defer w.invMu.Unlock()
|
||||
w.mayInvalidate = w.driver != nil
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (w *Window) processEvent(e event.Event) bool {
|
||||
switch e2 := e.(type) {
|
||||
case wakeupEvent:
|
||||
w.coalesced.wakeup = true
|
||||
case frameEvent:
|
||||
if e2.Size == (image.Point{}) {
|
||||
panic(errors.New("internal error: zero-sized Draw"))
|
||||
}
|
||||
w.metric = e2.Metric
|
||||
w.hasNextFrame = false
|
||||
e2.Frame = w.driver.Frame
|
||||
e2.Source = w.queue.Source()
|
||||
// Prepare the decorations and update the frame insets.
|
||||
viewport := image.Rectangle{
|
||||
Min: image.Point{
|
||||
X: e2.Metric.Dp(e2.Insets.Left),
|
||||
Y: e2.Metric.Dp(e2.Insets.Top),
|
||||
},
|
||||
Max: image.Point{
|
||||
X: e2.Size.X - e2.Metric.Dp(e2.Insets.Right),
|
||||
Y: e2.Size.Y - e2.Metric.Dp(e2.Insets.Bottom),
|
||||
},
|
||||
}
|
||||
// Scroll to focus if viewport is shrinking in any dimension.
|
||||
if old, new := w.viewport.Size(), viewport.Size(); new.X < old.X || new.Y < old.Y {
|
||||
w.queue.RevealFocus(viewport)
|
||||
}
|
||||
w.viewport = viewport
|
||||
wrapper := &w.decorations.Ops
|
||||
wrapper.Reset()
|
||||
m := op.Record(wrapper)
|
||||
offset := w.decorate(e2.FrameEvent, wrapper)
|
||||
w.lastFrame.deco = m.Stop()
|
||||
w.lastFrame.size = e2.Size
|
||||
w.lastFrame.sync = e2.Sync
|
||||
w.lastFrame.off = offset
|
||||
e2.Size = e2.Size.Sub(offset)
|
||||
w.coalesced.frame = &e2
|
||||
case DestroyEvent:
|
||||
if w.gpuErr != nil {
|
||||
e2.Err = w.gpuErr
|
||||
}
|
||||
w.destroyGPU()
|
||||
w.invMu.Lock()
|
||||
w.mayInvalidate = false
|
||||
w.driver = nil
|
||||
w.invMu.Unlock()
|
||||
if q := w.timer.quit; q != nil {
|
||||
q <- struct{}{}
|
||||
<-q
|
||||
}
|
||||
w.coalesced.destroy = &e2
|
||||
case ViewEvent:
|
||||
if !e2.Valid() && w.gpu != nil {
|
||||
w.ctx.Lock()
|
||||
w.gpu.Release()
|
||||
w.gpu = nil
|
||||
w.ctx.Unlock()
|
||||
}
|
||||
w.coalesced.view = &e2
|
||||
case ConfigEvent:
|
||||
w.decorations.Decorations.Maximized = e2.Config.Mode == Maximized
|
||||
wasFocused := w.decorations.Config.Focused
|
||||
w.decorations.Config = e2.Config
|
||||
e2.Config = w.effectiveConfig()
|
||||
w.coalesced.cfg = &e2
|
||||
if f := w.decorations.Config.Focused; f != wasFocused {
|
||||
w.queue.Queue(key.FocusEvent{Focus: f})
|
||||
}
|
||||
t, handled := w.queue.WakeupTime()
|
||||
if handled {
|
||||
w.setNextFrame(t)
|
||||
w.updateAnimation()
|
||||
}
|
||||
return handled
|
||||
case event.Event:
|
||||
focusDir := key.FocusDirection(-1)
|
||||
if e, ok := e2.(key.Event); ok && e.State == key.Press {
|
||||
isMobile := runtime.GOOS == "ios" || runtime.GOOS == "android"
|
||||
switch {
|
||||
case e.Name == key.NameTab && e.Modifiers == 0:
|
||||
focusDir = key.FocusForward
|
||||
case e.Name == key.NameTab && e.Modifiers == key.ModShift:
|
||||
focusDir = key.FocusBackward
|
||||
case e.Name == key.NameUpArrow && e.Modifiers == 0 && isMobile:
|
||||
focusDir = key.FocusUp
|
||||
case e.Name == key.NameDownArrow && e.Modifiers == 0 && isMobile:
|
||||
focusDir = key.FocusDown
|
||||
case e.Name == key.NameLeftArrow && e.Modifiers == 0 && isMobile:
|
||||
focusDir = key.FocusLeft
|
||||
case e.Name == key.NameRightArrow && e.Modifiers == 0 && isMobile:
|
||||
focusDir = key.FocusRight
|
||||
}
|
||||
}
|
||||
e := e2
|
||||
if focusDir != -1 {
|
||||
e = input.SystemEvent{Event: e}
|
||||
}
|
||||
w.queue.Queue(e)
|
||||
t, handled := w.queue.WakeupTime()
|
||||
if focusDir != -1 && !handled {
|
||||
w.moveFocus(focusDir)
|
||||
t, handled = w.queue.WakeupTime()
|
||||
}
|
||||
w.updateCursor()
|
||||
if handled {
|
||||
w.setNextFrame(t)
|
||||
w.updateAnimation()
|
||||
}
|
||||
return handled
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Event blocks until an event is received from the window, such as
|
||||
// [FrameEvent], or until [Invalidate] is called. The window is created
|
||||
// and shown the first time Event is called.
|
||||
func (w *Window) Event() event.Event {
|
||||
if w.driver == nil {
|
||||
w.init()
|
||||
}
|
||||
if w.driver == nil {
|
||||
e, ok := w.nextEvent()
|
||||
if !ok {
|
||||
panic("window initialization failed without a DestroyEvent")
|
||||
}
|
||||
return e
|
||||
}
|
||||
return w.driver.Event()
|
||||
}
|
||||
|
||||
func (w *Window) init() {
|
||||
debug.Parse()
|
||||
// Measure decoration height.
|
||||
deco := new(widget.Decorations)
|
||||
theme := material.NewTheme()
|
||||
theme.Shaper = text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Regular()))
|
||||
decoStyle := material.Decorations(theme, deco, 0, "")
|
||||
gtx := layout.Context{
|
||||
Ops: new(op.Ops),
|
||||
// Measure in Dp.
|
||||
Metric: unit.Metric{},
|
||||
}
|
||||
// Allow plenty of space.
|
||||
gtx.Constraints.Max.Y = 200
|
||||
dims := decoStyle.Layout(gtx)
|
||||
decoHeight := unit.Dp(dims.Size.Y)
|
||||
defaultOptions := []Option{
|
||||
Size(800, 600),
|
||||
Title("Gio"),
|
||||
Decorated(true),
|
||||
decoHeightOpt(decoHeight),
|
||||
}
|
||||
options := append(defaultOptions, w.initialOpts...)
|
||||
w.initialOpts = nil
|
||||
var cnf Config
|
||||
cnf.apply(unit.Metric{}, options)
|
||||
|
||||
w.nocontext = cnf.CustomRenderer
|
||||
w.decorations.Theme = theme
|
||||
w.decorations.Decorations = deco
|
||||
w.decorations.enabled = cnf.Decorated
|
||||
w.decorations.height = decoHeight
|
||||
w.imeState.compose = key.Range{Start: -1, End: -1}
|
||||
w.semantic.ids = make(map[input.SemanticID]input.SemanticNode)
|
||||
newWindow(&callbacks{w}, options)
|
||||
for _, acts := range w.initialActions {
|
||||
w.Perform(acts)
|
||||
}
|
||||
w.initialActions = nil
|
||||
}
|
||||
|
||||
func (w *Window) updateCursor() {
|
||||
if c := w.queue.Cursor(); c != w.cursor {
|
||||
w.cursor = c
|
||||
w.driver.SetCursor(c)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) fallbackDecorate() bool {
|
||||
cnf := w.decorations.Config
|
||||
return w.decorations.enabled && !cnf.Decorated && cnf.Mode != Fullscreen && !w.nocontext
|
||||
}
|
||||
|
||||
// decorate the window if enabled and returns the corresponding Insets.
|
||||
func (w *Window) decorate(e FrameEvent, o *op.Ops) image.Point {
|
||||
if !w.fallbackDecorate() {
|
||||
return image.Pt(0, 0)
|
||||
}
|
||||
deco := w.decorations.Decorations
|
||||
allActions := system.ActionMinimize | system.ActionMaximize | system.ActionUnmaximize |
|
||||
system.ActionClose | system.ActionMove
|
||||
style := material.Decorations(w.decorations.Theme, deco, allActions, w.decorations.Config.Title)
|
||||
// Update the decorations based on the current window mode.
|
||||
var actions system.Action
|
||||
switch m := w.decorations.Config.Mode; m {
|
||||
case Windowed:
|
||||
actions |= system.ActionUnmaximize
|
||||
case Minimized:
|
||||
actions |= system.ActionMinimize
|
||||
case Maximized:
|
||||
actions |= system.ActionMaximize
|
||||
case Fullscreen:
|
||||
actions |= system.ActionFullscreen
|
||||
default:
|
||||
panic(fmt.Errorf("unknown WindowMode %v", m))
|
||||
}
|
||||
gtx := layout.Context{
|
||||
Ops: o,
|
||||
Now: e.Now,
|
||||
Source: e.Source,
|
||||
Metric: e.Metric,
|
||||
Constraints: layout.Exact(e.Size),
|
||||
}
|
||||
// Update the window based on the actions on the decorations.
|
||||
opts, acts := splitActions(deco.Update(gtx))
|
||||
if len(opts) > 0 {
|
||||
w.driver.Configure(opts)
|
||||
}
|
||||
if acts != 0 {
|
||||
w.driver.Perform(acts)
|
||||
}
|
||||
style.Layout(gtx)
|
||||
// Offset to place the frame content below the decorations.
|
||||
decoHeight := gtx.Dp(w.decorations.Config.decoHeight)
|
||||
if w.decorations.currentHeight != decoHeight {
|
||||
w.decorations.currentHeight = decoHeight
|
||||
w.coalesced.cfg = &ConfigEvent{Config: w.effectiveConfig()}
|
||||
}
|
||||
return image.Pt(0, decoHeight)
|
||||
}
|
||||
|
||||
func (w *Window) effectiveConfig() Config {
|
||||
cnf := w.decorations.Config
|
||||
cnf.Size.Y -= w.decorations.currentHeight
|
||||
cnf.Decorated = w.decorations.enabled || cnf.Decorated
|
||||
return cnf
|
||||
}
|
||||
|
||||
// splitActions splits options from actions and return them and the remaining
|
||||
// actions.
|
||||
func splitActions(actions system.Action) ([]Option, system.Action) {
|
||||
var opts []Option
|
||||
walkActions(actions, func(action system.Action) {
|
||||
switch action {
|
||||
case system.ActionMinimize:
|
||||
opts = append(opts, Minimized.Option())
|
||||
case system.ActionMaximize:
|
||||
opts = append(opts, Maximized.Option())
|
||||
case system.ActionUnmaximize:
|
||||
opts = append(opts, Windowed.Option())
|
||||
case system.ActionFullscreen:
|
||||
opts = append(opts, Fullscreen.Option())
|
||||
default:
|
||||
return
|
||||
}
|
||||
actions &^= action
|
||||
})
|
||||
return opts, actions
|
||||
}
|
||||
|
||||
// Perform the actions on the window.
|
||||
func (w *Window) Perform(actions system.Action) {
|
||||
opts, acts := splitActions(actions)
|
||||
w.Option(opts...)
|
||||
if acts == 0 {
|
||||
return
|
||||
}
|
||||
if w.driver == nil {
|
||||
w.initialActions = append(w.initialActions, acts)
|
||||
return
|
||||
}
|
||||
w.Run(func() {
|
||||
w.driver.Perform(actions)
|
||||
})
|
||||
}
|
||||
|
||||
// Title sets the title of the window.
|
||||
func Title(t string) Option {
|
||||
return func(_ unit.Metric, cnf *Config) {
|
||||
cnf.Title = t
|
||||
}
|
||||
}
|
||||
|
||||
// Size sets the size of the window. The mode will be changed to Windowed.
|
||||
func Size(w, h unit.Dp) Option {
|
||||
if w <= 0 {
|
||||
panic("width must be larger than or equal to 0")
|
||||
}
|
||||
if h <= 0 {
|
||||
panic("height must be larger than or equal to 0")
|
||||
}
|
||||
return func(m unit.Metric, cnf *Config) {
|
||||
cnf.Mode = Windowed
|
||||
cnf.Size = image.Point{
|
||||
X: m.Dp(w),
|
||||
Y: m.Dp(h),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MaxSize sets the maximum size of the window.
|
||||
func MaxSize(w, h unit.Dp) Option {
|
||||
if w <= 0 {
|
||||
panic("width must be larger than or equal to 0")
|
||||
}
|
||||
if h <= 0 {
|
||||
panic("height must be larger than or equal to 0")
|
||||
}
|
||||
return func(m unit.Metric, cnf *Config) {
|
||||
cnf.MaxSize = image.Point{
|
||||
X: m.Dp(w),
|
||||
Y: m.Dp(h),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MinSize sets the minimum size of the window.
|
||||
func MinSize(w, h unit.Dp) Option {
|
||||
if w <= 0 {
|
||||
panic("width must be larger than or equal to 0")
|
||||
}
|
||||
if h <= 0 {
|
||||
panic("height must be larger than or equal to 0")
|
||||
}
|
||||
return func(m unit.Metric, cnf *Config) {
|
||||
cnf.MinSize = image.Point{
|
||||
X: m.Dp(w),
|
||||
Y: m.Dp(h),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// StatusColor sets the color of the Android status bar.
|
||||
func StatusColor(color color.NRGBA) Option {
|
||||
return func(_ unit.Metric, cnf *Config) {
|
||||
cnf.StatusColor = color
|
||||
}
|
||||
}
|
||||
|
||||
// NavigationColor sets the color of the navigation bar on Android, or the address bar in browsers.
|
||||
func NavigationColor(color color.NRGBA) Option {
|
||||
return func(_ unit.Metric, cnf *Config) {
|
||||
cnf.NavigationColor = color
|
||||
}
|
||||
}
|
||||
|
||||
// CustomRenderer controls whether the window contents is
|
||||
// rendered by the client. If true, no GPU context is created.
|
||||
//
|
||||
// Caller must assume responsibility for rendering which includes
|
||||
// initializing the render backend, swapping the framebuffer and
|
||||
// handling frame pacing.
|
||||
func CustomRenderer(custom bool) Option {
|
||||
return func(_ unit.Metric, cnf *Config) {
|
||||
cnf.CustomRenderer = custom
|
||||
}
|
||||
}
|
||||
|
||||
// Decorated controls whether Gio and/or the platform are responsible
|
||||
// for drawing window decorations. Providing false indicates that
|
||||
// the application will either be undecorated or will draw its own decorations.
|
||||
func Decorated(enabled bool) Option {
|
||||
return func(_ unit.Metric, cnf *Config) {
|
||||
cnf.Decorated = enabled
|
||||
}
|
||||
}
|
||||
|
||||
// TopMost windows will be rendered above all other non-top-most windows.
|
||||
//
|
||||
// TopMost windows are supported on macOS, Windows.
|
||||
func TopMost(enabled bool) Option {
|
||||
return func(_ unit.Metric, cnf *Config) {
|
||||
cnf.TopMost = enabled
|
||||
}
|
||||
}
|
||||
|
||||
// flushEvent is sent to detect when the user program
|
||||
// has completed processing of all prior events. Its an
|
||||
// [io/event.Event] but only for internal use.
|
||||
type flushEvent struct{}
|
||||
|
||||
func (t flushEvent) ImplementsEvent() {}
|
||||
|
||||
// theFlushEvent avoids allocating garbage when sending
|
||||
// flushEvents.
|
||||
var theFlushEvent flushEvent
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package f32
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Affine2D represents an affine 2D transformation. The zero value of Affine2D
|
||||
// represents the identity transform.
|
||||
type Affine2D struct {
|
||||
// in order to make the zero value of Affine2D represent the identity
|
||||
// transform we store it with the identity matrix subtracted, that is
|
||||
// if the actual transformation matrix is:
|
||||
// [sx, hx, ox]
|
||||
// [hy, sy, oy]
|
||||
// [ 0, 0, 1]
|
||||
// we store a = sx-1 and e = sy-1
|
||||
a, b, c float32
|
||||
d, e, f float32
|
||||
}
|
||||
|
||||
// NewAffine2D creates a new Affine2D transform from the matrix elements
|
||||
// in row major order. The rows are: [sx, hx, ox], [hy, sy, oy], [0, 0, 1].
|
||||
func NewAffine2D(sx, hx, ox, hy, sy, oy float32) Affine2D {
|
||||
return Affine2D{
|
||||
a: sx - 1, b: hx, c: ox,
|
||||
d: hy, e: sy - 1, f: oy,
|
||||
}
|
||||
}
|
||||
|
||||
// AffineId returns an identity transformation matrix that represents no transformation
|
||||
// when applied.
|
||||
func AffineId() Affine2D {
|
||||
return NewAffine2D(
|
||||
1, 0, 0,
|
||||
0, 1, 0,
|
||||
)
|
||||
}
|
||||
|
||||
// Offset the transformation.
|
||||
func (a Affine2D) Offset(offset Point) Affine2D {
|
||||
return Affine2D{
|
||||
a.a, a.b, a.c + offset.X,
|
||||
a.d, a.e, a.f + offset.Y,
|
||||
}
|
||||
}
|
||||
|
||||
// Scale the transformation around the given origin.
|
||||
func (a Affine2D) Scale(origin, factor Point) Affine2D {
|
||||
if origin == (Point{}) {
|
||||
return a.scale(factor)
|
||||
}
|
||||
a = a.Offset(origin.Mul(-1))
|
||||
a = a.scale(factor)
|
||||
return a.Offset(origin)
|
||||
}
|
||||
|
||||
// Rotate the transformation by the given angle (in radians) counter clockwise around the given origin.
|
||||
func (a Affine2D) Rotate(origin Point, radians float32) Affine2D {
|
||||
if origin == (Point{}) {
|
||||
return a.rotate(radians)
|
||||
}
|
||||
a = a.Offset(origin.Mul(-1))
|
||||
a = a.rotate(radians)
|
||||
return a.Offset(origin)
|
||||
}
|
||||
|
||||
// Shear the transformation by the given angle (in radians) around the given origin.
|
||||
func (a Affine2D) Shear(origin Point, radiansX, radiansY float32) Affine2D {
|
||||
if origin == (Point{}) {
|
||||
return a.shear(radiansX, radiansY)
|
||||
}
|
||||
a = a.Offset(origin.Mul(-1))
|
||||
a = a.shear(radiansX, radiansY)
|
||||
return a.Offset(origin)
|
||||
}
|
||||
|
||||
// Mul returns A*B.
|
||||
func (A Affine2D) Mul(B Affine2D) (r Affine2D) {
|
||||
r.a = (A.a+1)*(B.a+1) + A.b*B.d - 1
|
||||
r.b = (A.a+1)*B.b + A.b*(B.e+1)
|
||||
r.c = (A.a+1)*B.c + A.b*B.f + A.c
|
||||
r.d = A.d*(B.a+1) + (A.e+1)*B.d
|
||||
r.e = A.d*B.b + (A.e+1)*(B.e+1) - 1
|
||||
r.f = A.d*B.c + (A.e+1)*B.f + A.f
|
||||
return r
|
||||
}
|
||||
|
||||
// Invert the transformation. Note that if the matrix is close to singular
|
||||
// numerical errors may become large or infinity.
|
||||
func (a Affine2D) Invert() Affine2D {
|
||||
if a.a == 0 && a.b == 0 && a.d == 0 && a.e == 0 {
|
||||
return Affine2D{a: 0, b: 0, c: -a.c, d: 0, e: 0, f: -a.f}
|
||||
}
|
||||
a.a += 1
|
||||
a.e += 1
|
||||
det := a.a*a.e - a.b*a.d
|
||||
a.a, a.e = a.e/det, a.a/det
|
||||
a.b, a.d = -a.b/det, -a.d/det
|
||||
temp := a.c
|
||||
a.c = -a.a*a.c - a.b*a.f
|
||||
a.f = -a.d*temp - a.e*a.f
|
||||
a.a -= 1
|
||||
a.e -= 1
|
||||
return a
|
||||
}
|
||||
|
||||
// Transform p by returning a*p.
|
||||
func (a Affine2D) Transform(p Point) Point {
|
||||
return Point{
|
||||
X: p.X*(a.a+1) + p.Y*a.b + a.c,
|
||||
Y: p.X*a.d + p.Y*(a.e+1) + a.f,
|
||||
}
|
||||
}
|
||||
|
||||
// Elems returns the matrix elements of the transform in row-major order. The
|
||||
// rows are: [sx, hx, ox], [hy, sy, oy], [0, 0, 1].
|
||||
func (a Affine2D) Elems() (sx, hx, ox, hy, sy, oy float32) {
|
||||
return a.a + 1, a.b, a.c, a.d, a.e + 1, a.f
|
||||
}
|
||||
|
||||
// Split a transform into two parts, one which is pure offset and the
|
||||
// other representing the scaling, shearing and rotation part.
|
||||
func (a Affine2D) Split() (srs Affine2D, offset Point) {
|
||||
return Affine2D{
|
||||
a: a.a, b: a.b, c: 0,
|
||||
d: a.d, e: a.e, f: 0,
|
||||
}, Point{X: a.c, Y: a.f}
|
||||
}
|
||||
|
||||
func (a Affine2D) scale(factor Point) Affine2D {
|
||||
return Affine2D{
|
||||
(a.a+1)*factor.X - 1, a.b * factor.X, a.c * factor.X,
|
||||
a.d * factor.Y, (a.e+1)*factor.Y - 1, a.f * factor.Y,
|
||||
}
|
||||
}
|
||||
|
||||
func (a Affine2D) rotate(radians float32) Affine2D {
|
||||
sin, cos := math.Sincos(float64(radians))
|
||||
s, c := float32(sin), float32(cos)
|
||||
return Affine2D{
|
||||
(a.a+1)*c - a.d*s - 1, a.b*c - (a.e+1)*s, a.c*c - a.f*s,
|
||||
(a.a+1)*s + a.d*c, a.b*s + (a.e+1)*c - 1, a.c*s + a.f*c,
|
||||
}
|
||||
}
|
||||
|
||||
func (a Affine2D) shear(radiansX, radiansY float32) Affine2D {
|
||||
tx := float32(math.Tan(float64(radiansX)))
|
||||
ty := float32(math.Tan(float64(radiansY)))
|
||||
return Affine2D{
|
||||
(a.a + 1) + a.d*tx - 1, a.b + (a.e+1)*tx, a.c + a.f*tx,
|
||||
(a.a+1)*ty + a.d, a.b*ty + (a.e + 1) - 1, a.f*ty + a.f,
|
||||
}
|
||||
}
|
||||
|
||||
func (a Affine2D) String() string {
|
||||
sx, hx, ox, hy, sy, oy := a.Elems()
|
||||
|
||||
// precision 6, one period, negative sign and space per number
|
||||
const prec = 6
|
||||
const charsPerFloat = prec + 2 + 1
|
||||
s := make([]byte, 0, 6*charsPerFloat+6)
|
||||
|
||||
s = append(s, '[', '[')
|
||||
s = strconv.AppendFloat(s, float64(sx), 'g', prec, 32)
|
||||
s = append(s, ' ')
|
||||
s = strconv.AppendFloat(s, float64(hx), 'g', prec, 32)
|
||||
s = append(s, ' ')
|
||||
s = strconv.AppendFloat(s, float64(ox), 'g', prec, 32)
|
||||
s = append(s, ']', ' ', '[')
|
||||
s = strconv.AppendFloat(s, float64(hy), 'g', prec, 32)
|
||||
s = append(s, ' ')
|
||||
s = strconv.AppendFloat(s, float64(sy), 'g', prec, 32)
|
||||
s = append(s, ' ')
|
||||
s = strconv.AppendFloat(s, float64(oy), 'g', prec, 32)
|
||||
s = append(s, ']', ']')
|
||||
|
||||
return string(s)
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
/*
|
||||
Package f32 is a float32 implementation of package image's
|
||||
Point and affine transformations.
|
||||
|
||||
The coordinate space has the origin in the top left
|
||||
corner with the axes extending right and down.
|
||||
*/
|
||||
package f32
|
||||
|
||||
import (
|
||||
"image"
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// A Point is a two dimensional point.
|
||||
type Point struct {
|
||||
X, Y float32
|
||||
}
|
||||
|
||||
// String return a string representation of p.
|
||||
func (p Point) String() string {
|
||||
return "(" + strconv.FormatFloat(float64(p.X), 'f', -1, 32) +
|
||||
"," + strconv.FormatFloat(float64(p.Y), 'f', -1, 32) + ")"
|
||||
}
|
||||
|
||||
// Pt is shorthand for Point{X: x, Y: y}.
|
||||
func Pt(x, y float32) Point {
|
||||
return Point{X: x, Y: y}
|
||||
}
|
||||
|
||||
// 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}
|
||||
}
|
||||
|
||||
// Div returns the vector p/s.
|
||||
func (p Point) Div(s float32) Point {
|
||||
return Point{X: p.X / s, Y: p.Y / s}
|
||||
}
|
||||
|
||||
// Round returns the integer point closest to p.
|
||||
func (p Point) Round() image.Point {
|
||||
return image.Point{
|
||||
X: int(math.Round(float64(p.X))),
|
||||
Y: int(math.Round(float64(p.Y))),
|
||||
}
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
Package font provides type describing font faces attributes.
|
||||
*/
|
||||
package font
|
||||
|
||||
import (
|
||||
"github.com/go-text/typesetting/font"
|
||||
)
|
||||
|
||||
// A FontFace is a Font and a matching Face.
|
||||
type FontFace struct {
|
||||
Font Font
|
||||
Face Face
|
||||
}
|
||||
|
||||
// Style is the font style.
|
||||
type Style int
|
||||
|
||||
// Weight is a font weight, in CSS units subtracted 400 so the zero value
|
||||
// is normal text weight.
|
||||
type Weight int
|
||||
|
||||
// Font specify a particular typeface variant, style and weight.
|
||||
type Font struct {
|
||||
// Typeface specifies the name(s) of the the font faces to try. See [Typeface]
|
||||
// for details.
|
||||
Typeface Typeface
|
||||
// Style specifies the kind of text style.
|
||||
Style Style
|
||||
// Weight is the text weight.
|
||||
Weight Weight
|
||||
}
|
||||
|
||||
// Face is an opaque handle to a typeface. The concrete implementation depends
|
||||
// upon the kind of font and shaper in use.
|
||||
type Face interface {
|
||||
Face() *font.Face
|
||||
}
|
||||
|
||||
// Typeface identifies a list of font families to attempt to use for displaying
|
||||
// a string. The syntax is a comma-delimited list of family names. In order to
|
||||
// allow for the remote possibility of needing to express a font family name
|
||||
// containing a comma, name entries may be quoted using either single or double
|
||||
// quotes. Within quotes, a literal quotation mark can be expressed by escaping
|
||||
// it with `\`. A literal backslash may be expressed by escaping it with another
|
||||
// `\`.
|
||||
//
|
||||
// Here's an example Typeface:
|
||||
//
|
||||
// Times New Roman, Georgia, serif
|
||||
//
|
||||
// This is equivalent to the above:
|
||||
//
|
||||
// "Times New Roman", 'Georgia', serif
|
||||
//
|
||||
// Here are some valid uses of escape sequences:
|
||||
//
|
||||
// "Contains a literal \" doublequote", 'Literal \' Singlequote', "\\ Literal backslash", '\\ another'
|
||||
//
|
||||
// This syntax has the happy side effect that most CSS "font-family" rules are
|
||||
// valid Typefaces (without the trailing semicolon).
|
||||
//
|
||||
// Generic CSS font families are supported, and are automatically expanded to lists
|
||||
// of known font families with a matching style. The supported generic families are:
|
||||
//
|
||||
// - fantasy
|
||||
// - math
|
||||
// - emoji
|
||||
// - serif
|
||||
// - sans-serif
|
||||
// - cursive
|
||||
// - monospace
|
||||
type Typeface string
|
||||
|
||||
const (
|
||||
Regular Style = iota
|
||||
Italic
|
||||
)
|
||||
|
||||
const (
|
||||
Thin Weight = -300
|
||||
ExtraLight Weight = -200
|
||||
Light Weight = -100
|
||||
Normal Weight = 0
|
||||
Medium Weight = 100
|
||||
SemiBold Weight = 200
|
||||
Bold Weight = 300
|
||||
ExtraBold Weight = 400
|
||||
Black Weight = 500
|
||||
)
|
||||
|
||||
func (s Style) String() string {
|
||||
switch s {
|
||||
case Regular:
|
||||
return "Regular"
|
||||
case Italic:
|
||||
return "Italic"
|
||||
default:
|
||||
panic("invalid Style")
|
||||
}
|
||||
}
|
||||
|
||||
func (w Weight) String() string {
|
||||
switch w {
|
||||
case Thin:
|
||||
return "Thin"
|
||||
case ExtraLight:
|
||||
return "ExtraLight"
|
||||
case Light:
|
||||
return "Light"
|
||||
case Normal:
|
||||
return "Normal"
|
||||
case Medium:
|
||||
return "Medium"
|
||||
case SemiBold:
|
||||
return "SemiBold"
|
||||
case Bold:
|
||||
return "Bold"
|
||||
case ExtraBold:
|
||||
return "ExtraBold"
|
||||
case Black:
|
||||
return "Black"
|
||||
default:
|
||||
panic("invalid Weight")
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// Package gofont exports the Go fonts as a text.Collection.
|
||||
//
|
||||
// See https://blog.golang.org/go-fonts for a description of the
|
||||
// fonts, and the golang.org/x/image/font/gofont packages for the
|
||||
// font data.
|
||||
package gofont
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/image/font/gofont/gobold"
|
||||
"golang.org/x/image/font/gofont/gobolditalic"
|
||||
"golang.org/x/image/font/gofont/goitalic"
|
||||
"golang.org/x/image/font/gofont/gomedium"
|
||||
"golang.org/x/image/font/gofont/gomediumitalic"
|
||||
"golang.org/x/image/font/gofont/gomono"
|
||||
"golang.org/x/image/font/gofont/gomonobold"
|
||||
"golang.org/x/image/font/gofont/gomonobolditalic"
|
||||
"golang.org/x/image/font/gofont/gomonoitalic"
|
||||
"golang.org/x/image/font/gofont/goregular"
|
||||
"golang.org/x/image/font/gofont/gosmallcaps"
|
||||
"golang.org/x/image/font/gofont/gosmallcapsitalic"
|
||||
|
||||
"gioui.org/font"
|
||||
"gioui.org/font/opentype"
|
||||
)
|
||||
|
||||
var (
|
||||
regOnce sync.Once
|
||||
reg []font.FontFace
|
||||
once sync.Once
|
||||
collection []font.FontFace
|
||||
)
|
||||
|
||||
func loadRegular() {
|
||||
regOnce.Do(func() {
|
||||
faces, err := opentype.ParseCollection(goregular.TTF)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to parse font: %v", err))
|
||||
}
|
||||
reg = faces
|
||||
collection = append(collection, reg[0])
|
||||
})
|
||||
}
|
||||
|
||||
// Regular returns a collection of only the Go regular font face.
|
||||
func Regular() []font.FontFace {
|
||||
loadRegular()
|
||||
return reg
|
||||
}
|
||||
|
||||
// Regular returns a collection of all available Go font faces.
|
||||
func Collection() []font.FontFace {
|
||||
loadRegular()
|
||||
once.Do(func() {
|
||||
register(goitalic.TTF)
|
||||
register(gobold.TTF)
|
||||
register(gobolditalic.TTF)
|
||||
register(gomedium.TTF)
|
||||
register(gomediumitalic.TTF)
|
||||
register(gomono.TTF)
|
||||
register(gomonobold.TTF)
|
||||
register(gomonobolditalic.TTF)
|
||||
register(gomonoitalic.TTF)
|
||||
register(gosmallcaps.TTF)
|
||||
register(gosmallcapsitalic.TTF)
|
||||
// Ensure that any outside appends will not reuse the backing store.
|
||||
n := len(collection)
|
||||
collection = collection[:n:n]
|
||||
})
|
||||
return collection
|
||||
}
|
||||
|
||||
func register(ttf []byte) {
|
||||
faces, err := opentype.ParseCollection(ttf)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to parse font: %v", err))
|
||||
}
|
||||
collection = append(collection, faces[0])
|
||||
}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// Package opentype implements text layout and shaping for OpenType
|
||||
// files.
|
||||
//
|
||||
// NOTE: the OpenType specification allows for fonts to include bitmap images
|
||||
// in a variety of formats. In the interest of small binary sizes, the opentype
|
||||
// package only automatically imports the PNG image decoder. If you have a font
|
||||
// with glyphs in JPEG or TIFF formats, register those decoders with the image
|
||||
// package in order to ensure those glyphs are visible in text.
|
||||
package opentype
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
_ "image/png"
|
||||
|
||||
giofont "gioui.org/font"
|
||||
fontapi "github.com/go-text/typesetting/font"
|
||||
"github.com/go-text/typesetting/font/opentype"
|
||||
)
|
||||
|
||||
// Face is a thread-safe representation of a loaded font. For efficiency, applications
|
||||
// should construct a face for any given font file once, reusing it across different
|
||||
// text shapers.
|
||||
type Face struct {
|
||||
face *fontapi.Font
|
||||
font giofont.Font
|
||||
}
|
||||
|
||||
// Parse constructs a Face from source bytes.
|
||||
func Parse(src []byte) (Face, error) {
|
||||
ld, err := opentype.NewLoader(bytes.NewReader(src))
|
||||
if err != nil {
|
||||
return Face{}, err
|
||||
}
|
||||
font, md, err := parseLoader(ld)
|
||||
if err != nil {
|
||||
return Face{}, fmt.Errorf("failed parsing truetype font: %w", err)
|
||||
}
|
||||
return Face{
|
||||
face: font,
|
||||
font: md,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ParseCollection parse an Opentype font file, with support for collections.
|
||||
// Single font files are supported, returning a slice with length 1.
|
||||
// The returned fonts are automatically wrapped in a text.FontFace with
|
||||
// inferred font font.
|
||||
// BUG(whereswaldon): the only Variant that can be detected automatically is
|
||||
// "Mono".
|
||||
func ParseCollection(src []byte) ([]giofont.FontFace, error) {
|
||||
lds, err := opentype.NewLoaders(bytes.NewReader(src))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]giofont.FontFace, len(lds))
|
||||
for i, ld := range lds {
|
||||
face, md, err := parseLoader(ld)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading font %d of collection: %s", i, err)
|
||||
}
|
||||
ff := Face{
|
||||
face: face,
|
||||
font: md,
|
||||
}
|
||||
out[i] = giofont.FontFace{
|
||||
Face: ff,
|
||||
Font: ff.Font(),
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func DescriptionToFont(md fontapi.Description) giofont.Font {
|
||||
return giofont.Font{
|
||||
Typeface: giofont.Typeface(md.Family),
|
||||
Style: gioStyle(md.Aspect.Style),
|
||||
Weight: gioWeight(md.Aspect.Weight),
|
||||
}
|
||||
}
|
||||
|
||||
func FontToDescription(font giofont.Font) fontapi.Description {
|
||||
return fontapi.Description{
|
||||
Family: string(font.Typeface),
|
||||
Aspect: fontapi.Aspect{
|
||||
Style: mdStyle(font.Style),
|
||||
Weight: mdWeight(font.Weight),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// parseLoader parses the contents of the loader into a face and its font.
|
||||
func parseLoader(ld *opentype.Loader) (*fontapi.Font, giofont.Font, error) {
|
||||
ft, err := fontapi.NewFont(ld)
|
||||
if err != nil {
|
||||
return nil, giofont.Font{}, err
|
||||
}
|
||||
data := DescriptionToFont(ft.Describe())
|
||||
return ft, data, nil
|
||||
}
|
||||
|
||||
// Face returns a thread-unsafe wrapper for this Face suitable for use by a single shaper.
|
||||
// Face many be invoked any number of times and is safe so long as each return value is
|
||||
// only used by one goroutine.
|
||||
func (f Face) Face() *fontapi.Face {
|
||||
return fontapi.NewFace(f.face)
|
||||
}
|
||||
|
||||
// FontFace returns a text.Font with populated font metadata for the
|
||||
// font.
|
||||
// BUG(whereswaldon): the only Variant that can be detected automatically is
|
||||
// "Mono".
|
||||
func (f Face) Font() giofont.Font {
|
||||
return f.font
|
||||
}
|
||||
|
||||
func gioStyle(s fontapi.Style) giofont.Style {
|
||||
switch s {
|
||||
case fontapi.StyleItalic:
|
||||
return giofont.Italic
|
||||
case fontapi.StyleNormal:
|
||||
fallthrough
|
||||
default:
|
||||
return giofont.Regular
|
||||
}
|
||||
}
|
||||
|
||||
func mdStyle(g giofont.Style) fontapi.Style {
|
||||
switch g {
|
||||
case giofont.Italic:
|
||||
return fontapi.StyleItalic
|
||||
case giofont.Regular:
|
||||
fallthrough
|
||||
default:
|
||||
return fontapi.StyleNormal
|
||||
}
|
||||
}
|
||||
|
||||
func gioWeight(w fontapi.Weight) giofont.Weight {
|
||||
switch w {
|
||||
case fontapi.WeightThin:
|
||||
return giofont.Thin
|
||||
case fontapi.WeightExtraLight:
|
||||
return giofont.ExtraLight
|
||||
case fontapi.WeightLight:
|
||||
return giofont.Light
|
||||
case fontapi.WeightNormal:
|
||||
return giofont.Normal
|
||||
case fontapi.WeightMedium:
|
||||
return giofont.Medium
|
||||
case fontapi.WeightSemibold:
|
||||
return giofont.SemiBold
|
||||
case fontapi.WeightBold:
|
||||
return giofont.Bold
|
||||
case fontapi.WeightExtraBold:
|
||||
return giofont.ExtraBold
|
||||
case fontapi.WeightBlack:
|
||||
return giofont.Black
|
||||
default:
|
||||
return giofont.Normal
|
||||
}
|
||||
}
|
||||
|
||||
func mdWeight(g giofont.Weight) fontapi.Weight {
|
||||
switch g {
|
||||
case giofont.Thin:
|
||||
return fontapi.WeightThin
|
||||
case giofont.ExtraLight:
|
||||
return fontapi.WeightExtraLight
|
||||
case giofont.Light:
|
||||
return fontapi.WeightLight
|
||||
case giofont.Normal:
|
||||
return fontapi.WeightNormal
|
||||
case giofont.Medium:
|
||||
return fontapi.WeightMedium
|
||||
case giofont.SemiBold:
|
||||
return fontapi.WeightSemibold
|
||||
case giofont.Bold:
|
||||
return fontapi.WeightBold
|
||||
case giofont.ExtraBold:
|
||||
return fontapi.WeightExtraBold
|
||||
case giofont.Black:
|
||||
return fontapi.WeightBlack
|
||||
default:
|
||||
return fontapi.WeightNormal
|
||||
}
|
||||
}
|
||||
+479
@@ -0,0 +1,479 @@
|
||||
// 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 (
|
||||
"image"
|
||||
"math"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/internal/fling"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/input"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
// The duration is somewhat arbitrary.
|
||||
const doubleClickDuration = 200 * time.Millisecond
|
||||
|
||||
// Hover detects the hover gesture for a pointer area.
|
||||
type Hover struct {
|
||||
// entered tracks whether the pointer is inside the gesture.
|
||||
entered bool
|
||||
// pid is the pointer.ID.
|
||||
pid pointer.ID
|
||||
}
|
||||
|
||||
// Add the gesture to detect hovering over the current pointer area.
|
||||
func (h *Hover) Add(ops *op.Ops) {
|
||||
event.Op(ops, h)
|
||||
}
|
||||
|
||||
// Update state and report whether a pointer is inside the area.
|
||||
func (h *Hover) Update(q input.Source) bool {
|
||||
for {
|
||||
ev, ok := q.Event(pointer.Filter{
|
||||
Target: h,
|
||||
Kinds: pointer.Enter | pointer.Leave | pointer.Cancel,
|
||||
})
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
e, ok := ev.(pointer.Event)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch e.Kind {
|
||||
case pointer.Leave, pointer.Cancel:
|
||||
if h.entered && h.pid == e.PointerID {
|
||||
h.entered = false
|
||||
}
|
||||
case pointer.Enter:
|
||||
h.pid = e.PointerID
|
||||
h.entered = true
|
||||
}
|
||||
}
|
||||
return h.entered
|
||||
}
|
||||
|
||||
// Click detects click gestures in the form
|
||||
// of ClickEvents.
|
||||
type Click struct {
|
||||
// clickedAt is the timestamp at which
|
||||
// the last click occurred.
|
||||
clickedAt time.Duration
|
||||
// clicks is incremented if successive clicks
|
||||
// are performed within a fixed duration.
|
||||
clicks int
|
||||
// pressed tracks whether the pointer is pressed.
|
||||
pressed bool
|
||||
// hovered tracks whether the pointer is inside the gesture.
|
||||
hovered bool
|
||||
// entered tracks whether an Enter event has been received.
|
||||
entered bool
|
||||
// pid is the pointer.ID.
|
||||
pid pointer.ID
|
||||
}
|
||||
|
||||
// ClickEvent represent a click action, either a
|
||||
// KindPress for the beginning of a click or a
|
||||
// KindClick for a completed click.
|
||||
type ClickEvent struct {
|
||||
Kind ClickKind
|
||||
Position image.Point
|
||||
Source pointer.Source
|
||||
Modifiers key.Modifiers
|
||||
// NumClicks records successive clicks occurring
|
||||
// within a short duration of each other.
|
||||
NumClicks int
|
||||
}
|
||||
|
||||
type ClickKind uint8
|
||||
|
||||
// Drag detects drag gestures in the form of pointer.Drag events.
|
||||
type Drag struct {
|
||||
dragging bool
|
||||
pressed bool
|
||||
pid pointer.ID
|
||||
start f32.Point
|
||||
}
|
||||
|
||||
// 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
|
||||
estimator fling.Extrapolation
|
||||
flinger fling.Animation
|
||||
pid pointer.ID
|
||||
last int
|
||||
// Leftover scroll.
|
||||
scroll float32
|
||||
}
|
||||
|
||||
type ScrollState uint8
|
||||
|
||||
type Axis uint8
|
||||
|
||||
const (
|
||||
Horizontal Axis = iota
|
||||
Vertical
|
||||
Both
|
||||
)
|
||||
|
||||
const (
|
||||
// KindPress is reported for the first pointer
|
||||
// press.
|
||||
KindPress ClickKind = iota
|
||||
// KindClick is reported when a click action
|
||||
// is complete.
|
||||
KindClick
|
||||
// KindCancel is reported when the gesture is
|
||||
// cancelled.
|
||||
KindCancel
|
||||
)
|
||||
|
||||
const (
|
||||
// StateIdle is the default scroll state.
|
||||
StateIdle ScrollState = iota
|
||||
// StateDragging is reported during drag gestures.
|
||||
StateDragging
|
||||
// StateFlinging is reported when a fling is
|
||||
// in progress.
|
||||
StateFlinging
|
||||
)
|
||||
|
||||
const touchSlop = unit.Dp(3)
|
||||
|
||||
// Add the handler to the operation list to receive click events.
|
||||
func (c *Click) Add(ops *op.Ops) {
|
||||
event.Op(ops, c)
|
||||
}
|
||||
|
||||
// Hovered returns whether a pointer is inside the area.
|
||||
func (c *Click) Hovered() bool {
|
||||
return c.hovered
|
||||
}
|
||||
|
||||
// Pressed returns whether a pointer is pressing.
|
||||
func (c *Click) Pressed() bool {
|
||||
return c.pressed
|
||||
}
|
||||
|
||||
// Update state and return the next click events, if any.
|
||||
func (c *Click) Update(q input.Source) (ClickEvent, bool) {
|
||||
for {
|
||||
evt, ok := q.Event(pointer.Filter{
|
||||
Target: c,
|
||||
Kinds: pointer.Press | pointer.Release | pointer.Enter | pointer.Leave | pointer.Cancel,
|
||||
})
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
e, ok := evt.(pointer.Event)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch e.Kind {
|
||||
case pointer.Release:
|
||||
if !c.pressed || c.pid != e.PointerID {
|
||||
break
|
||||
}
|
||||
c.pressed = false
|
||||
if !c.entered || c.hovered {
|
||||
return ClickEvent{
|
||||
Kind: KindClick,
|
||||
Position: e.Position.Round(),
|
||||
Source: e.Source,
|
||||
Modifiers: e.Modifiers,
|
||||
NumClicks: c.clicks,
|
||||
}, true
|
||||
} else {
|
||||
return ClickEvent{Kind: KindCancel}, true
|
||||
}
|
||||
case pointer.Cancel:
|
||||
wasPressed := c.pressed
|
||||
c.pressed = false
|
||||
c.hovered = false
|
||||
c.entered = false
|
||||
if wasPressed {
|
||||
return ClickEvent{Kind: KindCancel}, true
|
||||
}
|
||||
case pointer.Press:
|
||||
if c.pressed {
|
||||
break
|
||||
}
|
||||
if e.Source == pointer.Mouse && e.Buttons != pointer.ButtonPrimary {
|
||||
break
|
||||
}
|
||||
c.pid = e.PointerID
|
||||
c.pressed = true
|
||||
if e.Time-c.clickedAt < doubleClickDuration {
|
||||
c.clicks++
|
||||
} else {
|
||||
c.clicks = 1
|
||||
}
|
||||
c.clickedAt = e.Time
|
||||
return ClickEvent{Kind: KindPress, Position: e.Position.Round(), Source: e.Source, Modifiers: e.Modifiers, NumClicks: c.clicks}, true
|
||||
case pointer.Leave:
|
||||
if !c.pressed {
|
||||
c.pid = e.PointerID
|
||||
}
|
||||
if c.pid == e.PointerID {
|
||||
c.hovered = false
|
||||
}
|
||||
case pointer.Enter:
|
||||
if !c.pressed {
|
||||
c.pid = e.PointerID
|
||||
}
|
||||
if c.pid == e.PointerID {
|
||||
c.hovered = true
|
||||
c.entered = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return ClickEvent{}, false
|
||||
}
|
||||
|
||||
func (ClickEvent) ImplementsEvent() {}
|
||||
|
||||
// Add the handler to the operation list to receive scroll events.
|
||||
// The bounds variable refers to the scrolling boundaries
|
||||
// as defined in [pointer.Filter].
|
||||
func (s *Scroll) Add(ops *op.Ops) {
|
||||
event.Op(ops, s)
|
||||
}
|
||||
|
||||
// Stop any remaining fling movement.
|
||||
func (s *Scroll) Stop() {
|
||||
s.flinger = fling.Animation{}
|
||||
}
|
||||
|
||||
// Update state and report the scroll distance along axis.
|
||||
func (s *Scroll) Update(cfg unit.Metric, q input.Source, t time.Time, axis Axis, scrollx, scrolly pointer.ScrollRange) int {
|
||||
total := 0
|
||||
f := pointer.Filter{
|
||||
Target: s,
|
||||
Kinds: pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll | pointer.Cancel,
|
||||
ScrollX: scrollx,
|
||||
ScrollY: scrolly,
|
||||
}
|
||||
for {
|
||||
evt, ok := q.Event(f)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
e, ok := evt.(pointer.Event)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch e.Kind {
|
||||
case pointer.Press:
|
||||
if s.dragging {
|
||||
break
|
||||
}
|
||||
// Only scroll on touch drags, or on Android where mice
|
||||
// drags also scroll by convention.
|
||||
if e.Source != pointer.Touch && runtime.GOOS != "android" {
|
||||
break
|
||||
}
|
||||
s.Stop()
|
||||
s.estimator = fling.Extrapolation{}
|
||||
v := s.val(axis, 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.Dp(touchSlop)), fling.Distance; d < -slop || d > slop {
|
||||
s.flinger.Start(cfg, t, fling.Velocity)
|
||||
}
|
||||
fallthrough
|
||||
case pointer.Cancel:
|
||||
s.dragging = false
|
||||
case pointer.Scroll:
|
||||
switch axis {
|
||||
case Horizontal:
|
||||
s.scroll += e.Scroll.X
|
||||
case Vertical:
|
||||
s.scroll += e.Scroll.Y
|
||||
case Both:
|
||||
s.scroll += e.Scroll.X + e.Scroll.Y
|
||||
}
|
||||
iscroll := int(s.scroll)
|
||||
s.scroll -= float32(iscroll)
|
||||
total += iscroll
|
||||
case pointer.Drag:
|
||||
if !s.dragging || s.pid != e.PointerID {
|
||||
continue
|
||||
}
|
||||
val := s.val(axis, 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.Dp(touchSlop)
|
||||
if dist := dist; dist >= slop || -slop >= dist {
|
||||
q.Execute(pointer.GrabCmd{Tag: s, ID: e.PointerID})
|
||||
}
|
||||
} else {
|
||||
s.last = v
|
||||
total += dist
|
||||
}
|
||||
}
|
||||
}
|
||||
total += s.flinger.Tick(t)
|
||||
if s.flinger.Active() {
|
||||
q.Execute(op.InvalidateCmd{})
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
func (s *Scroll) val(axis Axis, p f32.Point) float32 {
|
||||
switch axis {
|
||||
case Horizontal:
|
||||
return p.X
|
||||
case Vertical:
|
||||
return p.Y
|
||||
case Both:
|
||||
return p.X + p.Y
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// Add the handler to the operation list to receive drag events.
|
||||
func (d *Drag) Add(ops *op.Ops) {
|
||||
event.Op(ops, d)
|
||||
}
|
||||
|
||||
// Update state and return the next drag event, if any.
|
||||
func (d *Drag) Update(cfg unit.Metric, q input.Source, axis Axis) (pointer.Event, bool) {
|
||||
for {
|
||||
ev, ok := q.Event(pointer.Filter{
|
||||
Target: d,
|
||||
Kinds: pointer.Press | pointer.Drag | pointer.Release | pointer.Cancel,
|
||||
})
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
e, ok := ev.(pointer.Event)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
switch e.Kind {
|
||||
case pointer.Press:
|
||||
if !(e.Buttons == pointer.ButtonPrimary || e.Source == pointer.Touch) {
|
||||
continue
|
||||
}
|
||||
d.pressed = true
|
||||
if d.dragging {
|
||||
continue
|
||||
}
|
||||
d.dragging = true
|
||||
d.pid = e.PointerID
|
||||
d.start = e.Position
|
||||
case pointer.Drag:
|
||||
if !d.dragging || e.PointerID != d.pid {
|
||||
continue
|
||||
}
|
||||
switch axis {
|
||||
case Horizontal:
|
||||
e.Position.Y = d.start.Y
|
||||
case Vertical:
|
||||
e.Position.X = d.start.X
|
||||
case Both:
|
||||
// Do nothing
|
||||
}
|
||||
if e.Priority < pointer.Grabbed {
|
||||
diff := e.Position.Sub(d.start)
|
||||
slop := cfg.Dp(touchSlop)
|
||||
if diff.X*diff.X+diff.Y*diff.Y > float32(slop*slop) {
|
||||
q.Execute(pointer.GrabCmd{Tag: d, ID: e.PointerID})
|
||||
}
|
||||
}
|
||||
case pointer.Release, pointer.Cancel:
|
||||
d.pressed = false
|
||||
if !d.dragging || e.PointerID != d.pid {
|
||||
continue
|
||||
}
|
||||
d.dragging = false
|
||||
}
|
||||
|
||||
return e, true
|
||||
}
|
||||
|
||||
return pointer.Event{}, false
|
||||
}
|
||||
|
||||
// Dragging reports whether it is currently in use.
|
||||
func (d *Drag) Dragging() bool { return d.dragging }
|
||||
|
||||
// Pressed returns whether a pointer is pressing.
|
||||
func (d *Drag) Pressed() bool { return d.pressed }
|
||||
|
||||
func (a Axis) String() string {
|
||||
switch a {
|
||||
case Horizontal:
|
||||
return "Horizontal"
|
||||
case Vertical:
|
||||
return "Vertical"
|
||||
default:
|
||||
panic("invalid Axis")
|
||||
}
|
||||
}
|
||||
|
||||
func (ct ClickKind) String() string {
|
||||
switch ct {
|
||||
case KindPress:
|
||||
return "KindPress"
|
||||
case KindClick:
|
||||
return "KindClick"
|
||||
case KindCancel:
|
||||
return "KindCancel"
|
||||
default:
|
||||
panic("invalid ClickKind")
|
||||
}
|
||||
}
|
||||
|
||||
func (s ScrollState) String() string {
|
||||
switch s {
|
||||
case StateIdle:
|
||||
return "StateIdle"
|
||||
case StateDragging:
|
||||
return "StateDragging"
|
||||
case StateFlinging:
|
||||
return "StateFlinging"
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package gpu
|
||||
|
||||
import "gioui.org/gpu/internal/driver"
|
||||
|
||||
// An API carries the necessary GPU API specific resources to create a Device.
|
||||
// There is an API type for each supported GPU API such as OpenGL and Direct3D.
|
||||
type API = driver.API
|
||||
|
||||
// A RenderTarget denotes the destination framebuffer for a frame.
|
||||
type RenderTarget = driver.RenderTarget
|
||||
|
||||
// OpenGLRenderTarget is a render target suitable for the OpenGL backend.
|
||||
type OpenGLRenderTarget = driver.OpenGLRenderTarget
|
||||
|
||||
// Direct3D11RenderTarget is a render target suitable for the Direct3D 11 backend.
|
||||
type Direct3D11RenderTarget = driver.Direct3D11RenderTarget
|
||||
|
||||
// MetalRenderTarget is a render target suitable for the Metal backend.
|
||||
type MetalRenderTarget = driver.MetalRenderTarget
|
||||
|
||||
// VulkanRenderTarget is a render target suitable for the Vulkan backend.
|
||||
type VulkanRenderTarget = driver.VulkanRenderTarget
|
||||
|
||||
// OpenGL denotes the OpenGL or OpenGL ES API.
|
||||
type OpenGL = driver.OpenGL
|
||||
|
||||
// Direct3D11 denotes the Direct3D API.
|
||||
type Direct3D11 = driver.Direct3D11
|
||||
|
||||
// Metal denotes the Apple Metal API.
|
||||
type Metal = driver.Metal
|
||||
|
||||
// Vulkan denotes the Vulkan API.
|
||||
type Vulkan = driver.Vulkan
|
||||
|
||||
// ErrDeviceLost is returned from GPU operations when the underlying GPU device
|
||||
// is lost and should be recreated.
|
||||
var ErrDeviceLost = driver.ErrDeviceLost
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package gpu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gioui.org/internal/f32"
|
||||
)
|
||||
|
||||
type textureCacheKey struct {
|
||||
filter byte
|
||||
handle any
|
||||
}
|
||||
|
||||
type textureCache struct {
|
||||
res map[textureCacheKey]resourceCacheValue
|
||||
}
|
||||
|
||||
type resourceCacheValue struct {
|
||||
used bool
|
||||
resource resource
|
||||
}
|
||||
|
||||
// opCache is like a resourceCache but using concrete types and a
|
||||
// freelist instead of two maps to avoid runtime.mapaccess2 calls
|
||||
// since benchmarking showed them as a bottleneck.
|
||||
type opCache struct {
|
||||
// store the index + 1 in cache this key is stored in
|
||||
index map[opKey]int
|
||||
// list of indexes in cache that are free and can be used
|
||||
freelist []int
|
||||
cache []opCacheValue
|
||||
}
|
||||
|
||||
type opCacheValue struct {
|
||||
data pathData
|
||||
|
||||
bounds f32.Rectangle
|
||||
// the fields below are handled by opCache
|
||||
key opKey
|
||||
keep bool
|
||||
}
|
||||
|
||||
func newTextureCache() *textureCache {
|
||||
return &textureCache{
|
||||
res: make(map[textureCacheKey]resourceCacheValue),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *textureCache) get(key textureCacheKey) (resource, bool) {
|
||||
v, exists := r.res[key]
|
||||
if !exists {
|
||||
return nil, false
|
||||
}
|
||||
if !v.used {
|
||||
v.used = true
|
||||
r.res[key] = v
|
||||
}
|
||||
return v.resource, exists
|
||||
}
|
||||
|
||||
func (r *textureCache) put(key textureCacheKey, val resource) {
|
||||
v, exists := r.res[key]
|
||||
if exists && v.used {
|
||||
panic(fmt.Errorf("key exists, %v", key))
|
||||
}
|
||||
v.used = true
|
||||
v.resource = val
|
||||
r.res[key] = v
|
||||
}
|
||||
|
||||
func (r *textureCache) frame() {
|
||||
for k, v := range r.res {
|
||||
if v.used {
|
||||
v.used = false
|
||||
r.res[k] = v
|
||||
} else {
|
||||
delete(r.res, k)
|
||||
v.resource.release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *textureCache) release() {
|
||||
for _, v := range r.res {
|
||||
v.resource.release()
|
||||
}
|
||||
r.res = nil
|
||||
}
|
||||
|
||||
func newOpCache() *opCache {
|
||||
return &opCache{
|
||||
index: make(map[opKey]int),
|
||||
freelist: make([]int, 0),
|
||||
cache: make([]opCacheValue, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *opCache) get(key opKey) (o opCacheValue, exist bool) {
|
||||
v := r.index[key]
|
||||
if v == 0 {
|
||||
return
|
||||
}
|
||||
r.cache[v-1].keep = true
|
||||
return r.cache[v-1], true
|
||||
}
|
||||
|
||||
func (r *opCache) put(key opKey, val opCacheValue) {
|
||||
v := r.index[key]
|
||||
val.keep = true
|
||||
val.key = key
|
||||
if v == 0 {
|
||||
// not in cache
|
||||
i := len(r.cache)
|
||||
if len(r.freelist) > 0 {
|
||||
i = r.freelist[len(r.freelist)-1]
|
||||
r.freelist = r.freelist[:len(r.freelist)-1]
|
||||
r.cache[i] = val
|
||||
} else {
|
||||
r.cache = append(r.cache, val)
|
||||
}
|
||||
r.index[key] = i + 1
|
||||
} else {
|
||||
r.cache[v-1] = val
|
||||
}
|
||||
}
|
||||
|
||||
func (r *opCache) frame() {
|
||||
r.freelist = r.freelist[:0]
|
||||
for i, v := range r.cache {
|
||||
r.cache[i].keep = false
|
||||
if v.keep {
|
||||
continue
|
||||
}
|
||||
if v.data.data != nil {
|
||||
v.data.release()
|
||||
r.cache[i].data.data = nil
|
||||
}
|
||||
delete(r.index, v.key)
|
||||
r.freelist = append(r.freelist, i)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *opCache) release() {
|
||||
for i := range r.cache {
|
||||
r.cache[i].keep = false
|
||||
}
|
||||
r.frame()
|
||||
r.index = nil
|
||||
r.freelist = nil
|
||||
r.cache = nil
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
package gpu
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math"
|
||||
|
||||
"gioui.org/internal/f32"
|
||||
"gioui.org/internal/stroke"
|
||||
)
|
||||
|
||||
type quadSplitter struct {
|
||||
bounds f32.Rectangle
|
||||
contour uint32
|
||||
d *drawOps
|
||||
|
||||
// scratch space used by calls to stroke.SplitCubic
|
||||
scratch []stroke.QuadSegment
|
||||
}
|
||||
|
||||
func encodeQuadTo(data []byte, meta uint32, from, ctrl, to f32.Point) {
|
||||
// inlined code:
|
||||
// encodeVertex(data, meta, 1, -1, from, ctrl, to)
|
||||
// encodeVertex(data[vertStride:], meta, 1, 1, from, ctrl, to)
|
||||
// encodeVertex(data[vertStride*2:], meta, -1, -1, from, ctrl, to)
|
||||
// encodeVertex(data[vertStride*3:], meta, -1, 1, from, ctrl, to)
|
||||
// this code needs to stay in sync with `vertex.encode`.
|
||||
|
||||
bo := binary.LittleEndian
|
||||
data = data[:vertStride*4]
|
||||
|
||||
// encode the main template
|
||||
bo.PutUint32(data[4:8], meta)
|
||||
bo.PutUint32(data[8:12], math.Float32bits(from.X))
|
||||
bo.PutUint32(data[12:16], math.Float32bits(from.Y))
|
||||
bo.PutUint32(data[16:20], math.Float32bits(ctrl.X))
|
||||
bo.PutUint32(data[20:24], math.Float32bits(ctrl.Y))
|
||||
bo.PutUint32(data[24:28], math.Float32bits(to.X))
|
||||
bo.PutUint32(data[28:32], math.Float32bits(to.Y))
|
||||
|
||||
copy(data[vertStride*1:vertStride*2], data[vertStride*0:vertStride*1])
|
||||
copy(data[vertStride*2:vertStride*3], data[vertStride*0:vertStride*1])
|
||||
copy(data[vertStride*3:vertStride*4], data[vertStride*0:vertStride*1])
|
||||
|
||||
bo.PutUint32(data[vertStride*0:vertStride*0+4], math.Float32bits(nwCorner))
|
||||
bo.PutUint32(data[vertStride*1:vertStride*1+4], math.Float32bits(neCorner))
|
||||
bo.PutUint32(data[vertStride*2:vertStride*2+4], math.Float32bits(swCorner))
|
||||
bo.PutUint32(data[vertStride*3:vertStride*3+4], math.Float32bits(seCorner))
|
||||
}
|
||||
|
||||
const (
|
||||
nwCorner = 1*0.5 + 0*0.25
|
||||
neCorner = 1*0.5 + 1*0.25
|
||||
swCorner = 0*0.5 + 0*0.25
|
||||
seCorner = 0*0.5 + 1*0.25
|
||||
)
|
||||
|
||||
func encodeVertex(data []byte, meta uint32, cornerx, cornery int16, from, ctrl, to f32.Point) {
|
||||
var corner float32
|
||||
if cornerx == 1 {
|
||||
corner += .5
|
||||
}
|
||||
if cornery == 1 {
|
||||
corner += .25
|
||||
}
|
||||
v := vertex{
|
||||
Corner: corner,
|
||||
FromX: from.X,
|
||||
FromY: from.Y,
|
||||
CtrlX: ctrl.X,
|
||||
CtrlY: ctrl.Y,
|
||||
ToX: to.X,
|
||||
ToY: to.Y,
|
||||
}
|
||||
v.encode(data, meta)
|
||||
}
|
||||
|
||||
func (qs *quadSplitter) encodeQuadTo(from, ctrl, to f32.Point) {
|
||||
data := qs.d.writeVertCache(vertStride * 4)
|
||||
encodeQuadTo(data, qs.contour, from, ctrl, to)
|
||||
}
|
||||
|
||||
func (qs *quadSplitter) splitAndEncode(quad stroke.QuadSegment) {
|
||||
cbnd := f32.Rectangle{
|
||||
Min: quad.From,
|
||||
Max: quad.To,
|
||||
}.Canon()
|
||||
from, ctrl, to := quad.From, quad.Ctrl, quad.To
|
||||
|
||||
// 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(from)
|
||||
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 := from.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))
|
||||
qs.encodeQuadTo(from, ctrl0, mid)
|
||||
qs.encodeQuadTo(mid, ctrl1, to)
|
||||
if mid.X > cbnd.Max.X {
|
||||
cbnd.Max.X = mid.X
|
||||
}
|
||||
if mid.X < cbnd.Min.X {
|
||||
cbnd.Min.X = mid.X
|
||||
}
|
||||
} else {
|
||||
qs.encodeQuadTo(from, 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)*from.Y + 2*(1-t)*t*ctrl.Y + t*t*to.Y
|
||||
if y > cbnd.Max.Y {
|
||||
cbnd.Max.Y = y
|
||||
}
|
||||
if y < cbnd.Min.Y {
|
||||
cbnd.Min.Y = y
|
||||
}
|
||||
}
|
||||
|
||||
qs.bounds = unionRect(qs.bounds, cbnd)
|
||||
}
|
||||
|
||||
// Union is like f32.Rectangle.Union but ignores empty rectangles.
|
||||
func unionRect(r, s f32.Rectangle) f32.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
|
||||
}
|
||||
+1603
File diff suppressed because it is too large
Load Diff
+5
@@ -0,0 +1,5 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// This file exists so this package builds on non-Windows platforms.
|
||||
|
||||
package d3d11
|
||||
+871
@@ -0,0 +1,871 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package d3d11
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"math"
|
||||
"math/bits"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"gioui.org/gpu/internal/driver"
|
||||
"gioui.org/internal/d3d11"
|
||||
"gioui.org/shader"
|
||||
)
|
||||
|
||||
type Backend struct {
|
||||
dev *d3d11.Device
|
||||
ctx *d3d11.DeviceContext
|
||||
|
||||
// Temporary storage to avoid garbage.
|
||||
clearColor [4]float32
|
||||
viewport d3d11.VIEWPORT
|
||||
|
||||
pipeline *Pipeline
|
||||
vert struct {
|
||||
buffer *Buffer
|
||||
offset int
|
||||
}
|
||||
|
||||
program *Program
|
||||
|
||||
caps driver.Caps
|
||||
|
||||
floatFormat uint32
|
||||
}
|
||||
|
||||
type Pipeline struct {
|
||||
vert *d3d11.VertexShader
|
||||
frag *d3d11.PixelShader
|
||||
layout *d3d11.InputLayout
|
||||
blend *d3d11.BlendState
|
||||
stride int
|
||||
topology driver.Topology
|
||||
}
|
||||
|
||||
type Texture struct {
|
||||
backend *Backend
|
||||
format uint32
|
||||
bindings driver.BufferBinding
|
||||
tex *d3d11.Texture2D
|
||||
sampler *d3d11.SamplerState
|
||||
resView *d3d11.ShaderResourceView
|
||||
uaView *d3d11.UnorderedAccessView
|
||||
renderTarget *d3d11.RenderTargetView
|
||||
|
||||
width int
|
||||
height int
|
||||
mipmap bool
|
||||
foreign bool
|
||||
}
|
||||
|
||||
type VertexShader struct {
|
||||
backend *Backend
|
||||
shader *d3d11.VertexShader
|
||||
src shader.Sources
|
||||
}
|
||||
|
||||
type FragmentShader struct {
|
||||
backend *Backend
|
||||
shader *d3d11.PixelShader
|
||||
}
|
||||
|
||||
type Program struct {
|
||||
backend *Backend
|
||||
shader *d3d11.ComputeShader
|
||||
}
|
||||
|
||||
type Buffer struct {
|
||||
backend *Backend
|
||||
bind uint32
|
||||
buf *d3d11.Buffer
|
||||
resView *d3d11.ShaderResourceView
|
||||
uaView *d3d11.UnorderedAccessView
|
||||
size int
|
||||
immutable bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
driver.NewDirect3D11Device = newDirect3D11Device
|
||||
}
|
||||
|
||||
func detectFloatFormat(dev *d3d11.Device) (uint32, bool) {
|
||||
formats := []uint32{
|
||||
d3d11.DXGI_FORMAT_R16_FLOAT,
|
||||
d3d11.DXGI_FORMAT_R32_FLOAT,
|
||||
d3d11.DXGI_FORMAT_R16G16_FLOAT,
|
||||
d3d11.DXGI_FORMAT_R32G32_FLOAT,
|
||||
// These last two are really wasteful, but c'est la vie.
|
||||
d3d11.DXGI_FORMAT_R16G16B16A16_FLOAT,
|
||||
d3d11.DXGI_FORMAT_R32G32B32A32_FLOAT,
|
||||
}
|
||||
for _, format := range formats {
|
||||
need := uint32(d3d11.FORMAT_SUPPORT_TEXTURE2D | d3d11.FORMAT_SUPPORT_RENDER_TARGET)
|
||||
if support, _ := dev.CheckFormatSupport(format); support&need == need {
|
||||
return format, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func newDirect3D11Device(api driver.Direct3D11) (driver.Device, error) {
|
||||
dev := (*d3d11.Device)(api.Device)
|
||||
b := &Backend{
|
||||
dev: dev,
|
||||
ctx: dev.GetImmediateContext(),
|
||||
caps: driver.Caps{
|
||||
MaxTextureSize: 2048, // 9.1 maximum
|
||||
Features: driver.FeatureSRGB,
|
||||
},
|
||||
}
|
||||
featLvl := dev.GetFeatureLevel()
|
||||
switch {
|
||||
case featLvl < d3d11.FEATURE_LEVEL_9_1:
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(dev), dev.Vtbl.Release)
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(b.ctx), b.ctx.Vtbl.Release)
|
||||
return nil, fmt.Errorf("d3d11: feature level too low: %d", featLvl)
|
||||
case featLvl >= d3d11.FEATURE_LEVEL_11_0:
|
||||
b.caps.MaxTextureSize = 16384
|
||||
b.caps.Features |= driver.FeatureCompute
|
||||
case featLvl >= d3d11.FEATURE_LEVEL_9_3:
|
||||
b.caps.MaxTextureSize = 4096
|
||||
}
|
||||
if fmt, ok := detectFloatFormat(dev); ok {
|
||||
b.floatFormat = fmt
|
||||
b.caps.Features |= driver.FeatureFloatRenderTargets
|
||||
}
|
||||
// Disable backface culling to match OpenGL.
|
||||
state, err := dev.CreateRasterizerState(&d3d11.RASTERIZER_DESC{
|
||||
CullMode: d3d11.CULL_NONE,
|
||||
FillMode: d3d11.FILL_SOLID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer d3d11.IUnknownRelease(unsafe.Pointer(state), state.Vtbl.Release)
|
||||
b.ctx.RSSetState(state)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport image.Point) driver.Texture {
|
||||
var renderTarget *d3d11.RenderTargetView
|
||||
if target != nil {
|
||||
switch t := target.(type) {
|
||||
case driver.Direct3D11RenderTarget:
|
||||
renderTarget = (*d3d11.RenderTargetView)(t.RenderTarget)
|
||||
case *Texture:
|
||||
renderTarget = t.renderTarget
|
||||
default:
|
||||
panic(fmt.Errorf("d3d11: invalid render target type: %T", target))
|
||||
}
|
||||
}
|
||||
b.ctx.OMSetRenderTargets(renderTarget, nil)
|
||||
return &Texture{backend: b, renderTarget: renderTarget, foreign: true}
|
||||
}
|
||||
|
||||
func (b *Backend) CopyTexture(dstTex driver.Texture, dstOrigin image.Point, srcTex driver.Texture, srcRect image.Rectangle) {
|
||||
dst := (*d3d11.Resource)(unsafe.Pointer(dstTex.(*Texture).tex))
|
||||
src := (*d3d11.Resource)(srcTex.(*Texture).tex)
|
||||
b.ctx.CopySubresourceRegion(
|
||||
dst,
|
||||
0, // Destination subresource.
|
||||
uint32(dstOrigin.X), uint32(dstOrigin.Y), 0, // Destination coordinates (x, y, z).
|
||||
src,
|
||||
0, // Source subresource.
|
||||
&d3d11.BOX{
|
||||
Left: uint32(srcRect.Min.X),
|
||||
Top: uint32(srcRect.Min.Y),
|
||||
Right: uint32(srcRect.Max.X),
|
||||
Bottom: uint32(srcRect.Max.Y),
|
||||
Front: 0,
|
||||
Back: 1,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (b *Backend) EndFrame() {
|
||||
}
|
||||
|
||||
func (b *Backend) Caps() driver.Caps {
|
||||
return b.caps
|
||||
}
|
||||
|
||||
func (b *Backend) NewTimer() driver.Timer {
|
||||
panic("timers not supported")
|
||||
}
|
||||
|
||||
func (b *Backend) IsTimeContinuous() bool {
|
||||
panic("timers not supported")
|
||||
}
|
||||
|
||||
func (b *Backend) Release() {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(b.ctx), b.ctx.Vtbl.Release)
|
||||
*b = Backend{}
|
||||
}
|
||||
|
||||
func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, minFilter, magFilter driver.TextureFilter, bindings driver.BufferBinding) (driver.Texture, error) {
|
||||
var d3dfmt uint32
|
||||
switch format {
|
||||
case driver.TextureFormatFloat:
|
||||
d3dfmt = b.floatFormat
|
||||
case driver.TextureFormatSRGBA:
|
||||
d3dfmt = d3d11.DXGI_FORMAT_R8G8B8A8_UNORM_SRGB
|
||||
case driver.TextureFormatRGBA8:
|
||||
d3dfmt = d3d11.DXGI_FORMAT_R8G8B8A8_UNORM
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported texture format %d", format)
|
||||
}
|
||||
bindFlags := convBufferBinding(bindings)
|
||||
miscFlags := uint32(0)
|
||||
mipmap := minFilter == driver.FilterLinearMipmapLinear
|
||||
nmipmaps := 1
|
||||
if mipmap {
|
||||
// Flags required by ID3D11DeviceContext::GenerateMips.
|
||||
bindFlags |= d3d11.BIND_SHADER_RESOURCE | d3d11.BIND_RENDER_TARGET
|
||||
miscFlags |= d3d11.RESOURCE_MISC_GENERATE_MIPS
|
||||
dim := max(height, width)
|
||||
log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1
|
||||
nmipmaps = log2 + 1
|
||||
}
|
||||
tex, err := b.dev.CreateTexture2D(&d3d11.TEXTURE2D_DESC{
|
||||
Width: uint32(width),
|
||||
Height: uint32(height),
|
||||
MipLevels: uint32(nmipmaps),
|
||||
ArraySize: 1,
|
||||
Format: d3dfmt,
|
||||
SampleDesc: d3d11.DXGI_SAMPLE_DESC{
|
||||
Count: 1,
|
||||
Quality: 0,
|
||||
},
|
||||
BindFlags: bindFlags,
|
||||
MiscFlags: miscFlags,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
sampler *d3d11.SamplerState
|
||||
resView *d3d11.ShaderResourceView
|
||||
uaView *d3d11.UnorderedAccessView
|
||||
fbo *d3d11.RenderTargetView
|
||||
)
|
||||
if bindings&driver.BufferBindingTexture != 0 {
|
||||
var filter uint32
|
||||
switch {
|
||||
case minFilter == driver.FilterNearest && magFilter == driver.FilterNearest:
|
||||
filter = d3d11.FILTER_MIN_MAG_MIP_POINT
|
||||
case minFilter == driver.FilterLinear && magFilter == driver.FilterLinear:
|
||||
filter = d3d11.FILTER_MIN_MAG_LINEAR_MIP_POINT
|
||||
case minFilter == driver.FilterLinearMipmapLinear && magFilter == driver.FilterLinear:
|
||||
filter = d3d11.FILTER_MIN_MAG_MIP_LINEAR
|
||||
default:
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(tex), tex.Vtbl.Release)
|
||||
return nil, fmt.Errorf("unsupported texture filter combination %d, %d", minFilter, magFilter)
|
||||
}
|
||||
var err error
|
||||
sampler, err = b.dev.CreateSamplerState(&d3d11.SAMPLER_DESC{
|
||||
Filter: filter,
|
||||
AddressU: d3d11.TEXTURE_ADDRESS_CLAMP,
|
||||
AddressV: d3d11.TEXTURE_ADDRESS_CLAMP,
|
||||
AddressW: d3d11.TEXTURE_ADDRESS_CLAMP,
|
||||
MaxAnisotropy: 1,
|
||||
MinLOD: -math.MaxFloat32,
|
||||
MaxLOD: math.MaxFloat32,
|
||||
})
|
||||
if err != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(tex), tex.Vtbl.Release)
|
||||
return nil, err
|
||||
}
|
||||
resView, err = b.dev.CreateShaderResourceView(
|
||||
(*d3d11.Resource)(unsafe.Pointer(tex)),
|
||||
unsafe.Pointer(&d3d11.SHADER_RESOURCE_VIEW_DESC_TEX2D{
|
||||
SHADER_RESOURCE_VIEW_DESC: d3d11.SHADER_RESOURCE_VIEW_DESC{
|
||||
Format: d3dfmt,
|
||||
ViewDimension: d3d11.SRV_DIMENSION_TEXTURE2D,
|
||||
},
|
||||
Texture2D: d3d11.TEX2D_SRV{
|
||||
MostDetailedMip: 0,
|
||||
MipLevels: ^uint32(0),
|
||||
},
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(tex), tex.Vtbl.Release)
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(sampler), sampler.Vtbl.Release)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if bindings&driver.BufferBindingShaderStorageWrite != 0 {
|
||||
uaView, err = b.dev.CreateUnorderedAccessView(
|
||||
(*d3d11.Resource)(unsafe.Pointer(tex)),
|
||||
unsafe.Pointer(&d3d11.UNORDERED_ACCESS_VIEW_DESC_TEX2D{
|
||||
UNORDERED_ACCESS_VIEW_DESC: d3d11.UNORDERED_ACCESS_VIEW_DESC{
|
||||
Format: d3dfmt,
|
||||
ViewDimension: d3d11.UAV_DIMENSION_TEXTURE2D,
|
||||
},
|
||||
Texture2D: d3d11.TEX2D_UAV{
|
||||
MipSlice: 0,
|
||||
},
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
if sampler != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(sampler), sampler.Vtbl.Release)
|
||||
}
|
||||
if resView != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(resView), resView.Vtbl.Release)
|
||||
}
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(tex), tex.Vtbl.Release)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if bindings&driver.BufferBindingFramebuffer != 0 {
|
||||
resource := (*d3d11.Resource)(unsafe.Pointer(tex))
|
||||
fbo, err = b.dev.CreateRenderTargetView(resource)
|
||||
if err != nil {
|
||||
if uaView != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(uaView), uaView.Vtbl.Release)
|
||||
}
|
||||
if sampler != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(sampler), sampler.Vtbl.Release)
|
||||
}
|
||||
if resView != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(resView), resView.Vtbl.Release)
|
||||
}
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(tex), tex.Vtbl.Release)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &Texture{backend: b, format: d3dfmt, tex: tex, sampler: sampler, resView: resView, uaView: uaView, renderTarget: fbo, bindings: bindings, width: width, height: height, mipmap: mipmap}, nil
|
||||
}
|
||||
|
||||
func (b *Backend) newInputLayout(vertexShader shader.Sources, layout []driver.InputDesc) (*d3d11.InputLayout, error) {
|
||||
if len(vertexShader.Inputs) != len(layout) {
|
||||
return nil, fmt.Errorf("NewInputLayout: got %d inputs, expected %d", len(layout), len(vertexShader.Inputs))
|
||||
}
|
||||
descs := make([]d3d11.INPUT_ELEMENT_DESC, len(layout))
|
||||
for i, l := range layout {
|
||||
inp := vertexShader.Inputs[i]
|
||||
cname, err := windows.BytePtrFromString(inp.Semantic)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var format uint32
|
||||
switch l.Type {
|
||||
case shader.DataTypeFloat:
|
||||
switch l.Size {
|
||||
case 1:
|
||||
format = d3d11.DXGI_FORMAT_R32_FLOAT
|
||||
case 2:
|
||||
format = d3d11.DXGI_FORMAT_R32G32_FLOAT
|
||||
case 3:
|
||||
format = d3d11.DXGI_FORMAT_R32G32B32_FLOAT
|
||||
case 4:
|
||||
format = d3d11.DXGI_FORMAT_R32G32B32A32_FLOAT
|
||||
default:
|
||||
panic("unsupported data size")
|
||||
}
|
||||
case shader.DataTypeShort:
|
||||
switch l.Size {
|
||||
case 1:
|
||||
format = d3d11.DXGI_FORMAT_R16_SINT
|
||||
case 2:
|
||||
format = d3d11.DXGI_FORMAT_R16G16_SINT
|
||||
default:
|
||||
panic("unsupported data size")
|
||||
}
|
||||
default:
|
||||
panic("unsupported data type")
|
||||
}
|
||||
descs[i] = d3d11.INPUT_ELEMENT_DESC{
|
||||
SemanticName: cname,
|
||||
SemanticIndex: uint32(inp.SemanticIndex),
|
||||
Format: format,
|
||||
AlignedByteOffset: uint32(l.Offset),
|
||||
}
|
||||
}
|
||||
return b.dev.CreateInputLayout(descs, []byte(vertexShader.DXBC))
|
||||
}
|
||||
|
||||
func (b *Backend) NewBuffer(typ driver.BufferBinding, size int) (driver.Buffer, error) {
|
||||
return b.newBuffer(typ, size, nil, false)
|
||||
}
|
||||
|
||||
func (b *Backend) NewImmutableBuffer(typ driver.BufferBinding, data []byte) (driver.Buffer, error) {
|
||||
return b.newBuffer(typ, len(data), data, true)
|
||||
}
|
||||
|
||||
func (b *Backend) newBuffer(typ driver.BufferBinding, size int, data []byte, immutable bool) (*Buffer, error) {
|
||||
if typ&driver.BufferBindingUniforms != 0 {
|
||||
if typ != driver.BufferBindingUniforms {
|
||||
return nil, errors.New("uniform buffers cannot have other bindings")
|
||||
}
|
||||
if size%16 != 0 {
|
||||
return nil, fmt.Errorf("constant buffer size is %d, expected a multiple of 16", size)
|
||||
}
|
||||
}
|
||||
bind := convBufferBinding(typ)
|
||||
var usage, miscFlags, cpuFlags uint32
|
||||
if immutable {
|
||||
usage = d3d11.USAGE_IMMUTABLE
|
||||
}
|
||||
if typ&driver.BufferBindingShaderStorageWrite != 0 {
|
||||
cpuFlags = d3d11.CPU_ACCESS_READ
|
||||
}
|
||||
if typ&(driver.BufferBindingShaderStorageRead|driver.BufferBindingShaderStorageWrite) != 0 {
|
||||
miscFlags |= d3d11.RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS
|
||||
}
|
||||
buf, err := b.dev.CreateBuffer(&d3d11.BUFFER_DESC{
|
||||
ByteWidth: uint32(size),
|
||||
Usage: usage,
|
||||
BindFlags: bind,
|
||||
CPUAccessFlags: cpuFlags,
|
||||
MiscFlags: miscFlags,
|
||||
}, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
resView *d3d11.ShaderResourceView
|
||||
uaView *d3d11.UnorderedAccessView
|
||||
)
|
||||
if typ&driver.BufferBindingShaderStorageWrite != 0 {
|
||||
uaView, err = b.dev.CreateUnorderedAccessView(
|
||||
(*d3d11.Resource)(unsafe.Pointer(buf)),
|
||||
unsafe.Pointer(&d3d11.UNORDERED_ACCESS_VIEW_DESC_BUFFER{
|
||||
UNORDERED_ACCESS_VIEW_DESC: d3d11.UNORDERED_ACCESS_VIEW_DESC{
|
||||
Format: d3d11.DXGI_FORMAT_R32_TYPELESS,
|
||||
ViewDimension: d3d11.UAV_DIMENSION_BUFFER,
|
||||
},
|
||||
Buffer: d3d11.BUFFER_UAV{
|
||||
FirstElement: 0,
|
||||
NumElements: uint32(size / 4),
|
||||
Flags: d3d11.BUFFER_UAV_FLAG_RAW,
|
||||
},
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(buf), buf.Vtbl.Release)
|
||||
return nil, err
|
||||
}
|
||||
} else if typ&driver.BufferBindingShaderStorageRead != 0 {
|
||||
resView, err = b.dev.CreateShaderResourceView(
|
||||
(*d3d11.Resource)(unsafe.Pointer(buf)),
|
||||
unsafe.Pointer(&d3d11.SHADER_RESOURCE_VIEW_DESC_BUFFEREX{
|
||||
SHADER_RESOURCE_VIEW_DESC: d3d11.SHADER_RESOURCE_VIEW_DESC{
|
||||
Format: d3d11.DXGI_FORMAT_R32_TYPELESS,
|
||||
ViewDimension: d3d11.SRV_DIMENSION_BUFFEREX,
|
||||
},
|
||||
Buffer: d3d11.BUFFEREX_SRV{
|
||||
FirstElement: 0,
|
||||
NumElements: uint32(size / 4),
|
||||
Flags: d3d11.BUFFEREX_SRV_FLAG_RAW,
|
||||
},
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(buf), buf.Vtbl.Release)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &Buffer{backend: b, buf: buf, bind: bind, size: size, resView: resView, uaView: uaView, immutable: immutable}, nil
|
||||
}
|
||||
|
||||
func (b *Backend) NewComputeProgram(shader shader.Sources) (driver.Program, error) {
|
||||
cs, err := b.dev.CreateComputeShader([]byte(shader.DXBC))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Program{backend: b, shader: cs}, nil
|
||||
}
|
||||
|
||||
func (b *Backend) NewPipeline(desc driver.PipelineDesc) (driver.Pipeline, error) {
|
||||
vsh := desc.VertexShader.(*VertexShader)
|
||||
fsh := desc.FragmentShader.(*FragmentShader)
|
||||
blend, err := b.newBlendState(desc.BlendDesc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var layout *d3d11.InputLayout
|
||||
if l := desc.VertexLayout; l.Stride > 0 {
|
||||
var err error
|
||||
layout, err = b.newInputLayout(vsh.src, l.Inputs)
|
||||
if err != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(blend), blend.Vtbl.AddRef)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Retain shaders.
|
||||
vshRef := vsh.shader
|
||||
fshRef := fsh.shader
|
||||
d3d11.IUnknownAddRef(unsafe.Pointer(vshRef), vshRef.Vtbl.AddRef)
|
||||
d3d11.IUnknownAddRef(unsafe.Pointer(fshRef), fshRef.Vtbl.AddRef)
|
||||
|
||||
return &Pipeline{
|
||||
vert: vshRef,
|
||||
frag: fshRef,
|
||||
layout: layout,
|
||||
stride: desc.VertexLayout.Stride,
|
||||
blend: blend,
|
||||
topology: desc.Topology,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *Backend) newBlendState(desc driver.BlendDesc) (*d3d11.BlendState, error) {
|
||||
var d3ddesc d3d11.BLEND_DESC
|
||||
t0 := &d3ddesc.RenderTarget[0]
|
||||
t0.RenderTargetWriteMask = d3d11.COLOR_WRITE_ENABLE_ALL
|
||||
t0.BlendOp = d3d11.BLEND_OP_ADD
|
||||
t0.BlendOpAlpha = d3d11.BLEND_OP_ADD
|
||||
if desc.Enable {
|
||||
t0.BlendEnable = 1
|
||||
}
|
||||
scol, salpha := toBlendFactor(desc.SrcFactor)
|
||||
dcol, dalpha := toBlendFactor(desc.DstFactor)
|
||||
t0.SrcBlend = scol
|
||||
t0.SrcBlendAlpha = salpha
|
||||
t0.DestBlend = dcol
|
||||
t0.DestBlendAlpha = dalpha
|
||||
return b.dev.CreateBlendState(&d3ddesc)
|
||||
}
|
||||
|
||||
func (b *Backend) NewVertexShader(src shader.Sources) (driver.VertexShader, error) {
|
||||
vs, err := b.dev.CreateVertexShader([]byte(src.DXBC))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &VertexShader{b, vs, src}, nil
|
||||
}
|
||||
|
||||
func (b *Backend) NewFragmentShader(src shader.Sources) (driver.FragmentShader, error) {
|
||||
fs, err := b.dev.CreatePixelShader([]byte(src.DXBC))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &FragmentShader{b, fs}, nil
|
||||
}
|
||||
|
||||
func (b *Backend) Viewport(x, y, width, height int) {
|
||||
b.viewport = d3d11.VIEWPORT{
|
||||
TopLeftX: float32(x),
|
||||
TopLeftY: float32(y),
|
||||
Width: float32(width),
|
||||
Height: float32(height),
|
||||
MinDepth: 0.0,
|
||||
MaxDepth: 1.0,
|
||||
}
|
||||
b.ctx.RSSetViewports(&b.viewport)
|
||||
}
|
||||
|
||||
func (b *Backend) DrawArrays(off, count int) {
|
||||
b.prepareDraw()
|
||||
b.ctx.Draw(uint32(count), uint32(off))
|
||||
}
|
||||
|
||||
func (b *Backend) DrawElements(off, count int) {
|
||||
b.prepareDraw()
|
||||
b.ctx.DrawIndexed(uint32(count), uint32(off), 0)
|
||||
}
|
||||
|
||||
func (b *Backend) prepareDraw() {
|
||||
p := b.pipeline
|
||||
if p == nil {
|
||||
return
|
||||
}
|
||||
b.ctx.VSSetShader(p.vert)
|
||||
b.ctx.PSSetShader(p.frag)
|
||||
b.ctx.IASetInputLayout(p.layout)
|
||||
b.ctx.OMSetBlendState(p.blend, nil, 0xffffffff)
|
||||
if b.vert.buffer != nil {
|
||||
b.ctx.IASetVertexBuffers(b.vert.buffer.buf, uint32(p.stride), uint32(b.vert.offset))
|
||||
}
|
||||
var topology uint32
|
||||
switch p.topology {
|
||||
case driver.TopologyTriangles:
|
||||
topology = d3d11.PRIMITIVE_TOPOLOGY_TRIANGLELIST
|
||||
case driver.TopologyTriangleStrip:
|
||||
topology = d3d11.PRIMITIVE_TOPOLOGY_TRIANGLESTRIP
|
||||
default:
|
||||
panic("unsupported draw mode")
|
||||
}
|
||||
b.ctx.IASetPrimitiveTopology(topology)
|
||||
}
|
||||
|
||||
func (b *Backend) BindImageTexture(unit int, tex driver.Texture) {
|
||||
t := tex.(*Texture)
|
||||
if t.uaView != nil {
|
||||
b.ctx.CSSetUnorderedAccessViews(uint32(unit), t.uaView)
|
||||
} else {
|
||||
b.ctx.CSSetShaderResources(uint32(unit), t.resView)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Backend) DispatchCompute(x, y, z int) {
|
||||
b.ctx.CSSetShader(b.program.shader)
|
||||
b.ctx.Dispatch(uint32(x), uint32(y), uint32(z))
|
||||
}
|
||||
|
||||
func (t *Texture) Upload(offset, size image.Point, pixels []byte, stride int) {
|
||||
if stride == 0 {
|
||||
stride = size.X * 4
|
||||
}
|
||||
dst := &d3d11.BOX{
|
||||
Left: uint32(offset.X),
|
||||
Top: uint32(offset.Y),
|
||||
Right: uint32(offset.X + size.X),
|
||||
Bottom: uint32(offset.Y + size.Y),
|
||||
Front: 0,
|
||||
Back: 1,
|
||||
}
|
||||
res := (*d3d11.Resource)(unsafe.Pointer(t.tex))
|
||||
t.backend.ctx.UpdateSubresource(res, dst, uint32(stride), uint32(len(pixels)), pixels)
|
||||
if t.mipmap {
|
||||
t.backend.ctx.GenerateMips(t.resView)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Texture) Release() {
|
||||
if t.foreign {
|
||||
panic("texture not created by NewTexture")
|
||||
}
|
||||
if t.renderTarget != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(t.renderTarget), t.renderTarget.Vtbl.Release)
|
||||
}
|
||||
if t.sampler != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(t.sampler), t.sampler.Vtbl.Release)
|
||||
}
|
||||
if t.resView != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(t.resView), t.resView.Vtbl.Release)
|
||||
}
|
||||
if t.uaView != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(t.uaView), t.uaView.Vtbl.Release)
|
||||
}
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(t.tex), t.tex.Vtbl.Release)
|
||||
*t = Texture{}
|
||||
}
|
||||
|
||||
func (b *Backend) PrepareTexture(tex driver.Texture) {}
|
||||
|
||||
func (b *Backend) BindTexture(unit int, tex driver.Texture) {
|
||||
t := tex.(*Texture)
|
||||
b.ctx.PSSetSamplers(uint32(unit), t.sampler)
|
||||
b.ctx.PSSetShaderResources(uint32(unit), t.resView)
|
||||
}
|
||||
|
||||
func (b *Backend) BindPipeline(pipe driver.Pipeline) {
|
||||
b.pipeline = pipe.(*Pipeline)
|
||||
}
|
||||
|
||||
func (b *Backend) BindProgram(prog driver.Program) {
|
||||
b.program = prog.(*Program)
|
||||
}
|
||||
|
||||
func (s *VertexShader) Release() {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(s.shader), s.shader.Vtbl.Release)
|
||||
*s = VertexShader{}
|
||||
}
|
||||
|
||||
func (s *FragmentShader) Release() {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(s.shader), s.shader.Vtbl.Release)
|
||||
*s = FragmentShader{}
|
||||
}
|
||||
|
||||
func (s *Program) Release() {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(s.shader), s.shader.Vtbl.Release)
|
||||
*s = Program{}
|
||||
}
|
||||
|
||||
func (p *Pipeline) Release() {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(p.vert), p.vert.Vtbl.Release)
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(p.frag), p.frag.Vtbl.Release)
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(p.blend), p.blend.Vtbl.Release)
|
||||
if l := p.layout; l != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(l), l.Vtbl.Release)
|
||||
}
|
||||
*p = Pipeline{}
|
||||
}
|
||||
|
||||
func (b *Backend) BindStorageBuffer(binding int, buffer driver.Buffer) {
|
||||
buf := buffer.(*Buffer)
|
||||
if buf.resView != nil {
|
||||
b.ctx.CSSetShaderResources(uint32(binding), buf.resView)
|
||||
} else {
|
||||
b.ctx.CSSetUnorderedAccessViews(uint32(binding), buf.uaView)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Backend) BindUniforms(buffer driver.Buffer) {
|
||||
buf := buffer.(*Buffer)
|
||||
b.ctx.VSSetConstantBuffers(buf.buf)
|
||||
b.ctx.PSSetConstantBuffers(buf.buf)
|
||||
}
|
||||
|
||||
func (b *Backend) BindVertexBuffer(buf driver.Buffer, offset int) {
|
||||
b.vert.buffer = buf.(*Buffer)
|
||||
b.vert.offset = offset
|
||||
}
|
||||
|
||||
func (b *Backend) BindIndexBuffer(buf driver.Buffer) {
|
||||
b.ctx.IASetIndexBuffer(buf.(*Buffer).buf, d3d11.DXGI_FORMAT_R16_UINT, 0)
|
||||
}
|
||||
|
||||
func (b *Buffer) Download(dst []byte) error {
|
||||
res := (*d3d11.Resource)(unsafe.Pointer(b.buf))
|
||||
resMap, err := b.backend.ctx.Map(res, 0, d3d11.MAP_READ, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("d3d11: %v", err)
|
||||
}
|
||||
defer b.backend.ctx.Unmap(res, 0)
|
||||
data := sliceOf(resMap.PData, len(dst))
|
||||
copy(dst, data)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Buffer) Upload(data []byte) {
|
||||
var dst *d3d11.BOX
|
||||
if len(data) < b.size {
|
||||
dst = &d3d11.BOX{
|
||||
Left: 0,
|
||||
Right: uint32(len(data)),
|
||||
Top: 0,
|
||||
Bottom: 1,
|
||||
Front: 0,
|
||||
Back: 1,
|
||||
}
|
||||
}
|
||||
b.backend.ctx.UpdateSubresource((*d3d11.Resource)(unsafe.Pointer(b.buf)), dst, 0, 0, data)
|
||||
}
|
||||
|
||||
func (b *Buffer) Release() {
|
||||
if b.resView != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(b.resView), b.resView.Vtbl.Release)
|
||||
}
|
||||
if b.uaView != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(b.uaView), b.uaView.Vtbl.Release)
|
||||
}
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(b.buf), b.buf.Vtbl.Release)
|
||||
*b = Buffer{}
|
||||
}
|
||||
|
||||
func (t *Texture) ReadPixels(src image.Rectangle, pixels []byte, stride int) error {
|
||||
w, h := src.Dx(), src.Dy()
|
||||
tex, err := t.backend.dev.CreateTexture2D(&d3d11.TEXTURE2D_DESC{
|
||||
Width: uint32(w),
|
||||
Height: uint32(h),
|
||||
MipLevels: 1,
|
||||
ArraySize: 1,
|
||||
Format: t.format,
|
||||
SampleDesc: d3d11.DXGI_SAMPLE_DESC{
|
||||
Count: 1,
|
||||
Quality: 0,
|
||||
},
|
||||
Usage: d3d11.USAGE_STAGING,
|
||||
CPUAccessFlags: d3d11.CPU_ACCESS_READ,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("ReadPixels: %v", err)
|
||||
}
|
||||
defer d3d11.IUnknownRelease(unsafe.Pointer(tex), tex.Vtbl.Release)
|
||||
res := (*d3d11.Resource)(unsafe.Pointer(tex))
|
||||
t.backend.ctx.CopySubresourceRegion(
|
||||
res,
|
||||
0, // Destination subresource.
|
||||
0, 0, 0, // Destination coordinates (x, y, z).
|
||||
(*d3d11.Resource)(t.tex),
|
||||
0, // Source subresource.
|
||||
&d3d11.BOX{
|
||||
Left: uint32(src.Min.X),
|
||||
Top: uint32(src.Min.Y),
|
||||
Right: uint32(src.Max.X),
|
||||
Bottom: uint32(src.Max.Y),
|
||||
Front: 0,
|
||||
Back: 1,
|
||||
},
|
||||
)
|
||||
resMap, err := t.backend.ctx.Map(res, 0, d3d11.MAP_READ, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ReadPixels: %v", err)
|
||||
}
|
||||
defer t.backend.ctx.Unmap(res, 0)
|
||||
srcPitch := stride
|
||||
dstPitch := int(resMap.RowPitch)
|
||||
mapSize := dstPitch * h
|
||||
data := sliceOf(resMap.PData, mapSize)
|
||||
width := w * 4
|
||||
for r := range h {
|
||||
pixels := pixels[r*srcPitch:]
|
||||
copy(pixels[:width], data[r*dstPitch:])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Backend) BeginCompute() {
|
||||
}
|
||||
|
||||
func (b *Backend) EndCompute() {
|
||||
}
|
||||
|
||||
func (b *Backend) BeginRenderPass(tex driver.Texture, d driver.LoadDesc) {
|
||||
t := tex.(*Texture)
|
||||
b.ctx.OMSetRenderTargets(t.renderTarget, nil)
|
||||
if d.Action == driver.LoadActionClear {
|
||||
c := d.ClearColor
|
||||
b.clearColor = [4]float32{c.R, c.G, c.B, c.A}
|
||||
b.ctx.ClearRenderTargetView(t.renderTarget, &b.clearColor)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Backend) EndRenderPass() {
|
||||
}
|
||||
|
||||
func (f *Texture) ImplementsRenderTarget() {}
|
||||
|
||||
func convBufferBinding(typ driver.BufferBinding) uint32 {
|
||||
var bindings uint32
|
||||
if typ&driver.BufferBindingVertices != 0 {
|
||||
bindings |= d3d11.BIND_VERTEX_BUFFER
|
||||
}
|
||||
if typ&driver.BufferBindingIndices != 0 {
|
||||
bindings |= d3d11.BIND_INDEX_BUFFER
|
||||
}
|
||||
if typ&driver.BufferBindingUniforms != 0 {
|
||||
bindings |= d3d11.BIND_CONSTANT_BUFFER
|
||||
}
|
||||
if typ&driver.BufferBindingTexture != 0 {
|
||||
bindings |= d3d11.BIND_SHADER_RESOURCE
|
||||
}
|
||||
if typ&driver.BufferBindingFramebuffer != 0 {
|
||||
bindings |= d3d11.BIND_RENDER_TARGET
|
||||
}
|
||||
if typ&driver.BufferBindingShaderStorageWrite != 0 {
|
||||
bindings |= d3d11.BIND_UNORDERED_ACCESS
|
||||
} else if typ&driver.BufferBindingShaderStorageRead != 0 {
|
||||
bindings |= d3d11.BIND_SHADER_RESOURCE
|
||||
}
|
||||
return bindings
|
||||
}
|
||||
|
||||
func toBlendFactor(f driver.BlendFactor) (uint32, uint32) {
|
||||
switch f {
|
||||
case driver.BlendFactorOne:
|
||||
return d3d11.BLEND_ONE, d3d11.BLEND_ONE
|
||||
case driver.BlendFactorOneMinusSrcAlpha:
|
||||
return d3d11.BLEND_INV_SRC_ALPHA, d3d11.BLEND_INV_SRC_ALPHA
|
||||
case driver.BlendFactorZero:
|
||||
return d3d11.BLEND_ZERO, d3d11.BLEND_ZERO
|
||||
case driver.BlendFactorDstColor:
|
||||
return d3d11.BLEND_DEST_COLOR, d3d11.BLEND_DEST_ALPHA
|
||||
default:
|
||||
panic("unsupported blend source factor")
|
||||
}
|
||||
}
|
||||
|
||||
// sliceOf returns a slice from a (native) pointer.
|
||||
func sliceOf(ptr uintptr, cap int) []byte {
|
||||
return unsafe.Slice((*byte)(unsafe.Pointer(ptr)), cap)
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/internal/gl"
|
||||
)
|
||||
|
||||
// See gpu/api.go for documentation for the API types.
|
||||
|
||||
type API interface {
|
||||
implementsAPI()
|
||||
}
|
||||
|
||||
type RenderTarget interface {
|
||||
ImplementsRenderTarget()
|
||||
}
|
||||
|
||||
type OpenGLRenderTarget gl.Framebuffer
|
||||
|
||||
type Direct3D11RenderTarget struct {
|
||||
// RenderTarget is a *ID3D11RenderTargetView.
|
||||
RenderTarget unsafe.Pointer
|
||||
}
|
||||
|
||||
type MetalRenderTarget struct {
|
||||
// Texture is a MTLTexture.
|
||||
Texture uintptr
|
||||
}
|
||||
|
||||
type VulkanRenderTarget struct {
|
||||
// WaitSem is a VkSemaphore that must signaled before accessing Framebuffer.
|
||||
WaitSem uint64
|
||||
// SignalSem is a VkSemaphore that signal access to Framebuffer is complete.
|
||||
SignalSem uint64
|
||||
// Fence is a VkFence that is set when all commands to Framebuffer has completed.
|
||||
Fence uint64
|
||||
// Image is the VkImage to render into.
|
||||
Image uint64
|
||||
// Framebuffer is a VkFramebuffer for Image.
|
||||
Framebuffer uint64
|
||||
}
|
||||
|
||||
type OpenGL struct {
|
||||
// ES forces the use of ANGLE OpenGL ES libraries on macOS. It is
|
||||
// ignored on all other platforms.
|
||||
ES bool
|
||||
// Context contains the WebGL context for WebAssembly platforms. It is
|
||||
// empty for all other platforms; an OpenGL context is assumed current when
|
||||
// calling NewDevice.
|
||||
Context gl.Context
|
||||
// Shared instructs users of the context to restore the GL state after
|
||||
// use.
|
||||
Shared bool
|
||||
}
|
||||
|
||||
type Direct3D11 struct {
|
||||
// Device contains a *ID3D11Device.
|
||||
Device unsafe.Pointer
|
||||
}
|
||||
|
||||
type Metal struct {
|
||||
// Device is an MTLDevice.
|
||||
Device uintptr
|
||||
// Queue is a MTLCommandQueue.
|
||||
Queue uintptr
|
||||
// PixelFormat is the MTLPixelFormat of the default framebuffer.
|
||||
PixelFormat int
|
||||
}
|
||||
|
||||
type Vulkan struct {
|
||||
// PhysDevice is a VkPhysicalDevice.
|
||||
PhysDevice unsafe.Pointer
|
||||
// Device is a VkDevice.
|
||||
Device unsafe.Pointer
|
||||
// QueueFamily is the queue familily index of the queue.
|
||||
QueueFamily int
|
||||
// QueueIndex is the logical queue index of the queue.
|
||||
QueueIndex int
|
||||
// Format is a VkFormat that matches render targets.
|
||||
Format int
|
||||
}
|
||||
|
||||
// API specific device constructors.
|
||||
var (
|
||||
NewOpenGLDevice func(api OpenGL) (Device, error)
|
||||
NewDirect3D11Device func(api Direct3D11) (Device, error)
|
||||
NewMetalDevice func(api Metal) (Device, error)
|
||||
NewVulkanDevice func(api Vulkan) (Device, error)
|
||||
)
|
||||
|
||||
// NewDevice creates a new Device given the api.
|
||||
//
|
||||
// Note that the device does not assume ownership of the resources contained in
|
||||
// api; the caller must ensure the resources are valid until the device is
|
||||
// released.
|
||||
func NewDevice(api API) (Device, error) {
|
||||
switch api := api.(type) {
|
||||
case OpenGL:
|
||||
if NewOpenGLDevice != nil {
|
||||
return NewOpenGLDevice(api)
|
||||
}
|
||||
case Direct3D11:
|
||||
if NewDirect3D11Device != nil {
|
||||
return NewDirect3D11Device(api)
|
||||
}
|
||||
case Metal:
|
||||
if NewMetalDevice != nil {
|
||||
return NewMetalDevice(api)
|
||||
}
|
||||
case Vulkan:
|
||||
if NewVulkanDevice != nil {
|
||||
return NewVulkanDevice(api)
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("driver: no driver available for the API %T", api)
|
||||
}
|
||||
|
||||
func (OpenGL) implementsAPI() {}
|
||||
func (Direct3D11) implementsAPI() {}
|
||||
func (Metal) implementsAPI() {}
|
||||
func (Vulkan) implementsAPI() {}
|
||||
func (OpenGLRenderTarget) ImplementsRenderTarget() {}
|
||||
func (Direct3D11RenderTarget) ImplementsRenderTarget() {}
|
||||
func (MetalRenderTarget) ImplementsRenderTarget() {}
|
||||
func (VulkanRenderTarget) ImplementsRenderTarget() {}
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image"
|
||||
"time"
|
||||
|
||||
"gioui.org/internal/f32color"
|
||||
"gioui.org/shader"
|
||||
)
|
||||
|
||||
// Device represents the abstraction of underlying GPU
|
||||
// APIs such as OpenGL, Direct3D useful for rendering Gio
|
||||
// operations.
|
||||
type Device interface {
|
||||
BeginFrame(target RenderTarget, clear bool, viewport image.Point) Texture
|
||||
EndFrame()
|
||||
Caps() Caps
|
||||
NewTimer() Timer
|
||||
// IsContinuousTime reports whether all timer measurements
|
||||
// are valid at the point of call.
|
||||
IsTimeContinuous() bool
|
||||
NewTexture(format TextureFormat, width, height int, minFilter, magFilter TextureFilter, bindings BufferBinding) (Texture, error)
|
||||
NewImmutableBuffer(typ BufferBinding, data []byte) (Buffer, error)
|
||||
NewBuffer(typ BufferBinding, size int) (Buffer, error)
|
||||
NewComputeProgram(shader shader.Sources) (Program, error)
|
||||
NewVertexShader(src shader.Sources) (VertexShader, error)
|
||||
NewFragmentShader(src shader.Sources) (FragmentShader, error)
|
||||
NewPipeline(desc PipelineDesc) (Pipeline, error)
|
||||
|
||||
Viewport(x, y, width, height int)
|
||||
DrawArrays(off, count int)
|
||||
DrawElements(off, count int)
|
||||
|
||||
BeginRenderPass(t Texture, desc LoadDesc)
|
||||
EndRenderPass()
|
||||
PrepareTexture(t Texture)
|
||||
BindProgram(p Program)
|
||||
BindPipeline(p Pipeline)
|
||||
BindTexture(unit int, t Texture)
|
||||
BindVertexBuffer(b Buffer, offset int)
|
||||
BindIndexBuffer(b Buffer)
|
||||
BindImageTexture(unit int, texture Texture)
|
||||
BindUniforms(buf Buffer)
|
||||
BindStorageBuffer(binding int, buf Buffer)
|
||||
|
||||
BeginCompute()
|
||||
EndCompute()
|
||||
CopyTexture(dst Texture, dstOrigin image.Point, src Texture, srcRect image.Rectangle)
|
||||
DispatchCompute(x, y, z int)
|
||||
|
||||
Release()
|
||||
}
|
||||
|
||||
var ErrDeviceLost = errors.New("GPU device lost")
|
||||
|
||||
type LoadDesc struct {
|
||||
Action LoadAction
|
||||
ClearColor f32color.RGBA
|
||||
}
|
||||
|
||||
type Pipeline interface {
|
||||
Release()
|
||||
}
|
||||
|
||||
type PipelineDesc struct {
|
||||
VertexShader VertexShader
|
||||
FragmentShader FragmentShader
|
||||
VertexLayout VertexLayout
|
||||
BlendDesc BlendDesc
|
||||
PixelFormat TextureFormat
|
||||
Topology Topology
|
||||
}
|
||||
|
||||
type VertexLayout struct {
|
||||
Inputs []InputDesc
|
||||
Stride int
|
||||
}
|
||||
|
||||
// InputDesc describes a vertex attribute as laid out in a Buffer.
|
||||
type InputDesc struct {
|
||||
Type shader.DataType
|
||||
Size int
|
||||
|
||||
Offset int
|
||||
}
|
||||
|
||||
type BlendDesc struct {
|
||||
Enable bool
|
||||
SrcFactor, DstFactor BlendFactor
|
||||
}
|
||||
|
||||
type BlendFactor uint8
|
||||
|
||||
type Topology uint8
|
||||
|
||||
type (
|
||||
TextureFilter uint8
|
||||
TextureFormat uint8
|
||||
)
|
||||
|
||||
type BufferBinding uint8
|
||||
|
||||
type LoadAction uint8
|
||||
|
||||
type Features uint
|
||||
|
||||
type Caps struct {
|
||||
// BottomLeftOrigin is true if the driver has the origin in the lower left
|
||||
// corner. The OpenGL driver returns true.
|
||||
BottomLeftOrigin bool
|
||||
Features Features
|
||||
MaxTextureSize int
|
||||
}
|
||||
|
||||
type VertexShader interface {
|
||||
Release()
|
||||
}
|
||||
|
||||
type FragmentShader interface {
|
||||
Release()
|
||||
}
|
||||
|
||||
type Program interface {
|
||||
Release()
|
||||
}
|
||||
|
||||
type Buffer interface {
|
||||
Release()
|
||||
Upload(data []byte)
|
||||
Download(data []byte) error
|
||||
}
|
||||
|
||||
type Timer interface {
|
||||
Begin()
|
||||
End()
|
||||
Duration() (time.Duration, bool)
|
||||
Release()
|
||||
}
|
||||
|
||||
type Texture interface {
|
||||
RenderTarget
|
||||
Upload(offset, size image.Point, pixels []byte, stride int)
|
||||
ReadPixels(src image.Rectangle, pixels []byte, stride int) error
|
||||
Release()
|
||||
}
|
||||
|
||||
const (
|
||||
BufferBindingIndices BufferBinding = 1 << iota
|
||||
BufferBindingVertices
|
||||
BufferBindingUniforms
|
||||
BufferBindingTexture
|
||||
BufferBindingFramebuffer
|
||||
BufferBindingShaderStorageRead
|
||||
BufferBindingShaderStorageWrite
|
||||
)
|
||||
|
||||
const (
|
||||
TextureFormatSRGBA TextureFormat = iota
|
||||
TextureFormatFloat
|
||||
TextureFormatRGBA8
|
||||
// TextureFormatOutput denotes the format used by the output framebuffer.
|
||||
TextureFormatOutput
|
||||
)
|
||||
|
||||
const (
|
||||
FilterNearest TextureFilter = iota
|
||||
FilterLinear
|
||||
FilterLinearMipmapLinear
|
||||
)
|
||||
|
||||
const (
|
||||
FeatureTimers Features = 1 << iota
|
||||
FeatureFloatRenderTargets
|
||||
FeatureCompute
|
||||
FeatureSRGB
|
||||
)
|
||||
|
||||
const (
|
||||
TopologyTriangleStrip Topology = iota
|
||||
TopologyTriangles
|
||||
)
|
||||
|
||||
const (
|
||||
BlendFactorOne BlendFactor = iota
|
||||
BlendFactorOneMinusSrcAlpha
|
||||
BlendFactorZero
|
||||
BlendFactorDstColor
|
||||
)
|
||||
|
||||
const (
|
||||
LoadActionKeep LoadAction = iota
|
||||
LoadActionClear
|
||||
LoadActionInvalidate
|
||||
)
|
||||
|
||||
var ErrContentLost = errors.New("buffer content lost")
|
||||
|
||||
func (f Features) Has(feats Features) bool {
|
||||
return f&feats == feats
|
||||
}
|
||||
|
||||
func DownloadImage(d Device, t Texture, img *image.RGBA) error {
|
||||
r := img.Bounds()
|
||||
if err := t.ReadPixels(r, img.Pix, img.Stride); err != nil {
|
||||
return err
|
||||
}
|
||||
if d.Caps().BottomLeftOrigin {
|
||||
// OpenGL origin is in the lower-left corner. Flip the image to
|
||||
// match.
|
||||
flipImageY(r.Dx()*4, r.Dy(), img.Pix)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func flipImageY(stride, height int, pixels []byte) {
|
||||
// Flip image in y-direction. OpenGL's origin is in the lower
|
||||
// left corner.
|
||||
row := make([]uint8, stride)
|
||||
for y := range height / 2 {
|
||||
y1 := height - y - 1
|
||||
dest := y1 * stride
|
||||
src := y * stride
|
||||
copy(row, pixels[dest:])
|
||||
copy(pixels[dest:], pixels[src:src+len(row)])
|
||||
copy(pixels[src:], row)
|
||||
}
|
||||
}
|
||||
|
||||
func UploadImage(t Texture, offset image.Point, img *image.RGBA) {
|
||||
var pixels []byte
|
||||
size := img.Bounds().Size()
|
||||
min := img.Rect.Min
|
||||
start := img.PixOffset(min.X, min.Y)
|
||||
end := img.PixOffset(min.X+size.X, min.Y+size.Y-1)
|
||||
pixels = img.Pix[start:end]
|
||||
t.Upload(offset, size, pixels, img.Stride)
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// This file exists so this package builds on non-Darwin platforms.
|
||||
|
||||
package metal
|
||||
+1159
File diff suppressed because it is too large
Load Diff
+1374
File diff suppressed because it is too large
Load Diff
+176
@@ -0,0 +1,176 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package opengl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"gioui.org/internal/byteslice"
|
||||
"gioui.org/internal/gl"
|
||||
)
|
||||
|
||||
// SRGBFBO implements an intermediate sRGB FBO
|
||||
// for gamma-correct rendering on platforms without
|
||||
// sRGB enabled native framebuffers.
|
||||
type SRGBFBO struct {
|
||||
c *gl.Functions
|
||||
state *glState
|
||||
viewport image.Point
|
||||
fbo gl.Framebuffer
|
||||
tex gl.Texture
|
||||
blitted bool
|
||||
quad gl.Buffer
|
||||
prog gl.Program
|
||||
format textureTriple
|
||||
}
|
||||
|
||||
func NewSRGBFBO(f *gl.Functions, state *glState) (*SRGBFBO, error) {
|
||||
glVer := f.GetString(gl.VERSION)
|
||||
ver, _, err := gl.ParseGLVersion(glVer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exts := strings.Split(f.GetString(gl.EXTENSIONS), " ")
|
||||
srgbTriple, err := srgbaTripleFor(ver, exts)
|
||||
if err != nil {
|
||||
// Fall back to the linear RGB colorspace, at the cost of color precision loss.
|
||||
srgbTriple = textureTriple{gl.RGBA, gl.Enum(gl.RGBA), gl.Enum(gl.UNSIGNED_BYTE)}
|
||||
}
|
||||
s := &SRGBFBO{
|
||||
c: f,
|
||||
state: state,
|
||||
format: srgbTriple,
|
||||
fbo: f.CreateFramebuffer(),
|
||||
tex: f.CreateTexture(),
|
||||
}
|
||||
state.bindTexture(f, 0, s.tex)
|
||||
f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
|
||||
f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
|
||||
f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
|
||||
f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *SRGBFBO) Blit() {
|
||||
if !s.blitted {
|
||||
prog, err := gl.CreateProgram(s.c, blitVSrc, blitFSrc, []string{"pos", "uv"})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s.prog = prog
|
||||
s.state.useProgram(s.c, prog)
|
||||
s.c.Uniform1i(s.c.GetUniformLocation(prog, "tex"), 0)
|
||||
s.quad = s.c.CreateBuffer()
|
||||
s.state.bindBuffer(s.c, gl.ARRAY_BUFFER, s.quad)
|
||||
coords := byteslice.Slice([]float32{
|
||||
-1, +1, 0, 1,
|
||||
+1, +1, 1, 1,
|
||||
-1, -1, 0, 0,
|
||||
+1, -1, 1, 0,
|
||||
})
|
||||
s.c.BufferData(gl.ARRAY_BUFFER, len(coords), gl.STATIC_DRAW, coords)
|
||||
s.blitted = true
|
||||
}
|
||||
s.state.useProgram(s.c, s.prog)
|
||||
s.state.bindTexture(s.c, 0, s.tex)
|
||||
s.state.vertexAttribPointer(s.c, s.quad, 0 /* pos */, 2, gl.FLOAT, false, 4*4, 0)
|
||||
s.state.vertexAttribPointer(s.c, s.quad, 1 /* uv */, 2, gl.FLOAT, false, 4*4, 4*2)
|
||||
s.state.setVertexAttribArray(s.c, 0, true)
|
||||
s.state.setVertexAttribArray(s.c, 1, true)
|
||||
s.c.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
|
||||
s.state.bindFramebuffer(s.c, gl.FRAMEBUFFER, s.fbo)
|
||||
s.c.InvalidateFramebuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0)
|
||||
}
|
||||
|
||||
func (s *SRGBFBO) Framebuffer() gl.Framebuffer {
|
||||
return s.fbo
|
||||
}
|
||||
|
||||
func (s *SRGBFBO) Refresh(viewport image.Point) error {
|
||||
if viewport.X == 0 || viewport.Y == 0 {
|
||||
return errors.New("srgb: zero-sized framebuffer")
|
||||
}
|
||||
if s.viewport == viewport {
|
||||
return nil
|
||||
}
|
||||
s.viewport = viewport
|
||||
s.state.bindTexture(s.c, 0, s.tex)
|
||||
s.c.TexImage2D(gl.TEXTURE_2D, 0, s.format.internalFormat, viewport.X, viewport.Y, s.format.format, s.format.typ)
|
||||
s.state.bindFramebuffer(s.c, gl.FRAMEBUFFER, s.fbo)
|
||||
s.c.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, s.tex, 0)
|
||||
if st := s.c.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
|
||||
return fmt.Errorf("sRGB framebuffer incomplete (%dx%d), status: %#x error: %x", viewport.X, viewport.Y, 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.state.setClearColor(s.c, .5, .5, .5, 1.0)
|
||||
s.c.Clear(gl.COLOR_BUFFER_BIT)
|
||||
var pixel [4]byte
|
||||
s.c.ReadPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel[:])
|
||||
if pixel[0] == 128 { // Correct sRGB color value is ~188
|
||||
s.c.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, viewport.X, viewport.Y, gl.RGBA, gl.UNSIGNED_BYTE)
|
||||
if st := s.c.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
|
||||
return fmt.Errorf("fallback RGBA framebuffer incomplete (%dx%d), status: %#x error: %x", viewport.X, viewport.Y, st, s.c.GetError())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SRGBFBO) Release() {
|
||||
s.state.deleteFramebuffer(s.c, s.fbo)
|
||||
s.state.deleteTexture(s.c, s.tex)
|
||||
if s.blitted {
|
||||
s.state.deleteBuffer(s.c, s.quad)
|
||||
s.state.deleteProgram(s.c, s.prog)
|
||||
}
|
||||
s.c = nil
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
`
|
||||
)
|
||||
+1163
File diff suppressed because it is too large
Load Diff
+5
@@ -0,0 +1,5 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package vulkan
|
||||
|
||||
// Empty file to avoid the build error for platforms without Vulkan support.
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
// 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 {
|
||||
maxDims image.Point
|
||||
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: 1e6, Y: 1e6},
|
||||
})
|
||||
}
|
||||
|
||||
func (p *packer) tryAdd(s image.Point) (placement, bool) {
|
||||
if len(p.spaces) == 0 || len(p.sizes) == 0 {
|
||||
return placement{}, false
|
||||
}
|
||||
|
||||
var (
|
||||
bestIdx *image.Rectangle
|
||||
bestSize = p.maxDims
|
||||
lastSize = p.sizes[len(p.sizes)-1]
|
||||
)
|
||||
// Go backwards to prioritize smaller spaces.
|
||||
for i := range p.spaces {
|
||||
space := &p.spaces[i]
|
||||
rightSpace := space.Dx() - s.X
|
||||
bottomSpace := space.Dy() - s.Y
|
||||
if rightSpace < 0 || bottomSpace < 0 {
|
||||
continue
|
||||
}
|
||||
size := lastSize
|
||||
if x := space.Min.X + s.X; x > size.X {
|
||||
if x > p.maxDims.X {
|
||||
continue
|
||||
}
|
||||
size.X = x
|
||||
}
|
||||
if y := space.Min.Y + s.Y; y > size.Y {
|
||||
if y > p.maxDims.Y {
|
||||
continue
|
||||
}
|
||||
size.Y = y
|
||||
}
|
||||
if size.X*size.Y < bestSize.X*bestSize.Y {
|
||||
bestIdx = space
|
||||
bestSize = size
|
||||
}
|
||||
}
|
||||
if bestIdx == nil {
|
||||
return placement{}, false
|
||||
}
|
||||
// Remove space.
|
||||
bestSpace := *bestIdx
|
||||
*bestIdx = 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 := bestSpace.Min
|
||||
if rem := bestSpace.Dy() - s.Y; rem > 0 {
|
||||
p.spaces = append(p.spaces, image.Rectangle{
|
||||
Min: image.Point{X: pos.X, Y: pos.Y + s.Y},
|
||||
Max: image.Point{X: bestSpace.Max.X, Y: bestSpace.Max.Y},
|
||||
})
|
||||
}
|
||||
if rem := bestSpace.Dx() - s.X; rem > 0 {
|
||||
p.spaces = append(p.spaces, image.Rectangle{
|
||||
Min: image.Point{X: pos.X + s.X, Y: pos.Y},
|
||||
Max: image.Point{X: bestSpace.Max.X, Y: pos.Y + s.Y},
|
||||
})
|
||||
}
|
||||
idx := len(p.sizes) - 1
|
||||
p.sizes[idx] = bestSize
|
||||
return placement{Idx: idx, Pos: pos}, true
|
||||
}
|
||||
+424
@@ -0,0 +1,424 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package gpu
|
||||
|
||||
// GPU accelerated path drawing using the algorithms from
|
||||
// Pathfinder (https://github.com/servo/pathfinder).
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"image"
|
||||
"math"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/gpu/internal/driver"
|
||||
"gioui.org/internal/byteslice"
|
||||
"gioui.org/internal/f32"
|
||||
"gioui.org/internal/f32color"
|
||||
"gioui.org/shader"
|
||||
"gioui.org/shader/gio"
|
||||
)
|
||||
|
||||
type pather struct {
|
||||
ctx driver.Device
|
||||
|
||||
viewport image.Point
|
||||
|
||||
stenciler *stenciler
|
||||
coverer *coverer
|
||||
}
|
||||
|
||||
type coverer struct {
|
||||
ctx driver.Device
|
||||
pipelines [2][3]*pipeline
|
||||
texUniforms *coverTexUniforms
|
||||
colUniforms *coverColUniforms
|
||||
linearGradientUniforms *coverLinearGradientUniforms
|
||||
}
|
||||
|
||||
type coverTexUniforms struct {
|
||||
coverUniforms
|
||||
_ [12]byte // Padding to multiple of 16.
|
||||
}
|
||||
|
||||
type coverColUniforms struct {
|
||||
coverUniforms
|
||||
_ [128 - unsafe.Sizeof(coverUniforms{}) - unsafe.Sizeof(colorUniforms{})]byte // Padding to 128 bytes.
|
||||
colorUniforms
|
||||
}
|
||||
|
||||
type coverLinearGradientUniforms struct {
|
||||
coverUniforms
|
||||
_ [128 - unsafe.Sizeof(coverUniforms{}) - unsafe.Sizeof(gradientUniforms{})]byte // Padding to 128.
|
||||
gradientUniforms
|
||||
}
|
||||
|
||||
type coverUniforms struct {
|
||||
transform [4]float32
|
||||
uvCoverTransform [4]float32
|
||||
uvTransformR1 [4]float32
|
||||
uvTransformR2 [4]float32
|
||||
fbo float32
|
||||
}
|
||||
|
||||
type stenciler struct {
|
||||
ctx driver.Device
|
||||
pipeline struct {
|
||||
pipeline *pipeline
|
||||
uniforms *stencilUniforms
|
||||
}
|
||||
ipipeline struct {
|
||||
pipeline *pipeline
|
||||
uniforms *intersectUniforms
|
||||
}
|
||||
fbos fboSet
|
||||
intersections fboSet
|
||||
indexBuf driver.Buffer
|
||||
}
|
||||
|
||||
type stencilUniforms struct {
|
||||
transform [4]float32
|
||||
pathOffset [2]float32
|
||||
_ [8]byte // Padding to multiple of 16.
|
||||
}
|
||||
|
||||
type intersectUniforms struct {
|
||||
vert struct {
|
||||
uvTransform [4]float32
|
||||
subUVTransform [4]float32
|
||||
}
|
||||
}
|
||||
|
||||
type fboSet struct {
|
||||
fbos []FBO
|
||||
}
|
||||
|
||||
type FBO struct {
|
||||
size image.Point
|
||||
tex driver.Texture
|
||||
}
|
||||
|
||||
type pathData struct {
|
||||
ncurves int
|
||||
data driver.Buffer
|
||||
}
|
||||
|
||||
// vertex data suitable for passing to vertex programs.
|
||||
type vertex struct {
|
||||
// Corner encodes the corner: +0.5 for south, +.25 for east.
|
||||
Corner float32
|
||||
MaxY float32
|
||||
FromX, FromY float32
|
||||
CtrlX, CtrlY float32
|
||||
ToX, ToY float32
|
||||
}
|
||||
|
||||
// encode needs to stay in-sync with the code in clip.go encodeQuadTo.
|
||||
func (v vertex) encode(d []byte, maxy uint32) {
|
||||
d = d[0:32]
|
||||
bo := binary.LittleEndian
|
||||
bo.PutUint32(d[0:4], math.Float32bits(v.Corner))
|
||||
bo.PutUint32(d[4:8], maxy)
|
||||
bo.PutUint32(d[8:12], math.Float32bits(v.FromX))
|
||||
bo.PutUint32(d[12:16], math.Float32bits(v.FromY))
|
||||
bo.PutUint32(d[16:20], math.Float32bits(v.CtrlX))
|
||||
bo.PutUint32(d[20:24], math.Float32bits(v.CtrlY))
|
||||
bo.PutUint32(d[24:28], math.Float32bits(v.ToX))
|
||||
bo.PutUint32(d[28:32], math.Float32bits(v.ToY))
|
||||
}
|
||||
|
||||
const (
|
||||
// Number of path quads per draw batch.
|
||||
pathBatchSize = 10000
|
||||
// Size of a vertex as sent to gpu
|
||||
vertStride = 8 * 4
|
||||
)
|
||||
|
||||
func newPather(ctx driver.Device) *pather {
|
||||
return &pather{
|
||||
ctx: ctx,
|
||||
stenciler: newStenciler(ctx),
|
||||
coverer: newCoverer(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
func newCoverer(ctx driver.Device) *coverer {
|
||||
c := &coverer{
|
||||
ctx: ctx,
|
||||
}
|
||||
c.colUniforms = new(coverColUniforms)
|
||||
c.texUniforms = new(coverTexUniforms)
|
||||
c.linearGradientUniforms = new(coverLinearGradientUniforms)
|
||||
pipelines, err := createColorPrograms(ctx, gio.Shader_cover_vert, gio.Shader_cover_frag,
|
||||
[3]any{c.colUniforms, c.linearGradientUniforms, c.texUniforms},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
c.pipelines = pipelines
|
||||
return c
|
||||
}
|
||||
|
||||
func newStenciler(ctx driver.Device) *stenciler {
|
||||
// Allocate a suitably large index buffer for drawing paths.
|
||||
indices := make([]uint16, pathBatchSize*6)
|
||||
for i := range pathBatchSize {
|
||||
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
|
||||
}
|
||||
indexBuf, err := ctx.NewImmutableBuffer(driver.BufferBindingIndices, byteslice.Slice(indices))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
progLayout := driver.VertexLayout{
|
||||
Inputs: []driver.InputDesc{
|
||||
{Type: shader.DataTypeFloat, Size: 1, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).Corner))},
|
||||
{Type: shader.DataTypeFloat, Size: 1, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).MaxY))},
|
||||
{Type: shader.DataTypeFloat, Size: 2, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).FromX))},
|
||||
{Type: shader.DataTypeFloat, Size: 2, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).CtrlX))},
|
||||
{Type: shader.DataTypeFloat, Size: 2, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).ToX))},
|
||||
},
|
||||
Stride: vertStride,
|
||||
}
|
||||
iprogLayout := driver.VertexLayout{
|
||||
Inputs: []driver.InputDesc{
|
||||
{Type: shader.DataTypeFloat, Size: 2, Offset: 0},
|
||||
{Type: shader.DataTypeFloat, Size: 2, Offset: 4 * 2},
|
||||
},
|
||||
Stride: 4 * 4,
|
||||
}
|
||||
st := &stenciler{
|
||||
ctx: ctx,
|
||||
indexBuf: indexBuf,
|
||||
}
|
||||
vsh, fsh, err := newShaders(ctx, gio.Shader_stencil_vert, gio.Shader_stencil_frag)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer vsh.Release()
|
||||
defer fsh.Release()
|
||||
st.pipeline.uniforms = new(stencilUniforms)
|
||||
vertUniforms := newUniformBuffer(ctx, st.pipeline.uniforms)
|
||||
pipe, err := st.ctx.NewPipeline(driver.PipelineDesc{
|
||||
VertexShader: vsh,
|
||||
FragmentShader: fsh,
|
||||
VertexLayout: progLayout,
|
||||
BlendDesc: driver.BlendDesc{
|
||||
Enable: true,
|
||||
SrcFactor: driver.BlendFactorOne,
|
||||
DstFactor: driver.BlendFactorOne,
|
||||
},
|
||||
PixelFormat: driver.TextureFormatFloat,
|
||||
Topology: driver.TopologyTriangles,
|
||||
})
|
||||
st.pipeline.pipeline = &pipeline{pipe, vertUniforms}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
vsh, fsh, err = newShaders(ctx, gio.Shader_intersect_vert, gio.Shader_intersect_frag)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer vsh.Release()
|
||||
defer fsh.Release()
|
||||
st.ipipeline.uniforms = new(intersectUniforms)
|
||||
vertUniforms = newUniformBuffer(ctx, &st.ipipeline.uniforms.vert)
|
||||
ipipe, err := st.ctx.NewPipeline(driver.PipelineDesc{
|
||||
VertexShader: vsh,
|
||||
FragmentShader: fsh,
|
||||
VertexLayout: iprogLayout,
|
||||
BlendDesc: driver.BlendDesc{
|
||||
Enable: true,
|
||||
SrcFactor: driver.BlendFactorDstColor,
|
||||
DstFactor: driver.BlendFactorZero,
|
||||
},
|
||||
PixelFormat: driver.TextureFormatFloat,
|
||||
Topology: driver.TopologyTriangleStrip,
|
||||
})
|
||||
st.ipipeline.pipeline = &pipeline{ipipe, vertUniforms}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return st
|
||||
}
|
||||
|
||||
func (s *fboSet) resize(ctx driver.Device, format driver.TextureFormat, sizes []image.Point) {
|
||||
// Add fbos.
|
||||
for i := len(s.fbos); i < len(sizes); i++ {
|
||||
s.fbos = append(s.fbos, FBO{})
|
||||
}
|
||||
// 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 {
|
||||
if f.tex != nil {
|
||||
f.tex.Release()
|
||||
}
|
||||
// Add 5% extra space in each dimension to minimize resizing.
|
||||
sz = sz.Mul(105).Div(100)
|
||||
max := ctx.Caps().MaxTextureSize
|
||||
if sz.Y > max {
|
||||
sz.Y = max
|
||||
}
|
||||
if sz.X > max {
|
||||
sz.X = max
|
||||
}
|
||||
tex, err := ctx.NewTexture(format, sz.X, sz.Y, driver.FilterNearest, driver.FilterNearest,
|
||||
driver.BufferBindingTexture|driver.BufferBindingFramebuffer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
f.size = sz
|
||||
f.tex = tex
|
||||
}
|
||||
}
|
||||
// Delete extra fbos.
|
||||
s.delete(ctx, len(sizes))
|
||||
}
|
||||
|
||||
func (s *fboSet) delete(ctx driver.Device, idx int) {
|
||||
for i := idx; i < len(s.fbos); i++ {
|
||||
f := s.fbos[i]
|
||||
f.tex.Release()
|
||||
}
|
||||
s.fbos = s.fbos[:idx]
|
||||
}
|
||||
|
||||
func (s *stenciler) release() {
|
||||
s.fbos.delete(s.ctx, 0)
|
||||
s.intersections.delete(s.ctx, 0)
|
||||
s.pipeline.pipeline.Release()
|
||||
s.ipipeline.pipeline.Release()
|
||||
s.indexBuf.Release()
|
||||
}
|
||||
|
||||
func (p *pather) release() {
|
||||
p.stenciler.release()
|
||||
p.coverer.release()
|
||||
}
|
||||
|
||||
func (c *coverer) release() {
|
||||
for _, p := range c.pipelines {
|
||||
for _, p := range p {
|
||||
p.Release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func buildPath(ctx driver.Device, p []byte) pathData {
|
||||
buf, err := ctx.NewImmutableBuffer(driver.BufferBindingVertices, p)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return pathData{
|
||||
ncurves: len(p) / vertStride,
|
||||
data: buf,
|
||||
}
|
||||
}
|
||||
|
||||
func (p pathData) release() {
|
||||
p.data.Release()
|
||||
}
|
||||
|
||||
func (p *pather) begin(sizes []image.Point) {
|
||||
p.stenciler.begin(sizes)
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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, driver.TextureFormatFloat, sizes)
|
||||
}
|
||||
|
||||
func (s *stenciler) cover(idx int) FBO {
|
||||
return s.fbos.fbos[idx]
|
||||
}
|
||||
|
||||
func (s *stenciler) begin(sizes []image.Point) {
|
||||
s.fbos.resize(s.ctx, driver.TextureFormatFloat, sizes)
|
||||
}
|
||||
|
||||
func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) {
|
||||
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.pipeline.uniforms.transform = [4]float32{scale.X, scale.Y, orig.X, orig.Y}
|
||||
s.pipeline.uniforms.pathOffset = [2]float32{offset.X, offset.Y}
|
||||
s.pipeline.pipeline.UploadUniforms(s.ctx)
|
||||
// Draw in batches that fit in uint16 indices.
|
||||
start := 0
|
||||
nquads := data.ncurves / 4
|
||||
for start < nquads {
|
||||
batch := nquads - start
|
||||
if max := pathBatchSize; batch > max {
|
||||
batch = max
|
||||
}
|
||||
off := vertStride * start * 4
|
||||
s.ctx.BindVertexBuffer(data.data, off)
|
||||
s.ctx.DrawElements(0, batch*6)
|
||||
start += batch
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pather) cover(mat materialType, isFBO bool, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
|
||||
p.coverer.cover(mat, isFBO, col, col1, col2, scale, off, uvTrans, coverScale, coverOff)
|
||||
}
|
||||
|
||||
func (c *coverer) cover(mat materialType, isFBO bool, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
|
||||
var uniforms *coverUniforms
|
||||
switch mat {
|
||||
case materialColor:
|
||||
c.colUniforms.color = col
|
||||
uniforms = &c.colUniforms.coverUniforms
|
||||
case materialLinearGradient:
|
||||
c.linearGradientUniforms.color1 = col1
|
||||
c.linearGradientUniforms.color2 = col2
|
||||
|
||||
t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
|
||||
c.linearGradientUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
|
||||
c.linearGradientUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
|
||||
uniforms = &c.linearGradientUniforms.coverUniforms
|
||||
case materialTexture:
|
||||
t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
|
||||
c.texUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
|
||||
c.texUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
|
||||
uniforms = &c.texUniforms.coverUniforms
|
||||
}
|
||||
uniforms.fbo = 0
|
||||
if isFBO {
|
||||
uniforms.fbo = 1
|
||||
}
|
||||
uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
|
||||
uniforms.uvCoverTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y}
|
||||
fboIdx := 0
|
||||
if isFBO {
|
||||
fboIdx = 1
|
||||
}
|
||||
c.pipelines[fboIdx][mat].UploadUniforms(c.ctx)
|
||||
c.ctx.DrawArrays(0, 4)
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package gpu
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gioui.org/gpu/internal/driver"
|
||||
)
|
||||
|
||||
type timers struct {
|
||||
backend driver.Device
|
||||
timers []*timer
|
||||
}
|
||||
|
||||
type timer struct {
|
||||
Elapsed time.Duration
|
||||
backend driver.Device
|
||||
timer driver.Timer
|
||||
state timerState
|
||||
}
|
||||
|
||||
type timerState uint8
|
||||
|
||||
const (
|
||||
timerIdle timerState = iota
|
||||
timerRunning
|
||||
timerWaiting
|
||||
)
|
||||
|
||||
func newTimers(b driver.Device) *timers {
|
||||
return &timers{
|
||||
backend: b,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *timers) newTimer() *timer {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
tt := &timer{
|
||||
backend: t.backend,
|
||||
timer: t.backend.NewTimer(),
|
||||
}
|
||||
t.timers = append(t.timers, tt)
|
||||
return tt
|
||||
}
|
||||
|
||||
func (t *timer) begin() {
|
||||
if t == nil || t.state != timerIdle {
|
||||
return
|
||||
}
|
||||
t.timer.Begin()
|
||||
t.state = timerRunning
|
||||
}
|
||||
|
||||
func (t *timer) end() {
|
||||
if t == nil || t.state != timerRunning {
|
||||
return
|
||||
}
|
||||
t.timer.End()
|
||||
t.state = timerWaiting
|
||||
}
|
||||
|
||||
func (t *timers) ready() bool {
|
||||
if t == nil {
|
||||
return false
|
||||
}
|
||||
for _, tt := range t.timers {
|
||||
switch tt.state {
|
||||
case timerIdle:
|
||||
continue
|
||||
case timerRunning:
|
||||
return false
|
||||
}
|
||||
d, ok := tt.timer.Duration()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
tt.state = timerIdle
|
||||
tt.Elapsed = d
|
||||
}
|
||||
return t.backend.IsTimeContinuous()
|
||||
}
|
||||
|
||||
func (t *timers) Release() {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
for _, tt := range t.timers {
|
||||
tt.timer.Release()
|
||||
}
|
||||
t.timers = nil
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// Package byteslice provides byte slice views of other Go values such as
|
||||
// slices and structs.
|
||||
package byteslice
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Struct returns a byte slice view of a struct.
|
||||
func Struct(s any) []byte {
|
||||
v := reflect.ValueOf(s)
|
||||
sz := int(v.Elem().Type().Size())
|
||||
return unsafe.Slice((*byte)(unsafe.Pointer(v.Pointer())), sz)
|
||||
}
|
||||
|
||||
// Uint32 returns a byte slice view of a uint32 slice.
|
||||
func Uint32(s []uint32) []byte {
|
||||
n := len(s)
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
blen := n * int(unsafe.Sizeof(s[0]))
|
||||
return unsafe.Slice((*byte)(unsafe.Pointer(&s[0])), blen)
|
||||
}
|
||||
|
||||
// Slice returns a byte slice view of a slice.
|
||||
func Slice(s any) []byte {
|
||||
v := reflect.ValueOf(s)
|
||||
first := v.Index(0)
|
||||
sz := int(first.Type().Size())
|
||||
res := unsafe.Slice((*byte)(unsafe.Pointer(v.Pointer())), sz*v.Cap())
|
||||
return res[:sz*v.Len()]
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// Package cocoainit initializes support for multithreaded
|
||||
// programs in Cocoa.
|
||||
package cocoainit
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -xobjective-c -fobjc-arc
|
||||
#cgo LDFLAGS: -framework Foundation
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
static inline void activate_cocoa_multithreading() {
|
||||
[[NSThread new] start];
|
||||
}
|
||||
#pragma GCC visibility push(hidden)
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func init() {
|
||||
C.activate_cocoa_multithreading()
|
||||
}
|
||||
+1694
File diff suppressed because it is too large
Load Diff
+57
@@ -0,0 +1,57 @@
|
||||
// Package debug provides general debug feature management for Gio, including
|
||||
// the ability to toggle debug features using the GIODEBUG environment variable.
|
||||
package debug
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
const (
|
||||
debugVariable = "GIODEBUG"
|
||||
textSubsystem = "text"
|
||||
silentFeature = "silent"
|
||||
)
|
||||
|
||||
// Text controls whether the text subsystem has debug logging enabled.
|
||||
var Text atomic.Bool
|
||||
|
||||
var parseOnce sync.Once
|
||||
|
||||
// Parse processes the current value of GIODEBUG. If it is unset, it does nothing.
|
||||
// Otherwise it process its value, printing usage info the stderr if the value is
|
||||
// not understood. Parse will be automatically invoked when the first application
|
||||
// window is created, allowing applications to manipulate GIODEBUG programmatically
|
||||
// before it is parsed.
|
||||
func Parse() {
|
||||
parseOnce.Do(func() {
|
||||
val, ok := os.LookupEnv(debugVariable)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
print := false
|
||||
silent := false
|
||||
for part := range strings.SplitSeq(val, ",") {
|
||||
switch part {
|
||||
case textSubsystem:
|
||||
Text.Store(true)
|
||||
case silentFeature:
|
||||
silent = true
|
||||
default:
|
||||
print = true
|
||||
}
|
||||
}
|
||||
if print && !silent {
|
||||
fmt.Fprintf(os.Stderr,
|
||||
`Usage of %s:
|
||||
A comma-delimited list of debug subsystems to enable. Currently recognized systems:
|
||||
|
||||
- %s: text debug info including system font resolution
|
||||
- %s: silence this usage message even if GIODEBUG contains invalid content
|
||||
`, debugVariable, textSubsystem, silentFeature)
|
||||
}
|
||||
})
|
||||
}
|
||||
+247
@@ -0,0 +1,247 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build linux || windows || freebsd || openbsd
|
||||
// +build linux windows freebsd openbsd
|
||||
|
||||
package egl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"gioui.org/gpu"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
disp _EGLDisplay
|
||||
eglCtx *eglContext
|
||||
eglSurf _EGLSurface
|
||||
}
|
||||
|
||||
type eglContext struct {
|
||||
config _EGLConfig
|
||||
ctx _EGLContext
|
||||
visualID int
|
||||
srgb bool
|
||||
surfaceless bool
|
||||
}
|
||||
|
||||
var (
|
||||
nilEGLDisplay _EGLDisplay
|
||||
nilEGLSurface _EGLSurface
|
||||
nilEGLContext _EGLContext
|
||||
nilEGLConfig _EGLConfig
|
||||
EGL_DEFAULT_DISPLAY NativeDisplayType
|
||||
)
|
||||
|
||||
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() {
|
||||
c.ReleaseSurface()
|
||||
if c.eglCtx != nil {
|
||||
eglDestroyContext(c.disp, c.eglCtx.ctx)
|
||||
c.eglCtx = nil
|
||||
}
|
||||
eglTerminate(c.disp)
|
||||
c.disp = nilEGLDisplay
|
||||
}
|
||||
|
||||
func (c *Context) Present() error {
|
||||
if !eglSwapBuffers(c.disp, c.eglSurf) {
|
||||
return fmt.Errorf("eglSwapBuffers failed (%x)", eglGetError())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewContext(disp NativeDisplayType) (*Context, error) {
|
||||
if err := loadEGL(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
eglDisp := eglGetDisplay(disp)
|
||||
// eglGetDisplay can return EGL_NO_DISPLAY yet no error
|
||||
// (EGL_SUCCESS), in which case a default EGL display might be
|
||||
// available.
|
||||
if eglDisp == nilEGLDisplay {
|
||||
eglDisp = eglGetDisplay(EGL_DEFAULT_DISPLAY)
|
||||
}
|
||||
if eglDisp == nilEGLDisplay {
|
||||
return nil, fmt.Errorf("eglGetDisplay failed: 0x%x", eglGetError())
|
||||
}
|
||||
eglCtx, err := createContext(eglDisp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &Context{
|
||||
disp: eglDisp,
|
||||
eglCtx: eglCtx,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *Context) RenderTarget() (gpu.RenderTarget, error) {
|
||||
return gpu.OpenGLRenderTarget{}, nil
|
||||
}
|
||||
|
||||
func (c *Context) API() gpu.API {
|
||||
return gpu.OpenGL{}
|
||||
}
|
||||
|
||||
func (c *Context) ReleaseSurface() {
|
||||
if c.eglSurf == nilEGLSurface {
|
||||
return
|
||||
}
|
||||
// Make sure any in-flight GL commands are complete.
|
||||
eglWaitClient()
|
||||
c.ReleaseCurrent()
|
||||
eglDestroySurface(c.disp, c.eglSurf)
|
||||
c.eglSurf = nilEGLSurface
|
||||
}
|
||||
|
||||
func (c *Context) VisualID() int {
|
||||
return c.eglCtx.visualID
|
||||
}
|
||||
|
||||
func (c *Context) CreateSurface(win NativeWindowType) error {
|
||||
eglSurf, err := createSurface(c.disp, c.eglCtx, win)
|
||||
c.eglSurf = eglSurf
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Context) ReleaseCurrent() {
|
||||
if c.disp != nilEGLDisplay {
|
||||
eglMakeCurrent(c.disp, nilEGLSurface, nilEGLSurface, nilEGLContext)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) MakeCurrent() error {
|
||||
// OpenGL contexts are implicit and thread-local. Lock the OS thread.
|
||||
runtime.LockOSThread()
|
||||
|
||||
if c.eglSurf == nilEGLSurface && !c.eglCtx.surfaceless {
|
||||
return errors.New("no surface created yet EGL_KHR_surfaceless_context is not supported")
|
||||
}
|
||||
if !eglMakeCurrent(c.disp, c.eglSurf, c.eglSurf, c.eglCtx.ctx) {
|
||||
return fmt.Errorf("eglMakeCurrent error 0x%x", eglGetError())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Context) EnableVSync(enable bool) {
|
||||
if enable {
|
||||
eglSwapInterval(c.disp, 1)
|
||||
} else {
|
||||
eglSwapInterval(c.disp, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func hasExtension(exts []string, ext string) bool {
|
||||
return slices.Contains(exts, ext)
|
||||
}
|
||||
|
||||
func createContext(disp _EGLDisplay) (*eglContext, error) {
|
||||
major, minor, ret := eglInitialize(disp)
|
||||
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(disp, _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" || runtime.GOOS == "android" {
|
||||
// Some Mesa drivers crash if an sRGB framebuffer is requested without alpha.
|
||||
// https://bugs.freedesktop.org/show_bug.cgi?id=107782.
|
||||
//
|
||||
// Also, some Android devices (Samsung S9) need alpha for sRGB to work.
|
||||
attribs = append(attribs, _EGL_ALPHA_SIZE, 8)
|
||||
}
|
||||
}
|
||||
attribs = append(attribs, _EGL_NONE)
|
||||
eglCfg, ret := eglChooseConfig(disp, attribs)
|
||||
if !ret {
|
||||
return nil, fmt.Errorf("eglChooseConfig failed: 0x%x", eglGetError())
|
||||
}
|
||||
if eglCfg == nilEGLConfig {
|
||||
supportsNoCfg := hasExtension(exts, "EGL_KHR_no_config_context")
|
||||
if !supportsNoCfg {
|
||||
return nil, errors.New("eglChooseConfig returned no configs")
|
||||
}
|
||||
}
|
||||
var visID _EGLint
|
||||
if eglCfg != nilEGLConfig {
|
||||
var ok bool
|
||||
visID, ok = eglGetConfigAttrib(disp, eglCfg, _EGL_NATIVE_VISUAL_ID)
|
||||
if !ok {
|
||||
return nil, errors.New("newContext: eglGetConfigAttrib for _EGL_NATIVE_VISUAL_ID failed")
|
||||
}
|
||||
}
|
||||
ctxAttribs := []_EGLint{
|
||||
_EGL_CONTEXT_CLIENT_VERSION, 3,
|
||||
_EGL_NONE,
|
||||
}
|
||||
eglCtx := eglCreateContext(disp, eglCfg, nilEGLContext, ctxAttribs)
|
||||
if eglCtx == nilEGLContext {
|
||||
// Fall back to OpenGL ES 2 and rely on extensions.
|
||||
ctxAttribs := []_EGLint{
|
||||
_EGL_CONTEXT_CLIENT_VERSION, 2,
|
||||
_EGL_NONE,
|
||||
}
|
||||
eglCtx = eglCreateContext(disp, eglCfg, nilEGLContext, ctxAttribs)
|
||||
if eglCtx == nilEGLContext {
|
||||
return nil, fmt.Errorf("eglCreateContext failed: 0x%x", eglGetError())
|
||||
}
|
||||
}
|
||||
return &eglContext{
|
||||
config: _EGLConfig(eglCfg),
|
||||
ctx: _EGLContext(eglCtx),
|
||||
visualID: int(visID),
|
||||
srgb: srgb,
|
||||
surfaceless: hasExtension(exts, "EGL_KHR_surfaceless_context"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func createSurface(disp _EGLDisplay, eglCtx *eglContext, win NativeWindowType) (_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(disp, eglCtx.config, win, surfAttribs)
|
||||
if eglSurf == nilEGLSurface && eglCtx.srgb {
|
||||
// Try again without sRGB.
|
||||
eglCtx.srgb = false
|
||||
surfAttribs = []_EGLint{_EGL_NONE}
|
||||
eglSurf = eglCreateWindowSurface(disp, eglCtx.config, win, surfAttribs)
|
||||
}
|
||||
if eglSurf == nilEGLSurface {
|
||||
return nilEGLSurface, fmt.Errorf("newContext: eglCreateWindowSurface failed 0x%x (sRGB=%v)", eglGetError(), eglCtx.srgb)
|
||||
}
|
||||
return eglSurf, nil
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build linux || freebsd || openbsd
|
||||
// +build linux freebsd openbsd
|
||||
|
||||
package egl
|
||||
|
||||
/*
|
||||
#cgo linux,!android pkg-config: egl
|
||||
#cgo freebsd openbsd android LDFLAGS: -lEGL
|
||||
#cgo freebsd CFLAGS: -I/usr/local/include
|
||||
#cgo freebsd LDFLAGS: -L/usr/local/lib
|
||||
#cgo openbsd CFLAGS: -I/usr/X11R6/include
|
||||
#cgo openbsd LDFLAGS: -L/usr/X11R6/lib
|
||||
#cgo CFLAGS: -DEGL_NO_X11
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type (
|
||||
_EGLint = C.EGLint
|
||||
_EGLDisplay = C.EGLDisplay
|
||||
_EGLConfig = C.EGLConfig
|
||||
_EGLContext = C.EGLContext
|
||||
_EGLSurface = C.EGLSurface
|
||||
NativeDisplayType = C.EGLNativeDisplayType
|
||||
NativeWindowType = C.EGLNativeWindowType
|
||||
)
|
||||
|
||||
func loadEGL() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
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 nilEGLConfig, 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))
|
||||
}
|
||||
|
||||
func eglGetDisplay(disp NativeDisplayType) _EGLDisplay {
|
||||
return C.eglGetDisplay(disp)
|
||||
}
|
||||
|
||||
func eglCreateWindowSurface(disp _EGLDisplay, conf _EGLConfig, win NativeWindowType, attribs []_EGLint) _EGLSurface {
|
||||
eglSurf := C.eglCreateWindowSurface(disp, conf, win, &attribs[0])
|
||||
return eglSurf
|
||||
}
|
||||
|
||||
func eglWaitClient() bool {
|
||||
return C.eglWaitClient() == C.EGL_TRUE
|
||||
}
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package egl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
syscall "golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type (
|
||||
_EGLint int32
|
||||
_EGLDisplay uintptr
|
||||
_EGLConfig uintptr
|
||||
_EGLContext uintptr
|
||||
_EGLSurface uintptr
|
||||
NativeDisplayType uintptr
|
||||
NativeWindowType uintptr
|
||||
)
|
||||
|
||||
var (
|
||||
libEGL = syscall.DLL{}
|
||||
_eglChooseConfig *syscall.Proc
|
||||
_eglCreateContext *syscall.Proc
|
||||
_eglCreateWindowSurface *syscall.Proc
|
||||
_eglDestroyContext *syscall.Proc
|
||||
_eglDestroySurface *syscall.Proc
|
||||
_eglGetConfigAttrib *syscall.Proc
|
||||
_eglGetDisplay *syscall.Proc
|
||||
_eglGetError *syscall.Proc
|
||||
_eglInitialize *syscall.Proc
|
||||
_eglMakeCurrent *syscall.Proc
|
||||
_eglReleaseThread *syscall.Proc
|
||||
_eglSwapInterval *syscall.Proc
|
||||
_eglSwapBuffers *syscall.Proc
|
||||
_eglTerminate *syscall.Proc
|
||||
_eglQueryString *syscall.Proc
|
||||
_eglWaitClient *syscall.Proc
|
||||
)
|
||||
|
||||
var loadOnce = sync.OnceValue(loadDLLs)
|
||||
|
||||
func loadEGL() error {
|
||||
return loadOnce()
|
||||
}
|
||||
|
||||
func loadDLLs() error {
|
||||
if err := loadDLL(&libEGL, "libEGL.dll"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
procs := map[string]**syscall.Proc{
|
||||
"eglChooseConfig": &_eglChooseConfig,
|
||||
"eglCreateContext": &_eglCreateContext,
|
||||
"eglCreateWindowSurface": &_eglCreateWindowSurface,
|
||||
"eglDestroyContext": &_eglDestroyContext,
|
||||
"eglDestroySurface": &_eglDestroySurface,
|
||||
"eglGetConfigAttrib": &_eglGetConfigAttrib,
|
||||
"eglGetDisplay": &_eglGetDisplay,
|
||||
"eglGetError": &_eglGetError,
|
||||
"eglInitialize": &_eglInitialize,
|
||||
"eglMakeCurrent": &_eglMakeCurrent,
|
||||
"eglReleaseThread": &_eglReleaseThread,
|
||||
"eglSwapInterval": &_eglSwapInterval,
|
||||
"eglSwapBuffers": &_eglSwapBuffers,
|
||||
"eglTerminate": &_eglTerminate,
|
||||
"eglQueryString": &_eglQueryString,
|
||||
"eglWaitClient": &_eglWaitClient,
|
||||
}
|
||||
for name, proc := range procs {
|
||||
p, err := libEGL.FindProc(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to locate %s in %s: %w", name, libEGL.Name, err)
|
||||
}
|
||||
*proc = p
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadDLL(dll *syscall.DLL, name string) error {
|
||||
handle, err := syscall.LoadLibraryEx(name, 0, syscall.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)
|
||||
if err != nil {
|
||||
return fmt.Errorf("egl: failed to load %s: %v", name, err)
|
||||
}
|
||||
dll.Handle = handle
|
||||
dll.Name = name
|
||||
return nil
|
||||
}
|
||||
|
||||
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 NativeWindowType, 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 NativeDisplayType) _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 syscall.BytePtrToString((*byte)(unsafe.Pointer(r)))
|
||||
}
|
||||
|
||||
func eglWaitClient() bool {
|
||||
r, _, _ := _eglWaitClient.Call()
|
||||
return r != 0
|
||||
}
|
||||
|
||||
// issue34474KeepAlive calls runtime.KeepAlive as a
|
||||
// workaround for golang.org/issue/34474.
|
||||
func issue34474KeepAlive(v any) {
|
||||
runtime.KeepAlive(v)
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
/*
|
||||
Package f32 is an internal version of the public package f32 with
|
||||
extra types for internal use.
|
||||
*/
|
||||
package f32
|
||||
|
||||
import (
|
||||
"image"
|
||||
"math"
|
||||
|
||||
"gioui.org/f32"
|
||||
)
|
||||
|
||||
type Point = f32.Point
|
||||
|
||||
type Affine2D = f32.Affine2D
|
||||
|
||||
var NewAffine2D = f32.NewAffine2D
|
||||
|
||||
var AffineId = f32.AffineId
|
||||
|
||||
// A Rectangle contains the points (X, Y) where Min.X <= X < Max.X,
|
||||
// Min.Y <= Y < Max.Y.
|
||||
type Rectangle struct {
|
||||
Min, Max Point
|
||||
}
|
||||
|
||||
// String return a string representation of r.
|
||||
func (r Rectangle) String() string {
|
||||
return r.Min.String() + "-" + r.Max.String()
|
||||
}
|
||||
|
||||
// Rect is a shorthand for Rectangle{Point{x0, y0}, Point{x1, y1}}.
|
||||
// The returned Rectangle has x0 and y0 swapped if necessary so that
|
||||
// it's correctly formed.
|
||||
func Rect(x0, y0, x1, y1 float32) Rectangle {
|
||||
if x0 > x1 {
|
||||
x0, x1 = x1, x0
|
||||
}
|
||||
if y0 > y1 {
|
||||
y0, y1 = y1, y0
|
||||
}
|
||||
return Rectangle{Point{x0, y0}, Point{x1, y1}}
|
||||
}
|
||||
|
||||
// Pt is shorthand for Point{X: x, Y: y}.
|
||||
var Pt = f32.Pt
|
||||
|
||||
// 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
|
||||
}
|
||||
if r.Empty() {
|
||||
return Rectangle{}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Union returns the union of r and s.
|
||||
func (r Rectangle) Union(s Rectangle) Rectangle {
|
||||
if r.Empty() {
|
||||
return s
|
||||
}
|
||||
if s.Empty() {
|
||||
return r
|
||||
}
|
||||
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},
|
||||
}
|
||||
}
|
||||
|
||||
// Round returns the smallest integer rectangle that
|
||||
// contains r.
|
||||
func (r Rectangle) Round() image.Rectangle {
|
||||
return image.Rectangle{
|
||||
Min: image.Point{
|
||||
X: int(floor(r.Min.X)),
|
||||
Y: int(floor(r.Min.Y)),
|
||||
},
|
||||
Max: image.Point{
|
||||
X: int(ceil(r.Max.X)),
|
||||
Y: int(ceil(r.Max.Y)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// fRect converts a rectangle to a f32internal.Rectangle.
|
||||
func FRect(r image.Rectangle) Rectangle {
|
||||
return Rectangle{
|
||||
Min: FPt(r.Min), Max: FPt(r.Max),
|
||||
}
|
||||
}
|
||||
|
||||
// Fpt converts an point to a f32.Point.
|
||||
func FPt(p image.Point) Point {
|
||||
return Point{
|
||||
X: float32(p.X), Y: float32(p.Y),
|
||||
}
|
||||
}
|
||||
|
||||
func ceil(v float32) int {
|
||||
return int(math.Ceil(float64(v)))
|
||||
}
|
||||
|
||||
func floor(v float32) int {
|
||||
return int(math.Floor(float64(v)))
|
||||
}
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package f32color
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"math"
|
||||
)
|
||||
|
||||
//go:generate go run ./f32colorgen -out tables.go
|
||||
|
||||
// RGBA is a 32 bit floating point linear premultiplied color space.
|
||||
type RGBA struct {
|
||||
R, G, B, A float32
|
||||
}
|
||||
|
||||
// Array returns rgba values in a [4]float32 array.
|
||||
func (rgba RGBA) Array() [4]float32 {
|
||||
return [4]float32{rgba.R, rgba.G, rgba.B, rgba.A}
|
||||
}
|
||||
|
||||
// Float32 returns r, g, b, a values.
|
||||
func (col RGBA) Float32() (r, g, b, a float32) {
|
||||
return col.R, col.G, col.B, col.A
|
||||
}
|
||||
|
||||
// SRGBA converts from linear to sRGB color space.
|
||||
func (col RGBA) SRGB() color.NRGBA {
|
||||
if col.A == 0 {
|
||||
return color.NRGBA{}
|
||||
}
|
||||
return color.NRGBA{
|
||||
R: uint8(linearTosRGB(col.R/col.A)*255 + .5),
|
||||
G: uint8(linearTosRGB(col.G/col.A)*255 + .5),
|
||||
B: uint8(linearTosRGB(col.B/col.A)*255 + .5),
|
||||
A: uint8(col.A*255 + .5),
|
||||
}
|
||||
}
|
||||
|
||||
// Luminance calculates the relative luminance of a linear RGBA color.
|
||||
// Normalized to 0 for black and 1 for white.
|
||||
//
|
||||
// See https://www.w3.org/TR/WCAG20/#relativeluminancedef for more details
|
||||
func (col RGBA) Luminance() float32 {
|
||||
return 0.2126*col.R + 0.7152*col.G + 0.0722*col.B
|
||||
}
|
||||
|
||||
// Opaque returns the color without alpha component.
|
||||
func (col RGBA) Opaque() RGBA {
|
||||
col.A = 1.0
|
||||
return col
|
||||
}
|
||||
|
||||
// LinearFromSRGB converts from col in the sRGB colorspace to RGBA.
|
||||
func LinearFromSRGB(col color.NRGBA) RGBA {
|
||||
af := float32(col.A) / 0xFF
|
||||
return RGBA{
|
||||
R: srgb8ToLinear[col.R] * af, // sRGBToLinear(float32(col.R)/0xff) * af,
|
||||
G: srgb8ToLinear[col.G] * af, // sRGBToLinear(float32(col.G)/0xff) * af,
|
||||
B: srgb8ToLinear[col.B] * af, // sRGBToLinear(float32(col.B)/0xff) * af,
|
||||
A: af,
|
||||
}
|
||||
}
|
||||
|
||||
// NRGBAToRGBA converts from non-premultiplied sRGB color to premultiplied sRGB color.
|
||||
//
|
||||
// Each component in the result is `sRGBToLinear(c * alpha)`, where `c`
|
||||
// is the linear color.
|
||||
func NRGBAToRGBA(col color.NRGBA) color.RGBA {
|
||||
if col.A == 0xFF {
|
||||
return color.RGBA(col)
|
||||
}
|
||||
c := LinearFromSRGB(col)
|
||||
return color.RGBA{
|
||||
R: uint8(linearTosRGB(c.R)*255 + .5),
|
||||
G: uint8(linearTosRGB(c.G)*255 + .5),
|
||||
B: uint8(linearTosRGB(c.B)*255 + .5),
|
||||
A: col.A,
|
||||
}
|
||||
}
|
||||
|
||||
// NRGBAToLinearRGBA converts from non-premultiplied sRGB color to premultiplied linear RGBA color.
|
||||
//
|
||||
// Each component in the result is `c * alpha`, where `c` is the linear color.
|
||||
func NRGBAToLinearRGBA(col color.NRGBA) color.RGBA {
|
||||
if col.A == 0xFF {
|
||||
return color.RGBA(col)
|
||||
}
|
||||
c := LinearFromSRGB(col)
|
||||
return color.RGBA{
|
||||
R: uint8(c.R*255 + .5),
|
||||
G: uint8(c.G*255 + .5),
|
||||
B: uint8(c.B*255 + .5),
|
||||
A: col.A,
|
||||
}
|
||||
}
|
||||
|
||||
// RGBAToNRGBA converts from premultiplied sRGB color to non-premultiplied sRGB color.
|
||||
func RGBAToNRGBA(col color.RGBA) color.NRGBA {
|
||||
if col.A == 0xFF {
|
||||
return color.NRGBA(col)
|
||||
}
|
||||
|
||||
linear := RGBA{
|
||||
R: sRGBToLinear(float32(col.R) / 0xff),
|
||||
G: sRGBToLinear(float32(col.G) / 0xff),
|
||||
B: sRGBToLinear(float32(col.B) / 0xff),
|
||||
A: float32(col.A) / 0xff,
|
||||
}
|
||||
|
||||
return linear.SRGB()
|
||||
}
|
||||
|
||||
// linearTosRGB transforms color value from linear to sRGB.
|
||||
func linearTosRGB(c float32) float32 {
|
||||
// Formula from EXT_sRGB.
|
||||
switch {
|
||||
case c <= 0:
|
||||
return 0
|
||||
case 0 < c && c < 0.0031308:
|
||||
return 12.92 * c
|
||||
case 0.0031308 <= c && c < 1:
|
||||
return 1.055*float32(math.Pow(float64(c), 0.41666)) - 0.055
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
// sRGBToLinear transforms color value from sRGB to linear.
|
||||
func sRGBToLinear(c float32) float32 {
|
||||
// Formula from EXT_sRGB.
|
||||
if c <= 0.04045 {
|
||||
return c / 12.92
|
||||
} else {
|
||||
return float32(math.Pow(float64((c+0.055)/1.055), 2.4))
|
||||
}
|
||||
}
|
||||
|
||||
// MulAlpha applies the alpha to the color.
|
||||
func MulAlpha(c color.NRGBA, alpha uint8) color.NRGBA {
|
||||
c.A = uint8(uint32(c.A) * uint32(alpha) / 0xFF)
|
||||
return c
|
||||
}
|
||||
|
||||
// Disabled blends color towards the luminance and multiplies alpha.
|
||||
// Blending towards luminance will desaturate the color.
|
||||
// Multiplying alpha blends the color together more with the background.
|
||||
func Disabled(c color.NRGBA) (d color.NRGBA) {
|
||||
const r = 80 // blend ratio
|
||||
lum := approxLuminance(c)
|
||||
d = mix(c, color.NRGBA{A: c.A, R: lum, G: lum, B: lum}, r)
|
||||
d = MulAlpha(d, 128+32)
|
||||
return
|
||||
}
|
||||
|
||||
// Hovered blends dark colors towards white, and light colors towards
|
||||
// black. It is approximate because it operates in non-linear sRGB space.
|
||||
func Hovered(c color.NRGBA) (h color.NRGBA) {
|
||||
if c.A == 0 {
|
||||
// Provide a reasonable default for transparent widgets.
|
||||
return color.NRGBA{A: 0x44, R: 0x88, G: 0x88, B: 0x88}
|
||||
}
|
||||
const ratio = 0x20
|
||||
m := color.NRGBA{R: 0xff, G: 0xff, B: 0xff, A: c.A}
|
||||
if approxLuminance(c) > 128 {
|
||||
m = color.NRGBA{A: c.A}
|
||||
}
|
||||
return mix(m, c, ratio)
|
||||
}
|
||||
|
||||
// mix mixes c1 and c2 weighted by (1 - a/256) and a/256 respectively.
|
||||
func mix(c1, c2 color.NRGBA, a uint8) color.NRGBA {
|
||||
ai := int(a)
|
||||
return color.NRGBA{
|
||||
R: byte((int(c1.R)*ai + int(c2.R)*(256-ai)) / 256),
|
||||
G: byte((int(c1.G)*ai + int(c2.G)*(256-ai)) / 256),
|
||||
B: byte((int(c1.B)*ai + int(c2.B)*(256-ai)) / 256),
|
||||
A: byte((int(c1.A)*ai + int(c2.A)*(256-ai)) / 256),
|
||||
}
|
||||
}
|
||||
|
||||
// approxLuminance is a fast approximate version of RGBA.Luminance.
|
||||
func approxLuminance(c color.NRGBA) byte {
|
||||
const (
|
||||
r = 13933 // 0.2126 * 256 * 256
|
||||
g = 46871 // 0.7152 * 256 * 256
|
||||
b = 4732 // 0.0722 * 256 * 256
|
||||
t = r + g + b
|
||||
)
|
||||
return byte((r*int(c.R) + g*int(c.G) + b*int(c.B)) / t)
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// Code generated by f32colorgen. DO NOT EDIT.
|
||||
|
||||
package f32color
|
||||
|
||||
// table corresponds to sRGBToLinear(float32(index)/0xff)
|
||||
var srgb8ToLinear = [...]float32{
|
||||
0, 0.000303527, 0.000607054, 0.000910581, 0.001214108, 0.001517635, 0.001821162, 0.0021246888, 0.002428216, 0.002731743, 0.00303527, 0.0033465363, 0.0036765079, 0.004024718, 0.004391443, 0.004776954,
|
||||
0.005181518, 0.0056053926, 0.006048834, 0.0065120924, 0.0069954116, 0.007499033, 0.008023194, 0.008568126, 0.009134059, 0.00972122, 0.010329825, 0.010960096, 0.011612247, 0.012286489, 0.0129830325, 0.013702083,
|
||||
0.014443846, 0.015208517, 0.015996296, 0.016807377, 0.017641956, 0.01850022, 0.019382365, 0.020288566, 0.021219013, 0.022173887, 0.023153368, 0.024157634, 0.025186861, 0.026241226, 0.027320895, 0.028426042,
|
||||
0.029556837, 0.030713446, 0.031896036, 0.033104766, 0.03433981, 0.035601318, 0.03688945, 0.03820437, 0.039546244, 0.040915202, 0.042311415, 0.043735035, 0.04518621, 0.04666509, 0.048171826, 0.049706567,
|
||||
0.05126947, 0.05286066, 0.05448029, 0.056128502, 0.05780544, 0.059511248, 0.06124608, 0.06301004, 0.06480329, 0.06662596, 0.06847819, 0.07036012, 0.07227187, 0.07421359, 0.0761854, 0.078187436,
|
||||
0.080219835, 0.08228272, 0.08437622, 0.08650047, 0.08865561, 0.09084174, 0.09305899, 0.09530749, 0.09758737, 0.09989875, 0.102241755, 0.10461651, 0.10702312, 0.10946173, 0.11193245, 0.11443539,
|
||||
0.11697068, 0.11953844, 0.122138806, 0.12477185, 0.12743771, 0.1301365, 0.13286835, 0.13563335, 0.13843164, 0.14126332, 0.14412849, 0.14702728, 0.1499598, 0.15292618, 0.15592648, 0.15896088,
|
||||
0.16202942, 0.16513222, 0.16826941, 0.17144111, 0.1746474, 0.17788842, 0.18116425, 0.18447499, 0.18782078, 0.19120169, 0.19461782, 0.19806932, 0.20155625, 0.20507872, 0.20863685, 0.21223074,
|
||||
0.21586055, 0.21952623, 0.223228, 0.2269659, 0.23074009, 0.23455067, 0.23839766, 0.24228121, 0.24620141, 0.25015837, 0.25415218, 0.25818294, 0.26225075, 0.2663557, 0.27049786, 0.2746774,
|
||||
0.27889434, 0.28314883, 0.2874409, 0.29177073, 0.29613835, 0.30054384, 0.30498737, 0.30946898, 0.31398878, 0.31854683, 0.32314327, 0.32777816, 0.33245158, 0.33716366, 0.34191447, 0.3467041,
|
||||
0.35153273, 0.35640025, 0.3613069, 0.36625272, 0.37123778, 0.37626222, 0.3813261, 0.38642955, 0.39157256, 0.39675534, 0.40197787, 0.4072403, 0.4125427, 0.41788515, 0.42326775, 0.42869058,
|
||||
0.4341537, 0.43965724, 0.44520128, 0.45078585, 0.4564111, 0.46207705, 0.46778387, 0.47353154, 0.47932023, 0.48515, 0.4910209, 0.49693304, 0.5028866, 0.50888145, 0.5149178, 0.5209957,
|
||||
0.5271153, 0.53327656, 0.5394796, 0.5457246, 0.55201155, 0.5583405, 0.56471163, 0.5711249, 0.5775806, 0.58407855, 0.59061897, 0.5972019, 0.6038274, 0.6104957, 0.61720663, 0.6239605,
|
||||
0.6307572, 0.63759696, 0.64447975, 0.6514057, 0.6583749, 0.66538733, 0.6724432, 0.67954254, 0.6866855, 0.6938719, 0.7011021, 0.70837593, 0.71569365, 0.7230553, 0.7304609, 0.73791057,
|
||||
0.74540436, 0.7529423, 0.76052463, 0.7681513, 0.77582234, 0.7835379, 0.79129803, 0.79910284, 0.80695236, 0.8148467, 0.82278585, 0.83076996, 0.8387991, 0.84687334, 0.8549927, 0.8631573,
|
||||
0.8713672, 0.87962234, 0.8879232, 0.89626944, 0.90466136, 0.9130987, 0.92158204, 0.9301109, 0.9386859, 0.9473066, 0.9559735, 0.9646863, 0.9734455, 0.9822506, 0.9911022, 1,
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package fling
|
||||
|
||||
import (
|
||||
"math"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
type Animation struct {
|
||||
// Current offset in pixels.
|
||||
x float32
|
||||
// Initial time.
|
||||
t0 time.Time
|
||||
// Initial velocity in pixels pr second.
|
||||
v0 float32
|
||||
}
|
||||
|
||||
const (
|
||||
// dp/second.
|
||||
minFlingVelocity = unit.Dp(50)
|
||||
maxFlingVelocity = unit.Dp(8000)
|
||||
thresholdVelocity = 1
|
||||
)
|
||||
|
||||
// Start a fling given a starting velocity. Returns whether a
|
||||
// fling was started.
|
||||
func (f *Animation) Start(c unit.Metric, now time.Time, velocity float32) bool {
|
||||
min := float32(c.Dp(minFlingVelocity))
|
||||
v := velocity
|
||||
if -min <= v && v <= min {
|
||||
return false
|
||||
}
|
||||
max := float32(c.Dp(maxFlingVelocity))
|
||||
if v > max {
|
||||
v = max
|
||||
} else if v < -max {
|
||||
v = -max
|
||||
}
|
||||
f.init(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(dist)
|
||||
f.x += float32(idist)
|
||||
// Solving for the velocity x'(t) gives us
|
||||
//
|
||||
// x'(t) = v0*e^(k*t)
|
||||
v := f.v0 * ekt
|
||||
if -thresholdVelocity < v && v < thresholdVelocity {
|
||||
f.v0 = 0
|
||||
}
|
||||
return idist
|
||||
}
|
||||
+332
@@ -0,0 +1,332 @@
|
||||
// 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 := range e.samples {
|
||||
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 := range Q.rows {
|
||||
// Copy A column.
|
||||
for j := range Q.cols {
|
||||
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 := range i {
|
||||
d := dot(Q.col(j), Q.col(i))
|
||||
for k := range Q.cols {
|
||||
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 := range Q.cols {
|
||||
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 := range m.rows {
|
||||
for col := range m.cols {
|
||||
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 := range m.rows {
|
||||
for j := range m.cols {
|
||||
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 := range mm.rows {
|
||||
for j := range mm.cols {
|
||||
var v float32
|
||||
for k := range m.rows {
|
||||
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 := range m.rows {
|
||||
for j := range m.cols {
|
||||
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
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package gl
|
||||
|
||||
type (
|
||||
Attrib uint
|
||||
Enum uint
|
||||
)
|
||||
|
||||
const (
|
||||
ACTIVE_TEXTURE = 0x84E0
|
||||
ALL_BARRIER_BITS = 0xffffffff
|
||||
ARRAY_BUFFER = 0x8892
|
||||
ARRAY_BUFFER_BINDING = 0x8894
|
||||
BACK = 0x0405
|
||||
BLEND = 0xbe2
|
||||
BLEND_DST_RGB = 0x80C8
|
||||
BLEND_SRC_RGB = 0x80C9
|
||||
BLEND_DST_ALPHA = 0x80CA
|
||||
BLEND_SRC_ALPHA = 0x80CB
|
||||
CLAMP_TO_EDGE = 0x812f
|
||||
COLOR_ATTACHMENT0 = 0x8ce0
|
||||
COLOR_BUFFER_BIT = 0x4000
|
||||
COLOR_CLEAR_VALUE = 0x0C22
|
||||
COMPILE_STATUS = 0x8b81
|
||||
COMPUTE_SHADER = 0x91B9
|
||||
CURRENT_PROGRAM = 0x8B8D
|
||||
DEPTH_ATTACHMENT = 0x8d00
|
||||
DEPTH_BUFFER_BIT = 0x100
|
||||
DEPTH_CLEAR_VALUE = 0x0B73
|
||||
DEPTH_COMPONENT16 = 0x81a5
|
||||
DEPTH_COMPONENT24 = 0x81A6
|
||||
DEPTH_COMPONENT32F = 0x8CAC
|
||||
DEPTH_FUNC = 0x0B74
|
||||
DEPTH_TEST = 0xb71
|
||||
DEPTH_WRITEMASK = 0x0B72
|
||||
DRAW_FRAMEBUFFER = 0x8CA9
|
||||
DST_COLOR = 0x306
|
||||
DYNAMIC_DRAW = 0x88E8
|
||||
DYNAMIC_READ = 0x88E9
|
||||
ELEMENT_ARRAY_BUFFER = 0x8893
|
||||
ELEMENT_ARRAY_BUFFER_BINDING = 0x8895
|
||||
EXTENSIONS = 0x1f03
|
||||
FALSE = 0
|
||||
FLOAT = 0x1406
|
||||
FRAGMENT_SHADER = 0x8b30
|
||||
FRAMEBUFFER = 0x8d40
|
||||
FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING = 0x8210
|
||||
FRAMEBUFFER_BINDING = 0x8ca6
|
||||
FRAMEBUFFER_COMPLETE = 0x8cd5
|
||||
FRAMEBUFFER_SRGB = 0x8db9
|
||||
HALF_FLOAT = 0x140b
|
||||
HALF_FLOAT_OES = 0x8d61
|
||||
INFO_LOG_LENGTH = 0x8B84
|
||||
INVALID_INDEX = ^uint(0)
|
||||
GREATER = 0x204
|
||||
GEQUAL = 0x206
|
||||
LINEAR = 0x2601
|
||||
LINEAR_MIPMAP_LINEAR = 0x2703
|
||||
LINK_STATUS = 0x8b82
|
||||
LUMINANCE = 0x1909
|
||||
MAP_READ_BIT = 0x0001
|
||||
MAX_TEXTURE_SIZE = 0xd33
|
||||
NEAREST = 0x2600
|
||||
NO_ERROR = 0x0
|
||||
NUM_EXTENSIONS = 0x821D
|
||||
ONE = 0x1
|
||||
ONE_MINUS_SRC_ALPHA = 0x303
|
||||
PACK_ROW_LENGTH = 0x0D02
|
||||
PROGRAM_BINARY_LENGTH = 0x8741
|
||||
QUERY_RESULT = 0x8866
|
||||
QUERY_RESULT_AVAILABLE = 0x8867
|
||||
R16F = 0x822d
|
||||
R8 = 0x8229
|
||||
READ_FRAMEBUFFER = 0x8ca8
|
||||
READ_FRAMEBUFFER_BINDING = 0x8CAA
|
||||
READ_ONLY = 0x88B8
|
||||
READ_WRITE = 0x88BA
|
||||
RED = 0x1903
|
||||
RENDERER = 0x1F01
|
||||
RENDERBUFFER = 0x8d41
|
||||
RENDERBUFFER_BINDING = 0x8ca7
|
||||
RENDERBUFFER_HEIGHT = 0x8d43
|
||||
RENDERBUFFER_WIDTH = 0x8d42
|
||||
RGB = 0x1907
|
||||
RGBA = 0x1908
|
||||
RGBA8 = 0x8058
|
||||
SHADER_STORAGE_BUFFER = 0x90D2
|
||||
SHADER_STORAGE_BUFFER_BINDING = 0x90D3
|
||||
SHORT = 0x1402
|
||||
SRGB = 0x8c40
|
||||
SRGB_ALPHA_EXT = 0x8c42
|
||||
SRGB8 = 0x8c41
|
||||
SRGB8_ALPHA8 = 0x8c43
|
||||
STATIC_DRAW = 0x88e4
|
||||
STENCIL_BUFFER_BIT = 0x00000400
|
||||
TEXTURE_2D = 0xde1
|
||||
TEXTURE_BINDING_2D = 0x8069
|
||||
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
|
||||
TRUE = 1
|
||||
UNIFORM_BUFFER = 0x8A11
|
||||
UNIFORM_BUFFER_BINDING = 0x8A28
|
||||
UNPACK_ALIGNMENT = 0xcf5
|
||||
UNPACK_ROW_LENGTH = 0x0CF2
|
||||
UNSIGNED_BYTE = 0x1401
|
||||
UNSIGNED_SHORT = 0x1403
|
||||
VIEWPORT = 0x0BA2
|
||||
VERSION = 0x1f02
|
||||
VERTEX_ARRAY_BINDING = 0x85B5
|
||||
VERTEX_SHADER = 0x8b31
|
||||
VERTEX_ATTRIB_ARRAY_BUFFER_BINDING = 0x889F
|
||||
VERTEX_ATTRIB_ARRAY_ENABLED = 0x8622
|
||||
VERTEX_ATTRIB_ARRAY_POINTER = 0x8645
|
||||
VERTEX_ATTRIB_ARRAY_NORMALIZED = 0x886A
|
||||
VERTEX_ATTRIB_ARRAY_SIZE = 0x8623
|
||||
VERTEX_ATTRIB_ARRAY_STRIDE = 0x8624
|
||||
VERTEX_ATTRIB_ARRAY_TYPE = 0x8625
|
||||
WRITE_ONLY = 0x88B9
|
||||
ZERO = 0x0
|
||||
|
||||
// EXT_disjoint_timer_query
|
||||
TIME_ELAPSED_EXT = 0x88BF
|
||||
GPU_DISJOINT_EXT = 0x8FBB
|
||||
)
|
||||
+748
@@ -0,0 +1,748 @@
|
||||
// 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 reference to the Uint8Array JS type.
|
||||
uint8Array js.Value
|
||||
|
||||
// Cached JS arrays.
|
||||
arrayBuf js.Value
|
||||
int32Buf js.Value
|
||||
|
||||
isWebGL2 bool
|
||||
|
||||
_getExtension js.Value
|
||||
_activeTexture js.Value
|
||||
_attachShader js.Value
|
||||
_beginQuery js.Value
|
||||
_beginQueryEXT js.Value
|
||||
_bindAttribLocation js.Value
|
||||
_bindBuffer js.Value
|
||||
_bindBufferBase js.Value
|
||||
_bindFramebuffer js.Value
|
||||
_bindRenderbuffer js.Value
|
||||
_bindTexture js.Value
|
||||
_blendEquation js.Value
|
||||
_blendFunc js.Value
|
||||
_bufferData js.Value
|
||||
_bufferSubData js.Value
|
||||
_checkFramebufferStatus js.Value
|
||||
_clear js.Value
|
||||
_clearColor js.Value
|
||||
_clearDepth js.Value
|
||||
_compileShader js.Value
|
||||
_copyTexSubImage2D js.Value
|
||||
_createBuffer js.Value
|
||||
_createFramebuffer js.Value
|
||||
_createProgram js.Value
|
||||
_createQuery js.Value
|
||||
_createRenderbuffer js.Value
|
||||
_createShader js.Value
|
||||
_createTexture js.Value
|
||||
_deleteBuffer js.Value
|
||||
_deleteFramebuffer js.Value
|
||||
_deleteProgram js.Value
|
||||
_deleteQuery js.Value
|
||||
_deleteQueryEXT js.Value
|
||||
_deleteShader js.Value
|
||||
_deleteRenderbuffer js.Value
|
||||
_deleteTexture js.Value
|
||||
_depthFunc js.Value
|
||||
_depthMask js.Value
|
||||
_disableVertexAttribArray js.Value
|
||||
_disable js.Value
|
||||
_drawArrays js.Value
|
||||
_drawElements js.Value
|
||||
_enable js.Value
|
||||
_enableVertexAttribArray js.Value
|
||||
_endQuery js.Value
|
||||
_endQueryEXT js.Value
|
||||
_finish js.Value
|
||||
_flush js.Value
|
||||
_framebufferRenderbuffer js.Value
|
||||
_framebufferTexture2D js.Value
|
||||
_generateMipmap js.Value
|
||||
_getRenderbufferParameteri js.Value
|
||||
_getFramebufferAttachmentParameter js.Value
|
||||
_getParameter js.Value
|
||||
_getIndexedParameter js.Value
|
||||
_getProgramParameter js.Value
|
||||
_getProgramInfoLog js.Value
|
||||
_getQueryParameter js.Value
|
||||
_getQueryObjectEXT js.Value
|
||||
_getShaderParameter js.Value
|
||||
_getShaderInfoLog js.Value
|
||||
_getSupportedExtensions js.Value
|
||||
_getUniformBlockIndex js.Value
|
||||
_getUniformLocation js.Value
|
||||
_getVertexAttrib js.Value
|
||||
_getVertexAttribOffset js.Value
|
||||
_invalidateFramebuffer js.Value
|
||||
_isEnabled js.Value
|
||||
_linkProgram js.Value
|
||||
_pixelStorei js.Value
|
||||
_renderbufferStorage js.Value
|
||||
_readPixels js.Value
|
||||
_scissor js.Value
|
||||
_shaderSource js.Value
|
||||
_texImage2D js.Value
|
||||
_texStorage2D js.Value
|
||||
_texSubImage2D js.Value
|
||||
_texParameteri js.Value
|
||||
_uniformBlockBinding js.Value
|
||||
_uniform1f js.Value
|
||||
_uniform1i js.Value
|
||||
_uniform2f js.Value
|
||||
_uniform3f js.Value
|
||||
_uniform4f js.Value
|
||||
_useProgram js.Value
|
||||
_vertexAttribPointer js.Value
|
||||
_viewport js.Value
|
||||
}
|
||||
|
||||
type Context js.Value
|
||||
|
||||
func NewFunctions(ctx Context, forceES bool) (*Functions, error) {
|
||||
webgl := js.Value(ctx)
|
||||
f := &Functions{
|
||||
Ctx: webgl,
|
||||
uint8Array: js.Global().Get("Uint8Array"),
|
||||
_getExtension: _bind(webgl, `getExtension`),
|
||||
_activeTexture: _bind(webgl, `activeTexture`),
|
||||
_attachShader: _bind(webgl, `attachShader`),
|
||||
_beginQuery: _bind(webgl, `beginQuery`),
|
||||
_beginQueryEXT: _bind(webgl, `beginQueryEXT`),
|
||||
_bindAttribLocation: _bind(webgl, `bindAttribLocation`),
|
||||
_bindBuffer: _bind(webgl, `bindBuffer`),
|
||||
_bindBufferBase: _bind(webgl, `bindBufferBase`),
|
||||
_bindFramebuffer: _bind(webgl, `bindFramebuffer`),
|
||||
_bindRenderbuffer: _bind(webgl, `bindRenderbuffer`),
|
||||
_bindTexture: _bind(webgl, `bindTexture`),
|
||||
_blendEquation: _bind(webgl, `blendEquation`),
|
||||
_blendFunc: _bind(webgl, `blendFunc`),
|
||||
_bufferData: _bind(webgl, `bufferData`),
|
||||
_bufferSubData: _bind(webgl, `bufferSubData`),
|
||||
_checkFramebufferStatus: _bind(webgl, `checkFramebufferStatus`),
|
||||
_clear: _bind(webgl, `clear`),
|
||||
_clearColor: _bind(webgl, `clearColor`),
|
||||
_clearDepth: _bind(webgl, `clearDepth`),
|
||||
_compileShader: _bind(webgl, `compileShader`),
|
||||
_copyTexSubImage2D: _bind(webgl, `copyTexSubImage2D`),
|
||||
_createBuffer: _bind(webgl, `createBuffer`),
|
||||
_createFramebuffer: _bind(webgl, `createFramebuffer`),
|
||||
_createProgram: _bind(webgl, `createProgram`),
|
||||
_createQuery: _bind(webgl, `createQuery`),
|
||||
_createRenderbuffer: _bind(webgl, `createRenderbuffer`),
|
||||
_createShader: _bind(webgl, `createShader`),
|
||||
_createTexture: _bind(webgl, `createTexture`),
|
||||
_deleteBuffer: _bind(webgl, `deleteBuffer`),
|
||||
_deleteFramebuffer: _bind(webgl, `deleteFramebuffer`),
|
||||
_deleteProgram: _bind(webgl, `deleteProgram`),
|
||||
_deleteQuery: _bind(webgl, `deleteQuery`),
|
||||
_deleteQueryEXT: _bind(webgl, `deleteQueryEXT`),
|
||||
_deleteShader: _bind(webgl, `deleteShader`),
|
||||
_deleteRenderbuffer: _bind(webgl, `deleteRenderbuffer`),
|
||||
_deleteTexture: _bind(webgl, `deleteTexture`),
|
||||
_depthFunc: _bind(webgl, `depthFunc`),
|
||||
_depthMask: _bind(webgl, `depthMask`),
|
||||
_disableVertexAttribArray: _bind(webgl, `disableVertexAttribArray`),
|
||||
_disable: _bind(webgl, `disable`),
|
||||
_drawArrays: _bind(webgl, `drawArrays`),
|
||||
_drawElements: _bind(webgl, `drawElements`),
|
||||
_enable: _bind(webgl, `enable`),
|
||||
_enableVertexAttribArray: _bind(webgl, `enableVertexAttribArray`),
|
||||
_endQuery: _bind(webgl, `endQuery`),
|
||||
_endQueryEXT: _bind(webgl, `endQueryEXT`),
|
||||
_finish: _bind(webgl, `finish`),
|
||||
_flush: _bind(webgl, `flush`),
|
||||
_framebufferRenderbuffer: _bind(webgl, `framebufferRenderbuffer`),
|
||||
_framebufferTexture2D: _bind(webgl, `framebufferTexture2D`),
|
||||
_generateMipmap: _bind(webgl, `generateMipmap`),
|
||||
_getRenderbufferParameteri: _bind(webgl, `getRenderbufferParameteri`),
|
||||
_getFramebufferAttachmentParameter: _bind(webgl, `getFramebufferAttachmentParameter`),
|
||||
_getParameter: _bind(webgl, `getParameter`),
|
||||
_getIndexedParameter: _bind(webgl, `getIndexedParameter`),
|
||||
_getProgramParameter: _bind(webgl, `getProgramParameter`),
|
||||
_getProgramInfoLog: _bind(webgl, `getProgramInfoLog`),
|
||||
_getQueryParameter: _bind(webgl, `getQueryParameter`),
|
||||
_getQueryObjectEXT: _bind(webgl, `getQueryObjectEXT`),
|
||||
_getShaderParameter: _bind(webgl, `getShaderParameter`),
|
||||
_getShaderInfoLog: _bind(webgl, `getShaderInfoLog`),
|
||||
_getSupportedExtensions: _bind(webgl, `getSupportedExtensions`),
|
||||
_getUniformBlockIndex: _bind(webgl, `getUniformBlockIndex`),
|
||||
_getUniformLocation: _bind(webgl, `getUniformLocation`),
|
||||
_getVertexAttrib: _bind(webgl, `getVertexAttrib`),
|
||||
_getVertexAttribOffset: _bind(webgl, `getVertexAttribOffset`),
|
||||
_invalidateFramebuffer: _bind(webgl, `invalidateFramebuffer`),
|
||||
_isEnabled: _bind(webgl, `isEnabled`),
|
||||
_linkProgram: _bind(webgl, `linkProgram`),
|
||||
_pixelStorei: _bind(webgl, `pixelStorei`),
|
||||
_renderbufferStorage: _bind(webgl, `renderbufferStorage`),
|
||||
_readPixels: _bind(webgl, `readPixels`),
|
||||
_scissor: _bind(webgl, `scissor`),
|
||||
_shaderSource: _bind(webgl, `shaderSource`),
|
||||
_texImage2D: _bind(webgl, `texImage2D`),
|
||||
_texStorage2D: _bind(webgl, `texStorage2D`),
|
||||
_texSubImage2D: _bind(webgl, `texSubImage2D`),
|
||||
_texParameteri: _bind(webgl, `texParameteri`),
|
||||
_uniformBlockBinding: _bind(webgl, `uniformBlockBinding`),
|
||||
_uniform1f: _bind(webgl, `uniform1f`),
|
||||
_uniform1i: _bind(webgl, `uniform1i`),
|
||||
_uniform2f: _bind(webgl, `uniform2f`),
|
||||
_uniform3f: _bind(webgl, `uniform3f`),
|
||||
_uniform4f: _bind(webgl, `uniform4f`),
|
||||
_useProgram: _bind(webgl, `useProgram`),
|
||||
_vertexAttribPointer: _bind(webgl, `vertexAttribPointer`),
|
||||
_viewport: _bind(webgl, `viewport`),
|
||||
}
|
||||
if err := f.Init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func _bind(ctx js.Value, p string) js.Value {
|
||||
if o := ctx.Get(p); o.Truthy() {
|
||||
return o.Call("bind", ctx)
|
||||
}
|
||||
return js.Undefined()
|
||||
}
|
||||
|
||||
func (f *Functions) Init() error {
|
||||
webgl2Class := js.Global().Get("WebGL2RenderingContext")
|
||||
f.isWebGL2 = !webgl2Class.IsUndefined() && f.Ctx.InstanceOf(webgl2Class)
|
||||
if !f.isWebGL2 {
|
||||
f.EXT_disjoint_timer_query = f.getExtension("EXT_disjoint_timer_query")
|
||||
if f.getExtension("OES_texture_half_float").IsNull() && f.getExtension("OES_texture_float").IsNull() {
|
||||
return errors.New("gl: no support for neither OES_texture_half_float nor OES_texture_float")
|
||||
}
|
||||
if f.getExtension("EXT_sRGB").IsNull() {
|
||||
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").IsNull() && f.getExtension("EXT_color_buffer_float").IsNull() {
|
||||
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._getExtension.Invoke(name)
|
||||
}
|
||||
|
||||
func (f *Functions) ActiveTexture(t Enum) {
|
||||
f._activeTexture.Invoke(int(t))
|
||||
}
|
||||
|
||||
func (f *Functions) AttachShader(p Program, s Shader) {
|
||||
f._attachShader.Invoke(js.Value(p), js.Value(s))
|
||||
}
|
||||
|
||||
func (f *Functions) BeginQuery(target Enum, query Query) {
|
||||
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
||||
f._beginQuery.Invoke(int(target), js.Value(query))
|
||||
} else {
|
||||
f.EXT_disjoint_timer_query.Call("beginQueryEXT", int(target), js.Value(query))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Functions) BindAttribLocation(p Program, a Attrib, name string) {
|
||||
f._bindAttribLocation.Invoke(js.Value(p), int(a), name)
|
||||
}
|
||||
|
||||
func (f *Functions) BindBuffer(target Enum, b Buffer) {
|
||||
f._bindBuffer.Invoke(int(target), js.Value(b))
|
||||
}
|
||||
|
||||
func (f *Functions) BindBufferBase(target Enum, index int, b Buffer) {
|
||||
f._bindBufferBase.Invoke(int(target), index, js.Value(b))
|
||||
}
|
||||
|
||||
func (f *Functions) BindFramebuffer(target Enum, fb Framebuffer) {
|
||||
f._bindFramebuffer.Invoke(int(target), js.Value(fb))
|
||||
}
|
||||
|
||||
func (f *Functions) BindRenderbuffer(target Enum, rb Renderbuffer) {
|
||||
f._bindRenderbuffer.Invoke(int(target), js.Value(rb))
|
||||
}
|
||||
|
||||
func (f *Functions) BindTexture(target Enum, t Texture) {
|
||||
f._bindTexture.Invoke(int(target), js.Value(t))
|
||||
}
|
||||
|
||||
func (f *Functions) BindImageTexture(unit int, t Texture, level int, layered bool, layer int, access, format Enum) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (f *Functions) BindVertexArray(a VertexArray) {
|
||||
panic("not supported")
|
||||
}
|
||||
|
||||
func (f *Functions) BlendEquation(mode Enum) {
|
||||
f._blendEquation.Invoke(int(mode))
|
||||
}
|
||||
|
||||
func (f *Functions) BlendFuncSeparate(srcRGB, dstRGB, srcA, dstA Enum) {
|
||||
f._blendFunc.Invoke(int(srcRGB), int(dstRGB), int(srcA), int(dstA))
|
||||
}
|
||||
|
||||
func (f *Functions) BufferData(target Enum, size int, usage Enum, data []byte) {
|
||||
if data == nil {
|
||||
f._bufferData.Invoke(int(target), size, int(usage))
|
||||
} else {
|
||||
if len(data) != size {
|
||||
panic("size mismatch")
|
||||
}
|
||||
f._bufferData.Invoke(int(target), f.byteArrayOf(data), int(usage))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
|
||||
f._bufferSubData.Invoke(int(target), offset, f.byteArrayOf(src))
|
||||
}
|
||||
|
||||
func (f *Functions) CheckFramebufferStatus(target Enum) Enum {
|
||||
status := Enum(f._checkFramebufferStatus.Invoke(int(target)).Int())
|
||||
if status != FRAMEBUFFER_COMPLETE && f.Ctx.Call("isContextLost").Bool() {
|
||||
// If the context is lost, we say that everything is fine. That saves internal/opengl/opengl.go from panic.
|
||||
return FRAMEBUFFER_COMPLETE
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
func (f *Functions) Clear(mask Enum) {
|
||||
f._clear.Invoke(int(mask))
|
||||
}
|
||||
|
||||
func (f *Functions) ClearColor(red, green, blue, alpha float32) {
|
||||
f._clearColor.Invoke(red, green, blue, alpha)
|
||||
}
|
||||
|
||||
func (f *Functions) ClearDepthf(d float32) {
|
||||
f._clearDepth.Invoke(d)
|
||||
}
|
||||
|
||||
func (f *Functions) CompileShader(s Shader) {
|
||||
f._compileShader.Invoke(js.Value(s))
|
||||
}
|
||||
|
||||
func (f *Functions) CopyTexSubImage2D(target Enum, level, xoffset, yoffset, x, y, width, height int) {
|
||||
f._copyTexSubImage2D.Invoke(int(target), level, xoffset, yoffset, x, y, width, height)
|
||||
}
|
||||
|
||||
func (f *Functions) CreateBuffer() Buffer {
|
||||
return Buffer(f._createBuffer.Invoke())
|
||||
}
|
||||
|
||||
func (f *Functions) CreateFramebuffer() Framebuffer {
|
||||
return Framebuffer(f._createFramebuffer.Invoke())
|
||||
}
|
||||
|
||||
func (f *Functions) CreateProgram() Program {
|
||||
return Program(f._createProgram.Invoke())
|
||||
}
|
||||
|
||||
func (f *Functions) CreateQuery() Query {
|
||||
return Query(f._createQuery.Invoke())
|
||||
}
|
||||
|
||||
func (f *Functions) CreateRenderbuffer() Renderbuffer {
|
||||
return Renderbuffer(f._createRenderbuffer.Invoke())
|
||||
}
|
||||
|
||||
func (f *Functions) CreateShader(ty Enum) Shader {
|
||||
return Shader(f._createShader.Invoke(int(ty)))
|
||||
}
|
||||
|
||||
func (f *Functions) CreateTexture() Texture {
|
||||
return Texture(f._createTexture.Invoke())
|
||||
}
|
||||
|
||||
func (f *Functions) CreateVertexArray() VertexArray {
|
||||
panic("not supported")
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteBuffer(v Buffer) {
|
||||
f._deleteBuffer.Invoke(js.Value(v))
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteFramebuffer(v Framebuffer) {
|
||||
f._deleteFramebuffer.Invoke(js.Value(v))
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteProgram(p Program) {
|
||||
f._deleteProgram.Invoke(js.Value(p))
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteQuery(query Query) {
|
||||
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
||||
f._deleteQuery.Invoke(js.Value(query))
|
||||
} else {
|
||||
f.EXT_disjoint_timer_query.Call("deleteQueryEXT", js.Value(query))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteShader(s Shader) {
|
||||
f._deleteShader.Invoke(js.Value(s))
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteRenderbuffer(v Renderbuffer) {
|
||||
f._deleteRenderbuffer.Invoke(js.Value(v))
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteTexture(v Texture) {
|
||||
f._deleteTexture.Invoke(js.Value(v))
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteVertexArray(a VertexArray) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (f *Functions) DepthFunc(fn Enum) {
|
||||
f._depthFunc.Invoke(int(fn))
|
||||
}
|
||||
|
||||
func (f *Functions) DepthMask(mask bool) {
|
||||
f._depthMask.Invoke(mask)
|
||||
}
|
||||
|
||||
func (f *Functions) DisableVertexAttribArray(a Attrib) {
|
||||
f._disableVertexAttribArray.Invoke(int(a))
|
||||
}
|
||||
|
||||
func (f *Functions) Disable(cap Enum) {
|
||||
f._disable.Invoke(int(cap))
|
||||
}
|
||||
|
||||
func (f *Functions) DrawArrays(mode Enum, first, count int) {
|
||||
f._drawArrays.Invoke(int(mode), first, count)
|
||||
}
|
||||
|
||||
func (f *Functions) DrawElements(mode Enum, count int, ty Enum, offset int) {
|
||||
f._drawElements.Invoke(int(mode), count, int(ty), offset)
|
||||
}
|
||||
|
||||
func (f *Functions) DispatchCompute(x, y, z int) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (f *Functions) Enable(cap Enum) {
|
||||
f._enable.Invoke(int(cap))
|
||||
}
|
||||
|
||||
func (f *Functions) EnableVertexAttribArray(a Attrib) {
|
||||
f._enableVertexAttribArray.Invoke(int(a))
|
||||
}
|
||||
|
||||
func (f *Functions) EndQuery(target Enum) {
|
||||
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
||||
f._endQuery.Invoke(int(target))
|
||||
} else {
|
||||
f.EXT_disjoint_timer_query.Call("endQueryEXT", int(target))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Functions) Finish() {
|
||||
f._finish.Invoke()
|
||||
}
|
||||
|
||||
func (f *Functions) Flush() {
|
||||
f._flush.Invoke()
|
||||
}
|
||||
|
||||
func (f *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer) {
|
||||
f._framebufferRenderbuffer.Invoke(int(target), int(attachment), int(renderbuffertarget), js.Value(renderbuffer))
|
||||
}
|
||||
|
||||
func (f *Functions) FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int) {
|
||||
f._framebufferTexture2D.Invoke(int(target), int(attachment), int(texTarget), js.Value(t), level)
|
||||
}
|
||||
|
||||
func (f *Functions) GenerateMipmap(target Enum) {
|
||||
f._generateMipmap.Invoke(int(target))
|
||||
}
|
||||
|
||||
func (f *Functions) GetError() Enum {
|
||||
// Avoid slow getError calls. See gio#179.
|
||||
return 0
|
||||
}
|
||||
|
||||
func (f *Functions) GetRenderbufferParameteri(target, pname Enum) int {
|
||||
return paramVal(f._getRenderbufferParameteri.Invoke(int(pname)))
|
||||
}
|
||||
|
||||
func (f *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int {
|
||||
if !f.isWebGL2 && pname == FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING {
|
||||
// FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING is only available on WebGL 2
|
||||
return LINEAR
|
||||
}
|
||||
return paramVal(f._getFramebufferAttachmentParameter.Invoke(int(target), int(attachment), int(pname)))
|
||||
}
|
||||
|
||||
func (f *Functions) GetBinding(pname Enum) Object {
|
||||
obj := f._getParameter.Invoke(int(pname))
|
||||
if !obj.Truthy() {
|
||||
return Object{}
|
||||
}
|
||||
return Object(obj)
|
||||
}
|
||||
|
||||
func (f *Functions) GetBindingi(pname Enum, idx int) Object {
|
||||
obj := f._getIndexedParameter.Invoke(int(pname), idx)
|
||||
if !obj.Truthy() {
|
||||
return Object{}
|
||||
}
|
||||
return Object(obj)
|
||||
}
|
||||
|
||||
func (f *Functions) GetInteger(pname Enum) int {
|
||||
if !f.isWebGL2 {
|
||||
switch pname {
|
||||
case PACK_ROW_LENGTH, UNPACK_ROW_LENGTH:
|
||||
return 0 // PACK_ROW_LENGTH and UNPACK_ROW_LENGTH is only available on WebGL 2
|
||||
}
|
||||
}
|
||||
return paramVal(f._getParameter.Invoke(int(pname)))
|
||||
}
|
||||
|
||||
func (f *Functions) GetFloat(pname Enum) float32 {
|
||||
return float32(f._getParameter.Invoke(int(pname)).Float())
|
||||
}
|
||||
|
||||
func (f *Functions) GetInteger4(pname Enum) [4]int {
|
||||
arr := f._getParameter.Invoke(int(pname))
|
||||
var res [4]int
|
||||
for i := range res {
|
||||
res[i] = arr.Index(i).Int()
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (f *Functions) GetFloat4(pname Enum) [4]float32 {
|
||||
arr := f._getParameter.Invoke(int(pname))
|
||||
var res [4]float32
|
||||
for i := range res {
|
||||
res[i] = float32(arr.Index(i).Float())
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (f *Functions) GetProgrami(p Program, pname Enum) int {
|
||||
return paramVal(f._getProgramParameter.Invoke(js.Value(p), int(pname)))
|
||||
}
|
||||
|
||||
func (f *Functions) GetProgramInfoLog(p Program) string {
|
||||
return f._getProgramInfoLog.Invoke(js.Value(p)).String()
|
||||
}
|
||||
|
||||
func (f *Functions) GetQueryObjectuiv(query Query, pname Enum) uint {
|
||||
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
||||
return uint(paramVal(f._getQueryParameter.Invoke(js.Value(query), int(pname))))
|
||||
} 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._getShaderParameter.Invoke(js.Value(s), int(pname)))
|
||||
}
|
||||
|
||||
func (f *Functions) GetShaderInfoLog(s Shader) string {
|
||||
return f._getShaderInfoLog.Invoke(js.Value(s)).String()
|
||||
}
|
||||
|
||||
func (f *Functions) GetString(pname Enum) string {
|
||||
switch pname {
|
||||
case EXTENSIONS:
|
||||
extsjs := f._getSupportedExtensions.Invoke()
|
||||
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._getParameter.Invoke(int(pname)).String()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Functions) GetUniformBlockIndex(p Program, name string) uint {
|
||||
return uint(paramVal(f._getUniformBlockIndex.Invoke(js.Value(p), name)))
|
||||
}
|
||||
|
||||
func (f *Functions) GetUniformLocation(p Program, name string) Uniform {
|
||||
return Uniform(f._getUniformLocation.Invoke(js.Value(p), name))
|
||||
}
|
||||
|
||||
func (f *Functions) GetVertexAttrib(index int, pname Enum) int {
|
||||
return paramVal(f._getVertexAttrib.Invoke(index, int(pname)))
|
||||
}
|
||||
|
||||
func (f *Functions) GetVertexAttribBinding(index int, pname Enum) Object {
|
||||
obj := f._getVertexAttrib.Invoke(index, int(pname))
|
||||
if !obj.Truthy() {
|
||||
return Object{}
|
||||
}
|
||||
return Object(obj)
|
||||
}
|
||||
|
||||
func (f *Functions) GetVertexAttribPointer(index int, pname Enum) uintptr {
|
||||
return uintptr(f._getVertexAttribOffset.Invoke(index, int(pname)).Int())
|
||||
}
|
||||
|
||||
func (f *Functions) InvalidateFramebuffer(target, attachment Enum) {
|
||||
fn := f.Ctx.Get("invalidateFramebuffer")
|
||||
if !fn.IsUndefined() {
|
||||
if f.int32Buf.IsUndefined() {
|
||||
f.int32Buf = js.Global().Get("Int32Array").New(1)
|
||||
}
|
||||
f.int32Buf.SetIndex(0, int32(attachment))
|
||||
f._invalidateFramebuffer.Invoke(int(target), f.int32Buf)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Functions) IsEnabled(cap Enum) bool {
|
||||
return f._isEnabled.Invoke(int(cap)).Truthy()
|
||||
}
|
||||
|
||||
func (f *Functions) LinkProgram(p Program) {
|
||||
f._linkProgram.Invoke(js.Value(p))
|
||||
}
|
||||
|
||||
func (f *Functions) PixelStorei(pname Enum, param int) {
|
||||
f._pixelStorei.Invoke(int(pname), param)
|
||||
}
|
||||
|
||||
func (f *Functions) MemoryBarrier(barriers Enum) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (f *Functions) MapBufferRange(target Enum, offset, length int, access Enum) []byte {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (f *Functions) RenderbufferStorage(target, internalformat Enum, width, height int) {
|
||||
f._renderbufferStorage.Invoke(int(target), int(internalformat), width, height)
|
||||
}
|
||||
|
||||
func (f *Functions) ReadPixels(x, y, width, height int, format, ty Enum, data []byte) {
|
||||
ba := f.byteArrayOf(data)
|
||||
f._readPixels.Invoke(x, y, width, height, int(format), int(ty), ba)
|
||||
js.CopyBytesToGo(data, ba)
|
||||
}
|
||||
|
||||
func (f *Functions) Scissor(x, y, width, height int32) {
|
||||
f._scissor.Invoke(x, y, width, height)
|
||||
}
|
||||
|
||||
func (f *Functions) ShaderSource(s Shader, src string) {
|
||||
f._shaderSource.Invoke(js.Value(s), src)
|
||||
}
|
||||
|
||||
func (f *Functions) TexImage2D(target Enum, level int, internalFormat Enum, width, height int, format, ty Enum) {
|
||||
f._texImage2D.Invoke(int(target), int(level), int(internalFormat), int(width), int(height), 0, int(format), int(ty), nil)
|
||||
}
|
||||
|
||||
func (f *Functions) TexStorage2D(target Enum, levels int, internalFormat Enum, width, height int) {
|
||||
f._texStorage2D.Invoke(int(target), levels, int(internalFormat), width, height)
|
||||
}
|
||||
|
||||
func (f *Functions) TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte) {
|
||||
f._texSubImage2D.Invoke(int(target), level, x, y, width, height, int(format), int(ty), f.byteArrayOf(data))
|
||||
}
|
||||
|
||||
func (f *Functions) TexParameteri(target, pname Enum, param int) {
|
||||
f._texParameteri.Invoke(int(target), int(pname), int(param))
|
||||
}
|
||||
|
||||
func (f *Functions) UniformBlockBinding(p Program, uniformBlockIndex uint, uniformBlockBinding uint) {
|
||||
f._uniformBlockBinding.Invoke(js.Value(p), int(uniformBlockIndex), int(uniformBlockBinding))
|
||||
}
|
||||
|
||||
func (f *Functions) Uniform1f(dst Uniform, v float32) {
|
||||
f._uniform1f.Invoke(js.Value(dst), v)
|
||||
}
|
||||
|
||||
func (f *Functions) Uniform1i(dst Uniform, v int) {
|
||||
f._uniform1i.Invoke(js.Value(dst), v)
|
||||
}
|
||||
|
||||
func (f *Functions) Uniform2f(dst Uniform, v0, v1 float32) {
|
||||
f._uniform2f.Invoke(js.Value(dst), v0, v1)
|
||||
}
|
||||
|
||||
func (f *Functions) Uniform3f(dst Uniform, v0, v1, v2 float32) {
|
||||
f._uniform3f.Invoke(js.Value(dst), v0, v1, v2)
|
||||
}
|
||||
|
||||
func (f *Functions) Uniform4f(dst Uniform, v0, v1, v2, v3 float32) {
|
||||
f._uniform4f.Invoke(js.Value(dst), v0, v1, v2, v3)
|
||||
}
|
||||
|
||||
func (f *Functions) UseProgram(p Program) {
|
||||
f._useProgram.Invoke(js.Value(p))
|
||||
}
|
||||
|
||||
func (f *Functions) UnmapBuffer(target Enum) bool {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (f *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int) {
|
||||
f._vertexAttribPointer.Invoke(int(dst), size, int(ty), normalized, stride, offset)
|
||||
}
|
||||
|
||||
func (f *Functions) Viewport(x, y, width, height int) {
|
||||
f._viewport.Invoke(x, y, width, height)
|
||||
}
|
||||
|
||||
func (f *Functions) byteArrayOf(data []byte) js.Value {
|
||||
if len(data) == 0 {
|
||||
return js.Null()
|
||||
}
|
||||
f.resizeByteBuffer(len(data))
|
||||
ba := f.uint8Array.New(f.arrayBuf, int(0), int(len(data)))
|
||||
js.CopyBytesToJS(ba, data)
|
||||
return ba
|
||||
}
|
||||
|
||||
func (f *Functions) resizeByteBuffer(n int) {
|
||||
if n == 0 {
|
||||
return
|
||||
}
|
||||
if !f.arrayBuf.IsUndefined() && f.arrayBuf.Length() >= n {
|
||||
return
|
||||
}
|
||||
f.arrayBuf = js.Global().Get("ArrayBuffer").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()
|
||||
case js.TypeUndefined:
|
||||
return 0
|
||||
case js.TypeNull:
|
||||
return 0
|
||||
default:
|
||||
panic("unknown parameter type")
|
||||
}
|
||||
}
|
||||
+1323
File diff suppressed because it is too large
Load Diff
+721
@@ -0,0 +1,721 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package gl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func loadGLESv2Procs() error {
|
||||
dllName := "libGLESv2.dll"
|
||||
handle, err := windows.LoadLibraryEx(dllName, 0, windows.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gl: failed to load %s: %v", dllName, err)
|
||||
}
|
||||
gles := windows.DLL{Handle: handle, Name: dllName}
|
||||
// d3dcompiler_47.dll is needed internally for shader compilation to function.
|
||||
dllName = "d3dcompiler_47.dll"
|
||||
_, err = windows.LoadLibraryEx(dllName, 0, windows.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gl: failed to load %s: %v", dllName, err)
|
||||
}
|
||||
procs := map[string]**windows.Proc{
|
||||
"glActiveTexture": &_glActiveTexture,
|
||||
"glAttachShader": &_glAttachShader,
|
||||
"glBeginQuery": &_glBeginQuery,
|
||||
"glBindAttribLocation": &_glBindAttribLocation,
|
||||
"glBindBuffer": &_glBindBuffer,
|
||||
"glBindBufferBase": &_glBindBufferBase,
|
||||
"glBindFramebuffer": &_glBindFramebuffer,
|
||||
"glBindRenderbuffer": &_glBindRenderbuffer,
|
||||
"glBindTexture": &_glBindTexture,
|
||||
"glBindVertexArray": &_glBindVertexArray,
|
||||
"glBlendEquation": &_glBlendEquation,
|
||||
"glBlendFuncSeparate": &_glBlendFuncSeparate,
|
||||
"glBufferData": &_glBufferData,
|
||||
"glBufferSubData": &_glBufferSubData,
|
||||
"glCheckFramebufferStatus": &_glCheckFramebufferStatus,
|
||||
"glClear": &_glClear,
|
||||
"glClearColor": &_glClearColor,
|
||||
"glClearDepthf": &_glClearDepthf,
|
||||
"glDeleteQueries": &_glDeleteQueries,
|
||||
"glDeleteVertexArrays": &_glDeleteVertexArrays,
|
||||
"glCompileShader": &_glCompileShader,
|
||||
"glCopyTexSubImage2D": &_glCopyTexSubImage2D,
|
||||
"glGenerateMipmap": &_glGenerateMipmap,
|
||||
"glGenBuffers": &_glGenBuffers,
|
||||
"glGenFramebuffers": &_glGenFramebuffers,
|
||||
"glGenVertexArrays": &_glGenVertexArrays,
|
||||
"glGetUniformBlockIndex": &_glGetUniformBlockIndex,
|
||||
"glCreateProgram": &_glCreateProgram,
|
||||
"glGenRenderbuffers": &_glGenRenderbuffers,
|
||||
"glCreateShader": &_glCreateShader,
|
||||
"glGenTextures": &_glGenTextures,
|
||||
"glDeleteBuffers": &_glDeleteBuffers,
|
||||
"glDeleteFramebuffers": &_glDeleteFramebuffers,
|
||||
"glDeleteProgram": &_glDeleteProgram,
|
||||
"glDeleteShader": &_glDeleteShader,
|
||||
"glDeleteRenderbuffers": &_glDeleteRenderbuffers,
|
||||
"glDeleteTextures": &_glDeleteTextures,
|
||||
"glDepthFunc": &_glDepthFunc,
|
||||
"glDepthMask": &_glDepthMask,
|
||||
"glDisableVertexAttribArray": &_glDisableVertexAttribArray,
|
||||
"glDisable": &_glDisable,
|
||||
"glDrawArrays": &_glDrawArrays,
|
||||
"glDrawElements": &_glDrawElements,
|
||||
"glEnable": &_glEnable,
|
||||
"glEnableVertexAttribArray": &_glEnableVertexAttribArray,
|
||||
"glEndQuery": &_glEndQuery,
|
||||
"glFinish": &_glFinish,
|
||||
"glFlush": &_glFlush,
|
||||
"glFramebufferRenderbuffer": &_glFramebufferRenderbuffer,
|
||||
"glFramebufferTexture2D": &_glFramebufferTexture2D,
|
||||
"glGenQueries": &_glGenQueries,
|
||||
"glGetError": &_glGetError,
|
||||
"glGetRenderbufferParameteriv": &_glGetRenderbufferParameteriv,
|
||||
"glGetFloatv": &_glGetFloatv,
|
||||
"glGetFramebufferAttachmentParameteriv": &_glGetFramebufferAttachmentParameteriv,
|
||||
"glGetIntegerv": &_glGetIntegerv,
|
||||
"glGetIntegeri_v": &_glGetIntegeri_v,
|
||||
"glGetProgramiv": &_glGetProgramiv,
|
||||
"glGetProgramInfoLog": &_glGetProgramInfoLog,
|
||||
"glGetQueryObjectuiv": &_glGetQueryObjectuiv,
|
||||
"glGetShaderiv": &_glGetShaderiv,
|
||||
"glGetShaderInfoLog": &_glGetShaderInfoLog,
|
||||
"glGetString": &_glGetString,
|
||||
"glGetUniformLocation": &_glGetUniformLocation,
|
||||
"glGetVertexAttribiv": &_glGetVertexAttribiv,
|
||||
"glGetVertexAttribPointerv": &_glGetVertexAttribPointerv,
|
||||
"glInvalidateFramebuffer": &_glInvalidateFramebuffer,
|
||||
"glIsEnabled": &_glIsEnabled,
|
||||
"glLinkProgram": &_glLinkProgram,
|
||||
"glPixelStorei": &_glPixelStorei,
|
||||
"glReadPixels": &_glReadPixels,
|
||||
"glRenderbufferStorage": &_glRenderbufferStorage,
|
||||
"glScissor": &_glScissor,
|
||||
"glShaderSource": &_glShaderSource,
|
||||
"glTexImage2D": &_glTexImage2D,
|
||||
"glTexStorage2D": &_glTexStorage2D,
|
||||
"glTexSubImage2D": &_glTexSubImage2D,
|
||||
"glTexParameteri": &_glTexParameteri,
|
||||
"glUniformBlockBinding": &_glUniformBlockBinding,
|
||||
"glUniform1f": &_glUniform1f,
|
||||
"glUniform1i": &_glUniform1i,
|
||||
"glUniform2f": &_glUniform2f,
|
||||
"glUniform3f": &_glUniform3f,
|
||||
"glUniform4f": &_glUniform4f,
|
||||
"glUseProgram": &_glUseProgram,
|
||||
"glVertexAttribPointer": &_glVertexAttribPointer,
|
||||
"glViewport": &_glViewport,
|
||||
}
|
||||
for name, proc := range procs {
|
||||
p, err := gles.FindProc(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to locate %s in %s: %w", name, gles.Name, err)
|
||||
}
|
||||
*proc = p
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
glInitOnce sync.Once
|
||||
_glActiveTexture *windows.Proc
|
||||
_glAttachShader *windows.Proc
|
||||
_glBeginQuery *windows.Proc
|
||||
_glBindAttribLocation *windows.Proc
|
||||
_glBindBuffer *windows.Proc
|
||||
_glBindBufferBase *windows.Proc
|
||||
_glBindFramebuffer *windows.Proc
|
||||
_glBindRenderbuffer *windows.Proc
|
||||
_glBindTexture *windows.Proc
|
||||
_glBindVertexArray *windows.Proc
|
||||
_glBlendEquation *windows.Proc
|
||||
_glBlendFuncSeparate *windows.Proc
|
||||
_glBufferData *windows.Proc
|
||||
_glBufferSubData *windows.Proc
|
||||
_glCheckFramebufferStatus *windows.Proc
|
||||
_glClear *windows.Proc
|
||||
_glClearColor *windows.Proc
|
||||
_glClearDepthf *windows.Proc
|
||||
_glDeleteQueries *windows.Proc
|
||||
_glDeleteVertexArrays *windows.Proc
|
||||
_glCompileShader *windows.Proc
|
||||
_glCopyTexSubImage2D *windows.Proc
|
||||
_glGenerateMipmap *windows.Proc
|
||||
_glGenBuffers *windows.Proc
|
||||
_glGenFramebuffers *windows.Proc
|
||||
_glGenVertexArrays *windows.Proc
|
||||
_glGetUniformBlockIndex *windows.Proc
|
||||
_glCreateProgram *windows.Proc
|
||||
_glGenRenderbuffers *windows.Proc
|
||||
_glCreateShader *windows.Proc
|
||||
_glGenTextures *windows.Proc
|
||||
_glDeleteBuffers *windows.Proc
|
||||
_glDeleteFramebuffers *windows.Proc
|
||||
_glDeleteProgram *windows.Proc
|
||||
_glDeleteShader *windows.Proc
|
||||
_glDeleteRenderbuffers *windows.Proc
|
||||
_glDeleteTextures *windows.Proc
|
||||
_glDepthFunc *windows.Proc
|
||||
_glDepthMask *windows.Proc
|
||||
_glDisableVertexAttribArray *windows.Proc
|
||||
_glDisable *windows.Proc
|
||||
_glDrawArrays *windows.Proc
|
||||
_glDrawElements *windows.Proc
|
||||
_glEnable *windows.Proc
|
||||
_glEnableVertexAttribArray *windows.Proc
|
||||
_glEndQuery *windows.Proc
|
||||
_glFinish *windows.Proc
|
||||
_glFlush *windows.Proc
|
||||
_glFramebufferRenderbuffer *windows.Proc
|
||||
_glFramebufferTexture2D *windows.Proc
|
||||
_glGenQueries *windows.Proc
|
||||
_glGetError *windows.Proc
|
||||
_glGetRenderbufferParameteriv *windows.Proc
|
||||
_glGetFloatv *windows.Proc
|
||||
_glGetFramebufferAttachmentParameteriv *windows.Proc
|
||||
_glGetIntegerv *windows.Proc
|
||||
_glGetIntegeri_v *windows.Proc
|
||||
_glGetProgramiv *windows.Proc
|
||||
_glGetProgramInfoLog *windows.Proc
|
||||
_glGetQueryObjectuiv *windows.Proc
|
||||
_glGetShaderiv *windows.Proc
|
||||
_glGetShaderInfoLog *windows.Proc
|
||||
_glGetString *windows.Proc
|
||||
_glGetUniformLocation *windows.Proc
|
||||
_glGetVertexAttribiv *windows.Proc
|
||||
_glGetVertexAttribPointerv *windows.Proc
|
||||
_glInvalidateFramebuffer *windows.Proc
|
||||
_glIsEnabled *windows.Proc
|
||||
_glLinkProgram *windows.Proc
|
||||
_glPixelStorei *windows.Proc
|
||||
_glReadPixels *windows.Proc
|
||||
_glRenderbufferStorage *windows.Proc
|
||||
_glScissor *windows.Proc
|
||||
_glShaderSource *windows.Proc
|
||||
_glTexImage2D *windows.Proc
|
||||
_glTexStorage2D *windows.Proc
|
||||
_glTexSubImage2D *windows.Proc
|
||||
_glTexParameteri *windows.Proc
|
||||
_glUniformBlockBinding *windows.Proc
|
||||
_glUniform1f *windows.Proc
|
||||
_glUniform1i *windows.Proc
|
||||
_glUniform2f *windows.Proc
|
||||
_glUniform3f *windows.Proc
|
||||
_glUniform4f *windows.Proc
|
||||
_glUseProgram *windows.Proc
|
||||
_glVertexAttribPointer *windows.Proc
|
||||
_glViewport *windows.Proc
|
||||
)
|
||||
|
||||
type Functions struct {
|
||||
// Query caches.
|
||||
int32s [100]int32
|
||||
float32s [100]float32
|
||||
uintptrs [100]uintptr
|
||||
}
|
||||
|
||||
type Context any
|
||||
|
||||
func NewFunctions(ctx Context, forceES bool) (*Functions, error) {
|
||||
if ctx != nil {
|
||||
panic("non-nil context")
|
||||
}
|
||||
var err error
|
||||
glInitOnce.Do(func() {
|
||||
err = loadGLESv2Procs()
|
||||
})
|
||||
return new(Functions), err
|
||||
}
|
||||
|
||||
func (c *Functions) ActiveTexture(t Enum) {
|
||||
syscall.Syscall(_glActiveTexture.Addr(), 1, uintptr(t), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) AttachShader(p Program, s Shader) {
|
||||
syscall.Syscall(_glAttachShader.Addr(), 2, uintptr(p.V), uintptr(s.V), 0)
|
||||
}
|
||||
|
||||
func (f *Functions) BeginQuery(target Enum, query Query) {
|
||||
syscall.Syscall(_glBeginQuery.Addr(), 2, uintptr(target), uintptr(query.V), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) BindAttribLocation(p Program, a Attrib, name string) {
|
||||
cname := cString(name)
|
||||
c0 := &cname[0]
|
||||
syscall.Syscall(_glBindAttribLocation.Addr(), 3, uintptr(p.V), uintptr(a), uintptr(unsafe.Pointer(c0)))
|
||||
issue34474KeepAlive(c)
|
||||
}
|
||||
|
||||
func (c *Functions) BindBuffer(target Enum, b Buffer) {
|
||||
syscall.Syscall(_glBindBuffer.Addr(), 2, uintptr(target), uintptr(b.V), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) BindBufferBase(target Enum, index int, b Buffer) {
|
||||
syscall.Syscall(_glBindBufferBase.Addr(), 3, uintptr(target), uintptr(index), uintptr(b.V))
|
||||
}
|
||||
|
||||
func (c *Functions) BindFramebuffer(target Enum, fb Framebuffer) {
|
||||
syscall.Syscall(_glBindFramebuffer.Addr(), 2, uintptr(target), uintptr(fb.V), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) BindRenderbuffer(target Enum, rb Renderbuffer) {
|
||||
syscall.Syscall(_glBindRenderbuffer.Addr(), 2, uintptr(target), uintptr(rb.V), 0)
|
||||
}
|
||||
|
||||
func (f *Functions) BindImageTexture(unit int, t Texture, level int, layered bool, layer int, access, format Enum) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (c *Functions) BindTexture(target Enum, t Texture) {
|
||||
syscall.Syscall(_glBindTexture.Addr(), 2, uintptr(target), uintptr(t.V), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) BindVertexArray(a VertexArray) {
|
||||
syscall.Syscall(_glBindVertexArray.Addr(), 1, uintptr(a.V), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) BlendEquation(mode Enum) {
|
||||
syscall.Syscall(_glBlendEquation.Addr(), 1, uintptr(mode), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) BlendFuncSeparate(srcRGB, dstRGB, srcA, dstA Enum) {
|
||||
syscall.Syscall6(_glBlendFuncSeparate.Addr(), 4, uintptr(srcRGB), uintptr(dstRGB), uintptr(srcA), uintptr(dstA), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) BufferData(target Enum, size int, usage Enum, data []byte) {
|
||||
var p unsafe.Pointer
|
||||
if len(data) > 0 {
|
||||
p = unsafe.Pointer(&data[0])
|
||||
}
|
||||
syscall.Syscall6(_glBufferData.Addr(), 4, uintptr(target), uintptr(size), uintptr(p), uintptr(usage), 0, 0)
|
||||
}
|
||||
|
||||
func (f *Functions) BufferSubData(target Enum, offset int, src []byte) {
|
||||
if n := len(src); n > 0 {
|
||||
s0 := &src[0]
|
||||
syscall.Syscall6(_glBufferSubData.Addr(), 4, uintptr(target), uintptr(offset), uintptr(n), uintptr(unsafe.Pointer(s0)), 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 (f *Functions) CopyTexSubImage2D(target Enum, level, xoffset, yoffset, x, y, width, height int) {
|
||||
syscall.Syscall9(_glCopyTexSubImage2D.Addr(), 8, uintptr(target), uintptr(level), uintptr(xoffset), uintptr(yoffset), uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0)
|
||||
}
|
||||
|
||||
func (f *Functions) GenerateMipmap(target Enum) {
|
||||
syscall.Syscall(_glGenerateMipmap.Addr(), 1, uintptr(target), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) CreateBuffer() Buffer {
|
||||
var buf uintptr
|
||||
syscall.Syscall(_glGenBuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&buf)), 0)
|
||||
return Buffer{uint(buf)}
|
||||
}
|
||||
|
||||
func (c *Functions) CreateFramebuffer() Framebuffer {
|
||||
var fb uintptr
|
||||
syscall.Syscall(_glGenFramebuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&fb)), 0)
|
||||
return Framebuffer{uint(fb)}
|
||||
}
|
||||
|
||||
func (c *Functions) CreateProgram() Program {
|
||||
p, _, _ := syscall.Syscall(_glCreateProgram.Addr(), 0, 0, 0, 0)
|
||||
return Program{uint(p)}
|
||||
}
|
||||
|
||||
func (f *Functions) CreateQuery() Query {
|
||||
var q uintptr
|
||||
syscall.Syscall(_glGenQueries.Addr(), 2, 1, uintptr(unsafe.Pointer(&q)), 0)
|
||||
return Query{uint(q)}
|
||||
}
|
||||
|
||||
func (c *Functions) CreateRenderbuffer() Renderbuffer {
|
||||
var rb uintptr
|
||||
syscall.Syscall(_glGenRenderbuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&rb)), 0)
|
||||
return Renderbuffer{uint(rb)}
|
||||
}
|
||||
|
||||
func (c *Functions) CreateShader(ty Enum) Shader {
|
||||
s, _, _ := syscall.Syscall(_glCreateShader.Addr(), 1, uintptr(ty), 0, 0)
|
||||
return Shader{uint(s)}
|
||||
}
|
||||
|
||||
func (c *Functions) CreateTexture() Texture {
|
||||
var t uintptr
|
||||
syscall.Syscall(_glGenTextures.Addr(), 2, 1, uintptr(unsafe.Pointer(&t)), 0)
|
||||
return Texture{uint(t)}
|
||||
}
|
||||
|
||||
func (c *Functions) CreateVertexArray() VertexArray {
|
||||
var t uintptr
|
||||
syscall.Syscall(_glGenVertexArrays.Addr(), 2, 1, uintptr(unsafe.Pointer(&t)), 0)
|
||||
return VertexArray{uint(t)}
|
||||
}
|
||||
|
||||
func (c *Functions) DeleteBuffer(v Buffer) {
|
||||
syscall.Syscall(_glDeleteBuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v)), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) DeleteFramebuffer(v Framebuffer) {
|
||||
syscall.Syscall(_glDeleteFramebuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) DeleteProgram(p Program) {
|
||||
syscall.Syscall(_glDeleteProgram.Addr(), 1, uintptr(p.V), 0, 0)
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteQuery(query Query) {
|
||||
syscall.Syscall(_glDeleteQueries.Addr(), 2, 1, uintptr(unsafe.Pointer(&query.V)), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) DeleteShader(s Shader) {
|
||||
syscall.Syscall(_glDeleteShader.Addr(), 1, uintptr(s.V), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) DeleteRenderbuffer(v Renderbuffer) {
|
||||
syscall.Syscall(_glDeleteRenderbuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) DeleteTexture(v Texture) {
|
||||
syscall.Syscall(_glDeleteTextures.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteVertexArray(array VertexArray) {
|
||||
syscall.Syscall(_glDeleteVertexArrays.Addr(), 2, 1, uintptr(unsafe.Pointer(&array.V)), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) DepthFunc(f Enum) {
|
||||
syscall.Syscall(_glDepthFunc.Addr(), 1, uintptr(f), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) DepthMask(mask bool) {
|
||||
var m uintptr
|
||||
if mask {
|
||||
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 (f *Functions) DispatchCompute(x, y, z int) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (c *Functions) Enable(cap Enum) {
|
||||
syscall.Syscall(_glEnable.Addr(), 1, uintptr(cap), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) EnableVertexAttribArray(a Attrib) {
|
||||
syscall.Syscall(_glEnableVertexAttribArray.Addr(), 1, uintptr(a), 0, 0)
|
||||
}
|
||||
|
||||
func (f *Functions) EndQuery(target Enum) {
|
||||
syscall.Syscall(_glEndQuery.Addr(), 1, uintptr(target), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) Finish() {
|
||||
syscall.Syscall(_glFinish.Addr(), 0, 0, 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) Flush() {
|
||||
syscall.Syscall(_glFlush.Addr(), 0, 0, 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer) {
|
||||
syscall.Syscall6(_glFramebufferRenderbuffer.Addr(), 4, uintptr(target), uintptr(attachment), uintptr(renderbuffertarget), uintptr(renderbuffer.V), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int) {
|
||||
syscall.Syscall6(_glFramebufferTexture2D.Addr(), 5, uintptr(target), uintptr(attachment), uintptr(texTarget), uintptr(t.V), uintptr(level), 0)
|
||||
}
|
||||
|
||||
func (f *Functions) GetUniformBlockIndex(p Program, name string) uint {
|
||||
cname := cString(name)
|
||||
c0 := &cname[0]
|
||||
u, _, _ := syscall.Syscall(_glGetUniformBlockIndex.Addr(), 2, uintptr(p.V), uintptr(unsafe.Pointer(c0)), 0)
|
||||
issue34474KeepAlive(c0)
|
||||
return uint(u)
|
||||
}
|
||||
|
||||
func (c *Functions) GetBinding(pname Enum) Object {
|
||||
return Object{uint(c.GetInteger(pname))}
|
||||
}
|
||||
|
||||
func (c *Functions) GetBindingi(pname Enum, idx int) Object {
|
||||
return Object{uint(c.GetIntegeri(pname, idx))}
|
||||
}
|
||||
|
||||
func (c *Functions) GetError() Enum {
|
||||
e, _, _ := syscall.Syscall(_glGetError.Addr(), 0, 0, 0, 0)
|
||||
return Enum(e)
|
||||
}
|
||||
|
||||
func (c *Functions) GetRenderbufferParameteri(target, pname Enum) int {
|
||||
syscall.Syscall(_glGetRenderbufferParameteriv.Addr(), 3, uintptr(target), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
||||
return int(c.int32s[0])
|
||||
}
|
||||
|
||||
func (c *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int {
|
||||
syscall.Syscall6(_glGetFramebufferAttachmentParameteriv.Addr(), 4, uintptr(target), uintptr(attachment), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0, 0)
|
||||
return int(c.int32s[0])
|
||||
}
|
||||
|
||||
func (c *Functions) GetInteger4(pname Enum) [4]int {
|
||||
syscall.Syscall(_glGetIntegerv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0)
|
||||
var r [4]int
|
||||
for i := range r {
|
||||
r[i] = int(c.int32s[i])
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (c *Functions) GetInteger(pname Enum) int {
|
||||
syscall.Syscall(_glGetIntegerv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0)
|
||||
return int(c.int32s[0])
|
||||
}
|
||||
|
||||
func (c *Functions) GetIntegeri(pname Enum, idx int) int {
|
||||
syscall.Syscall(_glGetIntegeri_v.Addr(), 3, uintptr(pname), uintptr(idx), uintptr(unsafe.Pointer(&c.int32s[0])))
|
||||
return int(c.int32s[0])
|
||||
}
|
||||
|
||||
func (c *Functions) GetFloat(pname Enum) float32 {
|
||||
syscall.Syscall(_glGetFloatv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.float32s[0])), 0)
|
||||
return c.float32s[0]
|
||||
}
|
||||
|
||||
func (c *Functions) GetFloat4(pname Enum) [4]float32 {
|
||||
syscall.Syscall(_glGetFloatv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.float32s[0])), 0)
|
||||
var r [4]float32
|
||||
copy(r[:], c.float32s[:])
|
||||
return r
|
||||
}
|
||||
|
||||
func (c *Functions) GetProgrami(p Program, pname Enum) int {
|
||||
syscall.Syscall(_glGetProgramiv.Addr(), 3, uintptr(p.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
||||
return int(c.int32s[0])
|
||||
}
|
||||
|
||||
func (c *Functions) GetProgramInfoLog(p Program) string {
|
||||
n := c.GetProgrami(p, INFO_LOG_LENGTH)
|
||||
if n == 0 {
|
||||
return ""
|
||||
}
|
||||
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 windows.BytePtrToString((*byte)(unsafe.Pointer(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(c0)
|
||||
return Uniform{int(u)}
|
||||
}
|
||||
|
||||
func (c *Functions) GetVertexAttrib(index int, pname Enum) int {
|
||||
syscall.Syscall(_glGetVertexAttribiv.Addr(), 3, uintptr(index), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
||||
return int(c.int32s[0])
|
||||
}
|
||||
|
||||
func (c *Functions) GetVertexAttribBinding(index int, pname Enum) Object {
|
||||
return Object{uint(c.GetVertexAttrib(index, pname))}
|
||||
}
|
||||
|
||||
func (c *Functions) GetVertexAttribPointer(index int, pname Enum) uintptr {
|
||||
syscall.Syscall(_glGetVertexAttribPointerv.Addr(), 3, uintptr(index), uintptr(pname), uintptr(unsafe.Pointer(&c.uintptrs[0])))
|
||||
return c.uintptrs[0]
|
||||
}
|
||||
|
||||
func (c *Functions) InvalidateFramebuffer(target, attachment Enum) {
|
||||
addr := _glInvalidateFramebuffer.Addr()
|
||||
if addr == 0 {
|
||||
// InvalidateFramebuffer is just a hint. Skip it if not supported.
|
||||
return
|
||||
}
|
||||
syscall.Syscall(addr, 3, uintptr(target), 1, uintptr(unsafe.Pointer(&attachment)))
|
||||
}
|
||||
|
||||
func (f *Functions) IsEnabled(cap Enum) bool {
|
||||
u, _, _ := syscall.Syscall(_glIsEnabled.Addr(), 1, uintptr(cap), 0, 0)
|
||||
return u == TRUE
|
||||
}
|
||||
|
||||
func (c *Functions) LinkProgram(p Program) {
|
||||
syscall.Syscall(_glLinkProgram.Addr(), 1, uintptr(p.V), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) PixelStorei(pname Enum, param int) {
|
||||
syscall.Syscall(_glPixelStorei.Addr(), 2, uintptr(pname), uintptr(param), 0)
|
||||
}
|
||||
|
||||
func (f *Functions) MemoryBarrier(barriers Enum) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (f *Functions) MapBufferRange(target Enum, offset, length int, access Enum) []byte {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (f *Functions) ReadPixels(x, y, width, height int, format, ty Enum, data []byte) {
|
||||
d0 := &data[0]
|
||||
syscall.Syscall9(_glReadPixels.Addr(), 7, uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(format), uintptr(ty), uintptr(unsafe.Pointer(d0)), 0, 0)
|
||||
issue34474KeepAlive(d0)
|
||||
}
|
||||
|
||||
func (c *Functions) RenderbufferStorage(target, internalformat Enum, width, height int) {
|
||||
syscall.Syscall6(_glRenderbufferStorage.Addr(), 4, uintptr(target), uintptr(internalformat), uintptr(width), uintptr(height), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) Scissor(x, y, width, height int32) {
|
||||
syscall.Syscall6(_glScissor.Addr(), 4, uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) ShaderSource(s Shader, src string) {
|
||||
var n uintptr = uintptr(len(src))
|
||||
psrc := &src
|
||||
syscall.Syscall6(_glShaderSource.Addr(), 4, uintptr(s.V), 1, uintptr(unsafe.Pointer(psrc)), uintptr(unsafe.Pointer(&n)), 0, 0)
|
||||
issue34474KeepAlive(psrc)
|
||||
}
|
||||
|
||||
func (f *Functions) TexImage2D(target Enum, level int, internalFormat Enum, width int, height int, format Enum, ty Enum) {
|
||||
syscall.Syscall9(_glTexImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(internalFormat), uintptr(width), uintptr(height), 0, uintptr(format), uintptr(ty), 0)
|
||||
}
|
||||
|
||||
func (f *Functions) TexStorage2D(target Enum, levels int, internalFormat Enum, width, height int) {
|
||||
syscall.Syscall6(_glTexStorage2D.Addr(), 5, uintptr(target), uintptr(levels), uintptr(internalFormat), uintptr(width), uintptr(height), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte) {
|
||||
d0 := &data[0]
|
||||
syscall.Syscall9(_glTexSubImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(format), uintptr(ty), uintptr(unsafe.Pointer(d0)))
|
||||
issue34474KeepAlive(d0)
|
||||
}
|
||||
|
||||
func (c *Functions) TexParameteri(target, pname Enum, param int) {
|
||||
syscall.Syscall(_glTexParameteri.Addr(), 3, uintptr(target), uintptr(pname), uintptr(param))
|
||||
}
|
||||
|
||||
func (f *Functions) UniformBlockBinding(p Program, uniformBlockIndex uint, uniformBlockBinding uint) {
|
||||
syscall.Syscall(_glUniformBlockBinding.Addr(), 3, uintptr(p.V), uintptr(uniformBlockIndex), uintptr(uniformBlockBinding))
|
||||
}
|
||||
|
||||
func (c *Functions) Uniform1f(dst Uniform, v float32) {
|
||||
syscall.Syscall(_glUniform1f.Addr(), 2, uintptr(dst.V), uintptr(math.Float32bits(v)), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) Uniform1i(dst Uniform, v int) {
|
||||
syscall.Syscall(_glUniform1i.Addr(), 2, uintptr(dst.V), uintptr(v), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) Uniform2f(dst Uniform, v0, v1 float32) {
|
||||
syscall.Syscall(_glUniform2f.Addr(), 3, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)))
|
||||
}
|
||||
|
||||
func (c *Functions) Uniform3f(dst Uniform, v0, v1, v2 float32) {
|
||||
syscall.Syscall6(_glUniform3f.Addr(), 4, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)), uintptr(math.Float32bits(v2)), 0, 0)
|
||||
}
|
||||
|
||||
func (c *Functions) Uniform4f(dst Uniform, v0, v1, v2, v3 float32) {
|
||||
syscall.Syscall6(_glUniform4f.Addr(), 5, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)), uintptr(math.Float32bits(v2)), uintptr(math.Float32bits(v3)), 0)
|
||||
}
|
||||
|
||||
func (c *Functions) UseProgram(p Program) {
|
||||
syscall.Syscall(_glUseProgram.Addr(), 1, uintptr(p.V), 0, 0)
|
||||
}
|
||||
|
||||
func (f *Functions) UnmapBuffer(target Enum) bool {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (c *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int) {
|
||||
var norm uintptr
|
||||
if normalized {
|
||||
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 any) {
|
||||
runtime.KeepAlive(v)
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
//go:build !js
|
||||
|
||||
package gl
|
||||
|
||||
type (
|
||||
Object struct{ V uint }
|
||||
Buffer Object
|
||||
Framebuffer Object
|
||||
Program Object
|
||||
Renderbuffer Object
|
||||
Shader Object
|
||||
Texture Object
|
||||
Query Object
|
||||
Uniform struct{ V int }
|
||||
VertexArray Object
|
||||
)
|
||||
|
||||
func (o Object) valid() bool {
|
||||
return o.V != 0
|
||||
}
|
||||
|
||||
func (o Object) equal(o2 Object) bool {
|
||||
return o == o2
|
||||
}
|
||||
|
||||
func (u Framebuffer) Valid() bool {
|
||||
return Object(u).valid()
|
||||
}
|
||||
|
||||
func (u Uniform) Valid() bool {
|
||||
return u.V != -1
|
||||
}
|
||||
|
||||
func (p Program) Valid() bool {
|
||||
return Object(p).valid()
|
||||
}
|
||||
|
||||
func (s Shader) Valid() bool {
|
||||
return Object(s).valid()
|
||||
}
|
||||
|
||||
func (a VertexArray) Valid() bool {
|
||||
return Object(a).valid()
|
||||
}
|
||||
|
||||
func (f Framebuffer) Equal(f2 Framebuffer) bool {
|
||||
return Object(f).equal(Object(f2))
|
||||
}
|
||||
|
||||
func (p Program) Equal(p2 Program) bool {
|
||||
return Object(p).equal(Object(p2))
|
||||
}
|
||||
|
||||
func (s Shader) Equal(s2 Shader) bool {
|
||||
return Object(s).equal(Object(s2))
|
||||
}
|
||||
|
||||
func (u Uniform) Equal(u2 Uniform) bool {
|
||||
return u == u2
|
||||
}
|
||||
|
||||
func (a VertexArray) Equal(a2 VertexArray) bool {
|
||||
return Object(a).equal(Object(a2))
|
||||
}
|
||||
|
||||
func (r Renderbuffer) Equal(r2 Renderbuffer) bool {
|
||||
return Object(r).equal(Object(r2))
|
||||
}
|
||||
|
||||
func (t Texture) Equal(t2 Texture) bool {
|
||||
return Object(t).equal(Object(t2))
|
||||
}
|
||||
|
||||
func (b Buffer) Equal(b2 Buffer) bool {
|
||||
return Object(b).equal(Object(b2))
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package gl
|
||||
|
||||
import "syscall/js"
|
||||
|
||||
type (
|
||||
Object js.Value
|
||||
Buffer Object
|
||||
Framebuffer Object
|
||||
Program Object
|
||||
Renderbuffer Object
|
||||
Shader Object
|
||||
Texture Object
|
||||
Query Object
|
||||
Uniform Object
|
||||
VertexArray Object
|
||||
)
|
||||
|
||||
func (o Object) valid() bool {
|
||||
return js.Value(o).Truthy()
|
||||
}
|
||||
|
||||
func (o Object) equal(o2 Object) bool {
|
||||
return js.Value(o).Equal(js.Value(o2))
|
||||
}
|
||||
|
||||
func (b Buffer) Valid() bool {
|
||||
return Object(b).valid()
|
||||
}
|
||||
|
||||
func (f Framebuffer) Valid() bool {
|
||||
return Object(f).valid()
|
||||
}
|
||||
|
||||
func (p Program) Valid() bool {
|
||||
return Object(p).valid()
|
||||
}
|
||||
|
||||
func (r Renderbuffer) Valid() bool {
|
||||
return Object(r).valid()
|
||||
}
|
||||
|
||||
func (s Shader) Valid() bool {
|
||||
return Object(s).valid()
|
||||
}
|
||||
|
||||
func (t Texture) Valid() bool {
|
||||
return Object(t).valid()
|
||||
}
|
||||
|
||||
func (u Uniform) Valid() bool {
|
||||
return Object(u).valid()
|
||||
}
|
||||
|
||||
func (a VertexArray) Valid() bool {
|
||||
return Object(a).valid()
|
||||
}
|
||||
|
||||
func (f Framebuffer) Equal(f2 Framebuffer) bool {
|
||||
return Object(f).equal(Object(f2))
|
||||
}
|
||||
|
||||
func (p Program) Equal(p2 Program) bool {
|
||||
return Object(p).equal(Object(p2))
|
||||
}
|
||||
|
||||
func (s Shader) Equal(s2 Shader) bool {
|
||||
return Object(s).equal(Object(s2))
|
||||
}
|
||||
|
||||
func (u Uniform) Equal(u2 Uniform) bool {
|
||||
return Object(u).equal(Object(u2))
|
||||
}
|
||||
|
||||
func (a VertexArray) Equal(a2 VertexArray) bool {
|
||||
return Object(a).equal(Object(a2))
|
||||
}
|
||||
|
||||
func (r Renderbuffer) Equal(r2 Renderbuffer) bool {
|
||||
return Object(r).equal(Object(r2))
|
||||
}
|
||||
|
||||
func (t Texture) Equal(t2 Texture) bool {
|
||||
return Object(t).equal(Object(t2))
|
||||
}
|
||||
|
||||
func (b Buffer) Equal(b2 Buffer) bool {
|
||||
return Object(b).equal(Object(b2))
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package gl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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.Valid() {
|
||||
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 CreateComputeProgram(ctx *Functions, src string) (Program, error) {
|
||||
cs, err := CreateShader(ctx, COMPUTE_SHADER, src)
|
||||
if err != nil {
|
||||
return Program{}, err
|
||||
}
|
||||
defer ctx.DeleteShader(cs)
|
||||
prog := ctx.CreateProgram()
|
||||
if !prog.Valid() {
|
||||
return Program{}, errors.New("glCreateProgram failed")
|
||||
}
|
||||
ctx.AttachShader(prog, cs)
|
||||
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 CreateShader(ctx *Functions, typ Enum, src string) (Shader, error) {
|
||||
sh := ctx.CreateShader(typ)
|
||||
if !sh.Valid() {
|
||||
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
|
||||
}
|
||||
|
||||
func ParseGLVersion(glVer string) (version [2]int, gles bool, err error) {
|
||||
var ver [2]int
|
||||
if _, err := fmt.Sscanf(glVer, "OpenGL ES %d.%d", &ver[0], &ver[1]); err == nil {
|
||||
return ver, true, 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, true, nil
|
||||
} else if _, err := fmt.Sscanf(glVer, "%d.%d", &ver[0], &ver[1]); err == nil {
|
||||
return ver, false, nil
|
||||
}
|
||||
return ver, false, fmt.Errorf("failed to parse OpenGL ES version (%s)", glVer)
|
||||
}
|
||||
+497
@@ -0,0 +1,497 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package ops
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"image"
|
||||
"math"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/internal/byteslice"
|
||||
"gioui.org/internal/scene"
|
||||
)
|
||||
|
||||
type Ops struct {
|
||||
// version is incremented at each Reset.
|
||||
version uint32
|
||||
// data contains the serialized operations.
|
||||
data []byte
|
||||
// refs hold external references for operations.
|
||||
refs []any
|
||||
// stringRefs provides space for string references, pointers to which will
|
||||
// be stored in refs. Storing a string directly in refs would cause a heap
|
||||
// allocation, to store the string header in an interface value. The backing
|
||||
// array of stringRefs, on the other hand, gets reused between calls to
|
||||
// reset, making string references free on average.
|
||||
//
|
||||
// Appending to stringRefs might reallocate the backing array, which will
|
||||
// leave pointers to the old array in refs. This temporarily causes a slight
|
||||
// increase in memory usage, but this, too, amortizes away as the capacity
|
||||
// of stringRefs approaches its stable maximum.
|
||||
stringRefs []string
|
||||
// nextStateID is the id allocated for the next
|
||||
// StateOp.
|
||||
nextStateID uint32
|
||||
// multipOp indicates a multi-op such as clip.Path is being added.
|
||||
multipOp bool
|
||||
|
||||
macroStack stack
|
||||
stacks [_StackKind]stack
|
||||
}
|
||||
|
||||
type OpType byte
|
||||
|
||||
type Shape byte
|
||||
|
||||
// Start at a high number for easier debugging.
|
||||
const firstOpIndex = 200
|
||||
|
||||
const (
|
||||
TypeMacro OpType = iota + firstOpIndex
|
||||
TypeCall
|
||||
TypeDefer
|
||||
TypeTransform
|
||||
TypePopTransform
|
||||
TypePushOpacity
|
||||
TypePopOpacity
|
||||
TypeImage
|
||||
TypePaint
|
||||
TypeColor
|
||||
TypeLinearGradient
|
||||
TypePass
|
||||
TypePopPass
|
||||
TypeInput
|
||||
TypeKeyInputHint
|
||||
TypeSave
|
||||
TypeLoad
|
||||
TypeAux
|
||||
TypeClip
|
||||
TypePopClip
|
||||
TypeCursor
|
||||
TypePath
|
||||
TypeStroke
|
||||
TypeSemanticLabel
|
||||
TypeSemanticDesc
|
||||
TypeSemanticClass
|
||||
TypeSemanticSelected
|
||||
TypeSemanticEnabled
|
||||
TypeActionInput
|
||||
)
|
||||
|
||||
type StackID struct {
|
||||
id uint32
|
||||
prev uint32
|
||||
}
|
||||
|
||||
// StateOp represents a saved operation snapshot to be restored
|
||||
// later.
|
||||
type StateOp struct {
|
||||
id uint32
|
||||
macroID uint32
|
||||
ops *Ops
|
||||
}
|
||||
|
||||
// stack tracks the integer identities of stack operations to ensure correct
|
||||
// pairing of their push and pop methods.
|
||||
type stack struct {
|
||||
currentID uint32
|
||||
nextID uint32
|
||||
}
|
||||
|
||||
type StackKind uint8
|
||||
|
||||
// ClipOp is the shadow of clip.Op.
|
||||
type ClipOp struct {
|
||||
Bounds image.Rectangle
|
||||
Outline bool
|
||||
Shape Shape
|
||||
}
|
||||
|
||||
const (
|
||||
ClipStack StackKind = iota
|
||||
TransStack
|
||||
PassStack
|
||||
OpacityStack
|
||||
_StackKind
|
||||
)
|
||||
|
||||
const (
|
||||
Path Shape = iota
|
||||
Ellipse
|
||||
Rect
|
||||
)
|
||||
|
||||
const (
|
||||
TypeMacroLen = 1 + 4 + 4
|
||||
TypeCallLen = 1 + 4 + 4 + 4 + 4
|
||||
TypeDeferLen = 1
|
||||
TypeTransformLen = 1 + 1 + 4*6
|
||||
TypePopTransformLen = 1
|
||||
TypePushOpacityLen = 1 + 4
|
||||
TypePopOpacityLen = 1
|
||||
TypeRedrawLen = 1 + 8
|
||||
TypeImageLen = 1 + 1
|
||||
TypePaintLen = 1
|
||||
TypeColorLen = 1 + 4
|
||||
TypeLinearGradientLen = 1 + 8*2 + 4*2
|
||||
TypePassLen = 1
|
||||
TypePopPassLen = 1
|
||||
TypeInputLen = 1
|
||||
TypeKeyInputHintLen = 1 + 1
|
||||
TypeSaveLen = 1 + 4
|
||||
TypeLoadLen = 1 + 4
|
||||
TypeAuxLen = 1
|
||||
TypeClipLen = 1 + 4*4 + 1 + 1
|
||||
TypePopClipLen = 1
|
||||
TypeCursorLen = 2
|
||||
TypePathLen = 8 + 1
|
||||
TypeStrokeLen = 1 + 4
|
||||
TypeSemanticLabelLen = 1
|
||||
TypeSemanticDescLen = 1
|
||||
TypeSemanticClassLen = 2
|
||||
TypeSemanticSelectedLen = 2
|
||||
TypeSemanticEnabledLen = 2
|
||||
TypeActionInputLen = 1 + 1
|
||||
)
|
||||
|
||||
func (op *ClipOp) Decode(data []byte) {
|
||||
if len(data) < TypeClipLen || OpType(data[0]) != TypeClip {
|
||||
panic("invalid op")
|
||||
}
|
||||
data = data[:TypeClipLen]
|
||||
bo := binary.LittleEndian
|
||||
op.Bounds.Min.X = int(int32(bo.Uint32(data[1:])))
|
||||
op.Bounds.Min.Y = int(int32(bo.Uint32(data[5:])))
|
||||
op.Bounds.Max.X = int(int32(bo.Uint32(data[9:])))
|
||||
op.Bounds.Max.Y = int(int32(bo.Uint32(data[13:])))
|
||||
op.Outline = data[17] == 1
|
||||
op.Shape = Shape(data[18])
|
||||
}
|
||||
|
||||
func Reset(o *Ops) {
|
||||
o.macroStack = stack{}
|
||||
o.stacks = [_StackKind]stack{}
|
||||
// Leave references to the GC.
|
||||
for i := range o.refs {
|
||||
o.refs[i] = nil
|
||||
}
|
||||
for i := range o.stringRefs {
|
||||
o.stringRefs[i] = ""
|
||||
}
|
||||
o.data = o.data[:0]
|
||||
o.refs = o.refs[:0]
|
||||
o.stringRefs = o.stringRefs[:0]
|
||||
o.nextStateID = 0
|
||||
o.version++
|
||||
}
|
||||
|
||||
func Write(o *Ops, n int) []byte {
|
||||
if o.multipOp {
|
||||
panic("cannot mix multi ops with single ones")
|
||||
}
|
||||
o.data = append(o.data, make([]byte, n)...)
|
||||
return o.data[len(o.data)-n:]
|
||||
}
|
||||
|
||||
func BeginMulti(o *Ops) {
|
||||
if o.multipOp {
|
||||
panic("cannot interleave multi ops")
|
||||
}
|
||||
o.multipOp = true
|
||||
}
|
||||
|
||||
func EndMulti(o *Ops) {
|
||||
if !o.multipOp {
|
||||
panic("cannot end non multi ops")
|
||||
}
|
||||
o.multipOp = false
|
||||
}
|
||||
|
||||
func WriteMulti(o *Ops, n int) []byte {
|
||||
if !o.multipOp {
|
||||
panic("cannot use multi ops in single ops")
|
||||
}
|
||||
o.data = append(o.data, make([]byte, n)...)
|
||||
return o.data[len(o.data)-n:]
|
||||
}
|
||||
|
||||
func PushMacro(o *Ops) StackID {
|
||||
return o.macroStack.push()
|
||||
}
|
||||
|
||||
func PopMacro(o *Ops, id StackID) {
|
||||
o.macroStack.pop(id)
|
||||
}
|
||||
|
||||
func FillMacro(o *Ops, startPC PC) {
|
||||
pc := PCFor(o)
|
||||
// Fill out the macro definition reserved in Record.
|
||||
data := o.data[startPC.data:]
|
||||
data = data[:TypeMacroLen]
|
||||
data[0] = byte(TypeMacro)
|
||||
bo := binary.LittleEndian
|
||||
bo.PutUint32(data[1:], uint32(pc.data))
|
||||
bo.PutUint32(data[5:], uint32(pc.refs))
|
||||
}
|
||||
|
||||
func AddCall(o *Ops, callOps *Ops, pc PC, end PC) {
|
||||
data := Write1(o, TypeCallLen, callOps)
|
||||
data[0] = byte(TypeCall)
|
||||
bo := binary.LittleEndian
|
||||
bo.PutUint32(data[1:], uint32(pc.data))
|
||||
bo.PutUint32(data[5:], uint32(pc.refs))
|
||||
bo.PutUint32(data[9:], uint32(end.data))
|
||||
bo.PutUint32(data[13:], uint32(end.refs))
|
||||
}
|
||||
|
||||
func PushOp(o *Ops, kind StackKind) (StackID, uint32) {
|
||||
return o.stacks[kind].push(), o.macroStack.currentID
|
||||
}
|
||||
|
||||
func PopOp(o *Ops, kind StackKind, sid StackID, macroID uint32) {
|
||||
if o.macroStack.currentID != macroID {
|
||||
panic("stack push and pop must not cross macro boundary")
|
||||
}
|
||||
o.stacks[kind].pop(sid)
|
||||
}
|
||||
|
||||
func Write1(o *Ops, n int, ref1 any) []byte {
|
||||
o.data = append(o.data, make([]byte, n)...)
|
||||
o.refs = append(o.refs, ref1)
|
||||
return o.data[len(o.data)-n:]
|
||||
}
|
||||
|
||||
func Write1String(o *Ops, n int, ref1 string) []byte {
|
||||
o.data = append(o.data, make([]byte, n)...)
|
||||
o.stringRefs = append(o.stringRefs, ref1)
|
||||
o.refs = append(o.refs, &o.stringRefs[len(o.stringRefs)-1])
|
||||
return o.data[len(o.data)-n:]
|
||||
}
|
||||
|
||||
func Write2(o *Ops, n int, ref1, ref2 any) []byte {
|
||||
o.data = append(o.data, make([]byte, n)...)
|
||||
o.refs = append(o.refs, ref1, ref2)
|
||||
return o.data[len(o.data)-n:]
|
||||
}
|
||||
|
||||
func Write2String(o *Ops, n int, ref1 any, ref2 string) []byte {
|
||||
o.data = append(o.data, make([]byte, n)...)
|
||||
o.stringRefs = append(o.stringRefs, ref2)
|
||||
o.refs = append(o.refs, ref1, &o.stringRefs[len(o.stringRefs)-1])
|
||||
return o.data[len(o.data)-n:]
|
||||
}
|
||||
|
||||
func Write3(o *Ops, n int, ref1, ref2, ref3 any) []byte {
|
||||
o.data = append(o.data, make([]byte, n)...)
|
||||
o.refs = append(o.refs, ref1, ref2, ref3)
|
||||
return o.data[len(o.data)-n:]
|
||||
}
|
||||
|
||||
func PCFor(o *Ops) PC {
|
||||
return PC{data: uint32(len(o.data)), refs: uint32(len(o.refs))}
|
||||
}
|
||||
|
||||
func (s *stack) push() StackID {
|
||||
s.nextID++
|
||||
sid := StackID{
|
||||
id: s.nextID,
|
||||
prev: s.currentID,
|
||||
}
|
||||
s.currentID = s.nextID
|
||||
return sid
|
||||
}
|
||||
|
||||
func (s *stack) check(sid StackID) {
|
||||
if s.currentID != sid.id {
|
||||
panic("unbalanced operation")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stack) pop(sid StackID) {
|
||||
s.check(sid)
|
||||
s.currentID = sid.prev
|
||||
}
|
||||
|
||||
// Save the effective transformation.
|
||||
func Save(o *Ops) StateOp {
|
||||
o.nextStateID++
|
||||
s := StateOp{
|
||||
ops: o,
|
||||
id: o.nextStateID,
|
||||
macroID: o.macroStack.currentID,
|
||||
}
|
||||
bo := binary.LittleEndian
|
||||
data := Write(o, TypeSaveLen)
|
||||
data[0] = byte(TypeSave)
|
||||
bo.PutUint32(data[1:], uint32(s.id))
|
||||
return s
|
||||
}
|
||||
|
||||
// Load a previously saved operations state given
|
||||
// its ID.
|
||||
func (s StateOp) Load() {
|
||||
bo := binary.LittleEndian
|
||||
data := Write(s.ops, TypeLoadLen)
|
||||
data[0] = byte(TypeLoad)
|
||||
bo.PutUint32(data[1:], uint32(s.id))
|
||||
}
|
||||
|
||||
func DecodeCommand(d []byte) scene.Command {
|
||||
var cmd scene.Command
|
||||
copy(byteslice.Uint32(cmd[:]), d)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func EncodeCommand(out []byte, cmd scene.Command) {
|
||||
copy(out, byteslice.Uint32(cmd[:]))
|
||||
}
|
||||
|
||||
func DecodeTransform(data []byte) (t f32.Affine2D, push bool) {
|
||||
if OpType(data[0]) != TypeTransform {
|
||||
panic("invalid op")
|
||||
}
|
||||
push = data[1] != 0
|
||||
data = data[2:]
|
||||
data = data[:4*6]
|
||||
|
||||
bo := binary.LittleEndian
|
||||
a := math.Float32frombits(bo.Uint32(data))
|
||||
b := math.Float32frombits(bo.Uint32(data[4*1:]))
|
||||
c := math.Float32frombits(bo.Uint32(data[4*2:]))
|
||||
d := math.Float32frombits(bo.Uint32(data[4*3:]))
|
||||
e := math.Float32frombits(bo.Uint32(data[4*4:]))
|
||||
f := math.Float32frombits(bo.Uint32(data[4*5:]))
|
||||
return f32.NewAffine2D(a, b, c, d, e, f), push
|
||||
}
|
||||
|
||||
func DecodeOpacity(data []byte) float32 {
|
||||
if OpType(data[0]) != TypePushOpacity {
|
||||
panic("invalid op")
|
||||
}
|
||||
bo := binary.LittleEndian
|
||||
return math.Float32frombits(bo.Uint32(data[1:]))
|
||||
}
|
||||
|
||||
// DecodeSave decodes the state id of a save op.
|
||||
func DecodeSave(data []byte) int {
|
||||
if OpType(data[0]) != TypeSave {
|
||||
panic("invalid op")
|
||||
}
|
||||
bo := binary.LittleEndian
|
||||
return int(bo.Uint32(data[1:]))
|
||||
}
|
||||
|
||||
// DecodeLoad decodes the state id of a load op.
|
||||
func DecodeLoad(data []byte) int {
|
||||
if OpType(data[0]) != TypeLoad {
|
||||
panic("invalid op")
|
||||
}
|
||||
bo := binary.LittleEndian
|
||||
return int(bo.Uint32(data[1:]))
|
||||
}
|
||||
|
||||
type opProp struct {
|
||||
Size byte
|
||||
NumRefs byte
|
||||
}
|
||||
|
||||
var opProps = [0x100]opProp{
|
||||
TypeMacro: {Size: TypeMacroLen, NumRefs: 0},
|
||||
TypeCall: {Size: TypeCallLen, NumRefs: 1},
|
||||
TypeDefer: {Size: TypeDeferLen, NumRefs: 0},
|
||||
TypeTransform: {Size: TypeTransformLen, NumRefs: 0},
|
||||
TypePopTransform: {Size: TypePopTransformLen, NumRefs: 0},
|
||||
TypePushOpacity: {Size: TypePushOpacityLen, NumRefs: 0},
|
||||
TypePopOpacity: {Size: TypePopOpacityLen, NumRefs: 0},
|
||||
TypeImage: {Size: TypeImageLen, NumRefs: 2},
|
||||
TypePaint: {Size: TypePaintLen, NumRefs: 0},
|
||||
TypeColor: {Size: TypeColorLen, NumRefs: 0},
|
||||
TypeLinearGradient: {Size: TypeLinearGradientLen, NumRefs: 0},
|
||||
TypePass: {Size: TypePassLen, NumRefs: 0},
|
||||
TypePopPass: {Size: TypePopPassLen, NumRefs: 0},
|
||||
TypeInput: {Size: TypeInputLen, NumRefs: 1},
|
||||
TypeKeyInputHint: {Size: TypeKeyInputHintLen, NumRefs: 1},
|
||||
TypeSave: {Size: TypeSaveLen, NumRefs: 0},
|
||||
TypeLoad: {Size: TypeLoadLen, NumRefs: 0},
|
||||
TypeAux: {Size: TypeAuxLen, NumRefs: 0},
|
||||
TypeClip: {Size: TypeClipLen, NumRefs: 0},
|
||||
TypePopClip: {Size: TypePopClipLen, NumRefs: 0},
|
||||
TypeCursor: {Size: TypeCursorLen, NumRefs: 0},
|
||||
TypePath: {Size: TypePathLen, NumRefs: 0},
|
||||
TypeStroke: {Size: TypeStrokeLen, NumRefs: 0},
|
||||
TypeSemanticLabel: {Size: TypeSemanticLabelLen, NumRefs: 1},
|
||||
TypeSemanticDesc: {Size: TypeSemanticDescLen, NumRefs: 1},
|
||||
TypeSemanticClass: {Size: TypeSemanticClassLen, NumRefs: 0},
|
||||
TypeSemanticSelected: {Size: TypeSemanticSelectedLen, NumRefs: 0},
|
||||
TypeSemanticEnabled: {Size: TypeSemanticEnabledLen, NumRefs: 0},
|
||||
TypeActionInput: {Size: TypeActionInputLen, NumRefs: 0},
|
||||
}
|
||||
|
||||
func (t OpType) props() (size, numRefs uint32) {
|
||||
v := opProps[t]
|
||||
return uint32(v.Size), uint32(v.NumRefs)
|
||||
}
|
||||
|
||||
func (t OpType) Size() uint32 {
|
||||
return uint32(opProps[t].Size)
|
||||
}
|
||||
|
||||
func (t OpType) NumRefs() uint32 {
|
||||
return uint32(opProps[t].NumRefs)
|
||||
}
|
||||
|
||||
func (t OpType) String() string {
|
||||
switch t {
|
||||
case TypeMacro:
|
||||
return "Macro"
|
||||
case TypeCall:
|
||||
return "Call"
|
||||
case TypeDefer:
|
||||
return "Defer"
|
||||
case TypeTransform:
|
||||
return "Transform"
|
||||
case TypePopTransform:
|
||||
return "PopTransform"
|
||||
case TypePushOpacity:
|
||||
return "PushOpacity"
|
||||
case TypePopOpacity:
|
||||
return "PopOpacity"
|
||||
case TypeImage:
|
||||
return "Image"
|
||||
case TypePaint:
|
||||
return "Paint"
|
||||
case TypeColor:
|
||||
return "Color"
|
||||
case TypeLinearGradient:
|
||||
return "LinearGradient"
|
||||
case TypePass:
|
||||
return "Pass"
|
||||
case TypePopPass:
|
||||
return "PopPass"
|
||||
case TypeInput:
|
||||
return "Input"
|
||||
case TypeKeyInputHint:
|
||||
return "KeyInputHint"
|
||||
case TypeSave:
|
||||
return "Save"
|
||||
case TypeLoad:
|
||||
return "Load"
|
||||
case TypeAux:
|
||||
return "Aux"
|
||||
case TypeClip:
|
||||
return "Clip"
|
||||
case TypePopClip:
|
||||
return "PopClip"
|
||||
case TypeCursor:
|
||||
return "Cursor"
|
||||
case TypePath:
|
||||
return "Path"
|
||||
case TypeStroke:
|
||||
return "Stroke"
|
||||
case TypeSemanticLabel:
|
||||
return "SemanticDescription"
|
||||
default:
|
||||
panic("unknown OpType")
|
||||
}
|
||||
}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package ops
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// Reader parses an ops list.
|
||||
type Reader struct {
|
||||
pc PC
|
||||
stack []macro
|
||||
ops *Ops
|
||||
deferOps Ops
|
||||
deferDone bool
|
||||
}
|
||||
|
||||
// EncodedOp represents an encoded op returned by
|
||||
// Reader.
|
||||
type EncodedOp struct {
|
||||
Key Key
|
||||
Data []byte
|
||||
Refs []any
|
||||
}
|
||||
|
||||
// Key is a unique key for a given op.
|
||||
type Key struct {
|
||||
ops *Ops
|
||||
pc uint32
|
||||
version uint32
|
||||
}
|
||||
|
||||
// Shadow of op.MacroOp.
|
||||
type macroOp struct {
|
||||
ops *Ops
|
||||
start PC
|
||||
end PC
|
||||
}
|
||||
|
||||
// PC is an instruction counter for an operation list.
|
||||
type PC struct {
|
||||
data uint32
|
||||
refs uint32
|
||||
}
|
||||
|
||||
type macro struct {
|
||||
ops *Ops
|
||||
retPC PC
|
||||
endPC PC
|
||||
}
|
||||
|
||||
type opMacroDef struct {
|
||||
endpc PC
|
||||
}
|
||||
|
||||
func (pc PC) Add(op OpType) PC {
|
||||
size, numRefs := op.props()
|
||||
return PC{
|
||||
data: pc.data + size,
|
||||
refs: pc.refs + numRefs,
|
||||
}
|
||||
}
|
||||
|
||||
// Reset start reading from the beginning of ops.
|
||||
func (r *Reader) Reset(ops *Ops) {
|
||||
r.ResetAt(ops, PC{})
|
||||
}
|
||||
|
||||
// ResetAt is like Reset, except it starts reading from pc.
|
||||
func (r *Reader) ResetAt(ops *Ops, pc PC) {
|
||||
r.stack = r.stack[:0]
|
||||
Reset(&r.deferOps)
|
||||
r.deferDone = false
|
||||
r.pc = pc
|
||||
r.ops = ops
|
||||
}
|
||||
|
||||
func (r *Reader) Decode() (EncodedOp, bool) {
|
||||
if r.ops == nil {
|
||||
return EncodedOp{}, false
|
||||
}
|
||||
deferring := 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:]
|
||||
refs := r.ops.refs
|
||||
if len(data) == 0 {
|
||||
if r.deferDone {
|
||||
return EncodedOp{}, false
|
||||
}
|
||||
r.deferDone = true
|
||||
// Execute deferred macros.
|
||||
r.ops = &r.deferOps
|
||||
r.pc = PC{}
|
||||
continue
|
||||
}
|
||||
key := Key{ops: r.ops, pc: r.pc.data, version: r.ops.version}
|
||||
t := OpType(data[0])
|
||||
n, nrefs := t.props()
|
||||
data = data[:n]
|
||||
refs = refs[r.pc.refs:]
|
||||
refs = refs[:nrefs]
|
||||
switch t {
|
||||
case TypeDefer:
|
||||
deferring = true
|
||||
r.pc.data += n
|
||||
r.pc.refs += nrefs
|
||||
continue
|
||||
case TypeAux:
|
||||
// An Aux operations is always wrapped in a macro, and
|
||||
// its length is the remaining space.
|
||||
block := r.stack[len(r.stack)-1]
|
||||
n += block.endPC.data - r.pc.data - TypeAuxLen
|
||||
data = data[:n]
|
||||
case TypeCall:
|
||||
if deferring {
|
||||
deferring = false
|
||||
// Copy macro for deferred execution.
|
||||
if nrefs != 1 {
|
||||
panic("internal error: unexpected number of macro refs")
|
||||
}
|
||||
deferData := Write1(&r.deferOps, int(n), refs[0])
|
||||
copy(deferData, data)
|
||||
r.pc.data += n
|
||||
r.pc.refs += nrefs
|
||||
continue
|
||||
}
|
||||
var op macroOp
|
||||
op.decode(data, refs)
|
||||
retPC := r.pc
|
||||
retPC.data += n
|
||||
retPC.refs += nrefs
|
||||
r.stack = append(r.stack, macro{
|
||||
ops: r.ops,
|
||||
retPC: retPC,
|
||||
endPC: op.end,
|
||||
})
|
||||
r.ops = op.ops
|
||||
r.pc = op.start
|
||||
continue
|
||||
case TypeMacro:
|
||||
var op opMacroDef
|
||||
op.decode(data)
|
||||
if op.endpc != (PC{}) {
|
||||
r.pc = op.endpc
|
||||
} else {
|
||||
// Treat an incomplete macro as containing all remaining ops.
|
||||
r.pc.data = uint32(len(r.ops.data))
|
||||
r.pc.refs = uint32(len(r.ops.refs))
|
||||
}
|
||||
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 len(data) < TypeMacroLen || OpType(data[0]) != TypeMacro {
|
||||
panic("invalid op")
|
||||
}
|
||||
bo := binary.LittleEndian
|
||||
data = data[:TypeMacroLen]
|
||||
op.endpc.data = bo.Uint32(data[1:])
|
||||
op.endpc.refs = bo.Uint32(data[5:])
|
||||
}
|
||||
|
||||
func (m *macroOp) decode(data []byte, refs []any) {
|
||||
if len(data) < TypeCallLen || len(refs) < 1 || OpType(data[0]) != TypeCall {
|
||||
panic("invalid op")
|
||||
}
|
||||
bo := binary.LittleEndian
|
||||
data = data[:TypeCallLen]
|
||||
|
||||
m.ops = refs[0].(*Ops)
|
||||
m.start.data = bo.Uint32(data[1:])
|
||||
m.start.refs = bo.Uint32(data[5:])
|
||||
m.end.data = bo.Uint32(data[9:])
|
||||
m.end.refs = bo.Uint32(data[13:])
|
||||
}
|
||||
+251
@@ -0,0 +1,251 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// Package scene encodes and decodes graphics commands in the format used by the
|
||||
// compute renderer.
|
||||
package scene
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/internal/f32"
|
||||
)
|
||||
|
||||
type Op uint32
|
||||
|
||||
type Command [sceneElemSize / 4]uint32
|
||||
|
||||
// GPU commands from piet/scene.h in package gioui.org/shaders.
|
||||
const (
|
||||
OpNop Op = iota
|
||||
OpLine
|
||||
OpQuad
|
||||
OpCubic
|
||||
OpFillColor
|
||||
OpLineWidth
|
||||
OpTransform
|
||||
OpBeginClip
|
||||
OpEndClip
|
||||
OpFillImage
|
||||
OpSetFillMode
|
||||
OpGap
|
||||
)
|
||||
|
||||
// FillModes, from setup.h.
|
||||
type FillMode uint32
|
||||
|
||||
const (
|
||||
FillModeNonzero = 0
|
||||
FillModeStroke = 1
|
||||
)
|
||||
|
||||
const CommandSize = int(unsafe.Sizeof(Command{}))
|
||||
|
||||
const sceneElemSize = 36
|
||||
|
||||
func (c Command) Op() Op {
|
||||
return Op(c[0])
|
||||
}
|
||||
|
||||
func (c Command) String() string {
|
||||
switch Op(c[0]) {
|
||||
case OpNop:
|
||||
return "nop"
|
||||
case OpLine:
|
||||
from, to := DecodeLine(c)
|
||||
return fmt.Sprintf("line(%v, %v)", from, to)
|
||||
case OpGap:
|
||||
from, to := DecodeLine(c)
|
||||
return fmt.Sprintf("gap(%v, %v)", from, to)
|
||||
case OpQuad:
|
||||
from, ctrl, to := DecodeQuad(c)
|
||||
return fmt.Sprintf("quad(%v, %v, %v)", from, ctrl, to)
|
||||
case OpCubic:
|
||||
from, ctrl0, ctrl1, to := DecodeCubic(c)
|
||||
return fmt.Sprintf("cubic(%v, %v, %v, %v)", from, ctrl0, ctrl1, to)
|
||||
case OpFillColor:
|
||||
return fmt.Sprintf("fillcolor %#.8x", c[1])
|
||||
case OpLineWidth:
|
||||
return "linewidth"
|
||||
case OpTransform:
|
||||
t := f32.NewAffine2D(
|
||||
math.Float32frombits(c[1]),
|
||||
math.Float32frombits(c[3]),
|
||||
math.Float32frombits(c[5]),
|
||||
math.Float32frombits(c[2]),
|
||||
math.Float32frombits(c[4]),
|
||||
math.Float32frombits(c[6]),
|
||||
)
|
||||
return fmt.Sprintf("transform (%v)", t)
|
||||
case OpBeginClip:
|
||||
bounds := f32.Rectangle{
|
||||
Min: f32.Pt(math.Float32frombits(c[1]), math.Float32frombits(c[2])),
|
||||
Max: f32.Pt(math.Float32frombits(c[3]), math.Float32frombits(c[4])),
|
||||
}
|
||||
return fmt.Sprintf("beginclip (%v)", bounds)
|
||||
case OpEndClip:
|
||||
bounds := f32.Rectangle{
|
||||
Min: f32.Pt(math.Float32frombits(c[1]), math.Float32frombits(c[2])),
|
||||
Max: f32.Pt(math.Float32frombits(c[3]), math.Float32frombits(c[4])),
|
||||
}
|
||||
return fmt.Sprintf("endclip (%v)", bounds)
|
||||
case OpFillImage:
|
||||
return "fillimage"
|
||||
case OpSetFillMode:
|
||||
return "setfillmode"
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
func Line(start, end f32.Point) Command {
|
||||
return Command{
|
||||
0: uint32(OpLine),
|
||||
1: math.Float32bits(start.X),
|
||||
2: math.Float32bits(start.Y),
|
||||
3: math.Float32bits(end.X),
|
||||
4: math.Float32bits(end.Y),
|
||||
}
|
||||
}
|
||||
|
||||
func Gap(start, end f32.Point) Command {
|
||||
return Command{
|
||||
0: uint32(OpGap),
|
||||
1: math.Float32bits(start.X),
|
||||
2: math.Float32bits(start.Y),
|
||||
3: math.Float32bits(end.X),
|
||||
4: math.Float32bits(end.Y),
|
||||
}
|
||||
}
|
||||
|
||||
func Cubic(start, ctrl0, ctrl1, end f32.Point) Command {
|
||||
return Command{
|
||||
0: uint32(OpCubic),
|
||||
1: math.Float32bits(start.X),
|
||||
2: math.Float32bits(start.Y),
|
||||
3: math.Float32bits(ctrl0.X),
|
||||
4: math.Float32bits(ctrl0.Y),
|
||||
5: math.Float32bits(ctrl1.X),
|
||||
6: math.Float32bits(ctrl1.Y),
|
||||
7: math.Float32bits(end.X),
|
||||
8: math.Float32bits(end.Y),
|
||||
}
|
||||
}
|
||||
|
||||
func Quad(start, ctrl, end f32.Point) Command {
|
||||
return Command{
|
||||
0: uint32(OpQuad),
|
||||
1: math.Float32bits(start.X),
|
||||
2: math.Float32bits(start.Y),
|
||||
3: math.Float32bits(ctrl.X),
|
||||
4: math.Float32bits(ctrl.Y),
|
||||
5: math.Float32bits(end.X),
|
||||
6: math.Float32bits(end.Y),
|
||||
}
|
||||
}
|
||||
|
||||
func Transform(m f32.Affine2D) Command {
|
||||
sx, hx, ox, hy, sy, oy := m.Elems()
|
||||
return Command{
|
||||
0: uint32(OpTransform),
|
||||
1: math.Float32bits(sx),
|
||||
2: math.Float32bits(hy),
|
||||
3: math.Float32bits(hx),
|
||||
4: math.Float32bits(sy),
|
||||
5: math.Float32bits(ox),
|
||||
6: math.Float32bits(oy),
|
||||
}
|
||||
}
|
||||
|
||||
func SetLineWidth(width float32) Command {
|
||||
return Command{
|
||||
0: uint32(OpLineWidth),
|
||||
1: math.Float32bits(width),
|
||||
}
|
||||
}
|
||||
|
||||
func BeginClip(bbox f32.Rectangle) Command {
|
||||
return Command{
|
||||
0: uint32(OpBeginClip),
|
||||
1: math.Float32bits(bbox.Min.X),
|
||||
2: math.Float32bits(bbox.Min.Y),
|
||||
3: math.Float32bits(bbox.Max.X),
|
||||
4: math.Float32bits(bbox.Max.Y),
|
||||
}
|
||||
}
|
||||
|
||||
func EndClip(bbox f32.Rectangle) Command {
|
||||
return Command{
|
||||
0: uint32(OpEndClip),
|
||||
1: math.Float32bits(bbox.Min.X),
|
||||
2: math.Float32bits(bbox.Min.Y),
|
||||
3: math.Float32bits(bbox.Max.X),
|
||||
4: math.Float32bits(bbox.Max.Y),
|
||||
}
|
||||
}
|
||||
|
||||
func FillColor(col color.RGBA) Command {
|
||||
return Command{
|
||||
0: uint32(OpFillColor),
|
||||
1: uint32(col.R)<<24 | uint32(col.G)<<16 | uint32(col.B)<<8 | uint32(col.A),
|
||||
}
|
||||
}
|
||||
|
||||
func FillImage(index int, offset image.Point) Command {
|
||||
x := int16(offset.X)
|
||||
y := int16(offset.Y)
|
||||
return Command{
|
||||
0: uint32(OpFillImage),
|
||||
1: uint32(index),
|
||||
2: uint32(uint16(x)) | uint32(uint16(y))<<16,
|
||||
}
|
||||
}
|
||||
|
||||
func SetFillMode(mode FillMode) Command {
|
||||
return Command{
|
||||
0: uint32(OpSetFillMode),
|
||||
1: uint32(mode),
|
||||
}
|
||||
}
|
||||
|
||||
func DecodeLine(cmd Command) (from, to f32.Point) {
|
||||
if cmd[0] != uint32(OpLine) {
|
||||
panic("invalid command")
|
||||
}
|
||||
from = f32.Pt(math.Float32frombits(cmd[1]), math.Float32frombits(cmd[2]))
|
||||
to = f32.Pt(math.Float32frombits(cmd[3]), math.Float32frombits(cmd[4]))
|
||||
return
|
||||
}
|
||||
|
||||
func DecodeGap(cmd Command) (from, to f32.Point) {
|
||||
if cmd[0] != uint32(OpGap) {
|
||||
panic("invalid command")
|
||||
}
|
||||
from = f32.Pt(math.Float32frombits(cmd[1]), math.Float32frombits(cmd[2]))
|
||||
to = f32.Pt(math.Float32frombits(cmd[3]), math.Float32frombits(cmd[4]))
|
||||
return
|
||||
}
|
||||
|
||||
func DecodeQuad(cmd Command) (from, ctrl, to f32.Point) {
|
||||
if cmd[0] != uint32(OpQuad) {
|
||||
panic("invalid command")
|
||||
}
|
||||
from = f32.Pt(math.Float32frombits(cmd[1]), math.Float32frombits(cmd[2]))
|
||||
ctrl = f32.Pt(math.Float32frombits(cmd[3]), math.Float32frombits(cmd[4]))
|
||||
to = f32.Pt(math.Float32frombits(cmd[5]), math.Float32frombits(cmd[6]))
|
||||
return
|
||||
}
|
||||
|
||||
func DecodeCubic(cmd Command) (from, ctrl0, ctrl1, to f32.Point) {
|
||||
if cmd[0] != uint32(OpCubic) {
|
||||
panic("invalid command")
|
||||
}
|
||||
from = f32.Pt(math.Float32frombits(cmd[1]), math.Float32frombits(cmd[2]))
|
||||
ctrl0 = f32.Pt(math.Float32frombits(cmd[3]), math.Float32frombits(cmd[4]))
|
||||
ctrl1 = f32.Pt(math.Float32frombits(cmd[5]), math.Float32frombits(cmd[6]))
|
||||
to = f32.Pt(math.Float32frombits(cmd[7]), math.Float32frombits(cmd[8]))
|
||||
return
|
||||
}
|
||||
+760
@@ -0,0 +1,760 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// Most of the algorithms to compute strokes and their offsets have been
|
||||
// extracted, adapted from (and used as a reference implementation):
|
||||
// - github.com/tdewolff/canvas (Licensed under MIT)
|
||||
//
|
||||
// These algorithms have been implemented from:
|
||||
// Fast, precise flattening of cubic Bézier path and offset curves
|
||||
// Thomas F. Hain, et al.
|
||||
//
|
||||
// An electronic version is available at:
|
||||
// https://seant23.files.wordpress.com/2010/11/fastpreciseflatteningofbeziercurve.pdf
|
||||
//
|
||||
// Possible improvements (in term of speed and/or accuracy) on these
|
||||
// algorithms are:
|
||||
//
|
||||
// - Polar Stroking: New Theory and Methods for Stroking Paths,
|
||||
// M. Kilgard
|
||||
// https://arxiv.org/pdf/2007.00308.pdf
|
||||
//
|
||||
// - https://raphlinus.github.io/graphics/curves/2019/12/23/flatten-quadbez.html
|
||||
// R. Levien
|
||||
|
||||
// Package stroke implements conversion of strokes to filled outlines. It is used as a
|
||||
// fallback for stroke configurations not natively supported by the renderer.
|
||||
package stroke
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math"
|
||||
|
||||
"gioui.org/internal/f32"
|
||||
"gioui.org/internal/ops"
|
||||
"gioui.org/internal/scene"
|
||||
)
|
||||
|
||||
// The following are copies of types from op/clip to avoid a circular import of
|
||||
// that package.
|
||||
// TODO: when the old renderer is gone, this package can be merged with
|
||||
// op/clip, eliminating the duplicate types.
|
||||
type StrokeStyle struct {
|
||||
Width float32
|
||||
}
|
||||
|
||||
// strokeTolerance is used to reconcile rounding errors arising
|
||||
// when splitting quads into smaller and smaller segments to approximate
|
||||
// them into straight lines, and when joining back segments.
|
||||
//
|
||||
// The magic value of 0.01 was found by striking a compromise between
|
||||
// aesthetic looking (curves did look like curves, even after linearization)
|
||||
// and speed.
|
||||
const strokeTolerance = 0.01
|
||||
|
||||
type QuadSegment struct {
|
||||
From, Ctrl, To f32.Point
|
||||
}
|
||||
|
||||
type StrokeQuad struct {
|
||||
Contour uint32
|
||||
Quad QuadSegment
|
||||
}
|
||||
|
||||
type strokeState struct {
|
||||
p0, p1 f32.Point // p0 is the start point, p1 the end point.
|
||||
n0, n1 f32.Point // n0 is the normal vector at the start point, n1 at the end point.
|
||||
r0, r1 float32 // r0 is the curvature at the start point, r1 at the end point.
|
||||
ctl f32.Point // ctl is the control point of the quadratic Bézier segment.
|
||||
}
|
||||
|
||||
type StrokeQuads []StrokeQuad
|
||||
|
||||
func (qs *StrokeQuads) pen() f32.Point {
|
||||
return (*qs)[len(*qs)-1].Quad.To
|
||||
}
|
||||
|
||||
func (qs *StrokeQuads) lineTo(pt f32.Point) {
|
||||
end := qs.pen()
|
||||
*qs = append(*qs, StrokeQuad{
|
||||
Quad: QuadSegment{
|
||||
From: end,
|
||||
Ctrl: end.Add(pt).Mul(0.5),
|
||||
To: pt,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (qs *StrokeQuads) arc(f1, f2 f32.Point, angle float32) {
|
||||
pen := qs.pen()
|
||||
m, segments := ArcTransform(pen, f1.Add(pen), f2.Add(pen), angle)
|
||||
for range segments {
|
||||
p0 := qs.pen()
|
||||
p1 := m.Transform(p0)
|
||||
p2 := m.Transform(p1)
|
||||
ctl := p1.Mul(2).Sub(p0.Add(p2).Mul(.5))
|
||||
*qs = append(*qs, StrokeQuad{
|
||||
Quad: QuadSegment{
|
||||
From: p0, Ctrl: ctl, To: p2,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// split splits a slice of quads into slices of quads grouped
|
||||
// by contours (ie: splitted at move-to boundaries).
|
||||
func (qs StrokeQuads) split() []StrokeQuads {
|
||||
if len(qs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
c uint32
|
||||
o []StrokeQuads
|
||||
i = len(o)
|
||||
)
|
||||
for _, q := range qs {
|
||||
if q.Contour != c {
|
||||
c = q.Contour
|
||||
i = len(o)
|
||||
o = append(o, StrokeQuads{})
|
||||
}
|
||||
o[i] = append(o[i], q)
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
func (qs StrokeQuads) stroke(stroke StrokeStyle) StrokeQuads {
|
||||
var (
|
||||
o StrokeQuads
|
||||
hw = 0.5 * stroke.Width
|
||||
)
|
||||
|
||||
for _, ps := range qs.split() {
|
||||
rhs, lhs := ps.offset(hw, stroke)
|
||||
switch lhs {
|
||||
case nil:
|
||||
o = o.append(rhs)
|
||||
default:
|
||||
// Closed path.
|
||||
// Inner path should go opposite direction to cancel outer path.
|
||||
switch {
|
||||
case ps.ccw():
|
||||
lhs = lhs.reverse()
|
||||
o = o.append(rhs)
|
||||
o = o.append(lhs)
|
||||
default:
|
||||
rhs = rhs.reverse()
|
||||
o = o.append(lhs)
|
||||
o = o.append(rhs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
// offset returns the right-hand and left-hand sides of the path, offset by
|
||||
// the half-width hw.
|
||||
// The stroke handles how segments are joined and ends are capped.
|
||||
func (qs StrokeQuads) offset(hw float32, stroke StrokeStyle) (rhs, lhs StrokeQuads) {
|
||||
var (
|
||||
states []strokeState
|
||||
beg = qs[0].Quad.From
|
||||
end = qs[len(qs)-1].Quad.To
|
||||
closed = beg == end
|
||||
)
|
||||
for i := range qs {
|
||||
q := qs[i].Quad
|
||||
|
||||
var (
|
||||
n0 = strokePathNorm(q.From, q.Ctrl, q.To, 0, hw)
|
||||
n1 = strokePathNorm(q.From, q.Ctrl, q.To, 1, hw)
|
||||
r0 = strokePathCurv(q.From, q.Ctrl, q.To, 0)
|
||||
r1 = strokePathCurv(q.From, q.Ctrl, q.To, 1)
|
||||
)
|
||||
states = append(states, strokeState{
|
||||
p0: q.From,
|
||||
p1: q.To,
|
||||
n0: n0,
|
||||
n1: n1,
|
||||
r0: r0,
|
||||
r1: r1,
|
||||
ctl: q.Ctrl,
|
||||
})
|
||||
}
|
||||
|
||||
for i, state := range states {
|
||||
rhs = rhs.append(strokeQuadBezier(state, +hw, strokeTolerance))
|
||||
lhs = lhs.append(strokeQuadBezier(state, -hw, strokeTolerance))
|
||||
|
||||
// join the current and next segments
|
||||
if hasNext := i+1 < len(states); hasNext || closed {
|
||||
var next strokeState
|
||||
switch {
|
||||
case hasNext:
|
||||
next = states[i+1]
|
||||
case closed:
|
||||
next = states[0]
|
||||
}
|
||||
if state.n1 != next.n0 {
|
||||
strokePathRoundJoin(&rhs, &lhs, hw, state.p1, state.n1, next.n0, state.r1, next.r0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if closed {
|
||||
rhs.close()
|
||||
lhs.close()
|
||||
return rhs, lhs
|
||||
}
|
||||
|
||||
qbeg := &states[0]
|
||||
qend := &states[len(states)-1]
|
||||
|
||||
// Default to counter-clockwise direction.
|
||||
lhs = lhs.reverse()
|
||||
strokePathCap(stroke, &rhs, hw, qend.p1, qend.n1)
|
||||
|
||||
rhs = rhs.append(lhs)
|
||||
strokePathCap(stroke, &rhs, hw, qbeg.p0, qbeg.n0.Mul(-1))
|
||||
|
||||
rhs.close()
|
||||
|
||||
return rhs, nil
|
||||
}
|
||||
|
||||
func (qs *StrokeQuads) close() {
|
||||
p0 := (*qs)[len(*qs)-1].Quad.To
|
||||
p1 := (*qs)[0].Quad.From
|
||||
|
||||
if p1 == p0 {
|
||||
return
|
||||
}
|
||||
|
||||
*qs = append(*qs, StrokeQuad{
|
||||
Quad: QuadSegment{
|
||||
From: p0,
|
||||
Ctrl: p0.Add(p1).Mul(0.5),
|
||||
To: p1,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ccw returns whether the path is counter-clockwise.
|
||||
func (qs StrokeQuads) ccw() bool {
|
||||
// Use the Shoelace formula:
|
||||
// https://en.wikipedia.org/wiki/Shoelace_formula
|
||||
var area float32
|
||||
for _, ps := range qs.split() {
|
||||
for i := 1; i < len(ps); i++ {
|
||||
pi := ps[i].Quad.To
|
||||
pj := ps[i-1].Quad.To
|
||||
area += (pi.X - pj.X) * (pi.Y + pj.Y)
|
||||
}
|
||||
}
|
||||
return area <= 0.0
|
||||
}
|
||||
|
||||
func (qs StrokeQuads) reverse() StrokeQuads {
|
||||
if len(qs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ps := make(StrokeQuads, 0, len(qs))
|
||||
for i := range qs {
|
||||
q := qs[len(qs)-1-i]
|
||||
q.Quad.To, q.Quad.From = q.Quad.From, q.Quad.To
|
||||
ps = append(ps, q)
|
||||
}
|
||||
|
||||
return ps
|
||||
}
|
||||
|
||||
func (qs StrokeQuads) append(ps StrokeQuads) StrokeQuads {
|
||||
switch {
|
||||
case len(ps) == 0:
|
||||
return qs
|
||||
case len(qs) == 0:
|
||||
return ps
|
||||
}
|
||||
|
||||
// Consolidate quads and smooth out rounding errors.
|
||||
// We need to also check for the strokeTolerance to correctly handle
|
||||
// join/cap points or on-purpose disjoint quads.
|
||||
p0 := qs[len(qs)-1].Quad.To
|
||||
p1 := ps[0].Quad.From
|
||||
if p0 != p1 && lenPt(p0.Sub(p1)) < strokeTolerance {
|
||||
qs = append(qs, StrokeQuad{
|
||||
Quad: QuadSegment{
|
||||
From: p0,
|
||||
Ctrl: p0.Add(p1).Mul(0.5),
|
||||
To: p1,
|
||||
},
|
||||
})
|
||||
}
|
||||
return append(qs, ps...)
|
||||
}
|
||||
|
||||
func (q QuadSegment) Transform(t f32.Affine2D) QuadSegment {
|
||||
q.From = t.Transform(q.From)
|
||||
q.Ctrl = t.Transform(q.Ctrl)
|
||||
q.To = t.Transform(q.To)
|
||||
return q
|
||||
}
|
||||
|
||||
// strokePathNorm returns the normal vector at t.
|
||||
func strokePathNorm(p0, p1, p2 f32.Point, t, d float32) f32.Point {
|
||||
switch t {
|
||||
case 0:
|
||||
n := p1.Sub(p0)
|
||||
if n.X == 0 && n.Y == 0 {
|
||||
return f32.Point{}
|
||||
}
|
||||
n = rot90CW(n)
|
||||
return normPt(n, d)
|
||||
case 1:
|
||||
n := p2.Sub(p1)
|
||||
if n.X == 0 && n.Y == 0 {
|
||||
return f32.Point{}
|
||||
}
|
||||
n = rot90CW(n)
|
||||
return normPt(n, d)
|
||||
}
|
||||
panic("impossible")
|
||||
}
|
||||
|
||||
func rot90CW(p f32.Point) f32.Point { return f32.Pt(+p.Y, -p.X) }
|
||||
|
||||
func normPt(p f32.Point, l float32) f32.Point {
|
||||
if (p.X == 0 && p.Y == 0) || l == 0 {
|
||||
return f32.Point{}
|
||||
}
|
||||
isVerticalUnit := p.X == 0 && (p.Y == l || p.Y == -l)
|
||||
isHorizontalUnit := p.Y == 0 && (p.X == l || p.X == -l)
|
||||
if isVerticalUnit || isHorizontalUnit {
|
||||
if math.Signbit(float64(l)) {
|
||||
return f32.Point{X: -p.X, Y: -p.Y}
|
||||
} else {
|
||||
return f32.Point{X: p.X, Y: p.Y}
|
||||
}
|
||||
}
|
||||
d := math.Hypot(float64(p.X), float64(p.Y))
|
||||
l64 := float64(l)
|
||||
if math.Abs(d-l64) < 1e-10 {
|
||||
if math.Signbit(float64(l)) {
|
||||
return f32.Point{X: -p.X, Y: -p.Y}
|
||||
} else {
|
||||
return f32.Point{X: p.X, Y: p.Y}
|
||||
}
|
||||
}
|
||||
n := float32(l64 / d)
|
||||
return f32.Point{X: p.X * n, Y: p.Y * n}
|
||||
}
|
||||
|
||||
func lenPt(p f32.Point) float32 {
|
||||
return float32(math.Hypot(float64(p.X), float64(p.Y)))
|
||||
}
|
||||
|
||||
func perpDot(p, q f32.Point) float32 {
|
||||
return p.X*q.Y - p.Y*q.X
|
||||
}
|
||||
|
||||
func angleBetween(n0, n1 f32.Point) float64 {
|
||||
return math.Atan2(float64(n1.Y), float64(n1.X)) -
|
||||
math.Atan2(float64(n0.Y), float64(n0.X))
|
||||
}
|
||||
|
||||
// strokePathCurv returns the curvature at t, along the quadratic Bézier
|
||||
// curve defined by the triplet (beg, ctl, end).
|
||||
func strokePathCurv(beg, ctl, end f32.Point, t float32) float32 {
|
||||
var (
|
||||
d1p = quadBezierD1(beg, ctl, end, t)
|
||||
d2p = quadBezierD2(beg, ctl, end, t)
|
||||
|
||||
// Negative when bending right, ie: the curve is CW at this point.
|
||||
a = float64(perpDot(d1p, d2p))
|
||||
)
|
||||
|
||||
// We check early that the segment isn't too line-like and
|
||||
// save a costly call to math.Pow that will be discarded by dividing
|
||||
// with a too small 'a'.
|
||||
if math.Abs(a) < 1e-10 {
|
||||
return float32(math.NaN())
|
||||
}
|
||||
return float32(math.Pow(float64(d1p.X*d1p.X+d1p.Y*d1p.Y), 1.5) / a)
|
||||
}
|
||||
|
||||
// quadBezierSample returns the point on the Bézier curve at t.
|
||||
//
|
||||
// B(t) = (1-t)^2 P0 + 2(1-t)t P1 + t^2 P2
|
||||
func quadBezierSample(p0, p1, p2 f32.Point, t float32) f32.Point {
|
||||
t1 := 1 - t
|
||||
c0 := t1 * t1
|
||||
c1 := 2 * t1 * t
|
||||
c2 := t * t
|
||||
|
||||
o := p0.Mul(c0)
|
||||
o = o.Add(p1.Mul(c1))
|
||||
o = o.Add(p2.Mul(c2))
|
||||
return o
|
||||
}
|
||||
|
||||
// quadBezierD1 returns the first derivative of the Bézier curve with respect to t.
|
||||
//
|
||||
// B'(t) = 2(1-t)(P1 - P0) + 2t(P2 - P1)
|
||||
func quadBezierD1(p0, p1, p2 f32.Point, t float32) f32.Point {
|
||||
p10 := p1.Sub(p0).Mul(2 * (1 - t))
|
||||
p21 := p2.Sub(p1).Mul(2 * t)
|
||||
|
||||
return p10.Add(p21)
|
||||
}
|
||||
|
||||
// quadBezierD2 returns the second derivative of the Bézier curve with respect to t:
|
||||
//
|
||||
// B''(t) = 2(P2 - 2P1 + P0)
|
||||
func quadBezierD2(p0, p1, p2 f32.Point, t float32) f32.Point {
|
||||
p := p2.Sub(p1.Mul(2)).Add(p0)
|
||||
return p.Mul(2)
|
||||
}
|
||||
|
||||
func strokeQuadBezier(state strokeState, d, flatness float32) StrokeQuads {
|
||||
// Gio strokes are only quadratic Bézier curves, w/o any inflection point.
|
||||
// So we just have to flatten them.
|
||||
var qs StrokeQuads
|
||||
return flattenQuadBezier(qs, state.p0, state.ctl, state.p1, d, flatness)
|
||||
}
|
||||
|
||||
// flattenQuadBezier splits a Bézier quadratic curve into linear sub-segments,
|
||||
// themselves also encoded as Bézier (degenerate, flat) quadratic curves.
|
||||
func flattenQuadBezier(qs StrokeQuads, p0, p1, p2 f32.Point, d, flatness float32) StrokeQuads {
|
||||
var (
|
||||
t float32
|
||||
flat64 = float64(flatness)
|
||||
)
|
||||
for t < 1 {
|
||||
s2 := float64((p2.X-p0.X)*(p1.Y-p0.Y) - (p2.Y-p0.Y)*(p1.X-p0.X))
|
||||
den := math.Hypot(float64(p1.X-p0.X), float64(p1.Y-p0.Y))
|
||||
if s2*den == 0.0 {
|
||||
break
|
||||
}
|
||||
|
||||
s2 /= den
|
||||
t = 2.0 * float32(math.Sqrt(flat64/3.0/math.Abs(s2)))
|
||||
if t >= 1.0 {
|
||||
break
|
||||
}
|
||||
var q0, q1, q2 f32.Point
|
||||
q0, q1, q2, p0, p1, p2 = quadBezierSplit(p0, p1, p2, t)
|
||||
qs.addLine(q0, q1, q2, 0, d)
|
||||
}
|
||||
qs.addLine(p0, p1, p2, 1, d)
|
||||
return qs
|
||||
}
|
||||
|
||||
func (qs *StrokeQuads) addLine(p0, ctrl, p1 f32.Point, t, d float32) {
|
||||
switch i := len(*qs); i {
|
||||
case 0:
|
||||
p0 = p0.Add(strokePathNorm(p0, ctrl, p1, 0, d))
|
||||
default:
|
||||
// Address possible rounding errors and use previous point.
|
||||
p0 = (*qs)[i-1].Quad.To
|
||||
}
|
||||
|
||||
p1 = p1.Add(strokePathNorm(p0, ctrl, p1, 1, d))
|
||||
|
||||
*qs = append(*qs,
|
||||
StrokeQuad{
|
||||
Quad: QuadSegment{
|
||||
From: p0,
|
||||
Ctrl: p0.Add(p1).Mul(0.5),
|
||||
To: p1,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// quadInterp returns the interpolated point at t.
|
||||
func quadInterp(p, q f32.Point, t float32) f32.Point {
|
||||
return f32.Pt(
|
||||
(1-t)*p.X+t*q.X,
|
||||
(1-t)*p.Y+t*q.Y,
|
||||
)
|
||||
}
|
||||
|
||||
// quadBezierSplit returns the pair of triplets (from,ctrl,to) Bézier curve,
|
||||
// split before (resp. after) the provided parametric t value.
|
||||
func quadBezierSplit(p0, p1, p2 f32.Point, t float32) (f32.Point, f32.Point, f32.Point, f32.Point, f32.Point, f32.Point) {
|
||||
var (
|
||||
b0 = p0
|
||||
b1 = quadInterp(p0, p1, t)
|
||||
b2 = quadBezierSample(p0, p1, p2, t)
|
||||
|
||||
a0 = b2
|
||||
a1 = quadInterp(p1, p2, t)
|
||||
a2 = p2
|
||||
)
|
||||
|
||||
return b0, b1, b2, a0, a1, a2
|
||||
}
|
||||
|
||||
// strokePathRoundJoin joins the two paths rhs and lhs, creating an arc.
|
||||
func strokePathRoundJoin(rhs, lhs *StrokeQuads, hw float32, pivot, n0, n1 f32.Point, r0, r1 float32) {
|
||||
rp := pivot.Add(n1)
|
||||
lp := pivot.Sub(n1)
|
||||
angle := angleBetween(n0, n1)
|
||||
switch {
|
||||
case angle <= 0:
|
||||
// Path bends to the right, ie. CW (or 180 degree turn).
|
||||
c := pivot.Sub(lhs.pen())
|
||||
lhs.arc(c, c, float32(angle))
|
||||
lhs.lineTo(lp) // Add a line to accommodate for rounding errors.
|
||||
rhs.lineTo(rp)
|
||||
default:
|
||||
// Path bends to the left, ie. CCW.
|
||||
c := pivot.Sub(rhs.pen())
|
||||
rhs.arc(c, c, float32(angle))
|
||||
rhs.lineTo(rp) // Add a line to accommodate for rounding errors.
|
||||
lhs.lineTo(lp)
|
||||
}
|
||||
}
|
||||
|
||||
// strokePathCap caps the provided path qs, according to the provided stroke operation.
|
||||
func strokePathCap(stroke StrokeStyle, qs *StrokeQuads, hw float32, pivot, n0 f32.Point) {
|
||||
strokePathRoundCap(qs, hw, pivot, n0)
|
||||
}
|
||||
|
||||
// strokePathRoundCap caps the start or end of a path with a round cap.
|
||||
func strokePathRoundCap(qs *StrokeQuads, hw float32, pivot, n0 f32.Point) {
|
||||
c := pivot.Sub(qs.pen())
|
||||
qs.arc(c, c, math.Pi)
|
||||
}
|
||||
|
||||
// ArcTransform computes a transformation that can be used for generating quadratic bézier
|
||||
// curve approximations for an arc.
|
||||
//
|
||||
// The math is extracted from the following paper:
|
||||
//
|
||||
// "Drawing an elliptical arc using polylines, quadratic or
|
||||
// cubic Bezier curves", L. Maisonobe
|
||||
//
|
||||
// An electronic version may be found at:
|
||||
//
|
||||
// http://spaceroots.org/documents/ellipse/elliptical-arc.pdf
|
||||
func ArcTransform(p, f1, f2 f32.Point, angle float32) (transform f32.Affine2D, segments int) {
|
||||
const segmentsPerCircle = 16
|
||||
const anglePerSegment = 2 * math.Pi / segmentsPerCircle
|
||||
|
||||
s := angle / anglePerSegment
|
||||
if s < 0 {
|
||||
s = -s
|
||||
}
|
||||
segments = int(math.Ceil(float64(s)))
|
||||
if segments <= 0 {
|
||||
segments = 1
|
||||
}
|
||||
|
||||
var rx, ry, alpha float64
|
||||
if f1 == f2 {
|
||||
// degenerate case of a circle.
|
||||
rx = dist(f1, p)
|
||||
ry = rx
|
||||
} else {
|
||||
// semi-major axis: 2a = |PF1| + |PF2|
|
||||
a := 0.5 * (dist(f1, p) + dist(f2, p))
|
||||
// semi-minor axis: c^2 = a^2 - b^2 (c: focal distance)
|
||||
c := dist(f1, f2) * 0.5
|
||||
b := math.Sqrt(a*a - c*c)
|
||||
switch {
|
||||
case a > b:
|
||||
rx = a
|
||||
ry = b
|
||||
default:
|
||||
rx = b
|
||||
ry = a
|
||||
}
|
||||
if f1.X == f2.X {
|
||||
// special case of a "vertical" ellipse.
|
||||
alpha = math.Pi / 2
|
||||
if f1.Y < f2.Y {
|
||||
alpha = -alpha
|
||||
}
|
||||
} else {
|
||||
x := float64(f1.X-f2.X) * 0.5
|
||||
if x < 0 {
|
||||
x = -x
|
||||
}
|
||||
alpha = math.Acos(x / c)
|
||||
}
|
||||
}
|
||||
|
||||
θ := angle / float32(segments)
|
||||
ref := f32.AffineId() // transform from absolute frame to ellipse-based one
|
||||
rot := f32.AffineId() // rotation matrix for each segment
|
||||
inv := f32.AffineId() // transform from ellipse-based frame to absolute one
|
||||
center := f32.Point{
|
||||
X: 0.5 * (f1.X + f2.X),
|
||||
Y: 0.5 * (f1.Y + f2.Y),
|
||||
}
|
||||
ref = ref.Offset(f32.Point{}.Sub(center))
|
||||
ref = ref.Rotate(f32.Point{}, float32(-alpha))
|
||||
ref = ref.Scale(f32.Point{}, f32.Point{
|
||||
X: float32(1 / rx),
|
||||
Y: float32(1 / ry),
|
||||
})
|
||||
inv = ref.Invert()
|
||||
rot = rot.Rotate(f32.Point{}, 0.5*θ)
|
||||
|
||||
// Instead of invoking math.Sincos for every segment, compute a rotation
|
||||
// matrix once and apply for each segment.
|
||||
// Before applying the rotation matrix rot, transform the coordinates
|
||||
// to a frame centered to the ellipse (and warped into a unit circle), then rotate.
|
||||
// Finally, transform back into the original frame.
|
||||
return inv.Mul(rot).Mul(ref), segments
|
||||
}
|
||||
|
||||
func dist(p1, p2 f32.Point) float64 {
|
||||
var (
|
||||
x1 = float64(p1.X)
|
||||
y1 = float64(p1.Y)
|
||||
x2 = float64(p2.X)
|
||||
y2 = float64(p2.Y)
|
||||
dx = x2 - x1
|
||||
dy = y2 - y1
|
||||
)
|
||||
return math.Hypot(dx, dy)
|
||||
}
|
||||
|
||||
func StrokePathCommands(style StrokeStyle, scene []byte) StrokeQuads {
|
||||
quads := decodeToStrokeQuads(scene)
|
||||
return quads.stroke(style)
|
||||
}
|
||||
|
||||
// decodeToStrokeQuads decodes scene commands to quads ready to stroke.
|
||||
func decodeToStrokeQuads(pathData []byte) StrokeQuads {
|
||||
quads := make(StrokeQuads, 0, 2*len(pathData)/(scene.CommandSize+4))
|
||||
scratch := make([]QuadSegment, 0, 10)
|
||||
for len(pathData) >= scene.CommandSize+4 {
|
||||
contour := binary.LittleEndian.Uint32(pathData)
|
||||
cmd := ops.DecodeCommand(pathData[4:])
|
||||
switch cmd.Op() {
|
||||
case scene.OpLine:
|
||||
var q QuadSegment
|
||||
q.From, q.To = scene.DecodeLine(cmd)
|
||||
q.Ctrl = q.From.Add(q.To).Mul(.5)
|
||||
quad := StrokeQuad{
|
||||
Contour: contour,
|
||||
Quad: q,
|
||||
}
|
||||
quads = append(quads, quad)
|
||||
case scene.OpGap:
|
||||
// Ignore gaps for strokes.
|
||||
case scene.OpQuad:
|
||||
var q QuadSegment
|
||||
q.From, q.Ctrl, q.To = scene.DecodeQuad(cmd)
|
||||
quad := StrokeQuad{
|
||||
Contour: contour,
|
||||
Quad: q,
|
||||
}
|
||||
quads = append(quads, quad)
|
||||
case scene.OpCubic:
|
||||
from, ctrl0, ctrl1, to := scene.DecodeCubic(cmd)
|
||||
scratch = SplitCubic(from, ctrl0, ctrl1, to, scratch[:0])
|
||||
for _, q := range scratch {
|
||||
quad := StrokeQuad{
|
||||
Contour: contour,
|
||||
Quad: q,
|
||||
}
|
||||
quads = append(quads, quad)
|
||||
}
|
||||
default:
|
||||
panic("unsupported scene command")
|
||||
}
|
||||
pathData = pathData[scene.CommandSize+4:]
|
||||
}
|
||||
return quads
|
||||
}
|
||||
|
||||
func SplitCubic(from, ctrl0, ctrl1, to f32.Point, quads []QuadSegment) []QuadSegment {
|
||||
// Set the maximum distance proportionally to the longest side
|
||||
// of the bounding rectangle.
|
||||
hull := f32.Rectangle{
|
||||
Min: from,
|
||||
Max: ctrl0,
|
||||
}.Canon().Union(f32.Rectangle{
|
||||
Min: ctrl1,
|
||||
Max: to,
|
||||
}.Canon())
|
||||
l := hull.Dx()
|
||||
if h := hull.Dy(); h > l {
|
||||
l = h
|
||||
}
|
||||
maxDist := l * 0.001
|
||||
approxCubeTo(&quads, 0, maxDist*maxDist, from, ctrl0, ctrl1, to)
|
||||
return quads
|
||||
}
|
||||
|
||||
// approxCubeTo approximates a cubic Bézier by a series of quadratic
|
||||
// curves.
|
||||
func approxCubeTo(quads *[]QuadSegment, splits int, maxDistSq float32, from, 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
|
||||
// using, q0 := 3ctrl0 - pen, q1 := 3ctrl1 - to
|
||||
// C = (q0 + q1)/4
|
||||
q0 := ctrl0.Mul(3).Sub(from)
|
||||
q1 := ctrl1.Mul(3).Sub(to)
|
||||
c := q0.Add(q1).Mul(1.0 / 4.0)
|
||||
const maxSplits = 32
|
||||
if splits >= maxSplits {
|
||||
*quads = append(*quads, QuadSegment{From: from, Ctrl: c, To: 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|
|
||||
// reusing, q0 := 3ctrl0 - pen, q1 := 3ctrl1 - to
|
||||
// d = sqrt(3)/36 * |-q1 + q0|
|
||||
//
|
||||
// To save a square root, compare d² with the squared tolerance.
|
||||
v := q0.Sub(q1)
|
||||
d2 := (v.X*v.X + v.Y*v.Y) * 3 / (36 * 36)
|
||||
if d2 <= maxDistSq {
|
||||
*quads = append(*quads, QuadSegment{From: from, Ctrl: c, To: to})
|
||||
return splits
|
||||
}
|
||||
// De Casteljau split the curve and approximate the halves.
|
||||
t := float32(0.5)
|
||||
c0 := from.Add(ctrl0.Sub(from).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 = approxCubeTo(quads, splits, maxDistSq, from, c0, c01, c0112)
|
||||
splits = approxCubeTo(quads, splits, maxDistSq, c0112, c12, c2, to)
|
||||
return splits
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user