Files
gio/app/internal/window/os_android.go
T
Elias Naur f7f94c93fe app/internal/window: [android] re-apply view state lost on rotate etc.
Android can re-create our Activity and GioView at any time, losing view
and activity state such as the current cursor. Further, setting state
while there is no GioView attached is lost.

Track the current and unapplied state, and apply it when a GioView is
available.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2021-01-10 14:48:01 +01:00

755 lines
21 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
package window
/*
#cgo CFLAGS: -Werror
#cgo LDFLAGS: -landroid
#include <android/native_window_jni.h>
#include <android/configuration.h>
#include <android/keycodes.h>
#include <android/input.h>
#include <stdlib.h>
__attribute__ ((visibility ("hidden"))) jint gio_jni_GetEnv(JavaVM *vm, JNIEnv **env, jint version);
__attribute__ ((visibility ("hidden"))) jint gio_jni_GetJavaVM(JNIEnv *env, JavaVM **jvm);
__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"))) 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_CallStaticVoidMethodA(JNIEnv *env, jclass cls, jmethodID methodID, const jvalue *args);
__attribute__ ((visibility ("hidden"))) void gio_jni_CallVoidMethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
__attribute__ ((visibility ("hidden"))) jbyte *gio_jni_GetByteArrayElements(JNIEnv *env, jbyteArray arr);
__attribute__ ((visibility ("hidden"))) void gio_jni_ReleaseByteArrayElements(JNIEnv *env, jbyteArray arr, jbyte *bytes);
__attribute__ ((visibility ("hidden"))) jsize gio_jni_GetArrayLength(JNIEnv *env, jbyteArray arr);
__attribute__ ((visibility ("hidden"))) jstring gio_jni_NewString(JNIEnv *env, const jchar *unicodeChars, jsize len);
__attribute__ ((visibility ("hidden"))) jsize gio_jni_GetStringLength(JNIEnv *env, jstring str);
__attribute__ ((visibility ("hidden"))) const jchar *gio_jni_GetStringChars(JNIEnv *env, jstring str);
__attribute__ ((visibility ("hidden"))) jthrowable gio_jni_ExceptionOccurred(JNIEnv *env);
__attribute__ ((visibility ("hidden"))) void gio_jni_ExceptionClear(JNIEnv *env);
__attribute__ ((visibility ("hidden"))) jobject gio_jni_CallObjectMethodA(JNIEnv *env, jobject obj, jmethodID method, jvalue *args);
__attribute__ ((visibility ("hidden"))) jobject gio_jni_CallStaticObjectMethodA(JNIEnv *env, jclass cls, jmethodID method, jvalue *args);
*/
import "C"
import (
"errors"
"fmt"
"image"
"reflect"
"runtime"
"runtime/debug"
"sync"
"time"
"unicode/utf16"
"unsafe"
"gioui.org/f32"
"gioui.org/io/clipboard"
"gioui.org/io/key"
"gioui.org/io/pointer"
"gioui.org/io/system"
"gioui.org/unit"
)
type window struct {
callbacks Callbacks
view C.jobject
dpi int
fontScale float32
insets system.Insets
stage system.Stage
started bool
state, newState windowState
// mu protects the fields following it.
mu sync.Mutex
win *C.ANativeWindow
animating bool
}
// windowState tracks the View or Activity specific state lost when Android
// re-creates our Activity.
type windowState struct {
cursor *pointer.CursorName
}
// gioView hold cached JNI methods for GioView.
var gioView struct {
once sync.Once
getDensity C.jmethodID
getFontScale C.jmethodID
showTextInput C.jmethodID
hideTextInput C.jmethodID
postFrameCallback C.jmethodID
setCursor C.jmethodID
}
// ViewEvent is sent whenever the Window's underlying Android view
// changes.
type ViewEvent struct {
// View is a JNI global reference to the android.view.View
// instance backing the Window. The reference is valid until
// the next ViewEvent is received.
// A zero View means that there is currently no view attached.
View uintptr
}
type jvalue uint64 // The largest JNI type fits in 64 bits.
var dataDirChan = make(chan string, 1)
var android struct {
// mu protects all fields of this structure. However, once a
// non-nil jvm is returned from javaVM, all the other fields may
// be accessed unlocked.
mu sync.Mutex
jvm *C.JavaVM
// appCtx is the global Android App context.
appCtx C.jobject
// gioCls is the class of the Gio class.
gioCls C.jclass
mwriteClipboard C.jmethodID
mreadClipboard C.jmethodID
mwakeupMainThread C.jmethodID
}
// view maps from GioView JNI refenreces to windows.
var views = make(map[C.jlong]*window)
// windows maps from Callbacks to windows
var windows = make(map[Callbacks]*window)
var mainWindow = newWindowRendezvous()
var mainFuncs = make(chan func(env *C.JNIEnv), 1)
func getMethodID(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))
jm := C.gio_jni_GetMethodID(env, class, m, s)
if err := exception(env); err != nil {
panic(err)
}
return jm
}
func getStaticMethodID(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))
jm := C.gio_jni_GetStaticMethodID(env, class, m, s)
if err := exception(env); err != nil {
panic(err)
}
return jm
}
//export Java_org_gioui_Gio_runGoMain
func Java_org_gioui_Gio_runGoMain(env *C.JNIEnv, class C.jclass, jdataDir C.jbyteArray, context C.jobject) {
initJVM(env, class, context)
dirBytes := C.gio_jni_GetByteArrayElements(env, jdataDir)
if dirBytes == nil {
panic("runGoMain: GetByteArrayElements failed")
}
n := C.gio_jni_GetArrayLength(env, jdataDir)
dataDir := C.GoStringN((*C.char)(unsafe.Pointer(dirBytes)), n)
dataDirChan <- dataDir
C.gio_jni_ReleaseByteArrayElements(env, jdataDir, dirBytes)
runMain()
}
func initJVM(env *C.JNIEnv, gio C.jclass, ctx C.jobject) {
android.mu.Lock()
defer android.mu.Unlock()
if res := C.gio_jni_GetJavaVM(env, &android.jvm); res != 0 {
panic("gio: GetJavaVM failed")
}
android.appCtx = C.gio_jni_NewGlobalRef(env, ctx)
android.gioCls = C.jclass(C.gio_jni_NewGlobalRef(env, C.jobject(gio)))
android.mwriteClipboard = getStaticMethodID(env, gio, "writeClipboard", "(Landroid/content/Context;Ljava/lang/String;)V")
android.mreadClipboard = getStaticMethodID(env, gio, "readClipboard", "(Landroid/content/Context;)Ljava/lang/String;")
android.mwakeupMainThread = getStaticMethodID(env, gio, "wakeupMainThread", "()V")
}
func JavaVM() uintptr {
jvm := javaVM()
return uintptr(unsafe.Pointer(jvm))
}
func javaVM() *C.JavaVM {
android.mu.Lock()
defer android.mu.Unlock()
return android.jvm
}
func AppContext() uintptr {
android.mu.Lock()
defer android.mu.Unlock()
return uintptr(android.appCtx)
}
func GetDataDir() string {
return <-dataDirChan
}
//export Java_org_gioui_GioView_onCreateView
func Java_org_gioui_GioView_onCreateView(env *C.JNIEnv, class C.jclass, view C.jobject) C.jlong {
gioView.once.Do(func() {
m := &gioView
m.getDensity = getMethodID(env, class, "getDensity", "()I")
m.getFontScale = getMethodID(env, class, "getFontScale", "()F")
m.showTextInput = getMethodID(env, class, "showTextInput", "()V")
m.hideTextInput = getMethodID(env, class, "hideTextInput", "()V")
m.postFrameCallback = getMethodID(env, class, "postFrameCallback", "()V")
m.setCursor = getMethodID(env, class, "setCursor", "(I)V")
})
view = C.gio_jni_NewGlobalRef(env, view)
wopts := <-mainWindow.out
w, ok := windows[wopts.window]
if !ok {
w = &window{
callbacks: wopts.window,
}
windows[wopts.window] = w
}
w.callbacks.SetDriver(w)
w.view = view
handle := C.jlong(view)
views[handle] = w
w.loadConfig(env, class)
applyStateDiff(env, view, windowState{}, w.state)
w.setStage(system.StagePaused)
w.callbacks.Event(ViewEvent{View: uintptr(view)})
return handle
}
//export Java_org_gioui_GioView_onDestroyView
func Java_org_gioui_GioView_onDestroyView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
w := views[handle]
w.callbacks.Event(ViewEvent{View: 0})
w.callbacks.SetDriver(nil)
delete(views, handle)
C.gio_jni_DeleteGlobalRef(env, w.view)
w.view = 0
}
//export Java_org_gioui_GioView_onStopView
func Java_org_gioui_GioView_onStopView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
w := views[handle]
w.started = false
w.setStage(system.StagePaused)
}
//export Java_org_gioui_GioView_onStartView
func Java_org_gioui_GioView_onStartView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
w := views[handle]
w.started = true
if w.aNativeWindow() != nil {
w.setVisible()
}
}
//export Java_org_gioui_GioView_onSurfaceDestroyed
func Java_org_gioui_GioView_onSurfaceDestroyed(env *C.JNIEnv, class C.jclass, handle C.jlong) {
w := views[handle]
w.mu.Lock()
w.win = nil
w.mu.Unlock()
w.setStage(system.StagePaused)
}
//export Java_org_gioui_GioView_onSurfaceChanged
func Java_org_gioui_GioView_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 Java_org_gioui_GioView_onLowMemory
func Java_org_gioui_GioView_onLowMemory() {
runtime.GC()
debug.FreeOSMemory()
}
//export Java_org_gioui_GioView_onConfigurationChanged
func Java_org_gioui_GioView_onConfigurationChanged(env *C.JNIEnv, class C.jclass, view C.jlong) {
w := views[view]
w.loadConfig(env, class)
if w.stage >= system.StageRunning {
w.draw(true)
}
}
//export Java_org_gioui_GioView_onFrameCallback
func Java_org_gioui_GioView_onFrameCallback(env *C.JNIEnv, class C.jclass, view C.jlong, nanos C.jlong) {
w, exist := views[view]
if !exist {
return
}
if w.stage < system.StageRunning {
return
}
w.mu.Lock()
anim := w.animating
w.mu.Unlock()
if anim {
runInJVM(javaVM(), func(env *C.JNIEnv) {
callVoidMethod(env, w.view, gioView.postFrameCallback)
})
w.draw(false)
}
}
//export Java_org_gioui_GioView_onBack
func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong) C.jboolean {
w := views[view]
ev := &system.CommandEvent{Type: system.CommandBack}
w.callbacks.Event(ev)
if ev.Cancel {
return C.JNI_TRUE
}
return C.JNI_FALSE
}
//export Java_org_gioui_GioView_onFocusChange
func Java_org_gioui_GioView_onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) {
w := views[view]
w.callbacks.Event(key.FocusEvent{Focus: focus == C.JNI_TRUE})
}
//export Java_org_gioui_GioView_onWindowInsets
func Java_org_gioui_GioView_onWindowInsets(env *C.JNIEnv, class C.jclass, view C.jlong, top, right, bottom, left C.jint) {
w := views[view]
w.insets = system.Insets{
Top: unit.Px(float32(top)),
Right: unit.Px(float32(right)),
Bottom: unit.Px(float32(bottom)),
Left: unit.Px(float32(left)),
}
if w.stage >= system.StageRunning {
w.draw(true)
}
}
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(system.StageRunning)
w.draw(true)
}
func (w *window) setStage(stage system.Stage) {
if stage == w.stage {
return
}
w.stage = stage
w.callbacks.Event(system.StageEvent{stage})
}
func (w *window) nativeWindow(visID int) (*C.ANativeWindow, 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 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, gioView.getDensity))
w.fontScale = float32(C.gio_jni_CallFloatMethod(env, w.view, gioView.getFontScale))
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 {
runOnMain(func(env *C.JNIEnv) {
if w.view == 0 {
// View was destroyed while switching to main thread.
return
}
callVoidMethod(env, w.view, gioView.postFrameCallback)
})
}
}
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
}
const inchPrDp = 1.0 / 160
ppdp := float32(w.dpi) * inchPrDp
w.callbacks.Event(FrameEvent{
FrameEvent: system.FrameEvent{
Now: time.Now(),
Size: image.Point{
X: int(width),
Y: int(height),
},
Insets: w.insets,
Metric: unit.Metric{
PxPerDp: ppdp,
PxPerSp: w.fontScale * ppdp,
},
},
Sync: sync,
})
}
type keyMapper func(devId, keyCode C.int32_t) rune
func runInJVM(jvm *C.JavaVM, f func(env *C.JNIEnv)) {
if jvm == nil {
panic("nil JVM")
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var env *C.JNIEnv
if res := C.gio_jni_GetEnv(jvm, &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(jvm, &env, nil) != C.JNI_OK {
panic(errors.New("runInJVM: AttachCurrentThread failed"))
}
defer C.gio_jni_DetachCurrentThread(jvm)
}
f(env)
}
func convertKeyCode(code C.jint) (string, bool) {
var n string
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
case C.AKEYCODE_NUMPAD_ENTER:
n = key.NameEnter
case C.AKEYCODE_ENTER:
n = key.NameEnter
default:
return "", false
}
return n, true
}
//export Java_org_gioui_GioView_onKeyEvent
func Java_org_gioui_GioView_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.callbacks.Event(key.Event{Name: n})
}
if r != 0 {
w.callbacks.Event(key.EditEvent{Text: string(rune(r))})
}
}
//export Java_org_gioui_GioView_onTouchEvent
func Java_org_gioui_GioView_onTouchEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, action, pointerID, tool C.jint, x, y, scrollX, scrollY C.jfloat, jbtns C.jint, 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
case C.AMOTION_EVENT_ACTION_SCROLL:
typ = pointer.Scroll
default:
return
}
var src pointer.Source
var btns pointer.Buttons
if jbtns&C.AMOTION_EVENT_BUTTON_PRIMARY != 0 {
btns |= pointer.ButtonLeft
}
if jbtns&C.AMOTION_EVENT_BUTTON_SECONDARY != 0 {
btns |= pointer.ButtonRight
}
if jbtns&C.AMOTION_EVENT_BUTTON_TERTIARY != 0 {
btns |= pointer.ButtonMiddle
}
switch tool {
case C.AMOTION_EVENT_TOOL_TYPE_FINGER:
src = pointer.Touch
case C.AMOTION_EVENT_TOOL_TYPE_MOUSE:
src = pointer.Mouse
case C.AMOTION_EVENT_TOOL_TYPE_UNKNOWN:
// For example, triggered via 'adb shell input tap'.
// Instead of discarding it, treat it as a touch event.
src = pointer.Touch
default:
return
}
w.callbacks.Event(pointer.Event{
Type: typ,
Source: src,
Buttons: btns,
PointerID: pointer.ID(pointerID),
Time: time.Duration(t) * time.Millisecond,
Position: f32.Point{X: float32(x), Y: float32(y)},
Scroll: f32.Pt(float32(scrollX), float32(scrollY)),
})
}
func (w *window) ShowTextInput(show bool) {
runOnMain(func(env *C.JNIEnv) {
if w.view == 0 {
return
}
if show {
callVoidMethod(env, w.view, gioView.showTextInput)
} else {
callVoidMethod(env, w.view, gioView.hideTextInput)
}
})
}
func javaString(env *C.JNIEnv, str string) C.jstring {
if str == "" {
return 0
}
utf16Chars := utf16.Encode([]rune(str))
return C.gio_jni_NewString(env, (*C.jchar)(unsafe.Pointer(&utf16Chars[0])), C.int(len(utf16Chars)))
}
func varArgs(args []jvalue) *C.jvalue {
if len(args) == 0 {
return nil
}
return (*C.jvalue)(unsafe.Pointer(&args[0]))
}
func callStaticVoidMethod(env *C.JNIEnv, cls C.jclass, method C.jmethodID, args ...jvalue) error {
C.gio_jni_CallStaticVoidMethodA(env, cls, method, varArgs(args))
return exception(env)
}
func callStaticObjectMethod(env *C.JNIEnv, cls C.jclass, method C.jmethodID, args ...jvalue) (C.jobject, error) {
res := C.gio_jni_CallStaticObjectMethodA(env, cls, method, varArgs(args))
return res, exception(env)
}
func callVoidMethod(env *C.JNIEnv, obj C.jobject, method C.jmethodID, args ...jvalue) error {
C.gio_jni_CallVoidMethodA(env, obj, method, varArgs(args))
return exception(env)
}
func callObjectMethod(env *C.JNIEnv, obj C.jobject, method C.jmethodID, args ...jvalue) (C.jobject, error) {
res := C.gio_jni_CallObjectMethodA(env, obj, method, varArgs(args))
return res, exception(env)
}
// exception returns an error corresponding to the pending
// exception, or nil if no exception is pending. The pending
// exception is cleared.
func exception(env *C.JNIEnv) error {
thr := C.gio_jni_ExceptionOccurred(env)
if thr == 0 {
return nil
}
C.gio_jni_ExceptionClear(env)
cls := getObjectClass(env, C.jobject(thr))
toString := getMethodID(env, cls, "toString", "()Ljava/lang/String;")
msg, err := callObjectMethod(env, C.jobject(thr), toString)
if err != nil {
return err
}
return errors.New(goString(env, C.jstring(msg)))
}
func getObjectClass(env *C.JNIEnv, obj C.jobject) C.jclass {
if obj == 0 {
panic("null object")
}
cls := C.gio_jni_GetObjectClass(env, C.jobject(obj))
if err := exception(env); err != nil {
// GetObjectClass should never fail.
panic(err)
}
return cls
}
// goString converts the JVM jstring to a Go string.
func goString(env *C.JNIEnv, str C.jstring) string {
if str == 0 {
return ""
}
strlen := C.gio_jni_GetStringLength(env, C.jstring(str))
chars := C.gio_jni_GetStringChars(env, C.jstring(str))
var utf16Chars []uint16
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&utf16Chars))
hdr.Data = uintptr(unsafe.Pointer(chars))
hdr.Cap = int(strlen)
hdr.Len = int(strlen)
utf8 := utf16.Decode(utf16Chars)
return string(utf8)
}
func Main() {
}
func NewWindow(window Callbacks, opts *Options) error {
mainWindow.in <- windowAndOptions{window, opts}
return <-mainWindow.errs
}
func (w *window) WriteClipboard(s string) {
runOnMain(func(env *C.JNIEnv) {
jstr := javaString(env, s)
callStaticVoidMethod(env, android.gioCls, android.mwriteClipboard,
jvalue(android.appCtx), jvalue(jstr))
})
}
func (w *window) ReadClipboard() {
runOnMain(func(env *C.JNIEnv) {
c, err := callStaticObjectMethod(env, android.gioCls, android.mreadClipboard,
jvalue(android.appCtx))
if err != nil {
return
}
content := goString(env, C.jstring(c))
w.callbacks.Event(clipboard.Event{Text: content})
})
}
func (w *window) SetCursor(name pointer.CursorName) {
w.setState(func(state *windowState) {
state.cursor = &name
})
}
// setState adjust the window state on the main thread.
func (w *window) setState(f func(state *windowState)) {
runOnMain(func(env *C.JNIEnv) {
f(&w.newState)
if w.view == 0 {
// No View attached. The state will be applied at next onCreateView.
return
}
old := w.state
state := w.newState
applyStateDiff(env, w.view, old, state)
w.state = state
})
}
func applyStateDiff(env *C.JNIEnv, view C.jobject, old, state windowState) {
if state.cursor != nil && old.cursor != state.cursor {
setCursor(env, view, *state.cursor)
}
}
func setCursor(env *C.JNIEnv, view C.jobject, name pointer.CursorName) {
var curID int
switch name {
default:
fallthrough
case pointer.CursorDefault:
curID = 1000 // TYPE_ARROW
case pointer.CursorText:
curID = 1008 // TYPE_TEXT
case pointer.CursorPointer:
curID = 1002 // TYPE_HAND
case pointer.CursorCrossHair:
curID = 1007 // TYPE_CROSSHAIR
case pointer.CursorColResize:
curID = 1014 // TYPE_HORIZONTAL_DOUBLE_ARROW
case pointer.CursorRowResize:
curID = 1015 // TYPE_VERTICAL_DOUBLE_ARROW
case pointer.CursorNone:
curID = 0 // TYPE_NULL
}
callVoidMethod(env, view, gioView.setCursor, jvalue(curID))
}
// Close the window. Not implemented for Android.
func (w *window) Close() {}
// runOnMain runs a function on the Java main thread.
func runOnMain(f func(env *C.JNIEnv)) {
go func() {
mainFuncs <- f
runInJVM(javaVM(), func(env *C.JNIEnv) {
callStaticVoidMethod(env, android.gioCls, android.mwakeupMainThread)
})
}()
}
//export Java_org_gioui_Gio_scheduleMainFuncs
func Java_org_gioui_Gio_scheduleMainFuncs(env *C.JNIEnv, cls C.jclass) {
for {
select {
case f := <-mainFuncs:
f(env)
default:
return
}
}
}
func (_ ViewEvent) ImplementsEvent() {}