瀏覽代碼

Merge pull request #66945 from m4gr3d/add_mouse_cursor_shape_support_3x

Add cursor shape support for the Android platform
Fredia Huya-Kouadio 2 年之前
父節點
當前提交
6f153593dc

+ 1 - 0
platform/android/SCsub

@@ -16,6 +16,7 @@ android_files = [
     "java_class_wrapper.cpp",
     "java_godot_wrapper.cpp",
     "java_godot_io_wrapper.cpp",
+    "java_godot_view_wrapper.cpp",
     "jni_utils.cpp",
     "android_keys_utils.cpp",
     "plugin/godot_plugin_jni.cpp",

+ 1 - 1
platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt

@@ -79,7 +79,7 @@ open class GodotEditor : FullScreenGodotApp() {
 		super.onCreate(savedInstanceState)
 
 		// Enable long press, panning and scaling gestures
-		godotFragment?.mView?.inputHandler?.apply {
+		godotFragment?.renderView?.inputHandler?.apply {
 			enableLongPress(enableLongPressGestures())
 			enablePanningAndScalingGestures(enablePanAndScaleGestures())
 		}

+ 5 - 0
platform/android/java/lib/src/org/godotengine/godot/Godot.java

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

+ 26 - 0
platform/android/java/lib/src/org/godotengine/godot/GodotView.java

@@ -44,8 +44,12 @@ import org.godotengine.godot.xr.regular.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;
@@ -89,6 +93,10 @@ public class GodotView extends GLSurfaceView {
 		this.inputHandler = new GodotInputHandler(this);
 		this.godotRenderer = new GodotRenderer();
 
+		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+			setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT));
+		}
+
 		init(xrMode, p_translucent);
 	}
 
@@ -118,6 +126,24 @@ public class GodotView extends GLSurfaceView {
 		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(XRMode xrMode, boolean translucent) {
 		setPreserveEGLContextOnPause(true);
 		setFocusableInTouchMode(true);

+ 66 - 0
platform/android/java_godot_view_wrapper.cpp

@@ -0,0 +1,66 @@
+/*************************************************************************/
+/*  java_godot_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_godot_view_wrapper.h"
+
+GodotJavaViewWrapper::GodotJavaViewWrapper(jobject godot_view) {
+	JNIEnv *env = get_jni_env();
+	ERR_FAIL_NULL(env);
+
+	_godot_view = env->NewGlobalRef(godot_view);
+
+	_cls = (jclass)env->NewGlobalRef(env->GetObjectClass(godot_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 GodotJavaViewWrapper::can_update_pointer_icon() const {
+	return _set_pointer_icon != nullptr;
+}
+
+void GodotJavaViewWrapper::set_pointer_icon(int pointer_type) {
+	if (_set_pointer_icon != nullptr) {
+		JNIEnv *env = get_jni_env();
+		ERR_FAIL_NULL(env);
+
+		env->CallVoidMethod(_godot_view, _set_pointer_icon, pointer_type);
+	}
+}
+
+GodotJavaViewWrapper::~GodotJavaViewWrapper() {
+	JNIEnv *env = get_jni_env();
+	ERR_FAIL_NULL(env);
+
+	env->DeleteGlobalRef(_godot_view);
+	env->DeleteGlobalRef(_cls);
+}

+ 55 - 0
platform/android/java_godot_view_wrapper.h

@@ -0,0 +1,55 @@
+/*************************************************************************/
+/*  java_godot_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.                */
+/*************************************************************************/
+
+#ifndef JAVA_GODOT_VIEW_WRAPPER_H
+#define JAVA_GODOT_VIEW_WRAPPER_H
+
+#include <android/log.h>
+#include <jni.h>
+
+#include "string_android.h"
+
+// Class that makes functions in java/src/org/godotengine/godot/GodotView.java callable from C++
+class GodotJavaViewWrapper {
+private:
+	jclass _cls;
+	jobject _godot_view;
+	jmethodID _set_pointer_icon = 0;
+
+public:
+	GodotJavaViewWrapper(jobject godot_view);
+
+	bool can_update_pointer_icon() const;
+	void set_pointer_icon(int pointer_type);
+
+	~GodotJavaViewWrapper();
+};
+
+#endif // JAVA_GODOT_VIEW_WRAPPER_H

+ 26 - 1
platform/android/java_godot_wrapper.cpp

@@ -81,13 +81,23 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
 	_on_godot_setup_completed = p_env->GetMethodID(godot_class, "onGodotSetupCompleted", "()V");
 	_on_godot_main_loop_started = p_env->GetMethodID(godot_class, "onGodotMainLoopStarted", "()V");
 	_create_new_godot_instance = p_env->GetMethodID(godot_class, "createNewGodotInstance", "([Ljava/lang/String;)V");
+	_get_render_view = p_env->GetMethodID(godot_class, "getRenderView", "()Lorg/godotengine/godot/GodotView;");
 
 	// get some Activity method pointers...
 	_get_class_loader = p_env->GetMethodID(activity_class, "getClassLoader", "()Ljava/lang/ClassLoader;");
 }
 
 GodotJavaWrapper::~GodotJavaWrapper() {
-	// nothing to do here for now
+	if (godot_view) {
+		delete godot_view;
+	}
+
+	JNIEnv *env = get_jni_env();
+	ERR_FAIL_NULL(env);
+	env->DeleteGlobalRef(godot_instance);
+	env->DeleteGlobalRef(godot_class);
+	env->DeleteGlobalRef(activity);
+	env->DeleteGlobalRef(activity_class);
 }
 
 jobject GodotJavaWrapper::get_activity() {
@@ -117,6 +127,21 @@ jobject GodotJavaWrapper::get_class_loader() {
 	}
 }
 
+GodotJavaViewWrapper *GodotJavaWrapper::get_godot_view() {
+	if (godot_view != nullptr) {
+		return godot_view;
+	}
+	if (_get_render_view) {
+		JNIEnv *env = get_jni_env();
+		ERR_FAIL_NULL_V(env, nullptr);
+		jobject godot_render_view = env->CallObjectMethod(godot_instance, _get_render_view);
+		if (!env->IsSameObject(godot_render_view, nullptr)) {
+			godot_view = new GodotJavaViewWrapper(godot_render_view);
+		}
+	}
+	return godot_view;
+}
+
 void GodotJavaWrapper::on_video_init(JNIEnv *p_env) {
 	if (_on_video_init) {
 		if (p_env == nullptr) {

+ 5 - 0
platform/android/java_godot_wrapper.h

@@ -38,6 +38,7 @@
 #include <jni.h>
 
 #include "core/list.h"
+#include "java_godot_view_wrapper.h"
 #include "string_android.h"
 
 // Class that makes functions in java/src/org/godotengine/godot/Godot.java callable from C++
@@ -48,6 +49,8 @@ private:
 	jclass godot_class;
 	jclass activity_class;
 
+	GodotJavaViewWrapper *godot_view = nullptr;
+
 	jmethodID _on_video_init = nullptr;
 	jmethodID _create_offscreen_gl = nullptr;
 	jmethodID _destroy_offscreen_gl = nullptr;
@@ -72,6 +75,7 @@ private:
 	jmethodID _on_godot_main_loop_started = nullptr;
 	jmethodID _get_class_loader = nullptr;
 	jmethodID _create_new_godot_instance = nullptr;
+	jmethodID _get_render_view = nullptr;
 
 public:
 	GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance);
@@ -81,6 +85,7 @@ public:
 	jobject get_member_object(const char *p_name, const char *p_class, JNIEnv *p_env = NULL);
 
 	jobject get_class_loader();
+	GodotJavaViewWrapper *get_godot_view();
 
 	bool create_offscreen_gl(JNIEnv *p_env);
 	void destroy_offscreen_gl(JNIEnv *p_env);

+ 33 - 7
platform/android/os_android.cpp

@@ -273,17 +273,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 {

+ 30 - 3
platform/android/os_android.h

@@ -42,6 +42,31 @@ class GodotJavaWrapper;
 class GodotIOJavaWrapper;
 
 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_apk_expansion;
 	bool secondary_gl_available = false;
 
@@ -106,9 +131,6 @@ 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 Point2 get_mouse_position() const;
 	virtual int get_mouse_button_state() const;
 	virtual void set_window_title(const String &p_title);
@@ -141,6 +163,11 @@ public:
 	virtual void hide_virtual_keyboard();
 	virtual int get_virtual_keyboard_height() 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;
+
 	void set_opengl_extensions(const char *p_gl_extensions);
 	void set_display_size(Size2 p_size);