You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-11-04 12:00:25 +00:00
Merge pull request #103972 from m4gr3d/xr_editor_hybrid_support
Add support for running hybrid apps from the XR editor
This commit is contained in:
@@ -311,9 +311,7 @@ void EditorDebuggerNode::stop(bool p_force) {
|
||||
|
||||
// Also close all debugging sessions.
|
||||
_for_all(tabs, [&](ScriptEditorDebugger *dbg) {
|
||||
if (dbg->is_session_active()) {
|
||||
dbg->_stop_and_notify();
|
||||
}
|
||||
dbg->_stop_and_notify();
|
||||
});
|
||||
_break_state_changed();
|
||||
breakpoints.clear();
|
||||
|
||||
@@ -2176,7 +2176,7 @@ void EditorNode::try_autosave() {
|
||||
Node *scene = editor_data.get_edited_scene_root();
|
||||
|
||||
if (scene && !scene->get_scene_file_path().is_empty()) { // Only autosave if there is a scene and if it has a path.
|
||||
_save_scene(scene->get_scene_file_path());
|
||||
_save_scene(scene->get_scene_file_path(), -1, false);
|
||||
}
|
||||
}
|
||||
_menu_option(SCENE_SAVE_ALL_SCENES);
|
||||
|
||||
@@ -32,7 +32,8 @@ android_files = [
|
||||
"rendering_context_driver_vulkan_android.cpp",
|
||||
"variant/callable_jni.cpp",
|
||||
"dialog_utils_jni.cpp",
|
||||
"game_menu_utils_jni.cpp",
|
||||
"editor/game_menu_utils_jni.cpp",
|
||||
"editor/editor_utils_jni.cpp",
|
||||
]
|
||||
|
||||
env_android = env.Clone()
|
||||
|
||||
82
platform/android/editor/editor_utils_jni.cpp
Normal file
82
platform/android/editor/editor_utils_jni.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
/**************************************************************************/
|
||||
/* editor_utils_jni.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 "editor_utils_jni.h"
|
||||
|
||||
#include "jni_utils.h"
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
#include "editor/gui/editor_run_bar.h"
|
||||
#include "main/main.h"
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_editor_utils_EditorUtils_runScene(JNIEnv *p_env, jclass, jstring p_scene, jobjectArray p_scene_args) {
|
||||
#ifdef TOOLS_ENABLED
|
||||
Vector<String> scene_args;
|
||||
jint length = p_env->GetArrayLength(p_scene_args);
|
||||
for (jint i = 0; i < length; ++i) {
|
||||
jstring j_arg = (jstring)p_env->GetObjectArrayElement(p_scene_args, i);
|
||||
String arg = jstring_to_string(j_arg, p_env);
|
||||
scene_args.push_back(arg);
|
||||
p_env->DeleteLocalRef(j_arg);
|
||||
}
|
||||
|
||||
String scene = jstring_to_string(p_scene, p_env);
|
||||
|
||||
EditorRunBar *editor_run_bar = EditorRunBar::get_singleton();
|
||||
if (editor_run_bar != nullptr) {
|
||||
if (scene.is_empty()) {
|
||||
editor_run_bar->play_main_scene(false);
|
||||
} else {
|
||||
editor_run_bar->play_custom_scene(scene, scene_args);
|
||||
}
|
||||
} else {
|
||||
List<String> args;
|
||||
|
||||
for (const String &a : Main::get_forwardable_cli_arguments(Main::CLI_SCOPE_PROJECT)) {
|
||||
args.push_back(a);
|
||||
}
|
||||
|
||||
for (const String &arg : scene_args) {
|
||||
args.push_back(arg);
|
||||
}
|
||||
|
||||
if (!scene.is_empty()) {
|
||||
args.push_back("--scene");
|
||||
args.push_back(scene);
|
||||
}
|
||||
|
||||
Error err = OS::get_singleton()->create_instance(args);
|
||||
ERR_FAIL_COND(err);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
37
platform/android/editor/editor_utils_jni.h
Normal file
37
platform/android/editor/editor_utils_jni.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/**************************************************************************/
|
||||
/* editor_utils_jni.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
extern "C" {
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_editor_utils_EditorUtils_runScene(JNIEnv *p_env, jclass, jstring p_scene, jobjectArray p_scene_args);
|
||||
}
|
||||
@@ -45,7 +45,7 @@ static GameViewPlugin *_get_game_view_plugin() {
|
||||
|
||||
extern "C" {
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_setSuspend(JNIEnv *env, jclass clazz, jboolean enabled) {
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_editor_utils_GameMenuUtils_setSuspend(JNIEnv *env, jclass clazz, jboolean enabled) {
|
||||
#ifdef TOOLS_ENABLED
|
||||
GameViewPlugin *game_view_plugin = _get_game_view_plugin();
|
||||
if (game_view_plugin != nullptr && game_view_plugin->get_debugger().is_valid()) {
|
||||
@@ -54,7 +54,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_setSuspend
|
||||
#endif
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_nextFrame(JNIEnv *env, jclass clazz) {
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_editor_utils_GameMenuUtils_nextFrame(JNIEnv *env, jclass clazz) {
|
||||
#ifdef TOOLS_ENABLED
|
||||
GameViewPlugin *game_view_plugin = _get_game_view_plugin();
|
||||
if (game_view_plugin != nullptr && game_view_plugin->get_debugger().is_valid()) {
|
||||
@@ -63,7 +63,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_nextFrame(
|
||||
#endif
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_setNodeType(JNIEnv *env, jclass clazz, jint type) {
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_editor_utils_GameMenuUtils_setNodeType(JNIEnv *env, jclass clazz, jint type) {
|
||||
#ifdef TOOLS_ENABLED
|
||||
GameViewPlugin *game_view_plugin = _get_game_view_plugin();
|
||||
if (game_view_plugin != nullptr && game_view_plugin->get_debugger().is_valid()) {
|
||||
@@ -72,7 +72,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_setNodeTyp
|
||||
#endif
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_setSelectMode(JNIEnv *env, jclass clazz, jint mode) {
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_editor_utils_GameMenuUtils_setSelectMode(JNIEnv *env, jclass clazz, jint mode) {
|
||||
#ifdef TOOLS_ENABLED
|
||||
GameViewPlugin *game_view_plugin = _get_game_view_plugin();
|
||||
if (game_view_plugin != nullptr && game_view_plugin->get_debugger().is_valid()) {
|
||||
@@ -81,7 +81,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_setSelectM
|
||||
#endif
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_setSelectionVisible(JNIEnv *env, jclass clazz, jboolean visible) {
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_editor_utils_GameMenuUtils_setSelectionVisible(JNIEnv *env, jclass clazz, jboolean visible) {
|
||||
#ifdef TOOLS_ENABLED
|
||||
GameViewPlugin *game_view_plugin = _get_game_view_plugin();
|
||||
if (game_view_plugin != nullptr && game_view_plugin->get_debugger().is_valid()) {
|
||||
@@ -90,7 +90,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_setSelecti
|
||||
#endif
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_setCameraOverride(JNIEnv *env, jclass clazz, jboolean enabled) {
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_editor_utils_GameMenuUtils_setCameraOverride(JNIEnv *env, jclass clazz, jboolean enabled) {
|
||||
#ifdef TOOLS_ENABLED
|
||||
GameViewPlugin *game_view_plugin = _get_game_view_plugin();
|
||||
if (game_view_plugin != nullptr && game_view_plugin->get_debugger().is_valid()) {
|
||||
@@ -99,7 +99,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_setCameraO
|
||||
#endif
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_setCameraManipulateMode(JNIEnv *env, jclass clazz, jint mode) {
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_editor_utils_GameMenuUtils_setCameraManipulateMode(JNIEnv *env, jclass clazz, jint mode) {
|
||||
#ifdef TOOLS_ENABLED
|
||||
GameViewPlugin *game_view_plugin = _get_game_view_plugin();
|
||||
if (game_view_plugin != nullptr && game_view_plugin->get_debugger().is_valid()) {
|
||||
@@ -108,7 +108,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_setCameraM
|
||||
#endif
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_resetCamera2DPosition(JNIEnv *env, jclass clazz) {
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_editor_utils_GameMenuUtils_resetCamera2DPosition(JNIEnv *env, jclass clazz) {
|
||||
#ifdef TOOLS_ENABLED
|
||||
GameViewPlugin *game_view_plugin = _get_game_view_plugin();
|
||||
if (game_view_plugin != nullptr && game_view_plugin->get_debugger().is_valid()) {
|
||||
@@ -117,7 +117,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_resetCamer
|
||||
#endif
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_resetCamera3DPosition(JNIEnv *env, jclass clazz) {
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_editor_utils_GameMenuUtils_resetCamera3DPosition(JNIEnv *env, jclass clazz) {
|
||||
#ifdef TOOLS_ENABLED
|
||||
GameViewPlugin *game_view_plugin = _get_game_view_plugin();
|
||||
if (game_view_plugin != nullptr && game_view_plugin->get_debugger().is_valid()) {
|
||||
@@ -126,7 +126,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_resetCamer
|
||||
#endif
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_playMainScene(JNIEnv *env, jclass clazz) {
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_editor_utils_GameMenuUtils_playMainScene(JNIEnv *env, jclass clazz) {
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (EditorInterface::get_singleton()) {
|
||||
EditorInterface::get_singleton()->play_main_scene();
|
||||
@@ -134,7 +134,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_playMainSc
|
||||
#endif
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_setDebugMuteAudio(JNIEnv *env, jclass clazz, jboolean enabled) {
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_editor_utils_GameMenuUtils_setDebugMuteAudio(JNIEnv *env, jclass clazz, jboolean enabled) {
|
||||
#ifdef TOOLS_ENABLED
|
||||
GameViewPlugin *game_view_plugin = _get_game_view_plugin();
|
||||
if (game_view_plugin != nullptr && game_view_plugin->get_debugger().is_valid()) {
|
||||
@@ -33,15 +33,15 @@
|
||||
#include <jni.h>
|
||||
|
||||
extern "C" {
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_setSuspend(JNIEnv *env, jclass clazz, jboolean enabled);
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_nextFrame(JNIEnv *env, jclass clazz);
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_setNodeType(JNIEnv *env, jclass clazz, jint type);
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_setSelectMode(JNIEnv *env, jclass clazz, jint mode);
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_setSelectionVisible(JNIEnv *env, jclass clazz, jboolean visible);
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_setCameraOverride(JNIEnv *env, jclass clazz, jboolean enabled);
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_setCameraManipulateMode(JNIEnv *env, jclass clazz, jint mode);
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_resetCamera2DPosition(JNIEnv *env, jclass clazz);
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_resetCamera3DPosition(JNIEnv *env, jclass clazz);
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_playMainScene(JNIEnv *env, jclass clazz);
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_setDebugMuteAudio(JNIEnv *env, jclass clazz, jboolean enabled);
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_editor_utils_GameMenuUtils_setSuspend(JNIEnv *env, jclass clazz, jboolean enabled);
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_editor_utils_GameMenuUtils_nextFrame(JNIEnv *env, jclass clazz);
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_editor_utils_GameMenuUtils_setNodeType(JNIEnv *env, jclass clazz, jint type);
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_editor_utils_GameMenuUtils_setSelectMode(JNIEnv *env, jclass clazz, jint mode);
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_editor_utils_GameMenuUtils_setSelectionVisible(JNIEnv *env, jclass clazz, jboolean visible);
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_editor_utils_GameMenuUtils_setCameraOverride(JNIEnv *env, jclass clazz, jboolean enabled);
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_editor_utils_GameMenuUtils_setCameraManipulateMode(JNIEnv *env, jclass clazz, jint mode);
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_editor_utils_GameMenuUtils_resetCamera2DPosition(JNIEnv *env, jclass clazz);
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_editor_utils_GameMenuUtils_resetCamera3DPosition(JNIEnv *env, jclass clazz);
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_editor_utils_GameMenuUtils_playMainScene(JNIEnv *env, jclass clazz);
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_editor_utils_GameMenuUtils_setDebugMuteAudio(JNIEnv *env, jclass clazz, jboolean enabled);
|
||||
}
|
||||
@@ -180,6 +180,8 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: "libs", include: ["*.jar", "*.aar"])
|
||||
|
||||
implementation "androidx.fragment:fragment:$versions.fragmentVersion"
|
||||
implementation project(":lib")
|
||||
|
||||
|
||||
@@ -60,6 +60,22 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Intent filter used to intercept hybrid PANEL launch for the current editor project, and route it
|
||||
properly through the editor 'run' logic (e.g: debugger setup) -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="org.godotengine.xr.hybrid.PANEL" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Intent filter used to intercept hybrid IMMERSIVE launch for the current editor project, and route it
|
||||
properly through the editor 'run' logic (e.g: debugger setup) -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="org.godotengine.xr.hybrid.IMMERSIVE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".GodotGame"
|
||||
@@ -101,8 +117,7 @@
|
||||
android:autoRemoveFromRecents="true"
|
||||
android:screenOrientation="landscape"
|
||||
android:resizeableActivity="false"
|
||||
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
|
||||
</activity>
|
||||
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen" />
|
||||
|
||||
<!--
|
||||
We remove this meta-data originating from the vendors plugin as we only need the loader for
|
||||
|
||||
@@ -60,14 +60,19 @@ import org.godotengine.editor.utils.verifyApk
|
||||
import org.godotengine.godot.BuildConfig
|
||||
import org.godotengine.godot.GodotActivity
|
||||
import org.godotengine.godot.GodotLib
|
||||
import org.godotengine.godot.editor.utils.EditorUtils
|
||||
import org.godotengine.godot.editor.utils.GameMenuUtils
|
||||
import org.godotengine.godot.editor.utils.GameMenuUtils.GameEmbedMode
|
||||
import org.godotengine.godot.editor.utils.GameMenuUtils.fetchGameEmbedMode
|
||||
import org.godotengine.godot.error.Error
|
||||
import org.godotengine.godot.utils.DialogUtils
|
||||
import org.godotengine.godot.utils.GameMenuUtils
|
||||
import org.godotengine.godot.utils.GameMenuUtils.GameEmbedMode
|
||||
import org.godotengine.godot.utils.GameMenuUtils.fetchGameEmbedMode
|
||||
import org.godotengine.godot.utils.PermissionsUtil
|
||||
import org.godotengine.godot.utils.ProcessPhoenix
|
||||
import org.godotengine.godot.utils.isNativeXRDevice
|
||||
import org.godotengine.godot.xr.HybridMode
|
||||
import org.godotengine.godot.xr.getHybridAppLaunchMode
|
||||
import org.godotengine.godot.xr.HYBRID_APP_PANEL_CATEGORY
|
||||
import org.godotengine.godot.xr.HYBRID_APP_IMMERSIVE_CATEGORY
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
@@ -98,6 +103,8 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
||||
private const val EDITOR_PROJECT_MANAGER_ARG = "--project-manager"
|
||||
private const val EDITOR_PROJECT_MANAGER_ARG_SHORT = "-p"
|
||||
internal const val XR_MODE_ARG = "--xr-mode"
|
||||
private const val SCENE_ARG = "--scene"
|
||||
private const val PATH_ARG = "--path"
|
||||
|
||||
// Info for the various classes used by the editor.
|
||||
internal val EDITOR_MAIN_INFO = EditorWindowInfo(GodotEditor::class.java, 777, "")
|
||||
@@ -236,6 +243,50 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
||||
setupGameMenuBar()
|
||||
}
|
||||
|
||||
override fun onNewIntent(newIntent: Intent) {
|
||||
if (newIntent.hasCategory(HYBRID_APP_PANEL_CATEGORY) || newIntent.hasCategory(HYBRID_APP_IMMERSIVE_CATEGORY)) {
|
||||
val params = newIntent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS)
|
||||
Log.d(TAG, "Received hybrid transition intent $newIntent with parameters ${params.contentToString()}")
|
||||
// Override EXTRA_NEW_LAUNCH so the editor is not restarted
|
||||
newIntent.putExtra(EXTRA_NEW_LAUNCH, false)
|
||||
|
||||
godot?.runOnRenderThread {
|
||||
// Look for the scene and xr-mode arguments
|
||||
var scene = ""
|
||||
var xrMode = XR_MODE_DEFAULT
|
||||
var path = ""
|
||||
if (params != null) {
|
||||
val sceneIndex = params.indexOf(SCENE_ARG)
|
||||
if (sceneIndex != -1 && sceneIndex + 1 < params.size) {
|
||||
scene = params[sceneIndex +1]
|
||||
}
|
||||
|
||||
val xrModeIndex = params.indexOf(XR_MODE_ARG)
|
||||
if (xrModeIndex != -1 && xrModeIndex + 1 < params.size) {
|
||||
xrMode = params[xrModeIndex + 1]
|
||||
}
|
||||
|
||||
val pathIndex = params.indexOf(PATH_ARG)
|
||||
if (pathIndex != -1 && pathIndex + 1 < params.size) {
|
||||
path = params[pathIndex + 1]
|
||||
}
|
||||
}
|
||||
|
||||
val sceneArgs = mutableSetOf(XR_MODE_ARG, xrMode).apply {
|
||||
if (path.isNotEmpty() && scene.isEmpty()) {
|
||||
add(PATH_ARG)
|
||||
add(path)
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "Running scene $scene with arguments: $sceneArgs")
|
||||
EditorUtils.runScene(scene, sceneArgs.toTypedArray())
|
||||
}
|
||||
}
|
||||
|
||||
super.onNewIntent(newIntent)
|
||||
}
|
||||
|
||||
protected open fun shouldShowGameMenuBar() = gameMenuContainer != null
|
||||
|
||||
private fun setupGameMenuBar() {
|
||||
@@ -327,26 +378,41 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
||||
}
|
||||
}
|
||||
|
||||
return if (hasEditor) {
|
||||
EDITOR_MAIN_INFO
|
||||
} else {
|
||||
// Launching a game.
|
||||
val openxrEnabled = xrMode == XR_MODE_ON ||
|
||||
(xrMode == XR_MODE_DEFAULT && GodotLib.getGlobal("xr/openxr/enabled").toBoolean())
|
||||
if (openxrEnabled && isNativeXRDevice(applicationContext)) {
|
||||
XR_RUN_GAME_INFO
|
||||
} else {
|
||||
if (godot?.isProjectManagerHint() == true || isNativeXRDevice(applicationContext)) {
|
||||
if (hasEditor) {
|
||||
return EDITOR_MAIN_INFO
|
||||
}
|
||||
|
||||
// Launching a game.
|
||||
if (isNativeXRDevice(applicationContext)) {
|
||||
if (xrMode == XR_MODE_ON) {
|
||||
return XR_RUN_GAME_INFO
|
||||
}
|
||||
|
||||
if ((xrMode == XR_MODE_DEFAULT && GodotLib.getGlobal("xr/openxr/enabled").toBoolean())) {
|
||||
val hybridLaunchMode = getHybridAppLaunchMode()
|
||||
|
||||
return if (hybridLaunchMode == HybridMode.PANEL) {
|
||||
RUN_GAME_INFO
|
||||
} else {
|
||||
val resolvedEmbedMode = resolveGameEmbedModeIfNeeded(gameEmbedMode)
|
||||
if (resolvedEmbedMode == GameEmbedMode.DISABLED) {
|
||||
RUN_GAME_INFO
|
||||
} else {
|
||||
EMBEDDED_RUN_GAME_INFO
|
||||
}
|
||||
XR_RUN_GAME_INFO
|
||||
}
|
||||
}
|
||||
|
||||
// Native XR devices don't support embed mode yet.
|
||||
return RUN_GAME_INFO
|
||||
}
|
||||
|
||||
// Project manager doesn't support embed mode.
|
||||
if (godot?.isProjectManagerHint() == true) {
|
||||
return RUN_GAME_INFO
|
||||
}
|
||||
|
||||
// Check for embed mode launch.
|
||||
val resolvedEmbedMode = resolveGameEmbedModeIfNeeded(gameEmbedMode)
|
||||
return if (resolvedEmbedMode == GameEmbedMode.DISABLED) {
|
||||
RUN_GAME_INFO
|
||||
} else {
|
||||
EMBEDDED_RUN_GAME_INFO
|
||||
}
|
||||
}
|
||||
|
||||
@@ -626,6 +692,7 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
||||
return verifyApk(godot.fileAccessHandler, apkPath)
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun supportsFeature(featureTag: String): Boolean {
|
||||
if (featureTag == "xr_editor") {
|
||||
return isNativeXRDevice(applicationContext)
|
||||
@@ -639,11 +706,12 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
||||
return BuildConfig.FLAVOR == "picoos"
|
||||
}
|
||||
|
||||
return false
|
||||
return super.supportsFeature(featureTag)
|
||||
}
|
||||
|
||||
internal fun onEditorConnected(connectedEditorId: Int) {
|
||||
when (connectedEditorId) {
|
||||
internal fun onEditorConnected(editorId: Int) {
|
||||
Log.d(TAG, "Editor $editorId connected!")
|
||||
when (editorId) {
|
||||
EMBEDDED_RUN_GAME_INFO.windowId, RUN_GAME_INFO.windowId -> {
|
||||
runOnUiThread {
|
||||
embeddedGameViewContainerWindow?.isVisible = false
|
||||
@@ -652,12 +720,16 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
||||
|
||||
XR_RUN_GAME_INFO.windowId -> {
|
||||
runOnUiThread {
|
||||
updateEmbeddedGameView(true, false)
|
||||
updateEmbeddedGameView(gameRunning = true, gameEmbedded = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun onEditorDisconnected(editorId: Int) {
|
||||
Log.d(TAG, "Editor $editorId disconnected!")
|
||||
}
|
||||
|
||||
private fun updateEmbeddedGameView(gameRunning: Boolean, gameEmbedded: Boolean) {
|
||||
if (gameRunning) {
|
||||
embeddedGameStateLabel?.apply {
|
||||
|
||||
@@ -35,9 +35,11 @@ import android.util.Log
|
||||
import androidx.annotation.CallSuper
|
||||
import org.godotengine.godot.Godot
|
||||
import org.godotengine.godot.GodotLib
|
||||
import org.godotengine.godot.utils.GameMenuUtils
|
||||
import org.godotengine.godot.editor.utils.GameMenuUtils
|
||||
import org.godotengine.godot.utils.PermissionsUtil
|
||||
import org.godotengine.godot.utils.ProcessPhoenix
|
||||
import org.godotengine.godot.xr.HYBRID_APP_FEATURE
|
||||
import org.godotengine.godot.xr.isHybridAppEnabled
|
||||
|
||||
/**
|
||||
* Base class for the Godot play windows.
|
||||
@@ -101,4 +103,14 @@ abstract class BaseGodotGame: GodotEditor() {
|
||||
}
|
||||
|
||||
protected open fun getEditorGameEmbedMode() = GameMenuUtils.GameEmbedMode.AUTO
|
||||
|
||||
@CallSuper
|
||||
override fun supportsFeature(featureTag: String): Boolean {
|
||||
if (HYBRID_APP_FEATURE == featureTag) {
|
||||
// Check if hybrid is enabled
|
||||
return isHybridAppEnabled()
|
||||
}
|
||||
|
||||
return super.supportsFeature(featureTag)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,9 @@ internal class EditorMessageDispatcher(private val editor: BaseGodotEditor) {
|
||||
MSG_REGISTER_MESSENGER -> {
|
||||
val editorId = msg.arg1
|
||||
val messenger = msg.replyTo
|
||||
registerMessenger(editorId, messenger)
|
||||
registerMessenger(editorId, messenger) {
|
||||
editor.onEditorDisconnected(editorId)
|
||||
}
|
||||
}
|
||||
|
||||
MSG_DISPATCH_GAME_MENU_ACTION -> {
|
||||
@@ -211,8 +213,8 @@ internal class EditorMessageDispatcher(private val editor: BaseGodotEditor) {
|
||||
} else if (messenger.binder.isBinderAlive) {
|
||||
messenger.binder.linkToDeath({
|
||||
Log.v(TAG, "Removing messenger for $editorId")
|
||||
cleanEditorConnection(editorId)
|
||||
messengerDeathCallback?.run()
|
||||
cleanEditorConnection(editorId)
|
||||
}, 0)
|
||||
editorConnectionsInfos[editorId] = EditorConnectionInfo(messenger)
|
||||
editor.onEditorConnected(editorId)
|
||||
@@ -234,7 +236,8 @@ internal class EditorMessageDispatcher(private val editor: BaseGodotEditor) {
|
||||
/**
|
||||
* Utility method to register a [Messenger] attached to this handler with a host.
|
||||
*
|
||||
* This is done so that the host can send request to the editor instance attached to this handle.
|
||||
* This is done so that the host can send request (e.g: force-quit when the host exits) to the editor instance
|
||||
* attached to this handle.
|
||||
*
|
||||
* Note that this is only done when the editor instance is internal (not exported) to prevent
|
||||
* arbitrary apps from having the ability to send requests.
|
||||
|
||||
@@ -37,12 +37,17 @@ import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.core.view.isVisible
|
||||
import org.godotengine.editor.embed.GameMenuFragment
|
||||
import org.godotengine.godot.utils.GameMenuUtils
|
||||
import org.godotengine.godot.GodotLib
|
||||
import org.godotengine.godot.editor.utils.GameMenuUtils
|
||||
import org.godotengine.godot.utils.ProcessPhoenix
|
||||
import org.godotengine.godot.utils.isHorizonOSDevice
|
||||
import org.godotengine.godot.utils.isNativeXRDevice
|
||||
import org.godotengine.godot.xr.HYBRID_APP_PANEL_FEATURE
|
||||
import org.godotengine.godot.xr.XRMode
|
||||
import org.godotengine.godot.xr.isHybridAppEnabled
|
||||
|
||||
/**
|
||||
* Drives the 'run project' window of the Godot Editor.
|
||||
@@ -82,6 +87,18 @@ open class GodotGame : BaseGodotGame() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCommandLine(): MutableList<String> {
|
||||
val updatedArgs = super.getCommandLine()
|
||||
if (!updatedArgs.contains(XRMode.REGULAR.cmdLineArg)) {
|
||||
updatedArgs.add(XRMode.REGULAR.cmdLineArg)
|
||||
}
|
||||
if (!updatedArgs.contains(XR_MODE_ARG)) {
|
||||
updatedArgs.add(XR_MODE_ARG)
|
||||
updatedArgs.add("off")
|
||||
}
|
||||
return updatedArgs
|
||||
}
|
||||
|
||||
override fun enterPiPMode() {
|
||||
if (hasPiPSystemFeature()) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
@@ -245,4 +262,19 @@ open class GodotGame : BaseGodotGame() {
|
||||
expandGameMenuButton?.isVisible = shouldShowGameMenuBar() && isMenuBarCollapsable() && collapsed
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun supportsFeature(featureTag: String): Boolean {
|
||||
if (HYBRID_APP_PANEL_FEATURE == featureTag) {
|
||||
// Check if openxr is enabled
|
||||
if (!GodotLib.getGlobal("xr/openxr/enabled").toBoolean()) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if hybrid is enabled
|
||||
return isHybridAppEnabled()
|
||||
}
|
||||
|
||||
return super.supportsFeature(featureTag)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
|
||||
import android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
|
||||
import org.godotengine.editor.GodotGame
|
||||
import org.godotengine.editor.R
|
||||
import org.godotengine.godot.utils.GameMenuUtils
|
||||
import org.godotengine.godot.editor.utils.GameMenuUtils
|
||||
|
||||
/**
|
||||
* Host the Godot game from the editor when the embedded mode is enabled.
|
||||
|
||||
@@ -105,7 +105,7 @@ android {
|
||||
}
|
||||
|
||||
boolean devBuild = buildType == "dev"
|
||||
boolean debugSymbols = devBuild
|
||||
boolean debugSymbols = devBuild || (buildType == "debug" && isAndroidStudio())
|
||||
boolean runTests = devBuild
|
||||
boolean storeRelease = buildType == "release"
|
||||
boolean productionBuild = storeRelease
|
||||
|
||||
@@ -1026,7 +1026,7 @@ class Godot private constructor(val context: Context) {
|
||||
*/
|
||||
@Keep
|
||||
private fun hasFeature(feature: String): Boolean {
|
||||
if (primaryHost?.supportsFeature(feature) ?: false) {
|
||||
if (primaryHost?.supportsFeature(feature) == true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ abstract class GodotActivity : FragmentActivity(), GodotHost {
|
||||
private val TAG = GodotActivity::class.java.simpleName
|
||||
|
||||
@JvmStatic
|
||||
protected val EXTRA_COMMAND_LINE_PARAMS = "command_line_params"
|
||||
val EXTRA_COMMAND_LINE_PARAMS = "command_line_params"
|
||||
|
||||
@JvmStatic
|
||||
protected val EXTRA_NEW_LAUNCH = "new_launch_requested"
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/**************************************************************************/
|
||||
/* EditorUtils.kt */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
package org.godotengine.godot.editor.utils
|
||||
|
||||
/**
|
||||
* Utility class for accessing and using editor specific capabilities.
|
||||
*
|
||||
* This class is only functional on editor builds.
|
||||
*/
|
||||
object EditorUtils {
|
||||
@JvmStatic
|
||||
external fun runScene(scene: String, sceneArgs: Array<String>)
|
||||
}
|
||||
@@ -28,13 +28,15 @@
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
package org.godotengine.godot.utils
|
||||
package org.godotengine.godot.editor.utils
|
||||
|
||||
import android.util.Log
|
||||
import org.godotengine.godot.GodotLib
|
||||
|
||||
/**
|
||||
* Utility class for accessing and using game menu APIs.
|
||||
*
|
||||
* This class is only functional on editor builds.
|
||||
*/
|
||||
object GameMenuUtils {
|
||||
private val TAG = GameMenuUtils::class.java.simpleName
|
||||
@@ -0,0 +1,79 @@
|
||||
/**************************************************************************/
|
||||
/* HybridAppUtils.kt */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
/**
|
||||
* Contains utility methods and constants for hybrid apps.
|
||||
*/
|
||||
@file:JvmName("HybridAppUtils")
|
||||
|
||||
package org.godotengine.godot.xr
|
||||
|
||||
import android.util.Log
|
||||
import org.godotengine.godot.GodotLib
|
||||
|
||||
private const val TAG = "HybridAppUtils"
|
||||
|
||||
enum class HybridMode(private val nativeValue: Int) {
|
||||
NONE( -1),
|
||||
IMMERSIVE(0),
|
||||
PANEL(1);
|
||||
|
||||
companion object {
|
||||
fun fromNative(nativeValue: Int): HybridMode {
|
||||
for (mode in HybridMode.entries) {
|
||||
if (mode.nativeValue == nativeValue) {
|
||||
return mode
|
||||
}
|
||||
}
|
||||
return NONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const val HYBRID_APP_FEATURE = "godot_openxr_hybrid_app"
|
||||
const val HYBRID_APP_PANEL_FEATURE = "godot_openxr_panel_app"
|
||||
const val HYBRID_APP_PANEL_CATEGORY = "org.godotengine.xr.hybrid.PANEL"
|
||||
const val HYBRID_APP_IMMERSIVE_CATEGORY = "org.godotengine.xr.hybrid.IMMERSIVE"
|
||||
|
||||
fun isHybridAppEnabled() = GodotLib.getGlobal("xr/hybrid_app/enabled").toBoolean()
|
||||
|
||||
fun getHybridAppLaunchMode(): HybridMode {
|
||||
if (!isHybridAppEnabled()) {
|
||||
return HybridMode.NONE
|
||||
}
|
||||
|
||||
try {
|
||||
val launchModeValue = GodotLib.getGlobal("xr/hybrid_app/launch_mode").toInt()
|
||||
return HybridMode.fromNative(launchModeValue)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Unable to retrieve 'xr/hybrid_app/launch_mode' project setting", e)
|
||||
return HybridMode.NONE
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user