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