mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-07 18:35:34 +00:00
@@ -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