From d8a48e51bd6bad5478dbcfffcc7179773767eb94 Mon Sep 17 00:00:00 2001 From: Relintai Date: Sat, 8 Oct 2022 19:03:10 +0200 Subject: [PATCH] Ported: Add cursor shape support for the Android platform - m4gr3d https://github.com/godotengine/godot/commit/46f091a803f4ef8c5d96a1e12c392c6f2898ed93#diff-654c896c53f1fe1db3c63e1f0409d7929b54f1e4e43b49b21a0a4eba4f945c15 --- platform/android/SCsub | 1 + .../pandemonium/editor/PandemoniumEditor.kt | 2 +- .../pandemonium/pandemonium/Pandemonium.java | 5 ++ .../pandemonium/PandemoniumView.java | 27 ++++++++ .../android/java_pandemonium_view_wrapper.cpp | 66 +++++++++++++++++++ .../android/java_pandemonium_view_wrapper.h | 55 ++++++++++++++++ platform/android/java_pandemonium_wrapper.cpp | 27 +++++++- platform/android/java_pandemonium_wrapper.h | 10 ++- platform/android/os_android.cpp | 40 +++++++++-- platform/android/os_android.h | 33 +++++++++- 10 files changed, 252 insertions(+), 14 deletions(-) create mode 100644 platform/android/java_pandemonium_view_wrapper.cpp create mode 100644 platform/android/java_pandemonium_view_wrapper.h diff --git a/platform/android/SCsub b/platform/android/SCsub index f56e666b9..f71416204 100644 --- a/platform/android/SCsub +++ b/platform/android/SCsub @@ -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", diff --git a/platform/android/java/editor/src/main/java/net/relintai/pandemonium/editor/PandemoniumEditor.kt b/platform/android/java/editor/src/main/java/net/relintai/pandemonium/editor/PandemoniumEditor.kt index 24b7ae314..8411eca51 100644 --- a/platform/android/java/editor/src/main/java/net/relintai/pandemonium/editor/PandemoniumEditor.kt +++ b/platform/android/java/editor/src/main/java/net/relintai/pandemonium/editor/PandemoniumEditor.kt @@ -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()) } diff --git a/platform/android/java/lib/src/net/relintai/pandemonium/pandemonium/Pandemonium.java b/platform/android/java/lib/src/net/relintai/pandemonium/pandemonium/Pandemonium.java index 43b3a2250..ea5d256c1 100644 --- a/platform/android/java/lib/src/net/relintai/pandemonium/pandemonium/Pandemonium.java +++ b/platform/android/java/lib/src/net/relintai/pandemonium/pandemonium/Pandemonium.java @@ -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. diff --git a/platform/android/java/lib/src/net/relintai/pandemonium/pandemonium/PandemoniumView.java b/platform/android/java/lib/src/net/relintai/pandemonium/pandemonium/PandemoniumView.java index d023f46de..02c02d0de 100644 --- a/platform/android/java/lib/src/net/relintai/pandemonium/pandemonium/PandemoniumView.java +++ b/platform/android/java/lib/src/net/relintai/pandemonium/pandemonium/PandemoniumView.java @@ -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); diff --git a/platform/android/java_pandemonium_view_wrapper.cpp b/platform/android/java_pandemonium_view_wrapper.cpp new file mode 100644 index 000000000..cf2b3b1cc --- /dev/null +++ b/platform/android/java_pandemonium_view_wrapper.cpp @@ -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); +} \ No newline at end of file diff --git a/platform/android/java_pandemonium_view_wrapper.h b/platform/android/java_pandemonium_view_wrapper.h new file mode 100644 index 000000000..bb6e07b94 --- /dev/null +++ b/platform/android/java_pandemonium_view_wrapper.h @@ -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 +#include + +#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 \ No newline at end of file diff --git a/platform/android/java_pandemonium_wrapper.cpp b/platform/android/java_pandemonium_wrapper.cpp index 508daab4c..820850b4d 100644 --- a/platform/android/java_pandemonium_wrapper.cpp +++ b/platform/android/java_pandemonium_wrapper.cpp @@ -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) diff --git a/platform/android/java_pandemonium_wrapper.h b/platform/android/java_pandemonium_wrapper.h index c618fde21..bba9110cd 100644 --- a/platform/android/java_pandemonium_wrapper.h +++ b/platform/android/java_pandemonium_wrapper.h @@ -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 #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); diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index b9fe675eb..9f0cc9e52 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -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 { diff --git a/platform/android/os_android.h b/platform/android/os_android.h index aa68e827b..01f4df4c1 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -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);