浏览代码

Merge pull request #69281 from m4gr3d/implement_missing_display_server_overrides

Android: Add missing display server overrides
Rémi Verschelde 2 年之前
父节点
当前提交
ac2be7c61e

+ 35 - 2
platform/android/display_server_android.cpp

@@ -619,11 +619,11 @@ MouseButton DisplayServerAndroid::mouse_get_button_state() const {
 	return (MouseButton)Input::get_singleton()->get_mouse_button_mask();
 }
 
-void DisplayServerAndroid::cursor_set_shape(DisplayServer::CursorShape p_shape) {
+void DisplayServerAndroid::_cursor_set_shape_helper(CursorShape p_shape, bool force) {
 	if (!OS_Android::get_singleton()->get_godot_java()->get_godot_view()->can_update_pointer_icon()) {
 		return;
 	}
-	if (cursor_shape == p_shape) {
+	if (cursor_shape == p_shape && !force) {
 		return;
 	}
 
@@ -634,10 +634,23 @@ void DisplayServerAndroid::cursor_set_shape(DisplayServer::CursorShape p_shape)
 	}
 }
 
+void DisplayServerAndroid::cursor_set_shape(DisplayServer::CursorShape p_shape) {
+	_cursor_set_shape_helper(p_shape);
+}
+
 DisplayServer::CursorShape DisplayServerAndroid::cursor_get_shape() const {
 	return cursor_shape;
 }
 
+void DisplayServerAndroid::cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
+	String cursor_path = p_cursor.is_valid() ? p_cursor->get_path() : "";
+	if (!cursor_path.is_empty()) {
+		cursor_path = ProjectSettings::get_singleton()->globalize_path(cursor_path);
+	}
+	OS_Android::get_singleton()->get_godot_java()->get_godot_view()->configure_pointer_icon(android_cursors[cursor_shape], cursor_path, p_hotspot);
+	_cursor_set_shape_helper(p_shape, true);
+}
+
 void DisplayServerAndroid::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) {
 #if defined(VULKAN_ENABLED)
 	context_vulkan->set_vsync_mode(p_window, p_vsync_mode);
@@ -651,3 +664,23 @@ DisplayServer::VSyncMode DisplayServerAndroid::window_get_vsync_mode(WindowID p_
 	return DisplayServer::VSYNC_ENABLED;
 #endif
 }
+
+void DisplayServerAndroid::reset_swap_buffers_flag() {
+	swap_buffers_flag = false;
+}
+
+bool DisplayServerAndroid::should_swap_buffers() const {
+	return swap_buffers_flag;
+}
+
+void DisplayServerAndroid::swap_buffers() {
+	swap_buffers_flag = true;
+}
+
+void DisplayServerAndroid::set_native_icon(const String &p_filename) {
+	// NOT SUPPORTED
+}
+
+void DisplayServerAndroid::set_icon(const Ref<Image> &p_icon) {
+	// NOT SUPPORTED
+}

+ 10 - 0
platform/android/display_server_android.h

@@ -66,6 +66,7 @@ class DisplayServerAndroid : public DisplayServer {
 	MouseMode mouse_mode = MouseMode::MOUSE_MODE_VISIBLE;
 
 	bool keep_screen_on;
+	bool swap_buffers_flag;
 
 	CursorShape cursor_shape = CursorShape::CURSOR_ARROW;
 
@@ -188,8 +189,10 @@ public:
 	void process_magnetometer(const Vector3 &p_magnetometer);
 	void process_gyroscope(const Vector3 &p_gyroscope);
 
+	void _cursor_set_shape_helper(CursorShape p_shape, bool force = false);
 	virtual void cursor_set_shape(CursorShape p_shape) override;
 	virtual CursorShape cursor_get_shape() const override;
+	virtual void cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override;
 
 	virtual void mouse_set_mode(MouseMode p_mode) override;
 	virtual MouseMode mouse_get_mode() const override;
@@ -204,6 +207,13 @@ public:
 	virtual Point2i mouse_get_position() const override;
 	virtual MouseButton mouse_get_button_state() const override;
 
+	void reset_swap_buffers_flag();
+	bool should_swap_buffers() const;
+	virtual void swap_buffers() override;
+
+	virtual void set_native_icon(const String &p_filename) override;
+	virtual void set_icon(const Ref<Image> &p_icon) override;
+
 	DisplayServerAndroid(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, Error &r_error);
 	~DisplayServerAndroid();
 };

+ 2 - 1
platform/android/java/lib/src/org/godotengine/godot/Godot.java

@@ -175,6 +175,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
 	public GodotIO io;
 	public GodotNetUtils netUtils;
 	public GodotTTS tts;
+	DirectoryAccessHandler directoryAccessHandler;
 
 	public interface ResultCallback {
 		void callback(int requestCode, int resultCode, Intent data);
@@ -488,7 +489,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
 		netUtils = new GodotNetUtils(activity);
 		tts = new GodotTTS(activity);
 		Context context = getContext();
-		DirectoryAccessHandler directoryAccessHandler = new DirectoryAccessHandler(context);
+		directoryAccessHandler = new DirectoryAccessHandler(context);
 		FileAccessHandler fileAccessHandler = new FileAccessHandler(context);
 		mSensorManager = (SensorManager)activity.getSystemService(Context.SENSOR_SERVICE);
 		mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

+ 46 - 1
platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java

@@ -43,8 +43,13 @@ import org.godotengine.godot.xr.regular.RegularFallbackConfigChooser;
 
 import android.annotation.SuppressLint;
 import android.content.Context;
+import android.content.res.AssetManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.graphics.PixelFormat;
 import android.os.Build;
+import android.text.TextUtils;
+import android.util.SparseArray;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.PointerIcon;
@@ -52,6 +57,8 @@ import android.view.SurfaceView;
 
 import androidx.annotation.Keep;
 
+import java.io.InputStream;
+
 /**
  * A simple GLSurfaceView sub-class that demonstrate how to perform
  * OpenGL ES 2.0 rendering into a GL Surface. Note the following important
@@ -74,6 +81,7 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView
 	private final Godot godot;
 	private final GodotInputHandler inputHandler;
 	private final GodotRenderer godotRenderer;
+	private final SparseArray<PointerIcon> customPointerIcons = new SparseArray<>();
 
 	public GodotGLRenderView(Context context, Godot godot, XRMode xrMode, boolean p_use_debug_opengl) {
 		super(context);
@@ -168,13 +176,50 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView
 		inputHandler.onPointerCaptureChange(false);
 	}
 
+	/**
+	 * Used to configure the PointerIcon for the given type.
+	 *
+	 * Called from JNI
+	 */
+	@Keep
+	@Override
+	public void configurePointerIcon(int pointerType, String imagePath, float hotSpotX, float hotSpotY) {
+		if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
+			try {
+				Bitmap bitmap = null;
+				if (!TextUtils.isEmpty(imagePath)) {
+					if (godot.directoryAccessHandler.filesystemFileExists(imagePath)) {
+						// Try to load the bitmap from the file system
+						bitmap = BitmapFactory.decodeFile(imagePath);
+					} else if (godot.directoryAccessHandler.assetsFileExists(imagePath)) {
+						// Try to load the bitmap from the assets directory
+						AssetManager am = getContext().getAssets();
+						InputStream imageInputStream = am.open(imagePath);
+						bitmap = BitmapFactory.decodeStream(imageInputStream);
+					}
+				}
+
+				PointerIcon customPointerIcon = PointerIcon.create(bitmap, hotSpotX, hotSpotY);
+				customPointerIcons.put(pointerType, customPointerIcon);
+			} catch (Exception e) {
+				// Reset the custom pointer icon
+				customPointerIcons.delete(pointerType);
+			}
+		}
+	}
+
 	/**
 	 * called from JNI to change pointer icon
 	 */
 	@Keep
+	@Override
 	public void setPointerIcon(int pointerType) {
 		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-			setPointerIcon(PointerIcon.getSystemIcon(getContext(), pointerType));
+			PointerIcon pointerIcon = customPointerIcons.get(pointerType);
+			if (pointerIcon == null) {
+				pointerIcon = PointerIcon.getSystemIcon(getContext(), pointerType);
+			}
+			setPointerIcon(pointerIcon);
 		}
 	}
 

+ 2 - 0
platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java

@@ -48,5 +48,7 @@ public interface GodotRenderView {
 
 	GodotInputHandler getInputHandler();
 
+	void configurePointerIcon(int pointerType, String imagePath, float hotSpotX, float hotSpotY);
+
 	void setPointerIcon(int pointerType);
 }

+ 46 - 1
platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java

@@ -36,7 +36,12 @@ import org.godotengine.godot.vulkan.VkSurfaceView;
 
 import android.annotation.SuppressLint;
 import android.content.Context;
+import android.content.res.AssetManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.os.Build;
+import android.text.TextUtils;
+import android.util.SparseArray;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.PointerIcon;
@@ -44,10 +49,13 @@ import android.view.SurfaceView;
 
 import androidx.annotation.Keep;
 
+import java.io.InputStream;
+
 public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView {
 	private final Godot godot;
 	private final GodotInputHandler mInputHandler;
 	private final VkRenderer mRenderer;
+	private final SparseArray<PointerIcon> customPointerIcons = new SparseArray<>();
 
 	public GodotVulkanRenderView(Context context, Godot godot) {
 		super(context);
@@ -142,13 +150,50 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV
 		mInputHandler.onPointerCaptureChange(hasCapture);
 	}
 
+	/**
+	 * Used to configure the PointerIcon for the given type.
+	 *
+	 * Called from JNI
+	 */
+	@Keep
+	@Override
+	public void configurePointerIcon(int pointerType, String imagePath, float hotSpotX, float hotSpotY) {
+		if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
+			try {
+				Bitmap bitmap = null;
+				if (!TextUtils.isEmpty(imagePath)) {
+					if (godot.directoryAccessHandler.filesystemFileExists(imagePath)) {
+						// Try to load the bitmap from the file system
+						bitmap = BitmapFactory.decodeFile(imagePath);
+					} else if (godot.directoryAccessHandler.assetsFileExists(imagePath)) {
+						// Try to load the bitmap from the assets directory
+						AssetManager am = getContext().getAssets();
+						InputStream imageInputStream = am.open(imagePath);
+						bitmap = BitmapFactory.decodeStream(imageInputStream);
+					}
+				}
+
+				PointerIcon customPointerIcon = PointerIcon.create(bitmap, hotSpotX, hotSpotY);
+				customPointerIcons.put(pointerType, customPointerIcon);
+			} catch (Exception e) {
+				// Reset the custom pointer icon
+				customPointerIcons.delete(pointerType);
+			}
+		}
+	}
+
 	/**
 	 * called from JNI to change pointer icon
 	 */
 	@Keep
+	@Override
 	public void setPointerIcon(int pointerType) {
 		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-			setPointerIcon(PointerIcon.getSystemIcon(getContext(), pointerType));
+			PointerIcon pointerIcon = customPointerIcons.get(pointerType);
+			if (pointerIcon == null) {
+				pointerIcon = PointerIcon.getSystemIcon(getContext(), pointerType);
+			}
+			setPointerIcon(pointerIcon);
 		}
 	}
 

+ 3 - 0
platform/android/java/lib/src/org/godotengine/godot/io/directory/DirectoryAccessHandler.kt

@@ -79,6 +79,9 @@ class DirectoryAccessHandler(context: Context) {
 	private val assetsDirAccess = AssetsDirectoryAccess(context)
 	private val fileSystemDirAccess = FilesystemDirectoryAccess(context)
 
+	fun assetsFileExists(assetsPath: String) = assetsDirAccess.fileExists(assetsPath)
+	fun filesystemFileExists(path: String) = fileSystemDirAccess.fileExists(path)
+
 	private fun hasDirId(accessType: AccessType, dirId: Int): Boolean {
 		return when (accessType) {
 			ACCESS_RESOURCES -> assetsDirAccess.hasDirId(dirId)

+ 12 - 1
platform/android/java_godot_view_wrapper.cpp

@@ -42,6 +42,7 @@ GodotJavaViewWrapper::GodotJavaViewWrapper(jobject godot_view) {
 
 	int android_device_api_level = android_get_device_api_level();
 	if (android_device_api_level >= __ANDROID_API_N__) {
+		_configure_pointer_icon = env->GetMethodID(_cls, "configurePointerIcon", "(ILjava/lang/String;FF)V");
 		_set_pointer_icon = env->GetMethodID(_cls, "setPointerIcon", "(I)V");
 	}
 	if (android_device_api_level >= __ANDROID_API_O__) {
@@ -51,7 +52,7 @@ GodotJavaViewWrapper::GodotJavaViewWrapper(jobject godot_view) {
 }
 
 bool GodotJavaViewWrapper::can_update_pointer_icon() const {
-	return _set_pointer_icon != nullptr;
+	return _configure_pointer_icon != nullptr && _set_pointer_icon != nullptr;
 }
 
 bool GodotJavaViewWrapper::can_capture_pointer() const {
@@ -76,6 +77,16 @@ void GodotJavaViewWrapper::release_pointer_capture() {
 	}
 }
 
+void GodotJavaViewWrapper::configure_pointer_icon(int pointer_type, const String &image_path, const Vector2 &p_hotspot) {
+	if (_configure_pointer_icon != nullptr) {
+		JNIEnv *env = get_jni_env();
+		ERR_FAIL_NULL(env);
+
+		jstring jImagePath = env->NewStringUTF(image_path.utf8().get_data());
+		env->CallVoidMethod(_godot_view, _configure_pointer_icon, pointer_type, jImagePath, p_hotspot.x, p_hotspot.y);
+	}
+}
+
 void GodotJavaViewWrapper::set_pointer_icon(int pointer_type) {
 	if (_set_pointer_icon != nullptr) {
 		JNIEnv *env = get_jni_env();

+ 5 - 0
platform/android/java_godot_view_wrapper.h

@@ -31,6 +31,7 @@
 #ifndef JAVA_GODOT_VIEW_WRAPPER_H
 #define JAVA_GODOT_VIEW_WRAPPER_H
 
+#include "core/math/vector2.h"
 #include <android/log.h>
 #include <jni.h>
 
@@ -45,6 +46,8 @@ private:
 
 	jmethodID _request_pointer_capture = 0;
 	jmethodID _release_pointer_capture = 0;
+
+	jmethodID _configure_pointer_icon = 0;
 	jmethodID _set_pointer_icon = 0;
 
 public:
@@ -55,6 +58,8 @@ public:
 
 	void request_pointer_capture();
 	void release_pointer_capture();
+
+	void configure_pointer_icon(int pointer_type, const String &image_path, const Vector2 &p_hotspot);
 	void set_pointer_icon(int pointer_type);
 
 	~GodotJavaViewWrapper();

+ 5 - 1
platform/android/os_android.cpp

@@ -268,12 +268,16 @@ bool OS_Android::main_loop_iterate(bool *r_should_swap_buffers) {
 	if (!main_loop) {
 		return false;
 	}
+	DisplayServerAndroid::get_singleton()->reset_swap_buffers_flag();
 	DisplayServerAndroid::get_singleton()->process_events();
 	uint64_t current_frames_drawn = Engine::get_singleton()->get_frames_drawn();
 	bool exit = Main::iteration();
 
 	if (r_should_swap_buffers) {
-		*r_should_swap_buffers = !is_in_low_processor_usage_mode() || RenderingServer::get_singleton()->has_changed() || current_frames_drawn != Engine::get_singleton()->get_frames_drawn();
+		*r_should_swap_buffers = !is_in_low_processor_usage_mode() ||
+				DisplayServerAndroid::get_singleton()->should_swap_buffers() ||
+				RenderingServer::get_singleton()->has_changed() ||
+				current_frames_drawn != Engine::get_singleton()->get_frames_drawn();
 	}
 
 	return exit;