all: initial import

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2019-03-30 13:18:49 +01:00
commit 0f05231c35
102 changed files with 17926 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
.gradle
**/android/build
+3
View File
@@ -0,0 +1,3 @@
This project is dual-licensed under the Unlicense and MIT licenses.
You may use this code under the terms of either license.
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2019 Elias Naur
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.
+60
View File
@@ -0,0 +1,60 @@
# Gio
Gio implements portable immediate mode GUI programs in Go. Gio programs run on all the major platforms:
iOS/tvOS, Android, Linux (Wayland), macOS and Windows.
## Quickstart
Gio is designed to work with very few dependencies. It depends only on the platform libraries for
window management, input and GPU drawing.
For Linux you need Wayland and the `wayland-client`, `wayland-egl`, `wayland-cursor`, and `xkbcommon`
development packages.
Xcode is required for macOS and iOS.
For Windows you need the ANGLE drivers for emulating OpenGL ES. You can build ANGLE yourself or use
[mine](https://drive.google.com/file/d/1k2950mHNtR2iwhweHS1rJ7reChTa3rki/view?usp=sharing).
With Go 1.12 or newer,
$ go run gioui.org/apps/gophers
should display a simple (nonsense) demo.
## Android
For Android you need the Android SDK with the NDK installed. Point the ANDROID_HOME to the SDK root
directory.
To build a Gio program as an .aar package, use the gio tool. For example,
$ go run gioui.org/cmd/gio -target android gioui.org/apps/gophers
to produce gophers.aar, ready to use in an Android project. To run
the demo on an Android device:
$ git clone https://git.sr.ht/~eliasnaur/gio
$ cd gio/apps/gophers/android
$ go run gioui.org/cmd/gio -target android ..
$ ./gradlew installDebug # gradlew.bat on Windows
The gio tool passes command line arguments to os.Args at runtime:
$ go run gioui.org/cmd/gio -target android .. -token <github token>
## License
Dual-licensed under MIT or the [UNLICENSE](http://unlicense.org).
## Contributing
Discussion and patches: [~eliasnaur/gio-dev@lists.sr.ht](mailto:~eliasnaur/gio-dev@lists.sr.ht).
[Instructions](https://man.sr.ht/git.sr.ht/send-email.md). for using git-send-email for sending patches.
Contributors must agree to the [developer certificate og origin](https://developercertificate.org/),
to ensure their work is compatible with the MIT and the UNLICENSE. Sign your commits with Signed-off-by
statements to show your agreement. For convenience, the `git commit --sign` signs a commit with the
name and email from your `user.name` and `user.email` settings.
Bugs and TODOs go in the [issue tracker](https://todo.sr.ht/~eliasnaur/gio).
+25
View File
@@ -0,0 +1,25 @@
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 <http://unlicense.org/>
+11
View File
@@ -0,0 +1,11 @@
module gioui.org/apps
go 1.13
require (
gioui.org/ui v0.0.0-20190330124410-f25b44831f2b
github.com/google/go-github/v24 v24.0.1
golang.org/x/exp v0.0.0-20190321205749-f0864edee7f3
golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f
golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914
)
+41
View File
@@ -0,0 +1,41 @@
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
gioui.org/ui v0.0.0-20190330124410-f25b44831f2b h1:fY5FJRK/vwDZiNeJOQwGbr3QJd4ihJh6OFVvrqC4Djk=
gioui.org/ui v0.0.0-20190330124410-f25b44831f2b/go.mod h1:Nsy5gLRWhMMNMmed9+KjrQ8XXT7a8u8s21zgn/er6d4=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-github/v24 v24.0.1 h1:KCt1LjMJEey1qvPXxa9SjaWxwTsCWSq6p2Ju57UR4Q4=
github.com/google/go-github/v24 v24.0.1/go.mod h1:CRqaW1Uns1TCkP0wqTpxYyRxRjxwvKU/XSS44u6X74M=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/exp v0.0.0-20190321205749-f0864edee7f3 h1:Ep4L2ibjtJcW6IP73KbcJAU0cpNKsLNSSP2jE1xlCys=
golang.org/x/exp v0.0.0-20190321205749-f0864edee7f3/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f h1:FO4MZ3N56GnxbqxGKqh+YTzUWQ2sDwtFQEZgLOxh9Jc=
golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914 h1:jIOcLT9BZzyJ9ce+IwwZ+aF9yeCqzrR+NrD68a/SHKw=
golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180824143301-4910a1d54f87/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190329044733-9eb1bfa1ce65 h1:hOY+O8MxdkPV10pNf7/XEHaySCiPKxixMKUshfHsGn0=
golang.org/x/sys v0.0.0-20190329044733-9eb1bfa1ce65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+32
View File
@@ -0,0 +1,32 @@
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.1'
}
}
apply plugin: 'com.android.application'
android {
buildToolsVersion "28.0.3"
compileSdkVersion 27
defaultConfig {
targetSdkVersion 27
minSdkVersion 16
versionCode 1
versionName "0.1"
}
}
dependencies {
implementation fileTree(dir: '.', include: ['*.aar'])
}
repositories {
google()
jcenter()
}
Binary file not shown.
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored Executable
+172
View File
@@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
+84
View File
@@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.gioui.apps.demo"
android:versionCode="1"
android:versionName="1.0">
<uses-permission android:name="android.permission.INTERNET" />
<uses-feature android:glEsVersion="0x00020000"/>
<application android:label="Gopher Chat">
<activity android:name="org.gioui.GioActivity"
android:label="@string/app_name"
android:theme="@android:style/Theme.NoTitleBar"
android:configChanges="orientation|keyboardHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Gio Demo</string>
</resources>
@@ -0,0 +1,330 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
644702662225DDD70022507C /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 644702652225DDD70022507C /* main.m */; };
6447028A2225E97B0022507C /* Gophers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 644702892225E97B0022507C /* Gophers.framework */; };
64CCFF572121A32B00B48E05 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 64CCFF562121A32B00B48E05 /* Assets.xcassets */; };
64CCFF5A2121A32B00B48E05 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 64CCFF582121A32B00B48E05 /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
644702652225DDD70022507C /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
644702892225E97B0022507C /* Gophers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Gophers.framework; path = gophers/Gophers.framework; sourceTree = "<group>"; };
64CCFF432121A32800B48E05 /* gophers.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = gophers.app; sourceTree = BUILT_PRODUCTS_DIR; };
64CCFF562121A32B00B48E05 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
64CCFF592121A32B00B48E05 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
64CCFF5B2121A32B00B48E05 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
64CCFF402121A32800B48E05 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
6447028A2225E97B0022507C /* Gophers.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
64CCFF3A2121A32700B48E05 = {
isa = PBXGroup;
children = (
64CCFF452121A32800B48E05 /* gophers */,
64CCFF442121A32800B48E05 /* Products */,
644702892225E97B0022507C /* Gophers.framework */,
);
sourceTree = "<group>";
};
64CCFF442121A32800B48E05 /* Products */ = {
isa = PBXGroup;
children = (
64CCFF432121A32800B48E05 /* gophers.app */,
);
name = Products;
sourceTree = "<group>";
};
64CCFF452121A32800B48E05 /* gophers */ = {
isa = PBXGroup;
children = (
64CCFF562121A32B00B48E05 /* Assets.xcassets */,
64CCFF582121A32B00B48E05 /* LaunchScreen.storyboard */,
64CCFF5B2121A32B00B48E05 /* Info.plist */,
644702652225DDD70022507C /* main.m */,
);
path = gophers;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
64CCFF422121A32800B48E05 /* gophers */ = {
isa = PBXNativeTarget;
buildConfigurationList = 64CCFF602121A32B00B48E05 /* Build configuration list for PBXNativeTarget "gophers" */;
buildPhases = (
64CCFF3F2121A32800B48E05 /* Sources */,
64CCFF402121A32800B48E05 /* Frameworks */,
64CCFF412121A32800B48E05 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = gophers;
productName = gophers;
productReference = 64CCFF432121A32800B48E05 /* gophers.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
64CCFF3B2121A32800B48E05 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0940;
ORGANIZATIONNAME = eliasnaur.com;
TargetAttributes = {
64CCFF422121A32800B48E05 = {
CreatedOnToolsVersion = 9.4.1;
};
};
};
buildConfigurationList = 64CCFF3E2121A32800B48E05 /* Build configuration list for PBXProject "gophers" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 64CCFF3A2121A32700B48E05;
productRefGroup = 64CCFF442121A32800B48E05 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
64CCFF422121A32800B48E05 /* gophers */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
64CCFF412121A32800B48E05 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
64CCFF572121A32B00B48E05 /* Assets.xcassets in Resources */,
64CCFF5A2121A32B00B48E05 /* LaunchScreen.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
64CCFF3F2121A32800B48E05 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
644702662225DDD70022507C /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
64CCFF582121A32B00B48E05 /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
64CCFF592121A32B00B48E05 /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
64CCFF5E2121A32B00B48E05 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.4;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
64CCFF5F2121A32B00B48E05 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.4;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
64CCFF612121A32B00B48E05 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 9NFTYP4MQ3;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/gophers",
);
INFOPLIST_FILE = gophers/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.eliasnaur.gophers;
PRODUCT_NAME = "$(TARGET_NAME)";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
64CCFF622121A32B00B48E05 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 9NFTYP4MQ3;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/gophers",
);
INFOPLIST_FILE = gophers/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.eliasnaur.gophers;
PRODUCT_NAME = "$(TARGET_NAME)";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
64CCFF3E2121A32800B48E05 /* Build configuration list for PBXProject "gophers" */ = {
isa = XCConfigurationList;
buildConfigurations = (
64CCFF5E2121A32B00B48E05 /* Debug */,
64CCFF5F2121A32B00B48E05 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
64CCFF602121A32B00B48E05 /* Build configuration list for PBXNativeTarget "gophers" */ = {
isa = XCConfigurationList;
buildConfigurations = (
64CCFF612121A32B00B48E05 /* Debug */,
64CCFF622121A32B00B48E05 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 64CCFF3B2121A32800B48E05 /* Project object */;
}
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:../gophers/Gophers.framework">
</FileRef>
<FileRef
location = "self:">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>
@@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
+37
View File
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
+8
View File
@@ -0,0 +1,8 @@
@import UIKit;
@import Gophers;
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([GioAppDelegate class]));
}
}
+726
View File
@@ -0,0 +1,726 @@
// SPDX-License-Identifier: Unlicense OR MIT
package main
import (
"context"
"flag"
"fmt"
"image"
"image/color"
"log"
"net/http"
"os"
"golang.org/x/image/draw"
"golang.org/x/oauth2"
_ "image/jpeg"
_ "image/png"
_ "net/http/pprof"
"gioui.org/ui"
"gioui.org/ui/app"
gdraw "gioui.org/ui/draw"
"gioui.org/ui/f32"
"gioui.org/ui/gesture"
"gioui.org/ui/key"
"gioui.org/ui/layout"
"gioui.org/ui/measure"
"gioui.org/ui/pointer"
"gioui.org/ui/text"
"gioui.org/ui/widget"
"golang.org/x/exp/shiny/iconvg"
"github.com/google/go-github/v24/github"
"golang.org/x/image/font/gofont/gobold"
"golang.org/x/image/font/gofont/goitalic"
"golang.org/x/image/font/gofont/gomono"
"golang.org/x/image/font/gofont/goregular"
"golang.org/x/image/font/sfnt"
"golang.org/x/exp/shiny/materialdesign/icons"
)
type App struct {
w *app.Window
cfg *ui.Config
faces measure.Faces
pqueue *pointer.Queue
kqueue *key.Queue
fab *ActionButton
usersList *layout.List
edit, edit2 *text.Editor
users []*user
userClicks []gesture.Click
selectedUser *userPage
updateUsers chan []*user
}
type userPage struct {
cfg *ui.Config
faces measure.Faces
redraw redrawer
user *user
commitsList *layout.List
commits []*github.Commit
commitsResult chan []*github.Commit
}
type user struct {
name string
login string
company string
avatar image.Image
}
type icon struct {
src []byte
size ui.Value
// Cached values.
img image.Image
imgSize float32
}
type redrawer func()
type ActionButton struct {
face text.Face
cfg *ui.Config
Open bool
icons []*icon
sendIco *icon
btnClicker *gesture.Click
btnsClicker *gesture.Click
}
var (
profile = flag.Bool("profile", false, "serve profiling data at http://localhost:6060")
stats = flag.Bool("stats", false, "show rendering statistics")
token = flag.String("token", "", "Github authentication token")
)
var fonts struct {
regular *sfnt.Font
bold *sfnt.Font
italic *sfnt.Font
mono *sfnt.Font
}
func main() {
if *token == "" {
fmt.Println("The quota for anonymous GitHub API access is very low. Specify a token with -token to avoid quota errors.")
fmt.Println("See https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line.")
}
err := app.CreateWindow(app.WindowOptions{
Width: ui.Dp(400),
Height: ui.Dp(800),
Title: "Gopher Chat",
})
if err != nil {
log.Fatal(err)
}
app.Main()
}
func initProfiling() {
if !*profile {
return
}
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
}
func init() {
flag.Parse()
initProfiling()
fonts.regular = mustLoadFont(goregular.TTF)
fonts.bold = mustLoadFont(gobold.TTF)
fonts.italic = mustLoadFont(goitalic.TTF)
fonts.mono = mustLoadFont(gomono.TTF)
go func() {
for w := range app.Windows() {
w := w
go func() {
if err := newApp(w).run(); err != nil {
log.Fatal(err)
}
}()
}
}()
}
func (a *App) run() error {
a.w.Profiling = *stats
for a.w.IsAlive() {
select {
case users := <-a.updateUsers:
a.users = users
a.userClicks = make([]gesture.Click, len(users))
a.w.Redraw()
case e := <-a.w.Events():
switch e := e.(type) {
case pointer.Event:
a.pqueue.Push(e)
case key.Event:
a.kqueue.Push(e)
if e, ok := e.(key.Chord); ok {
switch e.Name {
case key.NameEscape:
os.Exit(0)
case 'P':
if e.Modifiers&key.ModCommand != 0 {
a.w.Profiling = !a.w.Profiling
}
}
}
case app.ChangeStage:
case app.Draw:
a.cfg = e.Config
a.faces.Cfg = a.cfg
cs := layout.ExactConstraints(a.w.Size())
root, _ := a.Layout(cs)
if a.w.Profiling {
op, _ := layout.Align(
layout.NE,
layout.Margin(a.cfg,
layout.Margins{Top: ui.Dp(16)},
text.Label{Src: textColor, Face: a.face(fonts.mono, 8), Text: a.w.Timings()},
),
).Layout(cs)
root = ui.Ops{root, op}
}
a.w.Draw(root)
a.w.SetTextInput(a.kqueue.Frame(root))
a.pqueue.Frame(root)
a.faces.Frame()
}
a.w.Ack()
}
}
return a.w.Err()
}
func newApp(w *app.Window) *App {
a := &App{
w: w,
updateUsers: make(chan []*user),
pqueue: new(pointer.Queue),
kqueue: new(key.Queue),
}
a.usersList = &layout.List{Axis: layout.Vertical}
a.fab = &ActionButton{
face: a.face(fonts.regular, 9),
sendIco: &icon{src: icons.ContentSend, size: ui.Dp(24)},
icons: []*icon{},
btnClicker: new(gesture.Click),
btnsClicker: new(gesture.Click),
}
a.edit2 = &text.Editor{
Src: textColor,
Face: a.face(fonts.italic, 14),
//Alignment: text.End,
SingleLine: true,
}
a.edit2.SetText("Single line editor. Edit me!")
a.edit = &text.Editor{
Src: textColor,
Face: a.face(fonts.regular, 14),
//Alignment: text.End,
//SingleLine: true,
}
a.edit.SetText(longTextSample)
go a.fetchContributors()
return a
}
func githubClient(ctx context.Context) *github.Client {
var tc *http.Client
if *token != "" {
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: *token},
)
tc = oauth2.NewClient(ctx, ts)
}
return github.NewClient(tc)
}
func (a *App) fetchContributors() {
ctx := context.Background()
client := githubClient(ctx)
cons, _, err := client.Repositories.ListContributors(ctx, "golang", "go", nil)
if err != nil {
fmt.Fprintf(os.Stderr, "github: failed to fetch contributors: %v\n", err)
return
}
var users []*user
userErrs := make(chan error, len(cons))
avatarErrs := make(chan error, len(cons))
for _, con := range cons {
con := con
avatar := con.GetAvatarURL()
if avatar == "" {
continue
}
u := &user{
login: con.GetLogin(),
}
users = append(users, u)
go func() {
guser, _, err := client.Users.Get(ctx, u.login)
if err != nil {
avatarErrs <- err
return
}
u.name = guser.GetName()
u.company = guser.GetCompany()
avatarErrs <- nil
}()
go func() {
a, err := fetchImage(avatar)
u.avatar = a
userErrs <- err
}()
}
for i := 0; i < len(cons); i++ {
if err := <-userErrs; err != nil {
fmt.Fprintf(os.Stderr, "github: failed to fetch user: %v\n", err)
}
if err := <-avatarErrs; err != nil {
fmt.Fprintf(os.Stderr, "github: failed to fetch avatar: %v\n", err)
}
}
// Drop users with no avatar or name.
for i := len(users) - 1; i >= 0; i-- {
if u := users[i]; u.name == "" || u.avatar == nil {
users = append(users[:i], users[i+1:]...)
}
}
a.updateUsers <- users
}
func fetchImage(url string) (image.Image, error) {
resp, err := http.Get(url)
if err != nil {
return nil, fmt.Errorf("fetchImage: http.Get(%q): %v", url, err)
}
defer resp.Body.Close()
img, _, err := image.Decode(resp.Body)
if err != nil {
return nil, fmt.Errorf("fetchImage: image decode failed: %v", err)
}
return img, nil
}
func mustLoadFont(fontData []byte) *sfnt.Font {
fnt, err := sfnt.Parse(fontData)
if err != nil {
panic("failed to load font")
}
return fnt
}
var (
backgroundColor = rgb(0xfbfbfb)
brandColor = rgb(0x62798c)
divColor = rgb(0xecedef)
textColor = rgb(0x333333)
secTextColor = rgb(0xe0e4e8)
tertTextColor = rgb(0xbbbbbb)
whiteColor = rgb(0xffffff)
accentColor = rgb(0x00c28c)
)
func rgb(c uint32) *image.Uniform {
return argb((0xff << 24) | c)
}
func argb(c uint32) *image.Uniform {
col := color.NRGBA{A: uint8(c >> 24), R: uint8(c >> 16), G: uint8(c >> 8), B: uint8(c)}
return &image.Uniform{col}
}
func (a *App) face(f *sfnt.Font, size float32) text.Face {
return a.faces.For(f, ui.Sp(size))
}
func (a *App) Layout(cs layout.Constraints) (ui.Op, layout.Dimens) {
if a.selectedUser == nil {
return a.layoutUsers(cs)
} else {
a.selectedUser.Update(a.pqueue)
return a.selectedUser.Layout(cs)
}
}
func newUserPage(cfg *ui.Config, user *user, redraw redrawer, faces measure.Faces) *userPage {
up := &userPage{
cfg: cfg,
faces: faces,
redraw: redraw,
user: user,
commitsList: &layout.List{Axis: layout.Vertical},
commitsResult: make(chan []*github.Commit, 1),
}
up.fetchCommits()
return up
}
func (up *userPage) Update(pqueue pointer.Events) {
up.commitsList.Scroll(up.cfg, pqueue)
}
func (up *userPage) Layout(cs layout.Constraints) (ui.Op, layout.Dimens) {
l := up.commitsList
var ops ui.Ops
if l.Dragging() {
ops = append(ops, key.OpHideInput{})
}
select {
case commits := <-up.commitsResult:
up.commits = commits
default:
}
for i, ok := l.Init(cs, len(up.commits)); ok; i, ok = l.Index() {
l.Elem(up.commit(i))
}
op, dims := l.Layout()
return append(ops, op), dims
}
func (up *userPage) commit(index int) layout.Widget {
sz := ui.Dp(48)
u := up.user
c := up.cfg
avatar := clipCircle(layout.Sized(c, sz, sz, widget.Image{Src: u.avatar, Rect: u.avatar.Bounds()}))
msg := up.commits[index].GetMessage()
label := text.Label{Src: textColor, Face: up.faces.For(fonts.regular, ui.Sp(12)), Text: msg}
return layout.Margin(c,
layout.Margins{Top: ui.Dp(16), Right: ui.Dp(8), Left: ui.Dp(8)},
layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) {
return (&layout.Flex{Axis: layout.Horizontal, MainAxisAlignment: layout.Start, CrossAxisAlignment: layout.Start}).
Init(cs).
Rigid(avatar).
Flexible(-1, 1, layout.Fit, layout.Margin(c, layout.Margins{Left: ui.Dp(8)}, label)).
Layout()
}),
)
}
func (up *userPage) fetchCommits() {
go func() {
ctx := context.Background()
gh := githubClient(ctx)
repoCommits, _, err := gh.Repositories.ListCommits(ctx, "golang", "go", &github.CommitsListOptions{
Author: up.user.login,
})
if err != nil {
log.Fatal(err)
}
var commits []*github.Commit
for _, commit := range repoCommits {
if c := commit.GetCommit(); c != nil {
commits = append(commits, c)
}
}
up.commitsResult <- commits
up.redraw()
}()
}
func (a *App) layoutUsers(cs layout.Constraints) (ui.Op, layout.Dimens) {
c := a.cfg
a.fab.Update(c, a.pqueue)
st := (&layout.Stack{Alignment: layout.Center}).Init(cs).
Rigid(layout.Align(
layout.SE,
layout.Margin(c,
layout.EqualMargins(ui.Dp(16)),
a.fab,
),
))
a.edit.Update(c, a.pqueue, a.kqueue)
a.edit2.Update(c, a.pqueue, a.kqueue)
return st.Expand(0, layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) {
return (&layout.Flex{Axis: layout.Vertical, MainAxisAlignment: layout.Start, CrossAxisAlignment: layout.Stretch}).Init(cs).
Rigid(layout.Margin(c,
layout.EqualMargins(ui.Dp(16)),
layout.Sized(c, ui.Dp(0), ui.Dp(200), a.edit),
)).
Rigid(layout.Margin(c,
layout.Margins{Bottom: ui.Dp(16), Left: ui.Dp(16), Right: ui.Dp(16)},
a.edit2,
)).
Rigid(layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) {
return (&layout.Stack{Alignment: layout.Center}).Init(cs).
Rigid(layout.Margin(c,
layout.Margins{Top: ui.Dp(16), Right: ui.Dp(8), Bottom: ui.Dp(8), Left: ui.Dp(8)},
text.Label{Src: rgb(0x888888), Face: a.face(fonts.regular, 9), Text: "GOPHERS"},
)).
Expand(0, fill(rgb(0xf2f2f2))).
Layout()
})).
Flexible(-1, 1, layout.Fit, a.layoutContributors()).
Layout()
})).
Layout()
}
func (a *ActionButton) Update(c *ui.Config, q pointer.Events) {
a.cfg = c
a.btnsClicker.Update(q)
a.btnClicker.Update(q)
}
func (a *ActionButton) Layout(cs layout.Constraints) (ui.Op, layout.Dimens) {
c := a.cfg
fl := (&layout.Flex{Axis: layout.Vertical, MainAxisAlignment: layout.Start, CrossAxisAlignment: layout.End, MainAxisSize: layout.Min}).Init(cs)
fabCol := brandColor
fl.Rigid(layout.Margin(c,
layout.Margins{Top: ui.Dp(4)},
layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) {
op, dims := fab(c, a.sendIco.image(c), fabCol, ui.Dp(56)).Layout(cs)
ops := ui.Ops{op, a.btnClicker.Op(gesture.Ellipse(dims.Size))}
return ops, dims
}),
))
return fl.Layout()
}
func (a *App) layoutContributors() layout.Widget {
return layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) {
c := a.cfg
l := a.usersList
l.Scroll(c, a.pqueue)
var ops ui.Ops
if l.Dragging() {
ops = append(ops, key.OpHideInput{})
}
for i, ok := l.Init(cs, len(a.users)); ok; i, ok = l.Index() {
l.Elem(a.user(c, i))
}
op, dims := l.Layout()
return append(ops, op), dims
})
}
func (a *App) user(c *ui.Config, index int) layout.Widget {
u := a.users[index]
click := &a.userClicks[index]
sz := ui.Dp(48)
for _, r := range click.Update(a.pqueue) {
if r.Type == gesture.TypeClick {
a.selectedUser = newUserPage(a.cfg, u, a.w.Redraw, a.faces)
}
}
avatar := clipCircle(layout.Sized(a.cfg, sz, sz, widget.Image{Src: u.avatar, Rect: u.avatar.Bounds()}))
return layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) {
elem := (&layout.Flex{Axis: layout.Vertical, MainAxisAlignment: layout.Start, CrossAxisAlignment: layout.Start}).Init(cs)
elem.Rigid(layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) {
op, dims := layout.Margin(c,
layout.EqualMargins(ui.Dp(8)),
layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) {
return centerRowOpts().Init(cs).
Rigid(layout.Margin(c, layout.Margins{Right: ui.Dp(8)}, avatar)).
Rigid(column(
baseline(
text.Label{Src: textColor, Face: a.face(fonts.regular, 11), Text: u.name},
layout.Align(layout.E, layout.Margin(c,
layout.Margins{Left: ui.Dp(2)},
text.Label{Src: textColor, Face: a.face(fonts.regular, 8), Text: "3 hours ago"},
)),
),
layout.Margin(c,
layout.Margins{Top: ui.Dp(4)},
text.Label{Src: tertTextColor, Face: a.face(fonts.regular, 10), Text: u.company},
),
)).
Layout()
}),
).Layout(cs)
ops := ui.Ops{op, click.Op(gesture.Rect(dims.Size))}
return ops, dims
}))
return elem.Layout()
})
}
func fill(img image.Image) layout.Widget {
return widget.Image{Src: img, Rect: image.Rectangle{Max: image.Point{X: 1, Y: 1}}}
}
func column(widgets ...layout.Widget) layout.Widget {
return flex(&layout.Flex{Axis: layout.Vertical, MainAxisAlignment: layout.Start, CrossAxisAlignment: layout.Start}, widgets...)
}
func centerColumn(widgets ...layout.Widget) layout.Widget {
return flex(&layout.Flex{Axis: layout.Vertical, MainAxisAlignment: layout.Start, CrossAxisAlignment: layout.Center, MainAxisSize: layout.Min}, widgets...)
}
func centerRowOpts(widgets ...layout.Widget) *layout.Flex {
return &layout.Flex{Axis: layout.Horizontal, MainAxisAlignment: layout.Start, CrossAxisAlignment: layout.Center, MainAxisSize: layout.Min}
}
func centerRow(widgets ...layout.Widget) layout.Widget {
return flex(centerRowOpts(), widgets...)
}
func baseline(widgets ...layout.Widget) layout.Widget {
return flex(&layout.Flex{Axis: layout.Horizontal, CrossAxisAlignment: layout.Baseline, MainAxisSize: layout.Min}, widgets...)
}
func flex(f *layout.Flex, widgets ...layout.Widget) layout.Widget {
return layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) {
f.Init(cs)
for _, w := range widgets {
f.Rigid(w)
}
return f.Layout()
})
}
func clipCircle(w layout.Widget) layout.Widget {
return layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) {
op, dims := w.Layout(cs)
max := dims.Size.X
if dy := dims.Size.Y; dy > max {
max = dy
}
szf := float32(max)
rr := szf * .5
op = gdraw.OpClip{
Path: rrect(szf, szf, rr, rr, rr, rr),
Op: op,
}
return op, dims
})
}
func fab(c *ui.Config, ico, col image.Image, size ui.Value) layout.Widget {
return layout.F(func(cs layout.Constraints) (ui.Op, layout.Dimens) {
szf := c.Pixels(size)
sz := int(szf + .5)
rr := szf * .5
dp := image.Point{X: (sz - ico.Bounds().Dx()) / 2, Y: (sz - ico.Bounds().Dy()) / 2}
dims := image.Point{X: sz, Y: sz}
op := gdraw.OpClip{
Path: rrect(szf, szf, rr, rr, rr, rr),
Op: ui.Ops{
gdraw.OpImage{Rect: f32.Rectangle{Max: f32.Point{X: float32(sz), Y: float32(sz)}}, Src: col, SrcRect: col.Bounds()},
gdraw.OpImage{
Rect: toRectF(ico.Bounds().Add(dp)),
Src: ico,
SrcRect: ico.Bounds(),
},
},
}
return op, layout.Dimens{Size: dims}
})
}
func toRectF(r image.Rectangle) f32.Rectangle {
return f32.Rectangle{
Min: f32.Point{X: float32(r.Min.X), Y: float32(r.Min.Y)},
Max: f32.Point{X: float32(r.Max.X), Y: float32(r.Max.Y)},
}
}
func (ic *icon) image(cfg *ui.Config) image.Image {
sz := cfg.Pixels(ic.size)
if sz == ic.imgSize {
return ic.img
}
m, _ := iconvg.DecodeMetadata(ic.src)
dx, dy := m.ViewBox.AspectRatio()
img := image.NewNRGBA(image.Rectangle{Max: image.Point{X: int(sz), Y: int(sz * dy / dx)}})
var ico iconvg.Rasterizer
ico.SetDstImage(img, img.Bounds(), draw.Src)
// Use white for icons.
m.Palette[0] = color.RGBA{A: 0xff, R: 0xff, G: 0xff, B: 0xff}
iconvg.Decode(&ico, ic.src, &iconvg.DecodeOptions{
Palette: &m.Palette,
})
ic.img = img
ic.imgSize = sz
return img
}
// https://pomax.github.io/bezierinfo/#circles_cubic.
func rrect(width, height, se, sw, nw, ne float32) *gdraw.Path {
w, h := float32(width), float32(height)
const c = 0.55228475 // 4*(sqrt(2)-1)/3
var b gdraw.PathBuilder
b.Move(f32.Point{X: w, Y: h - se})
b.Cube(f32.Point{X: 0, Y: se * c}, f32.Point{X: -se + se*c, Y: se}, f32.Point{X: -se, Y: se}) // SE
b.Line(f32.Point{X: sw - w + se, Y: 0})
b.Cube(f32.Point{X: -sw * c, Y: 0}, f32.Point{X: -sw, Y: -sw + sw*c}, f32.Point{X: -sw, Y: -sw}) // SW
b.Line(f32.Point{X: 0, Y: nw - h + sw})
b.Cube(f32.Point{X: 0, Y: -nw * c}, f32.Point{X: nw - nw*c, Y: -nw}, f32.Point{X: nw, Y: -nw}) // NW
b.Line(f32.Point{X: w - ne - nw, Y: 0})
b.Cube(f32.Point{X: ne * c, Y: 0}, f32.Point{X: ne, Y: ne - ne*c}, f32.Point{X: ne, Y: ne}) // NE
return b.Path()
}
const longTextSample = `1. I learned from my grandfather, Verus, to use good manners, and to
put restraint on anger. 2. In the famous memory of my father I had a
pattern of modesty and manliness. 3. Of my mother I learned to be
pious and generous; to keep myself not only from evil deeds, but even
from evil thoughts; and to live with a simplicity which is far from
customary among the rich. 4. I owe it to my great-grandfather that I
did not attend public lectures and discussions, but had good and able
teachers at home; and I owe him also the knowledge that for things of
this nature a man should count no expense too great.
5. My tutor taught me not to favour either green or blue at the
chariot races, nor, in the contests of gladiators, to be a supporter
either of light or heavy armed. He taught me also to endure labour;
not to need many things; to serve myself without troubling others; not
to intermeddle in the affairs of others, and not easily to listen to
slanders against them.
6. Of Diognetus I had the lesson not to busy myself about vain things;
not to credit the great professions of such as pretend to work
wonders, or of sorcerers about their charms, and their expelling of
Demons and the like; not to keep quails (for fighting or divination),
nor to run after such things; to suffer freedom of speech in others,
and to apply myself heartily to philosophy. Him also I must thank for
my hearing first Bacchius, then Tandasis and Marcianus; that I wrote
dialogues in my youth, and took a liking to the philosopher's pallet
and skins, and to the other things which, by the Grecian discipline,
belong to that profession.
7. To Rusticus I owe my first apprehensions that my nature needed
reform and cure; and that I did not fall into the ambition of the
common Sophists, either by composing speculative writings or by
declaiming harangues of exhortation in public; further, that I never
strove to be admired by ostentation of great patience in an ascetic
life, or by display of activity and application; that I gave over the
study of rhetoric, poetry, and the graces of language; and that I did
not pace my house in my senatorial robes, or practise any similar
affectation. I observed also the simplicity of style in his letters,
particularly in that which he wrote to my mother from Sinuessa. I
learned from him to be easily appeased, and to be readily reconciled
with those who had displeased me or given cause of offence, so soon as
they inclined to make their peace; to read with care; not to rest
satisfied with a slight and superficial knowledge; nor quickly to
assent to great talkers. I have him to thank that I met with the
discourses of Epictetus, which he furnished me from his own library.
8. From Apollonius I learned true liberty, and tenacity of purpose; to
regard nothing else, even in the smallest degree, but reason always;
and always to remain unaltered in the agonies of pain, in the losses
of children, or in long diseases. He afforded me a living example of
how the same man can, upon occasion, be most yielding and most
inflexible. He was patient in exposition; and, as might well be seen,
esteemed his fine skill and ability in teaching others the principles
of philosophy as the least of his endowments. It was from him that I
learned how to receive from friends what are thought favours without
seeming humbled by the giver or insensible to the gift.`
+57
View File
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: Unlicense OR MIT
// Command assets converts data files to Go source code.
package main
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
func main() {
if len(os.Args) != 2 {
fmt.Fprintln(os.Stderr, "please specify a directory to convert")
os.Exit(1)
}
var w bytes.Buffer
w.WriteString("// Code generated by command assets DO NOT EDIT.\n\n")
w.WriteString("package assets\n\n")
w.WriteString("import (\n")
w.WriteString("\t\"io\"\n")
w.WriteString("\t\"strings\"\n")
w.WriteString(")\n\n")
dir := os.Args[1]
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
if filepath.Ext(path) == ".go" {
return nil
}
name := path[len(dir)+1:]
name = strings.ReplaceAll(name, "/", "_")
name = strings.ReplaceAll(name, ".", "_")
name = strings.Title(name)
w.WriteString(fmt.Sprintf("func %s() io.Reader {\n", name))
content, err := ioutil.ReadFile(path)
if err != nil {
return err
}
w.WriteString(fmt.Sprintf("\tcontent := %q\n", content))
w.WriteString("\treturn strings.NewReader(content)\n")
w.WriteString("}\n")
return nil
})
err := ioutil.WriteFile(filepath.Join(dir, "assets.go"), w.Bytes(), 0644)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
}
+571
View File
@@ -0,0 +1,571 @@
// SPDX-License-Identifier: Unlicense OR MIT
package main
import (
"archive/zip"
"bytes"
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"golang.org/x/sync/errgroup"
)
// zip.Writer with a sticky error.
type zipWriter struct {
err error
w *zip.Writer
}
// Writer that saves any errors.
type errWriter struct {
w io.Writer
err *error
}
var (
target = flag.String("target", "", "specify target (ios or android)")
archNames = flag.String("arch", "", "specify architecture(s) to include")
destPath = flag.String("o", "", "output file (for Android .aar) or directory (for iOS .framework)")
verbose = flag.Bool("v", false, "verbose output")
)
func main() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s [flags] <pkg>\n", os.Args[0])
flag.PrintDefaults()
os.Exit(2)
}
flag.Parse()
pkg := flag.Arg(0)
if pkg == "" {
flag.Usage()
}
if *target == "" {
fmt.Fprintf(os.Stderr, "Please specify -target\n\n")
flag.PrintDefaults()
os.Exit(2)
}
// Expand relative package paths.
out, err := exec.Command("go", "list", pkg).CombinedOutput()
out = bytes.TrimSpace(out)
if err != nil {
errorf("gio: %s", out)
}
pkg = string(out)
appArgs := flag.Args()[1:]
if err := run(pkg, appArgs); err != nil {
errorf("gio: %v", err)
}
}
func run(pkg string, appArgs []string) error {
tmpDir, err := ioutil.TempDir("", "gio-")
if err != nil {
return err
}
defer os.RemoveAll(tmpDir)
var ldflags string
if len(appArgs) > 0 {
// Pass along arguments to the app.
ldflags = fmt.Sprintf("-X gioui.org/ui/app.extraArgs=%s", strings.Join(appArgs, "|"))
}
var archs []string
switch *target {
case "tvos":
// Only 64-bit support.
archs = []string{"arm64", "amd64"}
default:
archs = []string{"arm", "arm64", "386", "amd64"}
}
if *archNames != "" {
archs = strings.Split(*archNames, ",")
}
switch *target {
case "ios", "tvos":
return runIOS(tmpDir, *target, pkg, archs, ldflags)
case "android":
return runAndroid(tmpDir, pkg, archs, ldflags)
default:
return fmt.Errorf("invalid -target %s\n", *target)
}
}
func runIOS(tmpDir, target, pkg string, archs []string, ldflags string) error {
frameworkRoot := *destPath
if frameworkRoot == "" {
appName := filepath.Base(pkg)
frameworkRoot = fmt.Sprintf("%s.framework", strings.Title(appName))
}
framework := filepath.Base(frameworkRoot)
suf := ".framework"
if !strings.HasSuffix(framework, suf) {
return fmt.Errorf("the specified output %q does not end in '.framework'", frameworkRoot)
}
framework = framework[:len(framework)-len(suf)]
if err := os.RemoveAll(frameworkRoot); err != nil {
return err
}
frameworkDir := filepath.Join(frameworkRoot, "Versions", "A")
for _, dir := range []string{"Headers", "Modules"} {
p := filepath.Join(frameworkDir, dir)
if err := os.MkdirAll(p, 0755); err != nil {
return err
}
}
symlinks := [][2]string{
{"Versions/Current/Headers", "Headers"},
{"Versions/Current/Modules", "Modules"},
{"Versions/Current/" + framework, framework},
{"A", filepath.Join("Versions", "Current")},
}
for _, l := range symlinks {
if err := os.Symlink(l[0], filepath.Join(frameworkRoot, l[1])); err != nil && !os.IsExist(err) {
return err
}
}
exe := filepath.Join(frameworkDir, framework)
lipo := exec.Command("xcrun", "lipo", "-o", exe, "-create")
var builds errgroup.Group
for _, a := range archs {
arch := allArchs[a]
var platformSDK string
var platformOS string
switch target {
case "ios":
platformOS = "ios"
platformSDK = "iphone"
case "tvos":
platformOS = "tvos"
platformSDK = "appletv"
}
switch a {
case "arm", "arm64":
platformSDK += "os"
case "386", "amd64":
platformOS += "-simulator"
platformSDK += "simulator"
default:
return fmt.Errorf("unsupported -arch: %s", a)
}
sdkPathOut, err := runCmd(exec.Command("xcrun", "--sdk", platformSDK, "--show-sdk-path"))
if err != nil {
return err
}
sdkPath := string(bytes.TrimSpace(sdkPathOut))
clangOut, err := runCmd(exec.Command("xcrun", "--sdk", platformSDK, "--find", "clang"))
if err != nil {
return err
}
clang := string(bytes.TrimSpace(clangOut))
cflags := fmt.Sprintf("-fmodules -fobjc-arc -fembed-bitcode -Werror -arch %s -isysroot %s -m%s-version-min=9.0", arch.iosArch, sdkPath, platformOS)
lib := filepath.Join(tmpDir, "gio-"+a)
cmd := exec.Command(
"go",
"build",
"-ldflags=-s -w "+ldflags,
"-buildmode=c-archive",
"-o", lib,
"-tags", "ios",
pkg,
)
lipo.Args = append(lipo.Args, lib)
cmd.Env = append(
os.Environ(),
"GOOS=darwin",
"GOARCH="+a,
"CGO_ENABLED=1",
"CC="+clang,
"CGO_CFLAGS="+cflags,
"CGO_LDFLAGS="+cflags,
)
builds.Go(func() error {
_, err := runCmd(cmd)
return err
})
}
if err := builds.Wait(); err != nil {
return err
}
if _, err := runCmd(lipo); err != nil {
return err
}
appDir, err := appDir()
if err != nil {
return err
}
headerDst := filepath.Join(frameworkDir, "Headers", framework+".h")
headerSrc := filepath.Join(appDir, "framework_ios.h")
if err := copyFile(headerDst, headerSrc); err != nil {
return err
}
module := fmt.Sprintf(`framework module "%s" {
header "%[1]s.h"
export *
}`, framework)
moduleFile := filepath.Join(frameworkDir, "Modules", "module.modulemap")
return ioutil.WriteFile(moduleFile, []byte(module), 0644)
}
func runAndroid(tmpDir, pkg string, archs []string, ldflags string) (err error) {
androidHome := os.Getenv("ANDROID_HOME")
if androidHome == "" {
return errors.New("ANDROID_HOME is not set. Please point it to the root of the Android SDK.")
}
ndkRoot := filepath.Join(androidHome, "ndk-bundle")
if _, err := os.Stat(ndkRoot); err != nil {
return fmt.Errorf("No NDK found in $ANDROID_HOME/ndk-bundle (%s). Use `sdkmanager ndk-bundle` to install it.", ndkRoot)
}
tcRoot := filepath.Join(ndkRoot, "toolchains", "llvm", "prebuilt", archNDK())
sdk := os.Getenv("ANDROID_HOME")
if sdk == "" {
return errors.New("Please set ANDROID_HOME to the Android SDK path")
}
if _, err := os.Stat(sdk); err != nil {
return err
}
platform, err := latestPlatform(sdk)
if err != nil {
return err
}
var builds errgroup.Group
for _, a := range archs {
arch := allArchs[a]
clang := filepath.Join(tcRoot, "bin", arch.clang)
if _, err := os.Stat(clang); err != nil {
return fmt.Errorf("No NDK compiler found. Please make sure you have NDK >= r19c installed. Use the command `sdkmanager ndk-bundle` to install it. Path %s", clang)
}
if runtime.GOOS == "windows" {
// Because of https://github.com/android-ndk/ndk/issues/920,
// we need NDK r19c, not just r19b. Check for the presence of
// clang++.cmd which is only available in r19c.
clangpp := filepath.Join(tcRoot, "bin", arch.clang+"++.cmd")
if _, err := os.Stat(clangpp); err != nil {
return fmt.Errorf("NDK version r19b detected, but >= r19c is required. Use the command `sdkmanager ndk-bundle` to install it.")
}
}
archDir := filepath.Join(tmpDir, "jni", arch.jniArch)
if err := os.MkdirAll(archDir, 0755); err != nil {
return fmt.Errorf("failed to create %q: %v", archDir, err)
}
libFile := filepath.Join(archDir, "libgio.so")
cmd := exec.Command(
"go",
"build",
"-ldflags=-w -s "+ldflags,
"-buildmode=c-shared",
"-o", libFile,
pkg,
)
cmd.Env = append(
os.Environ(),
"GOOS=android",
"GOARCH="+a,
"CGO_ENABLED=1",
"CC="+clang,
"CGO_CFLAGS=-Werror",
)
builds.Go(func() error {
_, err := runCmd(cmd)
return err
})
}
if err := builds.Wait(); err != nil {
return err
}
aarFile := *destPath
if aarFile == "" {
aarFile = fmt.Sprintf("%s.aar", filepath.Base(pkg))
}
if filepath.Ext(aarFile) != ".aar" {
return fmt.Errorf("the specified output %q does not end in '.aar'", aarFile)
}
aar, err := os.Create(aarFile)
if err != nil {
return err
}
defer func() {
if cerr := aar.Close(); err == nil {
err = cerr
}
}()
aarw := newZipWriter(aar)
defer aarw.Close()
aarw.Create("R.txt")
aarw.Create("res/")
manifest := aarw.Create("AndroidManifest.xml")
manifest.Write([]byte(`<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.gioui.app">
<uses-sdk android:minSdkVersion="16"/>
<uses-feature android:glEsVersion="0x00030000" android:required="true" />
</manifest>`))
proguard := aarw.Create("proguard.txt")
proguard.Write([]byte(`-keep class org.gioui.** { *; }`))
for _, a := range archs {
arch := allArchs[a]
libFile := filepath.Join("jni", arch.jniArch, "libgio.so")
aarw.Add(filepath.ToSlash(libFile), filepath.Join(tmpDir, libFile))
}
appDir, err := appDir()
if err != nil {
return err
}
javaFiles, err := filepath.Glob(filepath.Join(appDir, "*.java"))
if err != nil {
return err
}
if len(javaFiles) > 0 {
clsPath := filepath.Join(platform, "android.jar")
classes := filepath.Join(tmpDir, "classes")
if err := os.MkdirAll(classes, 0755); err != nil {
return err
}
javac := exec.Command(
"javac",
"-target", "1.8",
"-source", "1.8",
"-sourcepath", appDir,
"-bootclasspath", clsPath,
"-d", classes,
)
javac.Args = append(javac.Args, javaFiles...)
if _, err := runCmd(javac); err != nil {
return err
}
jarFile := filepath.Join(tmpDir, "classes.jar")
if err := writeJar(jarFile, classes); err != nil {
return err
}
aarw.Add("classes.jar", jarFile)
}
return aarw.Close()
}
func newZipWriter(w io.Writer) *zipWriter {
return &zipWriter{
w: zip.NewWriter(w),
}
}
func (z *zipWriter) Close() error {
err := z.w.Close()
if z.err == nil {
z.err = err
}
return z.err
}
func (z *zipWriter) Create(name string) io.Writer {
if z.err != nil {
return ioutil.Discard
}
w, err := z.w.Create(name)
if err != nil {
z.err = err
return ioutil.Discard
}
return &errWriter{w: w, err: &z.err}
}
func (z *zipWriter) Add(name, file string) {
if z.err != nil {
return
}
w := z.Create(name)
f, err := os.Open(file)
if err != nil {
z.err = err
return
}
defer f.Close()
if _, err := io.Copy(w, f); err != nil {
z.err = err
return
}
}
func (w *errWriter) Write(p []byte) (n int, err error) {
if err := *w.err; err != nil {
return 0, err
}
n, err = w.w.Write(p)
*w.err = err
return
}
func errorf(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, format+"\n", args...)
os.Exit(2)
}
func runCmd(cmd *exec.Cmd) ([]byte, error) {
if *verbose {
fmt.Printf("%s\n", strings.Join(cmd.Args, " "))
}
out, err := cmd.Output()
if err == nil {
return out, nil
}
if err, ok := err.(*exec.ExitError); ok {
return nil, fmt.Errorf("%s failed: %s%s", strings.Join(cmd.Args, " "), out, err.Stderr)
}
return nil, err
}
func copyFile(dst, src string) (err error) {
r, err := os.Open(src)
if err != nil {
return err
}
defer r.Close()
w, err := os.Create(dst)
if err != nil {
return err
}
defer func() {
if cerr := w.Close(); err == nil {
err = cerr
}
}()
_, err = io.Copy(w, r)
return err
}
func appDir() (string, error) {
cmd := exec.Command("go", "list", "-f", "{{.Dir}}", "gioui.org/ui/app")
cmd.Env = append(
os.Environ(),
"GOOS=android",
)
out, err := runCmd(cmd)
if err != nil {
return "", err
}
appDir := string(bytes.TrimSpace(out))
return appDir, nil
}
func writeJar(jarFile, dir string) (err error) {
jar, err := os.Create(jarFile)
if err != nil {
return err
}
defer func() {
if cerr := jar.Close(); err == nil {
err = cerr
}
}()
jarw := newZipWriter(jar)
const manifestHeader = `Manifest-Version: 1.0
Created-By: 1.0 (Go)
`
jarw.Create("META-INF/MANIFEST.MF").Write([]byte(manifestHeader))
err = filepath.Walk(dir, func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
if f.IsDir() {
return nil
}
if filepath.Ext(path) == ".class" {
rel := filepath.ToSlash(path[len(dir)+1:])
jarw.Add(rel, path)
}
return nil
})
if err != nil {
return err
}
return jarw.Close()
}
type arch struct {
iosArch string
jniArch string
clang string
// TODO: Remove when https://github.com/android-ndk/ndk/issues/920
// is solved and released.
r19bWindowsClangArgs string
}
var allArchs = map[string]arch{
"arm": arch{
iosArch: "armv7",
jniArch: "armeabi-v7a",
clang: "armv7a-linux-androideabi16-clang",
r19bWindowsClangArgs: "--target=armv7a-linux-androideabi16 -fno-addrsig",
},
"arm64": arch{
iosArch: "arm64",
jniArch: "arm64-v8a",
clang: "aarch64-linux-android21-clang",
r19bWindowsClangArgs: "--target=aarch64-linux-androideabi21 -fno-addrsig",
},
"386": arch{
iosArch: "i386",
jniArch: "x86",
clang: "i686-linux-android16-clang",
r19bWindowsClangArgs: "--target=i686-linux-androideabi16 -fno-addrsig",
},
"amd64": arch{
iosArch: "x86_64",
jniArch: "x86_64",
clang: "x86_64-linux-android21-clang",
r19bWindowsClangArgs: "--target=x86_64-linux-androideabi21 -fno-addrsig",
},
}
func archNDK() string {
if runtime.GOOS == "windows" && runtime.GOARCH == "386" {
return "windows"
} else {
var arch string
switch runtime.GOARCH {
case "386":
arch = "x86"
case "amd64":
arch = "x86_64"
default:
panic("unsupported GOARCH: " + runtime.GOARCH)
}
return runtime.GOOS + "-" + arch
}
}
func latestPlatform(sdk string) (string, error) {
allPlats, err := filepath.Glob(filepath.Join(sdk, "platforms", "android-*"))
if err != nil {
return "", err
}
var bestVer int
var bestPlat string
for _, platform := range allPlats {
_, name := filepath.Split(platform)
// The glob above guarantees the "android-" prefix.
verStr := name[len("android-"):]
ver, err := strconv.Atoi(verStr)
if err != nil {
continue
}
if ver < bestVer {
continue
}
bestVer = ver
bestPlat = platform
}
if bestPlat == "" {
return "", fmt.Errorf("no platforms found in %q", sdk)
}
return bestPlat, nil
}
+5
View File
@@ -0,0 +1,5 @@
module gioui.org/cmd
go 1.12
require golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4
+2
View File
@@ -0,0 +1,2 @@
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+53
View File
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: Unlicense OR MIT
package org.gioui;
import android.app.Activity;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
public class GioActivity extends Activity {
private GioView view;
static {
System.loadLibrary("gio");
}
@Override public void onCreate(Bundle state) {
super.onCreate(state);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Window w = getWindow();
w.setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
}
this.view = new GioView(this);
setContentView(view);
}
@Override public void onDestroy() {
view.destroy();
super.onDestroy();
}
@Override public void onStart() {
super.onStart();
view.start();
}
@Override public void onStop() {
view.stop();
super.onStop();
}
@Override public void onConfigurationChanged(Configuration c) {
super.onConfigurationChanged(c);
view.configurationChanged();
}
@Override public void onLowMemory() {
super.onLowMemory();
view.lowMemory();
}
}
+183
View File
@@ -0,0 +1,183 @@
// SPDX-License-Identifier: Unlicense OR MIT
package org.gioui;
import android.content.Context;
import android.os.Handler;
import android.util.AttributeSet;
import android.text.Editable;
import android.view.Choreographer;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.Surface;
import android.view.SurfaceView;
import android.view.SurfaceHolder;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.EditorInfo;
public class GioView extends SurfaceView implements Choreographer.FrameCallback {
private final SurfaceHolder.Callback callbacks;
private final InputMethodManager imm;
private final Handler handler;
private long nhandle;
public GioView(Context context) {
this(context, null);
}
public GioView(Context context, AttributeSet attrs) {
super(context, attrs);
handler = new Handler();
imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
setFocusable(true);
setFocusableInTouchMode(true);
callbacks = new SurfaceHolder.Callback() {
@Override public void surfaceCreated(SurfaceHolder holder) {
// Ignore; surfaceChanged is guaranteed to be called immediately after this.
}
@Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
onSurfaceChanged(nhandle, getHolder().getSurface());
}
@Override public void surfaceDestroyed(SurfaceHolder holder) {
onSurfaceDestroyed(nhandle);
}
};
getHolder().addCallback(callbacks);
nhandle = onCreateView(this);
}
@Override public boolean onKeyDown(int keyCode, KeyEvent event) {
onKeyEvent(nhandle, keyCode, event.getUnicodeChar(), event.getEventTime());
return false;
}
@Override public boolean onTouchEvent(MotionEvent event) {
for (int j = 0; j < event.getHistorySize(); j++) {
long time = event.getHistoricalEventTime(j);
for (int i = 0; i < event.getPointerCount(); i++) {
onTouchEvent(
nhandle,
event.ACTION_MOVE,
event.getPointerId(i),
event.getToolType(i),
event.getHistoricalX(i, j),
event.getHistoricalY(i, j),
time);
}
}
int act = event.getActionMasked();
int idx = event.getActionIndex();
for (int i = 0; i < event.getPointerCount(); i++) {
int pact = event.ACTION_MOVE;
if (i == idx) {
pact = act;
}
onTouchEvent(
nhandle,
act,
event.getPointerId(i),
event.getToolType(i),
event.getX(i),
event.getY(i),
event.getEventTime());
}
return true;
}
@Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
return new InputConnection(this);
}
void showTextInput() {
post(new Runnable() {
@Override public void run() {
GioView.this.requestFocus();
imm.showSoftInput(GioView.this, 0);
}
});
}
void hideTextInput() {
post(new Runnable() {
@Override public void run() {
imm.hideSoftInputFromWindow(getWindowToken(), 0);
}
});
}
void postFrameCallbackOnMainThread() {
handler.post(new Runnable() {
@Override public void run() {
postFrameCallback();
}
});
}
void postFrameCallback() {
Choreographer.getInstance().removeFrameCallback(this);
Choreographer.getInstance().postFrameCallback(this);
}
@Override public void doFrame(long nanos) {
onFrameCallback(nhandle, nanos);
}
int getDensity() {
return getResources().getDisplayMetrics().densityDpi;
}
float getFontScale() {
return getResources().getConfiguration().fontScale;
}
void start() {
onStartView(nhandle);
}
void stop() {
onStopView(nhandle);
}
void destroy() {
getHolder().removeCallback(callbacks);
onDestroyView(nhandle);
nhandle = 0;
}
void configurationChanged() {
onConfigurationChanged(nhandle);
}
void lowMemory() {
onLowMemory();
}
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 onLowMemory();
static private native void onTouchEvent(long handle, int action, int pointerID, int tool, float x, float y, long time);
static private native void onKeyEvent(long handle, int code, int character, long time);
static private native void onFrameCallback(long handle, long nanos);
private static class InputConnection extends BaseInputConnection {
private final Editable editable;
InputConnection(View view) {
super(view, true);
editable = Editable.Factory.getInstance().newEditable("");
}
@Override public Editable getEditable() {
return editable;
}
}
}
+94
View File
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
import (
"image"
"os"
"strings"
"gioui.org/ui"
)
type Draw struct {
Config *ui.Config
Size image.Point
// Whether this draw is system generated
// and needs to a complete frame before
// proceeding.
sync bool
}
type ChangeStage struct {
Stage Stage
}
type Stage uint8
type Event interface {
ImplementsEvent()
}
type Input interface {
ImplementsInput()
}
const (
StageDead Stage = iota
StageInvisible
StageVisible
)
const (
inchPrDp = 1.0 / 160
mmPrDp = 25.4 / 160
// monitorScale is the extra scale applied to
// monitor outputs to compensate for the extra
// viewing distance compared to phone and tables.
monitorScale = 1.50
// minDensity is the minimum pixels per dp to
// ensure font and ui legibility on low-dpi
// screens.
minDensity = 1.25
)
// extraArgs contains extra arguments to append to
// os.Args. The arguments are separated with |.
// Useful for running programs on mobiles where the
// command line is not available.
// Set it with the go tool linker flag -X.
var extraArgs string
var windows = make(chan *Window)
func CreateWindow(opts WindowOptions) error {
if opts.Width.V <= 0 || opts.Height.V <= 0 {
panic("window width and height must be larger than 0")
}
return createWindow(opts)
}
func Windows() <-chan *Window {
return windows
}
func (l Stage) String() string {
switch l {
case StageDead:
return "StageDead"
case StageInvisible:
return "StageInvisible"
case StageVisible:
return "StageVisible"
default:
panic("unexpected Stage value")
}
}
func (_ Draw) ImplementsEvent() {}
func (_ ChangeStage) ImplementsEvent() {}
func init() {
args := strings.Split(extraArgs, "|")
os.Args = append(os.Args, args...)
}
+254
View File
@@ -0,0 +1,254 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build linux windows
package app
import (
"errors"
"fmt"
"runtime"
"strings"
"gioui.org/ui/app/internal/gl"
)
type context struct {
c *gl.Functions
driver *window
eglCtx *eglContext
nwindow _EGLNativeWindowType
eglWin *eglWindow
eglSurf _EGLSurface
width, height int
// For sRGB emulation.
srgbFBO *gl.SRGBFBO
}
type eglContext struct {
disp _EGLDisplay
config _EGLConfig
ctx _EGLContext
visualID int
srgb bool
}
const (
_EGL_ALPHA_SIZE = 0x3021
_EGL_BLUE_SIZE = 0x3022
_EGL_CONFIG_CAVEAT = 0x3027
_EGL_CONTEXT_CLIENT_VERSION = 0x3098
_EGL_DEPTH_SIZE = 0x3025
_EGL_GL_COLORSPACE_KHR = 0x309d
_EGL_GL_COLORSPACE_SRGB_KHR = 0x3089
_EGL_GREEN_SIZE = 0x3023
_EGL_EXTENSIONS = 0x3055
_EGL_NATIVE_VISUAL_ID = 0x302e
_EGL_NONE = 0x3038
_EGL_OPENGL_ES2_BIT = 0x4
_EGL_RED_SIZE = 0x3024
_EGL_RENDERABLE_TYPE = 0x3040
_EGL_SURFACE_TYPE = 0x3033
_EGL_WINDOW_BIT = 0x4
)
func (c *context) Release() {
if c.srgbFBO != nil {
c.srgbFBO.Release()
}
if c.eglSurf != nilEGLSurface {
eglMakeCurrent(c.eglCtx.disp, nilEGLSurface, nilEGLSurface, nilEGLContext)
eglDestroySurface(c.eglCtx.disp, c.eglSurf)
c.eglSurf = nilEGLSurface
}
if c.eglWin != nil {
c.eglWin.destroy()
c.eglWin = nil
}
if c.eglCtx != nil {
eglDestroyContext(c.eglCtx.disp, c.eglCtx.ctx)
eglTerminate(c.eglCtx.disp)
eglReleaseThread()
c.eglCtx = nil
}
c.driver = nil
}
func (c *context) Present() error {
if c.eglWin == nil {
panic("context is not active")
}
if c.srgbFBO != nil {
c.srgbFBO.Blit()
}
if !eglSwapBuffers(c.eglCtx.disp, c.eglSurf) {
return fmt.Errorf("eglSwapBuffers failed (%x%x)", eglGetError())
}
if c.srgbFBO != nil {
c.srgbFBO.AfterPresent()
}
return nil
}
func newContext(w *window) (*context, error) {
eglCtx, err := createContext(_EGLNativeDisplayType(w.display()))
if err != nil {
return nil, err
}
c := &context{
driver: w,
eglCtx: eglCtx,
}
return c, nil
}
func (c *context) Functions() *gl.Functions {
return c.c
}
func (c *context) MakeCurrent() error {
w, width, height := c.driver.nativeWindow(int(c.eglCtx.visualID))
win := _EGLNativeWindowType(w)
if c.nwindow == win && width == c.width && height == c.height {
return nil
}
if win == nilEGLNativeWindowType {
if c.srgbFBO != nil {
c.srgbFBO.Release()
c.srgbFBO = nil
}
}
if c.eglSurf != nilEGLSurface {
// Make sure any in-flight GL commands are complete.
c.c.Finish()
eglMakeCurrent(c.eglCtx.disp, nilEGLSurface, nilEGLSurface, nilEGLContext)
eglDestroySurface(c.eglCtx.disp, c.eglSurf)
c.eglSurf = nilEGLSurface
}
c.width, c.height = width, height
c.nwindow = win
if c.nwindow == nilEGLNativeWindowType {
if c.eglWin != nil {
c.eglWin.destroy()
c.eglWin = nil
}
return nil
}
if c.eglWin == nil {
var err error
c.eglWin, err = newEGLWindow(win, width, height)
if err != nil {
return err
}
} else {
c.eglWin.resize(width, height)
}
eglSurf, err := createSurfaceAndMakeCurrent(c.eglCtx, c.eglWin.window())
c.eglSurf = eglSurf
if err != nil {
c.eglWin.destroy()
c.eglWin = nil
c.nwindow = nilEGLNativeWindowType
return err
}
if c.eglCtx.srgb {
return nil
}
if c.srgbFBO == nil {
var err error
c.srgbFBO, err = gl.NewSRGBFBO(c.c)
if err != nil {
c.Release()
return err
}
}
if err := c.srgbFBO.Refresh(c.width, c.height); err != nil {
c.Release()
return err
}
return nil
}
func createContext(disp _EGLNativeDisplayType) (*eglContext, error) {
eglDisp := eglGetDisplay(disp)
if eglDisp == 0 {
return nil, fmt.Errorf("eglGetDisplay(_EGL_DEFAULT_DISPLAY) failed: 0x%x", eglGetError())
}
_, minor, ret := eglInitialize(eglDisp)
if !ret {
return nil, fmt.Errorf("eglInitialize failed: 0x%x", eglGetError())
}
// sRGB framebuffer support on EGL 1.5 or if EGL_KHR_gl_colorspace is supported.
exts := eglQueryString(eglDisp, _EGL_EXTENSIONS)
srgb := minor >= 5 || strings.Contains(exts, "EGL_KHR_gl_colorspace")
attribs := []_EGLint{
_EGL_RENDERABLE_TYPE, _EGL_OPENGL_ES2_BIT,
_EGL_SURFACE_TYPE, _EGL_WINDOW_BIT,
_EGL_BLUE_SIZE, 8,
_EGL_GREEN_SIZE, 8,
_EGL_RED_SIZE, 8,
_EGL_CONFIG_CAVEAT, _EGL_NONE,
}
if srgb {
if runtime.GOOS == "linux" {
// Some Mesa drivers crash if an sRGB framebuffer is requested without alpha.
// https://bugs.freedesktop.org/show_bug.cgi?id=107782.
attribs = append(attribs, _EGL_ALPHA_SIZE, 1)
}
// Only request a depth buffer if we're going to render directly to the framebuffer.
attribs = append(attribs, _EGL_DEPTH_SIZE, 16)
}
attribs = append(attribs, _EGL_NONE)
eglCfg, ret := eglChooseConfig(eglDisp, attribs)
if !ret {
return nil, fmt.Errorf("eglChooseConfig failed: 0x%x", eglGetError())
}
if eglCfg == nilEGLConfig {
return nil, errors.New("eglChooseConfig returned 0 configs")
}
var eglCtx _EGLContext
ctxAttribs := []_EGLint{
_EGL_CONTEXT_CLIENT_VERSION, 3,
_EGL_NONE,
}
eglCtx = eglCreateContext(eglDisp, eglCfg, nilEGLContext, ctxAttribs)
if eglCtx == nilEGLContext {
return nil, fmt.Errorf("eglCreateContext failed: 0x%x", eglGetError())
}
visID, ret := eglGetConfigAttrib(eglDisp, eglCfg, _EGL_NATIVE_VISUAL_ID)
if !ret {
return nil, errors.New("newContext: eglGetConfigAttrib for _EGL_NATIVE_VISUAL_ID failed")
}
return &eglContext{
disp: eglDisp,
config: _EGLConfig(eglCfg),
ctx: _EGLContext(eglCtx),
visualID: int(visID),
srgb: srgb,
}, nil
}
func createSurfaceAndMakeCurrent(eglCtx *eglContext, win _EGLNativeWindowType) (_EGLSurface, error) {
var surfAttribs []_EGLint
if eglCtx.srgb {
surfAttribs = append(surfAttribs, _EGL_GL_COLORSPACE_KHR, _EGL_GL_COLORSPACE_SRGB_KHR)
}
surfAttribs = append(surfAttribs, _EGL_NONE)
eglSurf := eglCreateWindowSurface(eglCtx.disp, eglCtx.config, win, surfAttribs)
if eglSurf == nilEGLSurface {
return nilEGLSurface, fmt.Errorf("newContext: eglCreateWindowSurface failed 0x%x", eglGetError())
}
if !eglMakeCurrent(eglCtx.disp, eglSurf, eglSurf, eglCtx.ctx) {
eglDestroySurface(eglCtx.disp, eglSurf)
return nilEGLSurface, fmt.Errorf("eglMakeCurrent error 0x%x", eglGetError())
}
// eglSwapInterval 1 leads to erratic frame rates and unnecessary blocking.
// We rely on platform specific frame rate limiting instead, except on Windows
// where eglSwapInterval is all there is.
if runtime.GOOS != "windows" {
eglSwapInterval(eglCtx.disp, 0)
} else {
eglSwapInterval(eglCtx.disp, 1)
}
return eglSurf, nil
}
+22
View File
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
/*
#include <EGL/egl.h>
*/
import "C"
type (
_EGLNativeDisplayType = C.EGLNativeDisplayType
_EGLNativeWindowType = C.EGLNativeWindowType
)
func eglGetDisplay(disp _EGLNativeDisplayType) _EGLDisplay {
return C.eglGetDisplay(disp)
}
func eglCreateWindowSurface(disp _EGLDisplay, conf _EGLConfig, win _EGLNativeWindowType, attribs []_EGLint) _EGLSurface {
eglSurf := C.eglCreateWindowSurface(disp, conf, win, &attribs[0])
return eglSurf
}
+90
View File
@@ -0,0 +1,90 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
/*
#cgo LDFLAGS: -lEGL
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <GLES3/gl3.h>
*/
import "C"
type (
_EGLint = C.EGLint
_EGLDisplay = C.EGLDisplay
_EGLConfig = C.EGLConfig
_EGLContext = C.EGLContext
_EGLSurface = C.EGLSurface
)
var (
nilEGLSurface _EGLSurface
nilEGLContext _EGLContext
nilEGLConfig _EGLConfig
nilEGLNativeWindowType _EGLNativeWindowType
)
func eglChooseConfig(disp _EGLDisplay, attribs []_EGLint) (_EGLConfig, bool) {
var cfg C.EGLConfig
var ncfg C.EGLint
if C.eglChooseConfig(disp, &attribs[0], &cfg, 1, &ncfg) != C.EGL_TRUE {
return nil, false
}
return _EGLConfig(cfg), true
}
func eglCreateContext(disp _EGLDisplay, cfg _EGLConfig, shareCtx _EGLContext, attribs []_EGLint) _EGLContext {
ctx := C.eglCreateContext(disp, cfg, shareCtx, &attribs[0])
return _EGLContext(ctx)
}
func eglDestroySurface(disp _EGLDisplay, surf _EGLSurface) bool {
return C.eglDestroySurface(disp, surf) == C.EGL_TRUE
}
func eglDestroyContext(disp _EGLDisplay, ctx _EGLContext) bool {
return C.eglDestroyContext(disp, ctx) == C.EGL_TRUE
}
func eglGetConfigAttrib(disp _EGLDisplay, cfg _EGLConfig, attr _EGLint) (_EGLint, bool) {
var val _EGLint
ret := C.eglGetConfigAttrib(disp, cfg, attr, &val)
return val, ret == C.EGL_TRUE
}
func eglGetError() _EGLint {
return C.eglGetError()
}
func eglInitialize(disp _EGLDisplay) (_EGLint, _EGLint, bool) {
var maj, min _EGLint
ret := C.eglInitialize(disp, &maj, &min)
return maj, min, ret == C.EGL_TRUE
}
func eglMakeCurrent(disp _EGLDisplay, draw, read _EGLSurface, ctx _EGLContext) bool {
return C.eglMakeCurrent(disp, draw, read, ctx) == C.EGL_TRUE
}
func eglReleaseThread() bool {
return C.eglReleaseThread() == C.EGL_TRUE
}
func eglSwapBuffers(disp _EGLDisplay, surf _EGLSurface) bool {
return C.eglSwapBuffers(disp, surf) == C.EGL_TRUE
}
func eglSwapInterval(disp _EGLDisplay, interval _EGLint) bool {
return C.eglSwapInterval(disp, interval) == C.EGL_TRUE
}
func eglTerminate(disp _EGLDisplay) bool {
return C.eglTerminate(disp) == C.EGL_TRUE
}
func eglQueryString(disp _EGLDisplay, name _EGLint) string {
return C.GoString(C.eglQueryString(disp, name))
}
+58
View File
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build linux,!android
package app
import (
"errors"
"unsafe"
)
/*
#cgo LDFLAGS: -lwayland-egl
#include <wayland-client.h>
#include <wayland-egl.h>
#include <EGL/egl.h>
*/
import "C"
type (
_EGLNativeDisplayType = C.EGLNativeDisplayType
_EGLNativeWindowType = C.EGLNativeWindowType
)
type eglWindow struct {
w *C.struct_wl_egl_window
}
func newEGLWindow(w _EGLNativeWindowType, width, height int) (*eglWindow, error) {
surf := (*C.struct_wl_surface)(unsafe.Pointer(w))
win := C.wl_egl_window_create(surf, C.int(width), C.int(height))
if win == nil {
return nil, errors.New("wl_egl_create_window failed")
}
return &eglWindow{win}, nil
}
func (w *eglWindow) window() _EGLNativeWindowType {
return w.w
}
func (w *eglWindow) resize(width, height int) {
C.wl_egl_window_resize(w.w, C.int(width), C.int(height), 0, 0)
}
func (w *eglWindow) destroy() {
C.wl_egl_window_destroy(w.w)
}
func eglGetDisplay(disp _EGLNativeDisplayType) _EGLDisplay {
return C.eglGetDisplay(disp)
}
func eglCreateWindowSurface(disp _EGLDisplay, conf _EGLConfig, win _EGLNativeWindowType, attribs []_EGLint) _EGLSurface {
eglSurf := C.eglCreateWindowSurface(disp, conf, win, &attribs[0])
return eglSurf
}
+20
View File
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build android windows
package app
type eglWindow struct {
w _EGLNativeWindowType
}
func newEGLWindow(w _EGLNativeWindowType, width, height int) (*eglWindow, error) {
return &eglWindow{w}, nil
}
func (w *eglWindow) window() _EGLNativeWindowType {
return w.w
}
func (w *eglWindow) resize(width, height int) {}
func (w *eglWindow) destroy() {}
+175
View File
@@ -0,0 +1,175 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
import (
"os"
"reflect"
"unsafe"
syscall "golang.org/x/sys/windows"
"gioui.org/ui/app/internal/gl"
)
type (
_EGLint int32
_EGLDisplay uintptr
_EGLConfig uintptr
_EGLContext uintptr
_EGLSurface uintptr
_EGLNativeDisplayType uintptr
_EGLNativeWindowType uintptr
)
var (
libEGL = syscall.NewLazyDLL("libEGL.dll")
_eglChooseConfig = libEGL.NewProc("eglChooseConfig")
_eglCreateContext = libEGL.NewProc("eglCreateContext")
_eglCreateWindowSurface = libEGL.NewProc("eglCreateWindowSurface")
_eglDestroyContext = libEGL.NewProc("eglDestroyContext")
_eglDestroySurface = libEGL.NewProc("eglDestroySurface")
_eglGetConfigAttrib = libEGL.NewProc("eglGetConfigAttrib")
_eglGetDisplay = libEGL.NewProc("eglGetDisplay")
_eglGetError = libEGL.NewProc("eglGetError")
_eglInitialize = libEGL.NewProc("eglInitialize")
_eglMakeCurrent = libEGL.NewProc("eglMakeCurrent")
_eglReleaseThread = libEGL.NewProc("eglReleaseThread")
_eglSwapInterval = libEGL.NewProc("eglSwapInterval")
_eglSwapBuffers = libEGL.NewProc("eglSwapBuffers")
_eglTerminate = libEGL.NewProc("eglTerminate")
_eglQueryString = libEGL.NewProc("eglQueryString")
)
const (
nilEGLSurface _EGLSurface = 0
nilEGLContext _EGLContext = 0
nilEGLConfig _EGLConfig = 0
nilEGLNativeWindowType _EGLNativeWindowType = 0
)
func init() {
mustLoadDLL(libEGL, "libEGL.dll")
mustLoadDLL(gl.LibGLESv2, "libGLESv2.dll")
// d3dcompiler_47.dll is needed internally for shader compilation to function.
mustLoadDLL(syscall.NewLazyDLL("d3dcompiler_47.dll"), "d3dcompiler_47.dll")
}
func mustLoadDLL(dll *syscall.LazyDLL, name string) {
loadErr := dll.Load()
if loadErr == nil {
return
}
user32 := syscall.NewLazySystemDLL("user32.dll")
messageBox := user32.NewProc("MessageBoxW")
if err := messageBox.Find(); err != nil {
panic(loadErr)
}
pmsg := syscall.StringToUTF16Ptr("Failed to load " + name)
ptitle := syscall.StringToUTF16Ptr("Error")
const MB_ICONERROR = 0x00000010
const MB_SYSTEMMODAL = 0x00001000
messageBox.Call(0 /* HWND */, uintptr(unsafe.Pointer(pmsg)), uintptr(unsafe.Pointer(ptitle)), MB_ICONERROR|MB_SYSTEMMODAL)
os.Exit(1)
}
func eglChooseConfig(disp _EGLDisplay, attribs []_EGLint) (_EGLConfig, bool) {
var cfg _EGLConfig
var ncfg _EGLint
r, _, _ := _eglChooseConfig.Call(uintptr(disp), uintptr(unsafe.Pointer(&attribs[0])), uintptr(unsafe.Pointer(&cfg)), 1, uintptr(unsafe.Pointer(&ncfg)))
return cfg, r != 0 && ncfg > 0
}
func eglCreateContext(disp _EGLDisplay, cfg _EGLConfig, shareCtx _EGLContext, attribs []_EGLint) _EGLContext {
c, _, _ := _eglCreateContext.Call(uintptr(disp), uintptr(cfg), uintptr(shareCtx), uintptr(unsafe.Pointer(&attribs[0])))
return _EGLContext(c)
}
func eglCreateWindowSurface(disp _EGLDisplay, cfg _EGLConfig, win _EGLNativeWindowType, attribs []_EGLint) _EGLSurface {
s, _, _ := _eglCreateWindowSurface.Call(uintptr(disp), uintptr(cfg), uintptr(win), uintptr(unsafe.Pointer(&attribs[0])))
return _EGLSurface(s)
}
func eglDestroySurface(disp _EGLDisplay, surf _EGLSurface) bool {
r, _, _ := _eglDestroySurface.Call(uintptr(disp), uintptr(surf))
return r != 0
}
func eglDestroyContext(disp _EGLDisplay, ctx _EGLContext) bool {
r, _, _ := _eglDestroyContext.Call(uintptr(disp), uintptr(ctx))
return r != 0
}
func eglGetConfigAttrib(disp _EGLDisplay, cfg _EGLConfig, attr _EGLint) (_EGLint, bool) {
var val uintptr
r, _, _ := _eglGetConfigAttrib.Call(uintptr(disp), uintptr(cfg), uintptr(attr), uintptr(unsafe.Pointer(&val)))
return _EGLint(val), r != 0
}
func eglGetDisplay(disp _EGLNativeDisplayType) _EGLDisplay {
d, _, _ := _eglGetDisplay.Call(uintptr(disp))
return _EGLDisplay(d)
}
func eglGetError() _EGLint {
e, _, _ := _eglGetError.Call()
return _EGLint(e)
}
func eglInitialize(disp _EGLDisplay) (_EGLint, _EGLint, bool) {
var maj, min uintptr
r, _, _ := _eglInitialize.Call(uintptr(disp), uintptr(unsafe.Pointer(&maj)), uintptr(unsafe.Pointer(&min)))
return _EGLint(maj), _EGLint(min), r != 0
}
func eglMakeCurrent(disp _EGLDisplay, draw, read _EGLSurface, ctx _EGLContext) bool {
r, _, _ := _eglMakeCurrent.Call(uintptr(disp), uintptr(draw), uintptr(read), uintptr(ctx))
return r != 0
}
func eglReleaseThread() bool {
r, _, _ := _eglReleaseThread.Call()
return r != 0
}
func eglSwapInterval(disp _EGLDisplay, interval _EGLint) bool {
r, _, _ := _eglSwapInterval.Call(uintptr(disp), uintptr(interval))
return r != 0
}
func eglSwapBuffers(disp _EGLDisplay, surf _EGLSurface) bool {
r, _, _ := _eglSwapBuffers.Call(uintptr(disp), uintptr(surf))
return r != 0
}
func eglTerminate(disp _EGLDisplay) bool {
r, _, _ := _eglTerminate.Call(uintptr(disp))
return r != 0
}
func eglQueryString(disp _EGLDisplay, name _EGLint) string {
r, _, _ := _eglQueryString.Call(uintptr(disp), uintptr(name))
return goString(r)
}
func goString(s uintptr) string {
if s == 0 {
return ""
}
sh := reflect.SliceHeader{
Data: s,
Len: 1 << 30,
Cap: 1 << 30,
}
sl := *(*[]byte)(unsafe.Pointer(&sh))
var v string
for i, c := range sl {
if c == 0 {
if i > 0 {
v = string(sl[:i-1])
}
break
}
}
return v
}
+8
View File
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: Unlicense OR MIT
@import UIKit;
@interface GioAppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
+119
View File
@@ -0,0 +1,119 @@
// SPDX-License-Identifier: Unlicense OR MIT
//+build darwin,ios
package app
/*
#cgo CFLAGS: -fmodules -fobjc-arc -x objective-c
#include <CoreFoundation/CoreFoundation.h>
#include <OpenGLES/ES2/gl.h>
#include <OpenGLES/ES2/glext.h>
#include "gl_ios.h"
*/
import "C"
import (
"errors"
"fmt"
"gioui.org/ui/app/internal/gl"
)
type context struct {
owner *window
c *gl.Functions
ctx C.CFTypeRef
layer C.CFTypeRef
init bool
frameBuffer gl.Framebuffer
colorBuffer, depthBuffer gl.Renderbuffer
}
func init() {
layerFactory = func() uintptr {
return uintptr(C.gio_createGLLayer())
}
}
func newContext(w *window) (*context, error) {
ctx := C.gio_createContext()
if ctx == 0 {
return nil, fmt.Errorf("failed to create EAGLContext")
}
c := &context{
ctx: ctx,
owner: w,
layer: C.CFTypeRef(w.contextLayer()),
}
return c, nil
}
func (c *context) Functions() *gl.Functions {
return c.c
}
func (c *context) Release() {
if c.ctx == 0 {
return
}
C.gio_renderbufferStorage(c.ctx, 0, C.GLenum(gl.RENDERBUFFER))
c.c.DeleteFramebuffer(c.frameBuffer)
c.c.DeleteRenderbuffer(c.colorBuffer)
c.c.DeleteRenderbuffer(c.depthBuffer)
C.gio_makeCurrent(0)
C.CFRelease(c.ctx)
c.ctx = 0
}
func (c *context) Present() error {
if c.layer == 0 {
panic("context is not active")
}
// Discard depth buffer as recommended in
// https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/WorkingwithEAGLContexts/WorkingwithEAGLContexts.html
c.c.BindFramebuffer(gl.FRAMEBUFFER, c.frameBuffer)
c.c.InvalidateFramebuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT)
c.c.BindRenderbuffer(gl.RENDERBUFFER, c.colorBuffer)
if C.gio_presentRenderbuffer(c.ctx, C.GLenum(gl.RENDERBUFFER)) == 0 {
return errors.New("presentRenderBuffer failed")
}
return nil
}
func (c *context) MakeCurrent() error {
if C.gio_makeCurrent(c.ctx) == 0 {
C.CFRelease(c.ctx)
c.ctx = 0
return errors.New("[EAGLContext setCurrentContext] failed")
}
if !c.init {
c.init = true
c.frameBuffer = c.c.CreateFramebuffer()
c.colorBuffer = c.c.CreateRenderbuffer()
c.depthBuffer = c.c.CreateRenderbuffer()
}
if !c.owner.isVisible() {
// Make sure any in-flight GL commands are complete.
c.c.Finish()
return nil
}
currentRB := gl.Renderbuffer(c.c.GetInteger(gl.RENDERBUFFER_BINDING))
c.c.BindRenderbuffer(gl.RENDERBUFFER, c.colorBuffer)
if C.gio_renderbufferStorage(c.ctx, c.layer, C.GLenum(gl.RENDERBUFFER)) == 0 {
return errors.New("renderbufferStorage failed")
}
w := c.c.GetRenderbufferParameteri(gl.RENDERBUFFER, gl.RENDERBUFFER_WIDTH)
h := c.c.GetRenderbufferParameteri(gl.RENDERBUFFER, gl.RENDERBUFFER_HEIGHT)
c.c.BindRenderbuffer(gl.RENDERBUFFER, c.depthBuffer)
c.c.RenderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, w, h)
c.c.BindRenderbuffer(gl.RENDERBUFFER, currentRB)
c.c.BindFramebuffer(gl.FRAMEBUFFER, c.frameBuffer)
c.c.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, c.colorBuffer)
c.c.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, c.depthBuffer)
if st := c.c.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
return fmt.Errorf("framebuffer incomplete, status: %#x\n", st)
}
return nil
}
+7
View File
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: Unlicense OR MIT
__attribute__ ((visibility ("hidden"))) int gio_renderbufferStorage(CFTypeRef ctx, CFTypeRef layer, GLenum buffer);
__attribute__ ((visibility ("hidden"))) int gio_presentRenderbuffer(CFTypeRef ctx, GLenum buffer);
__attribute__ ((visibility ("hidden"))) int gio_makeCurrent(CFTypeRef ctx);
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createContext(void);
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createGLLayer();
+44
View File
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: Unlicense OR MIT
//+build darwin,ios
@import UIKit;
@import OpenGLES;
#include "gl_ios.h"
int gio_renderbufferStorage(CFTypeRef ctxRef, CFTypeRef layerRef, GLenum buffer) {
EAGLContext *ctx = (__bridge EAGLContext *)ctxRef;
CAEAGLLayer *layer = (__bridge CAEAGLLayer *)layerRef;
return (int)[ctx renderbufferStorage:buffer fromDrawable:layer];
}
int gio_presentRenderbuffer(CFTypeRef ctxRef, GLenum buffer) {
EAGLContext *ctx = (__bridge EAGLContext *)ctxRef;
return (int)[ctx presentRenderbuffer:buffer];
}
int gio_makeCurrent(CFTypeRef ctxRef) {
EAGLContext *ctx = (__bridge EAGLContext *)ctxRef;
return (int)[EAGLContext setCurrentContext:ctx];
}
CFTypeRef gio_createContext(void) {
EAGLContext *ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
if (ctx == nil) {
return nil;
}
return CFBridgingRetain(ctx);
}
CFTypeRef gio_createGLLayer() {
CAEAGLLayer *layer = [[CAEAGLLayer layer] init];
if (layer == nil) {
return nil;
}
layer.drawableProperties = @{kEAGLDrawablePropertyColorFormat: kEAGLColorFormatSRGBA8};
//layer.presentsWithTransaction = YES;
layer.opaque = YES;
layer.anchorPoint = CGPointMake(0, 0);
return CFBridgingRetain(layer);
}
+59
View File
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build darwin,!ios
package app
import (
"gioui.org/ui/app/internal/gl"
)
/*
#cgo CFLAGS: -DGL_SILENCE_DEPRECATION -fmodules -fobjc-arc -x objective-c
#include <CoreFoundation/CoreFoundation.h>
#include <CoreGraphics/CoreGraphics.h>
#include <AppKit/AppKit.h>
#include <OpenGL/gl3.h>
#include "gl_macos.h"
*/
import "C"
type context struct {
c *gl.Functions
ctx C.CFTypeRef
}
func init() {
viewFactory = func() uintptr {
return uintptr(C.gio_createGLView())
}
}
func newContext(w *window) (*context, error) {
ctx := C.gio_contextForView(w.contextView())
c := &context{
ctx: ctx,
}
return c, nil
}
func (c *context) Functions() *gl.Functions {
return c.c
}
func (c *context) Release() {
C.gio_clearCurrentContext()
C.CFRelease(c.ctx)
c.ctx = 0
}
func (c *context) Present() error {
C.glFlush()
return nil
}
func (c *context) MakeCurrent() error {
C.gio_makeCurrentContext(c.ctx)
return nil
}
+7
View File
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: Unlicense OR MIT
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createGLView();
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_contextForView(CFTypeRef viewRef);
__attribute__ ((visibility ("hidden"))) void gio_makeCurrentContext(CFTypeRef ctx);
__attribute__ ((visibility ("hidden"))) void gio_flushContextBuffer(CFTypeRef ctx);
__attribute__ ((visibility ("hidden"))) void gio_clearCurrentContext();
+148
View File
@@ -0,0 +1,148 @@
// SPDX-License-Identifier: Unlicense OR MIT
//+build darwin,!ios
@import AppKit;
#include <CoreFoundation/CoreFoundation.h>
#include <OpenGL/OpenGL.h>
#include <OpenGL/gl3.h>
#include "os_macos.h"
#include "gl_macos.h"
#include "_cgo_export.h"
static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) {
NSPoint p = [view convertPoint:[event locationInWindow] fromView:nil];
CGFloat scale = view.layer.contentsScale;
if (!event.hasPreciseScrollingDeltas) {
// dx and dy are in rows and columns.
dx *= 10;
dy *= 10;
}
gio_onMouse((__bridge CFTypeRef)view, typ, p.x*scale, p.y*scale, dx*scale, dy*scale, [event timestamp]);
}
static CVReturn displayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) {
CFTypeRef view = (CFTypeRef *)displayLinkContext;
gio_onFrameCallback(view);
return COREVIDEO_TRUE;
}
@interface GioView : NSOpenGLView
@end
@implementation GioView {
CVDisplayLinkRef displayLink;
}
- (instancetype)initWithFrame:(NSRect)frameRect
pixelFormat:(NSOpenGLPixelFormat *)format {
self = [super initWithFrame:frameRect pixelFormat:format];
if (self) {
CVDisplayLinkCreateWithActiveCGDisplays(&displayLink);
CVDisplayLinkSetOutputCallback(displayLink, displayLinkCallback, (__bridge void*)self);
}
return self;
}
- (void)dealloc {
CVDisplayLinkRelease(displayLink);
}
- (void)setAnimating:(BOOL)anim {
if (anim) {
CVDisplayLinkStart(displayLink);
} else {
CVDisplayLinkStop(displayLink);
}
}
- (void)updateDisplay:(CGDirectDisplayID)dispID {
CVDisplayLinkSetCurrentCGDisplay(displayLink, dispID);
}
- (void)prepareOpenGL {
// Bind a default VBA to emulate OpenGL ES 2.
GLuint defVBA;
glGenVertexArrays(1, &defVBA);
glBindVertexArray(defVBA);
glEnable(GL_FRAMEBUFFER_SRGB);
}
- (BOOL)isFlipped {
return YES;
}
- (void)drawRect:(NSRect)r {
gio_onDraw((__bridge CFTypeRef)self);
}
- (void)mouseDown:(NSEvent *)event {
handleMouse(self, event, GIO_MOUSE_DOWN, 0, 0);
}
- (void)mouseUp:(NSEvent *)event {
handleMouse(self, event, GIO_MOUSE_UP, 0, 0);
}
- (void)mouseMoved:(NSEvent *)event {
handleMouse(self, event, GIO_MOUSE_MOVE, 0, 0);
}
- (void)mouseDragged:(NSEvent *)event {
handleMouse(self, event, GIO_MOUSE_MOVE, 0, 0);
}
- (void)scrollWheel:(NSEvent *)event {
CGFloat dx = -event.scrollingDeltaX;
CGFloat dy = -event.scrollingDeltaY;
handleMouse(self, event, GIO_MOUSE_MOVE, dx, dy);
}
- (void)keyDown:(NSEvent *)event {
NSString *keys = [event charactersIgnoringModifiers];
gio_onKeys((__bridge CFTypeRef)self, (char *)[keys UTF8String], [event timestamp], [event modifierFlags]);
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
}
- (void)insertText:(id)string {
const char *utf8 = [string UTF8String];
gio_onText((__bridge CFTypeRef)self, (char *)utf8);
}
@end
CFTypeRef gio_createGLView() {
@autoreleasepool {
NSOpenGLPixelFormatAttribute attr[] = {
NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
NSOpenGLPFAColorSize, 24,
NSOpenGLPFADepthSize, 16,
NSOpenGLPFAAccelerated,
// Opt-in to automatic GPU switching. CGL-only property.
kCGLPFASupportsAutomaticGraphicsSwitching,
NSOpenGLPFAAllowOfflineRenderers,
0
};
id pixFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr];
NSRect frame = NSMakeRect(0, 0, 0, 0);
GioView* view = [[GioView alloc] initWithFrame:frame pixelFormat:pixFormat];
[view setWantsBestResolutionOpenGLSurface:YES];
[view setWantsLayer:YES]; // The default in Mojave.
return CFBridgingRetain(view);
}
}
void gio_updateDisplayLink(CFTypeRef viewRef, CGDirectDisplayID dispID) {
GioView *view = (__bridge GioView *)viewRef;
[view updateDisplay:dispID];
}
void gio_setAnimating(CFTypeRef viewRef, BOOL anim) {
GioView *view = (__bridge GioView *)viewRef;
dispatch_async(dispatch_get_main_queue(), ^{
[view setAnimating:anim];
});
}
CFTypeRef gio_contextForView(CFTypeRef viewRef) {
NSOpenGLView *view = (__bridge NSOpenGLView *)viewRef;
return (__bridge CFTypeRef)view.openGLContext;
}
void gio_clearCurrentContext(void) {
[NSOpenGLContext clearCurrentContext];
}
void gio_makeCurrentContext(CFTypeRef ctxRef) {
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
return [ctx makeCurrentContext];
}
+473
View File
@@ -0,0 +1,473 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build darwin linux
package gl
import (
"unsafe"
)
/*
#cgo linux LDFLAGS: -lGLESv2 -ldl
#cgo darwin,!ios LDFLAGS: -framework OpenGL
#include <stdlib.h>
#ifdef __APPLE__
#cgo CFLAGS: -DGL_SILENCE_DEPRECATION
#include "TargetConditionals.h"
#if TARGET_OS_IPHONE
#include <OpenGLES/ES3/gl.h>
#else
#include <OpenGL/gl3.h>
#endif
#else
#define __USE_GNU
#include <dlfcn.h>
#include <GLES2/gl2.h>
#include <GLES3/gl3.h>
#endif
static void (*_glInvalidateFramebuffer)(GLenum target, GLsizei numAttachments, const GLenum *attachments);
static void (*_glBeginQuery)(GLenum target, GLuint id);
static void (*_glDeleteQueries)(GLsizei n, const GLuint *ids);
static void (*_glEndQuery)(GLenum target);
static void (*_glGenQueries)(GLsizei n, GLuint *ids);
static void (*_glGetQueryObjectuiv)(GLuint id, GLenum pname, GLuint *params);
// The pointer-free version of glVertexAttribPointer, to avoid the Cgo pointer checks.
__attribute__ ((visibility ("hidden"))) void gio_glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, uintptr_t offset) {
glVertexAttribPointer(index, size, type, normalized, stride, (const GLvoid *)offset);
}
// The pointer-free version of glDrawElements, to avoid the Cgo pointer checks.
__attribute__ ((visibility ("hidden"))) void gio_glDrawElements(GLenum mode, GLsizei count, GLenum type, const uintptr_t offset) {
glDrawElements(mode, count, type, (const GLvoid *)offset);
}
__attribute__ ((visibility ("hidden"))) void gio_glInvalidateFramebuffer(GLenum target, GLenum attachment) {
// Framebuffer invalidation is just a hint and can safely be ignored.
if (_glInvalidateFramebuffer != NULL) {
_glInvalidateFramebuffer(target, 1, &attachment);
}
}
__attribute__ ((visibility ("hidden"))) void gio_glBeginQuery(GLenum target, GLenum attachment) {
_glBeginQuery(target, attachment);
}
__attribute__ ((visibility ("hidden"))) void gio_glDeleteQueries(GLsizei n, const GLuint *ids) {
_glDeleteQueries(n, ids);
}
__attribute__ ((visibility ("hidden"))) void gio_glEndQuery(GLenum target) {
_glEndQuery(target);
}
__attribute__ ((visibility ("hidden"))) void gio_glGenQueries(GLsizei n, GLuint *ids) {
_glGenQueries(n, ids);
}
__attribute__ ((visibility ("hidden"))) void gio_glGetQueryObjectuiv(GLuint id, GLenum pname, GLuint *params) {
_glGetQueryObjectuiv(id, pname, params);
}
__attribute__((constructor)) static void gio_loadGLFunctions() {
#ifdef __APPLE__
#if TARGET_OS_IPHONE
_glInvalidateFramebuffer = glInvalidateFramebuffer;
_glBeginQuery = glBeginQuery;
_glDeleteQueries = glDeleteQueries;
_glEndQuery = glEndQuery;
_glGenQueries = glGenQueries;
_glGetQueryObjectuiv = glGetQueryObjectuiv;
#endif
#else
// Load libGLESv3 if available.
dlopen("libGLESv3.so", RTLD_NOW | RTLD_GLOBAL);
_glInvalidateFramebuffer = dlsym(RTLD_DEFAULT, "glInvalidateFramebuffer");
// Fall back to EXT_invalidate_framebuffer if available.
if (_glInvalidateFramebuffer == NULL) {
_glInvalidateFramebuffer = dlsym(RTLD_DEFAULT, "glDiscardFramebufferEXT");
}
_glBeginQuery = dlsym(RTLD_DEFAULT, "glBeginQuery");
if (_glBeginQuery == NULL)
_glBeginQuery = dlsym(RTLD_DEFAULT, "glBeginQueryEXT");
_glDeleteQueries = dlsym(RTLD_DEFAULT, "glDeleteQueries");
if (_glDeleteQueries == NULL)
_glDeleteQueries = dlsym(RTLD_DEFAULT, "glDeleteQueriesEXT");
_glEndQuery = dlsym(RTLD_DEFAULT, "glEndQuery");
if (_glEndQuery == NULL)
_glEndQuery = dlsym(RTLD_DEFAULT, "glEndQueryEXT");
_glGenQueries = dlsym(RTLD_DEFAULT, "glGenQueries");
if (_glGenQueries == NULL)
_glGenQueries = dlsym(RTLD_DEFAULT, "glGenQueriesEXT");
_glGetQueryObjectuiv = dlsym(RTLD_DEFAULT, "glGetQueryObjectuiv");
if (_glGetQueryObjectuiv == NULL)
_glGetQueryObjectuiv = dlsym(RTLD_DEFAULT, "glGetQueryObjectuivEXT");
#endif
}
*/
import "C"
type Functions struct{}
func (f *Functions) ActiveTexture(texture Enum) {
C.glActiveTexture(C.GLenum(texture))
}
func (f *Functions) AttachShader(p Program, s Shader) {
C.glAttachShader(C.GLuint(p), C.GLuint(s))
}
func (f *Functions) BeginQuery(target Enum, query Query) {
C.gio_glBeginQuery(C.GLenum(target), C.GLenum(query))
}
func (f *Functions) BindAttribLocation(p Program, a Attrib, name string) {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
C.glBindAttribLocation(C.GLuint(p), C.GLuint(a), cname)
}
func (f *Functions) BindBuffer(target Enum, b Buffer) {
C.glBindBuffer(C.GLenum(target), C.GLuint(b))
}
func (f *Functions) BindFramebuffer(target Enum, fb Framebuffer) {
C.glBindFramebuffer(C.GLenum(target), C.GLuint(fb))
}
func (f *Functions) BindRenderbuffer(target Enum, fb Renderbuffer) {
C.glBindRenderbuffer(C.GLenum(target), C.GLuint(fb))
}
func (f *Functions) BindTexture(target Enum, t Texture) {
C.glBindTexture(C.GLenum(target), C.GLuint(t))
}
func (f *Functions) BlendEquation(mode Enum) {
C.glBlendEquation(C.GLenum(mode))
}
func (f *Functions) BlendFunc(sfactor, dfactor Enum) {
C.glBlendFunc(C.GLenum(sfactor), C.GLenum(dfactor))
}
func (f *Functions) BufferData(target Enum, src []byte, usage Enum) {
var p unsafe.Pointer
if len(src) > 0 {
p = unsafe.Pointer(&src[0])
}
C.glBufferData(C.GLenum(target), C.GLsizeiptr(len(src)), p, C.GLenum(usage))
}
func (f *Functions) CheckFramebufferStatus(target Enum) Enum {
return Enum(C.glCheckFramebufferStatus(C.GLenum(target)))
}
func (f *Functions) Clear(mask Enum) {
C.glClear(C.GLbitfield(mask))
}
func (f *Functions) ClearColor(red float32, green float32, blue float32, alpha float32) {
C.glClearColor(C.GLfloat(red), C.GLfloat(green), C.GLfloat(blue), C.GLfloat(alpha))
}
func (f *Functions) ClearDepthf(d float32) {
C.glClearDepthf(C.GLfloat(d))
}
func (f *Functions) CompileShader(s Shader) {
C.glCompileShader(C.GLuint(s))
}
func (f *Functions) CreateBuffer() Buffer {
var handle C.GLuint
C.glGenBuffers(1, &handle)
return Buffer(handle)
}
func (f *Functions) CreateFramebuffer() Framebuffer {
var handle C.GLuint
C.glGenFramebuffers(1, &handle)
return Framebuffer(handle)
}
func (f *Functions) CreateProgram() Program {
return Program(C.glCreateProgram())
}
func (f *Functions) CreateQuery() Query {
var handle C.GLuint
C.gio_glGenQueries(1, &handle)
return Query(handle)
}
func (f *Functions) CreateRenderbuffer() Renderbuffer {
var handle C.GLuint
C.glGenRenderbuffers(1, &handle)
return Renderbuffer(handle)
}
func (f *Functions) CreateShader(ty Enum) Shader {
return Shader(C.glCreateShader(C.GLenum(ty)))
}
func (f *Functions) CreateTexture() Texture {
var handle C.GLuint
C.glGenTextures(1, &handle)
return Texture(handle)
}
func (f *Functions) DeleteBuffer(v Buffer) {
handle := C.GLuint(v)
C.glDeleteBuffers(1, &handle)
}
func (f *Functions) DeleteFramebuffer(v Framebuffer) {
handle := C.GLuint(v)
C.glDeleteFramebuffers(1, &handle)
}
func (f *Functions) DeleteProgram(p Program) {
C.glDeleteProgram(C.GLuint(p))
}
func (f *Functions) DeleteQuery(query Query) {
handle := C.GLuint(query)
C.gio_glDeleteQueries(1, &handle)
}
func (f *Functions) DeleteRenderbuffer(v Renderbuffer) {
handle := C.GLuint(v)
C.glDeleteRenderbuffers(1, &handle)
}
func (f *Functions) DeleteShader(s Shader) {
C.glDeleteShader(C.GLuint(s))
}
func (f *Functions) DeleteTexture(v Texture) {
handle := C.GLuint(v)
C.glDeleteTextures(1, &handle)
}
func (f *Functions) DepthFunc(v Enum) {
C.glDepthFunc(C.GLenum(v))
}
func (f *Functions) DepthMask(mask bool) {
m := C.GLboolean(C.GL_FALSE)
if mask {
m = C.GLboolean(C.GL_TRUE)
}
C.glDepthMask(m)
}
func (f *Functions) DisableVertexAttribArray(a Attrib) {
C.glDisableVertexAttribArray(C.GLuint(a))
}
func (f *Functions) Disable(cap Enum) {
C.glDisable(C.GLenum(cap))
}
func (f *Functions) DrawArrays(mode Enum, first int, count int) {
C.glDrawArrays(C.GLenum(mode), C.GLint(first), C.GLsizei(count))
}
func (f *Functions) DrawElements(mode Enum, count int, ty Enum, offset int) {
C.gio_glDrawElements(C.GLenum(mode), C.GLsizei(count), C.GLenum(ty), C.uintptr_t(offset))
}
func (f *Functions) Enable(cap Enum) {
C.glEnable(C.GLenum(cap))
}
func (f *Functions) EndQuery(target Enum) {
C.gio_glEndQuery(C.GLenum(target))
}
func (f *Functions) EnableVertexAttribArray(a Attrib) {
C.glEnableVertexAttribArray(C.GLuint(a))
}
func (f *Functions) Finish() {
C.glFinish()
}
func (f *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer) {
C.glFramebufferRenderbuffer(C.GLenum(target), C.GLenum(attachment), C.GLenum(renderbuffertarget), C.GLuint(renderbuffer))
}
func (f *Functions) FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int) {
C.glFramebufferTexture2D(C.GLenum(target), C.GLenum(attachment), C.GLenum(texTarget), C.GLuint(t), C.GLint(level))
}
func (f *Functions) GetError() Enum {
return Enum(C.glGetError())
}
func (f *Functions) GetRenderbufferParameteri(target, pname Enum) int {
// Hope this is enough room.
var buf [100]C.GLint
C.glGetRenderbufferParameteriv(C.GLenum(target), C.GLenum(pname), &buf[0])
return int(buf[0])
}
func (f *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int {
// Hope this is enough room.
var buf [100]C.GLint
C.glGetFramebufferAttachmentParameteriv(C.GLenum(target), C.GLenum(attachment), C.GLenum(pname), &buf[0])
return int(buf[0])
}
func (f *Functions) GetInteger(pname Enum) int {
// Hope this is enough room.
var buf [100]C.GLint
C.glGetIntegerv(C.GLenum(pname), &buf[0])
return int(buf[0])
}
func (f *Functions) GetProgrami(p Program, pname Enum) int {
// Hope this is enough room.
var buf [100]C.GLint
C.glGetProgramiv(C.GLuint(p), C.GLenum(pname), &buf[0])
return int(buf[0])
}
func (f *Functions) GetProgramInfoLog(p Program) string {
var plen C.GLsizei
C.glGetProgramInfoLog(C.GLuint(p), 0, &plen, nil)
if plen == 0 {
return ""
}
// Make room for the string and the null terminator.
buf := make([]byte, plen+1)
C.glGetProgramInfoLog(C.GLuint(p), C.GLsizei(len(buf)), &plen, (*C.GLchar)(unsafe.Pointer(&buf[0])))
return string(buf[:len(buf)-1])
}
func (f *Functions) GetQueryObjectuiv(query Query, pname Enum) uint {
// Hope this is enough room.
var buf [100]C.GLuint
C.gio_glGetQueryObjectuiv(C.GLuint(query), C.GLenum(pname), &buf[0])
return uint(buf[0])
}
func (f *Functions) GetShaderi(s Shader, pname Enum) int {
// Hope this is enough room.
var buf [100]C.GLint
C.glGetShaderiv(C.GLuint(s), C.GLenum(pname), &buf[0])
return int(buf[0])
}
func (f *Functions) GetShaderInfoLog(s Shader) string {
var plen C.GLsizei
C.glGetShaderInfoLog(C.GLuint(s), 0, &plen, nil)
if plen == 0 {
return ""
}
// Make room for the string and the null terminator.
buf := make([]byte, plen+1)
C.glGetShaderInfoLog(C.GLuint(s), C.GLsizei(len(buf)), &plen, (*C.GLchar)(unsafe.Pointer(&buf[0])))
return string(buf[:len(buf)-1])
}
func (f *Functions) GetString(pname Enum) string {
str := C.glGetString(C.GLenum(pname))
return C.GoString((*C.char)(unsafe.Pointer(str)))
}
func (f *Functions) GetUniformLocation(p Program, name string) Uniform {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
return Uniform(C.glGetUniformLocation(C.GLuint(p), cname))
}
func (f *Functions) InvalidateFramebuffer(target, attachment Enum) {
C.gio_glInvalidateFramebuffer(C.GLenum(target), C.GLenum(attachment))
}
func (f *Functions) LinkProgram(p Program) {
C.glLinkProgram(C.GLuint(p))
}
func (f *Functions) PixelStorei(pname Enum, param int32) {
C.glPixelStorei(C.GLenum(pname), C.GLint(param))
}
func (f *Functions) Scissor(x, y, width, height int32) {
C.glScissor(C.GLint(x), C.GLint(y), C.GLsizei(width), C.GLsizei(height))
}
func (f *Functions) RenderbufferStorage(target, internalformat Enum, width, height int) {
C.glRenderbufferStorage(C.GLenum(target), C.GLenum(internalformat), C.GLsizei(width), C.GLsizei(height))
}
func (f *Functions) ShaderSource(s Shader, src string) {
csrc := C.CString(src)
defer C.free(unsafe.Pointer(csrc))
strlen := C.GLint(len(src))
C.glShaderSource(C.GLuint(s), 1, &csrc, &strlen)
}
func (f *Functions) TexImage2D(target Enum, level int, internalFormat int, width int, height int, format Enum, ty Enum, data []byte) {
var p unsafe.Pointer
if len(data) > 0 {
p = unsafe.Pointer(&data[0])
}
C.glTexImage2D(C.GLenum(target), C.GLint(level), C.GLint(internalFormat), C.GLsizei(width), C.GLsizei(height), 0, C.GLenum(format), C.GLenum(ty), p)
}
func (f *Functions) TexSubImage2D(target Enum, level int, x int, y int, width int, height int, format Enum, ty Enum, data []byte) {
var p unsafe.Pointer
if len(data) > 0 {
p = unsafe.Pointer(&data[0])
}
C.glTexSubImage2D(C.GLenum(target), C.GLint(level), C.GLint(x), C.GLint(y), C.GLsizei(width), C.GLsizei(height), C.GLenum(format), C.GLenum(ty), p)
}
func (f *Functions) TexParameteri(target, pname Enum, param int) {
C.glTexParameteri(C.GLenum(target), C.GLenum(pname), C.GLint(param))
}
func (f *Functions) Uniform1f(dst Uniform, v float32) {
C.glUniform1f(C.GLint(dst), C.GLfloat(v))
}
func (f *Functions) Uniform1i(dst Uniform, v int) {
C.glUniform1i(C.GLint(dst), C.GLint(v))
}
func (f *Functions) Uniform2f(dst Uniform, v0 float32, v1 float32) {
C.glUniform2f(C.GLint(dst), C.GLfloat(v0), C.GLfloat(v1))
}
func (f *Functions) Uniform3f(dst Uniform, v0 float32, v1 float32, v2 float32) {
C.glUniform3f(C.GLint(dst), C.GLfloat(v0), C.GLfloat(v1), C.GLfloat(v2))
}
func (f *Functions) Uniform4f(dst Uniform, v0 float32, v1 float32, v2 float32, v3 float32) {
C.glUniform4f(C.GLint(dst), C.GLfloat(v0), C.GLfloat(v1), C.GLfloat(v2), C.GLfloat(v3))
}
func (f *Functions) UseProgram(p Program) {
C.glUseProgram(C.GLuint(p))
}
func (f *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride int, offset int) {
var n C.GLboolean = C.GL_FALSE
if normalized {
n = C.GL_TRUE
}
C.gio_glVertexAttribPointer(C.GLuint(dst), C.GLint(size), C.GLenum(ty), n, C.GLsizei(stride), C.uintptr_t(offset))
}
func (f *Functions) Viewport(x int, y int, width int, height int) {
C.glViewport(C.GLint(x), C.GLint(y), C.GLsizei(width), C.GLsizei(height))
}
+166
View File
@@ -0,0 +1,166 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gl
type (
Attrib uint
Buffer uint
Enum uint
Framebuffer uint
Program uint
Renderbuffer uint
Shader uint
Texture uint
Uniform int
Query uint
)
type Context interface {
Functions() *Functions
Present() error
MakeCurrent() error
Release()
}
const (
ARRAY_BUFFER = 0x8892
BLEND = 0xbe2
CLAMP_TO_EDGE = 0x812f
COLOR_ATTACHMENT0 = 0x8ce0
COLOR_BUFFER_BIT = 0x4000
COMPILE_STATUS = 0x8b81
DEPTH_BUFFER_BIT = 0x100
DEPTH_ATTACHMENT = 0x8d00
DEPTH_COMPONENT16 = 0x81a5
DEPTH_TEST = 0xb71
DST_COLOR = 0x306
ELEMENT_ARRAY_BUFFER = 0x8893
EXTENSIONS = 0x1f03
FLOAT = 0x1406
FRAGMENT_SHADER = 0x8b30
FRAMEBUFFER = 0x8d40
FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING = 0x8210
FRAMEBUFFER_BINDING = 0x8ca6
FRAMEBUFFER_COMPLETE = 0x8cd5
HALF_FLOAT = 0x140b
GREATER = 0x204
LINEAR = 0x2601
LINK_STATUS = 0x8b82
LUMINANCE = 0x1909
MAX_TEXTURE_SIZE = 0xd33
NEAREST = 0x2600
ONE = 0x1
ONE_MINUS_SRC_ALPHA = 0x303
QUERY_RESULT = 0x8866
QUERY_RESULT_AVAILABLE = 0x8867
R16F = 0x822d
R8 = 0x8229
READ_FRAMEBUFFER = 0x8ca8
RED = 0x1903
RENDERBUFFER = 0x8d41
RENDERBUFFER_BINDING = 0x8ca7
RENDERBUFFER_HEIGHT = 0x8d43
RENDERBUFFER_WIDTH = 0x8d42
RGB = 0x1907
RGBA = 0x1908
RGBA8 = 0x8058
SHORT = 0x1402
SRGB = 0x8c40
SRGB_ALPHA_EXT = 0x8c42
SRGB8 = 0x8c41
SRGB8_ALPHA8 = 0x8c43
STATIC_DRAW = 0x88e4
TEXTURE_2D = 0xde1
TEXTURE_MAG_FILTER = 0x2800
TEXTURE_MIN_FILTER = 0x2801
TEXTURE_WRAP_S = 0x2802
TEXTURE_WRAP_T = 0x2803
TEXTURE0 = 0x84c0
TEXTURE1 = 0x84c1
TRIANGLE_STRIP = 0x5
TRIANGLES = 0x4
UNPACK_ALIGNMENT = 0xcf5
UNSIGNED_BYTE = 0x1401
UNSIGNED_SHORT = 0x1403
VERSION = 0x1f02
VERTEX_SHADER = 0x8b31
ZERO = 0x0
// EXT_disjoint_timer_query
TIME_ELAPSED_EXT = 0x88BF
GPU_DISJOINT_EXT = 0x8FBB
)
// Enforce Functions interface.
var _ interface {
ActiveTexture(texture Enum)
AttachShader(p Program, s Shader)
BeginQuery(target Enum, query Query)
BindAttribLocation(p Program, a Attrib, name string)
BindBuffer(target Enum, b Buffer)
BindFramebuffer(target Enum, fb Framebuffer)
BindRenderbuffer(target Enum, rb Renderbuffer)
BindTexture(target Enum, t Texture)
BlendEquation(mode Enum)
BlendFunc(sfactor, dfactor Enum)
BufferData(target Enum, src []byte, usage Enum)
CheckFramebufferStatus(target Enum) Enum
Clear(mask Enum)
ClearColor(red, green, blue, alpha float32)
ClearDepthf(d float32)
CompileShader(s Shader)
CreateBuffer() Buffer
CreateFramebuffer() Framebuffer
CreateProgram() Program
CreateQuery() Query
CreateRenderbuffer() Renderbuffer
CreateShader(ty Enum) Shader
CreateTexture() Texture
DeleteBuffer(v Buffer)
DeleteFramebuffer(v Framebuffer)
DeleteProgram(p Program)
DeleteQuery(query Query)
DeleteRenderbuffer(v Renderbuffer)
DeleteShader(s Shader)
DeleteTexture(v Texture)
DepthFunc(f Enum)
DepthMask(mask bool)
DisableVertexAttribArray(a Attrib)
Disable(cap Enum)
DrawArrays(mode Enum, first, count int)
DrawElements(mode Enum, count int, ty Enum, offset int)
Enable(cap Enum)
EnableVertexAttribArray(a Attrib)
EndQuery(target Enum)
Finish()
FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer)
FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int)
GetError() Enum
GetRenderbufferParameteri(target, pname Enum) int
GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int
GetInteger(pname Enum) int
GetProgrami(p Program, pname Enum) int
GetProgramInfoLog(p Program) string
GetQueryObjectuiv(query Query, pname Enum) uint
GetShaderi(s Shader, pname Enum) int
GetShaderInfoLog(s Shader) string
GetString(pname Enum) string
GetUniformLocation(p Program, name string) Uniform
InvalidateFramebuffer(target, attachment Enum)
LinkProgram(p Program)
PixelStorei(pname Enum, param int32)
RenderbufferStorage(target, internalformat Enum, width, height int)
Scissor(x, y, width, height int32)
ShaderSource(s Shader, src string)
TexImage2D(target Enum, level int, internalFormat int, width, height int, format, ty Enum, data []byte)
TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte)
TexParameteri(target, pname Enum, param int)
Uniform1f(dst Uniform, v float32)
Uniform1i(dst Uniform, v int)
Uniform2f(dst Uniform, v0, v1 float32)
Uniform3f(dst Uniform, v0, v1, v2 float32)
Uniform4f(dst Uniform, v0, v1, v2, v3 float32)
UseProgram(p Program)
VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int)
Viewport(x, y, width, height int)
} = (*Functions)(nil)
+397
View File
@@ -0,0 +1,397 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gl
import (
"math"
"reflect"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
var (
LibGLESv2 = windows.NewLazyDLL("libGLESv2.dll")
_glActiveTexture = LibGLESv2.NewProc("glActiveTexture")
_glAttachShader = LibGLESv2.NewProc("glAttachShader")
_glBeginQuery = LibGLESv2.NewProc("glBeginQuery")
_glBindAttribLocation = LibGLESv2.NewProc("glBindAttribLocation")
_glBindBuffer = LibGLESv2.NewProc("glBindBuffer")
_glBindFramebuffer = LibGLESv2.NewProc("glBindFramebuffer")
_glBindRenderbuffer = LibGLESv2.NewProc("glBindRenderbuffer")
_glBindTexture = LibGLESv2.NewProc("glBindTexture")
_glBlendEquation = LibGLESv2.NewProc("glBlendEquation")
_glBlendFunc = LibGLESv2.NewProc("glBlendFunc")
_glBufferData = LibGLESv2.NewProc("glBufferData")
_glCheckFramebufferStatus = LibGLESv2.NewProc("glCheckFramebufferStatus")
_glClear = LibGLESv2.NewProc("glClear")
_glClearColor = LibGLESv2.NewProc("glClearColor")
_glClearDepthf = LibGLESv2.NewProc("glClearDepthf")
_glDeleteQueries = LibGLESv2.NewProc("glDeleteQueries")
_glCompileShader = LibGLESv2.NewProc("glCompileShader")
_glGenBuffers = LibGLESv2.NewProc("glGenBuffers")
_glGenFramebuffers = LibGLESv2.NewProc("glGenFramebuffers")
_glCreateProgram = LibGLESv2.NewProc("glCreateProgram")
_glGenRenderbuffers = LibGLESv2.NewProc("glGenRenderbuffers")
_glCreateShader = LibGLESv2.NewProc("glCreateShader")
_glGenTextures = LibGLESv2.NewProc("glGenTextures")
_glDeleteBuffers = LibGLESv2.NewProc("glDeleteBuffers")
_glDeleteFramebuffers = LibGLESv2.NewProc("glDeleteFramebuffers")
_glDeleteProgram = LibGLESv2.NewProc("glDeleteProgram")
_glDeleteShader = LibGLESv2.NewProc("glDeleteShader")
_glDeleteRenderbuffers = LibGLESv2.NewProc("glDeleteRenderbuffers")
_glDeleteTextures = LibGLESv2.NewProc("glDeleteTextures")
_glDepthFunc = LibGLESv2.NewProc("glDepthFunc")
_glDepthMask = LibGLESv2.NewProc("glDepthMask")
_glDisableVertexAttribArray = LibGLESv2.NewProc("glDisableVertexAttribArray")
_glDisable = LibGLESv2.NewProc("glDisable")
_glDrawArrays = LibGLESv2.NewProc("glDrawArrays")
_glDrawElements = LibGLESv2.NewProc("glDrawElements")
_glEnable = LibGLESv2.NewProc("glEnable")
_glEnableVertexAttribArray = LibGLESv2.NewProc("glEnableVertexAttribArray")
_glEndQuery = LibGLESv2.NewProc("glEndQuery")
_glFinish = LibGLESv2.NewProc("glFinish")
_glFramebufferRenderbuffer = LibGLESv2.NewProc("glFramebufferRenderbuffer")
_glFramebufferTexture2D = LibGLESv2.NewProc("glFramebufferTexture2D")
_glGenQueries = LibGLESv2.NewProc("glGenQueries")
_glGetError = LibGLESv2.NewProc("glGetError")
_glGetRenderbufferParameteri = LibGLESv2.NewProc("glGetRenderbufferParameteri")
_glGetFramebufferAttachmentParameteri = LibGLESv2.NewProc("glGetFramebufferAttachmentParameteri")
_glGetIntegerv = LibGLESv2.NewProc("glGetIntegerv")
_glGetProgramiv = LibGLESv2.NewProc("glGetProgramiv")
_glGetProgramInfoLog = LibGLESv2.NewProc("glGetProgramInfoLog")
_glGetQueryObjectuiv = LibGLESv2.NewProc("glGetQueryObjectuiv")
_glGetShaderiv = LibGLESv2.NewProc("glGetShaderiv")
_glGetShaderInfoLog = LibGLESv2.NewProc("glGetShaderInfoLog")
_glGetString = LibGLESv2.NewProc("glGetString")
_glGetUniformLocation = LibGLESv2.NewProc("glGetUniformLocation")
_glInvalidateFramebuffer = LibGLESv2.NewProc("glInvalidateFramebuffer")
_glLinkProgram = LibGLESv2.NewProc("glLinkProgram")
_glPixelStorei = LibGLESv2.NewProc("glPixelStorei")
_glRenderbufferStorage = LibGLESv2.NewProc("glRenderbufferStorage")
_glScissor = LibGLESv2.NewProc("glScissor")
_glShaderSource = LibGLESv2.NewProc("glShaderSource")
_glTexImage2D = LibGLESv2.NewProc("glTexImage2D")
_glTexSubImage2D = LibGLESv2.NewProc("glTexSubImage2D")
_glTexParameteri = LibGLESv2.NewProc("glTexParameteri")
_glUniform1f = LibGLESv2.NewProc("glUniform1f")
_glUniform1i = LibGLESv2.NewProc("glUniform1i")
_glUniform2f = LibGLESv2.NewProc("glUniform2f")
_glUniform3f = LibGLESv2.NewProc("glUniform3f")
_glUniform4f = LibGLESv2.NewProc("glUniform4f")
_glUseProgram = LibGLESv2.NewProc("glUseProgram")
_glVertexAttribPointer = LibGLESv2.NewProc("glVertexAttribPointer")
_glViewport = LibGLESv2.NewProc("glViewport")
)
type Functions struct{}
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), uintptr(s), 0)
}
func (f *Functions) BeginQuery(target Enum, query Query) {
syscall.Syscall(_glBeginQuery.Addr(), 2, uintptr(target), uintptr(query), 0)
}
func (c *Functions) BindAttribLocation(p Program, a Attrib, name string) {
cname := cString(name)
syscall.Syscall(_glBindAttribLocation.Addr(), 3, uintptr(p), uintptr(a), uintptr(unsafe.Pointer(&cname[0])))
}
func (c *Functions) BindBuffer(target Enum, b Buffer) {
syscall.Syscall(_glBindBuffer.Addr(), 2, uintptr(target), uintptr(b), 0)
}
func (c *Functions) BindFramebuffer(target Enum, fb Framebuffer) {
syscall.Syscall(_glBindFramebuffer.Addr(), 2, uintptr(target), uintptr(fb), 0)
}
func (c *Functions) BindRenderbuffer(target Enum, rb Renderbuffer) {
syscall.Syscall(_glBindRenderbuffer.Addr(), 2, uintptr(target), uintptr(rb), 0)
}
func (c *Functions) BindTexture(target Enum, t Texture) {
syscall.Syscall(_glBindTexture.Addr(), 2, uintptr(target), uintptr(t), 0)
}
func (c *Functions) BlendEquation(mode Enum) {
syscall.Syscall(_glBlendEquation.Addr(), 1, uintptr(mode), 0, 0)
}
func (c *Functions) BlendFunc(sfactor, dfactor Enum) {
syscall.Syscall(_glBlendFunc.Addr(), 2, uintptr(sfactor), uintptr(dfactor), 0)
}
func (c *Functions) BufferData(target Enum, src []byte, usage Enum) {
if n := len(src); n == 0 {
syscall.Syscall6(_glBufferData.Addr(), 4, uintptr(target), 0, 0, uintptr(usage), 0, 0)
} else {
syscall.Syscall6(_glBufferData.Addr(), 4, uintptr(target), uintptr(n), uintptr(unsafe.Pointer(&src[0])), uintptr(usage), 0, 0)
}
}
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), 0, 0)
}
func (c *Functions) CreateBuffer() Buffer {
var buf uintptr
syscall.Syscall(_glGenBuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&buf)), 0)
return Buffer(buf)
}
func (c *Functions) CreateFramebuffer() Framebuffer {
var fb uintptr
syscall.Syscall(_glGenFramebuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&fb)), 0)
return Framebuffer(fb)
}
func (c *Functions) CreateProgram() Program {
p, _, _ := syscall.Syscall(_glCreateProgram.Addr(), 0, 0, 0, 0)
return Program(p)
}
func (f *Functions) CreateQuery() Query {
var q uintptr
syscall.Syscall(_glGenQueries.Addr(), 2, 1, uintptr(unsafe.Pointer(&q)), 0)
return Query(q)
}
func (c *Functions) CreateRenderbuffer() Renderbuffer {
var rb uintptr
syscall.Syscall(_glGenRenderbuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&rb)), 0)
return Renderbuffer(rb)
}
func (c *Functions) CreateShader(ty Enum) Shader {
s, _, _ := syscall.Syscall(_glCreateShader.Addr(), 1, uintptr(ty), 0, 0)
return Shader(s)
}
func (c *Functions) CreateTexture() Texture {
var t uintptr
syscall.Syscall(_glGenTextures.Addr(), 2, 1, uintptr(unsafe.Pointer(&t)), 0)
return Texture(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)), 0)
}
func (c *Functions) DeleteProgram(p Program) {
syscall.Syscall(_glDeleteProgram.Addr(), 1, uintptr(p), 0, 0)
}
func (f *Functions) DeleteQuery(query Query) {
syscall.Syscall(_glDeleteQueries.Addr(), 2, 1, uintptr(unsafe.Pointer(&query)), 0)
}
func (c *Functions) DeleteShader(s Shader) {
syscall.Syscall(_glDeleteShader.Addr(), 1, uintptr(s), 0, 0)
}
func (c *Functions) DeleteRenderbuffer(v Renderbuffer) {
syscall.Syscall(_glDeleteRenderbuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v)), 0)
}
func (c *Functions) DeleteTexture(v Texture) {
syscall.Syscall(_glDeleteTextures.Addr(), 2, 1, uintptr(unsafe.Pointer(&v)), 0)
}
func (c *Functions) DepthFunc(f Enum) {
syscall.Syscall(_glDepthFunc.Addr(), 1, uintptr(f), 0, 0)
}
func (c *Functions) DepthMask(mask bool) {
var m uintptr
if mask {
m = 1
}
syscall.Syscall(_glDepthMask.Addr(), 1, m, 0, 0)
}
func (c *Functions) DisableVertexAttribArray(a Attrib) {
syscall.Syscall(_glDisableVertexAttribArray.Addr(), 1, uintptr(a), 0, 0)
}
func (c *Functions) Disable(cap Enum) {
syscall.Syscall(_glDisable.Addr(), 1, uintptr(cap), 0, 0)
}
func (c *Functions) DrawArrays(mode Enum, first, count int) {
syscall.Syscall(_glDrawArrays.Addr(), 3, uintptr(mode), uintptr(first), uintptr(count))
}
func (c *Functions) DrawElements(mode Enum, count int, ty Enum, offset int) {
syscall.Syscall6(_glDrawElements.Addr(), 4, uintptr(mode), uintptr(count), uintptr(ty), uintptr(offset), 0, 0)
}
func (c *Functions) Enable(cap Enum) {
syscall.Syscall(_glEnable.Addr(), 1, uintptr(cap), 0, 0)
}
func (c *Functions) EnableVertexAttribArray(a Attrib) {
syscall.Syscall(_glEnableVertexAttribArray.Addr(), 1, uintptr(a), 0, 0)
}
func (f *Functions) EndQuery(target Enum) {
syscall.Syscall(_glEndQuery.Addr(), 1, uintptr(target), 0, 0)
}
func (c *Functions) Finish() {
syscall.Syscall(_glFinish.Addr(), 0, 0, 0, 0)
}
func (c *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer) {
syscall.Syscall6(_glFramebufferRenderbuffer.Addr(), 4, uintptr(target), uintptr(attachment), uintptr(renderbuffertarget), uintptr(renderbuffer), 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), uintptr(level), 0)
}
func (c *Functions) GetError() Enum {
e, _, _ := syscall.Syscall(_glGetError.Addr(), 0, 0, 0, 0)
return Enum(e)
}
func (c *Functions) GetRenderbufferParameteri(target, pname Enum) int {
p, _, _ := syscall.Syscall(_glGetRenderbufferParameteri.Addr(), 2, uintptr(target), uintptr(pname), 0)
return int(p)
}
func (c *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int {
p, _, _ := syscall.Syscall(_glGetFramebufferAttachmentParameteri.Addr(), 3, uintptr(target), uintptr(attachment), uintptr(pname))
return int(p)
}
func (c *Functions) GetInteger(pname Enum) int {
// Hopefully enough room.
var params [100]int32
syscall.Syscall(_glGetIntegerv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&params[0])), 0)
return int(params[0])
}
func (c *Functions) GetProgrami(p Program, pname Enum) int {
// Hopefully enough space.
var params [100]int32
syscall.Syscall(_glGetProgramiv.Addr(), 3, uintptr(p), uintptr(pname), uintptr(unsafe.Pointer(&params[0])))
return int(params[0])
}
func (c *Functions) GetProgramInfoLog(p Program) string {
var n uintptr
syscall.Syscall6(_glGetProgramInfoLog.Addr(), 4, uintptr(p), 0, uintptr(unsafe.Pointer(&n)), 0, 0, 0)
if n == 0 {
return ""
}
// Make space for the null terminator.
buf := make([]byte, n+1)
syscall.Syscall6(_glGetProgramInfoLog.Addr(), 4, uintptr(p), uintptr(len(buf)), uintptr(unsafe.Pointer(&n)), uintptr(unsafe.Pointer(&buf[0])), 0, 0)
return string(buf[:len(buf)-1])
}
func (f *Functions) GetQueryObjectuiv(query Query, pname Enum) uint {
// Hope this is enough room.
var buf [100]int32
syscall.Syscall(_glGetQueryObjectuiv.Addr(), 3, uintptr(query), uintptr(pname), uintptr(unsafe.Pointer(&buf[0])))
return uint(buf[0])
}
func (c *Functions) GetShaderi(s Shader, pname Enum) int {
// Hopefully enough room.
var params [100]int32
syscall.Syscall(_glGetShaderiv.Addr(), 3, uintptr(s), uintptr(pname), uintptr(unsafe.Pointer(&params[0])))
return int(params[0])
}
func (c *Functions) GetShaderInfoLog(s Shader) string {
var n uintptr
syscall.Syscall6(_glGetShaderInfoLog.Addr(), 4, uintptr(s), 0, uintptr(unsafe.Pointer(&n)), 0, 0, 0)
if n == 0 {
return ""
}
// Make space for the null terminator.
buf := make([]byte, n+1)
syscall.Syscall6(_glGetShaderInfoLog.Addr(), 4, uintptr(s), uintptr(len(buf)), uintptr(unsafe.Pointer(&n)), uintptr(unsafe.Pointer(&buf[0])), 0, 0)
return string(buf[:len(buf)-1])
}
func (c *Functions) GetString(pname Enum) string {
s, _, _ := syscall.Syscall(_glGetString.Addr(), 1, uintptr(pname), 0, 0)
return goString(s)
}
func (c *Functions) GetUniformLocation(p Program, name string) Uniform {
cname := cString(name)
u, _, _ := syscall.Syscall(_glGetUniformLocation.Addr(), 2, uintptr(p), uintptr(unsafe.Pointer(&cname[0])), 0)
return Uniform(u)
}
func (c *Functions) InvalidateFramebuffer(target, attachment Enum) {
addr := _glInvalidateFramebuffer.Addr()
if addr == 0 {
// InvalidateFramebuffer is just a hint. Skip it if not supported.
return
}
syscall.Syscall(addr, 3, uintptr(target), 1, uintptr(unsafe.Pointer(&attachment)))
}
func (c *Functions) LinkProgram(p Program) {
syscall.Syscall(_glLinkProgram.Addr(), 1, uintptr(p), 0, 0)
}
func (c *Functions) PixelStorei(pname Enum, param int32) {
syscall.Syscall(_glPixelStorei.Addr(), 2, uintptr(pname), uintptr(param), 0)
}
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))
syscall.Syscall6(_glShaderSource.Addr(), 4, uintptr(s), 1, uintptr(unsafe.Pointer(&src)), uintptr(unsafe.Pointer(&n)), 0, 0)
}
func (c *Functions) TexImage2D(target Enum, level int, internalFormat int, width, height int, format, ty Enum, data []byte) {
if len(data) == 0 {
syscall.Syscall9(_glTexImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(internalFormat), uintptr(width), uintptr(height), 0, uintptr(format), uintptr(ty), 0)
} else {
syscall.Syscall9(_glTexImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(internalFormat), uintptr(width), uintptr(height), 0, uintptr(format), uintptr(ty), uintptr(unsafe.Pointer(&data[0])))
}
}
func (c *Functions) TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte) {
syscall.Syscall9(_glTexSubImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(format), uintptr(ty), uintptr(unsafe.Pointer(&data[0])))
}
func (c *Functions) TexParameteri(target, pname Enum, param int) {
syscall.Syscall(_glTexParameteri.Addr(), 3, uintptr(target), uintptr(pname), uintptr(param))
}
func (c *Functions) Uniform1f(dst Uniform, v float32) {
syscall.Syscall(_glUniform1f.Addr(), 2, uintptr(dst), uintptr(math.Float32bits(v)), 0)
}
func (c *Functions) Uniform1i(dst Uniform, v int) {
syscall.Syscall(_glUniform1i.Addr(), 2, uintptr(dst), uintptr(v), 0)
}
func (c *Functions) Uniform2f(dst Uniform, v0, v1 float32) {
syscall.Syscall(_glUniform2f.Addr(), 3, uintptr(dst), 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), 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), 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), 0, 0)
}
func (c *Functions) VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int) {
var norm uintptr
if normalized {
norm = 1
}
syscall.Syscall6(_glVertexAttribPointer.Addr(), 6, uintptr(dst), uintptr(size), uintptr(ty), norm, uintptr(stride), uintptr(offset))
}
func (c *Functions) Viewport(x, y, width, height int) {
syscall.Syscall6(_glViewport.Addr(), 4, uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0, 0)
}
func cString(s string) []byte {
b := make([]byte, len(s)+1)
copy(b, s)
return b
}
func goString(s uintptr) string {
if s == 0 {
return ""
}
sh := reflect.SliceHeader{
Data: s,
Len: 1 << 30,
Cap: 1 << 30,
}
sl := *(*[]byte)(unsafe.Pointer(&sh))
var v string
for i, c := range sl {
if c == 0 {
if i > 0 {
v = string(sl[:i-1])
}
break
}
}
return v
}
+160
View File
@@ -0,0 +1,160 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gl
import (
"fmt"
"strings"
)
// SRGBFBO implements an intermediate sRGB FBO
// for gamma-correct rendering on platforms without
// sRGB enabled native framebuffers.
type SRGBFBO struct {
c *Functions
width, height int
frameBuffer Framebuffer
depthBuffer Renderbuffer
colorTex Texture
quad Buffer
prog Program
es3 bool
}
func NewSRGBFBO(f *Functions) (*SRGBFBO, error) {
var es3 bool
glVer := f.GetString(VERSION)
ver, err := ParseGLVersion(glVer)
if err != nil {
return nil, fmt.Errorf("failed to parse OpenGL ES version (%s): %v", glVer, err)
}
if ver[0] >= 3 {
es3 = true
} else {
exts := f.GetString(EXTENSIONS)
if !strings.Contains(exts, "EXT_sRGB") {
return nil, fmt.Errorf("no support for OpenGL ES 3 nor EXT_sRGB")
}
}
prog, err := CreateProgram(f, blitVSrc, blitFSrc, []string{"pos", "uv"})
if err != nil {
return nil, err
}
f.UseProgram(prog)
f.Uniform1i(GetUniformLocation(f, prog, "tex"), 0)
s := &SRGBFBO{
c: f,
es3: es3,
prog: prog,
frameBuffer: f.CreateFramebuffer(),
colorTex: f.CreateTexture(),
depthBuffer: f.CreateRenderbuffer(),
}
s.quad = f.CreateBuffer()
f.BindBuffer(ARRAY_BUFFER, s.quad)
f.BufferData(ARRAY_BUFFER,
BytesView([]float32{
-1, +1, 0, 1,
+1, +1, 1, 1,
-1, -1, 0, 0,
+1, -1, 1, 0,
}),
STATIC_DRAW)
f.BindTexture(TEXTURE_2D, s.colorTex)
f.TexParameteri(TEXTURE_2D, TEXTURE_WRAP_S, CLAMP_TO_EDGE)
f.TexParameteri(TEXTURE_2D, TEXTURE_WRAP_T, CLAMP_TO_EDGE)
f.TexParameteri(TEXTURE_2D, TEXTURE_MAG_FILTER, NEAREST)
f.TexParameteri(TEXTURE_2D, TEXTURE_MIN_FILTER, NEAREST)
return s, nil
}
func (s *SRGBFBO) Blit() {
s.c.BindFramebuffer(FRAMEBUFFER, 0)
s.c.ClearColor(1, 0, 1, 1)
s.c.Clear(COLOR_BUFFER_BIT)
s.c.UseProgram(s.prog)
s.c.BindTexture(TEXTURE_2D, s.colorTex)
s.c.BindBuffer(ARRAY_BUFFER, s.quad)
s.c.VertexAttribPointer(0 /* pos */, 2, FLOAT, false, 4*4, 0)
s.c.VertexAttribPointer(1 /* uv */, 2, FLOAT, false, 4*4, 4*2)
s.c.EnableVertexAttribArray(0)
s.c.EnableVertexAttribArray(1)
s.c.DrawArrays(TRIANGLE_STRIP, 0, 4)
s.c.BindTexture(TEXTURE_2D, 0)
s.c.DisableVertexAttribArray(0)
s.c.DisableVertexAttribArray(1)
s.c.InvalidateFramebuffer(FRAMEBUFFER, COLOR_ATTACHMENT0)
s.c.InvalidateFramebuffer(FRAMEBUFFER, DEPTH_ATTACHMENT)
// The Android emulator requires framebuffer 0 bound at eglSwapBuffer time.
// Bind the default sRGB framebuffer in afterPresent.
}
func (s *SRGBFBO) AfterPresent() {
s.c.BindFramebuffer(FRAMEBUFFER, s.frameBuffer)
}
func (s *SRGBFBO) Refresh(w, h int) error {
s.width, s.height = w, h
if w == 0 || h == 0 {
return nil
}
s.c.BindTexture(TEXTURE_2D, s.colorTex)
if s.es3 {
s.c.TexImage2D(TEXTURE_2D, 0, SRGB8_ALPHA8, w, h, RGBA, UNSIGNED_BYTE, nil)
} else /* EXT_sRGB */ {
s.c.TexImage2D(TEXTURE_2D, 0, SRGB_ALPHA_EXT, w, h, SRGB_ALPHA_EXT, UNSIGNED_BYTE, nil)
}
currentRB := Renderbuffer(s.c.GetInteger(RENDERBUFFER_BINDING))
s.c.BindRenderbuffer(RENDERBUFFER, s.depthBuffer)
s.c.RenderbufferStorage(RENDERBUFFER, DEPTH_COMPONENT16, w, h)
s.c.BindRenderbuffer(RENDERBUFFER, currentRB)
s.c.BindFramebuffer(FRAMEBUFFER, s.frameBuffer)
s.c.FramebufferTexture2D(FRAMEBUFFER, COLOR_ATTACHMENT0, TEXTURE_2D, s.colorTex, 0)
s.c.FramebufferRenderbuffer(FRAMEBUFFER, DEPTH_ATTACHMENT, RENDERBUFFER, s.depthBuffer)
if st := s.c.CheckFramebufferStatus(FRAMEBUFFER); st != FRAMEBUFFER_COMPLETE {
return fmt.Errorf("sRGB framebuffer incomplete (%dx%d), status: %#x error: %x", s.width, s.height, st, s.c.GetError())
}
return nil
}
func (s *SRGBFBO) Release() {
s.c.DeleteFramebuffer(s.frameBuffer)
s.c.DeleteTexture(s.colorTex)
s.c.DeleteRenderbuffer(s.depthBuffer)
}
const (
blitVSrc = `
#version 100
precision highp float;
attribute vec2 pos;
attribute vec2 uv;
varying vec2 vUV;
void main() {
gl_Position = vec4(pos, 0, 1);
vUV = uv;
}
`
blitFSrc = `
#version 100
precision mediump float;
uniform sampler2D tex;
varying vec2 vUV;
void main() {
vec4 col = texture2D(tex, vUV);
vec3 rgb = col.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));
rgb = vec3(cut.r ? lin.r : exp.r, cut.g ? lin.g : exp.g, cut.b ? lin.b : exp.b);
gl_FragColor = vec4(rgb, col.a);
}
`
)
+85
View File
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gl
import (
"errors"
"fmt"
"reflect"
"strings"
"unsafe"
)
func CreateProgram(ctx *Functions, vsSrc, fsSrc string, attribs []string) (Program, error) {
vs, err := createShader(ctx, VERTEX_SHADER, vsSrc)
if err != nil {
return 0, err
}
defer ctx.DeleteShader(vs)
fs, err := createShader(ctx, FRAGMENT_SHADER, fsSrc)
if err != nil {
return 0, err
}
defer ctx.DeleteShader(fs)
prog := ctx.CreateProgram()
if prog == 0 {
return 0, 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 0, fmt.Errorf("program link failed: %s", strings.TrimSpace(log))
}
return prog, nil
}
func GetUniformLocation(ctx *Functions, prog Program, name string) Uniform {
loc := ctx.GetUniformLocation(prog, name)
if loc == -1 {
panic(fmt.Errorf("uniform %s not found", name))
}
return loc
}
func createShader(ctx *Functions, typ Enum, src string) (Shader, error) {
sh := ctx.CreateShader(typ)
if sh == 0 {
return 0, 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 0, fmt.Errorf("shader compilation failed: %s", strings.TrimSpace(log))
}
return sh, nil
}
// BytesView returns a byte slice view of a slice.
func BytesView(s interface{}) []byte {
v := reflect.ValueOf(s)
first := v.Index(0)
sz := int(first.Type().Size())
return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
Data: uintptr(unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(first.UnsafeAddr())))),
Len: v.Len() * sz,
Cap: v.Cap() * sz,
}))
}
func ParseGLVersion(glVer string) ([2]int, error) {
var ver [2]int
if _, err := fmt.Sscanf(glVer, "OpenGL ES %d.%d", &ver[0], &ver[1]); err != nil {
if _, err := fmt.Sscanf(glVer, "%d.%d", &ver[0], &ver[1]); err != nil {
return [2]int{}, fmt.Errorf("failed to parse OpenGL ES version (%s): %v", glVer, err)
}
}
return ver, nil
}
+76
View File
@@ -0,0 +1,76 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
import (
"image"
)
// genAreaLUT generates the lookup table conpatible with the stencilFSrc
// fragment shaders. The table contains the area of a pixel square above
// a line. The square has area 1 and is centered in (0, 0).
// The y-axis intersection of the line in [-8;+8] is specified by the
// first coordinate.
// The slope of the line [0;16] is specified by the second coordinate.
func genAreaLUT(width, height int) *image.Gray {
lut := image.NewGray(image.Rectangle{Max: image.Point{X: width, Y: height}})
for v := 0; v < height; v++ {
a := float32(v) * 16 / float32(height)
for u := 0; u < width; u++ {
var area float32
switch u {
case 0:
area = 1.0
case width - 1:
area = 0.0
default:
b := (float32(u) - float32(width)/2) / 16
// f(x) = ax+b.
area = computeLineArea(a, b)
}
lut.Pix[v*height+u] = uint8(area*255 + 0.5)
}
}
return lut
}
func computeLineArea(a, b float32) float32 {
// Compute intersections with the square edges.
// Right and left.
ry := a*+0.5 + b
ly := a*-0.5 + b
// Top and bottom.
tx := (+0.5 - b) / a
bx := (-0.5 - b) / a
// The line will intersect zero or two edges.
if ry <= -0.5 {
// Line is below the square.
return 1.0
}
if ly >= 0.5 {
// Line is above the square.
return 0.0
}
// The slope is positive, so there are only 4 possible
// pairs of edges: (bottom, right), (left, right),
// (bottom, top), (left, top).
if ry <= 0.5 {
// Intersection with right edge.
if ly <= -0.5 {
// (bottom, right).
return 1.0 - (0.5-bx)*(ry-(-0.5))/2
} else {
// (left, right).
return 1.0*(0.5-ry) + 1.0*(ry-ly)/2
}
} else {
// Intersection with top edge.
if ly <= -0.5 {
// (bottom, top).
return (bx-(-0.5))*1.0 + (tx-bx)*1.0/2
} else {
// (left, top).
return (tx - (-0.5)) * (0.5 - ly) / 2
}
}
}
File diff suppressed because it is too large Load Diff
+85
View File
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
import (
"image"
)
// packer packs a set of many smaller rectangles into
// much fewer larger atlases.
type packer struct {
maxDim int
spaces []image.Rectangle
sizes []image.Point
pos image.Point
}
type placement struct {
Idx int
Pos image.Point
}
// add adds the given rectangle to the atlases and
// return the allocated position.
func (p *packer) add(s image.Point) (placement, bool) {
if place, ok := p.tryAdd(s); ok {
return place, true
}
p.newPage()
return p.tryAdd(s)
}
func (p *packer) clear() {
p.sizes = p.sizes[:0]
p.spaces = p.spaces[:0]
}
func (p *packer) newPage() {
p.pos = image.Point{}
p.sizes = append(p.sizes, image.Point{})
p.spaces = p.spaces[:0]
p.spaces = append(p.spaces, image.Rectangle{
Max: image.Point{X: p.maxDim, Y: p.maxDim},
})
}
func (p *packer) tryAdd(s image.Point) (placement, bool) {
// Go backwards to prioritize smaller spaces first.
for i := len(p.spaces) - 1; i >= 0; i-- {
space := p.spaces[i]
rightSpace := space.Dx() - s.X
bottomSpace := space.Dy() - s.Y
if rightSpace >= 0 && bottomSpace >= 0 {
// Remove space.
p.spaces[i] = p.spaces[len(p.spaces)-1]
p.spaces = p.spaces[:len(p.spaces)-1]
// Put s in the top left corner and add the (at most)
// two smaller spaces.
pos := space.Min
if bottomSpace > 0 {
p.spaces = append(p.spaces, image.Rectangle{
Min: image.Point{X: pos.X, Y: pos.Y + s.Y},
Max: image.Point{X: space.Max.X, Y: space.Max.Y},
})
}
if rightSpace > 0 {
p.spaces = append(p.spaces, image.Rectangle{
Min: image.Point{X: pos.X + s.X, Y: pos.Y},
Max: image.Point{X: space.Max.X, Y: pos.Y + s.Y},
})
}
idx := len(p.sizes) - 1
size := &p.sizes[idx]
if x := pos.X + s.X; x > size.X {
size.X = x
}
if y := pos.Y + s.Y; y > size.Y {
size.Y = y
}
return placement{Idx: idx, Pos: pos}, true
}
}
return placement{}, false
}
+596
View File
@@ -0,0 +1,596 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
// GPU accelerated path drawing using the algorithms from
// Pathfinder (https://github.com/pcwalton/pathfinder).
import (
"image"
"unsafe"
"gioui.org/ui/app/internal/gl"
"gioui.org/ui/f32"
"gioui.org/ui/internal/path"
)
type pather struct {
ctx *gl.Functions
viewport image.Point
stenciler *stenciler
coverer *coverer
}
type coverer struct {
ctx *gl.Functions
prog [2]gl.Program
vars [2]struct {
z gl.Uniform
uScale, uOffset gl.Uniform
uUVScale, uUVOffset gl.Uniform
uCoverUVScale, uCoverUVOffset gl.Uniform
uColor gl.Uniform
}
}
type stenciler struct {
ctx *gl.Functions
defFBO gl.Framebuffer
indexBufQuads int
prog gl.Program
iprog gl.Program
fbos fboSet
intersections fboSet
uScale, uOffset gl.Uniform
uPathOffset gl.Uniform
uIntersectUVOffset gl.Uniform
uIntersectUVScale gl.Uniform
indexBuf gl.Buffer
areaLUT gl.Texture
}
type fboSet struct {
fbos []stencilFBO
}
type stencilFBO struct {
size image.Point
fbo gl.Framebuffer
tex gl.Texture
}
type pathData struct {
ncurves int
data gl.Buffer
}
var (
pathAttribs = []string{"corner", "maxy", "from", "ctrl", "to"}
attribPathCorner gl.Attrib = 0
attribPathMaxY gl.Attrib = 1
attribPathFrom gl.Attrib = 2
attribPathCtrl gl.Attrib = 3
attribPathTo gl.Attrib = 4
intersectAttribs = []string{"pos", "uv"}
)
func newPather(ctx *gl.Functions) *pather {
return &pather{
ctx: ctx,
stenciler: newStenciler(ctx),
coverer: newCoverer(ctx),
}
}
func newCoverer(ctx *gl.Functions) *coverer {
prog, err := createColorPrograms(ctx, coverVSrc, coverFSrc)
if err != nil {
panic(err)
}
c := &coverer{
ctx: ctx,
prog: prog,
}
for i, prog := range prog {
ctx.UseProgram(prog)
switch materialType(i) {
case materialTexture:
uTex := gl.GetUniformLocation(ctx, prog, "tex")
ctx.Uniform1i(uTex, 0)
c.vars[i].uUVScale = gl.GetUniformLocation(ctx, prog, "uvScale")
c.vars[i].uUVOffset = gl.GetUniformLocation(ctx, prog, "uvOffset")
case materialColor:
c.vars[i].uColor = gl.GetUniformLocation(ctx, prog, "color")
}
uCover := gl.GetUniformLocation(ctx, prog, "cover")
ctx.Uniform1i(uCover, 1)
c.vars[i].z = gl.GetUniformLocation(ctx, prog, "z")
c.vars[i].uScale = gl.GetUniformLocation(ctx, prog, "scale")
c.vars[i].uOffset = gl.GetUniformLocation(ctx, prog, "offset")
c.vars[i].uCoverUVScale = gl.GetUniformLocation(ctx, prog, "uvCoverScale")
c.vars[i].uCoverUVOffset = gl.GetUniformLocation(ctx, prog, "uvCoverOffset")
}
return c
}
func newStenciler(ctx *gl.Functions) *stenciler {
defFBO := gl.Framebuffer(ctx.GetInteger(gl.FRAMEBUFFER_BINDING))
prog, err := gl.CreateProgram(ctx, stencilVSrc, stencilFSrc, pathAttribs)
if err != nil {
panic(err)
}
uAreaLUT := gl.GetUniformLocation(ctx, prog, "areaLUT")
ctx.UseProgram(prog)
ctx.Uniform1i(uAreaLUT, 0)
areaLUT, err := loadLUT(ctx, genAreaLUT(256, 256))
if err != nil {
panic(err)
}
iprog, err := gl.CreateProgram(ctx, intersectVSrc, intersectFSrc, intersectAttribs)
if err != nil {
panic(err)
}
coverLoc := gl.GetUniformLocation(ctx, iprog, "cover")
ctx.UseProgram(iprog)
ctx.Uniform1i(coverLoc, 0)
return &stenciler{
ctx: ctx,
defFBO: defFBO,
prog: prog,
iprog: iprog,
areaLUT: areaLUT,
uScale: gl.GetUniformLocation(ctx, prog, "scale"),
uOffset: gl.GetUniformLocation(ctx, prog, "offset"),
uPathOffset: gl.GetUniformLocation(ctx, prog, "pathOffset"),
uIntersectUVScale: gl.GetUniformLocation(ctx, iprog, "uvScale"),
uIntersectUVOffset: gl.GetUniformLocation(ctx, iprog, "uvOffset"),
indexBuf: ctx.CreateBuffer(),
}
}
func (s *fboSet) resize(ctx *gl.Functions, sizes []image.Point, internalFormat int, format, ty gl.Enum) {
// Add fbos.
for i := len(s.fbos); i < len(sizes); i++ {
tex := ctx.CreateTexture()
ctx.BindTexture(gl.TEXTURE_2D, tex)
ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
fbo := ctx.CreateFramebuffer()
s.fbos = append(s.fbos, stencilFBO{
fbo: fbo,
tex: tex,
})
}
// Resize fbos.
for i, sz := range sizes {
f := &s.fbos[i]
// Resizing or recreating FBOs can introduce rendering stalls.
// Avoid if the space waste is not too high.
resize := sz.X > f.size.X || sz.Y > f.size.Y
waste := float32(sz.X*sz.Y) / float32(f.size.X*f.size.Y)
resize = resize || waste > 1.2
if resize {
f.size = sz
ctx.BindTexture(gl.TEXTURE_2D, f.tex)
ctx.TexImage2D(gl.TEXTURE_2D, 0, internalFormat, sz.X, sz.Y, format, ty, nil)
ctx.BindFramebuffer(gl.FRAMEBUFFER, f.fbo)
ctx.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, f.tex, 0)
}
}
// Delete extra fbos.
s.delete(ctx, len(sizes))
}
func (s *fboSet) invalidate(ctx *gl.Functions) {
for _, f := range s.fbos {
ctx.BindFramebuffer(gl.FRAMEBUFFER, f.fbo)
ctx.InvalidateFramebuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0)
}
}
func (s *fboSet) delete(ctx *gl.Functions, idx int) {
for i := idx; i < len(s.fbos); i++ {
f := s.fbos[i]
ctx.DeleteFramebuffer(f.fbo)
ctx.DeleteTexture(f.tex)
}
s.fbos = s.fbos[:idx]
}
func (s *stenciler) release() {
s.fbos.delete(s.ctx, 0)
s.ctx.DeleteTexture(s.areaLUT)
s.ctx.DeleteProgram(s.prog)
s.ctx.DeleteBuffer(s.indexBuf)
}
func (p *pather) release() {
p.stenciler.release()
p.coverer.release()
}
func (c *coverer) release() {
for _, p := range c.prog {
c.ctx.DeleteProgram(p)
}
}
func buildPath(ctx *gl.Functions, p *path.Path) *pathData {
buf := ctx.CreateBuffer()
ctx.BindBuffer(gl.ARRAY_BUFFER, buf)
ctx.BufferData(gl.ARRAY_BUFFER, gl.BytesView(p.Vertices), gl.STATIC_DRAW)
return &pathData{
ncurves: len(p.Vertices),
data: buf,
}
}
func (p *pathData) release(ctx *gl.Functions) {
ctx.DeleteBuffer(p.data)
}
func (p *pather) begin(sizes []image.Point) {
p.stenciler.begin(sizes)
}
func (p *pather) end() {
p.stenciler.end()
}
func (p *pather) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data *pathData) {
p.stenciler.stencilPath(bounds, offset, uv, data)
}
func (s *stenciler) beginIntersect(sizes []image.Point) {
s.ctx.ActiveTexture(gl.TEXTURE1)
s.ctx.BindTexture(gl.TEXTURE_2D, 0)
s.ctx.ActiveTexture(gl.TEXTURE0)
s.ctx.BlendFunc(gl.DST_COLOR, gl.ZERO)
// 8 bit coverage is enough, but OpenGL ES only supports single channel
// floating point formats. Replace with GL_RGB+GL_UNSIGNED_BYTE if
// no floating point support is available.
s.intersections.resize(s.ctx, sizes, gl.R16F, gl.RED, gl.HALF_FLOAT)
s.ctx.ClearColor(1.0, 0.0, 0.0, 0.0)
s.ctx.UseProgram(s.iprog)
}
func (s *stenciler) endIntersect() {
s.ctx.BindFramebuffer(gl.FRAMEBUFFER, s.defFBO)
}
func (s *stenciler) invalidateFBO() {
s.intersections.invalidate(s.ctx)
s.fbos.invalidate(s.ctx)
s.ctx.BindFramebuffer(gl.FRAMEBUFFER, s.defFBO)
}
func (s *stenciler) cover(idx int) stencilFBO {
return s.fbos.fbos[idx]
}
func (s *stenciler) begin(sizes []image.Point) {
s.ctx.ActiveTexture(gl.TEXTURE1)
s.ctx.BindTexture(gl.TEXTURE_2D, 0)
s.ctx.ActiveTexture(gl.TEXTURE0)
s.ctx.BlendFunc(gl.ONE, gl.ONE)
s.fbos.resize(s.ctx, sizes, gl.R16F, gl.RED, gl.HALF_FLOAT)
s.ctx.ClearColor(0.0, 0.0, 0.0, 0.0)
s.ctx.BindTexture(gl.TEXTURE_2D, s.areaLUT)
s.ctx.UseProgram(s.prog)
s.ctx.EnableVertexAttribArray(attribPathCorner)
s.ctx.EnableVertexAttribArray(attribPathMaxY)
s.ctx.EnableVertexAttribArray(attribPathFrom)
s.ctx.EnableVertexAttribArray(attribPathCtrl)
s.ctx.EnableVertexAttribArray(attribPathTo)
s.ctx.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, s.indexBuf)
}
func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data *pathData) {
s.ctx.BindBuffer(gl.ARRAY_BUFFER, data.data)
s.ctx.Viewport(uv.X, uv.Y, bounds.Dx(), bounds.Dy())
// Transform UI coordinates to OpenGL coordinates.
texSize := f32.Point{X: float32(bounds.Dx()), Y: float32(bounds.Dy())}
scale := f32.Point{X: 2 / texSize.X, Y: 2 / texSize.Y}
orig := f32.Point{X: -1 - float32(bounds.Min.X)*2/texSize.X, Y: -1 - float32(bounds.Min.Y)*2/texSize.Y}
s.ctx.Uniform2f(s.uScale, scale.X, scale.Y)
s.ctx.Uniform2f(s.uOffset, orig.X, orig.Y)
s.ctx.Uniform2f(s.uPathOffset, offset.X, offset.Y)
// Draw in batches that fit in uint16 indices.
start := 0
nquads := data.ncurves / 4
for start < nquads {
batch := nquads - start
if max := int(^uint16(0)) / 6; batch > max {
batch = max
}
// Enlarge VBO if necessary.
if batch > s.indexBufQuads {
indices := make([]uint16, batch*6)
for i := 0; i < batch; i++ {
i := uint16(i)
indices[i*6+0] = i*4 + 0
indices[i*6+1] = i*4 + 1
indices[i*6+2] = i*4 + 2
indices[i*6+3] = i*4 + 2
indices[i*6+4] = i*4 + 1
indices[i*6+5] = i*4 + 3
}
s.ctx.BufferData(gl.ELEMENT_ARRAY_BUFFER, gl.BytesView(indices), gl.STATIC_DRAW)
s.indexBufQuads = batch
}
off := path.VertStride * start * 4
s.ctx.VertexAttribPointer(attribPathCorner, 2, gl.SHORT, false, path.VertStride, off+int(unsafe.Offsetof((*(*path.Vertex)(nil)).CornerX)))
s.ctx.VertexAttribPointer(attribPathMaxY, 1, gl.FLOAT, false, path.VertStride, off+int(unsafe.Offsetof((*(*path.Vertex)(nil)).MaxY)))
s.ctx.VertexAttribPointer(attribPathFrom, 2, gl.FLOAT, false, path.VertStride, off+int(unsafe.Offsetof((*(*path.Vertex)(nil)).FromX)))
s.ctx.VertexAttribPointer(attribPathCtrl, 2, gl.FLOAT, false, path.VertStride, off+int(unsafe.Offsetof((*(*path.Vertex)(nil)).CtrlX)))
s.ctx.VertexAttribPointer(attribPathTo, 2, gl.FLOAT, false, path.VertStride, off+int(unsafe.Offsetof((*(*path.Vertex)(nil)).ToX)))
s.ctx.DrawElements(gl.TRIANGLES, batch*6, gl.UNSIGNED_SHORT, 0)
start += batch
}
}
func (s *stenciler) end() {
s.ctx.DisableVertexAttribArray(attribPathCorner)
s.ctx.DisableVertexAttribArray(attribPathMaxY)
s.ctx.DisableVertexAttribArray(attribPathFrom)
s.ctx.DisableVertexAttribArray(attribPathCtrl)
s.ctx.DisableVertexAttribArray(attribPathTo)
s.ctx.BindFramebuffer(gl.FRAMEBUFFER, s.defFBO)
}
func (p *pather) cover(z float32, mat materialType, col [4]float32, scale, off, uvScale, uvOff, coverScale, coverOff f32.Point) {
p.coverer.cover(z, mat, col, scale, off, uvScale, uvOff, coverScale, coverOff)
}
func (c *coverer) cover(z float32, mat materialType, col [4]float32, scale, off, uvScale, uvOff, coverScale, coverOff f32.Point) {
c.ctx.UseProgram(c.prog[mat])
switch mat {
case materialColor:
c.ctx.Uniform4f(c.vars[mat].uColor, col[0], col[1], col[2], col[3])
case materialTexture:
c.ctx.Uniform2f(c.vars[mat].uUVScale, uvScale.X, uvScale.Y)
c.ctx.Uniform2f(c.vars[mat].uUVOffset, uvOff.X, uvOff.Y)
}
c.ctx.Uniform1f(c.vars[mat].z, z)
c.ctx.Uniform2f(c.vars[mat].uScale, scale.X, scale.Y)
c.ctx.Uniform2f(c.vars[mat].uOffset, off.X, off.Y)
c.ctx.Uniform2f(c.vars[mat].uCoverUVScale, coverScale.X, coverScale.Y)
c.ctx.Uniform2f(c.vars[mat].uCoverUVOffset, coverOff.X, coverOff.Y)
c.ctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
}
func loadLUT(ctx *gl.Functions, lut *image.Gray) (gl.Texture, error) {
tex := ctx.CreateTexture()
ctx.BindTexture(gl.TEXTURE_2D, tex)
ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
ctx.PixelStorei(gl.UNPACK_ALIGNMENT, 1)
if lut.Stride != lut.Bounds().Dx() {
panic("unsupported LUT stride")
}
ver, _ := gl.ParseGLVersion(ctx.GetString(gl.VERSION))
intf, f := gl.R8, gl.RED
if ver[0] < 3 {
// R8, RED not supported on OpenGL ES 2.0.
intf, f = gl.LUMINANCE, gl.LUMINANCE
}
ctx.TexImage2D(gl.TEXTURE_2D, 0, intf, lut.Bounds().Dx(), lut.Bounds().Dy(), gl.Enum(f), gl.UNSIGNED_BYTE, lut.Pix)
ctx.PixelStorei(gl.UNPACK_ALIGNMENT, 4)
return tex, nil
}
const stencilVSrc = `
#version 100
precision highp float;
uniform vec2 scale;
uniform vec2 offset;
uniform vec2 pathOffset;
attribute vec2 corner;
attribute float maxy;
attribute vec2 from;
attribute vec2 ctrl;
attribute vec2 to;
varying vec2 vFrom;
varying vec2 vCtrl;
varying vec2 vTo;
void main() {
// Add a one pixel overlap so curve quads cover their
// entire curves. Could use conservative rasterization
// if available.
vec2 from = from + pathOffset;
vec2 ctrl = ctrl + pathOffset;
vec2 to = to + pathOffset;
float maxy = maxy + pathOffset.y;
vec2 pos;
if (corner.x > 0.0) {
// East.
pos.x = max(max(from.x, ctrl.x), to.x)+1.0;
} else {
// West.
pos.x = min(min(from.x, ctrl.x), to.x)-1.0;
}
if (corner.y > 0.0) {
// North.
pos.y = maxy + 1.0;
} else {
// South.
pos.y = min(min(from.y, ctrl.y), to.y) - 1.0;
}
vFrom = from-pos;
vCtrl = ctrl-pos;
vTo = to-pos;
pos *= scale;
pos += offset;
gl_Position = vec4(pos, 1, 1);
}
`
const stencilFSrc = `
#version 100
precision mediump float;
varying vec2 vFrom;
varying vec2 vCtrl;
varying vec2 vTo;
uniform sampler2D areaLUT;
void main() {
float dx = vTo.x - vFrom.x;
// Sort from and to in increasing order so the root below
// is always the positive square root, if any.
// We need the direction of the curve below, so this can't be
// done from the vertex shader.
bool increasing = vTo.x >= vFrom.x;
vec2 left = increasing ? vFrom : vTo;
vec2 right = increasing ? vTo : vFrom;
// The signed horizontal extent of the fragment.
vec2 extent = clamp(vec2(vFrom.x, vTo.x), -0.5, 0.5);
// Find the t where the curve crosses the middle of the
// extent, x₀.
// Given the bezier curve with x coordinates P₀, P₁, P₂
// where P₀ is at the origin, its x coordinate in t
// is given by:
//
// x(t) = 2(1-t)tP₁ + t²P₂
//
// Rearranging:
//
// x(t) = (P₂ - 2P₁)t² + 2P₁t
//
// Setting x(t) = x₀ and using Muller's quadratic formula ("Citardauq")
// for robustnesss,
//
// t = 2x₀/(2P₁±√(4P₁²+4(P₂-2P₁)x₀))
//
// which simplifies to
//
// t = x₀/(P₁±√(P₁²+(P₂-2P₁)x₀))
//
// Setting v = P₂-P₁,
//
// t = x₀/(P₁±√(P₁²+(v-P₁)x₀))
//
// t lie in [0; 1]; P₂ ≥ P₁ and P₁ ≥ 0 since we split curves where
// the control point lies before the start point or after the end point.
// It can then be shown that only the positive square root is valid.
float midx = mix(extent.x, extent.y, 0.5);
float x0 = midx - left.x;
vec2 p1 = vCtrl - left;
vec2 v = right - vCtrl;
float t = x0/(p1.x+sqrt(p1.x*p1.x+(v.x-p1.x)*x0));
// Find y(t) on the curve.
float y = mix(mix(left.y, vCtrl.y, t), mix(vCtrl.y, right.y, t), t);
// And the slope.
vec2 d_half = mix(p1, v, t);
float dy = d_half.y/d_half.x;
// Together, y and dy form a line approximation. The areaLUT table
// maps the line to a pixel coverage.
float width = extent.y - extent.x;
// The first axis maps y in [-8;+8] to [0;1].
float areau = y/16.0 + 0.5;
// The second axis maps slopes in [0;16] to [0;1]. The area is symmetric
// around dy = 0. Scale slope with extent width.
float areav = abs(dy*width)/16.0;
// Look up coverage from y and slope and scale to extent.
gl_FragColor.r = texture2D(areaLUT, vec2(areau, areav)).r*width;
}
`
const coverVSrc = `
#version 100
precision highp float;
uniform float z;
uniform vec2 scale;
uniform vec2 offset;
uniform vec2 uvScale;
uniform vec2 uvOffset;
uniform vec2 uvCoverScale;
uniform vec2 uvCoverOffset;
attribute vec2 pos;
varying vec2 vCoverUV;
attribute vec2 uv;
varying vec2 vUV;
void main() {
gl_Position = vec4(pos*scale + offset, z, 1);
vUV = uv*uvScale + uvOffset;
vCoverUV = uv*uvCoverScale+uvCoverOffset;
}
`
const coverFSrc = `
#version 100
precision mediump float;
// Use high precision to be pixel accurate for
// large cover atlases.
varying highp vec2 vCoverUV;
uniform sampler2D cover;
varying vec2 vUV;
HEADER
void main() {
gl_FragColor = GET_COLOR;
float cover = abs(texture2D(cover, vCoverUV).r);
gl_FragColor *= cover;
}
`
const intersectVSrc = `
#version 100
precision highp float;
attribute vec2 pos;
attribute vec2 uv;
uniform vec2 uvScale;
uniform vec2 uvOffset;
varying vec2 vUV;
void main() {
vec2 p = pos;
p.y = -p.y;
gl_Position = vec4(p, 0, 1);
vUV = uv*uvScale + uvOffset;
}
`
const intersectFSrc = `
#version 100
precision mediump float;
// Use high precision to be pixel accurate for
// large cover atlases.
varying highp vec2 vUV;
uniform sampler2D cover;
void main() {
float cover = abs(texture2D(cover, vUV).r);
gl_FragColor.r = cover;
}
`
+93
View File
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
import (
"time"
"gioui.org/ui/app/internal/gl"
)
type timers struct {
ctx *gl.Functions
timers []*timer
}
type timer struct {
Elapsed time.Duration
ctx *gl.Functions
obj gl.Query
state timerState
}
type timerState uint8
const (
timerIdle timerState = iota
timerRunning
timerWaiting
)
func newTimers(ctx *gl.Functions, exts string) *timers {
return &timers{
ctx: ctx,
}
}
func (t *timers) newTimer() *timer {
if t == nil {
return nil
}
tt := &timer{
ctx: t.ctx,
obj: t.ctx.CreateQuery(),
}
t.timers = append(t.timers, tt)
return tt
}
func (t *timer) begin() {
if t == nil || t.state != timerIdle {
return
}
t.ctx.BeginQuery(gl.TIME_ELAPSED_EXT, t.obj)
t.state = timerRunning
}
func (t *timer) end() {
if t == nil || t.state != timerRunning {
return
}
t.ctx.EndQuery(gl.TIME_ELAPSED_EXT)
t.state = timerWaiting
}
func (t *timers) ready() bool {
if t == nil {
return false
}
for _, tt := range t.timers {
if tt.state != timerWaiting {
return false
}
if t.ctx.GetQueryObjectuiv(tt.obj, gl.QUERY_RESULT_AVAILABLE) == 0 {
return false
}
}
for _, tt := range t.timers {
tt.state = timerIdle
nanos := t.ctx.GetQueryObjectuiv(tt.obj, gl.QUERY_RESULT)
tt.Elapsed = time.Duration(nanos)
}
return t.ctx.GetInteger(gl.GPU_DISJOINT_EXT) == 0
}
func (t *timers) release() {
if t == nil {
return
}
for _, tt := range t.timers {
t.ctx.DeleteQuery(tt.obj)
}
t.timers = nil
}
+58
View File
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
/*
#cgo LDFLAGS: -llog
#include <stdlib.h>
#include <android/log.h>
*/
import "C"
import (
"bufio"
"log"
"os"
"runtime"
"syscall"
"unsafe"
)
func init() {
// Android's logcat already include timstamps.
log.SetFlags(log.Flags() &^ log.LstdFlags)
logFd(C.ANDROID_LOG_INFO, os.Stdout.Fd())
logFd(C.ANDROID_LOG_ERROR, os.Stderr.Fd())
}
func logFd(prio C.int, fd uintptr) {
r, w, err := os.Pipe()
if err != nil {
panic(err)
}
if err := syscall.Dup3(int(w.Fd()), int(fd), 0); err != nil {
panic(err)
}
go func() {
tag := C.CString("gio")
defer C.free(unsafe.Pointer(tag))
// 1024 is the truncation limit from android/log.h, plus a \n.
lineBuf := bufio.NewReaderSize(r, 1024)
// The buffer to pass to C, including the terminating '\0'.
buf := make([]byte, lineBuf.Size()+1)
cbuf := (*C.char)(unsafe.Pointer(&buf[0]))
for {
line, _, err := lineBuf.ReadLine()
if err != nil {
break
}
copy(buf, line)
buf[len(line)] = 0
C.__android_log_write(prio, tag, cbuf)
}
// The garbage collector doesn't know that w's fd was dup'ed.
// Avoid finalizing w, and thereby avoid its finalizer closing its fd.
runtime.KeepAlive(w)
}()
}
+133
View File
@@ -0,0 +1,133 @@
// SPDX-License-Identifier: Unlicense OR MIT
#include <jni.h>
#include "os_android.h"
#include "_cgo_export.h"
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserver) {
JNIEnv *env;
if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) {
return -1;
}
setJVM(vm);
jclass viewClass = (*env)->FindClass(env, "org/gioui/GioView");
if (viewClass == NULL) {
return -1;
}
static const JNINativeMethod methods[] = {
{
.name = "onCreateView",
.signature = "(Lorg/gioui/GioView;)J",
.fnPtr = onCreateView
},
{
.name = "onDestroyView",
.signature = "(J)V",
.fnPtr = onDestroyView
},
{
.name = "onStartView",
.signature = "(J)V",
.fnPtr = onStartView
},
{
.name = "onStopView",
.signature = "(J)V",
.fnPtr = onStopView
},
{
.name = "onSurfaceDestroyed",
.signature = "(J)V",
.fnPtr = onSurfaceDestroyed
},
{
.name = "onSurfaceChanged",
.signature = "(JLandroid/view/Surface;)V",
.fnPtr = onSurfaceChanged
},
{
.name = "onConfigurationChanged",
.signature = "(J)V",
.fnPtr = onConfigurationChanged
},
{
.name = "onLowMemory",
.signature = "()V",
.fnPtr = onLowMemory
},
{
.name = "onTouchEvent",
.signature = "(JIIIFFJ)V",
.fnPtr = onTouchEvent
},
{
.name = "onKeyEvent",
.signature = "(JIIJ)V",
.fnPtr = onKeyEvent
},
{
.name = "onFrameCallback",
.signature = "(JJ)V",
.fnPtr = onFrameCallback
}
};
if ((*env)->RegisterNatives(env, viewClass, methods, sizeof(methods)/sizeof(methods[0])) != 0) {
return -1;
}
return JNI_VERSION_1_6;
}
jint gio_jni_GetEnv(JavaVM *vm, JNIEnv **env, jint version) {
return (*vm)->GetEnv(vm, (void **)env, version);
}
jint gio_jni_AttachCurrentThread(JavaVM *vm, JNIEnv **p_env, void *thr_args) {
return (*vm)->AttachCurrentThread(vm, p_env, thr_args);
}
jint gio_jni_DetachCurrentThread(JavaVM *vm) {
return (*vm)->DetachCurrentThread(vm);
}
jobject gio_jni_NewGlobalRef(JNIEnv *env, jobject obj) {
return (*env)->NewGlobalRef(env, obj);
}
void gio_jni_DeleteGlobalRef(JNIEnv *env, jobject obj) {
(*env)->DeleteGlobalRef(env, obj);
}
jclass gio_jni_GetObjectClass(JNIEnv *env, jobject obj) {
return (*env)->GetObjectClass(env, obj);
}
jmethodID gio_jni_GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
return (*env)->GetMethodID(env, clazz, name, sig);
}
jmethodID gio_jni_GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
return (*env)->GetStaticMethodID(env, clazz, name, sig);
}
jint gio_jni_CallStaticIntMethodII(JNIEnv *env, jclass clazz, jmethodID methodID, jint a1, jint a2) {
return (*env)->CallStaticIntMethod(env, clazz, methodID, a1, a2);
}
jfloat gio_jni_CallFloatMethod(JNIEnv *env, jobject obj, jmethodID methodID) {
return (*env)->CallFloatMethod(env, obj, methodID);
}
jint gio_jni_CallIntMethod(JNIEnv *env, jobject obj, jmethodID methodID) {
return (*env)->CallIntMethod(env, obj, methodID);
}
void gio_jni_CallVoidMethod(JNIEnv *env, jobject obj, jmethodID methodID) {
(*env)->CallVoidMethod(env, obj, methodID);
}
void gio_jni_CallVoidMethod_J(JNIEnv *env, jobject obj, jmethodID methodID, jlong a1) {
(*env)->CallVoidMethod(env, obj, methodID, a1);
}
+384
View File
@@ -0,0 +1,384 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
/*
#cgo LDFLAGS: -landroid
#include <android/native_window_jni.h>
#include <android/configuration.h>
#include <android/keycodes.h>
#include <android/input.h>
#include <stdlib.h>
#include "os_android.h"
*/
import "C"
import (
"errors"
"fmt"
"image"
"runtime"
"runtime/debug"
"sync"
"time"
"unsafe"
"gioui.org/ui/f32"
"gioui.org/ui/key"
"gioui.org/ui/pointer"
"gioui.org/ui"
)
type window struct {
*Window
view C.jobject
dpi int
fontScale float32
stage Stage
started bool
mu sync.Mutex
win *C.ANativeWindow
animating bool
mgetDensity C.jmethodID
mgetFontScale C.jmethodID
mshowTextInput C.jmethodID
mhideTextInput C.jmethodID
mpostFrameCallback C.jmethodID
mpostFrameCallbackOnMainThread C.jmethodID
}
var theJVM *C.JavaVM
var views = make(map[C.jlong]*window)
func jniGetMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID {
m := C.CString(method)
defer C.free(unsafe.Pointer(m))
s := C.CString(sig)
defer C.free(unsafe.Pointer(s))
return C.gio_jni_GetMethodID(env, class, m, s)
}
func jniGetStaticMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID {
m := C.CString(method)
defer C.free(unsafe.Pointer(m))
s := C.CString(sig)
defer C.free(unsafe.Pointer(s))
return C.gio_jni_GetStaticMethodID(env, class, m, s)
}
//export setJVM
func setJVM(vm *C.JavaVM) {
theJVM = vm
}
//export onCreateView
func onCreateView(env *C.JNIEnv, class C.jclass, view C.jobject) C.jlong {
view = C.gio_jni_NewGlobalRef(env, view)
w := &window{
view: view,
mgetDensity: jniGetMethodID(env, class, "getDensity", "()I"),
mgetFontScale: jniGetMethodID(env, class, "getFontScale", "()F"),
mshowTextInput: jniGetMethodID(env, class, "showTextInput", "()V"),
mhideTextInput: jniGetMethodID(env, class, "hideTextInput", "()V"),
mpostFrameCallback: jniGetMethodID(env, class, "postFrameCallback", "()V"),
mpostFrameCallbackOnMainThread: jniGetMethodID(env, class, "postFrameCallbackOnMainThread", "()V"),
}
ow := newWindow(w)
w.Window = ow
handle := C.jlong(view)
views[handle] = w
w.loadConfig(env, class)
windows <- ow
w.setStage(StageInvisible)
return handle
}
//export onDestroyView
func onDestroyView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
w := views[handle]
delete(views, handle)
w.setStage(StageDead)
C.gio_jni_DeleteGlobalRef(env, w.view)
w.view = 0
}
//export onStopView
func onStopView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
w := views[handle]
w.started = false
w.setStage(StageInvisible)
}
//export onStartView
func onStartView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
w := views[handle]
w.started = true
if w.aNativeWindow() != nil {
w.setVisible()
}
}
//export onSurfaceDestroyed
func onSurfaceDestroyed(env *C.JNIEnv, class C.jclass, handle C.jlong) {
w := views[handle]
w.mu.Lock()
w.win = nil
w.mu.Unlock()
w.setStage(StageInvisible)
}
//export onSurfaceChanged
func onSurfaceChanged(env *C.JNIEnv, class C.jclass, handle C.jlong, surf C.jobject) {
w := views[handle]
w.mu.Lock()
w.win = C.ANativeWindow_fromSurface(env, surf)
w.mu.Unlock()
if w.started {
w.setVisible()
}
}
//export onLowMemory
func onLowMemory() {
runtime.GC()
debug.FreeOSMemory()
}
//export onConfigurationChanged
func onConfigurationChanged(env *C.JNIEnv, class C.jclass, view C.jlong) {
w := views[view]
w.loadConfig(env, class)
if w.stage >= StageVisible {
w.draw(true)
}
}
//export onFrameCallback
func onFrameCallback(env *C.JNIEnv, class C.jclass, view C.jlong, nanos C.jlong) {
w, exist := views[view]
if !exist {
return
}
if w.stage < StageVisible {
return
}
w.mu.Lock()
anim := w.animating
w.mu.Unlock()
if anim {
runInJVM(func(env *C.JNIEnv) {
C.gio_jni_CallVoidMethod(env, w.view, w.mpostFrameCallback)
})
w.draw(false)
}
}
func (w *window) setVisible() {
win := w.aNativeWindow()
width, height := C.ANativeWindow_getWidth(win), C.ANativeWindow_getHeight(win)
if width == 0 || height == 0 {
return
}
w.setStage(StageVisible)
w.draw(true)
}
func (w *window) setStage(stage Stage) {
if stage == w.stage {
return
}
w.stage = stage
w.event(ChangeStage{stage})
}
func (w *window) display() unsafe.Pointer {
return nil
}
func (w *window) nativeWindow(visID int) (unsafe.Pointer, int, int) {
win := w.aNativeWindow()
var width, height int
if win != nil {
if C.ANativeWindow_setBuffersGeometry(win, 0, 0, C.int32_t(visID)) != 0 {
panic(errors.New("ANativeWindow_setBuffersGeometry failed"))
}
w, h := C.ANativeWindow_getWidth(win), C.ANativeWindow_getHeight(win)
width, height = int(w), int(h)
}
return unsafe.Pointer(win), width, height
}
func (w *window) aNativeWindow() *C.ANativeWindow {
w.mu.Lock()
defer w.mu.Unlock()
return w.win
}
func (w *window) loadConfig(env *C.JNIEnv, class C.jclass) {
dpi := int(C.gio_jni_CallIntMethod(env, w.view, w.mgetDensity))
w.fontScale = float32(C.gio_jni_CallFloatMethod(env, w.view, w.mgetFontScale))
switch dpi {
case C.ACONFIGURATION_DENSITY_NONE,
C.ACONFIGURATION_DENSITY_DEFAULT,
C.ACONFIGURATION_DENSITY_ANY:
// Assume standard density.
w.dpi = C.ACONFIGURATION_DENSITY_MEDIUM
default:
w.dpi = int(dpi)
}
}
func (w *window) setAnimating(anim bool) {
w.mu.Lock()
w.animating = anim
w.mu.Unlock()
if anim {
runInJVM(func(env *C.JNIEnv) {
C.gio_jni_CallVoidMethod(env, w.view, w.mpostFrameCallbackOnMainThread)
})
}
}
func (w *window) draw(sync bool) {
win := w.aNativeWindow()
width, height := C.ANativeWindow_getWidth(win), C.ANativeWindow_getHeight(win)
if width == 0 || height == 0 {
return
}
ppdp := float32(w.dpi) * inchPrDp
w.event(Draw{
Size: image.Point{
X: int(width),
Y: int(height),
},
Config: &ui.Config{
PxPerDp: ppdp,
PxPerSp: w.fontScale * ppdp,
Now: time.Now(),
},
sync: sync,
})
}
type keyMapper func(devId, keyCode C.int32_t) rune
func runInJVM(f func(env *C.JNIEnv)) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var env *C.JNIEnv
var detach bool
if res := C.gio_jni_GetEnv(theJVM, &env, C.JNI_VERSION_1_6); res != C.JNI_OK {
if res != C.JNI_EDETACHED {
panic(fmt.Errorf("JNI GetEnv failed with error %d", res))
}
if C.gio_jni_AttachCurrentThread(theJVM, &env, nil) != C.JNI_OK {
panic(errors.New("runInJVM: AttachCurrentThread failed"))
}
detach = true
}
if detach {
defer func() {
C.gio_jni_DetachCurrentThread(theJVM)
}()
}
f(env)
}
func convertKeyCode(code C.jint) (rune, bool) {
var n rune
switch code {
case C.AKEYCODE_DPAD_UP:
n = key.NameUpArrow
case C.AKEYCODE_DPAD_DOWN:
n = key.NameDownArrow
case C.AKEYCODE_DPAD_LEFT:
n = key.NameLeftArrow
case C.AKEYCODE_DPAD_RIGHT:
n = key.NameRightArrow
case C.AKEYCODE_FORWARD_DEL:
n = key.NameDeleteForward
case C.AKEYCODE_DEL:
n = key.NameDeleteBackward
default:
return 0, false
}
return n, true
}
//export onKeyEvent
func onKeyEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, keyCode, r C.jint, t C.jlong) {
w := views[handle]
if n, ok := convertKeyCode(keyCode); ok {
w.event(key.Chord{Name: n})
}
if r != 0 {
w.event(key.Edit{Text: string(rune(r))})
}
}
//export onTouchEvent
func onTouchEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, action, pointerID, tool C.jint, x, y C.jfloat, t C.jlong) {
w := views[handle]
var typ pointer.Type
switch action {
case C.AMOTION_EVENT_ACTION_DOWN, C.AMOTION_EVENT_ACTION_POINTER_DOWN:
typ = pointer.Press
case C.AMOTION_EVENT_ACTION_UP, C.AMOTION_EVENT_ACTION_POINTER_UP:
typ = pointer.Release
case C.AMOTION_EVENT_ACTION_CANCEL:
typ = pointer.Cancel
case C.AMOTION_EVENT_ACTION_MOVE:
typ = pointer.Move
default:
return
}
var src pointer.Source
switch tool {
case C.AMOTION_EVENT_TOOL_TYPE_FINGER:
src = pointer.Touch
case C.AMOTION_EVENT_TOOL_TYPE_MOUSE:
src = pointer.Mouse
default:
return
}
w.event(pointer.Event{
Type: typ,
Source: src,
PointerID: pointer.ID(pointerID),
Time: time.Duration(t) * time.Millisecond,
Position: f32.Point{X: float32(x), Y: float32(y)},
})
}
func (w *window) setTextInput(s key.TextInputState) {
if w.view == 0 {
return
}
switch s {
case key.TextInputOpen:
runInJVM(func(env *C.JNIEnv) {
C.gio_jni_CallVoidMethod(env, w.view, w.mshowTextInput)
})
case key.TextInputClosed:
runInJVM(func(env *C.JNIEnv) {
C.gio_jni_CallVoidMethod(env, w.view, w.mhideTextInput)
})
}
}
func Main() {
// Android runs in c-shared mode where is never reached.
panic("unreachable")
}
func createWindow(opts WindowOptions) error {
return errors.New("createWindow not supported")
}
+16
View File
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: Unlicense OR MIT
__attribute__ ((visibility ("hidden"))) jint gio_jni_GetEnv(JavaVM *vm, JNIEnv **env, jint version);
__attribute__ ((visibility ("hidden"))) jint gio_jni_AttachCurrentThread(JavaVM *vm, JNIEnv **p_env, void *thr_args);
__attribute__ ((visibility ("hidden"))) jint gio_jni_DetachCurrentThread(JavaVM *vm);
__attribute__ ((visibility ("hidden"))) jobject gio_jni_NewGlobalRef(JNIEnv *env, jobject obj);
__attribute__ ((visibility ("hidden"))) void gio_jni_DeleteGlobalRef(JNIEnv *env, jobject obj);
__attribute__ ((visibility ("hidden"))) jclass gio_jni_GetObjectClass(JNIEnv *env, jobject obj);
__attribute__ ((visibility ("hidden"))) jmethodID gio_jni_GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
__attribute__ ((visibility ("hidden"))) jmethodID gio_jni_GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
__attribute__ ((visibility ("hidden"))) jint gio_jni_CallStaticIntMethodII(JNIEnv *env, jclass clazz, jmethodID methodID, jint a1, jint a2);
__attribute__ ((visibility ("hidden"))) jfloat gio_jni_CallFloatMethod(JNIEnv *env, jobject obj, jmethodID methodID);
__attribute__ ((visibility ("hidden"))) jint gio_jni_CallIntMethod(JNIEnv *env, jobject obj, jmethodID methodID);
__attribute__ ((visibility ("hidden"))) void gio_jni_CallVoidMethod(JNIEnv *env, jobject obj, jmethodID methodID);
__attribute__ ((visibility ("hidden"))) void gio_jni_CallVoidMethod_J(JNIEnv *env, jobject obj, jmethodID methodID, jlong a1);
+243
View File
@@ -0,0 +1,243 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build darwin,ios
package app
/*
#cgo CFLAGS: -fmodules -fobjc-arc -x objective-c
#include <CoreGraphics/CoreGraphics.h>
#include <UIKit/UIKit.h>
#include <stdint.h>
#include "os_ios.h"
*/
import "C"
import (
"image"
"runtime"
"runtime/debug"
"sync/atomic"
"time"
"gioui.org/ui/f32"
"gioui.org/ui/key"
"gioui.org/ui/pointer"
"gioui.org/ui"
)
type window struct {
view C.CFTypeRef
w *Window
layer C.CFTypeRef
visible atomic.Value
pointerMap []C.CFTypeRef
}
var layerFactory func() uintptr
var views = make(map[C.CFTypeRef]*window)
func init() {
// Darwin requires UI operations happen on the main thread only.
runtime.LockOSThread()
}
//export onCreate
func onCreate(view C.CFTypeRef) {
w := &window{
view: view,
}
ow := newWindow(w)
w.w = ow
w.visible.Store(false)
w.layer = C.CFTypeRef(layerFactory())
C.gio_addLayerToView(view, w.layer)
views[view] = w
windows <- ow
w.w.event(ChangeStage{StageInvisible})
}
//export onDraw
func onDraw(view C.CFTypeRef, dpi, sdpi, width, height C.CGFloat, sync C.int) {
if width == 0 || height == 0 {
return
}
w := views[view]
wasVisible := w.isVisible()
w.visible.Store(true)
C.gio_updateView(view, w.layer)
if !wasVisible {
w.w.event(ChangeStage{StageVisible})
}
isSync := false
if sync != 0 {
isSync = true
}
w.w.event(Draw{
Size: image.Point{
X: int(width + .5),
Y: int(height + .5),
},
Config: &ui.Config{
PxPerDp: float32(dpi) * inchPrDp,
PxPerSp: float32(sdpi) * inchPrDp,
Now: time.Now(),
},
sync: isSync,
})
}
//export onStop
func onStop(view C.CFTypeRef) {
w := views[view]
w.visible.Store(false)
w.w.event(ChangeStage{StageInvisible})
}
//export onDestroy
func onDestroy(view C.CFTypeRef) {
w := views[view]
delete(views, view)
w.w.event(ChangeStage{StageDead})
C.gio_removeLayer(w.layer)
C.CFRelease(w.layer)
w.layer = 0
w.view = 0
}
//export onLowMemory
func onLowMemory() {
runtime.GC()
debug.FreeOSMemory()
}
//export onUpArrow
func onUpArrow(view C.CFTypeRef) {
views[view].onKeyCommand(key.NameUpArrow)
}
//export onDownArrow
func onDownArrow(view C.CFTypeRef) {
views[view].onKeyCommand(key.NameDownArrow)
}
//export onLeftArrow
func onLeftArrow(view C.CFTypeRef) {
views[view].onKeyCommand(key.NameLeftArrow)
}
//export onRightArrow
func onRightArrow(view C.CFTypeRef) {
views[view].onKeyCommand(key.NameRightArrow)
}
//export onDeleteBackward
func onDeleteBackward(view C.CFTypeRef) {
views[view].onKeyCommand(key.NameDeleteBackward)
}
//export onText
func onText(view C.CFTypeRef, str *C.char) {
w := views[view]
w.w.event(key.Edit{
Text: C.GoString(str),
})
}
//export onTouch
func onTouch(last C.int, view, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) {
var typ pointer.Type
switch phase {
case C.UITouchPhaseBegan:
typ = pointer.Press
case C.UITouchPhaseMoved:
typ = pointer.Move
case C.UITouchPhaseEnded:
typ = pointer.Release
case C.UITouchPhaseCancelled:
typ = pointer.Cancel
default:
return
}
w := views[view]
t := time.Duration(float64(ti) * float64(time.Second))
p := f32.Point{X: float32(x), Y: float32(y)}
w.w.event(pointer.Event{
Type: typ,
Source: pointer.Touch,
PointerID: w.lookupTouch(last != 0, touchRef),
Position: p,
Time: t,
})
}
func (w *window) setAnimating(anim bool) {
if w.view == 0 {
return
}
var animi C.int
if anim {
animi = 1
}
C.gio_setAnimating(w.view, animi)
}
func (w *window) onKeyCommand(name rune) {
w.w.event(key.Chord{
Name: name,
})
}
// lookupTouch maps an UITouch pointer value to an index. If
// last is set, the map is cleared.
func (w *window) lookupTouch(last bool, touch C.CFTypeRef) pointer.ID {
id := -1
for i, ref := range w.pointerMap {
if ref == touch {
id = i
break
}
}
if id == -1 {
id = len(w.pointerMap)
w.pointerMap = append(w.pointerMap, touch)
}
if last {
w.pointerMap = w.pointerMap[:0]
}
return pointer.ID(id)
}
func (w *window) contextLayer() uintptr {
return uintptr(w.layer)
}
func (w *window) isVisible() bool {
return w.visible.Load().(bool)
}
func (w *window) setTextInput(s key.TextInputState) {
if w.view == 0 {
return
}
switch s {
case key.TextInputOpen:
C.gio_showTextInput(w.view)
case key.TextInputClosed:
C.gio_hideTextInput(w.view)
}
}
func createWindow(opts WindowOptions) error {
panic("unsupported")
}
func Main() {
// iOS runs in c-archive mode, so this is never reached.
panic("unreachable")
}
+8
View File
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: Unlicense OR MIT
__attribute__ ((visibility ("hidden"))) void gio_showTextInput(CFTypeRef viewRef);
__attribute__ ((visibility ("hidden"))) void gio_hideTextInput(CFTypeRef viewRef);
__attribute__ ((visibility ("hidden"))) void gio_addLayerToView(CFTypeRef viewRef, CFTypeRef layerRef);
__attribute__ ((visibility ("hidden"))) void gio_updateView(CFTypeRef viewRef, CFTypeRef layerRef);
__attribute__ ((visibility ("hidden"))) void gio_removeLayer(CFTypeRef layerRef);
__attribute__ ((visibility ("hidden"))) void gio_setAnimating(CFTypeRef viewRef, int anim);
+253
View File
@@ -0,0 +1,253 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build darwin,ios
@import UIKit;
#include <stdint.h>
#include "_cgo_export.h"
#include "os_ios.h"
#include "framework_ios.h"
@interface GioViewController : UIViewController
@property UIScreen *screen;
@end
@interface GioView: UIView <UIKeyInput>
- (void)setAnimating:(BOOL)anim;
@end
static void redraw(CFTypeRef viewRef, BOOL sync) {
UIView *v = (__bridge UIView *)viewRef;
CGFloat scale = v.layer.contentsScale;
// Use 163 as the standard ppi on iOS.
CGFloat dpi = 163*scale;
CGFloat sdpi = dpi;
if (@available(iOS 11.0, tvOS 11.0, *)) {
UIFontMetrics *metrics = [UIFontMetrics defaultMetrics];
sdpi = [metrics scaledValueForValue:sdpi];
}
onDraw(viewRef, dpi, sdpi, v.bounds.size.width*scale, v.bounds.size.height*scale, sync);
}
@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];
controller.screen = self.window.screen;
self.window.rootViewController = controller;
[self.window makeKeyAndVisible];
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application {
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
if (self.window.rootViewController.view != nil) {
onStop((__bridge CFTypeRef)self.window.rootViewController.view);
}
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
GioViewController *c = (GioViewController*)self.window.rootViewController;
if (c.view != nil) {
CFTypeRef viewRef = (__bridge CFTypeRef)c.view;
redraw(viewRef, YES);
}
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
}
- (void)applicationWillTerminate:(UIApplication *)application {
}
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
onLowMemory();
}
@end
@implementation GioViewController
- (void)loadView {
CGRect zeroFrame = CGRectMake(0, 0, 0, 0);
self.view = [[GioView alloc] initWithFrame:zeroFrame];
#ifndef TARGET_OS_TV
self.view.multipleTouchEnabled = YES;
#endif
self.view.contentScaleFactor = self.screen.nativeScale;
onCreate((__bridge CFTypeRef)self.view);
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
CFTypeRef viewRef = (__bridge CFTypeRef)self.view;
redraw(viewRef, YES);
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
CFTypeRef viewRef = (__bridge CFTypeRef)self.view;
onDestroy(viewRef);
}
- (void)viewDidLayoutSubviews {
redraw((__bridge CFTypeRef)self.view, YES);
}
@end
static void handleTouches(int last, UIView *view, NSSet<UITouch *> *touches, UIEvent *event) {
CGFloat scale = view.contentScaleFactor;
NSUInteger i = 0;
NSUInteger n = [touches count];
CFTypeRef viewRef = (__bridge CFTypeRef)view;
for (UITouch *touch in touches) {
CFTypeRef touchRef = (__bridge CFTypeRef)touch;
i++;
NSArray<UITouch *> *coalescedTouches = [event coalescedTouchesForTouch:touch];
NSUInteger j = 0;
NSUInteger m = [coalescedTouches count];
for (UITouch *coalescedTouch in [event coalescedTouchesForTouch:touch]) {
CGPoint loc = [coalescedTouch locationInView:view];
j++;
int lastTouch = last && i == n && j == m;
onTouch(lastTouch, viewRef, touchRef, touch.phase, loc.x*scale, loc.y*scale, [coalescedTouch timestamp]);
}
}
}
@implementation GioView
CADisplayLink *displayLink;
NSArray<UIKeyCommand *> *_keyCommands;
- (void)onFrameCallback:(CADisplayLink *)link {
redraw((__bridge CFTypeRef)self, NO);
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
__weak id weakSelf = self;
displayLink = [CADisplayLink displayLinkWithTarget:weakSelf selector:@selector(onFrameCallback:)];
displayLink.paused = YES;
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[displayLink addToRunLoop:runLoop forMode:[runLoop currentMode]];
}
return self;
}
- (void)dealloc {
[displayLink invalidate];
}
- (void)setAnimating:(BOOL)anim {
displayLink.paused = !anim;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
handleTouches(0, self, touches, event);
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
handleTouches(0, self, touches, event);
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
handleTouches(1, self, touches, event);
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
handleTouches(1, self, touches, event);
}
- (void)insertText:(NSString *)text {
onText((__bridge CFTypeRef)self, (char *)text.UTF8String);
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (BOOL)hasText {
return YES;
}
- (void)deleteBackward {
onDeleteBackward((__bridge CFTypeRef)self);
}
- (void)onUpArrow {
onUpArrow((__bridge CFTypeRef)self);
}
- (void)onDownArrow {
onDownArrow((__bridge CFTypeRef)self);
}
- (void)onLeftArrow {
onLeftArrow((__bridge CFTypeRef)self);
}
- (void)onRightArrow {
onRightArrow((__bridge CFTypeRef)self);
}
- (NSArray<UIKeyCommand *> *)keyCommands {
if (_keyCommands == nil) {
_keyCommands = @[
[UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow
modifierFlags:0
action:@selector(onUpArrow)],
[UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow
modifierFlags:0
action:@selector(onDownArrow)],
[UIKeyCommand keyCommandWithInput:UIKeyInputLeftArrow
modifierFlags:0
action:@selector(onLeftArrow)],
[UIKeyCommand keyCommandWithInput:UIKeyInputRightArrow
modifierFlags:0
action:@selector(onRightArrow)]
];
}
return _keyCommands;
}
@end
void gio_setAnimating(CFTypeRef viewRef, int anim) {
GioView *view = (__bridge GioView *)viewRef;
dispatch_async(dispatch_get_main_queue(), ^{
[view setAnimating:(anim ? YES : NO)];
});
}
void gio_showTextInput(CFTypeRef viewRef) {
UIView *view = (__bridge UIView *)viewRef;
dispatch_async(dispatch_get_main_queue(), ^{
[view becomeFirstResponder];
});
}
void gio_hideTextInput(CFTypeRef viewRef) {
UIView *view = (__bridge UIView *)viewRef;
dispatch_async(dispatch_get_main_queue(), ^{
[view resignFirstResponder];
});
}
void gio_addLayerToView(CFTypeRef viewRef, CFTypeRef layerRef) {
UIView *view = (__bridge UIView *)viewRef;
CALayer *layer = (__bridge CALayer *)layerRef;
[view.layer addSublayer:layer];
}
void gio_updateView(CFTypeRef viewRef, CFTypeRef layerRef) {
UIView *view = (__bridge UIView *)viewRef;
CAEAGLLayer *layer = (__bridge CAEAGLLayer *)layerRef;
layer.contentsScale = view.contentScaleFactor;
layer.bounds = view.bounds;
}
void gio_removeLayer(CFTypeRef layerRef) {
CALayer *layer = (__bridge CALayer *)layerRef;
[layer removeFromSuperlayer];
}
+254
View File
@@ -0,0 +1,254 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build darwin,!ios
package app
/*
#cgo CFLAGS: -DGL_SILENCE_DEPRECATION -Werror -fmodules -fobjc-arc -x objective-c
#include <AppKit/AppKit.h>
#include "os_macos.h"
*/
import "C"
import (
"errors"
"image"
"runtime"
"sync"
"time"
"unsafe"
"gioui.org/ui/f32"
"gioui.org/ui/key"
"gioui.org/ui/pointer"
"gioui.org/ui"
)
func init() {
// Darwin requires that UI operations happen on the main thread only.
runtime.LockOSThread()
}
type window struct {
view C.CFTypeRef
w *Window
stage Stage
}
// Only support one main window for now.
var singleWindow struct {
mu sync.Mutex
hasOpts bool
opts WindowOptions
}
var viewFactory func() uintptr
var views = make(map[C.CFTypeRef]*window)
func (w *window) contextView() C.CFTypeRef {
return w.view
}
func (w *window) setTextInput(s key.TextInputState) {}
func (w *window) setAnimating(anim bool) {
var animb C.BOOL
if anim {
animb = 1
}
C.gio_setAnimating(w.view, animb)
}
func (w *window) setStage(stage Stage) {
if stage == w.stage {
return
}
w.stage = stage
w.w.event(ChangeStage{stage})
}
//export gio_onFrameCallback
func gio_onFrameCallback(view C.CFTypeRef) {
w := views[view]
w.draw(false)
}
//export gio_onKeys
func gio_onKeys(view C.CFTypeRef, cstr *C.char, ti C.double, mods C.NSUInteger) {
str := C.GoString(cstr)
var kmods key.Modifiers
if mods&C.NSEventModifierFlagCommand != 0 {
kmods |= key.ModCommand
}
w := views[view]
for _, k := range str {
if n, ok := convertKey(k); ok {
w.w.event(key.Chord{Name: n, Modifiers: kmods})
}
}
}
//export gio_onText
func gio_onText(view C.CFTypeRef, cstr *C.char) {
str := C.GoString(cstr)
w := views[view]
w.w.event(key.Edit{Text: str})
}
//export gio_onMouse
func gio_onMouse(view C.CFTypeRef, cdir C.int, x, y, dx, dy C.CGFloat, ti C.double) {
var typ pointer.Type
switch cdir {
case C.GIO_MOUSE_MOVE:
typ = pointer.Move
case C.GIO_MOUSE_UP:
typ = pointer.Release
case C.GIO_MOUSE_DOWN:
typ = pointer.Press
default:
panic("invalid direction")
}
t := time.Duration(float64(ti)*float64(time.Second) + .5)
w := views[view]
w.w.event(pointer.Event{
Type: typ,
Source: pointer.Mouse,
Time: t,
Position: f32.Point{X: float32(x), Y: float32(y)},
Scroll: f32.Point{X: float32(dx), Y: float32(dy)},
})
}
//export gio_onDraw
func gio_onDraw(view C.CFTypeRef) {
w := views[view]
w.draw(true)
}
func (w *window) draw(sync bool) {
width, height := int(C.gio_viewWidth(w.view)+.5), int(C.gio_viewHeight(w.view)+.5)
if width == 0 || height == 0 {
return
}
cfg := getConfig()
cfg.Now = time.Now()
w.setStage(StageVisible)
w.w.event(Draw{
Size: image.Point{
X: width,
Y: height,
},
Config: *cfg,
sync: sync,
})
}
func getConfig() ui.Config {
ppdp := float32(C.gio_getPixelsPerDP())
ppdp *= monitorScale
if ppdp < minDensity {
ppdp = minDensity
}
return ui.Config{
PxPerDp: ppdp,
PxPerSp: ppdp,
}
}
//export gio_onTerminate
func gio_onTerminate(view C.CFTypeRef) {
w := views[view]
delete(views, view)
w.setStage(StageDead)
close(windows)
}
//export gio_onHide
func gio_onHide(view C.CFTypeRef) {
w := views[view]
w.setStage(StageInvisible)
}
//export gio_onShow
func gio_onShow(view C.CFTypeRef) {
w := views[view]
w.setStage(StageVisible)
}
//export gio_onCreate
func gio_onCreate(view C.CFTypeRef) {
w := &window{
view: view,
}
ow := newWindow(w)
w.w = ow
views[view] = w
windows <- ow
}
func createWindow(opts WindowOptions) error {
singleWindow.mu.Lock()
defer singleWindow.mu.Unlock()
if singleWindow.hasOpts {
panic("only one window supported")
}
singleWindow.opts = opts
singleWindow.hasOpts = true
return nil
}
func Main() {
view := C.CFTypeRef(viewFactory())
if view == 0 {
// TODO: return this error from CreateWindow.
panic(errors.New("CreateWindow: failed to create view"))
}
cfg := getConfig()
opts := singleWindow.opts
w := cfg.Pixels(opts.Width)
h := cfg.Pixels(opts.Height)
title := C.CString(opts.Title)
defer C.free(unsafe.Pointer(title))
C.gio_main(view, title, C.CGFloat(w), C.CGFloat(h))
}
func convertKey(k rune) (rune, bool) {
if '0' <= k && k <= '9' || 'A' <= k && k <= 'Z' {
return k, true
}
if 'a' <= k && k <= 'z' {
return k - 0x20, true
}
var n rune
switch k {
case 0x1b:
n = key.NameEscape
case C.NSLeftArrowFunctionKey:
n = key.NameLeftArrow
case C.NSRightArrowFunctionKey:
n = key.NameRightArrow
case C.NSUpArrowFunctionKey:
n = key.NameUpArrow
case C.NSDownArrowFunctionKey:
n = key.NameDownArrow
case 0xd:
n = key.NameReturn
case C.NSHomeFunctionKey:
n = key.NameHome
case C.NSEndFunctionKey:
n = key.NameEnd
case 0x7f:
n = key.NameDeleteBackward
case C.NSDeleteFunctionKey:
n = key.NameDeleteForward
case C.NSPageUpFunctionKey:
n = key.NamePageUp
case C.NSPageDownFunctionKey:
n = key.NamePageDown
default:
return 0, false
}
return n, true
}
+17
View File
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: Unlicense OR MIT
#ifndef _OS_MACOS_H
#define _OS_MACOS_H
#define GIO_MOUSE_MOVE 1
#define GIO_MOUSE_UP 2
#define GIO_MOUSE_DOWN 3
__attribute__ ((visibility ("hidden"))) void gio_main(CFTypeRef viewRef, const char *title, CGFloat width, CGFloat height);
__attribute__ ((visibility ("hidden"))) CGFloat gio_viewWidth(CFTypeRef viewRef);
__attribute__ ((visibility ("hidden"))) CGFloat gio_viewHeight(CFTypeRef viewRef);
__attribute__ ((visibility ("hidden"))) void gio_setAnimating(CFTypeRef viewRef, BOOL anim);
__attribute__ ((visibility ("hidden"))) void gio_updateDisplayLink(CFTypeRef viewRef, CGDirectDisplayID dispID);
__attribute__ ((visibility ("hidden"))) CGFloat gio_getPixelsPerDP();
#endif
+120
View File
@@ -0,0 +1,120 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build darwin,!ios
@import AppKit;
#include "os_macos.h"
#include "_cgo_export.h"
@interface GioDelegate : NSObject<NSApplicationDelegate, NSWindowDelegate>
@property (strong,nonatomic) NSWindow *window;
@end
@implementation GioDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[[NSRunningApplication currentApplication] activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)];
[self.window makeKeyAndOrderFront:self];
gio_onShow((__bridge CFTypeRef)self.window.contentView);
}
- (void)applicationDidHide:(NSNotification *)aNotification {
gio_onHide((__bridge CFTypeRef)self.window.contentView);
}
- (void)applicationWillUnhide:(NSNotification *)notification {
gio_onShow((__bridge CFTypeRef)self.window.contentView);
}
- (void)windowWillMiniaturize:(NSNotification *)notification {
gio_onHide((__bridge CFTypeRef)self.window.contentView);
}
- (void)windowDidDeminiaturize:(NSNotification *)notification {
gio_onShow((__bridge CFTypeRef)self.window.contentView);
}
- (void)windowDidChangeScreen:(NSNotification *)notification {
CGDirectDisplayID dispID = [[[self.window screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue];
gio_updateDisplayLink((__bridge CFTypeRef)self.window.contentView, dispID);
}
- (void)windowWillClose:(NSNotification *)notification {
gio_onTerminate((__bridge CFTypeRef)self.window.contentView);
self.window.delegate = nil;
[NSApp terminate:nil];
}
@end
CGFloat gio_viewHeight(CFTypeRef viewRef) {
NSView *view = (__bridge NSView *)viewRef;
NSRect bounds = [view convertRectToBacking:[view bounds]];
return bounds.size.height;
}
CGFloat gio_viewWidth(CFTypeRef viewRef) {
NSView *view = (__bridge NSView *)viewRef;
NSRect bounds = [view convertRectToBacking:[view bounds]];
return bounds.size.width;
}
// Points pr. dp.
static CGFloat getPointsPerDP(NSScreen *screen) {
NSDictionary *description = [screen deviceDescription];
NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue];
CGSize displayPhysicalSize = CGDisplayScreenSize([[description objectForKey:@"NSScreenNumber"] unsignedIntValue]);
return (25.4/160)*displayPixelSize.width / displayPhysicalSize.width;
}
// Pixels pr dp.
CGFloat gio_getPixelsPerDP() {
NSScreen *screen = [NSScreen mainScreen];
return [screen backingScaleFactor] * getPointsPerDP(screen);
}
void gio_main(CFTypeRef viewRef, const char *title, CGFloat width, CGFloat height) {
@autoreleasepool {
NSView *view = (NSView *)CFBridgingRelease(viewRef);
[NSApplication sharedApplication];
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
NSMenuItem *mainMenu = [NSMenuItem new];
NSMenu *menu = [NSMenu new];
NSMenuItem *hideMenuItem = [[NSMenuItem alloc] initWithTitle:@"Hide"
action:@selector(hide:)
keyEquivalent:@"h"];
[menu addItem:hideMenuItem];
NSMenuItem *quitMenuItem = [[NSMenuItem alloc] initWithTitle:@"Quit"
action:@selector(terminate:)
keyEquivalent:@"q"];
[menu addItem:quitMenuItem];
[mainMenu setSubmenu:menu];
NSMenu *menuBar = [NSMenu new];
[menuBar addItem:mainMenu];
[NSApp setMainMenu:menuBar];
// Width and height are in pixels; convert to points
CGFloat scale = [[NSScreen mainScreen] backingScaleFactor];
width /= scale;
height /= scale;
NSRect rect = NSMakeRect(0, 0, width, height);
NSWindowStyleMask styleMask = NSWindowStyleMaskTitled |
NSWindowStyleMaskResizable |
NSWindowStyleMaskMiniaturizable |
NSWindowStyleMaskClosable;
NSWindow* window = [[NSWindow alloc] initWithContentRect:rect
styleMask:styleMask
backing:NSBackingStoreBuffered
defer:NO];
window.title = [NSString stringWithUTF8String: title];
[window cascadeTopLeftFromPoint:NSMakePoint(20,20)];
[window setAcceptsMouseMovedEvents:YES];
[window setContentView:view];
[window makeFirstResponder:view];
GioDelegate *del = [[GioDelegate alloc] init];
del.window = window;
[window setDelegate:del];
[NSApp setDelegate:del];
gio_onCreate((__bridge CFTypeRef)view);
[NSApp run];
}
}
+140
View File
@@ -0,0 +1,140 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build linux,!android
#include <wayland-client.h>
#include "wayland_xdg_shell.h"
#include "wayland_text_input.h"
#include "os_wayland.h"
#include "_cgo_export.h"
static const struct wl_registry_listener registry_listener = {
// Cast away const parameter.
.global = (void (*)(void *, struct wl_registry *, uint32_t, const char *, uint32_t))gio_onRegistryGlobal,
.global_remove = gio_onRegistryGlobalRemove
};
void gio_wl_registry_add_listener(struct wl_registry *reg) {
wl_registry_add_listener(reg, &registry_listener, NULL);
}
static struct wl_surface_listener surface_listener = {.enter = gio_onSurfaceEnter, .leave = gio_onSurfaceLeave};
void gio_wl_surface_add_listener(struct wl_surface *surface) {
wl_surface_add_listener(surface, &surface_listener, NULL);
}
static const struct xdg_surface_listener xdg_surface_listener = {
.configure = gio_onXdgSurfaceConfigure,
};
void gio_xdg_surface_add_listener(struct xdg_surface *surface) {
xdg_surface_add_listener(surface, &xdg_surface_listener, NULL);
}
static const struct xdg_toplevel_listener xdg_toplevel_listener = {
.configure = gio_onToplevelConfigure,
.close = gio_onToplevelClose,
};
void gio_xdg_toplevel_add_listener(struct xdg_toplevel *toplevel) {
xdg_toplevel_add_listener(toplevel, &xdg_toplevel_listener, NULL);
}
static void xdg_wm_base_handle_ping(void *data, struct xdg_wm_base *wm, uint32_t serial) {
xdg_wm_base_pong(wm, serial);
}
static const struct xdg_wm_base_listener xdg_wm_base_listener = {
.ping = xdg_wm_base_handle_ping,
};
void gio_xdg_wm_base_add_listener(struct xdg_wm_base *wm) {
xdg_wm_base_add_listener(wm, &xdg_wm_base_listener, NULL);
}
static const struct wl_callback_listener wl_callback_listener = {
.done = gio_onFrameDone,
};
void gio_wl_callback_add_listener(struct wl_callback *callback, void *data) {
wl_callback_add_listener(callback, &wl_callback_listener, data);
}
static const struct wl_output_listener wl_output_listener = {
// Cast away const parameter.
.geometry = (void (*)(void *, struct wl_output *, int32_t, int32_t, int32_t, int32_t, int32_t, const char *, const char *, int32_t))gio_onOutputGeometry,
.mode = gio_onOutputMode,
.done = gio_onOutputDone,
.scale = gio_onOutputScale,
};
void gio_wl_output_add_listener(struct wl_output *output) {
wl_output_add_listener(output, &wl_output_listener, NULL);
}
static const struct wl_seat_listener wl_seat_listener = {
.capabilities = gio_onSeatCapabilities,
// Cast away const parameter.
.name = (void (*)(void *, struct wl_seat *, const char *))gio_onSeatName,
};
void gio_wl_seat_add_listener(struct wl_seat *seat) {
wl_seat_add_listener(seat, &wl_seat_listener, NULL);
}
static const struct wl_pointer_listener wl_pointer_listener = {
.enter = gio_onPointerEnter,
.leave = gio_onPointerLeave,
.motion = gio_onPointerMotion,
.button = gio_onPointerButton,
.axis = gio_onPointerAxis,
.frame = gio_onPointerFrame,
.axis_source = gio_onPointerAxisSource,
.axis_stop = gio_onPointerAxisStop,
.axis_discrete = gio_onPointerAxisDiscrete,
};
void gio_wl_pointer_add_listener(struct wl_pointer *pointer) {
wl_pointer_add_listener(pointer, &wl_pointer_listener, NULL);
}
static const struct wl_touch_listener wl_touch_listener = {
.down = gio_onTouchDown,
.up = gio_onTouchUp,
.motion = gio_onTouchMotion,
.frame = gio_onTouchFrame,
.cancel = gio_onTouchCancel,
.shape = gio_onTouchShape,
.orientation = gio_onTouchOrientation
};
void gio_wl_touch_add_listener(struct wl_touch *touch) {
wl_touch_add_listener(touch, &wl_touch_listener, NULL);
}
static const struct wl_keyboard_listener wl_keyboard_listener = {
.keymap = gio_onKeyboardKeymap,
.enter = gio_onKeyboardEnter,
.leave = gio_onKeyboardLeave,
.key = gio_onKeyboardKey,
.modifiers = gio_onKeyboardModifiers,
.repeat_info = gio_onKeyboardRepeatInfo
};
void gio_wl_keyboard_add_listener(struct wl_keyboard *keyboard) {
wl_keyboard_add_listener(keyboard, &wl_keyboard_listener, NULL);
}
static const struct zwp_text_input_v3_listener zwp_text_input_v3_listener = {
.enter = gio_onTextInputEnter,
.leave = gio_onTextInputLeave,
// Cast away const parameter.
.preedit_string = (void (*)(void *, struct zwp_text_input_v3 *, const char *, int32_t, int32_t))gio_onTextInputPreeditString,
.commit_string = (void (*)(void *, struct zwp_text_input_v3 *, const char *))gio_onTextInputCommitString,
.delete_surrounding_text = gio_onTextInputDeleteSurroundingText,
.done = gio_onTextInputDone
};
void gio_zwp_text_input_v3_add_listener(struct zwp_text_input_v3 *im) {
zwp_text_input_v3_add_listener(im, &zwp_text_input_v3_listener, NULL);
}
+1208
View File
File diff suppressed because it is too large Load Diff
+14
View File
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: Unlicense OR MIT
__attribute__ ((visibility ("hidden"))) void gio_wl_registry_add_listener(struct wl_registry *reg);
__attribute__ ((visibility ("hidden"))) void gio_wl_surface_add_listener(struct wl_surface *surface);
__attribute__ ((visibility ("hidden"))) void gio_xdg_surface_add_listener(struct xdg_surface *surface);
__attribute__ ((visibility ("hidden"))) void gio_xdg_toplevel_add_listener(struct xdg_toplevel *toplevel);
__attribute__ ((visibility ("hidden"))) void gio_xdg_wm_base_add_listener(struct xdg_wm_base *wm);
__attribute__ ((visibility ("hidden"))) void gio_wl_callback_add_listener(struct wl_callback *callback, void *data);
__attribute__ ((visibility ("hidden"))) void gio_wl_output_add_listener(struct wl_output *output);
__attribute__ ((visibility ("hidden"))) void gio_wl_seat_add_listener(struct wl_seat *seat);
__attribute__ ((visibility ("hidden"))) void gio_wl_pointer_add_listener(struct wl_pointer *pointer);
__attribute__ ((visibility ("hidden"))) void gio_wl_touch_add_listener(struct wl_touch *touch);
__attribute__ ((visibility ("hidden"))) void gio_wl_keyboard_add_listener(struct wl_keyboard *keyboard);
__attribute__ ((visibility ("hidden"))) void gio_zwp_text_input_v3_add_listener(struct zwp_text_input_v3 *im);
+718
View File
@@ -0,0 +1,718 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
import (
"fmt"
"image"
"runtime"
"sync"
"time"
"unicode"
"unsafe"
syscall "golang.org/x/sys/windows"
"gioui.org/ui/f32"
"gioui.org/ui/key"
"gioui.org/ui/pointer"
"gioui.org/ui"
)
var winMap = make(map[syscall.Handle]*window)
type rect struct {
left, top, right, bottom int32
}
type wndClassEx struct {
cbSize uint32
style uint32
lpfnWndProc uintptr
cnClsExtra int32
cbWndExtra int32
hInstance syscall.Handle
hIcon syscall.Handle
hCursor syscall.Handle
hbrBackground syscall.Handle
lpszMenuName *uint16
lpszClassName *uint16
hIconSm syscall.Handle
}
type msg struct {
hwnd syscall.Handle
message uint32
wParam uintptr
lParam uintptr
time uint32
pt point
lPrivate uint32
}
type point struct {
x, y int32
}
type window struct {
hwnd syscall.Handle
hdc syscall.Handle
w *Window
width int
height int
stage Stage
mu sync.Mutex
animating bool
}
const (
_CS_HREDRAW = 0x0002
_CS_VREDRAW = 0x0001
_CS_OWNDC = 0x0020
_CW_USEDEFAULT = -2147483648
_IDC_ARROW = 32512
_INFINITE = 0xFFFFFFFF
_LOGPIXELSX = 88
_SIZE_MAXIMIZED = 2
_SIZE_MINIMIZED = 1
_SIZE_RESTORED = 0
_SW_SHOWDEFAULT = 10
_USER_TIMER_MINIMUM = 0x0000000A
_VK_CONTROL = 0x11
_VK_BACK = 0x08
_VK_DELETE = 0x2e
_VK_DOWN = 0x28
_VK_END = 0x23
_VK_ESCAPE = 0x1b
_VK_HOME = 0x24
_VK_LEFT = 0x25
_VK_NEXT = 0x22
_VK_PRIOR = 0x21
_VK_RIGHT = 0x27
_VK_RETURN = 0x0d
_VK_UP = 0x26
_UNICODE_NOCHAR = 65535
_WM_CANCELMODE = 0x001F
_WM_CHAR = 0x0102
_WM_CREATE = 0x0001
_WM_DESTROY = 0x0002
_WM_KEYDOWN = 0x0100
_WM_KEYUP = 0x0101
_WM_LBUTTONDOWN = 0x0201
_WM_LBUTTONUP = 0x0202
_WM_MOUSEMOVE = 0x0200
_WM_MOUSEWHEEL = 0x020A
_WM_PAINT = 0x000F
_WM_QUIT = 0x0012
_WM_SHOWWINDOW = 0x0018
_WM_SIZE = 0x0005
_WM_SYSKEYDOWN = 0x0104
_WM_TIMER = 0x0113
_WM_UNICHAR = 0x0109
_WM_USER = 0x0400
_WS_CLIPCHILDREN = 0x00010000
_WS_CLIPSIBLINGS = 0x04000000
_WS_VISIBLE = 0x10000000
_WS_OVERLAPPED = 0x00000000
_WS_OVERLAPPEDWINDOW = _WS_OVERLAPPED | _WS_CAPTION | _WS_SYSMENU | _WS_THICKFRAME |
_WS_MINIMIZEBOX | _WS_MAXIMIZEBOX
_WS_CAPTION = 0x00C00000
_WS_SYSMENU = 0x00080000
_WS_THICKFRAME = 0x00040000
_WS_MINIMIZEBOX = 0x00020000
_WS_MAXIMIZEBOX = 0x00010000
_WS_EX_APPWINDOW = 0x00040000
_WS_EX_WINDOWEDGE = 0x00000100
_QS_ALLINPUT = 0x04FF
_MWMO_WAITALL = 0x0001
_MWMO_INPUTAVAILABLE = 0x0004
_WAIT_OBJECT_0 = 0
_PM_REMOVE = 0x0001
)
const _WM_REDRAW = _WM_USER + 0
var onceMu sync.Mutex
var mainDone = make(chan struct{})
func Main() {
<-mainDone
}
func createWindow(opts WindowOptions) error {
onceMu.Lock()
defer onceMu.Unlock()
if len(winMap) > 0 {
panic("multiple windows are not supported")
}
cerr := make(chan error, 1)
go func() {
// Call win32 API from a single OS thread.
runtime.LockOSThread()
w, err := createNativeWindow(opts)
if err != nil {
cerr <- err
return
}
defer w.destroy()
cerr <- nil
windows <- w.w
showWindow(w.hwnd, _SW_SHOWDEFAULT)
setForegroundWindow(w.hwnd)
setFocus(w.hwnd)
if err := w.loop(); err != nil {
panic(err)
}
close(windows)
close(mainDone)
}()
return <-cerr
}
func createNativeWindow(opts WindowOptions) (*window, error) {
setProcessDPIAware()
screenDC, err := getDC(0)
if err != nil {
return nil, err
}
cfg := configForDC(screenDC)
releaseDC(screenDC)
hInst, err := getModuleHandle()
if err != nil {
return nil, err
}
curs, err := loadCursor(_IDC_ARROW)
if err != nil {
return nil, err
}
wcls := wndClassEx{
cbSize: uint32(unsafe.Sizeof(wndClassEx{})),
style: _CS_HREDRAW | _CS_VREDRAW | _CS_OWNDC,
lpfnWndProc: syscall.NewCallback(windowProc),
hInstance: hInst,
hCursor: curs,
lpszClassName: syscall.StringToUTF16Ptr("GioWindow"),
}
cls, err := registerClassEx(&wcls)
if err != nil {
return nil, err
}
defer unregisterClass(cls, hInst)
wr := rect{
right: int32(cfg.Pixels(opts.Width) + .5),
bottom: int32(cfg.Pixels(opts.Height) + .5),
}
dwStyle := uint32(_WS_OVERLAPPEDWINDOW)
dwExStyle := uint32(_WS_EX_APPWINDOW | _WS_EX_WINDOWEDGE)
adjustWindowRectEx(&wr, dwStyle, 0, dwExStyle)
hwnd, err := createWindowEx(dwExStyle,
cls,
opts.Title,
dwStyle|_WS_CLIPSIBLINGS|_WS_CLIPCHILDREN,
_CW_USEDEFAULT, _CW_USEDEFAULT,
wr.right-wr.left,
wr.bottom-wr.top,
0,
0,
hInst,
0)
if err != nil {
return nil, err
}
w := &window{
hwnd: hwnd,
stage: StageInvisible,
}
winMap[hwnd] = w
w.hdc, err = getDC(hwnd)
if err != nil {
return nil, err
}
w.w = newWindow(w)
return w, nil
}
func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
w := winMap[hwnd]
switch msg {
case _WM_UNICHAR:
if wParam == _UNICODE_NOCHAR {
// Tell the system that we accept WM_UNICHAR messages.
return 1
}
fallthrough
case _WM_CHAR:
if r := rune(wParam); unicode.IsPrint(r) {
w.w.event(key.Edit{Text: string(r)})
}
// The message is processed.
return 1
case _WM_KEYDOWN, _WM_SYSKEYDOWN:
if n, ok := convertKeyCode(wParam); ok {
cmd := key.Chord{Name: n}
if getKeyState(_VK_CONTROL)&0x1000 != 0 {
cmd.Modifiers |= key.ModCommand
}
w.w.event(cmd)
}
case _WM_LBUTTONDOWN:
setCapture(w.hwnd)
x, y := coordsFromlParam(lParam)
p := f32.Point{X: float32(x), Y: float32(y)}
w.w.event(pointer.Event{
Type: pointer.Press,
Source: pointer.Mouse,
Position: p,
Time: getMessageTime(),
})
case _WM_CANCELMODE:
w.w.event(pointer.Event{
Type: pointer.Cancel,
})
case _WM_LBUTTONUP:
releaseCapture()
x, y := coordsFromlParam(lParam)
p := f32.Point{X: float32(x), Y: float32(y)}
w.w.event(pointer.Event{
Type: pointer.Release,
Source: pointer.Mouse,
Position: p,
Time: getMessageTime(),
})
case _WM_MOUSEMOVE:
x, y := coordsFromlParam(lParam)
p := f32.Point{X: float32(x), Y: float32(y)}
w.w.event(pointer.Event{
Type: pointer.Move,
Source: pointer.Mouse,
Position: p,
Time: getMessageTime(),
})
case _WM_MOUSEWHEEL:
w.scrollEvent(wParam, lParam)
case _WM_DESTROY:
delete(winMap, hwnd)
w.setStage(StageDead)
case _WM_REDRAW:
w.mu.Lock()
anim := w.animating
w.mu.Unlock()
if anim {
w.draw(false)
w.postRedraw()
}
case _WM_PAINT:
w.draw(true)
case _WM_SIZE:
switch wParam {
case _SIZE_MINIMIZED:
w.setStage(StageInvisible)
case _SIZE_MAXIMIZED, _SIZE_RESTORED:
w.setStage(StageVisible)
w.draw(true)
}
}
return defWindowProc(hwnd, msg, wParam, lParam)
}
func coordsFromlParam(lParam uintptr) (int, int) {
x := int(int16(lParam & 0xffff))
y := int(int16((lParam >> 16) & 0xffff))
return x, y
}
func (w *window) scrollEvent(wParam, lParam uintptr) {
x, y := coordsFromlParam(lParam)
// The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast
// to other mouse events.
np := point{x: int32(x), y: int32(y)}
screenToClient(w.hwnd, &np)
p := f32.Point{X: float32(np.x), Y: float32(np.y)}
dist := float32(int16(wParam >> 16))
w.w.event(pointer.Event{
Type: pointer.Move,
Source: pointer.Mouse,
Position: p,
Scroll: f32.Point{Y: -dist},
Time: getMessageTime(),
})
}
// Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/
func (w *window) loop() error {
loop:
for w.stage > StageDead {
var msg msg
// Since posted messages are always returned before system messages,
// but we want our WM_REDRAW to always come last, just like WM_PAINT.
// So peek for system messages first, and fall back to processing
// all messages.
if !peekMessage(&msg, w.hwnd, 0, _WM_REDRAW-1, _PM_REMOVE) {
getMessage(&msg, w.hwnd, 0, 0)
}
// Clear queue of all other redraws.
if msg.message == _WM_REDRAW {
for peekMessage(&msg, w.hwnd, _WM_REDRAW, _WM_REDRAW, _PM_REMOVE) {
}
}
if msg.message == _WM_QUIT {
postQuitMessage(msg.wParam)
break loop
}
translateMessage(&msg)
dispatchMessage(&msg)
}
return nil
}
func (w *window) setAnimating(anim bool) {
w.mu.Lock()
w.animating = anim
w.mu.Unlock()
if anim {
w.postRedraw()
}
}
func (w *window) postRedraw() {
if err := postMessage(w.hwnd, _WM_REDRAW, 0, 0); err != nil {
panic(err)
}
}
func (w *window) setStage(s Stage) {
w.stage = s
w.w.event(ChangeStage{s})
}
func (w *window) draw(sync bool) {
var r rect
getClientRect(w.hwnd, &r)
w.width = int(r.right - r.left)
w.height = int(r.bottom - r.top)
cfg := configForDC(w.hdc)
cfg.Now = time.Now()
w.w.event(Draw{
Size: image.Point{
X: w.width,
Y: w.height,
},
Config: cfg,
sync: sync,
})
}
func (w *window) destroy() {
if w.hdc != 0 {
releaseDC(w.hdc)
w.hdc = 0
}
if w.hwnd != 0 {
destroyWindow(w.hwnd)
w.hwnd = 0
}
}
func (w *window) setTextInput(s key.TextInputState) {}
func (w *window) display() uintptr {
return uintptr(w.hdc)
}
func (w *window) nativeWindow(visID int) (uintptr, int, int) {
return uintptr(w.hwnd), w.width, w.height
}
func convertKeyCode(code uintptr) (rune, bool) {
if '0' <= code && code <= '9' || 'A' <= code && code <= 'Z' {
return rune(code), true
}
var r rune
switch code {
case _VK_ESCAPE:
r = key.NameEscape
case _VK_LEFT:
r = key.NameLeftArrow
case _VK_RIGHT:
r = key.NameRightArrow
case _VK_RETURN:
r = key.NameReturn
case _VK_UP:
r = key.NameUpArrow
case _VK_DOWN:
r = key.NameDownArrow
case _VK_HOME:
r = key.NameHome
case _VK_END:
r = key.NameEnd
case _VK_BACK:
r = key.NameDeleteBackward
case _VK_DELETE:
r = key.NameDeleteForward
case _VK_PRIOR:
r = key.NamePageUp
case _VK_NEXT:
r = key.NamePageDown
default:
return 0, false
}
return r, true
}
func configForDC(hdc syscall.Handle) *ui.Config {
dpi := getDeviceCaps(hdc, _LOGPIXELSX)
ppdp := float32(dpi) * inchPrDp * monitorScale
// Force a minimum density to keep text legible and to handle bogus output geometry.
if ppdp < minDensity {
ppdp = minDensity
}
return &ui.Config{
PxPerDp: ppdp,
PxPerSp: ppdp,
}
}
var (
kernel32 = syscall.NewLazySystemDLL("kernel32.dll")
_GetModuleHandleW = kernel32.NewProc("GetModuleHandleW")
user32 = syscall.NewLazySystemDLL("user32.dll")
_AdjustWindowRectEx = user32.NewProc("AdjustWindowRectEx")
_CallMsgFilter = user32.NewProc("CallMsgFilterW")
_CreateWindowEx = user32.NewProc("CreateWindowExW")
_DefWindowProc = user32.NewProc("DefWindowProcW")
_DestroyWindow = user32.NewProc("DestroyWindow")
_DispatchMessage = user32.NewProc("DispatchMessageW")
_GetClientRect = user32.NewProc("GetClientRect")
_GetDC = user32.NewProc("GetDC")
_GetKeyState = user32.NewProc("GetKeyState")
_GetMessage = user32.NewProc("GetMessageW")
_GetMessageTime = user32.NewProc("GetMessageTime")
_KillTimer = user32.NewProc("KillTimer")
_LoadCursor = user32.NewProc("LoadCursorW")
_MsgWaitForMultipleObjectsEx = user32.NewProc("MsgWaitForMultipleObjectsEx")
_PeekMessage = user32.NewProc("PeekMessageW")
_PostMessage = user32.NewProc("PostMessageW")
_PostQuitMessage = user32.NewProc("PostQuitMessage")
_ReleaseCapture = user32.NewProc("ReleaseCapture")
_RegisterClassExW = user32.NewProc("RegisterClassExW")
_ReleaseDC = user32.NewProc("ReleaseDC")
_ScreenToClient = user32.NewProc("ScreenToClient")
_ShowWindow = user32.NewProc("ShowWindow")
_SetCapture = user32.NewProc("SetCapture")
_SetForegroundWindow = user32.NewProc("SetForegroundWindow")
_SetFocus = user32.NewProc("SetFocus")
_SetProcessDPIAware = user32.NewProc("SetProcessDPIAware")
_SetTimer = user32.NewProc("SetTimer")
_TranslateMessage = user32.NewProc("TranslateMessage")
_UnregisterClass = user32.NewProc("UnregisterClassW")
_UpdateWindow = user32.NewProc("UpdateWindow")
gdi32 = syscall.NewLazySystemDLL("gdi32")
_GetDeviceCaps = gdi32.NewProc("GetDeviceCaps")
)
func getModuleHandle() (syscall.Handle, error) {
h, _, err := _GetModuleHandleW.Call(uintptr(0))
if h == 0 {
return 0, fmt.Errorf("GetModuleHandleW failed: %v", err)
}
return syscall.Handle(h), nil
}
func adjustWindowRectEx(r *rect, dwStyle uint32, bMenu int, dwExStyle uint32) {
_AdjustWindowRectEx.Call(uintptr(unsafe.Pointer(r)), uintptr(dwStyle), uintptr(bMenu), uintptr(dwExStyle))
}
func callMsgFilter(m *msg, nCode uintptr) bool {
r, _, _ := _CallMsgFilter.Call(uintptr(unsafe.Pointer(m)), nCode)
return r != 0
}
func createWindowEx(dwExStyle uint32, lpClassName uint16, lpWindowName string, dwStyle uint32, x, y, w, h int32, hWndParent, hMenu, hInstance syscall.Handle, lpParam uintptr) (syscall.Handle, error) {
hwnd, _, err := _CreateWindowEx.Call(
uintptr(dwExStyle),
uintptr(lpClassName),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpWindowName))),
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 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 getClientRect(hwnd syscall.Handle, r *rect) {
_GetClientRect.Call(uintptr(hwnd), uintptr(unsafe.Pointer(r)))
}
func getDC(hwnd syscall.Handle) (syscall.Handle, error) {
hdc, _, err := _GetDC.Call(uintptr(hwnd))
if hdc == 0 {
return 0, fmt.Errorf("GetDC failed: %v", err)
}
return syscall.Handle(hdc), nil
}
func getDeviceCaps(hdc syscall.Handle, index int32) int {
c, _, _ := _GetDeviceCaps.Call(uintptr(hdc), uintptr(index))
return int(c)
}
func getKeyState(nVirtKey int32) int16 {
c, _, _ := _GetKeyState.Call(uintptr(nVirtKey))
return int16(c)
}
func getMessage(m *msg, hwnd syscall.Handle, wMsgFilterMin, wMsgFilterMax uint32) int32 {
r, _, _ := _GetMessage.Call(uintptr(unsafe.Pointer(m)),
uintptr(hwnd),
uintptr(wMsgFilterMin),
uintptr(wMsgFilterMax))
return int32(r)
}
func getMessageTime() time.Duration {
r, _, _ := _GetMessageTime.Call()
return time.Duration(r) * time.Millisecond
}
func killTimer(hwnd syscall.Handle, nIDEvent uintptr) error {
r, _, err := _SetTimer.Call(uintptr(hwnd), uintptr(nIDEvent), 0, 0)
if r == 0 {
return fmt.Errorf("KillTimer failed: %v", err)
}
return nil
}
func loadCursor(curID uint16) (syscall.Handle, error) {
h, _, err := _LoadCursor.Call(0, uintptr(curID))
if h == 0 {
return 0, fmt.Errorf("LoadCursorW failed: %v", err)
}
return syscall.Handle(h), nil
}
func msgWaitForMultipleObjectsEx(nCount uint32, pHandles uintptr, millis, mask, flags uint32) (uint32, error) {
r, _, err := _MsgWaitForMultipleObjectsEx.Call(uintptr(nCount), pHandles, uintptr(millis), uintptr(mask), uintptr(flags))
res := uint32(r)
if res == 0xFFFFFFFF {
return 0, fmt.Errorf("MsgWaitForMultipleObjectsEx failed: %v", err)
}
return res, nil
}
func peekMessage(m *msg, hwnd syscall.Handle, wMsgFilterMin, wMsgFilterMax, wRemoveMsg uint32) bool {
r, _, _ := _PeekMessage.Call(uintptr(unsafe.Pointer(m)), uintptr(hwnd), uintptr(wMsgFilterMin), uintptr(wMsgFilterMax), uintptr(wRemoveMsg))
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 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(unsafe.Pointer(hwnd)))
return syscall.Handle(r)
}
func setTimer(hwnd syscall.Handle, nIDEvent uintptr, uElapse uint32, timerProc uintptr) error {
r, _, err := _SetTimer.Call(uintptr(hwnd), uintptr(nIDEvent), uintptr(uElapse), timerProc)
if r == 0 {
return fmt.Errorf("SetTimer failed: %v", err)
}
return nil
}
func screenToClient(hwnd syscall.Handle, p *point) {
_ScreenToClient.Call(uintptr(hwnd), uintptr(unsafe.Pointer(p)))
}
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))
}
+98
View File
@@ -0,0 +1,98 @@
// +build linux,!android
/* Generated by wayland-scanner 1.16.0 */
/*
* Copyright © 2012, 2013 Intel Corporation
* Copyright © 2015, 2016 Jan Arne Petersen
* Copyright © 2017, 2018 Red Hat, Inc.
* Copyright © 2018 Purism SPC
*
* Permission to use, copy, modify, distribute, and sell this
* software and its documentation for any purpose is hereby granted
* without fee, provided that the above copyright notice appear in
* all copies and that both that copyright notice and this permission
* notice appear in supporting documentation, and that the name of
* the copyright holders not be used in advertising or publicity
* pertaining to distribution of the software without specific,
* written prior permission. The copyright holders make no
* representations about the suitability of this software for any
* purpose. It is provided "as is" without express or implied
* warranty.
*
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
* SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
* SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
* ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
* THIS SOFTWARE.
*/
#include <stdlib.h>
#include <stdint.h>
#include "wayland-util.h"
#ifndef __has_attribute
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
#endif
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
#else
#define WL_PRIVATE
#endif
extern const struct wl_interface wl_seat_interface;
extern const struct wl_interface wl_surface_interface;
extern const struct wl_interface zwp_text_input_v3_interface;
static const struct wl_interface *types[] = {
NULL,
NULL,
NULL,
NULL,
&wl_surface_interface,
&wl_surface_interface,
&zwp_text_input_v3_interface,
&wl_seat_interface,
};
static const struct wl_message zwp_text_input_v3_requests[] = {
{ "destroy", "", types + 0 },
{ "enable", "", types + 0 },
{ "disable", "", types + 0 },
{ "set_surrounding_text", "sii", types + 0 },
{ "set_text_change_cause", "u", types + 0 },
{ "set_content_type", "uu", types + 0 },
{ "set_cursor_rectangle", "iiii", types + 0 },
{ "commit", "", types + 0 },
};
static const struct wl_message zwp_text_input_v3_events[] = {
{ "enter", "o", types + 4 },
{ "leave", "o", types + 5 },
{ "preedit_string", "?sii", types + 0 },
{ "commit_string", "?s", types + 0 },
{ "delete_surrounding_text", "uu", types + 0 },
{ "done", "u", types + 0 },
};
WL_PRIVATE const struct wl_interface zwp_text_input_v3_interface = {
"zwp_text_input_v3", 1,
8, zwp_text_input_v3_requests,
6, zwp_text_input_v3_events,
};
static const struct wl_message zwp_text_input_manager_v3_requests[] = {
{ "destroy", "", types + 0 },
{ "get_text_input", "no", types + 6 },
};
WL_PRIVATE const struct wl_interface zwp_text_input_manager_v3_interface = {
"zwp_text_input_manager_v3", 1,
2, zwp_text_input_manager_v3_requests,
0, NULL,
};
+819
View File
@@ -0,0 +1,819 @@
/* Generated by wayland-scanner 1.16.0 */
#ifndef TEXT_INPUT_UNSTABLE_V3_CLIENT_PROTOCOL_H
#define TEXT_INPUT_UNSTABLE_V3_CLIENT_PROTOCOL_H
#include <stdint.h>
#include <stddef.h>
#include "wayland-client.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @page page_text_input_unstable_v3 The text_input_unstable_v3 protocol
* Protocol for composing text
*
* @section page_desc_text_input_unstable_v3 Description
*
* This protocol allows compositors to act as input methods and to send text
* to applications. A text input object is used to manage state of what are
* typically text entry fields in the application.
*
* This document adheres to the RFC 2119 when using words like "must",
* "should", "may", etc.
*
* Warning! The protocol described in this file is experimental and
* backward incompatible changes may be made. Backward compatible changes
* may be added together with the corresponding interface version bump.
* Backward incompatible changes are done by bumping the version number in
* the protocol and interface names and resetting the interface version.
* Once the protocol is to be declared stable, the 'z' prefix and the
* version number in the protocol and interface names are removed and the
* interface version number is reset.
*
* @section page_ifaces_text_input_unstable_v3 Interfaces
* - @subpage page_iface_zwp_text_input_v3 - text input
* - @subpage page_iface_zwp_text_input_manager_v3 - text input manager
* @section page_copyright_text_input_unstable_v3 Copyright
* <pre>
*
* Copyright © 2012, 2013 Intel Corporation
* Copyright © 2015, 2016 Jan Arne Petersen
* Copyright © 2017, 2018 Red Hat, Inc.
* Copyright © 2018 Purism SPC
*
* Permission to use, copy, modify, distribute, and sell this
* software and its documentation for any purpose is hereby granted
* without fee, provided that the above copyright notice appear in
* all copies and that both that copyright notice and this permission
* notice appear in supporting documentation, and that the name of
* the copyright holders not be used in advertising or publicity
* pertaining to distribution of the software without specific,
* written prior permission. The copyright holders make no
* representations about the suitability of this software for any
* purpose. It is provided "as is" without express or implied
* warranty.
*
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
* SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
* SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
* ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
* THIS SOFTWARE.
* </pre>
*/
struct wl_seat;
struct wl_surface;
struct zwp_text_input_manager_v3;
struct zwp_text_input_v3;
/**
* @page page_iface_zwp_text_input_v3 zwp_text_input_v3
* @section page_iface_zwp_text_input_v3_desc Description
*
* The zwp_text_input_v3 interface represents text input and input methods
* associated with a seat. It provides enter/leave events to follow the
* text input focus for a seat.
*
* Requests are used to enable/disable the text-input object and set
* state information like surrounding and selected text or the content type.
* The information about the entered text is sent to the text-input object
* via the preedit_string and commit_string events.
*
* Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices
* must not point to middle bytes inside a code point: they must either
* point to the first byte of a code point or to the end of the buffer.
* Lengths must be measured between two valid indices.
*
* Focus moving throughout surfaces will result in the emission of
* zwp_text_input_v3.enter and zwp_text_input_v3.leave events. The focused
* surface must commit zwp_text_input_v3.enable and
* zwp_text_input_v3.disable requests as the keyboard focus moves across
* editable and non-editable elements of the UI. Those two requests are not
* expected to be paired with each other, the compositor must be able to
* handle consecutive series of the same request.
*
* State is sent by the state requests (set_surrounding_text,
* set_content_type and set_cursor_rectangle) and a commit request. After an
* enter event or disable request all state information is invalidated and
* needs to be resent by the client.
* @section page_iface_zwp_text_input_v3_api API
* See @ref iface_zwp_text_input_v3.
*/
/**
* @defgroup iface_zwp_text_input_v3 The zwp_text_input_v3 interface
*
* The zwp_text_input_v3 interface represents text input and input methods
* associated with a seat. It provides enter/leave events to follow the
* text input focus for a seat.
*
* Requests are used to enable/disable the text-input object and set
* state information like surrounding and selected text or the content type.
* The information about the entered text is sent to the text-input object
* via the preedit_string and commit_string events.
*
* Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices
* must not point to middle bytes inside a code point: they must either
* point to the first byte of a code point or to the end of the buffer.
* Lengths must be measured between two valid indices.
*
* Focus moving throughout surfaces will result in the emission of
* zwp_text_input_v3.enter and zwp_text_input_v3.leave events. The focused
* surface must commit zwp_text_input_v3.enable and
* zwp_text_input_v3.disable requests as the keyboard focus moves across
* editable and non-editable elements of the UI. Those two requests are not
* expected to be paired with each other, the compositor must be able to
* handle consecutive series of the same request.
*
* State is sent by the state requests (set_surrounding_text,
* set_content_type and set_cursor_rectangle) and a commit request. After an
* enter event or disable request all state information is invalidated and
* needs to be resent by the client.
*/
extern const struct wl_interface zwp_text_input_v3_interface;
/**
* @page page_iface_zwp_text_input_manager_v3 zwp_text_input_manager_v3
* @section page_iface_zwp_text_input_manager_v3_desc Description
*
* A factory for text-input objects. This object is a global singleton.
* @section page_iface_zwp_text_input_manager_v3_api API
* See @ref iface_zwp_text_input_manager_v3.
*/
/**
* @defgroup iface_zwp_text_input_manager_v3 The zwp_text_input_manager_v3 interface
*
* A factory for text-input objects. This object is a global singleton.
*/
extern const struct wl_interface zwp_text_input_manager_v3_interface;
#ifndef ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_ENUM
#define ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_ENUM
/**
* @ingroup iface_zwp_text_input_v3
* text change reason
*
* Reason for the change of surrounding text or cursor posision.
*/
enum zwp_text_input_v3_change_cause {
/**
* input method caused the change
*/
ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_INPUT_METHOD = 0,
/**
* something else than the input method caused the change
*/
ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_OTHER = 1,
};
#endif /* ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_ENUM */
#ifndef ZWP_TEXT_INPUT_V3_CONTENT_HINT_ENUM
#define ZWP_TEXT_INPUT_V3_CONTENT_HINT_ENUM
/**
* @ingroup iface_zwp_text_input_v3
* content hint
*
* Content hint is a bitmask to allow to modify the behavior of the text
* input.
*/
enum zwp_text_input_v3_content_hint {
/**
* no special behavior
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE = 0x0,
/**
* suggest word completions
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_COMPLETION = 0x1,
/**
* suggest word corrections
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_SPELLCHECK = 0x2,
/**
* switch to uppercase letters at the start of a sentence
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_AUTO_CAPITALIZATION = 0x4,
/**
* prefer lowercase letters
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_LOWERCASE = 0x8,
/**
* prefer uppercase letters
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_UPPERCASE = 0x10,
/**
* prefer casing for titles and headings (can be language dependent)
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_TITLECASE = 0x20,
/**
* characters should be hidden
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_HIDDEN_TEXT = 0x40,
/**
* typed text should not be stored
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA = 0x80,
/**
* just Latin characters should be entered
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_LATIN = 0x100,
/**
* the text input is multiline
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_MULTILINE = 0x200,
};
#endif /* ZWP_TEXT_INPUT_V3_CONTENT_HINT_ENUM */
#ifndef ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ENUM
#define ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ENUM
/**
* @ingroup iface_zwp_text_input_v3
* content purpose
*
* The content purpose allows to specify the primary purpose of a text
* input.
*
* This allows an input method to show special purpose input panels with
* extra characters or to disallow some characters.
*/
enum zwp_text_input_v3_content_purpose {
/**
* default input, allowing all characters
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL = 0,
/**
* allow only alphabetic characters
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ALPHA = 1,
/**
* allow only digits
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DIGITS = 2,
/**
* input a number (including decimal separator and sign)
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NUMBER = 3,
/**
* input a phone number
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PHONE = 4,
/**
* input an URL
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_URL = 5,
/**
* input an email address
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_EMAIL = 6,
/**
* input a name of a person
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NAME = 7,
/**
* input a password (combine with sensitive_data hint)
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PASSWORD = 8,
/**
* input is a numeric password (combine with sensitive_data hint)
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PIN = 9,
/**
* input a date
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATE = 10,
/**
* input a time
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TIME = 11,
/**
* input a date and time
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATETIME = 12,
/**
* input for a terminal
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL = 13,
};
#endif /* ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ENUM */
/**
* @ingroup iface_zwp_text_input_v3
* @struct zwp_text_input_v3_listener
*/
struct zwp_text_input_v3_listener {
/**
* enter event
*
* Notification that this seat's text-input focus is on a certain
* surface.
*
* When the seat has the keyboard capability the text-input focus
* follows the keyboard focus. This event sets the current surface
* for the text-input object.
*/
void (*enter)(void *data,
struct zwp_text_input_v3 *zwp_text_input_v3,
struct wl_surface *surface);
/**
* leave event
*
* Notification that this seat's text-input focus is no longer on
* a certain surface. The client should reset any preedit string
* previously set.
*
* The leave notification clears the current surface. It is sent
* before the enter notification for the new focus.
*
* When the seat has the keyboard capability the text-input focus
* follows the keyboard focus.
*/
void (*leave)(void *data,
struct zwp_text_input_v3 *zwp_text_input_v3,
struct wl_surface *surface);
/**
* pre-edit
*
* Notify when a new composing text (pre-edit) should be set at
* the current cursor position. Any previously set composing text
* must be removed. Any previously existing selected text must be
* removed.
*
* The argument text contains the pre-edit string buffer.
*
* The parameters cursor_begin and cursor_end are counted in bytes
* relative to the beginning of the submitted text buffer. Cursor
* should be hidden when both are equal to -1.
*
* They could be represented by the client as a line if both values
* are the same, or as a text highlight otherwise.
*
* Values set with this event are double-buffered. They must be
* applied and reset to initial on the next zwp_text_input_v3.done
* event.
*
* The initial value of text is an empty string, and cursor_begin,
* cursor_end and cursor_hidden are all 0.
*/
void (*preedit_string)(void *data,
struct zwp_text_input_v3 *zwp_text_input_v3,
const char *text,
int32_t cursor_begin,
int32_t cursor_end);
/**
* text commit
*
* Notify when text should be inserted into the editor widget.
* The text to commit could be either just a single character after
* a key press or the result of some composing (pre-edit).
*
* Values set with this event are double-buffered. They must be
* applied and reset to initial on the next zwp_text_input_v3.done
* event.
*
* The initial value of text is an empty string.
*/
void (*commit_string)(void *data,
struct zwp_text_input_v3 *zwp_text_input_v3,
const char *text);
/**
* delete surrounding text
*
* Notify when the text around the current cursor position should
* be deleted.
*
* Before_length and after_length are the number of bytes before
* and after the current cursor index (excluding the selection) to
* delete.
*
* If a preedit text is present, in effect before_length is counted
* from the beginning of it, and after_length from its end (see
* done event sequence).
*
* Values set with this event are double-buffered. They must be
* applied and reset to initial on the next zwp_text_input_v3.done
* event.
*
* The initial values of both before_length and after_length are 0.
* @param before_length length of text before current cursor position
* @param after_length length of text after current cursor position
*/
void (*delete_surrounding_text)(void *data,
struct zwp_text_input_v3 *zwp_text_input_v3,
uint32_t before_length,
uint32_t after_length);
/**
* apply changes
*
* Instruct the application to apply changes to state requested
* by the preedit_string, commit_string and delete_surrounding_text
* events. The state relating to these events is double-buffered,
* and each one modifies the pending state. This event replaces the
* current state with the pending state.
*
* The application must proceed by evaluating the changes in the
* following order:
*
* 1. Replace existing preedit string with the cursor. 2. Delete
* requested surrounding text. 3. Insert commit string with the
* cursor at its end. 4. Calculate surrounding text to send. 5.
* Insert new preedit text in cursor position. 6. Place cursor
* inside preedit text.
*
* The serial number reflects the last state of the
* zwp_text_input_v3 object known to the compositor. The value of
* the serial argument must be equal to the number of commit
* requests already issued on that object. When the client receives
* a done event with a serial different than the number of past
* commit requests, it must proceed as normal, except it should not
* change the current state of the zwp_text_input_v3 object.
*/
void (*done)(void *data,
struct zwp_text_input_v3 *zwp_text_input_v3,
uint32_t serial);
};
/**
* @ingroup iface_zwp_text_input_v3
*/
static inline int
zwp_text_input_v3_add_listener(struct zwp_text_input_v3 *zwp_text_input_v3,
const struct zwp_text_input_v3_listener *listener, void *data)
{
return wl_proxy_add_listener((struct wl_proxy *) zwp_text_input_v3,
(void (**)(void)) listener, data);
}
#define ZWP_TEXT_INPUT_V3_DESTROY 0
#define ZWP_TEXT_INPUT_V3_ENABLE 1
#define ZWP_TEXT_INPUT_V3_DISABLE 2
#define ZWP_TEXT_INPUT_V3_SET_SURROUNDING_TEXT 3
#define ZWP_TEXT_INPUT_V3_SET_TEXT_CHANGE_CAUSE 4
#define ZWP_TEXT_INPUT_V3_SET_CONTENT_TYPE 5
#define ZWP_TEXT_INPUT_V3_SET_CURSOR_RECTANGLE 6
#define ZWP_TEXT_INPUT_V3_COMMIT 7
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_ENTER_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_LEAVE_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_PREEDIT_STRING_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_COMMIT_STRING_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_DELETE_SURROUNDING_TEXT_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_DONE_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_ENABLE_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_DISABLE_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_SET_SURROUNDING_TEXT_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_SET_TEXT_CHANGE_CAUSE_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_SET_CONTENT_TYPE_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_SET_CURSOR_RECTANGLE_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_COMMIT_SINCE_VERSION 1
/** @ingroup iface_zwp_text_input_v3 */
static inline void
zwp_text_input_v3_set_user_data(struct zwp_text_input_v3 *zwp_text_input_v3, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) zwp_text_input_v3, user_data);
}
/** @ingroup iface_zwp_text_input_v3 */
static inline void *
zwp_text_input_v3_get_user_data(struct zwp_text_input_v3 *zwp_text_input_v3)
{
return wl_proxy_get_user_data((struct wl_proxy *) zwp_text_input_v3);
}
static inline uint32_t
zwp_text_input_v3_get_version(struct zwp_text_input_v3 *zwp_text_input_v3)
{
return wl_proxy_get_version((struct wl_proxy *) zwp_text_input_v3);
}
/**
* @ingroup iface_zwp_text_input_v3
*
* Destroy the wp_text_input object. Also disables all surfaces enabled
* through this wp_text_input object.
*/
static inline void
zwp_text_input_v3_destroy(struct zwp_text_input_v3 *zwp_text_input_v3)
{
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
ZWP_TEXT_INPUT_V3_DESTROY);
wl_proxy_destroy((struct wl_proxy *) zwp_text_input_v3);
}
/**
* @ingroup iface_zwp_text_input_v3
*
* Requests text input on the surface previously obtained from the enter
* event.
*
* This request must be issued every time the active text input changes
* to a new one, including within the current surface. Use
* zwp_text_input_v3.disable when there is no longer any input focus on
* the current surface.
*
* This request resets all state associated with previous enable, disable,
* set_surrounding_text, set_text_change_cause, set_content_type, and
* set_cursor_rectangle requests, as well as the state associated with
* preedit_string, commit_string, and delete_surrounding_text events.
*
* The set_surrounding_text, set_content_type and set_cursor_rectangle
* requests must follow if the text input supports the necessary
* functionality.
*
* State set with this request is double-buffered. It will get applied on
* the next zwp_text_input_v3.commit request, and stay valid until the
* next committed enable or disable request.
*
* The changes must be applied by the compositor after issuing a
* zwp_text_input_v3.commit request.
*/
static inline void
zwp_text_input_v3_enable(struct zwp_text_input_v3 *zwp_text_input_v3)
{
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
ZWP_TEXT_INPUT_V3_ENABLE);
}
/**
* @ingroup iface_zwp_text_input_v3
*
* Explicitly disable text input on the current surface (typically when
* there is no focus on any text entry inside the surface).
*
* State set with this request is double-buffered. It will get applied on
* the next zwp_text_input_v3.commit request.
*/
static inline void
zwp_text_input_v3_disable(struct zwp_text_input_v3 *zwp_text_input_v3)
{
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
ZWP_TEXT_INPUT_V3_DISABLE);
}
/**
* @ingroup iface_zwp_text_input_v3
*
* Sets the surrounding plain text around the input, excluding the preedit
* text.
*
* The client should notify the compositor of any changes in any of the
* values carried with this request, including changes caused by handling
* incoming text-input events as well as changes caused by other
* mechanisms like keyboard typing.
*
* If the client is unaware of the text around the cursor, it should not
* issue this request, to signify lack of support to the compositor.
*
* Text is UTF-8 encoded, and should include the cursor position, the
* complete selection and additional characters before and after them.
* There is a maximum length of wayland messages, so text can not be
* longer than 4000 bytes.
*
* Cursor is the byte offset of the cursor within text buffer.
*
* Anchor is the byte offset of the selection anchor within text buffer.
* If there is no selected text, anchor is the same as cursor.
*
* If any preedit text is present, it is replaced with a cursor for the
* purpose of this event.
*
* Values set with this request are double-buffered. They will get applied
* on the next zwp_text_input_v3.commit request, and stay valid until the
* next committed enable or disable request.
*
* The initial state for affected fields is empty, meaning that the text
* input does not support sending surrounding text. If the empty values
* get applied, subsequent attempts to change them may have no effect.
*/
static inline void
zwp_text_input_v3_set_surrounding_text(struct zwp_text_input_v3 *zwp_text_input_v3, const char *text, int32_t cursor, int32_t anchor)
{
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
ZWP_TEXT_INPUT_V3_SET_SURROUNDING_TEXT, text, cursor, anchor);
}
/**
* @ingroup iface_zwp_text_input_v3
*
* Tells the compositor why the text surrounding the cursor changed.
*
* Whenever the client detects an external change in text, cursor, or
* anchor posision, it must issue this request to the compositor. This
* request is intended to give the input method a chance to update the
* preedit text in an appropriate way, e.g. by removing it when the user
* starts typing with a keyboard.
*
* cause describes the source of the change.
*
* The value set with this request is double-buffered. It must be applied
* and reset to initial at the next zwp_text_input_v3.commit request.
*
* The initial value of cause is input_method.
*/
static inline void
zwp_text_input_v3_set_text_change_cause(struct zwp_text_input_v3 *zwp_text_input_v3, uint32_t cause)
{
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
ZWP_TEXT_INPUT_V3_SET_TEXT_CHANGE_CAUSE, cause);
}
/**
* @ingroup iface_zwp_text_input_v3
*
* Sets the content purpose and content hint. While the purpose is the
* basic purpose of an input field, the hint flags allow to modify some of
* the behavior.
*
* Values set with this request are double-buffered. They will get applied
* on the next zwp_text_input_v3.commit request.
* Subsequent attempts to update them may have no effect. The values
* remain valid until the next committed enable or disable request.
*
* The initial value for hint is none, and the initial value for purpose
* is normal.
*/
static inline void
zwp_text_input_v3_set_content_type(struct zwp_text_input_v3 *zwp_text_input_v3, uint32_t hint, uint32_t purpose)
{
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
ZWP_TEXT_INPUT_V3_SET_CONTENT_TYPE, hint, purpose);
}
/**
* @ingroup iface_zwp_text_input_v3
*
* Marks an area around the cursor as a x, y, width, height rectangle in
* surface local coordinates.
*
* Allows the compositor to put a window with word suggestions near the
* cursor, without obstructing the text being input.
*
* If the client is unaware of the position of edited text, it should not
* issue this request, to signify lack of support to the compositor.
*
* Values set with this request are double-buffered. They will get applied
* on the next zwp_text_input_v3.commit request, and stay valid until the
* next committed enable or disable request.
*
* The initial values describing a cursor rectangle are empty. That means
* the text input does not support describing the cursor area. If the
* empty values get applied, subsequent attempts to change them may have
* no effect.
*/
static inline void
zwp_text_input_v3_set_cursor_rectangle(struct zwp_text_input_v3 *zwp_text_input_v3, int32_t x, int32_t y, int32_t width, int32_t height)
{
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
ZWP_TEXT_INPUT_V3_SET_CURSOR_RECTANGLE, x, y, width, height);
}
/**
* @ingroup iface_zwp_text_input_v3
*
* Atomically applies state changes recently sent to the compositor.
*
* The commit request establishes and updates the state of the client, and
* must be issued after any changes to apply them.
*
* Text input state (enabled status, content purpose, content hint,
* surrounding text and change cause, cursor rectangle) is conceptually
* double-buffered within the context of a text input, i.e. between a
* committed enable request and the following committed enable or disable
* request.
*
* Protocol requests modify the pending state, as opposed to the current
* state in use by the input method. A commit request atomically applies
* all pending state, replacing the current state. After commit, the new
* pending state is as documented for each related request.
*
* Requests are applied in the order of arrival.
*
* Neither current nor pending state are modified unless noted otherwise.
*
* The compositor must count the number of commit requests coming from
* each zwp_text_input_v3 object and use the count as the serial in done
* events.
*/
static inline void
zwp_text_input_v3_commit(struct zwp_text_input_v3 *zwp_text_input_v3)
{
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
ZWP_TEXT_INPUT_V3_COMMIT);
}
#define ZWP_TEXT_INPUT_MANAGER_V3_DESTROY 0
#define ZWP_TEXT_INPUT_MANAGER_V3_GET_TEXT_INPUT 1
/**
* @ingroup iface_zwp_text_input_manager_v3
*/
#define ZWP_TEXT_INPUT_MANAGER_V3_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_manager_v3
*/
#define ZWP_TEXT_INPUT_MANAGER_V3_GET_TEXT_INPUT_SINCE_VERSION 1
/** @ingroup iface_zwp_text_input_manager_v3 */
static inline void
zwp_text_input_manager_v3_set_user_data(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) zwp_text_input_manager_v3, user_data);
}
/** @ingroup iface_zwp_text_input_manager_v3 */
static inline void *
zwp_text_input_manager_v3_get_user_data(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3)
{
return wl_proxy_get_user_data((struct wl_proxy *) zwp_text_input_manager_v3);
}
static inline uint32_t
zwp_text_input_manager_v3_get_version(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3)
{
return wl_proxy_get_version((struct wl_proxy *) zwp_text_input_manager_v3);
}
/**
* @ingroup iface_zwp_text_input_manager_v3
*
* Destroy the wp_text_input_manager object.
*/
static inline void
zwp_text_input_manager_v3_destroy(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3)
{
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_manager_v3,
ZWP_TEXT_INPUT_MANAGER_V3_DESTROY);
wl_proxy_destroy((struct wl_proxy *) zwp_text_input_manager_v3);
}
/**
* @ingroup iface_zwp_text_input_manager_v3
*
* Creates a new text-input object for a given seat.
*/
static inline struct zwp_text_input_v3 *
zwp_text_input_manager_v3_get_text_input(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3, struct wl_seat *seat)
{
struct wl_proxy *id;
id = wl_proxy_marshal_constructor((struct wl_proxy *) zwp_text_input_manager_v3,
ZWP_TEXT_INPUT_MANAGER_V3_GET_TEXT_INPUT, &zwp_text_input_v3_interface, NULL, seat);
return (struct zwp_text_input_v3 *) id;
}
#ifdef __cplusplus
}
#endif
#endif
+77
View File
@@ -0,0 +1,77 @@
// +build linux,!android
/* Generated by wayland-scanner 1.16.0 */
/*
* Copyright © 2018 Simon Ser
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <stdlib.h>
#include <stdint.h>
#include "wayland-util.h"
#ifndef __has_attribute
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
#endif
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
#else
#define WL_PRIVATE
#endif
extern const struct wl_interface xdg_toplevel_interface;
extern const struct wl_interface zxdg_toplevel_decoration_v1_interface;
static const struct wl_interface *types[] = {
NULL,
&zxdg_toplevel_decoration_v1_interface,
&xdg_toplevel_interface,
};
static const struct wl_message zxdg_decoration_manager_v1_requests[] = {
{ "destroy", "", types + 0 },
{ "get_toplevel_decoration", "no", types + 1 },
};
WL_PRIVATE const struct wl_interface zxdg_decoration_manager_v1_interface = {
"zxdg_decoration_manager_v1", 1,
2, zxdg_decoration_manager_v1_requests,
0, NULL,
};
static const struct wl_message zxdg_toplevel_decoration_v1_requests[] = {
{ "destroy", "", types + 0 },
{ "set_mode", "u", types + 0 },
{ "unset_mode", "", types + 0 },
};
static const struct wl_message zxdg_toplevel_decoration_v1_events[] = {
{ "configure", "u", types + 0 },
};
WL_PRIVATE const struct wl_interface zxdg_toplevel_decoration_v1_interface = {
"zxdg_toplevel_decoration_v1", 1,
3, zxdg_toplevel_decoration_v1_requests,
1, zxdg_toplevel_decoration_v1_events,
};
+376
View File
@@ -0,0 +1,376 @@
/* Generated by wayland-scanner 1.16.0 */
#ifndef XDG_DECORATION_UNSTABLE_V1_CLIENT_PROTOCOL_H
#define XDG_DECORATION_UNSTABLE_V1_CLIENT_PROTOCOL_H
#include <stdint.h>
#include <stddef.h>
#include "wayland-client.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @page page_xdg_decoration_unstable_v1 The xdg_decoration_unstable_v1 protocol
* @section page_ifaces_xdg_decoration_unstable_v1 Interfaces
* - @subpage page_iface_zxdg_decoration_manager_v1 - window decoration manager
* - @subpage page_iface_zxdg_toplevel_decoration_v1 - decoration object for a toplevel surface
* @section page_copyright_xdg_decoration_unstable_v1 Copyright
* <pre>
*
* Copyright © 2018 Simon Ser
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
* </pre>
*/
struct xdg_toplevel;
struct zxdg_decoration_manager_v1;
struct zxdg_toplevel_decoration_v1;
/**
* @page page_iface_zxdg_decoration_manager_v1 zxdg_decoration_manager_v1
* @section page_iface_zxdg_decoration_manager_v1_desc Description
*
* This interface allows a compositor to announce support for server-side
* decorations.
*
* A window decoration is a set of window controls as deemed appropriate by
* the party managing them, such as user interface components used to move,
* resize and change a window's state.
*
* A client can use this protocol to request being decorated by a supporting
* compositor.
*
* If compositor and client do not negotiate the use of a server-side
* decoration using this protocol, clients continue to self-decorate as they
* see fit.
*
* Warning! The protocol described in this file is experimental and
* backward incompatible changes may be made. Backward compatible changes
* may be added together with the corresponding interface version bump.
* Backward incompatible changes are done by bumping the version number in
* the protocol and interface names and resetting the interface version.
* Once the protocol is to be declared stable, the 'z' prefix and the
* version number in the protocol and interface names are removed and the
* interface version number is reset.
* @section page_iface_zxdg_decoration_manager_v1_api API
* See @ref iface_zxdg_decoration_manager_v1.
*/
/**
* @defgroup iface_zxdg_decoration_manager_v1 The zxdg_decoration_manager_v1 interface
*
* This interface allows a compositor to announce support for server-side
* decorations.
*
* A window decoration is a set of window controls as deemed appropriate by
* the party managing them, such as user interface components used to move,
* resize and change a window's state.
*
* A client can use this protocol to request being decorated by a supporting
* compositor.
*
* If compositor and client do not negotiate the use of a server-side
* decoration using this protocol, clients continue to self-decorate as they
* see fit.
*
* Warning! The protocol described in this file is experimental and
* backward incompatible changes may be made. Backward compatible changes
* may be added together with the corresponding interface version bump.
* Backward incompatible changes are done by bumping the version number in
* the protocol and interface names and resetting the interface version.
* Once the protocol is to be declared stable, the 'z' prefix and the
* version number in the protocol and interface names are removed and the
* interface version number is reset.
*/
extern const struct wl_interface zxdg_decoration_manager_v1_interface;
/**
* @page page_iface_zxdg_toplevel_decoration_v1 zxdg_toplevel_decoration_v1
* @section page_iface_zxdg_toplevel_decoration_v1_desc Description
*
* The decoration object allows the compositor to toggle server-side window
* decorations for a toplevel surface. The client can request to switch to
* another mode.
*
* The xdg_toplevel_decoration object must be destroyed before its
* xdg_toplevel.
* @section page_iface_zxdg_toplevel_decoration_v1_api API
* See @ref iface_zxdg_toplevel_decoration_v1.
*/
/**
* @defgroup iface_zxdg_toplevel_decoration_v1 The zxdg_toplevel_decoration_v1 interface
*
* The decoration object allows the compositor to toggle server-side window
* decorations for a toplevel surface. The client can request to switch to
* another mode.
*
* The xdg_toplevel_decoration object must be destroyed before its
* xdg_toplevel.
*/
extern const struct wl_interface zxdg_toplevel_decoration_v1_interface;
#define ZXDG_DECORATION_MANAGER_V1_DESTROY 0
#define ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION 1
/**
* @ingroup iface_zxdg_decoration_manager_v1
*/
#define ZXDG_DECORATION_MANAGER_V1_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_zxdg_decoration_manager_v1
*/
#define ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION_SINCE_VERSION 1
/** @ingroup iface_zxdg_decoration_manager_v1 */
static inline void
zxdg_decoration_manager_v1_set_user_data(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) zxdg_decoration_manager_v1, user_data);
}
/** @ingroup iface_zxdg_decoration_manager_v1 */
static inline void *
zxdg_decoration_manager_v1_get_user_data(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1)
{
return wl_proxy_get_user_data((struct wl_proxy *) zxdg_decoration_manager_v1);
}
static inline uint32_t
zxdg_decoration_manager_v1_get_version(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1)
{
return wl_proxy_get_version((struct wl_proxy *) zxdg_decoration_manager_v1);
}
/**
* @ingroup iface_zxdg_decoration_manager_v1
*
* Destroy the decoration manager. This doesn't destroy objects created
* with the manager.
*/
static inline void
zxdg_decoration_manager_v1_destroy(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1)
{
wl_proxy_marshal((struct wl_proxy *) zxdg_decoration_manager_v1,
ZXDG_DECORATION_MANAGER_V1_DESTROY);
wl_proxy_destroy((struct wl_proxy *) zxdg_decoration_manager_v1);
}
/**
* @ingroup iface_zxdg_decoration_manager_v1
*
* Create a new decoration object associated with the given toplevel.
*
* Creating an xdg_toplevel_decoration from an xdg_toplevel which has a
* buffer attached or committed is a client error, and any attempts by a
* client to attach or manipulate a buffer prior to the first
* xdg_toplevel_decoration.configure event must also be treated as
* errors.
*/
static inline struct zxdg_toplevel_decoration_v1 *
zxdg_decoration_manager_v1_get_toplevel_decoration(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1, struct xdg_toplevel *toplevel)
{
struct wl_proxy *id;
id = wl_proxy_marshal_constructor((struct wl_proxy *) zxdg_decoration_manager_v1,
ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION, &zxdg_toplevel_decoration_v1_interface, NULL, toplevel);
return (struct zxdg_toplevel_decoration_v1 *) id;
}
#ifndef ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM
#define ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM
enum zxdg_toplevel_decoration_v1_error {
/**
* xdg_toplevel has a buffer attached before configure
*/
ZXDG_TOPLEVEL_DECORATION_V1_ERROR_UNCONFIGURED_BUFFER = 0,
/**
* xdg_toplevel already has a decoration object
*/
ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ALREADY_CONSTRUCTED = 1,
/**
* xdg_toplevel destroyed before the decoration object
*/
ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ORPHANED = 2,
};
#endif /* ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM */
#ifndef ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM
#define ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
* window decoration modes
*
* These values describe window decoration modes.
*/
enum zxdg_toplevel_decoration_v1_mode {
/**
* no server-side window decoration
*/
ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE = 1,
/**
* server-side window decoration
*/
ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE = 2,
};
#endif /* ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM */
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
* @struct zxdg_toplevel_decoration_v1_listener
*/
struct zxdg_toplevel_decoration_v1_listener {
/**
* suggest a surface change
*
* The configure event asks the client to change its decoration
* mode. The configured state should not be applied immediately.
* Clients must send an ack_configure in response to this event.
* See xdg_surface.configure and xdg_surface.ack_configure for
* details.
*
* A configure event can be sent at any time. The specified mode
* must be obeyed by the client.
* @param mode the decoration mode
*/
void (*configure)(void *data,
struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1,
uint32_t mode);
};
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
*/
static inline int
zxdg_toplevel_decoration_v1_add_listener(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1,
const struct zxdg_toplevel_decoration_v1_listener *listener, void *data)
{
return wl_proxy_add_listener((struct wl_proxy *) zxdg_toplevel_decoration_v1,
(void (**)(void)) listener, data);
}
#define ZXDG_TOPLEVEL_DECORATION_V1_DESTROY 0
#define ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE 1
#define ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE 2
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
*/
#define ZXDG_TOPLEVEL_DECORATION_V1_CONFIGURE_SINCE_VERSION 1
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
*/
#define ZXDG_TOPLEVEL_DECORATION_V1_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
*/
#define ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE_SINCE_VERSION 1
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
*/
#define ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE_SINCE_VERSION 1
/** @ingroup iface_zxdg_toplevel_decoration_v1 */
static inline void
zxdg_toplevel_decoration_v1_set_user_data(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) zxdg_toplevel_decoration_v1, user_data);
}
/** @ingroup iface_zxdg_toplevel_decoration_v1 */
static inline void *
zxdg_toplevel_decoration_v1_get_user_data(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
{
return wl_proxy_get_user_data((struct wl_proxy *) zxdg_toplevel_decoration_v1);
}
static inline uint32_t
zxdg_toplevel_decoration_v1_get_version(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
{
return wl_proxy_get_version((struct wl_proxy *) zxdg_toplevel_decoration_v1);
}
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
*
* Switch back to a mode without any server-side decorations at the next
* commit.
*/
static inline void
zxdg_toplevel_decoration_v1_destroy(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
{
wl_proxy_marshal((struct wl_proxy *) zxdg_toplevel_decoration_v1,
ZXDG_TOPLEVEL_DECORATION_V1_DESTROY);
wl_proxy_destroy((struct wl_proxy *) zxdg_toplevel_decoration_v1);
}
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
*
* Set the toplevel surface decoration mode. This informs the compositor
* that the client prefers the provided decoration mode.
*
* After requesting a decoration mode, the compositor will respond by
* emitting a xdg_surface.configure event. The client should then update
* its content, drawing it without decorations if the received mode is
* server-side decorations. The client must also acknowledge the configure
* when committing the new content (see xdg_surface.ack_configure).
*
* The compositor can decide not to use the client's mode and enforce a
* different mode instead.
*
* Clients whose decoration mode depend on the xdg_toplevel state may send
* a set_mode request in response to a xdg_surface.configure event and wait
* for the next xdg_surface.configure event to prevent unwanted state.
* Such clients are responsible for preventing configure loops and must
* make sure not to send multiple successive set_mode requests with the
* same decoration mode.
*/
static inline void
zxdg_toplevel_decoration_v1_set_mode(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, uint32_t mode)
{
wl_proxy_marshal((struct wl_proxy *) zxdg_toplevel_decoration_v1,
ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE, mode);
}
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
*
* Unset the toplevel surface decoration mode. This informs the compositor
* that the client doesn't prefer a particular decoration mode.
*
* This request has the same semantics as set_mode.
*/
static inline void
zxdg_toplevel_decoration_v1_unset_mode(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
{
wl_proxy_marshal((struct wl_proxy *) zxdg_toplevel_decoration_v1,
ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE);
}
#ifdef __cplusplus
}
#endif
#endif
+176
View File
@@ -0,0 +1,176 @@
// +build linux,!android
/* Generated by wayland-scanner 1.16.0 */
/*
* Copyright © 2008-2013 Kristian Høgsberg
* Copyright © 2013 Rafael Antognolli
* Copyright © 2013 Jasper St. Pierre
* Copyright © 2010-2013 Intel Corporation
* Copyright © 2015-2017 Samsung Electronics Co., Ltd
* Copyright © 2015-2017 Red Hat Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <stdlib.h>
#include <stdint.h>
#include "wayland-util.h"
#ifndef __has_attribute
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
#endif
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
#else
#define WL_PRIVATE
#endif
extern const struct wl_interface wl_output_interface;
extern const struct wl_interface wl_seat_interface;
extern const struct wl_interface wl_surface_interface;
extern const struct wl_interface xdg_popup_interface;
extern const struct wl_interface xdg_positioner_interface;
extern const struct wl_interface xdg_surface_interface;
extern const struct wl_interface xdg_toplevel_interface;
static const struct wl_interface *types[] = {
NULL,
NULL,
NULL,
NULL,
&xdg_positioner_interface,
&xdg_surface_interface,
&wl_surface_interface,
&xdg_toplevel_interface,
&xdg_popup_interface,
&xdg_surface_interface,
&xdg_positioner_interface,
&xdg_toplevel_interface,
&wl_seat_interface,
NULL,
NULL,
NULL,
&wl_seat_interface,
NULL,
&wl_seat_interface,
NULL,
NULL,
&wl_output_interface,
&wl_seat_interface,
NULL,
};
static const struct wl_message xdg_wm_base_requests[] = {
{ "destroy", "", types + 0 },
{ "create_positioner", "n", types + 4 },
{ "get_xdg_surface", "no", types + 5 },
{ "pong", "u", types + 0 },
};
static const struct wl_message xdg_wm_base_events[] = {
{ "ping", "u", types + 0 },
};
WL_PRIVATE const struct wl_interface xdg_wm_base_interface = {
"xdg_wm_base", 2,
4, xdg_wm_base_requests,
1, xdg_wm_base_events,
};
static const struct wl_message xdg_positioner_requests[] = {
{ "destroy", "", types + 0 },
{ "set_size", "ii", types + 0 },
{ "set_anchor_rect", "iiii", types + 0 },
{ "set_anchor", "u", types + 0 },
{ "set_gravity", "u", types + 0 },
{ "set_constraint_adjustment", "u", types + 0 },
{ "set_offset", "ii", types + 0 },
};
WL_PRIVATE const struct wl_interface xdg_positioner_interface = {
"xdg_positioner", 2,
7, xdg_positioner_requests,
0, NULL,
};
static const struct wl_message xdg_surface_requests[] = {
{ "destroy", "", types + 0 },
{ "get_toplevel", "n", types + 7 },
{ "get_popup", "n?oo", types + 8 },
{ "set_window_geometry", "iiii", types + 0 },
{ "ack_configure", "u", types + 0 },
};
static const struct wl_message xdg_surface_events[] = {
{ "configure", "u", types + 0 },
};
WL_PRIVATE const struct wl_interface xdg_surface_interface = {
"xdg_surface", 2,
5, xdg_surface_requests,
1, xdg_surface_events,
};
static const struct wl_message xdg_toplevel_requests[] = {
{ "destroy", "", types + 0 },
{ "set_parent", "?o", types + 11 },
{ "set_title", "s", types + 0 },
{ "set_app_id", "s", types + 0 },
{ "show_window_menu", "ouii", types + 12 },
{ "move", "ou", types + 16 },
{ "resize", "ouu", types + 18 },
{ "set_max_size", "ii", types + 0 },
{ "set_min_size", "ii", types + 0 },
{ "set_maximized", "", types + 0 },
{ "unset_maximized", "", types + 0 },
{ "set_fullscreen", "?o", types + 21 },
{ "unset_fullscreen", "", types + 0 },
{ "set_minimized", "", types + 0 },
};
static const struct wl_message xdg_toplevel_events[] = {
{ "configure", "iia", types + 0 },
{ "close", "", types + 0 },
};
WL_PRIVATE const struct wl_interface xdg_toplevel_interface = {
"xdg_toplevel", 2,
14, xdg_toplevel_requests,
2, xdg_toplevel_events,
};
static const struct wl_message xdg_popup_requests[] = {
{ "destroy", "", types + 0 },
{ "grab", "ou", types + 22 },
};
static const struct wl_message xdg_popup_events[] = {
{ "configure", "iiii", types + 0 },
{ "popup_done", "", types + 0 },
};
WL_PRIVATE const struct wl_interface xdg_popup_interface = {
"xdg_popup", 2,
2, xdg_popup_requests,
2, xdg_popup_events,
};
File diff suppressed because it is too large Load Diff
+293
View File
@@ -0,0 +1,293 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
import (
"errors"
"fmt"
"image"
"sync"
"time"
"gioui.org/ui/app/internal/gpu"
"gioui.org/ui/key"
"gioui.org/ui/pointer"
"gioui.org/ui"
)
type WindowOptions struct {
Width ui.Value
Height ui.Value
Title string
}
type Window struct {
Profiling bool
driver *window
lastFrame time.Time
gpu *gpu.GPU
timings string
inputState key.TextInputState
err error
events chan Event
acks chan struct{}
mu sync.Mutex
stage Stage
size image.Point
skipAcks int
syncGPU bool
animating bool
hasNextFrame bool
nextFrame time.Time
delayedDraw *time.Timer
}
// driver is the interface for the platform implementation
// of a Window.
var _ interface {
// setAnimating sets the animation flag. When the window is animating,
// Draw events are delivered as fast as the display can handle them.
setAnimating(anim bool)
// setTextInput updates the virtual keyboard state.
setTextInput(s key.TextInputState)
} = (*window)(nil)
func newWindow(nw *window) *Window {
w := &Window{
driver: nw,
// Make room for a backlog of input events.
events: make(chan Event, 30),
acks: make(chan struct{}),
stage: StageInvisible,
}
return w
}
func (w *Window) Events() <-chan Event {
return w.events
}
func (w *Window) Ack() {
w.mu.Lock()
st := w.stage
needAck := w.skipAcks == 0
if !needAck {
w.skipAcks--
}
sync := w.syncGPU
w.syncGPU = false
w.mu.Unlock()
if w.gpu != nil {
switch {
case st < StageVisible:
w.gpu.Release()
w.gpu = nil
case sync:
w.gpu.Refresh()
}
}
if needAck {
w.acks <- struct{}{}
}
}
func (w *Window) Timings() string {
return w.timings
}
func (w *Window) SetTextInput(s key.TextInputState) {
if !w.IsAlive() {
return
}
if s != w.inputState && (s == key.TextInputClosed || s == key.TextInputOpen) {
w.driver.setTextInput(s)
}
if s == key.TextInputFocus {
w.Redraw()
}
w.inputState = s
}
func (w *Window) Err() error {
return w.err
}
func (w *Window) Draw(root ui.Op) {
if !w.IsAlive() {
return
}
w.mu.Lock()
stage := w.stage
sync := w.syncGPU
size := w.size
w.hasNextFrame = false
w.syncGPU = false
w.mu.Unlock()
if stage < StageVisible {
return
}
if w.gpu != nil {
if sync {
w.gpu.Refresh()
}
if err := w.gpu.Flush(); err != nil {
w.gpu.Release()
w.gpu = nil
}
}
if w.gpu == nil {
ctx, err := newContext(w.driver)
if err != nil {
w.err = err
return
}
w.gpu, err = gpu.NewGPU(ctx)
if err != nil {
w.err = err
return
}
}
now := time.Now()
frameDur := now.Sub(w.lastFrame)
frameDur = frameDur.Truncate(100 * time.Microsecond)
w.lastFrame = now
if w.Profiling {
w.timings = fmt.Sprintf("t:%7s %s", frameDur, w.gpu.Timings())
w.setNextFrame(time.Time{})
}
if t, ok := collectRedraws(root); ok {
w.setNextFrame(t)
}
w.updateAnimation()
w.gpu.Draw(w.Profiling, size, root)
}
func (w *Window) Redraw() {
if !w.IsAlive() {
return
}
w.setNextFrame(time.Time{})
w.updateAnimation()
}
func (w *Window) updateAnimation() {
w.mu.Lock()
defer w.mu.Unlock()
animate := false
if w.stage >= StageVisible && w.hasNextFrame {
if dt := time.Until(w.nextFrame); dt <= 0 {
animate = true
} else {
w.delayedDraw = time.AfterFunc(dt, w.Redraw)
}
}
if animate != w.animating {
w.animating = animate
w.driver.setAnimating(animate)
}
}
func (w *Window) setNextFrame(at time.Time) {
w.mu.Lock()
defer w.mu.Unlock()
if !w.hasNextFrame || at.Before(w.nextFrame) {
if w.delayedDraw != nil {
w.delayedDraw.Stop()
w.delayedDraw = nil
}
w.hasNextFrame = true
w.nextFrame = at
}
}
func (w *Window) Size() image.Point {
w.mu.Lock()
defer w.mu.Unlock()
return w.size
}
func (w *Window) Stage() Stage {
w.mu.Lock()
defer w.mu.Unlock()
return w.stage
}
func (w *Window) IsAlive() bool {
return w.Stage() != StageDead && w.err == nil
}
func (w *Window) contextDriver() interface{} {
return w.driver
}
func (w *Window) event(e Event) {
w.mu.Lock()
needAck := false
needRedraw := false
switch e := e.(type) {
case pointer.Event:
needRedraw = true
case key.Event:
needRedraw = true
case ChangeStage:
needAck = true
w.stage = e.Stage
w.syncGPU = true
case Draw:
if e.Size == (image.Point{}) {
panic(errors.New("internal error: zero-sized Draw"))
}
if w.stage < StageVisible {
// No drawing if not visible.
break
}
needAck = true
w.syncGPU = e.sync
w.size = e.Size
}
if !needAck {
w.skipAcks++
}
stage := w.stage
w.mu.Unlock()
if needRedraw {
w.setNextFrame(time.Time{})
}
w.updateAnimation()
w.events <- e
if needAck {
<-w.acks
}
if stage == StageDead {
close(w.events)
}
}
func collectRedraws(op ui.Op) (time.Time, bool) {
type childOp interface {
ChildOp() ui.Op
}
switch op := op.(type) {
case ui.Ops:
var earliest time.Time
var valid bool
for _, op := range op {
if t, ok := collectRedraws(op); ok {
if !valid || t.Before(earliest) {
valid = true
earliest = t
}
}
}
return earliest, valid
case ui.OpRedraw:
return op.At, true
case childOp:
return collectRedraws(op.ChildOp())
default:
return time.Time{}, false
}
}
+51
View File
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: Unlicense OR MIT
package draw
import (
"image"
"math"
"gioui.org/ui/f32"
"gioui.org/ui/internal/path"
"gioui.org/ui"
)
type OpImage struct {
Rect f32.Rectangle
Src image.Image
SrcRect image.Rectangle
}
func (OpImage) ImplementsOp() {}
// ClipRect returns a special case of OpClip
// that clips to a pixel aligned rectangular area.
func ClipRect(r image.Rectangle, op ui.Op) OpClip {
return OpClip{
Path: &Path{
data: &path.Path{
Bounds: toRectF(r),
},
},
Op: op,
}
}
func itof(i int) float32 {
switch i {
case ui.Inf:
return float32(math.Inf(+1))
case -ui.Inf:
return float32(math.Inf(-1))
default:
return float32(i)
}
}
func toRectF(r image.Rectangle) f32.Rectangle {
return f32.Rectangle{
Min: f32.Point{X: itof(r.Min.X), Y: itof(r.Min.Y)},
Max: f32.Point{X: itof(r.Max.X), Y: itof(r.Max.Y)},
}
}
+267
View File
@@ -0,0 +1,267 @@
// SPDX-License-Identifier: Unlicense OR MIT
package draw
import (
"math"
"gioui.org/ui/f32"
"gioui.org/ui/internal/path"
"gioui.org/ui"
)
type OpClip struct {
Path *Path
Op ui.Op
}
type Path struct {
data *path.Path
}
type PathBuilder struct {
verts []path.Vertex
firstVert int
maxy float32
pen f32.Point
bounds f32.Rectangle
hasBounds bool
}
// Data is for internal use only.
func (p *Path) Data() interface{} {
return p.data
}
func (p OpClip) ChildOp() ui.Op {
return p.Op
}
func (p OpClip) ImplementsOp() {}
// MoveTo moves the pen to the given position.
func (p *PathBuilder) Move(to f32.Point) {
p.end()
to = to.Add(p.pen)
p.maxy = to.Y
p.pen = to
}
// end completes the current contour.
func (p *PathBuilder) end() {
// Fill in maximal Y coordinates of the NW and NE corners
// and offset their curve coordinates.
for i := p.firstVert; i < len(p.verts); i++ {
p.verts[i].MaxY = p.maxy
}
p.firstVert = len(p.verts)
}
// Line records a line from the pen to end.
func (p *PathBuilder) Line(to f32.Point) {
to = to.Add(p.pen)
p.lineTo(to)
}
func (p *PathBuilder) lineTo(to f32.Point) {
// Model lines as degenerate quadratic beziers.
p.quadTo(to.Add(p.pen).Mul(.5), to)
}
// Quad records a quadratic bezier from the pen to end
// with the control point ctrl.
func (p *PathBuilder) Quad(ctrl, to f32.Point) {
ctrl = ctrl.Add(p.pen)
to = to.Add(p.pen)
p.quadTo(ctrl, to)
}
func (p *PathBuilder) quadTo(ctrl, to f32.Point) {
// Zero width curve don't contribute to stenciling.
if p.pen.X == to.X && p.pen.X == ctrl.X {
p.pen = to
return
}
bounds := f32.Rectangle{
Min: p.pen,
Max: to,
}.Canon()
// If the curve contain areas where a vertical line
// intersects it twice, split the curve in two x monotone
// lower and upper curves. The stencil fragment program
// expects only one intersection per curve.
// Find the t where the derivative in x is 0.
v0 := ctrl.Sub(p.pen)
v1 := to.Sub(ctrl)
d := v0.X - v1.X
// t = v0 / d. Split if t is in ]0;1[.
if v0.X > 0 && d > v0.X || v0.X < 0 && d < v0.X {
t := v0.X / d
ctrl0 := p.pen.Mul(1 - t).Add(ctrl.Mul(t))
ctrl1 := ctrl.Mul(1 - t).Add(to.Mul(t))
mid := ctrl0.Mul(1 - t).Add(ctrl1.Mul(t))
p.simpleQuadTo(ctrl0, mid)
p.simpleQuadTo(ctrl1, to)
if mid.X > bounds.Max.X {
bounds.Max.X = mid.X
}
if mid.X < bounds.Min.X {
bounds.Min.X = mid.X
}
} else {
p.simpleQuadTo(ctrl, to)
}
// Find the y extremum, if any.
d = v0.Y - v1.Y
if v0.Y > 0 && d > v0.Y || v0.Y < 0 && d < v0.Y {
t := v0.Y / d
y := (1-t)*(1-t)*p.pen.Y + 2*(1-t)*t*ctrl.Y + t*t*to.Y
if y > bounds.Max.Y {
bounds.Max.Y = y
}
if y < bounds.Min.Y {
bounds.Min.Y = y
}
}
p.expand(bounds)
}
// Cube records a cubic bezier from the pen through
// two control points ending in to.
func (p *PathBuilder) Cube(ctrl0, ctrl1, to f32.Point) {
ctrl0 = ctrl0.Add(p.pen)
ctrl1 = ctrl1.Add(p.pen)
to = to.Add(p.pen)
// Set the maximum distance proportionally to the longest side
// of the bounding rectangle.
hull := f32.Rectangle{
Min: p.pen,
Max: ctrl0,
}.Canon().Add(ctrl1).Add(to)
l := hull.Dx()
if h := hull.Dy(); h > l {
l = h
}
p.approxCubeTo(0, l*0.001, ctrl0, ctrl1, to)
}
// approxCube approximates a cubic beziér by a series of quadratic
// curves.
func (p *PathBuilder) approxCubeTo(splits int, maxDist float32, ctrl0, ctrl1, to f32.Point) int {
// The idea is from
// https://caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html
// where a quadratic approximates a cubic by eliminating its t³ term
// from its polynomial expression anchored at the starting point:
//
// P(t) = pen + 3t(ctrl0 - pen) + 3t²(ctrl1 - 2ctrl0 + pen) + t³(to - 3ctrl1 + 3ctrl0 - pen)
//
// The control point for the new quadratic Q1 that shares starting point, pen, with P is
//
// C1 = (3ctrl0 - pen)/2
//
// The reverse cubic that is 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 beziér, Q, shares both start and end points with its cubic
// and use the midpoint between the two curves Q1 and Q2 as control point:
//
// C = (3ctrl0 - pen + 3ctrl1 - to)/4
c := ctrl0.Mul(3).Sub(p.pen).Add(ctrl1.Mul(3)).Sub(to).Mul(1.0 / 4.0)
const maxSplits = 32
if splits >= maxSplits {
p.quadTo(c, to)
return splits
}
// The maximum distance between the cubic P and its approximation Q given t
// can be shown to be
//
// d = sqrt(3)/36*|to - 3ctrl1 + 3ctrl0 - pen|
//
// To save a square root, compare d² with the squared tolerance.
v := to.Sub(ctrl1.Mul(3)).Add(ctrl0.Mul(3)).Sub(p.pen)
d2 := (v.X*v.X + v.Y*v.Y) * 3 / (36 * 36)
if d2 <= maxDist*maxDist {
p.quadTo(c, to)
return splits
}
// De Casteljau split the curve and approximate the halves.
t := float32(0.5)
c0 := p.pen.Add(ctrl0.Sub(p.pen).Mul(t))
c1 := ctrl0.Add(ctrl1.Sub(ctrl0).Mul(t))
c2 := ctrl1.Add(to.Sub(ctrl1).Mul(t))
c01 := c0.Add(c1.Sub(c0).Mul(t))
c12 := c1.Add(c2.Sub(c1).Mul(t))
c0112 := c01.Add(c12.Sub(c01).Mul(t))
splits++
splits = p.approxCubeTo(splits, maxDist, c0, c01, c0112)
splits = p.approxCubeTo(splits, maxDist, c12, c2, to)
return splits
}
func (p *PathBuilder) expand(b f32.Rectangle) {
if !p.hasBounds {
p.hasBounds = true
inf := float32(math.Inf(+1))
p.bounds = f32.Rectangle{
Min: f32.Point{X: inf, Y: inf},
Max: f32.Point{X: -inf, Y: -inf},
}
}
p.bounds = p.bounds.Union(b)
}
func (p *PathBuilder) vertex(cornerx, cornery int16, ctrl, to f32.Point) {
p.verts = append(p.verts, path.Vertex{
CornerX: cornerx,
CornerY: cornery,
FromX: p.pen.X,
FromY: p.pen.Y,
CtrlX: ctrl.X,
CtrlY: ctrl.Y,
ToX: to.X,
ToY: to.Y,
})
}
func (p *PathBuilder) simpleQuadTo(ctrl, to f32.Point) {
if p.pen.Y > p.maxy {
p.maxy = p.pen.Y
}
if ctrl.Y > p.maxy {
p.maxy = ctrl.Y
}
if to.Y > p.maxy {
p.maxy = to.Y
}
// NW.
p.vertex(-1, 1, ctrl, to)
// NE.
p.vertex(1, 1, ctrl, to)
// SW.
p.vertex(-1, -1, ctrl, to)
// SE.
p.vertex(1, -1, ctrl, to)
p.pen = to
}
func (p *PathBuilder) Path() *Path {
p.end()
data := &Path{
data: &path.Path{
Bounds: p.bounds,
},
}
if !p.bounds.Empty() {
data.data.Vertices = p.verts
}
return data
}
+95
View File
@@ -0,0 +1,95 @@
// SPDX-License-Identifier: Unlicense OR MIT
package f32
type Point struct {
X, Y float32
}
type Rectangle struct {
Min, Max Point
}
func (p Point) Add(p2 Point) Point {
return Point{X: p.X + p2.X, Y: p.Y + p2.Y}
}
func (p Point) Sub(p2 Point) Point {
return Point{X: p.X - p2.X, Y: p.Y - p2.Y}
}
func (p Point) Mul(s float32) Point {
return Point{X: p.X * s, Y: p.Y * s}
}
func (r Rectangle) Size() Point {
return Point{X: r.Dx(), Y: r.Dy()}
}
func (r Rectangle) Dx() float32 {
return r.Max.X - r.Min.X
}
func (r Rectangle) Dy() float32 {
return r.Max.Y - r.Min.Y
}
func (r Rectangle) Intersect(s Rectangle) Rectangle {
if r.Min.X < s.Min.X {
r.Min.X = s.Min.X
}
if r.Min.Y < s.Min.Y {
r.Min.Y = s.Min.Y
}
if r.Max.X > s.Max.X {
r.Max.X = s.Max.X
}
if r.Max.Y > s.Max.Y {
r.Max.Y = s.Max.Y
}
return r
}
func (r Rectangle) Union(s Rectangle) Rectangle {
if r.Min.X > s.Min.X {
r.Min.X = s.Min.X
}
if r.Min.Y > s.Min.Y {
r.Min.Y = s.Min.Y
}
if r.Max.X < s.Max.X {
r.Max.X = s.Max.X
}
if r.Max.Y < s.Max.Y {
r.Max.Y = s.Max.Y
}
return r
}
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
}
func (r Rectangle) Empty() bool {
return r.Min.X >= r.Max.X || r.Min.Y >= r.Max.Y
}
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},
}
}
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},
}
}
+324
View File
@@ -0,0 +1,324 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gesture
import (
"math"
"strconv"
"strings"
"time"
)
// Estimator 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 estimator struct {
// Index into points.
idx int
// Circular buffer of samples.
samples []sample
// 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
)
// Sample adds a sample to the estimation.
func (e *estimator) Sample(t time.Duration, val float32) {
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 *estimator) Estimate() estimate {
if len(e.samples) == 0 {
return estimate{}
}
values := e.values[:0]
times := e.times[:0]
first := e.get(0)
t := first.t
// Walk backwards collecting samples.
for i := 0; i < len(e.samples); i++ {
p := e.get(-i)
age := first.t - p.t
if age >= maxAge || t-p.t >= maxSampleGap {
// If the samples are too old or
// too much time passed between samples
// assume they're not part of the fling.
break
}
t = p.t
values = append(values, first.v-p.v)
times = append(times, float32((-age).Seconds()))
}
coef, ok := polyFit(times, values)
if !ok {
return estimate{}
}
dist := values[len(values)-1] - values[0]
return estimate{
Velocity: coef[1],
Distance: dist,
}
}
func (e *estimator) get(i int) sample {
idx := (e.idx + i - 1 + len(e.samples)) % len(e.samples)
return e.samples[idx]
}
// fit computes the least squares polynomial fit for
// the set of points in X, Y. If the fitting fails
// because of contradicting or insufficient data,
// fit returns false.
func polyFit(X, Y []float32) (coefficients, bool) {
if len(X) != len(Y) {
panic("X and Y lengths differ")
}
if len(X) <= degree {
// Not enough points to fit a curve.
return coefficients{}, false
}
// Use a method similar to Android's VelocityTracker.cpp:
// https://android.googlesource.com/platform/frameworks/base/+/56a2301/libs/androidfw/VelocityTracker.cpp
// where all weights are 1.
// First, expand the X vector to the matrix A in column-major order.
A := newMatrix(degree+1, len(X))
for i, x := range X {
A.set(0, i, 1)
for j := 1; j < A.rows; j++ {
A.set(j, i, A.get(j-1, i)*x)
}
}
Q, Rt, ok := decomposeQR(A)
if !ok {
return coefficients{}, false
}
// Solve R*B = Qt*Y for B, which is then the polynomial coefficients.
// Since R is upper triangular, we can proceed from bottom right to
// upper left.
// https://en.wikipedia.org/wiki/Non-linear_least_squares
var B coefficients
for i := Q.rows - 1; i >= 0; i-- {
B[i] = dot(Q.col(i), Y)
for j := Q.rows - 1; j > i; j-- {
B[i] -= Rt.get(i, j) * B[j]
}
B[i] /= Rt.get(i, i)
}
return B, true
}
// decomposeQR computes and returns Q, Rt where Q*transpose(Rt) = A, if
// possible. R is guaranteed to be upper triangular and only the square
// part of Rt is returned.
func decomposeQR(A *matrix) (*matrix, *matrix, bool) {
// Gram-Schmidt QR decompose A where Q*R = A.
// https://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process
Q := newMatrix(A.rows, A.cols) // Column-major.
Rt := newMatrix(A.rows, A.rows) // R transposed, row-major.
for i := 0; i < Q.rows; i++ {
// Copy A column.
for j := 0; j < Q.cols; j++ {
Q.set(i, j, A.get(i, j))
}
// Subtract projections. Note that int the projection
//
// proju a = <u, a>/<u, u> u
//
// the normalized column e replaces u, where <e, e> = 1:
//
// proje a = <e, a>/<e, e> e = <e, a> e
for j := 0; j < i; j++ {
d := dot(Q.col(j), Q.col(i))
for k := 0; k < Q.cols; k++ {
Q.set(i, k, Q.get(i, k)-d*Q.get(j, k))
}
}
// Normalize Q columns.
n := norm(Q.col(i))
if n < 0.000001 {
// Degenerate data, no solution.
return nil, nil, false
}
invNorm := 1 / n
for j := 0; j < Q.cols; j++ {
Q.set(i, j, Q.get(i, j)*invNorm)
}
// Update Rt.
for j := i; j < Rt.cols; j++ {
Rt.set(i, j, dot(Q.col(i), A.col(j)))
}
}
return Q, Rt, true
}
func norm(V []float32) float32 {
var n float32
for _, v := range V {
n += v * v
}
return float32(math.Sqrt(float64(n)))
}
func dot(V1, V2 []float32) float32 {
var d float32
for i, v1 := range V1 {
d += v1 * V2[i]
}
return d
}
func newMatrix(rows, cols int) *matrix {
return &matrix{
rows: rows,
cols: cols,
data: make([]float32, rows*cols),
}
}
func (m *matrix) set(row, col int, v float32) {
if row < 0 || row >= m.rows {
panic("row out of range")
}
if col < 0 || col >= m.cols {
panic("col out of range")
}
m.data[row*m.cols+col] = v
}
func (m *matrix) get(row, col int) float32 {
if row < 0 || row >= m.rows {
panic("row out of range")
}
if col < 0 || col >= m.cols {
panic("col out of range")
}
return m.data[row*m.cols+col]
}
func (m *matrix) col(c int) []float32 {
return m.data[c*m.cols : (c+1)*m.cols]
}
func (m *matrix) approxEqual(m2 *matrix) bool {
if m.rows != m2.rows || m.cols != m2.cols {
return false
}
const epsilon = 0.00001
for row := 0; row < m.rows; row++ {
for col := 0; col < m.cols; col++ {
d := m2.get(row, col) - m.get(row, col)
if d < -epsilon || d > epsilon {
return false
}
}
}
return true
}
func (m *matrix) transpose() *matrix {
t := &matrix{
rows: m.cols,
cols: m.rows,
data: make([]float32, len(m.data)),
}
for i := 0; i < m.rows; i++ {
for j := 0; j < m.cols; j++ {
t.set(j, i, m.get(i, j))
}
}
return t
}
func (m *matrix) mul(m2 *matrix) *matrix {
if m.rows != m2.cols {
panic("mismatched matrices")
}
mm := &matrix{
rows: m.rows,
cols: m2.cols,
data: make([]float32, m.rows*m2.cols),
}
for i := 0; i < mm.rows; i++ {
for j := 0; j < mm.cols; j++ {
var v float32
for k := 0; k < m.rows; k++ {
v += m.get(k, j) * m2.get(i, k)
}
mm.set(i, j, v)
}
}
return mm
}
func (m *matrix) String() string {
var b strings.Builder
for i := 0; i < m.rows; i++ {
for j := 0; j < m.cols; j++ {
v := m.get(i, j)
b.WriteString(strconv.FormatFloat(float64(v), 'g', -1, 32))
b.WriteString(", ")
}
b.WriteString("\n")
}
return b.String()
}
func (c coefficients) approxEqual(c2 coefficients) bool {
const epsilon = 0.00001
for i, v := range c {
d := v - c2[i]
if d < -epsilon || d > epsilon {
return false
}
}
return true
}
+43
View File
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gesture
import "testing"
func TestDecomposeQR(t *testing.T) {
A := &matrix{
rows: 3, cols: 3,
data: []float32{
12, 6, -4,
-51, 167, 24,
4, -68, -41,
},
}
Q, Rt, ok := decomposeQR(A)
if !ok {
t.Fatal("decomposeQR failed")
}
R := Rt.transpose()
QR := Q.mul(R)
if !A.approxEqual(QR) {
t.Log("A\n", A)
t.Log("Q\n", Q)
t.Log("R\n", R)
t.Log("QR\n", QR)
t.Fatal("Q*R not approximately equal to A")
}
}
func TestFit(t *testing.T) {
X := []float32{-1, 0, 1}
Y := []float32{2, 0, 2}
got, ok := polyFit(X, Y)
if !ok {
t.Fatal("polyFit failed")
}
want := coefficients{0, 0, 2}
if !got.approxEqual(want) {
t.Fatalf("polyFit: got %v want %v", got, want)
}
}
+317
View File
@@ -0,0 +1,317 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gesture
import (
"image"
"math"
"runtime"
"time"
"gioui.org/ui/f32"
"gioui.org/ui/pointer"
"gioui.org/ui"
)
type ClickEvent struct {
Type ClickType
Position f32.Point
}
type ClickState uint8
type ClickType uint8
type Click struct {
State ClickState
}
type Scroll struct {
dragging bool
axis Axis
estimator estimator
flinger flinger
pid pointer.ID
grab bool
last int
// Leftover scroll.
scroll float32
}
type flinger struct {
// Current offset in pixels.
x float32
// Initial time.
t0 time.Time
// Initial velocity in pixels pr second.
v0 float32
}
type Axis uint8
const (
Horizontal Axis = iota
Vertical
)
const (
StateNormal ClickState = iota
StateFocused
StatePressed
)
const (
TypePress ClickType = iota
TypeClick
)
var (
touchSlop = ui.Dp(3)
// Pixels/second.
minFlingVelocity = ui.Dp(50)
maxFlingVelocity = ui.Dp(8000)
)
const (
thresholdVelocity = 1
)
func (c *Click) Op(a pointer.Area) pointer.OpHandler {
return pointer.OpHandler{Area: a, Key: c}
}
func (c *Click) Update(q pointer.Events) []ClickEvent {
var events []ClickEvent
for _, e := range q.For(c) {
switch e.Type {
case pointer.Release:
if c.State == StatePressed {
events = append(events, ClickEvent{Type: TypeClick, Position: e.Position})
}
c.State = StateNormal
case pointer.Cancel:
c.State = StateNormal
case pointer.Press:
if c.State == StatePressed || !e.Hit {
break
}
c.State = StatePressed
events = append(events, ClickEvent{Type: TypePress, Position: e.Position})
case pointer.Move:
if c.State == StatePressed && !e.Hit {
c.State = StateNormal
} else if c.State < StateFocused {
c.State = StateFocused
}
}
}
return events
}
func (s *Scroll) Op(a pointer.Area) ui.Op {
oph := pointer.OpHandler{Area: a, Key: s, Grab: s.grab}
if !s.flinger.Active() {
return oph
}
return ui.Ops{oph, ui.OpRedraw{}}
}
func (s *Scroll) Stop() {
s.flinger = flinger{}
}
func (s *Scroll) Dragging() bool {
return s.dragging
}
func (s *Scroll) Scroll(cfg *ui.Config, q pointer.Events, axis Axis) int {
if s.axis != axis {
s.axis = axis
return 0
}
total := 0
for _, e := range q.For(s) {
switch e.Type {
case pointer.Press:
if s.dragging || e.Source != pointer.Touch {
break
}
s.Stop()
s.estimator = estimator{}
v := s.val(e.Position)
s.last = int(math.Round(float64(v)))
s.estimator.Sample(e.Time, v)
s.dragging = true
s.pid = e.PointerID
case pointer.Release:
if s.pid != e.PointerID {
break
}
fling := s.estimator.Estimate()
if slop, d := cfg.Pixels(touchSlop), fling.Distance; d >= slop || -slop >= d {
if min, v := cfg.Pixels(minFlingVelocity), fling.Velocity; v >= min || -min >= v {
max := cfg.Pixels(maxFlingVelocity)
if v > max {
v = max
} else if v < -max {
v = -max
}
s.flinger.Init(cfg.Now, v)
}
}
fallthrough
case pointer.Cancel:
s.dragging = false
s.grab = false
case pointer.Move:
// Scroll
switch s.axis {
case Horizontal:
s.scroll += e.Scroll.X
case Vertical:
s.scroll += e.Scroll.Y
}
iscroll := int(math.Round(float64(s.scroll)))
s.scroll -= float32(iscroll)
total += iscroll
if !s.dragging || s.pid != e.PointerID {
continue
}
// Drag
val := s.val(e.Position)
s.estimator.Sample(e.Time, val)
v := int(math.Round(float64(val)))
dist := s.last - v
if e.Priority < pointer.Grabbed {
slop := cfg.Pixels(touchSlop)
if dist := float32(dist); dist >= slop || -slop >= dist {
s.grab = true
}
} else {
s.last = v
total += dist
}
}
}
total += s.flinger.Tick(cfg.Now)
return total
}
func (s *Scroll) val(p f32.Point) float32 {
if s.axis == Horizontal {
return p.X
} else {
return p.Y
}
}
func (f *flinger) Init(now time.Time, v0 float32) {
f.t0 = now
f.v0 = v0
f.x = 0
}
func (f *flinger) Active() bool {
return f.v0 != 0
}
// Tick computes and returns a fling distance since
// the last time Tick was called.
func (f *flinger) Tick(now time.Time) int {
if !f.Active() {
return 0
}
var k float32
if runtime.GOOS == "darwin" {
k = -2 // iOS
} else {
k = -4.2 // Android and default
}
t := now.Sub(f.t0)
// The acceleration x''(t) of a point mass with a drag
// force, f, proportional with velocity, x'(t), is
// governed by the equation
//
// x''(t) = kx'(t)
//
// Given the starting position x(0) = 0, the starting
// velocity x'(0) = v0, the position is then
// given by
//
// x(t) = v0*e^(k*t)/k - v0/k
//
ekt := float32(math.Exp(float64(k) * t.Seconds()))
x := f.v0*ekt/k - f.v0/k
dist := x - f.x
idist := int(math.Round(float64(dist)))
f.x += float32(idist)
// Solving for the velocity x'(t) gives us
//
// x'(t) = v0*e^(k*t)
v := f.v0 * ekt
if v < thresholdVelocity && v > -thresholdVelocity {
f.v0 = 0
}
return idist
}
func Rect(sz image.Point) pointer.Area {
return func(pos f32.Point) pointer.HitResult {
if 0 <= pos.X && pos.X < float32(sz.X) &&
0 <= pos.Y && pos.Y < float32(sz.Y) {
return pointer.HitOpaque
} else {
return pointer.HitNone
}
}
}
func Ellipse(sz image.Point) pointer.Area {
return func(pos f32.Point) pointer.HitResult {
rx := float32(sz.X) / 2
ry := float32(sz.Y) / 2
rx2 := rx * rx
ry2 := ry * ry
xh := pos.X - rx
yk := pos.Y - ry
if xh*xh*ry2+yk*yk*rx2 <= rx2*ry2 {
return pointer.HitOpaque
} else {
return pointer.HitNone
}
}
}
func (a Axis) String() string {
switch a {
case Horizontal:
return "Horizontal"
case Vertical:
return "Vertical"
default:
panic("invalid Axis")
}
}
func (ct ClickType) String() string {
switch ct {
case TypePress:
return "TypePress"
case TypeClick:
return "TypeClick"
default:
panic("invalid ClickType")
}
}
func (cs ClickState) String() string {
switch cs {
case StateNormal:
return "StateNormal"
case StateFocused:
return "StateFocused"
case StatePressed:
return "StatePressed"
default:
panic("invalid ClickState")
}
}
+8
View File
@@ -0,0 +1,8 @@
module gioui.org/ui
require (
golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f
golang.org/x/sys v0.0.0-20190329044733-9eb1bfa1ce65
)
go 1.12
+5
View File
@@ -0,0 +1,5 @@
golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f h1:FO4MZ3N56GnxbqxGKqh+YTzUWQ2sDwtFQEZgLOxh9Jc=
golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/sys v0.0.0-20190329044733-9eb1bfa1ce65 h1:hOY+O8MxdkPV10pNf7/XEHaySCiPKxixMKUshfHsGn0=
golang.org/x/sys v0.0.0-20190329044733-9eb1bfa1ce65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+33
View File
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: Unlicense OR MIT
package path
import (
"unsafe"
"gioui.org/ui/f32"
)
type Path struct {
Vertices []Vertex
Bounds f32.Rectangle
}
// The vertex data suitable for passing to vertex programs.
type Vertex struct {
CornerX, CornerY int16
MaxY float32
FromX, FromY float32
CtrlX, CtrlY float32
ToX, ToY float32
}
const VertStride = 7*4 + 2*2
func init() {
// Check that struct vertex has the expected size and
// that it contains no padding.
if unsafe.Sizeof(*(*Vertex)(nil)) != VertStride {
panic("unexpected struct size")
}
}
+74
View File
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: Unlicense OR MIT
package key
type OpHandler struct {
Key Key
Focus bool
}
type OpHideInput struct{}
type Key interface{}
type Events interface {
For(k Key) []Event
}
type Event interface {
isKeyEvent()
}
type Focus struct {
Focus bool
}
type Chord struct {
Name rune
Modifiers Modifiers
}
type Edit struct {
Text string
}
type Modifiers uint32
type TextInputState uint8
const (
ModCommand Modifiers = 1 << iota
)
const (
TextInputKeep TextInputState = iota
TextInputFocus
TextInputClosed
TextInputOpen
)
const (
NameLeftArrow = '←'
NameRightArrow = '→'
NameUpArrow = '↑'
NameDownArrow = '↓'
NameReturn = '⏎'
NameEnter = '⌤'
NameEscape = '⎋'
NameHome = '⇱'
NameEnd = '⇲'
NameDeleteBackward = '⌫'
NameDeleteForward = '⌦'
NamePageUp = '⇞'
NamePageDown = '⇟'
)
func (OpHandler) ImplementsOp() {}
func (OpHideInput) ImplementsOp() {}
func (Edit) ImplementsEvent() {}
func (Chord) ImplementsEvent() {}
func (Focus) ImplementsEvent() {}
func (Edit) isKeyEvent() {}
func (Chord) isKeyEvent() {}
func (Focus) isKeyEvent() {}
+109
View File
@@ -0,0 +1,109 @@
// SPDX-License-Identifier: Unlicense OR MIT
package key
import (
"gioui.org/ui"
)
type Queue struct {
focus Key
events []Event
handlers map[Key]bool
}
type listenerPriority uint8
const (
priNone listenerPriority = iota
priDefault
priCurrentFocus
priNewFocus
)
func (q *Queue) Frame(op ui.Op) TextInputState {
q.events = q.events[:0]
f, pri, hide := resolveFocus(op, q.focus)
changed := f != nil && f != q.focus
for k, active := range q.handlers {
if !active || changed {
delete(q.handlers, k)
} else {
q.handlers[k] = false
}
}
q.focus = f
switch {
case pri == priNewFocus:
return TextInputOpen
case hide:
return TextInputClosed
case changed:
return TextInputFocus
default:
return TextInputKeep
}
}
func (q *Queue) Push(e Event) {
q.events = append(q.events, e)
}
func (q *Queue) For(k Key) []Event {
if q.handlers == nil {
q.handlers = make(map[Key]bool)
}
_, exists := q.handlers[k]
q.handlers[k] = true
if !exists {
if k == q.focus {
// Prepend focus event.
q.events = append(q.events, nil)
copy(q.events[1:], q.events)
q.events[0] = Focus{Focus: true}
} else {
return []Event{Focus{Focus: false}}
}
}
if k != q.focus {
return nil
}
return q.events
}
func resolveFocus(op ui.Op, focus Key) (Key, listenerPriority, bool) {
type childOp interface {
ChildOp() ui.Op
}
var k Key
var pri listenerPriority
var hide bool
switch op := op.(type) {
case ui.Ops:
for i := len(op) - 1; i >= 0; i-- {
newK, newPri, h := resolveFocus(op[i], focus)
hide = hide || h
if newPri > pri {
k, pri = newK, newPri
}
}
case OpHandler:
var newPri listenerPriority
switch {
case op.Focus:
newPri = priNewFocus
case op.Key == focus:
newPri = priCurrentFocus
default:
newPri = priDefault
}
if newPri > pri {
k, pri = op.Key, newPri
}
case OpHideInput:
hide = true
case childOp:
return resolveFocus(op.ChildOp(), focus)
}
return k, pri, hide
}
+254
View File
@@ -0,0 +1,254 @@
// SPDX-License-Identifier: Unlicense OR MIT
package layout
import (
"image"
"gioui.org/ui/f32"
"gioui.org/ui"
)
type Flex struct {
Axis Axis
MainAxisAlignment MainAxisAlignment
CrossAxisAlignment CrossAxisAlignment
MainAxisSize MainAxisSize
cs Constraints
children []flexChild
taken int
maxCross int
maxBaseline int
ccache [10]flexChild
opCache [10]ui.Op
}
type flexChild struct {
op ui.Op
dims Dimens
}
type MainAxisSize uint8
type FlexMode uint8
type MainAxisAlignment uint8
type CrossAxisAlignment uint8
const (
Loose FlexMode = iota
Fit
)
const (
Max MainAxisSize = iota
Min
)
const (
Start = 100 + iota
End
Center
SpaceAround MainAxisAlignment = iota
SpaceBetween
SpaceEvenly
Baseline CrossAxisAlignment = iota
Stretch
)
func (f *Flex) Init(cs Constraints) *Flex {
f.cs = cs
if f.children == nil {
f.children = f.ccache[:0]
}
f.children = f.children[:0]
f.maxCross = 0
f.maxBaseline = 0
return f
}
func (f *Flex) Rigid(w Widget) *Flex {
mainc := axisMainConstraint(f.Axis, f.cs)
mainMax := mainc.Max
if mainc.Max != ui.Inf {
mainMax -= f.taken
}
cs := axisConstraints(f.Axis, Constraint{Max: mainMax}, f.crossConstraintChild(f.cs))
op, dims := w.Layout(cs)
f.taken += axisMain(f.Axis, dims.Size)
if c := axisCross(f.Axis, dims.Size); c > f.maxCross {
f.maxCross = c
}
if b := dims.Baseline; b > f.maxBaseline {
f.maxBaseline = b
}
f.children = append(f.children, flexChild{op, dims})
return f
}
func (f *Flex) Flexible(idx int, flex float32, mode FlexMode, w Widget) *Flex {
mainc := axisMainConstraint(f.Axis, f.cs)
var flexSize int
if mainc.Max != ui.Inf && mainc.Max > f.taken {
flexSize = mainc.Max - f.taken
}
submainc := Constraint{Max: int(float32(flexSize) * flex)}
if mode == Fit {
submainc.Min = submainc.Max
}
cs := axisConstraints(f.Axis, submainc, f.crossConstraintChild(f.cs))
op, dims := w.Layout(cs)
f.taken += axisMain(f.Axis, dims.Size)
if c := axisCross(f.Axis, dims.Size); c > f.maxCross {
f.maxCross = c
}
if b := dims.Baseline; b > f.maxBaseline {
f.maxBaseline = b
}
f.children = append(f.children, flexChild{op, dims})
if idx < 0 {
idx += len(f.children)
}
f.children[idx], f.children[len(f.children)-1] = f.children[len(f.children)-1], f.children[idx]
return f
}
func (f *Flex) Layout() (ui.Op, Dimens) {
mainc := axisMainConstraint(f.Axis, f.cs)
crossSize := axisCrossConstraint(f.Axis, f.cs).Constrain(f.maxCross)
var space int
if mainc.Max != ui.Inf && f.MainAxisSize == Max {
if mainc.Max > f.taken {
space = mainc.Max - f.taken
}
} else if mainc.Min > f.taken {
space = mainc.Min - f.taken
}
var mainSize int
var baseline int
switch f.MainAxisAlignment {
case Center:
mainSize += space / 2
case End:
mainSize += space
case SpaceEvenly:
mainSize += space / (1 + len(f.children))
case SpaceAround:
mainSize += space / (len(f.children) * 2)
}
var ops ui.Ops
if len(f.children) > len(f.opCache) {
ops = make([]ui.Op, len(f.children))
} else {
ops = f.opCache[:len(f.children)]
}
for i, child := range f.children {
dims := child.dims
b := dims.Baseline
var cross int
switch f.CrossAxisAlignment {
case End:
cross = crossSize - axisCross(f.Axis, dims.Size)
case Center:
cross = (crossSize - axisCross(f.Axis, dims.Size)) / 2
case Baseline:
if f.Axis == Horizontal {
cross = f.maxBaseline - b
}
}
off := ui.Offset(toPointF(axisPoint(f.Axis, mainSize, cross)))
ops[i] = ui.OpLayer{Op: ui.OpTransform{Transform: off, Op: child.op}}
mainSize += axisMain(f.Axis, dims.Size)
switch f.MainAxisAlignment {
case SpaceEvenly:
mainSize += space / (1 + len(f.children))
case SpaceAround:
mainSize += space / len(f.children)
case SpaceBetween:
mainSize += space / (len(f.children) - 1)
}
if b != dims.Size.Y {
baseline = b
}
}
switch f.MainAxisAlignment {
case Start:
mainSize += space
case SpaceEvenly:
mainSize += space / (1 + len(f.children))
case SpaceAround:
mainSize += space / (len(f.children) * 2)
}
sz := axisPoint(f.Axis, mainSize, crossSize)
if baseline == 0 {
baseline = sz.Y
}
return ops, Dimens{Size: sz, Baseline: baseline}
}
func axisPoint(a Axis, main, cross int) image.Point {
if a == Horizontal {
return image.Point{main, cross}
} else {
return image.Point{cross, main}
}
}
func axisMain(a Axis, sz image.Point) int {
if a == Horizontal {
return sz.X
} else {
return sz.Y
}
}
func axisCross(a Axis, sz image.Point) int {
if a == Horizontal {
return sz.Y
} else {
return sz.X
}
}
func axisMainConstraint(a Axis, cs Constraints) Constraint {
if a == Horizontal {
return cs.Width
} else {
return cs.Height
}
}
func axisCrossConstraint(a Axis, cs Constraints) Constraint {
if a == Horizontal {
return cs.Height
} else {
return cs.Width
}
}
func (f *Flex) crossConstraintChild(cs Constraints) Constraint {
c := axisCrossConstraint(f.Axis, cs)
switch f.CrossAxisAlignment {
case Stretch:
c.Min = c.Max
default:
c.Min = 0
}
return c
}
func axisConstraints(a Axis, mainc, crossc Constraint) Constraints {
if a == Horizontal {
return Constraints{mainc, crossc}
} else {
return Constraints{crossc, mainc}
}
}
func toPointF(p image.Point) f32.Point {
return f32.Point{X: float32(p.X), Y: float32(p.Y)}
}
+206
View File
@@ -0,0 +1,206 @@
// SPDX-License-Identifier: Unlicense OR MIT
package layout
import (
"image"
"gioui.org/ui/draw"
"gioui.org/ui/gesture"
"gioui.org/ui/pointer"
"gioui.org/ui"
)
type scrollChild struct {
op ui.Op
size image.Point
}
type List struct {
Axis Axis
CrossAxisAlignment CrossAxisAlignment
// The distance scrolled since last call to Init.
Distance int
scroll gesture.Scroll
scrollDir int
offset int
first int
cs Constraints
len int
maxSize int
children []scrollChild
elem func(w Widget)
size image.Point
ops ui.Ops
}
type Interface interface {
Len() int
At(i int) Widget
}
func (l *List) Init(cs Constraints, len int) (int, bool) {
l.maxSize = 0
l.children = l.children[:0]
l.cs = cs
l.len = len
l.elem = nil
if l.first > len {
l.first = len
}
l.ops = l.ops[:0]
if len == 0 {
return 0, false
}
return l.Index()
}
func (l *List) Dragging() bool {
return l.scroll.Dragging()
}
func (l *List) Scroll(c *ui.Config, q pointer.Events) {
l.Distance = 0
d := l.scroll.Scroll(c, q, gesture.Axis(l.Axis))
l.scrollDir = d
l.Distance += d
l.offset += d
}
func (l *List) Index() (int, bool) {
i, ok := l.next()
if !ok {
l.draw()
}
return i, ok
}
func (l *List) Layout() (ui.Op, Dimens) {
ops := append(ui.Ops{l.scroll.Op(gesture.Rect(l.size))}, l.ops...)
return ops, Dimens{Size: l.size}
}
func (l *List) next() (int, bool) {
mainc := axisMainConstraint(l.Axis, l.cs)
if l.offset <= 0 {
if l.first > 0 {
l.elem = l.backward
return l.first - 1, true
}
l.offset = 0
}
if l.maxSize-l.offset < mainc.Max {
i := l.first + len(l.children)
if i < l.len {
l.elem = l.forward
return i, true
}
missing := mainc.Max - (l.maxSize - l.offset)
if missing > l.offset {
missing = l.offset
}
l.offset -= missing
}
return 0, false
}
func (l *List) Elem(w Widget) {
l.elem(w)
}
func (l *List) backward(w Widget) {
subcs := axisConstraints(l.Axis, Constraint{Max: ui.Inf}, l.crossConstraintChild(l.cs))
l.first--
op, dims := w.Layout(subcs)
mainSize := axisMain(l.Axis, dims.Size)
l.offset += mainSize
l.maxSize += mainSize
l.children = append([]scrollChild{{op, dims.Size}}, l.children...)
}
func (l *List) forward(w Widget) {
subcs := axisConstraints(l.Axis, Constraint{Max: ui.Inf}, l.crossConstraintChild(l.cs))
op, dims := w.Layout(subcs)
mainSize := axisMain(l.Axis, dims.Size)
l.maxSize += mainSize
l.children = append(l.children, scrollChild{op, dims.Size})
}
func (l *List) draw() {
mainc := axisMainConstraint(l.Axis, l.cs)
for len(l.children) > 0 {
sz := l.children[0].size
mainSize := axisMain(l.Axis, sz)
if l.offset <= mainSize {
break
}
l.first++
l.offset -= mainSize
l.children = l.children[1:]
}
size := -l.offset
var maxCross int
for i, child := range l.children {
sz := child.size
if c := axisCross(l.Axis, sz); c > maxCross {
maxCross = c
}
size += axisMain(l.Axis, sz)
if size >= mainc.Max {
l.children = l.children[:i+1]
break
}
}
pos := -l.offset
for _, child := range l.children {
sz := child.size
var cross int
switch l.CrossAxisAlignment {
case End:
cross = maxCross - axisCross(l.Axis, sz)
case Center:
cross = (maxCross - axisCross(l.Axis, sz)) / 2
}
max := axisMain(l.Axis, sz) + pos
if max > mainc.Max {
max = mainc.Max
}
min := pos
if min < 0 {
min = 0
}
op := draw.ClipRect(
image.Rectangle{
Min: axisPoint(l.Axis, min, -ui.Inf),
Max: axisPoint(l.Axis, max, ui.Inf),
},
ui.OpTransform{Transform: ui.Offset(toPointF(axisPoint(l.Axis, pos, cross))), Op: child.op},
)
l.ops = append(l.ops, ui.OpLayer{Op: op})
pos += axisMain(l.Axis, sz)
}
atStart := l.first == 0 && l.offset <= 0
atEnd := l.first+len(l.children) == l.len && mainc.Max >= pos
if atStart && l.scrollDir < 0 || atEnd && l.scrollDir > 0 {
l.scroll.Stop()
}
l.size = axisPoint(l.Axis, mainc.Constrain(pos), maxCross)
}
func (l *List) crossConstraintChild(cs Constraints) Constraint {
c := axisCrossConstraint(l.Axis, cs)
switch l.CrossAxisAlignment {
case Stretch:
c.Min = c.Max
default:
c.Min = 0
}
return c
}
+213
View File
@@ -0,0 +1,213 @@
// SPDX-License-Identifier: Unlicense OR MIT
package layout
import (
"image"
"math"
"gioui.org/ui"
)
type Widget interface {
Layout(cs Constraints) (ui.Op, Dimens)
}
type Constraints struct {
Width Constraint
Height Constraint
}
type Constraint struct {
Min, Max int
}
type Dimens struct {
Size image.Point
Baseline int
}
type Axis uint8
type F func(cs Constraints) (ui.Op, Dimens)
const (
Horizontal Axis = iota
Vertical
)
func (c Constraint) Constrain(v int) int {
if v < c.Min {
return c.Min
} else if v > c.Max {
return c.Max
}
return v
}
func (c Constraints) Constrain(p image.Point) image.Point {
return image.Point{X: c.Width.Constrain(p.X), Y: c.Height.Constrain(p.Y)}
}
func (c Constraints) Expand() Constraints {
return Constraints{Width: c.Width.Expand(), Height: c.Height.Expand()}
}
func (c Constraint) Expand() Constraint {
return Constraint{Min: c.Max, Max: c.Max}
}
func (c Constraints) Loose() Constraints {
return Constraints{Width: c.Width.Loose(), Height: c.Height.Loose()}
}
func (c Constraint) Loose() Constraint {
return Constraint{Max: c.Max}
}
// ExactConstraints returns the constraints that exactly represents the
// given dimensions.
func ExactConstraints(size image.Point) Constraints {
return Constraints{
Width: Constraint{Min: size.X, Max: size.X},
Height: Constraint{Min: size.Y, Max: size.Y},
}
}
func (f F) Layout(cs Constraints) (ui.Op, Dimens) {
return f(cs)
}
type Margins struct {
Top, Right, Bottom, Left ui.Value
}
func Margin(c *ui.Config, m Margins, w Widget) Widget {
return F(func(cs Constraints) (ui.Op, Dimens) {
mcs := cs
t, r, b, l := int(c.Pixels(m.Top)+0.5), int(c.Pixels(m.Right)+0.5), int(c.Pixels(m.Bottom)+0.5), int(c.Pixels(m.Left)+0.5)
if mcs.Width.Max != ui.Inf {
mcs.Width.Min -= l + r
mcs.Width.Max -= l + r
if mcs.Width.Min < 0 {
mcs.Width.Min = 0
}
if mcs.Width.Max < mcs.Width.Min {
mcs.Width.Max = mcs.Width.Min
}
}
if mcs.Height.Max != ui.Inf {
mcs.Height.Min -= t + b
mcs.Height.Max -= t + b
if mcs.Height.Min < 0 {
mcs.Height.Min = 0
}
if mcs.Height.Max < mcs.Height.Min {
mcs.Height.Max = mcs.Height.Min
}
}
op, dims := w.Layout(mcs)
op = ui.OpTransform{Transform: ui.Offset(toPointF(image.Point{X: l, Y: t})), Op: op}
return op, Dimens{
Size: cs.Constrain(dims.Size.Add(image.Point{X: r + l, Y: t + b})),
Baseline: dims.Baseline + t,
}
})
}
func EqualMargins(v ui.Value) Margins {
return Margins{Top: v, Right: v, Bottom: v, Left: v}
}
func isInf(v ui.Value) bool {
return math.IsInf(float64(v.V), 1)
}
func Capped(c *ui.Config, maxWidth, maxHeight ui.Value, wt Widget) Widget {
return F(func(cs Constraints) (ui.Op, Dimens) {
if !isInf(maxWidth) {
mw := int(c.Pixels(maxWidth) + .5)
if mw < cs.Width.Min {
mw = cs.Width.Min
}
if mw < cs.Width.Max {
cs.Width.Max = mw
}
}
if !isInf(maxHeight) {
mh := int(c.Pixels(maxHeight) + 0.5)
if mh < cs.Height.Min {
mh = cs.Height.Min
}
if mh < cs.Height.Max {
cs.Height.Max = mh
}
}
return wt.Layout(cs)
})
}
func Sized(c *ui.Config, width, height ui.Value, wt Widget) Widget {
return F(func(cs Constraints) (ui.Op, Dimens) {
if h := int(c.Pixels(height) + 0.5); h != 0 {
if cs.Height.Min < h {
cs.Height.Min = h
}
if h < cs.Height.Max {
cs.Height.Max = h
}
}
if w := int(c.Pixels(width) + .5); w != 0 {
if cs.Width.Min < w {
cs.Width.Min = w
}
if w < cs.Width.Max {
cs.Width.Max = w
}
}
return wt.Layout(cs)
})
}
func Expand(w Widget) Widget {
return F(func(cs Constraints) (ui.Op, Dimens) {
if cs.Height.Max != ui.Inf {
cs.Height.Min = cs.Height.Max
}
if cs.Width.Max != ui.Inf {
cs.Width.Min = cs.Width.Max
}
return w.Layout(cs)
})
}
func Align(alignment Direction, w Widget) Widget {
return F(func(cs Constraints) (ui.Op, Dimens) {
op, dims := w.Layout(cs.Loose())
sz := dims.Size
if cs.Width.Max != ui.Inf {
sz.X = cs.Width.Max
}
if cs.Height.Max != ui.Inf {
sz.Y = cs.Height.Max
}
var p image.Point
switch alignment {
case N, S, Center:
p.X = (sz.X - dims.Size.X) / 2
case NE, SE, E:
p.X = sz.X - dims.Size.X
}
switch alignment {
case W, Center, E:
p.Y = (sz.Y - dims.Size.Y) / 2
case SW, S, SE:
p.Y = sz.Y - dims.Size.Y
}
op = ui.OpTransform{Transform: ui.Offset(toPointF(p)), Op: op}
return op, Dimens{
Size: sz,
Baseline: dims.Baseline,
}
})
}
+118
View File
@@ -0,0 +1,118 @@
// SPDX-License-Identifier: Unlicense OR MIT
package layout
import (
"image"
"gioui.org/ui"
)
type Stack struct {
Alignment Direction
cs Constraints
children []stackChild
maxSZ image.Point
baseline int
ccache [10]stackChild
opCache [10]ui.Op
}
type stackChild struct {
op ui.Op
dims Dimens
}
type Direction uint8
const (
NW Direction = iota
N
NE
E
SE
S
SW
W
)
func (s *Stack) Init(cs Constraints) *Stack {
if s.children == nil {
s.children = s.ccache[:0]
}
s.children = s.children[:0]
s.maxSZ = image.Point{}
s.baseline = 0
s.cs = cs
return s
}
func (s *Stack) Rigid(w Widget) *Stack {
op, dims := w.Layout(s.cs)
if w := dims.Size.X; w > s.maxSZ.X {
s.maxSZ.X = w
}
if h := dims.Size.Y; h > s.maxSZ.Y {
s.maxSZ.Y = h
}
s.add(op, dims)
return s
}
func (s *Stack) Expand(idx int, w Widget) *Stack {
cs := Constraints{
Width: Constraint{Min: s.maxSZ.X, Max: s.maxSZ.X},
Height: Constraint{Min: s.maxSZ.Y, Max: s.maxSZ.Y},
}
s.add(w.Layout(cs))
if idx < 0 {
idx += len(s.children)
}
s.children[idx], s.children[len(s.children)-1] = s.children[len(s.children)-1], s.children[idx]
return s
}
func (s *Stack) add(op ui.Op, dims Dimens) {
s.children = append(s.children, stackChild{op, dims})
if s.baseline == 0 {
if b := dims.Baseline; b != dims.Size.Y {
s.baseline = b
}
}
}
func (s *Stack) Layout() (ui.Op, Dimens) {
var ops ui.Ops
if len(s.children) > len(s.opCache) {
ops = make([]ui.Op, len(s.children))
} else {
ops = s.opCache[:len(s.children)]
}
for i, ch := range s.children {
sz := ch.dims.Size
var p image.Point
switch s.Alignment {
case N, S, Center:
p.X = (s.maxSZ.X - sz.X) / 2
case NE, SE, E:
p.X = s.maxSZ.X - sz.X
}
switch s.Alignment {
case W, Center, E:
p.Y = (s.maxSZ.Y - sz.Y) / 2
case SW, S, SE:
p.Y = s.maxSZ.Y - sz.Y
}
ops[i] = ui.OpLayer{Op: ui.OpTransform{Transform: ui.Offset(toPointF(p)), Op: ch.op}}
}
b := s.baseline
if b == 0 {
b = s.maxSZ.Y
}
return ops, Dimens{
Size: s.maxSZ,
Baseline: b,
}
}
+289
View File
@@ -0,0 +1,289 @@
// SPDX-License-Identifier: Unlicense OR MIT
package measure
import (
"math"
"unicode"
"unicode/utf8"
"gioui.org/ui/draw"
"gioui.org/ui/f32"
"gioui.org/ui/text"
"gioui.org/ui"
"golang.org/x/image/font"
"golang.org/x/image/font/sfnt"
"golang.org/x/image/math/fixed"
)
type Faces struct {
Cfg *ui.Config
faceCache map[faceKey]*textFace
layoutCache map[layoutKey]cachedLayout
pathCache map[pathKey]cachedPath
}
type cachedLayout struct {
active bool
layout *text.Layout
}
type cachedPath struct {
active bool
path *draw.Path
}
type layoutKey struct {
f *sfnt.Font
ppem fixed.Int26_6
str string
singleLine bool
maxWidth int
}
type pathKey struct {
f *sfnt.Font
ppem fixed.Int26_6
str string
}
type faceKey struct {
font *sfnt.Font
size ui.Value
}
type textFace struct {
faces *Faces
size ui.Value
font *opentype
}
func (f *Faces) Frame() {
f.init()
for pk, p := range f.pathCache {
if !p.active {
delete(f.pathCache, pk)
continue
}
p.active = false
f.pathCache[pk] = p
}
for lk, l := range f.layoutCache {
if !l.active {
delete(f.layoutCache, lk)
continue
}
l.active = false
f.layoutCache[lk] = l
}
}
func (f *Faces) For(fnt *sfnt.Font, size ui.Value) text.Face {
f.init()
fk := faceKey{fnt, size}
if f, exist := f.faceCache[fk]; exist {
return f
}
face := &textFace{
faces: f,
size: size,
font: &opentype{Font: fnt, Hinting: font.HintingFull},
}
f.faceCache[fk] = face
return face
}
func (f *Faces) init() {
if f.faceCache != nil {
return
}
f.faceCache = make(map[faceKey]*textFace)
f.pathCache = make(map[pathKey]cachedPath)
f.layoutCache = make(map[layoutKey]cachedLayout)
}
func (f *textFace) Layout(str string, singleLine bool, maxWidth int) *text.Layout {
ppem := fixed.Int26_6(f.faces.Cfg.Pixels(f.size)*64 + .5)
lk := layoutKey{
f: f.font.Font,
ppem: ppem,
str: str,
singleLine: singleLine,
maxWidth: maxWidth,
}
if l, ok := f.faces.layoutCache[lk]; ok {
l.active = true
f.faces.layoutCache[lk] = l
return l.layout
}
l := layoutText(ppem, str, f.font, singleLine, maxWidth)
f.faces.layoutCache[lk] = cachedLayout{active: true, layout: l}
return l
}
func (f *textFace) Path(str text.String) *draw.Path {
ppem := fixed.Int26_6(f.faces.Cfg.Pixels(f.size)*64 + .5)
pk := pathKey{
f: f.font.Font,
ppem: ppem,
str: str.String,
}
if p, ok := f.faces.pathCache[pk]; ok {
p.active = true
f.faces.pathCache[pk] = p
return p.path
}
p := textPath(ppem, f.font, str)
f.faces.pathCache[pk] = cachedPath{active: true, path: p}
return p
}
func layoutText(ppem fixed.Int26_6, str string, f *opentype, singleLine bool, maxWidth int) *text.Layout {
m := f.Metrics(ppem)
lineTmpl := text.Line{
Ascent: m.Ascent,
// m.Height is equal to m.Ascent + m.Descent + linegap.
// Compute the descent including the linegap.
Descent: m.Height - m.Ascent,
Bounds: f.Bounds(ppem),
}
var lines []text.Line
maxDotX := fixed.Int26_6(math.MaxInt32)
if maxWidth != ui.Inf {
maxDotX = fixed.I(maxWidth)
}
type state struct {
r rune
advs []fixed.Int26_6
adv fixed.Int26_6
x fixed.Int26_6
idx int
valid bool
}
var prev, word state
endLine := func() {
line := lineTmpl
line.Text.Advances = prev.advs
line.Text.String = str[:prev.idx]
line.Width = prev.x + prev.adv
line.Bounds.Max.X += prev.x
lines = append(lines, line)
str = str[prev.idx:]
prev = state{}
word = state{}
}
for prev.idx < len(str) {
c, s := utf8.DecodeRuneInString(str[prev.idx:])
nl := text.IsNewline(c)
if singleLine && nl {
nl = false
c = ' '
s = 1
}
a, ok := f.GlyphAdvance(ppem, c)
if !ok {
prev.idx += s
continue
}
next := state{
r: c,
advs: prev.advs,
idx: prev.idx + s,
x: prev.x + prev.adv,
valid: true,
}
if nl {
// The newline is zero width; use the previous
// character for line measurements.
prev.advs = append(prev.advs, 0)
prev.idx = next.idx
endLine()
continue
}
next.adv = a
var k fixed.Int26_6
if prev.valid {
k = f.Kern(ppem, prev.r, next.r)
}
// Break the line if we're out of space.
if prev.idx > 0 && next.x+next.adv+k >= maxDotX {
// If the line contains no word breaks, break off the last rune.
if word.idx == 0 {
word = prev
}
next.x -= word.x + word.adv
next.idx -= word.idx
next.advs = next.advs[len(word.advs):]
prev = word
endLine()
} else {
next.adv += k
}
next.advs = append(next.advs, next.adv)
if unicode.IsSpace(next.r) {
word = next
}
prev = next
}
endLine()
return &text.Layout{Lines: lines}
}
func textPath(ppem fixed.Int26_6, f *opentype, str text.String) *draw.Path {
var lastPos f32.Point
var builder draw.PathBuilder
var x fixed.Int26_6
var advIdx int
for _, r := range str.String {
if !unicode.IsSpace(r) {
segs, ok := f.LoadGlyph(ppem, r)
if !ok {
continue
}
// Move to glyph position.
pos := f32.Point{
X: float32(x) / 64,
}
builder.Move(pos.Sub(lastPos))
lastPos = pos
var lastArg f32.Point
// Convert sfnt.Segments to relative segments.
for _, fseg := range segs {
nargs := 1
switch fseg.Op {
case sfnt.SegmentOpQuadTo:
nargs = 2
case sfnt.SegmentOpCubeTo:
nargs = 3
}
var args [3]f32.Point
for i := 0; i < nargs; i++ {
a := f32.Point{
X: float32(fseg.Args[i].X) / 64,
Y: float32(fseg.Args[i].Y) / 64,
}
args[i] = a.Sub(lastArg)
if i == nargs-1 {
lastArg = a
}
}
switch fseg.Op {
case sfnt.SegmentOpMoveTo:
builder.Move(args[0])
case sfnt.SegmentOpLineTo:
builder.Line(args[0])
case sfnt.SegmentOpQuadTo:
builder.Quad(args[0], args[1])
case sfnt.SegmentOpCubeTo:
builder.Cube(args[0], args[1], args[2])
default:
panic("unsupported segment op")
}
}
lastPos = lastPos.Add(lastArg)
}
x += str.Advances[advIdx]
advIdx++
}
return builder.Path()
}
+63
View File
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: Unlicense OR MIT
package measure
import (
"golang.org/x/image/font"
"golang.org/x/image/font/sfnt"
"golang.org/x/image/math/fixed"
)
type opentype struct {
Font *sfnt.Font
Hinting font.Hinting
buf sfnt.Buffer
}
func (f *opentype) GlyphAdvance(ppem fixed.Int26_6, r rune) (advance fixed.Int26_6, ok bool) {
g, err := f.Font.GlyphIndex(&f.buf, r)
if err != nil {
return 0, false
}
adv, err := f.Font.GlyphAdvance(&f.buf, g, ppem, f.Hinting)
return adv, err == nil
}
func (f *opentype) Kern(ppem fixed.Int26_6, r0, r1 rune) fixed.Int26_6 {
g0, err := f.Font.GlyphIndex(&f.buf, r0)
if err != nil {
return 0
}
g1, err := f.Font.GlyphIndex(&f.buf, r1)
if err != nil {
return 0
}
adv, err := f.Font.Kern(&f.buf, g0, g1, ppem, f.Hinting)
if err != nil {
return 0
}
return adv
}
func (f *opentype) Metrics(ppem fixed.Int26_6) font.Metrics {
m, _ := f.Font.Metrics(&f.buf, ppem, f.Hinting)
return m
}
func (f *opentype) Bounds(ppem fixed.Int26_6) fixed.Rectangle26_6 {
r, _ := f.Font.Bounds(&f.buf, ppem, f.Hinting)
return r
}
func (f *opentype) LoadGlyph(ppem fixed.Int26_6, r rune) ([]sfnt.Segment, bool) {
g, err := f.Font.GlyphIndex(&f.buf, r)
if err != nil {
return nil, false
}
segs, err := f.Font.LoadGlyph(&f.buf, g, ppem, nil)
if err != nil {
return nil, false
}
return segs, true
}
+107
View File
@@ -0,0 +1,107 @@
// SPDX-License-Identifier: Unlicense OR MIT
package pointer
import (
"time"
"gioui.org/ui/f32"
)
type Event struct {
Type Type
Source Source
PointerID ID
Priority Priority
Time time.Duration
Hit bool
Position f32.Point
Scroll f32.Point
}
type OpHandler struct {
Key Key
Area Area
Grab bool
}
type Area func(pos f32.Point) HitResult
type Key interface{}
type Events interface {
For(k Key) []Event
}
type HitResult uint8
const (
HitNone HitResult = iota
HitTransparent
HitOpaque
)
type ID uint16
type Type uint8
type Priority uint8
type Source uint8
const (
Cancel Type = iota
Press
Release
Move
)
const (
Mouse Source = iota
Touch
)
const (
Shared Priority = iota
Foremost
Grabbed
)
func (t Type) String() string {
switch t {
case Press:
return "Press"
case Release:
return "Release"
case Cancel:
return "Cancel"
case Move:
return "Move"
default:
panic("unknown Type")
}
}
func (p Priority) String() string {
switch p {
case Shared:
return "Shared"
case Foremost:
return "Foremost"
case Grabbed:
return "Grabbed"
default:
panic("unknown priority")
}
}
func (s Source) String() string {
switch s {
case Mouse:
return "Mouse"
case Touch:
return "Touch"
default:
panic("unknown source")
}
}
func (OpHandler) ImplementsOp() {}
func (Event) ImplementsEvent() {}
+247
View File
@@ -0,0 +1,247 @@
// SPDX-License-Identifier: Unlicense OR MIT
package pointer
import (
"gioui.org/ui/f32"
"gioui.org/ui"
)
type Queue struct {
// The root of the tree of ops relevant to pointer handling.
root ui.Op
handlers map[Key]*handler
pointers []pointerInfo
scratch []Key
}
type pointerInfo struct {
id ID
pressed bool
handlers []Key
}
type handler struct {
area Area
active bool
transform ui.Transform
events []Event
wantsGrab bool
}
type childOp interface {
ChildOp() ui.Op
}
func (q *Queue) collectHandlers(op ui.Op, t ui.Transform) ui.Op {
switch op := op.(type) {
case ui.Ops:
var all ui.Ops
for _, op := range op {
if op := q.collectHandlers(op, t); op != nil {
if ops, ok := op.(ui.Ops); ok {
all = append(all, ops...)
} else {
all = append(all, op)
}
}
}
return all
case ui.OpLayer:
child := q.collectHandlers(op.ChildOp(), t)
if child == nil {
return nil
}
return ui.OpLayer{Op: child}
case ui.OpTransform:
return q.collectHandlers(op.ChildOp(), t.Mul(op.Transform))
case OpHandler:
h, ok := q.handlers[op.Key]
if !ok {
h = new(handler)
q.handlers[op.Key] = h
}
h.area = op.Area
h.transform = t
h.wantsGrab = h.wantsGrab || op.Grab
return op
case childOp:
return q.collectHandlers(op.ChildOp(), t)
default:
return nil
}
}
func (q *Queue) opHit(handlers *[]Key, op ui.Op, pos f32.Point) (HitResult, bool) {
if op == nil {
return HitNone, false
}
switch op := op.(type) {
case ui.Ops:
hitRes := HitNone
var layer bool
for i := len(op) - 1; i >= 0; i-- {
op := op[i]
if _, ok := op.(ui.OpLayer); layer && ok {
continue
}
res, l := q.opHit(handlers, op, pos)
if res > hitRes {
hitRes = res
}
layer = layer || l
}
return hitRes, layer
case ui.OpLayer:
res, layer := q.opHit(handlers, op.Op, pos)
return res, layer || res == HitOpaque
case OpHandler:
h, ok := q.handlers[op.Key]
if !ok {
return HitNone, false
}
tpos := h.transform.InvTransform(pos)
res := h.area(tpos)
if res != HitNone {
*handlers = append(*handlers, op.Key)
}
return res, false
default:
panic("unexpected op")
}
}
func (q *Queue) init() {
if q.handlers == nil {
q.handlers = make(map[Key]*handler)
}
}
func (q *Queue) Frame(op ui.Op) {
q.init()
for k, h := range q.handlers {
if !h.active {
q.dropHandler(k)
} else {
// Reset handler.
h.events = h.events[:0]
}
}
q.root = q.collectHandlers(op, ui.Transform{})
}
func (q *Queue) For(k Key) []Event {
if k == nil {
panic("nil handler")
}
q.init()
h, ok := q.handlers[k]
if !ok {
h = new(handler)
q.handlers[k] = h
}
if !h.active {
h.active = true
// Prepend a Cancel.
h.events = append(h.events, Event{})
copy(h.events[1:], h.events)
h.events[0] = Event{Type: Cancel}
}
return h.events
}
func (q *Queue) dropHandler(k Key) {
delete(q.handlers, k)
for i := range q.pointers {
p := &q.pointers[i]
for i := len(p.handlers) - 1; i >= 0; i-- {
if p.handlers[i] == k {
p.handlers = append(p.handlers[:i], p.handlers[i+1:]...)
}
}
}
}
func (q *Queue) Push(e Event) {
q.init()
if e.Type == Cancel {
q.pointers = q.pointers[:0]
for k := range q.handlers {
q.dropHandler(k)
}
return
}
pidx := -1
for i, p := range q.pointers {
if p.id == e.PointerID {
pidx = i
break
}
}
if pidx == -1 {
q.pointers = append(q.pointers, pointerInfo{id: e.PointerID})
pidx = len(q.pointers) - 1
}
p := &q.pointers[pidx]
if !p.pressed && (e.Type == Move || e.Type == Press) {
p.handlers, q.scratch = q.scratch[:0], p.handlers
q.opHit(&p.handlers, q.root, e.Position)
// Drop handlers no longer hit.
loop:
for _, h := range q.scratch {
for _, h2 := range p.handlers {
if h == h2 {
continue loop
}
}
q.dropHandler(h)
}
if e.Type == Press {
p.pressed = true
}
}
if p.pressed {
// Resolve grabs.
q.scratch = q.scratch[:0]
for i, k := range p.handlers {
h := q.handlers[k]
if h.wantsGrab {
q.scratch = append(q.scratch, p.handlers[:i]...)
q.scratch = append(q.scratch, p.handlers[i+1:]...)
break
}
}
// Drop handlers that lost their grab.
for _, k := range q.scratch {
q.dropHandler(k)
}
}
if e.Type == Release {
q.pointers = append(q.pointers[:pidx], q.pointers[pidx+1:]...)
}
for i, k := range p.handlers {
h := q.handlers[k]
e := e
switch {
case p.pressed && len(p.handlers) == 1:
e.Priority = Grabbed
case i == 0:
e.Priority = Foremost
}
e.Position = h.transform.InvTransform(e.Position)
e.Hit = h.area(e.Position) != HitNone
h.events = append(h.events, e)
if e.Type == Release {
// Release grab when the number of grabs reaches zero.
grabs := 0
for _, p := range q.pointers {
if p.pressed && len(p.handlers) == 1 && p.handlers[0] == k {
grabs++
}
}
if grabs == 0 {
h.wantsGrab = false
}
}
}
}
+155
View File
@@ -0,0 +1,155 @@
// SPDX-License-Identifier: Unlicense OR MIT
package text
import (
"fmt"
"io"
"strings"
"unicode/utf8"
)
const bufferDebug = false
// editBuffer implements a gap buffer for text editing.
type editBuffer struct {
// caret is the caret position in bytes.
caret int
// pos is the byte position for Read and ReadRune.
pos int
// The gap start and end in bytes.
gapstart, gapend int
text []byte
}
const minSpace = 5
func (e *editBuffer) deleteRuneForward() {
e.moveGap(0)
_, s := utf8.DecodeRune(e.text[e.gapend:])
e.gapend += s
e.dump()
}
func (e *editBuffer) deleteRune() {
e.moveGap(0)
_, s := utf8.DecodeLastRune(e.text[:e.gapstart])
e.gapstart -= s
e.caret -= s
e.dump()
}
// moveGap moves the gap to the caret position. After returning,
// the gap is guaranteed to be at least space bytes long.
func (e *editBuffer) moveGap(space int) {
if e.gapLen() < space {
if space < minSpace {
space = minSpace
}
txt := make([]byte, e.len()+space)
// Expand to capacity.
txt = txt[:cap(txt)]
gaplen := len(txt) - e.len()
if e.caret > e.gapstart {
copy(txt, e.text[:e.gapstart])
copy(txt[e.caret+gaplen:], e.text[e.caret:])
copy(txt[e.gapstart:], e.text[e.gapend:e.caret+e.gapLen()])
} else {
copy(txt, e.text[:e.caret])
copy(txt[e.gapstart+gaplen:], e.text[e.gapend:])
copy(txt[e.caret+gaplen:], e.text[e.caret:e.gapstart])
}
e.text = txt
e.gapstart = e.caret
e.gapend = e.gapstart + gaplen
} else {
if e.caret > e.gapstart {
copy(e.text[e.gapstart:], e.text[e.gapend:e.caret+e.gapLen()])
} else {
copy(e.text[e.caret+e.gapLen():], e.text[e.caret:e.gapstart])
}
l := e.gapLen()
e.gapstart = e.caret
e.gapend = e.gapstart + l
}
e.dump()
}
func (e *editBuffer) len() int {
return len(e.text) - e.gapLen()
}
func (e *editBuffer) gapLen() int {
return e.gapend - e.gapstart
}
func (e *editBuffer) Read(p []byte) (int, error) {
if e.pos == e.len() {
return 0, io.EOF
}
var n int
if e.pos < e.gapstart {
n += copy(p, e.text[e.pos:e.gapstart])
p = p[n:]
}
n += copy(p, e.text[e.gapend:])
e.pos += n
return n, nil
}
func (e *editBuffer) ReadRune() (rune, int, error) {
if e.pos == e.len() {
return 0, 0, io.EOF
}
r, s := e.runeAt(e.pos)
e.pos += s
return r, s, nil
}
func (e *editBuffer) String() string {
var b strings.Builder
b.Grow(e.len())
b.Write(e.text[:e.gapstart])
b.Write(e.text[e.gapend:])
return b.String()
}
func (e *editBuffer) prepend(s string) {
e.moveGap(len(s))
copy(e.text[e.caret:], s)
e.gapstart += len(s)
e.dump()
}
func (e *editBuffer) dump() {
if bufferDebug {
fmt.Printf("len(e.text) %d e.len() %d e.gapstart %d e.gapend %d e.caret %d txt:\n'%+x'<-%d->'%+x'\n", len(e.text), e.len(), e.gapstart, e.gapend, e.caret, e.text[:e.gapstart], e.gapLen(), e.text[e.gapend:])
}
}
func (e *editBuffer) moveLeft() {
_, s := e.runeBefore(e.caret)
e.caret -= s
e.dump()
}
func (e *editBuffer) moveRight() {
_, s := e.runeAt(e.caret)
e.caret += s
e.dump()
}
func (e *editBuffer) runeBefore(idx int) (rune, int) {
if idx >= e.gapstart {
idx += e.gapLen()
}
return utf8.DecodeLastRune(e.text[:idx])
}
func (e *editBuffer) runeAt(idx int) (rune, int) {
if idx >= e.gapstart {
idx += e.gapLen()
}
return utf8.DecodeRune(e.text[idx:])
}
+536
View File
@@ -0,0 +1,536 @@
// SPDX-License-Identifier: Unlicense OR MIT
package text
import (
"image"
"math"
"time"
"unicode/utf8"
"gioui.org/ui/draw"
"gioui.org/ui/f32"
"gioui.org/ui/gesture"
"gioui.org/ui/key"
"gioui.org/ui/layout"
"gioui.org/ui/pointer"
"gioui.org/ui"
"golang.org/x/image/math/fixed"
)
type Editor struct {
Src image.Image
Face Face
Alignment Alignment
SingleLine bool
cfg *ui.Config
blinkStart time.Time
focused bool
rr editBuffer
maxWidth int
viewSize image.Point
valid bool
lines []Line
dims layout.Dimens
padTop, padBottom int
padLeft, padRight int
requestFocus bool
it lineIterator
// carXOff is the offset to the current caret
// position when moving between lines.
carXOff fixed.Int26_6
scroller gesture.Scroll
scrollOff image.Point
clicker gesture.Click
ops ui.Ops
}
type linePath struct {
path *draw.Path
off f32.Point
}
const (
blinksPerSecond = 1
maxBlinkDuration = 10 * time.Second
)
func (e *Editor) Update(c *ui.Config, pq pointer.Events, kq key.Events) {
e.cfg = c
sbounds := e.scrollBounds()
var smin, smax int
var axis gesture.Axis
if e.SingleLine {
axis = gesture.Horizontal
smin, smax = sbounds.Min.X, sbounds.Max.X
} else {
axis = gesture.Vertical
smin, smax = sbounds.Min.Y, sbounds.Max.Y
}
sdist := e.scroller.Scroll(c, pq, axis)
var soff int
if e.SingleLine {
e.scrollOff.X += sdist
soff = e.scrollOff.X
} else {
e.scrollOff.Y += sdist
soff = e.scrollOff.Y
}
scrollTo := false
for _, evt := range e.clicker.Update(pq) {
switch evt.Type {
case gesture.TypePress:
scrollTo = true
e.blinkStart = c.Now
e.moveCoord(image.Point{
X: int(math.Round(float64(evt.Position.X))),
Y: int(math.Round(float64(evt.Position.Y))),
})
e.requestFocus = true
}
}
stop := (sdist > 0 && soff >= smax) || (sdist < 0 && soff <= smin)
for _, ke := range kq.For(e) {
e.blinkStart = c.Now
switch ke := ke.(type) {
case key.Focus:
e.focused = ke.Focus
case key.Chord:
if e.Command(ke) {
stop = true
scrollTo = true
}
case key.Edit:
stop = true
scrollTo = true
e.append(ke.Text)
}
}
if sdist == 0 && scrollTo {
e.scrollToCaret()
}
if stop {
e.scroller.Stop()
}
}
func (e *Editor) caretWidth() fixed.Int26_6 {
oneDp := int(e.cfg.Pixels(ui.Dp(1)) + .5)
return fixed.Int26_6(oneDp * 64)
}
func (e *Editor) Layout(cs layout.Constraints) (ui.Op, layout.Dimens) {
twoDp := int(e.cfg.Pixels(ui.Dp(2)) + 0.5)
e.padLeft, e.padRight = twoDp, twoDp
maxWidth := cs.Width.Max
if e.SingleLine {
maxWidth = ui.Inf
}
if maxWidth != ui.Inf {
maxWidth -= e.padLeft + e.padRight
}
if maxWidth != e.maxWidth {
e.maxWidth = maxWidth
e.valid = false
}
e.layout()
lines, size := e.lines, e.dims.Size
e.viewSize = cs.Constrain(size)
carLine, _, carX, carY := e.layoutCaret()
off := image.Point{
X: -e.scrollOff.X + e.padLeft,
Y: -e.scrollOff.Y + e.padTop,
}
clip := image.Rectangle{
Min: image.Point{X: 0, Y: 0},
Max: image.Point{X: e.viewSize.X, Y: e.viewSize.Y},
}
e.ops = e.ops[:0]
e.ops = append(e.ops, key.OpHandler{Key: e, Focus: e.requestFocus})
e.requestFocus = false
e.it = lineIterator{
Lines: lines,
Clip: clip,
Alignment: e.Alignment,
Width: e.viewWidth(),
Offset: off,
}
for {
str, lineOff, ok := e.it.Next()
if !ok {
break
}
path := e.Face.Path(str)
e.ops = append(e.ops, ui.OpTransform{
Transform: ui.Offset(lineOff),
Op: draw.OpClip{Path: path, Op: draw.OpImage{Rect: toRectF(clip).Sub(lineOff), Src: e.Src, SrcRect: e.Src.Bounds()}},
})
}
if e.focused {
now := e.cfg.Now
dt := now.Sub(e.blinkStart)
blinking := dt < maxBlinkDuration
const timePerBlink = time.Second / blinksPerSecond
nextBlink := now.Add(timePerBlink/2 - dt%(timePerBlink/2))
on := !blinking || dt%timePerBlink < timePerBlink/2
if on {
carWidth := e.caretWidth()
carX -= carWidth / 2
carAsc, carDesc := -lines[carLine].Bounds.Min.Y, lines[carLine].Bounds.Max.Y
carRect := image.Rectangle{
Min: image.Point{X: carX.Ceil(), Y: carY - carAsc.Ceil()},
Max: image.Point{X: carX.Ceil() + carWidth.Ceil(), Y: carY + carDesc.Ceil()},
}
carRect = carRect.Add(image.Point{
X: -e.scrollOff.X + e.padLeft,
Y: -e.scrollOff.Y + e.padTop,
})
carRect = clip.Intersect(carRect)
if !carRect.Empty() {
e.ops = append(e.ops, draw.OpImage{Src: e.Src, Rect: toRectF(carRect), SrcRect: e.Src.Bounds()})
}
}
if blinking {
e.ops = append(e.ops, ui.OpRedraw{At: nextBlink})
}
}
baseline := e.padTop + e.dims.Baseline
area := gesture.Rect(e.viewSize)
e.ops = append(e.ops, e.scroller.Op(area), e.clicker.Op(area))
return e.ops, layout.Dimens{Size: e.viewSize, Baseline: baseline}
}
func (e *Editor) layout() {
e.adjustScroll()
if e.valid {
return
}
e.layoutText()
e.valid = true
}
func (e *Editor) scrollBounds() image.Rectangle {
var b image.Rectangle
if e.SingleLine {
if len(e.lines) > 0 {
b.Min.X = align(e.Alignment, e.lines[0].Width, e.viewWidth()).Floor()
if b.Min.X > 0 {
b.Min.X = 0
}
}
b.Max.X = e.dims.Size.X + b.Min.X - e.viewSize.X
} else {
b.Max.Y = e.dims.Size.Y - e.viewSize.Y
}
return b
}
func (e *Editor) adjustScroll() {
b := e.scrollBounds()
if e.scrollOff.X > b.Max.X {
e.scrollOff.X = b.Max.X
}
if e.scrollOff.X < b.Min.X {
e.scrollOff.X = b.Min.X
}
if e.scrollOff.Y > b.Max.Y {
e.scrollOff.Y = b.Max.Y
}
if e.scrollOff.Y < b.Min.Y {
e.scrollOff.Y = b.Min.Y
}
}
func (e *Editor) moveCoord(pos image.Point) {
e.layout()
var (
prevDesc fixed.Int26_6
carLine int
y int
)
for _, l := range e.lines {
y += (prevDesc + l.Ascent).Ceil()
prevDesc = l.Descent
if y+prevDesc.Ceil() >= pos.Y+e.scrollOff.Y-e.padTop {
break
}
carLine++
}
x := fixed.I(pos.X + e.scrollOff.X - e.padLeft)
e.moveToLine(x, carLine)
}
func (e *Editor) layoutText() {
textLayout := e.Face.Layout(e.rr.String(), e.SingleLine, e.maxWidth)
lines := textLayout.Lines
dims := linesDimens(lines)
for i := 0; i < len(lines)-1; i++ {
s := lines[i].Text.String
// To avoid layout flickering while editing, assume a soft newline takes
// up all available space.
if len(s) > 0 {
r, _ := utf8.DecodeLastRuneInString(s)
if !IsNewline(r) {
dims.Size.X = e.maxWidth
break
}
}
}
padTop, padBottom := textPadding(lines)
dims.Size.Y += padTop + padBottom
dims.Size.X += e.padLeft + e.padRight
e.padTop = padTop
e.padBottom = padBottom
e.lines, e.dims = lines, dims
}
func (e *Editor) viewWidth() int {
return e.viewSize.X - e.padLeft - e.padRight
}
func (e *Editor) layoutCaret() (carLine, carCol int, x fixed.Int26_6, y int) {
e.layout()
var idx int
var prevDesc fixed.Int26_6
loop:
for carLine = 0; carLine < len(e.lines); carLine++ {
l := e.lines[carLine]
y += (prevDesc + l.Ascent).Ceil()
prevDesc = l.Descent
if carLine == len(e.lines)-1 || idx+len(l.Text.String) > e.rr.caret {
str := l.Text.String
for _, adv := range l.Text.Advances {
if idx == e.rr.caret {
break loop
}
x += adv
_, s := utf8.DecodeRuneInString(str)
idx += s
str = str[s:]
carCol++
}
break
}
idx += len(l.Text.String)
}
x += align(e.Alignment, e.lines[carLine].Width, e.viewWidth())
return
}
func (e *Editor) invalidate() {
e.valid = false
}
func (e *Editor) deleteRune() {
e.rr.deleteRune()
e.carXOff = 0
e.invalidate()
}
func (e *Editor) deleteRuneForward() {
e.rr.deleteRuneForward()
e.carXOff = 0
e.invalidate()
}
func (e *Editor) SetText(s string) {
e.rr = editBuffer{}
e.prepend(s)
}
func (e *Editor) append(s string) {
e.prepend(s)
e.rr.caret += len(s)
}
func (e *Editor) prepend(s string) {
e.rr.prepend(s)
e.carXOff = 0
e.invalidate()
}
func (e *Editor) movePages(pages int) {
e.layout()
_, _, carX, carY := e.layoutCaret()
y := carY + pages*e.viewSize.Y
var (
prevDesc fixed.Int26_6
carLine2 int
)
y2 := e.lines[0].Ascent.Ceil()
for i := 1; i < len(e.lines); i++ {
if y2 >= y {
break
}
l := e.lines[i]
h := (prevDesc + l.Ascent).Ceil()
prevDesc = l.Descent
if y2+h-y >= y-y2 {
break
}
y2 += h
carLine2++
}
e.carXOff = e.moveToLine(carX+e.carXOff, carLine2)
}
func (e *Editor) moveToLine(carX fixed.Int26_6, carLine2 int) fixed.Int26_6 {
e.layout()
carLine, carCol, _, _ := e.layoutCaret()
if carLine2 < 0 {
carLine2 = 0
}
if carLine2 >= len(e.lines) {
carLine2 = len(e.lines) - 1
}
// Move to start of line.
for i := carCol - 1; i >= 0; i-- {
_, s := e.rr.runeBefore(e.rr.caret)
e.rr.caret -= s
}
if carLine2 != carLine {
// Move to start of line2.
if carLine2 > carLine {
for i := carLine; i < carLine2; i++ {
e.rr.caret += len(e.lines[i].Text.String)
}
} else {
for i := carLine - 1; i >= carLine2; i-- {
e.rr.caret -= len(e.lines[i].Text.String)
}
}
}
l2 := e.lines[carLine2]
carX2 := align(e.Alignment, l2.Width, e.viewWidth())
// Only move past the end of the last line
end := 0
if carLine2 < len(e.lines)-1 {
end = 1
}
// Move to rune closest to previous horizontal position.
for i := 0; i < len(l2.Text.Advances)-end; i++ {
adv := l2.Text.Advances[i]
if carX2 >= carX {
break
}
if carX2+adv-carX >= carX-carX2 {
break
}
carX2 += adv
_, s := e.rr.runeAt(e.rr.caret)
e.rr.caret += s
}
return carX - carX2
}
func (e *Editor) moveLeft() {
e.rr.moveLeft()
e.carXOff = 0
}
func (e *Editor) moveRight() {
e.rr.moveRight()
e.carXOff = 0
}
func (e *Editor) moveStart() {
carLine, carCol, x, _ := e.layoutCaret()
advances := e.lines[carLine].Text.Advances
for i := carCol - 1; i >= 0; i-- {
_, s := e.rr.runeBefore(e.rr.caret)
e.rr.caret -= s
x -= advances[i]
}
e.carXOff = -x
}
func (e *Editor) moveEnd() {
carLine, carCol, x, _ := e.layoutCaret()
l := e.lines[carLine]
// Only move past the end of the last line
end := 0
if carLine < len(e.lines)-1 {
end = 1
}
for i := carCol; i < len(l.Text.Advances)-end; i++ {
adv := l.Text.Advances[i]
_, s := e.rr.runeAt(e.rr.caret)
e.rr.caret += s
x += adv
}
a := align(e.Alignment, l.Width, e.viewWidth())
e.carXOff = l.Width + a - x
}
func (e *Editor) scrollToCaret() {
carWidth := e.caretWidth()
carLine, _, x, y := e.layoutCaret()
l := e.lines[carLine]
if e.SingleLine {
minx := (x - carWidth/2).Ceil()
if d := minx - e.scrollOff.X + e.padLeft; d < 0 {
e.scrollOff.X += d
}
maxx := (x + carWidth/2).Ceil()
if d := maxx - (e.scrollOff.X + e.viewSize.X - e.padRight); d > 0 {
e.scrollOff.X += d
}
} else {
miny := y + l.Bounds.Min.Y.Floor()
if d := miny - e.scrollOff.Y + e.padTop; d < 0 {
e.scrollOff.Y += d
}
maxy := y + l.Bounds.Max.Y.Ceil()
if d := maxy - (e.scrollOff.Y + e.viewSize.Y - e.padBottom); d > 0 {
e.scrollOff.Y += d
}
}
}
func (e *Editor) Command(k key.Chord) bool {
if !e.focused {
return false
}
switch k.Name {
case key.NameReturn, key.NameEnter:
if !e.SingleLine {
e.append("\n")
}
case key.NameDeleteBackward:
e.deleteRune()
case key.NameDeleteForward:
e.deleteRuneForward()
case key.NameUpArrow:
line, _, carX, _ := e.layoutCaret()
e.carXOff = e.moveToLine(carX+e.carXOff, line-1)
case key.NameDownArrow:
line, _, carX, _ := e.layoutCaret()
e.carXOff = e.moveToLine(carX+e.carXOff, line+1)
case key.NameLeftArrow:
e.moveLeft()
case key.NameRightArrow:
e.moveRight()
case key.NamePageUp:
e.movePages(-1)
case key.NamePageDown:
e.movePages(+1)
case key.NameHome:
e.moveStart()
case key.NameEnd:
e.moveEnd()
default:
return false
}
return true
}
+146
View File
@@ -0,0 +1,146 @@
// SPDX-License-Identifier: Unlicense OR MIT
package text
import (
"image"
"math"
"unicode/utf8"
"gioui.org/ui/draw"
"gioui.org/ui/f32"
"gioui.org/ui/layout"
"gioui.org/ui"
"golang.org/x/image/math/fixed"
)
type Label struct {
Face Face
Src image.Image
Alignment Alignment
Text string
it lineIterator
}
type lineIterator struct {
Lines []Line
Clip image.Rectangle
Alignment Alignment
Width int
Offset image.Point
y, prevDesc fixed.Int26_6
}
func (l *lineIterator) Next() (String, f32.Point, bool) {
for len(l.Lines) > 0 {
line := l.Lines[0]
l.Lines = l.Lines[1:]
x := align(l.Alignment, line.Width, l.Width) + fixed.I(l.Offset.X)
l.y += l.prevDesc + line.Ascent
l.prevDesc = line.Descent
// Align baseline and line start to the pixel grid.
off := fixed.Point26_6{X: fixed.I(x.Floor()), Y: fixed.I(l.y.Ceil())}
x, l.y = off.X, off.Y
off.Y += fixed.I(l.Offset.Y)
if (off.Y + line.Bounds.Min.Y).Floor() > l.Clip.Max.Y {
break
}
if (off.Y + line.Bounds.Max.Y).Ceil() < l.Clip.Min.Y {
continue
}
str := line.Text
for len(str.Advances) > 0 {
adv := str.Advances[0]
if (off.X + adv + line.Bounds.Max.X - line.Width).Ceil() >= l.Clip.Min.X {
break
}
off.X += adv
_, s := utf8.DecodeRuneInString(str.String)
str.String = str.String[s:]
str.Advances = str.Advances[1:]
}
n := 0
endx := off.X
for i, adv := range str.Advances {
if (endx + line.Bounds.Min.X).Floor() > l.Clip.Max.X {
str.String = str.String[:n]
str.Advances = str.Advances[:i]
break
}
_, s := utf8.DecodeRuneInString(str.String[n:])
n += s
endx += adv
}
offf := f32.Point{X: float32(off.X) / 64, Y: float32(off.Y) / 64}
return str, offf, true
}
return String{}, f32.Point{}, false
}
func (l Label) Layout(cs layout.Constraints) (ui.Op, layout.Dimens) {
textLayout := l.Face.Layout(l.Text, false, cs.Width.Max)
lines := textLayout.Lines
dims := linesDimens(lines)
dims.Size = cs.Constrain(dims.Size)
padTop, padBottom := textPadding(lines)
clip := image.Rectangle{
Min: image.Point{X: -ui.Inf, Y: -padTop},
Max: image.Point{X: ui.Inf, Y: dims.Size.Y + padBottom},
}
var ops ui.Ops = make([]ui.Op, len(lines))[:0]
l.it = lineIterator{
Lines: lines,
Clip: clip,
Alignment: l.Alignment,
Width: dims.Size.X,
}
for {
str, off, ok := l.it.Next()
if !ok {
break
}
path := l.Face.Path(str)
lclip := toRectF(clip).Sub(off)
op := ui.OpTransform{
Transform: ui.Offset(off),
Op: draw.OpClip{Path: path, Op: draw.OpImage{Rect: lclip, Src: l.Src, SrcRect: l.Src.Bounds()}},
}
ops = append(ops, op)
}
return ops, dims
}
func itof(i int) float32 {
switch i {
case ui.Inf:
return float32(math.Inf(+1))
case -ui.Inf:
return float32(math.Inf(-1))
default:
return float32(i)
}
}
func toRectF(r image.Rectangle) f32.Rectangle {
return f32.Rectangle{
Min: f32.Point{X: itof(r.Min.X), Y: itof(r.Min.Y)},
Max: f32.Point{X: itof(r.Max.X), Y: itof(r.Max.Y)},
}
}
func textPadding(lines []Line) (padTop int, padBottom int) {
if len(lines) > 0 {
first := lines[0]
if d := -first.Bounds.Min.Y - first.Ascent; d > 0 {
padTop = d.Ceil()
}
last := lines[len(lines)-1]
if d := last.Bounds.Max.Y - last.Descent; d > 0 {
padBottom = d.Ceil()
}
}
return
}
+91
View File
@@ -0,0 +1,91 @@
// SPDX-License-Identifier: Unlicense OR MIT
package text
import (
"fmt"
"image"
"gioui.org/ui/draw"
"gioui.org/ui/layout"
"golang.org/x/image/math/fixed"
)
type Line struct {
Text String
// Width is the width of the line.
Width fixed.Int26_6
// Ascent is the height above the baseline.
Ascent fixed.Int26_6
// Descent is the height below the baseline, including
// the line gap.
Descent fixed.Int26_6
// Bounds is the visible bounds of the line.
Bounds fixed.Rectangle26_6
}
type String struct {
String string
Advances []fixed.Int26_6
}
type Layout struct {
Lines []Line
}
type Face interface {
Layout(str string, singleLine bool, maxWidth int) *Layout
Path(str String) *draw.Path
}
type Alignment uint8
const (
Start Alignment = iota
End
Center
)
func linesDimens(lines []Line) layout.Dimens {
var width fixed.Int26_6
var h int
var baseline int
if len(lines) > 0 {
baseline = lines[0].Ascent.Ceil()
var prevDesc fixed.Int26_6
for _, l := range lines {
h += (prevDesc + l.Ascent).Ceil()
prevDesc = l.Descent
if l.Width > width {
width = l.Width
}
}
h += lines[len(lines)-1].Descent.Ceil()
}
w := width.Ceil()
return layout.Dimens{
Size: image.Point{
X: w,
Y: h,
},
Baseline: baseline,
}
}
func IsNewline(r rune) bool {
return r == '\n'
}
func align(align Alignment, width fixed.Int26_6, maxWidth int) fixed.Int26_6 {
mw := fixed.I(maxWidth)
switch align {
case Center:
return fixed.I(((mw - width) / 2).Floor())
case End:
return fixed.I((mw - width).Floor())
case Start:
return 0
default:
panic(fmt.Errorf("unknown alignment %v", align))
}
}
+73
View File
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: Unlicense OR MIT
package ui
import (
"time"
"gioui.org/ui/f32"
)
type Config struct {
PxPerDp float32
PxPerSp float32
Now time.Time
}
type Op interface {
ImplementsOp()
}
type OpLayer struct {
Op Op
}
type OpRedraw struct {
At time.Time
}
type Ops []Op
type OpTransform struct {
Transform Transform
Op Op
}
type Transform struct {
// TODO: general transforms.
offset f32.Point
}
func (t Transform) InvTransform(p f32.Point) f32.Point {
return p.Sub(t.offset)
}
func (t Transform) Transform(p f32.Point) f32.Point {
return p.Add(t.offset)
}
func (t Transform) Mul(t2 Transform) Transform {
return Transform{
offset: t.offset.Add(t2.offset),
}
}
func (t OpTransform) ChildOp() Op {
return t.Op
}
func (o OpLayer) ChildOp() Op {
return o.Op
}
func Offset(o f32.Point) Transform {
return Transform{o}
}
// Inf is the int value that represents an unbounded maximum constraint.
const Inf = int(^uint(0) >> 1)
func (Ops) ImplementsOp() {}
func (OpLayer) ImplementsOp() {}
func (OpTransform) ImplementsOp() {}
func (OpRedraw) ImplementsOp() {}
+41
View File
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: Unlicense OR MIT
package ui
type Value struct {
V float32
U Unit
}
type Unit uint8
const (
UnitPx Unit = iota
UnitDp
UnitSp
)
func Px(v float32) Value {
return Value{V: v, U: UnitPx}
}
func Dp(v float32) Value {
return Value{V: v, U: UnitDp}
}
func Sp(v float32) Value {
return Value{V: v, U: UnitSp}
}
func (c *Config) Pixels(v Value) float32 {
switch v.U {
case UnitPx:
return v.V
case UnitDp:
return c.PxPerDp * v.V
case UnitSp:
return c.PxPerSp * v.V
default:
panic("unknown unit")
}
}
+32
View File
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: Unlicense OR MIT
package widget
import (
"image"
"gioui.org/ui/draw"
"gioui.org/ui/f32"
"gioui.org/ui/layout"
"gioui.org/ui"
)
type Image struct {
Src image.Image
Rect image.Rectangle
}
func (im Image) Layout(cs layout.Constraints) (ui.Op, layout.Dimens) {
d := image.Point{X: cs.Width.Max, Y: cs.Height.Max}
if d.X == ui.Inf {
d.X = cs.Width.Min
}
if d.Y == ui.Inf {
d.Y = cs.Height.Min
}
dr := f32.Rectangle{
Max: f32.Point{X: float32(d.X), Y: float32(d.Y)},
}
op := draw.OpImage{Rect: dr, Src: im.Src, SrcRect: im.Rect}
return op, layout.Dimens{Size: d, Baseline: d.Y}
}
+3
View File
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: Unlicense OR MIT
runtime: go111

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