forked from joejulian/gio
@@ -0,0 +1,2 @@
|
||||
.gradle
|
||||
**/android/build
|
||||
@@ -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
@@ -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.
|
||||
@@ -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).
|
||||
@@ -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
@@ -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
@@ -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=
|
||||
@@ -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
|
||||
+172
@@ -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" "$@"
|
||||
Vendored
+84
@@ -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 */;
|
||||
}
|
||||
+10
@@ -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>
|
||||
+8
@@ -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>
|
||||
+5
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -0,0 +1,8 @@
|
||||
@import UIKit;
|
||||
@import Gophers;
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
@autoreleasepool {
|
||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass([GioAppDelegate class]));
|
||||
}
|
||||
}
|
||||
@@ -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.`
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
module gioui.org/cmd
|
||||
|
||||
go 1.12
|
||||
|
||||
require golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4
|
||||
@@ -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=
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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() {}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
@import UIKit;
|
||||
|
||||
@interface GioAppDelegate : UIResponder <UIApplicationDelegate>
|
||||
@property (strong, nonatomic) UIWindow *window;
|
||||
@end
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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(¶ms[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(¶ms[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(¶ms[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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
`
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
`
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}()
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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
@@ -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];
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -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, ®istry_listener, NULL);
|
||||
}
|
||||
|
||||
static struct wl_surface_listener surface_listener = {.enter = gio_onSurfaceEnter, .leave = gio_onSurfaceLeave};
|
||||
|
||||
void gio_wl_surface_add_listener(struct wl_surface *surface) {
|
||||
wl_surface_add_listener(surface, &surface_listener, NULL);
|
||||
}
|
||||
|
||||
static const struct xdg_surface_listener xdg_surface_listener = {
|
||||
.configure = gio_onXdgSurfaceConfigure,
|
||||
};
|
||||
|
||||
void gio_xdg_surface_add_listener(struct xdg_surface *surface) {
|
||||
xdg_surface_add_listener(surface, &xdg_surface_listener, NULL);
|
||||
}
|
||||
|
||||
static const struct xdg_toplevel_listener xdg_toplevel_listener = {
|
||||
.configure = gio_onToplevelConfigure,
|
||||
.close = gio_onToplevelClose,
|
||||
};
|
||||
|
||||
void gio_xdg_toplevel_add_listener(struct xdg_toplevel *toplevel) {
|
||||
xdg_toplevel_add_listener(toplevel, &xdg_toplevel_listener, NULL);
|
||||
}
|
||||
|
||||
static void xdg_wm_base_handle_ping(void *data, struct xdg_wm_base *wm, uint32_t serial) {
|
||||
xdg_wm_base_pong(wm, serial);
|
||||
}
|
||||
static const struct xdg_wm_base_listener xdg_wm_base_listener = {
|
||||
.ping = xdg_wm_base_handle_ping,
|
||||
};
|
||||
|
||||
void gio_xdg_wm_base_add_listener(struct xdg_wm_base *wm) {
|
||||
xdg_wm_base_add_listener(wm, &xdg_wm_base_listener, NULL);
|
||||
}
|
||||
|
||||
static const struct wl_callback_listener wl_callback_listener = {
|
||||
.done = gio_onFrameDone,
|
||||
};
|
||||
|
||||
void gio_wl_callback_add_listener(struct wl_callback *callback, void *data) {
|
||||
wl_callback_add_listener(callback, &wl_callback_listener, data);
|
||||
}
|
||||
|
||||
static const struct wl_output_listener wl_output_listener = {
|
||||
// Cast away const parameter.
|
||||
.geometry = (void (*)(void *, struct wl_output *, int32_t, int32_t, int32_t, int32_t, int32_t, const char *, const char *, int32_t))gio_onOutputGeometry,
|
||||
.mode = gio_onOutputMode,
|
||||
.done = gio_onOutputDone,
|
||||
.scale = gio_onOutputScale,
|
||||
};
|
||||
|
||||
void gio_wl_output_add_listener(struct wl_output *output) {
|
||||
wl_output_add_listener(output, &wl_output_listener, NULL);
|
||||
}
|
||||
|
||||
static const struct wl_seat_listener wl_seat_listener = {
|
||||
.capabilities = gio_onSeatCapabilities,
|
||||
// Cast away const parameter.
|
||||
.name = (void (*)(void *, struct wl_seat *, const char *))gio_onSeatName,
|
||||
};
|
||||
|
||||
void gio_wl_seat_add_listener(struct wl_seat *seat) {
|
||||
wl_seat_add_listener(seat, &wl_seat_listener, NULL);
|
||||
}
|
||||
|
||||
static const struct wl_pointer_listener wl_pointer_listener = {
|
||||
.enter = gio_onPointerEnter,
|
||||
.leave = gio_onPointerLeave,
|
||||
.motion = gio_onPointerMotion,
|
||||
.button = gio_onPointerButton,
|
||||
.axis = gio_onPointerAxis,
|
||||
.frame = gio_onPointerFrame,
|
||||
.axis_source = gio_onPointerAxisSource,
|
||||
.axis_stop = gio_onPointerAxisStop,
|
||||
.axis_discrete = gio_onPointerAxisDiscrete,
|
||||
};
|
||||
|
||||
void gio_wl_pointer_add_listener(struct wl_pointer *pointer) {
|
||||
wl_pointer_add_listener(pointer, &wl_pointer_listener, NULL);
|
||||
}
|
||||
|
||||
static const struct wl_touch_listener wl_touch_listener = {
|
||||
.down = gio_onTouchDown,
|
||||
.up = gio_onTouchUp,
|
||||
.motion = gio_onTouchMotion,
|
||||
.frame = gio_onTouchFrame,
|
||||
.cancel = gio_onTouchCancel,
|
||||
.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);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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},
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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=
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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)}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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() {}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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:])
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user