Ported: Add cursor shape support for the Android platform - m4gr3d

46f091a803 (diff-654c896c53f1fe1db3c63e1f0409d7929b54f1e4e43b49b21a0a4eba4f945c15)
This commit is contained in:
Relintai 2022-10-08 19:03:10 +02:00
parent 35249df651
commit d8a48e51bd
10 changed files with 252 additions and 14 deletions

View File

@ -15,6 +15,7 @@ android_files = [
"java_class_wrapper.cpp",
"java_pandemonium_wrapper.cpp",
"java_pandemonium_io_wrapper.cpp",
"java_pandemonium_view_wrapper.cpp",
"jni_utils.cpp",
"android_keys_utils.cpp",
"plugin/pandemonium_plugin_jni.cpp",

View File

@ -81,7 +81,7 @@ open class PandemoniumEditor : FullScreenPandemoniumApp() {
super.onCreate(savedInstanceState);
// Enable long press, panning and scaling gestures
pandemoniumFragment?.mView?.inputHandler?.apply {
pandemoniumFragment?.renderView?.inputHandler?.apply {
enableLongPress(enableLongPressGestures())
enablePanningAndScalingGestures(enablePanAndScaleGestures())
}

View File

@ -574,6 +574,11 @@ public class Pandemonium extends Fragment implements SensorEventListener, IDownl
return mView.getHolder().getSurface();
}
@Keep
public GodotView getRenderView() { // used by native side to get renderView
return mView;
}
/**
* Used by the native code (java_pandemonium_wrapper.h) to access the input fallback mapping.
* @return The input fallback mapping for the current XR mode.

View File

@ -40,9 +40,14 @@ import net.relintai.pandemonium.pandemonium.config.RegularFallbackConfigChooser;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.PixelFormat;
import android.os.Build;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.PointerIcon;
import androidx.annotation.Keep;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
@ -85,6 +90,10 @@ public class PandemoniumView extends PandemoniumGLSurfaceView {
this.inputHandler = new PandemoniumInputHandler(this);
this.pandemoniumRenderer = new PandemoniumRenderer();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT));
}
init(p_translucent);
}
@ -114,6 +123,24 @@ public class PandemoniumView extends PandemoniumGLSurfaceView {
return inputHandler.onGenericMotionEvent(event) || super.onGenericMotionEvent(event);
}
/**
* Called from JNI to change the pointer icon
*/
@Keep
private void setPointerIcon(int pointerType) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
setPointerIcon(PointerIcon.getSystemIcon(getContext(), pointerType));
}
}
@Override
public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return getPointerIcon();
}
return super.onResolvePointerIcon(event, pointerIndex);
}
private void init(boolean translucent) {
setPreserveEGLContextOnPause(true);
setFocusableInTouchMode(true);

View File

@ -0,0 +1,66 @@
/*************************************************************************/
/* java_pandemonium_view_wrapper.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* 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. */
/*************************************************************************/
#include "java_pandemonium_view_wrapper.h"
PandemoniumJavaViewWrapper::PandemoniumJavaViewWrapper(jobject pandemonium_view) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL(env);
_pandemonium_view = env->NewGlobalRef(pandemonium_view);
_cls = (jclass)env->NewGlobalRef(env->GetObjectClass(pandemonium_view));
int android_device_api_level = android_get_device_api_level();
if (android_device_api_level >= __ANDROID_API_N__) {
_set_pointer_icon = env->GetMethodID(_cls, "setPointerIcon", "(I)V");
}
}
bool PandemoniumJavaViewWrapper::can_update_pointer_icon() const {
return _set_pointer_icon != nullptr;
}
void PandemoniumJavaViewWrapper::set_pointer_icon(int pointer_type) {
if (_set_pointer_icon != nullptr) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL(env);
env->CallVoidMethod(_pandemonium_view, _set_pointer_icon, pointer_type);
}
}
PandemoniumJavaViewWrapper::~PandemoniumJavaViewWrapper() {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL(env);
env->DeleteGlobalRef(_pandemonium_view);
env->DeleteGlobalRef(_cls);
}

View File

@ -0,0 +1,55 @@
#ifndef JAVA_PANDEMONIUM_VIEW_WRAPPER_H
#define JAVA_PANDEMONIUM_VIEW_WRAPPER_H
/*************************************************************************/
/* java_pandemonium_view_wrapper.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* 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. */
/*************************************************************************/
#include <android/log.h>
#include <jni.h>
#include "string_android.h"
// Class that makes functions in java/src/org/pandemoniumengine/pandemonium/PandemoniumView.java callable from C++
class PandemoniumJavaViewWrapper {
private:
jclass _cls;
jobject _pandemonium_view;
jmethodID _set_pointer_icon = 0;
public:
PandemoniumJavaViewWrapper(jobject pandemonium_view);
bool can_update_pointer_icon() const;
void set_pointer_icon(int pointer_type);
~PandemoniumJavaViewWrapper();
};
#endif // JAVA_GODOT_VIEW_WRAPPER_H

View File

@ -82,13 +82,23 @@ PandemoniumJavaWrapper::PandemoniumJavaWrapper(JNIEnv *p_env, jobject p_activity
_on_pandemonium_main_loop_started = p_env->GetMethodID(pandemonium_class, "onPandemoniumMainLoopStarted", "()V");
_create_new_pandemonium_instance = p_env->GetMethodID(pandemonium_class, "createNewPandemoniumInstance", "([Ljava/lang/String;)V");
_request_framebuffer_swap = p_env->GetMethodID(pandemonium_class, "requestFramebufferSwap", "()V");
_get_render_view = p_env->GetMethodID(pandemonium_class, "getRenderView", "()Ljava/src/org/pandemoniumengine/pandemonium/PandemoniumView;");
// get some Activity method pointers...
_get_class_loader = p_env->GetMethodID(activity_class, "getClassLoader", "()Ljava/lang/ClassLoader;");
}
PandemoniumJavaWrapper::~PandemoniumJavaWrapper() {
// nothing to do here for now
if (pandemonium_view) {
delete pandemonium_view;
}
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL(env);
env->DeleteGlobalRef(pandemonium_instance);
env->DeleteGlobalRef(pandemonium_class);
env->DeleteGlobalRef(activity);
env->DeleteGlobalRef(activity_class);
}
jobject PandemoniumJavaWrapper::get_activity() {
@ -127,6 +137,21 @@ void PandemoniumJavaWrapper::gfx_init(bool gl2) {
// Maybe we're supposed to communicate this back or store it?
}
PandemoniumJavaViewWrapper *PandemoniumJavaWrapper::get_pandemonium_view() {
if (pandemonium_view != nullptr) {
return pandemonium_view;
}
if (_get_render_view) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL_V(env, nullptr);
jobject pandemonium_render_view = env->CallObjectMethod(pandemonium_instance, _get_render_view);
if (!env->IsSameObject(pandemonium_render_view, nullptr)) {
pandemonium_view = new PandemoniumJavaViewWrapper(pandemonium_render_view);
}
}
return pandemonium_view;
}
void PandemoniumJavaWrapper::on_video_init(JNIEnv *p_env) {
if (_on_video_init) {
if (p_env == NULL)

View File

@ -1,10 +1,11 @@
#ifndef JAVA_PANDEMONIUM_WRAPPER_H
#define JAVA_PANDEMONIUM_WRAPPER_H
/*************************************************************************/
/* java_pandemonium_wrapper.h */
/* java_pandemonium_wrapper.h */
/*************************************************************************/
/* This file is part of: */
/* PANDEMONIUM ENGINE */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
@ -37,6 +38,7 @@
#include <jni.h>
#include "core/containers/list.h"
#include "java_pandemonium_view_wrapper.h"
#include "string_android.h"
// Class that makes functions in java/src/org/pandemoniumengine/pandemonium/Pandemonium.java callable from C++
@ -47,6 +49,8 @@ private:
jclass pandemonium_class;
jclass activity_class;
PandemoniumJavaViewWrapper *pandemonium_view = NULL;
jmethodID _on_video_init = 0;
jmethodID _create_offscreen_gl = 0;
jmethodID _destroy_offscreen_gl = 0;
@ -72,6 +76,7 @@ private:
jmethodID _get_class_loader = 0;
jmethodID _create_new_pandemonium_instance = 0;
jmethodID _request_framebuffer_swap = 0;
jmethodID _get_render_view = 0;
public:
PandemoniumJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_pandemonium_instance);
@ -81,6 +86,7 @@ public:
jobject get_member_object(const char *p_name, const char *p_class, JNIEnv *p_env = NULL);
jobject get_class_loader();
PandemoniumJavaViewWrapper *get_pandemonium_view();
void gfx_init(bool gl2);
bool create_offscreen_gl(JNIEnv *p_env);

View File

@ -234,17 +234,43 @@ Error OS_Android::open_dynamic_library(const String p_path, void *&p_library_han
return OK;
}
void OS_Android::set_mouse_show(bool p_show) {
//android has no mouse...
void OS_Android::set_mouse_mode(MouseMode p_mode) {
if (!godot_java->get_godot_view()->can_update_pointer_icon()) {
return;
}
if (mouse_mode == p_mode || p_mode == MouseMode::MOUSE_MODE_CAPTURED) {
return;
}
if (p_mode == MouseMode::MOUSE_MODE_HIDDEN) {
godot_java->get_godot_view()->set_pointer_icon(CURSOR_TYPE_NULL);
} else {
set_cursor_shape(cursor_shape);
}
mouse_mode = p_mode;
}
void OS_Android::set_mouse_grab(bool p_grab) {
//it really has no mouse...!
OS::MouseMode OS_Android::get_mouse_mode() const {
return mouse_mode;
}
bool OS_Android::is_mouse_grab_enabled() const {
//*sigh* technology has evolved so much since i was a kid..
return false;
void OS_Android::set_cursor_shape(CursorShape p_shape) {
if (!godot_java->get_godot_view()->can_update_pointer_icon()) {
return;
}
if (cursor_shape == p_shape) {
return;
}
cursor_shape = p_shape;
if (mouse_mode == MouseMode::MOUSE_MODE_VISIBLE || mouse_mode == MouseMode::MOUSE_MODE_CONFINED) {
godot_java->get_godot_view()->set_pointer_icon(android_cursors[cursor_shape]);
}
}
OS::CursorShape OS_Android::get_cursor_shape() const {
return cursor_shape;
}
Point2 OS_Android::get_mouse_position() const {

View File

@ -41,6 +41,31 @@ class PandemoniumJavaWrapper;
class PandemoniumIOJavaWrapper;
class OS_Android : public OS_Unix {
// https://developer.android.com/reference/android/view/PointerIcon
// mapping between Godot's cursor shape to Android's'
int android_cursors[CURSOR_MAX] = {
1000, //CURSOR_ARROW
1008, //CURSOR_IBEAM
1002, //CURSOR_POINTIN
1007, //CURSOR_CROSS
1004, //CURSOR_WAIT
1004, //CURSOR_BUSY
1021, //CURSOR_DRAG
1021, //CURSOR_CAN_DRO
1000, //CURSOR_FORBIDD (no corresponding icon in Android's icon so fallback to default)
1015, //CURSOR_VSIZE
1014, //CURSOR_HSIZE
1017, //CURSOR_BDIAGSI
1016, //CURSOR_FDIAGSI
1020, //CURSOR_MOVE
1015, //CURSOR_VSPLIT
1014, //CURSOR_HSPLIT
1003, //CURSOR_HELP
};
const int CURSOR_TYPE_NULL = 0;
MouseMode mouse_mode = MouseMode::MOUSE_MODE_VISIBLE;
CursorShape cursor_shape = CursorShape::CURSOR_ARROW;
bool use_gl2;
bool use_apk_expansion;
@ -100,9 +125,11 @@ public:
virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false);
virtual void set_mouse_show(bool p_show);
virtual void set_mouse_grab(bool p_grab);
virtual bool is_mouse_grab_enabled() const;
virtual void set_cursor_shape(CursorShape p_shape);
virtual CursorShape get_cursor_shape() const;
virtual void set_mouse_mode(MouseMode p_mode);
virtual MouseMode get_mouse_mode() const;
virtual Point2 get_mouse_position() const;
virtual int get_mouse_button_state() const;
virtual void set_window_title(const String &p_title);