feat: updated engine
This commit is contained in:
parent
cbe99774ff
commit
f4cf6b3999
6607 changed files with 910135 additions and 430025 deletions
|
|
@ -131,14 +131,7 @@ android {
|
|||
}
|
||||
|
||||
buildTypes {
|
||||
dev {
|
||||
initWith debug
|
||||
applicationIdSuffix ".dev"
|
||||
manifestPlaceholders += [editorBuildSuffix: " (dev)"]
|
||||
}
|
||||
|
||||
debug {
|
||||
initWith release
|
||||
applicationIdSuffix ".debug"
|
||||
manifestPlaceholders += [editorBuildSuffix: " (debug)"]
|
||||
signingConfig signingConfigs.debug
|
||||
|
|
@ -147,6 +140,14 @@ android {
|
|||
release {
|
||||
if (hasReleaseSigningConfigs()) {
|
||||
signingConfig signingConfigs.release
|
||||
} else {
|
||||
// We default to the debug signingConfigs when the release signing configs are not
|
||||
// available (e.g: development in Android Studio).
|
||||
signingConfig signingConfigs.debug
|
||||
// In addition, we update the application ID to allow installing an Android studio release build
|
||||
// side by side with a production build from the store.
|
||||
applicationIdSuffix ".release"
|
||||
manifestPlaceholders += [editorBuildSuffix: " (release)"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,14 @@
|
|||
<uses-feature
|
||||
android:glEsVersion="0x00030000"
|
||||
android:required="true" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.vulkan.version"
|
||||
android:required="false"
|
||||
android:version="0x401000" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.vulkan.level"
|
||||
android:required="false"
|
||||
android:version="1" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
|
|
@ -52,7 +60,7 @@
|
|||
android:configChanges="layoutDirection|locale|orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"
|
||||
android:exported="false"
|
||||
android:icon="@mipmap/themed_icon"
|
||||
android:launchMode="singleTask"
|
||||
android:launchMode="singleInstancePerTask"
|
||||
android:screenOrientation="userLandscape">
|
||||
<layout
|
||||
android:defaultWidth="@dimen/editor_default_window_width"
|
||||
|
|
@ -85,6 +93,15 @@
|
|||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:scheme="file" />
|
||||
<data android:scheme="content" />
|
||||
<data android:mimeType="*/*" />
|
||||
<data android:host="*" />
|
||||
<data android:pathPattern=".*\\.godot" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
<activity
|
||||
android:name=".GodotGame"
|
||||
|
|
@ -94,6 +111,7 @@
|
|||
android:label="@string/godot_game_activity_name"
|
||||
android:launchMode="singleTask"
|
||||
android:process=":GodotGame"
|
||||
android:taskAffinity=":game"
|
||||
android:autoRemoveFromRecents="true"
|
||||
android:theme="@style/GodotGameTheme"
|
||||
android:supportsPictureInPicture="true"
|
||||
|
|
|
|||
|
|
@ -34,9 +34,12 @@ import android.Manifest
|
|||
import android.app.ActivityManager
|
||||
import android.app.ActivityOptions
|
||||
import android.content.ComponentName
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Debug
|
||||
|
|
@ -71,7 +74,9 @@ import org.godotengine.godot.utils.DialogUtils
|
|||
import org.godotengine.godot.utils.PermissionsUtil
|
||||
import org.godotengine.godot.utils.ProcessPhoenix
|
||||
import org.godotengine.openxr.vendors.utils.*
|
||||
import java.io.File
|
||||
import kotlin.math.min
|
||||
import kotlin.text.indexOf
|
||||
|
||||
/**
|
||||
* Base class for the Godot Android Editor activities.
|
||||
|
|
@ -142,6 +147,8 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
|||
internal const val GAME_MENU_ACTION_SET_NODE_TYPE = "setNodeType"
|
||||
internal const val GAME_MENU_ACTION_SET_SELECT_MODE = "setSelectMode"
|
||||
internal const val GAME_MENU_ACTION_SET_SELECTION_VISIBLE = "setSelectionVisible"
|
||||
internal const val GAME_MENU_ACTION_SET_SELECTION_AVOID_LOCKED = "setSelectionAvoidLocked"
|
||||
internal const val GAME_MENU_ACTION_SET_SELECTION_PREFER_GROUP = "setSelectionPreferGroup"
|
||||
internal const val GAME_MENU_ACTION_SET_CAMERA_OVERRIDE = "setCameraOverride"
|
||||
internal const val GAME_MENU_ACTION_SET_CAMERA_MANIPULATE_MODE = "setCameraManipulateMode"
|
||||
internal const val GAME_MENU_ACTION_RESET_CAMERA_2D_POSITION = "resetCamera2DPosition"
|
||||
|
|
@ -152,6 +159,7 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
|||
internal const val GAME_MENU_ACTION_SET_TIME_SCALE = "setTimeScale"
|
||||
|
||||
private const val GAME_WORKSPACE = "Game"
|
||||
private const val SCRIPT_WORKSPACE = "Script"
|
||||
|
||||
internal const val SNACKBAR_SHOW_DURATION_MS = 5000L
|
||||
|
||||
|
|
@ -198,6 +206,12 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
|||
protected var gameMenuFragment: GameMenuFragment? = null
|
||||
protected val gameMenuState = Bundle()
|
||||
|
||||
private val updatedCommandLineParams = ArrayList<String>()
|
||||
|
||||
private var changingOrientationAllowed = false
|
||||
private var distractionFreeModeEnabled = false
|
||||
private var activeWorkspace: String? = null
|
||||
|
||||
override fun getGodotAppLayout() = R.layout.godot_editor_layout
|
||||
|
||||
internal open fun getEditorWindowInfo() = EDITOR_MAIN_INFO
|
||||
|
|
@ -254,7 +268,7 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
|||
|
||||
editorMessageDispatcher.parseStartIntent(packageManager, intent)
|
||||
|
||||
if (BuildConfig.BUILD_TYPE == "dev" && WAIT_FOR_DEBUGGER) {
|
||||
if (BuildConfig.BUILD_TYPE == "debug" && WAIT_FOR_DEBUGGER) {
|
||||
Debug.waitForDebugger()
|
||||
}
|
||||
|
||||
|
|
@ -264,6 +278,14 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
|||
setupGameMenuBar()
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
|
||||
// Show EditorTitleBar on small screens only in landscape due to width limitations in portrait.
|
||||
// TODO: Enable for portrait once the title bar width is optimized.
|
||||
EditorUtils.toggleTitleBar(isLargeScreen || newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
gradleBuildProvider.buildEnvDisconnect()
|
||||
super.onDestroy()
|
||||
|
|
@ -319,6 +341,91 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
|||
super.onNewIntent(newIntent)
|
||||
}
|
||||
|
||||
override fun handleStartIntent(intent: Intent, newLaunch: Boolean) {
|
||||
when (intent.action) {
|
||||
Intent.ACTION_VIEW -> {
|
||||
val rootDir = Environment.getExternalStorageDirectory().canonicalPath
|
||||
|
||||
val dataPath = when (intent.scheme) {
|
||||
ContentResolver.SCHEME_FILE -> {
|
||||
intent.data?.path
|
||||
}
|
||||
|
||||
ContentResolver.SCHEME_CONTENT -> {
|
||||
// This approach is not recommend with 'content' scheme, but we require the filesystem path in
|
||||
// order to open its parent directory and load the project.
|
||||
val uriPath = intent.data?.path
|
||||
if (uriPath != null) {
|
||||
// Try and see if the external storage directory is part of the uri path.
|
||||
val rootDirIndex = uriPath.indexOf(rootDir)
|
||||
if (rootDirIndex != -1) {
|
||||
uriPath.substring(rootDirIndex)
|
||||
} else {
|
||||
// Try and see if we can retrieve an existing relative path.
|
||||
val pathParts = uriPath.split(':', '/')
|
||||
var currentPath = ""
|
||||
for (index in pathParts.size -1 downTo 0) {
|
||||
currentPath = if (currentPath == "") {
|
||||
pathParts[index]
|
||||
} else {
|
||||
"${pathParts[index]}/$currentPath"
|
||||
}
|
||||
val currentFile = File(rootDir, currentPath)
|
||||
if (currentFile.exists()) {
|
||||
break
|
||||
}
|
||||
}
|
||||
currentPath
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (!dataPath.isNullOrBlank()) {
|
||||
var dataFile = File(dataPath)
|
||||
if (!dataFile.isAbsolute) {
|
||||
dataFile = File(rootDir, dataPath)
|
||||
}
|
||||
|
||||
val dataDir = dataFile.parentFile
|
||||
if (dataDir?.isDirectory == true) {
|
||||
val loadProjectArgs = arrayOf(EDITOR_ARG, PATH_ARG, dataDir.absolutePath)
|
||||
if (newLaunch) {
|
||||
// Update the command line parameters to load the specified project.
|
||||
updatedCommandLineParams.addAll(loadProjectArgs)
|
||||
} else {
|
||||
// Check if we are already editing the specified directory.
|
||||
var isEditor = false
|
||||
var nextIsPath = false
|
||||
var currentPath = ""
|
||||
for (arg in commandLine) {
|
||||
if (nextIsPath) {
|
||||
currentPath = arg
|
||||
nextIsPath = false
|
||||
}
|
||||
|
||||
if (arg == EDITOR_ARG || arg == EDITOR_ARG_SHORT) {
|
||||
isEditor = true
|
||||
} else if (arg == PATH_ARG) {
|
||||
nextIsPath = true
|
||||
}
|
||||
}
|
||||
if (!isEditor || currentPath != dataDir.absolutePath) {
|
||||
onNewGodotInstanceRequested(loadProjectArgs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.handleStartIntent(intent, newLaunch)
|
||||
}
|
||||
|
||||
protected open fun shouldShowGameMenuBar() = gameMenuContainer != null
|
||||
|
||||
private fun setupGameMenuBar() {
|
||||
|
|
@ -345,6 +452,7 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
|||
val longPressEnabled = enableLongPressGestures()
|
||||
val panScaleEnabled = enablePanAndScaleGestures()
|
||||
val overrideVolumeButtonsEnabled = overrideVolumeButtons()
|
||||
val hapticEnabled = enableHapticOnLongPress()
|
||||
|
||||
runOnUiThread {
|
||||
// Enable long press, panning and scaling gestures
|
||||
|
|
@ -352,6 +460,7 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
|||
enableLongPress(longPressEnabled)
|
||||
enablePanningAndScalingGestures(panScaleEnabled)
|
||||
setOverrideVolumeButtons(overrideVolumeButtonsEnabled)
|
||||
enableHapticFeedback(hapticEnabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -404,7 +513,10 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
|||
|
||||
override fun getCommandLine(): MutableList<String> {
|
||||
val params = super.getCommandLine()
|
||||
if (BuildConfig.BUILD_TYPE == "dev" && !params.contains("--benchmark")) {
|
||||
if (updatedCommandLineParams.isNotEmpty()) {
|
||||
params.addAll(updatedCommandLineParams)
|
||||
}
|
||||
if (BuildConfig.BUILD_TYPE == "debug" && !params.contains("--benchmark")) {
|
||||
params.add("--benchmark")
|
||||
}
|
||||
return params
|
||||
|
|
@ -603,7 +715,7 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
|||
/**
|
||||
* The Godot Android Editor sets its own orientation via its AndroidManifest
|
||||
*/
|
||||
protected open fun overrideOrientationRequest() = true
|
||||
protected open fun overrideOrientationRequest() = !changingOrientationAllowed
|
||||
|
||||
protected open fun overrideVolumeButtons() = false
|
||||
|
||||
|
|
@ -613,6 +725,12 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
|||
protected open fun enableLongPressGestures() =
|
||||
java.lang.Boolean.parseBoolean(GodotLib.getEditorSetting("interface/touchscreen/enable_long_press_as_right_click"))
|
||||
|
||||
/**
|
||||
* Enable haptic feedback on long-press right-click for the Godot Android editor.
|
||||
*/
|
||||
protected open fun enableHapticOnLongPress() =
|
||||
java.lang.Boolean.parseBoolean(GodotLib.getEditorSetting("interface/touchscreen/haptic_on_long_press"))
|
||||
|
||||
/**
|
||||
* Disable scroll deadzone for the Godot Android editor.
|
||||
*/
|
||||
|
|
@ -801,6 +919,8 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
|||
}
|
||||
|
||||
override fun onEditorWorkspaceSelected(workspace: String) {
|
||||
activeWorkspace = workspace
|
||||
|
||||
if (workspace == GAME_WORKSPACE && shouldShowGameMenuBar()) {
|
||||
if (editorMessageDispatcher.bringEditorWindowToFront(EMBEDDED_RUN_GAME_INFO) || editorMessageDispatcher.bringEditorWindowToFront(RUN_GAME_INFO)) {
|
||||
return
|
||||
|
|
@ -813,6 +933,23 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
|||
embeddedGameViewContainerWindow?.isVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
toggleScriptEditorOrientation()
|
||||
}
|
||||
|
||||
override fun onDistractionFreeModeChanged(enabled: Boolean) {
|
||||
distractionFreeModeEnabled = enabled
|
||||
toggleScriptEditorOrientation()
|
||||
}
|
||||
|
||||
private fun toggleScriptEditorOrientation() {
|
||||
if (activeWorkspace == SCRIPT_WORKSPACE && distractionFreeModeEnabled) {
|
||||
changingOrientationAllowed = true
|
||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER
|
||||
} else if (changingOrientationAllowed) {
|
||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
|
||||
changingOrientationAllowed = false
|
||||
}
|
||||
}
|
||||
|
||||
internal open fun bringSelfToFront() {
|
||||
|
|
@ -845,6 +982,14 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
|||
val enabled = actionData.getBoolean(KEY_GAME_MENU_ACTION_PARAM1)
|
||||
toggleSelectionVisibility(enabled)
|
||||
}
|
||||
GAME_MENU_ACTION_SET_SELECTION_AVOID_LOCKED -> {
|
||||
val enabled = actionData.getBoolean(KEY_GAME_MENU_ACTION_PARAM1)
|
||||
toggleSelectionAvoidLocked(enabled)
|
||||
}
|
||||
GAME_MENU_ACTION_SET_SELECTION_PREFER_GROUP -> {
|
||||
val enabled = actionData.getBoolean(KEY_GAME_MENU_ACTION_PARAM1)
|
||||
toggleSelectionPreferGroup(enabled)
|
||||
}
|
||||
GAME_MENU_ACTION_SET_CAMERA_OVERRIDE -> {
|
||||
val enabled = actionData.getBoolean(KEY_GAME_MENU_ACTION_PARAM1)
|
||||
overrideCamera(enabled)
|
||||
|
|
@ -905,6 +1050,20 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
|||
}
|
||||
}
|
||||
|
||||
override fun toggleSelectionAvoidLocked(enabled: Boolean) {
|
||||
gameMenuState.putBoolean(GAME_MENU_ACTION_SET_SELECTION_AVOID_LOCKED, enabled)
|
||||
godot?.runOnRenderThread {
|
||||
GameMenuUtils.setSelectionAvoidLocked(enabled)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toggleSelectionPreferGroup(enabled: Boolean) {
|
||||
gameMenuState.putBoolean(GAME_MENU_ACTION_SET_SELECTION_PREFER_GROUP, enabled)
|
||||
godot?.runOnRenderThread {
|
||||
GameMenuUtils.setSelectionPreferGroup(enabled)
|
||||
}
|
||||
}
|
||||
|
||||
override fun overrideCamera(enabled: Boolean) {
|
||||
gameMenuState.putBoolean(GAME_MENU_ACTION_SET_CAMERA_OVERRIDE, enabled)
|
||||
godot?.runOnRenderThread {
|
||||
|
|
|
|||
|
|
@ -30,12 +30,7 @@
|
|||
|
||||
package org.godotengine.editor
|
||||
|
||||
import android.app.PictureInPictureParams
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Rect
|
||||
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
|
||||
|
|
@ -55,7 +50,6 @@ open class GodotGame : BaseGodotGame() {
|
|||
private val TAG = GodotGame::class.java.simpleName
|
||||
}
|
||||
|
||||
private val gameViewSourceRectHint = Rect()
|
||||
private val expandGameMenuButton: View? by lazy { findViewById(R.id.game_menu_expand_button) }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
|
@ -75,13 +69,6 @@ open class GodotGame : BaseGodotGame() {
|
|||
gameMenuFragment?.expandGameMenu()
|
||||
}
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val gameView = findViewById<View>(R.id.godot_fragment_container)
|
||||
gameView?.addOnLayoutChangeListener { v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
|
||||
gameView.getGlobalVisibleRect(gameViewSourceRectHint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCommandLine(): MutableList<String> {
|
||||
|
|
@ -96,27 +83,7 @@ open class GodotGame : BaseGodotGame() {
|
|||
return updatedArgs
|
||||
}
|
||||
|
||||
override fun enterPiPMode() {
|
||||
if (hasPiPSystemFeature()) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val builder = PictureInPictureParams.Builder().setSourceRectHint(gameViewSourceRectHint)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
builder.setSeamlessResizeEnabled(false)
|
||||
}
|
||||
setPictureInPictureParams(builder.build())
|
||||
}
|
||||
|
||||
Log.v(TAG, "Entering PiP mode")
|
||||
enterPictureInPictureMode()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true the if the device supports picture-in-picture (PiP).
|
||||
*/
|
||||
protected fun hasPiPSystemFeature(): Boolean {
|
||||
return packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
|
||||
}
|
||||
override fun isPiPEnabled() = true
|
||||
|
||||
override fun shouldShowGameMenuBar(): Boolean {
|
||||
return intent.getBooleanExtra(
|
||||
|
|
@ -127,21 +94,11 @@ open class GodotGame : BaseGodotGame() {
|
|||
|
||||
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {
|
||||
super.onPictureInPictureModeChanged(isInPictureInPictureMode)
|
||||
Log.v(TAG, "onPictureInPictureModeChanged: $isInPictureInPictureMode")
|
||||
|
||||
// Hide the game menu fragment when in PiP.
|
||||
gameMenuContainer?.isVisible = !isInPictureInPictureMode
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
|
||||
if (isInPictureInPictureMode && !isFinishing) {
|
||||
// We get in this state when PiP is closed, so we terminate the activity.
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getGodotAppLayout() = R.layout.godot_game_layout
|
||||
|
||||
override fun getEditorWindowInfo() = RUN_GAME_INFO
|
||||
|
|
@ -173,6 +130,22 @@ open class GodotGame : BaseGodotGame() {
|
|||
editorMessageDispatcher.dispatchGameMenuAction(EDITOR_MAIN_INFO, actionBundle)
|
||||
}
|
||||
|
||||
override fun toggleSelectionAvoidLocked(enabled: Boolean) {
|
||||
val actionBundle = Bundle().apply {
|
||||
putString(KEY_GAME_MENU_ACTION, GAME_MENU_ACTION_SET_SELECTION_AVOID_LOCKED)
|
||||
putBoolean(KEY_GAME_MENU_ACTION_PARAM1, enabled)
|
||||
}
|
||||
editorMessageDispatcher.dispatchGameMenuAction(EDITOR_MAIN_INFO, actionBundle)
|
||||
}
|
||||
|
||||
override fun toggleSelectionPreferGroup(enabled: Boolean) {
|
||||
val actionBundle = Bundle().apply {
|
||||
putString(KEY_GAME_MENU_ACTION, GAME_MENU_ACTION_SET_SELECTION_PREFER_GROUP)
|
||||
putBoolean(KEY_GAME_MENU_ACTION_PARAM1, enabled)
|
||||
}
|
||||
editorMessageDispatcher.dispatchGameMenuAction(EDITOR_MAIN_INFO, actionBundle)
|
||||
}
|
||||
|
||||
override fun overrideCamera(enabled: Boolean) {
|
||||
val actionBundle = Bundle().apply {
|
||||
putString(KEY_GAME_MENU_ACTION, GAME_MENU_ACTION_SET_CAMERA_OVERRIDE)
|
||||
|
|
@ -258,7 +231,7 @@ open class GodotGame : BaseGodotGame() {
|
|||
|
||||
override fun isCloseButtonEnabled() = !isHorizonOSDevice(applicationContext)
|
||||
|
||||
override fun isPiPButtonEnabled() = hasPiPSystemFeature()
|
||||
override fun isPiPButtonEnabled() = isPiPModeSupported()
|
||||
|
||||
override fun isMenuBarCollapsable() = true
|
||||
|
||||
|
|
|
|||
|
|
@ -31,13 +31,18 @@
|
|||
package org.godotengine.editor.embed
|
||||
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.graphics.Color
|
||||
import android.graphics.Point
|
||||
import android.os.Bundle
|
||||
import android.util.Rational
|
||||
import android.view.Gravity
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND
|
||||
import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
|
||||
import android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
|
||||
import android.widget.CheckBox
|
||||
import org.godotengine.editor.GodotGame
|
||||
import org.godotengine.editor.R
|
||||
import org.godotengine.godot.editor.utils.GameMenuUtils
|
||||
|
|
@ -50,22 +55,67 @@ class EmbeddedGodotGame : GodotGame() {
|
|||
companion object {
|
||||
private val TAG = EmbeddedGodotGame::class.java.simpleName
|
||||
|
||||
private const val FULL_SCREEN_WIDTH = WindowManager.LayoutParams.MATCH_PARENT
|
||||
private const val FULL_SCREEN_HEIGHT = WindowManager.LayoutParams.MATCH_PARENT
|
||||
private const val PREFS_NAME = "embedded_game_window_prefs"
|
||||
private const val KEY_X = "embedded_window_x"
|
||||
private const val KEY_Y = "embedded_window_y"
|
||||
private const val KEY_WIDTH = "embedded_window_width"
|
||||
private const val KEY_HEIGHT = "embedded_window_height"
|
||||
private const val KEY_FREE_RESIZE = "is_free_resize"
|
||||
|
||||
private const val RESIZE_THRESHOLD = 80f
|
||||
private const val MIN_WINDOW_SIZE = 400
|
||||
private const val MAX_SCREEN_PERCENT = 0.9f
|
||||
|
||||
private const val RESIZE_UI_HIDE_DELAY_MS = 2000L
|
||||
}
|
||||
|
||||
private val defaultWidthInPx : Int by lazy {
|
||||
resources.getDimensionPixelSize(R.dimen.embed_game_window_default_width)
|
||||
}
|
||||
private val defaultHeightInPx : Int by lazy {
|
||||
resources.getDimensionPixelSize(R.dimen.embed_game_window_default_height)
|
||||
}
|
||||
private val defaultWidthInPx: Int by lazy { resources.getDimensionPixelSize(R.dimen.embed_game_window_default_width) }
|
||||
private val defaultHeightInPx: Int by lazy { resources.getDimensionPixelSize(R.dimen.embed_game_window_default_height) }
|
||||
|
||||
private var layoutWidthInPx = 0
|
||||
private var layoutHeightInPx = 0
|
||||
|
||||
private var gameRequestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
private var isFullscreen = false
|
||||
private var gameRequestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
|
||||
private var resizingEnabled = false
|
||||
private var isResizing = false
|
||||
private var activeCorner = 0
|
||||
private var isFreeResize = false
|
||||
|
||||
private var initialWinX = 0
|
||||
private var initialWinY = 0
|
||||
private var initialWidth = 0
|
||||
private var initialHeight = 0
|
||||
private var initialTouchX = 0f
|
||||
private var initialTouchY = 0f
|
||||
|
||||
private val lockAspectRatioCheckBox: CheckBox by lazy { findViewById(R.id.lockAspectRatioCheckBox) }
|
||||
private val cornerHandles = mutableListOf<View>()
|
||||
|
||||
private val screenBounds: android.graphics.Rect by lazy {
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
|
||||
windowManager.currentWindowMetrics.bounds
|
||||
} else {
|
||||
val size = Point()
|
||||
windowManager.defaultDisplay.getRealSize(size)
|
||||
android.graphics.Rect(0, 0, size.x, size.y)
|
||||
}
|
||||
}
|
||||
|
||||
private val maxAllowedWidth: Int get() = (screenBounds.width() * MAX_SCREEN_PERCENT).toInt()
|
||||
private val maxAllowedHeight: Int get() = (screenBounds.height() * MAX_SCREEN_PERCENT).toInt()
|
||||
|
||||
private var lockedAspectRatio: Float = 1.6f
|
||||
|
||||
private val disableResizeHandler = android.os.Handler(android.os.Looper.getMainLooper())
|
||||
|
||||
private val disableResizeRunnable = Runnable {
|
||||
if (isResizing) return@Runnable // Keep it active user is resizing again
|
||||
resizingEnabled = false
|
||||
gameMenuFragment?.toggleDragButton(false)
|
||||
lockAspectRatioCheckBox.visibility = View.GONE
|
||||
cornerHandles.forEach { it.visibility = View.GONE }
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
@ -75,14 +125,54 @@ class EmbeddedGodotGame : GodotGame() {
|
|||
val layoutParams = window.attributes
|
||||
layoutParams.flags = layoutParams.flags or FLAG_NOT_TOUCH_MODAL or FLAG_WATCH_OUTSIDE_TOUCH
|
||||
layoutParams.flags = layoutParams.flags and FLAG_DIM_BEHIND.inv()
|
||||
layoutParams.gravity = Gravity.END or Gravity.BOTTOM
|
||||
layoutParams.gravity = Gravity.TOP or Gravity.START
|
||||
|
||||
layoutWidthInPx = defaultWidthInPx
|
||||
layoutHeightInPx = defaultHeightInPx
|
||||
|
||||
layoutParams.width = layoutWidthInPx
|
||||
layoutParams.height = layoutHeightInPx
|
||||
loadWindowBounds(layoutParams)
|
||||
window.attributes = layoutParams
|
||||
|
||||
setupOverlayUI()
|
||||
}
|
||||
|
||||
override fun getGodotAppLayout() = R.layout.godot_embedded_game_layout
|
||||
|
||||
private fun setupOverlayUI() {
|
||||
lockAspectRatioCheckBox.isChecked = !isFreeResize
|
||||
|
||||
lockAspectRatioCheckBox.setOnCheckedChangeListener { _, isChecked ->
|
||||
isFreeResize = !isChecked
|
||||
hideResizeUI()
|
||||
if (isChecked) {
|
||||
val lp = window.attributes
|
||||
lockedAspectRatio = lp.width.toFloat() / lp.height.toFloat().coerceAtLeast(1f)
|
||||
}
|
||||
saveWindowBounds(updatePiPParams = false)
|
||||
}
|
||||
|
||||
cornerHandles.apply {
|
||||
clear()
|
||||
add(findViewById(R.id.handleTopLeft))
|
||||
add(findViewById(R.id.handleTopRight))
|
||||
add(findViewById(R.id.handleBottomLeft))
|
||||
add(findViewById(R.id.handleBottomRight))
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateLabelText(w: Int, h: Int) {
|
||||
lockAspectRatioCheckBox.text = getString(R.string.lock_aspect_ratio_btn_text, w, h)
|
||||
}
|
||||
|
||||
private fun showResizeUI() {
|
||||
disableResizeHandler.removeCallbacks(disableResizeRunnable)
|
||||
if (!resizingEnabled) {
|
||||
resizingEnabled = true
|
||||
lockAspectRatioCheckBox.visibility = View.VISIBLE
|
||||
cornerHandles.forEach { it.visibility = View.VISIBLE }
|
||||
}
|
||||
}
|
||||
|
||||
private fun hideResizeUI() {
|
||||
disableResizeHandler.removeCallbacks(disableResizeRunnable)
|
||||
disableResizeHandler.postDelayed(disableResizeRunnable, RESIZE_UI_HIDE_DELAY_MS)
|
||||
}
|
||||
|
||||
override fun setRequestedOrientation(requestedOrientation: Int) {
|
||||
|
|
@ -97,40 +187,222 @@ class EmbeddedGodotGame : GodotGame() {
|
|||
}
|
||||
|
||||
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
|
||||
if (isFullscreen) return super.dispatchTouchEvent(event)
|
||||
val layoutParams = window.attributes
|
||||
|
||||
when (event.actionMasked) {
|
||||
MotionEvent.ACTION_OUTSIDE -> {
|
||||
if (!isFullscreen) {
|
||||
if (gameMenuFragment?.isAlwaysOnTop() == true) {
|
||||
enterPiPMode()
|
||||
} else {
|
||||
minimizeGameWindow()
|
||||
if (gameMenuFragment?.isAlwaysOnTop() == true) {
|
||||
updatePiPParams(aspectRatio = Rational(layoutWidthInPx, layoutHeightInPx))
|
||||
enterPiPMode()
|
||||
} else {
|
||||
minimizeGameWindow()
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
if (resizingEnabled) {
|
||||
// Check if the click is inside the label's bounds
|
||||
val location = IntArray(2)
|
||||
lockAspectRatioCheckBox.getLocationOnScreen(location)
|
||||
val checkBoxRect = android.graphics.Rect(
|
||||
location[0], location[1],
|
||||
location[0] + lockAspectRatioCheckBox.width,
|
||||
location[1] + lockAspectRatioCheckBox.height
|
||||
)
|
||||
|
||||
if (checkBoxRect.contains(event.rawX.toInt(), event.rawY.toInt())) {
|
||||
// Let the CheckBox handle the click itself
|
||||
return super.dispatchTouchEvent(event)
|
||||
}
|
||||
activeCorner = getTouchedCorner(event.x, event.y, layoutParams.width, layoutParams.height)
|
||||
if (activeCorner != 0) {
|
||||
isResizing = true
|
||||
|
||||
initialTouchX = event.rawX
|
||||
initialTouchY = event.rawY
|
||||
initialWinX = layoutParams.x
|
||||
initialWinY = layoutParams.y
|
||||
initialWidth = layoutParams.width
|
||||
initialHeight = layoutParams.height
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
// val layoutParams = window.attributes
|
||||
// TODO: Add logic to move the embedded window.
|
||||
// window.attributes = layoutParams
|
||||
if (resizingEnabled && isResizing) {
|
||||
val dx = (event.rawX - initialTouchX).toInt()
|
||||
val dy = (event.rawY - initialTouchY).toInt()
|
||||
applyResizeLogic(layoutParams, dx, dy)
|
||||
updateLabelText(layoutParams.width, layoutParams.height)
|
||||
window.attributes = layoutParams
|
||||
return true
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_UP -> {
|
||||
if (isResizing) {
|
||||
isResizing = false
|
||||
saveWindowBounds()
|
||||
hideResizeUI()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.dispatchTouchEvent(event)
|
||||
}
|
||||
|
||||
private fun applyResizeLogic(layoutParams: WindowManager.LayoutParams, dx: Int, dy: Int) {
|
||||
var newW = initialWidth
|
||||
var newH = initialHeight
|
||||
|
||||
when (activeCorner) {
|
||||
Gravity.TOP or Gravity.START -> {
|
||||
newW = (initialWidth - dx).coerceIn(MIN_WINDOW_SIZE, maxAllowedWidth)
|
||||
newH = if (isFreeResize) {
|
||||
(initialHeight - dy).coerceIn(MIN_WINDOW_SIZE, maxAllowedHeight)
|
||||
} else {
|
||||
(newW / lockedAspectRatio).toInt()
|
||||
}
|
||||
layoutParams.x = initialWinX + (initialWidth - newW)
|
||||
layoutParams.y = initialWinY + (initialHeight - newH)
|
||||
}
|
||||
|
||||
Gravity.TOP or Gravity.END -> {
|
||||
newW = (initialWidth + dx).coerceIn(MIN_WINDOW_SIZE, maxAllowedWidth)
|
||||
newH = if (isFreeResize) {
|
||||
(initialHeight - dy).coerceIn(MIN_WINDOW_SIZE, maxAllowedHeight)
|
||||
} else {
|
||||
(newW / lockedAspectRatio).toInt()
|
||||
}
|
||||
layoutParams.y = initialWinY + (initialHeight - newH)
|
||||
}
|
||||
|
||||
Gravity.BOTTOM or Gravity.START -> {
|
||||
newW = (initialWidth - dx).coerceIn(MIN_WINDOW_SIZE, maxAllowedWidth)
|
||||
newH = if (isFreeResize) {
|
||||
(initialHeight + dy).coerceIn(MIN_WINDOW_SIZE, maxAllowedHeight)
|
||||
} else {
|
||||
(newW / lockedAspectRatio).toInt()
|
||||
}
|
||||
layoutParams.x = initialWinX + (initialWidth - newW)
|
||||
}
|
||||
|
||||
Gravity.BOTTOM or Gravity.END -> {
|
||||
newW = (initialWidth + dx).coerceIn(MIN_WINDOW_SIZE, maxAllowedWidth)
|
||||
newH = if (isFreeResize) {
|
||||
(initialHeight + dy).coerceIn(MIN_WINDOW_SIZE, maxAllowedHeight)
|
||||
} else {
|
||||
(newW / lockedAspectRatio).toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Final aspect-lock check
|
||||
if (!isFreeResize) {
|
||||
// Cap height based on both max height and width-constrained limit.
|
||||
// effectiveMaxHeight helps ensure that the width (newW) calculated below is guaranteed to be <= maxAllowedWidth.
|
||||
val maxHeightFromWidth = (maxAllowedWidth / lockedAspectRatio).toInt()
|
||||
val effectiveMaxHeight = minOf(maxAllowedHeight, maxHeightFromWidth)
|
||||
|
||||
val clampedH = newH.coerceIn(MIN_WINDOW_SIZE, effectiveMaxHeight)
|
||||
|
||||
if (clampedH != newH) {
|
||||
newH = clampedH
|
||||
newW = (newH * lockedAspectRatio).toInt()
|
||||
|
||||
// Re-adjust pivots for the new clamped dimensions
|
||||
if (activeCorner == (Gravity.TOP or Gravity.START) || activeCorner == (Gravity.TOP or Gravity.END)) {
|
||||
layoutParams.y = initialWinY + (initialHeight - newH)
|
||||
}
|
||||
if (activeCorner == (Gravity.TOP or Gravity.START) || activeCorner == (Gravity.BOTTOM or Gravity.START)) {
|
||||
layoutParams.x = initialWinX + (initialWidth - newW)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layoutParams.width = newW
|
||||
layoutParams.height = newH
|
||||
|
||||
val hittingLimit = newW >= maxAllowedWidth || newH >= maxAllowedHeight
|
||||
lockAspectRatioCheckBox.setTextColor(if (hittingLimit) Color.RED else Color.WHITE)
|
||||
}
|
||||
|
||||
private fun getTouchedCorner(x: Float, y: Float, w: Int, h: Int): Int {
|
||||
return when {
|
||||
x < RESIZE_THRESHOLD && y < RESIZE_THRESHOLD -> Gravity.TOP or Gravity.START
|
||||
x > w - RESIZE_THRESHOLD && y < RESIZE_THRESHOLD -> Gravity.TOP or Gravity.END
|
||||
x < RESIZE_THRESHOLD && y > h - RESIZE_THRESHOLD -> Gravity.BOTTOM or Gravity.START
|
||||
x > w - RESIZE_THRESHOLD && y > h - RESIZE_THRESHOLD -> Gravity.BOTTOM or Gravity.END
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveWindowBounds(updatePiPParams: Boolean = true) {
|
||||
if (isFullscreen) return
|
||||
|
||||
val layoutParams = window.attributes
|
||||
layoutWidthInPx = layoutParams.width
|
||||
layoutHeightInPx = layoutParams.height
|
||||
getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit().apply {
|
||||
putInt(KEY_X, layoutParams.x)
|
||||
putInt(KEY_Y, layoutParams.y)
|
||||
putInt(KEY_WIDTH, layoutParams.width)
|
||||
putInt(KEY_HEIGHT, layoutParams.height)
|
||||
putBoolean(KEY_FREE_RESIZE, isFreeResize)
|
||||
apply()
|
||||
}
|
||||
if (updatePiPParams) {
|
||||
updatePiPParams(aspectRatio = Rational(layoutParams.width, layoutParams.height))
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadWindowBounds(layoutParams: WindowManager.LayoutParams) {
|
||||
val prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE)
|
||||
isFreeResize = prefs.getBoolean(KEY_FREE_RESIZE, false)
|
||||
layoutWidthInPx = prefs.getInt(KEY_WIDTH, defaultWidthInPx)
|
||||
layoutHeightInPx = prefs.getInt(KEY_HEIGHT, defaultHeightInPx)
|
||||
layoutParams.x = prefs.getInt(KEY_X, screenBounds.width() - layoutWidthInPx)
|
||||
layoutParams.y = prefs.getInt(KEY_Y, screenBounds.height() - layoutHeightInPx)
|
||||
layoutParams.width = layoutWidthInPx
|
||||
layoutParams.height = layoutHeightInPx
|
||||
lockedAspectRatio = layoutParams.width.toFloat() / layoutParams.height.toFloat().coerceAtLeast(1f)
|
||||
updateLabelText(layoutWidthInPx, layoutHeightInPx)
|
||||
}
|
||||
|
||||
override fun dragGameWindow(view: View, event: MotionEvent): Boolean {
|
||||
if (isFullscreen) return false
|
||||
val lp = window.attributes
|
||||
when (event.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
showResizeUI()
|
||||
gameMenuFragment?.toggleDragButton(true)
|
||||
initialTouchX = event.rawX
|
||||
initialTouchY = event.rawY
|
||||
initialWinX = lp.x
|
||||
initialWinY = lp.y
|
||||
return true
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
lp.x = (initialWinX + (event.rawX - initialTouchX)).toInt()
|
||||
lp.y = (initialWinY + (event.rawY - initialTouchY)).toInt()
|
||||
window.attributes = lp
|
||||
return true
|
||||
}
|
||||
MotionEvent.ACTION_UP -> {
|
||||
saveWindowBounds(updatePiPParams = false) // Only window position is changed, no need to update aspect ratio.
|
||||
hideResizeUI()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun getEditorWindowInfo() = EMBEDDED_RUN_GAME_INFO
|
||||
|
||||
override fun getEditorGameEmbedMode() = GameMenuUtils.GameEmbedMode.ENABLED
|
||||
|
||||
override fun isGameEmbedded() = true
|
||||
|
||||
private fun updateWindowDimensions(widthInPx: Int, heightInPx: Int) {
|
||||
val layoutParams = window.attributes
|
||||
layoutParams.width = widthInPx
|
||||
layoutParams.height = heightInPx
|
||||
window.attributes = layoutParams
|
||||
}
|
||||
|
||||
override fun isMinimizedButtonEnabled() = true
|
||||
override fun isMinimizedButtonEnabled() = isFullscreen
|
||||
|
||||
override fun isCloseButtonEnabled() = true
|
||||
|
||||
|
|
@ -140,24 +412,40 @@ class EmbeddedGodotGame : GodotGame() {
|
|||
|
||||
override fun isMenuBarCollapsable() = false
|
||||
|
||||
override fun isAlwaysOnTopSupported() = hasPiPSystemFeature()
|
||||
override fun isDragButtonEnabled() = !isFullscreen
|
||||
|
||||
override fun isAlwaysOnTopSupported() = isPiPModeSupported()
|
||||
|
||||
override fun onFullScreenUpdated(enabled: Boolean) {
|
||||
godot?.enableImmersiveMode(enabled)
|
||||
isFullscreen = enabled
|
||||
|
||||
val layoutParams = window.attributes
|
||||
if (enabled) {
|
||||
layoutWidthInPx = FULL_SCREEN_WIDTH
|
||||
layoutHeightInPx = FULL_SCREEN_HEIGHT
|
||||
layoutWidthInPx = WindowManager.LayoutParams.MATCH_PARENT
|
||||
layoutHeightInPx = WindowManager.LayoutParams.MATCH_PARENT
|
||||
requestedOrientation = gameRequestedOrientation
|
||||
layoutParams.x = 0
|
||||
layoutParams.y = 0
|
||||
if (resizingEnabled) {
|
||||
disableResizeRunnable.run()
|
||||
}
|
||||
} else {
|
||||
layoutWidthInPx = defaultWidthInPx
|
||||
layoutHeightInPx = defaultHeightInPx
|
||||
loadWindowBounds(layoutParams)
|
||||
|
||||
// Cache the last used orientation in fullscreen to reapply when re-entering fullscreen.
|
||||
gameRequestedOrientation = requestedOrientation
|
||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
}
|
||||
updateWindowDimensions(layoutWidthInPx, layoutHeightInPx)
|
||||
gameMenuFragment?.refreshButtonsVisibility()
|
||||
}
|
||||
|
||||
private fun updateWindowDimensions(widthInPx: Int, heightInPx: Int) {
|
||||
val layoutParams = window.attributes
|
||||
layoutParams.width = widthInPx
|
||||
layoutParams.height = heightInPx
|
||||
window.attributes = layoutParams
|
||||
}
|
||||
|
||||
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {
|
||||
|
|
|
|||
|
|
@ -36,9 +36,11 @@ import android.os.Bundle
|
|||
import android.preference.PreferenceManager
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.ImageButton
|
||||
import android.widget.PopupMenu
|
||||
import android.widget.RadioButton
|
||||
import androidx.core.content.edit
|
||||
|
|
@ -47,6 +49,7 @@ import androidx.fragment.app.Fragment
|
|||
import org.godotengine.editor.BaseGodotEditor
|
||||
import org.godotengine.editor.BaseGodotEditor.Companion.SNACKBAR_SHOW_DURATION_MS
|
||||
import org.godotengine.editor.R
|
||||
import org.godotengine.godot.feature.PictureInPictureProvider
|
||||
import org.godotengine.godot.utils.DialogUtils
|
||||
|
||||
/**
|
||||
|
|
@ -65,7 +68,7 @@ class GameMenuFragment : Fragment(), PopupMenu.OnMenuItemClickListener {
|
|||
/**
|
||||
* Used to be notified of events fired when interacting with the game menu.
|
||||
*/
|
||||
interface GameMenuListener {
|
||||
interface GameMenuListener : PictureInPictureProvider {
|
||||
|
||||
/**
|
||||
* Kotlin representation of the RuntimeNodeSelect::SelectMode enum in 'scene/debugger/scene_debugger.h'.
|
||||
|
|
@ -96,6 +99,8 @@ class GameMenuFragment : Fragment(), PopupMenu.OnMenuItemClickListener {
|
|||
fun suspendGame(suspended: Boolean)
|
||||
fun dispatchNextFrame()
|
||||
fun toggleSelectionVisibility(enabled: Boolean)
|
||||
fun toggleSelectionAvoidLocked(enabled: Boolean)
|
||||
fun toggleSelectionPreferGroup(enabled: Boolean)
|
||||
fun overrideCamera(enabled: Boolean)
|
||||
fun selectRuntimeNode(nodeType: NodeType)
|
||||
fun selectRuntimeNodeSelectMode(selectMode: SelectMode)
|
||||
|
|
@ -109,16 +114,16 @@ class GameMenuFragment : Fragment(), PopupMenu.OnMenuItemClickListener {
|
|||
fun isGameEmbeddingSupported(): Boolean
|
||||
fun embedGameOnPlay(embedded: Boolean)
|
||||
|
||||
fun enterPiPMode() {}
|
||||
fun minimizeGameWindow() {}
|
||||
fun closeGameWindow() {}
|
||||
fun dragGameWindow(view: View, event: MotionEvent): Boolean { return false}
|
||||
|
||||
fun isMinimizedButtonEnabled() = false
|
||||
fun isFullScreenButtonEnabled() = false
|
||||
fun isCloseButtonEnabled() = false
|
||||
fun isPiPButtonEnabled() = false
|
||||
fun isMenuBarCollapsable() = false
|
||||
|
||||
fun isDragButtonEnabled() = false
|
||||
fun isAlwaysOnTopSupported() = false
|
||||
|
||||
fun onFullScreenUpdated(enabled: Boolean) {}
|
||||
|
|
@ -128,6 +133,9 @@ class GameMenuFragment : Fragment(), PopupMenu.OnMenuItemClickListener {
|
|||
private val collapseMenuButton: View? by lazy {
|
||||
view?.findViewById(R.id.game_menu_collapse_button)
|
||||
}
|
||||
private val dragButton: View? by lazy {
|
||||
view?.findViewById(R.id.game_menu_drag_button)
|
||||
}
|
||||
private val suspendButton: View? by lazy {
|
||||
view?.findViewById(R.id.game_menu_suspend_button)
|
||||
}
|
||||
|
|
@ -137,9 +145,6 @@ class GameMenuFragment : Fragment(), PopupMenu.OnMenuItemClickListener {
|
|||
private val setTimeScaleButton: Button? by lazy {
|
||||
view?.findViewById<Button>(R.id.game_menu_set_time_scale_button)
|
||||
}
|
||||
private val resetTimeScaleButton: View? by lazy {
|
||||
view?.findViewById(R.id.game_menu_reset_time_scale_button)
|
||||
}
|
||||
private val unselectNodesButton: RadioButton? by lazy {
|
||||
view?.findViewById(R.id.game_menu_unselect_nodes_button)
|
||||
}
|
||||
|
|
@ -149,15 +154,15 @@ class GameMenuFragment : Fragment(), PopupMenu.OnMenuItemClickListener {
|
|||
private val select3DNodesButton: RadioButton? by lazy {
|
||||
view?.findViewById(R.id.game_menu_select_3d_nodes_button)
|
||||
}
|
||||
private val guiVisibilityButton: View? by lazy {
|
||||
view?.findViewById(R.id.game_menu_gui_visibility_button)
|
||||
}
|
||||
private val toolSelectButton: RadioButton? by lazy {
|
||||
view?.findViewById(R.id.game_menu_tool_select_button)
|
||||
}
|
||||
private val listSelectButton: RadioButton? by lazy {
|
||||
view?.findViewById(R.id.game_menu_list_select_button)
|
||||
}
|
||||
private val selectDropdownButton: ImageButton? by lazy {
|
||||
view?.findViewById(R.id.game_menu_select_dropdown_button)
|
||||
}
|
||||
private val audioMuteButton: View? by lazy {
|
||||
view?.findViewById(R.id.game_menu_audio_mute_button)
|
||||
}
|
||||
|
|
@ -181,13 +186,35 @@ class GameMenuFragment : Fragment(), PopupMenu.OnMenuItemClickListener {
|
|||
PopupMenu(context, optionsButton).apply {
|
||||
setOnMenuItemClickListener(this@GameMenuFragment)
|
||||
inflate(R.menu.options_menu)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
menu.setGroupDividerEnabled(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val selectDropdownMenu: PopupMenu by lazy {
|
||||
PopupMenu(context, selectDropdownButton).apply {
|
||||
inflate(R.menu.select_dropdown_menu)
|
||||
setOnMenuItemClickListener { item: MenuItem ->
|
||||
when (item.itemId) {
|
||||
R.id.menu_show_selection_visibility -> {
|
||||
item.isChecked = !item.isChecked
|
||||
menuListener?.toggleSelectionVisibility(item.isChecked)
|
||||
}
|
||||
R.id.menu_dont_select_locked_nodes -> {
|
||||
item.isChecked = !item.isChecked
|
||||
menuListener?.toggleSelectionAvoidLocked(item.isChecked)
|
||||
}
|
||||
R.id.menu_select_group_over_children -> {
|
||||
item.isChecked = !item.isChecked
|
||||
menuListener?.toggleSelectionPreferGroup(item.isChecked)
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val timeScaleMenu: PopupMenu by lazy {
|
||||
PopupMenu(context, setTimeScaleButton).apply {
|
||||
inflate(R.menu.time_scale_options)
|
||||
|
|
@ -263,6 +290,7 @@ class GameMenuFragment : Fragment(), PopupMenu.OnMenuItemClickListener {
|
|||
val isCloseButtonEnabled = menuListener?.isCloseButtonEnabled() == true
|
||||
val isPiPButtonEnabled = menuListener?.isPiPButtonEnabled() == true
|
||||
val isMenuBarCollapsable = menuListener?.isMenuBarCollapsable() == true
|
||||
val isDragButtonEnabled = menuListener?.isDragButtonEnabled() == true
|
||||
|
||||
// Show the divider if any of the window controls is visible
|
||||
view.findViewById<View>(R.id.game_menu_window_controls_divider)?.isVisible =
|
||||
|
|
@ -270,7 +298,8 @@ class GameMenuFragment : Fragment(), PopupMenu.OnMenuItemClickListener {
|
|||
isFullScreenButtonEnabled ||
|
||||
isCloseButtonEnabled ||
|
||||
isPiPButtonEnabled ||
|
||||
isMenuBarCollapsable
|
||||
isMenuBarCollapsable ||
|
||||
isDragButtonEnabled
|
||||
|
||||
collapseMenuButton?.apply {
|
||||
isVisible = isMenuBarCollapsable
|
||||
|
|
@ -278,6 +307,13 @@ class GameMenuFragment : Fragment(), PopupMenu.OnMenuItemClickListener {
|
|||
collapseGameMenu()
|
||||
}
|
||||
}
|
||||
dragButton?.apply {
|
||||
isVisible = isDragButtonEnabled
|
||||
setOnTouchListener { v, event ->
|
||||
menuListener?.dragGameWindow(v, event) == true
|
||||
}
|
||||
|
||||
}
|
||||
fullscreenButton?.apply{
|
||||
isVisible = isFullScreenButtonEnabled
|
||||
setOnClickListener {
|
||||
|
|
@ -322,13 +358,6 @@ class GameMenuFragment : Fragment(), PopupMenu.OnMenuItemClickListener {
|
|||
}
|
||||
}
|
||||
|
||||
resetTimeScaleButton?.apply {
|
||||
setOnClickListener {
|
||||
menuListener?.resetTimeScale()
|
||||
setTimeScaleButton?.text = "1.0x"
|
||||
}
|
||||
}
|
||||
|
||||
unselectNodesButton?.apply{
|
||||
setOnCheckedChangeListener { buttonView, isChecked ->
|
||||
if (isChecked) {
|
||||
|
|
@ -350,13 +379,6 @@ class GameMenuFragment : Fragment(), PopupMenu.OnMenuItemClickListener {
|
|||
}
|
||||
}
|
||||
}
|
||||
guiVisibilityButton?.apply{
|
||||
setOnClickListener {
|
||||
val isActivated = !it.isActivated
|
||||
menuListener?.toggleSelectionVisibility(!isActivated)
|
||||
it.isActivated = isActivated
|
||||
}
|
||||
}
|
||||
|
||||
toolSelectButton?.apply{
|
||||
setOnCheckedChangeListener { buttonView, isChecked ->
|
||||
|
|
@ -372,6 +394,9 @@ class GameMenuFragment : Fragment(), PopupMenu.OnMenuItemClickListener {
|
|||
}
|
||||
}
|
||||
}
|
||||
selectDropdownButton?.setOnClickListener {
|
||||
selectDropdownMenu.show()
|
||||
}
|
||||
audioMuteButton?.apply{
|
||||
setOnClickListener {
|
||||
val isActivated = !it.isActivated
|
||||
|
|
@ -405,12 +430,16 @@ class GameMenuFragment : Fragment(), PopupMenu.OnMenuItemClickListener {
|
|||
select2DNodesButton?.isChecked = nodeType == GameMenuListener.NodeType.TYPE_2D
|
||||
select3DNodesButton?.isChecked = nodeType == GameMenuListener.NodeType.TYPE_3D
|
||||
|
||||
guiVisibilityButton?.isActivated = !gameMenuState.getBoolean(BaseGodotEditor.GAME_MENU_ACTION_SET_SELECTION_VISIBLE, true)
|
||||
|
||||
val selectMode = gameMenuState.getSerializable(BaseGodotEditor.GAME_MENU_ACTION_SET_SELECT_MODE) as GameMenuListener.SelectMode? ?: GameMenuListener.SelectMode.SINGLE
|
||||
toolSelectButton?.isChecked = selectMode == GameMenuListener.SelectMode.SINGLE
|
||||
listSelectButton?.isChecked = selectMode == GameMenuListener.SelectMode.LIST
|
||||
|
||||
selectDropdownMenu.menu.apply {
|
||||
findItem(R.id.menu_show_selection_visibility)?.isChecked = gameMenuState.getBoolean(BaseGodotEditor.GAME_MENU_ACTION_SET_SELECTION_VISIBLE, true)
|
||||
findItem(R.id.menu_dont_select_locked_nodes)?.isChecked = gameMenuState.getBoolean(BaseGodotEditor.GAME_MENU_ACTION_SET_SELECTION_AVOID_LOCKED, false)
|
||||
findItem(R.id.menu_select_group_over_children)?.isChecked = gameMenuState.getBoolean(BaseGodotEditor.GAME_MENU_ACTION_SET_SELECTION_PREFER_GROUP, false)
|
||||
}
|
||||
|
||||
audioMuteButton?.isActivated = gameMenuState.getBoolean(BaseGodotEditor.GAME_MENU_ACTION_SET_DEBUG_MUTE_AUDIO, false)
|
||||
|
||||
popupMenu.menu.apply {
|
||||
|
|
@ -445,6 +474,10 @@ class GameMenuFragment : Fragment(), PopupMenu.OnMenuItemClickListener {
|
|||
|
||||
internal fun isAlwaysOnTop() = isGameEmbedded && alwaysOnTopChecked
|
||||
|
||||
internal fun toggleDragButton(pressed: Boolean) {
|
||||
dragButton?.isPressed = pressed
|
||||
}
|
||||
|
||||
private fun collapseGameMenu() {
|
||||
view?.isVisible = false
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit {
|
||||
|
|
@ -461,6 +494,11 @@ class GameMenuFragment : Fragment(), PopupMenu.OnMenuItemClickListener {
|
|||
menuListener?.onGameMenuCollapsed(false)
|
||||
}
|
||||
|
||||
internal fun refreshButtonsVisibility() {
|
||||
minimizeButton?.isVisible = menuListener?.isMinimizedButtonEnabled() == true
|
||||
dragButton?.isVisible = menuListener?.isDragButtonEnabled() == true
|
||||
}
|
||||
|
||||
private fun updateAlwaysOnTop(enabled: Boolean) {
|
||||
alwaysOnTopChecked = enabled
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_checked="true" android:color="#FFFFFF" />
|
||||
<item android:color="#808080" />
|
||||
</selector>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:tint="@color/game_menu_icons_color_state"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z" />
|
||||
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<solid android:color="#1C1C1C" />
|
||||
<corners android:radius="8dp" />
|
||||
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_checked="true">
|
||||
<shape>
|
||||
<solid android:color="#333333" />
|
||||
<corners android:radius="6dp" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="@android:color/transparent" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M480,880L310,710L367,653L440,726L440,520L235,520L308,592L250,650L80,480L249,311L306,368L234,440L440,440L440,234L367,307L310,250L480,80L650,250L593,307L520,234L520,440L725,440L652,368L710,310L880,480L710,650L653,593L726,520L520,520L520,725L592,652L650,710L480,880Z"/>
|
||||
</vector>
|
||||
|
|
@ -2,9 +2,6 @@
|
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<solid android:color="#3333b5e5" />
|
||||
<corners android:radius="5dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@android:color/holo_blue_dark" />
|
||||
<solid android:color="#333333" />
|
||||
<corners android:radius="6dp" />
|
||||
</shape>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:pathData="M20,20 L20,4 M20,20 L4,20"
|
||||
android:strokeColor="#007AFF"
|
||||
android:strokeWidth="4"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"/>
|
||||
</vector>
|
||||
|
|
@ -3,6 +3,8 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingHorizontal="4dp"
|
||||
android:background="@android:color/black">
|
||||
|
||||
<HorizontalScrollView
|
||||
|
|
@ -14,142 +16,159 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="48dp"
|
||||
android:paddingVertical="4dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/game_menu_suspend_button"
|
||||
style="?android:attr/borderlessButtonStyle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:padding="6dp"
|
||||
android:background="@drawable/game_menu_button_bg"
|
||||
android:src="@drawable/suspend" />
|
||||
android:src="@drawable/pause" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/game_menu_next_frame_button"
|
||||
style="?android:attr/borderlessButtonStyle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:padding="6dp"
|
||||
android:background="@drawable/game_menu_button_bg"
|
||||
android:src="@drawable/next_frame" />
|
||||
|
||||
<View
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginHorizontal="4dp"
|
||||
android:background="#262626" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/game_menu_set_time_scale_button"
|
||||
style="?android:attr/borderlessButtonStyle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:background="@drawable/game_menu_button_bg"
|
||||
android:text="1.0x"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/game_menu_reset_time_scale_button"
|
||||
style="?android:attr/borderlessButtonStyle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="@drawable/game_menu_button_bg"
|
||||
android:src="@drawable/reset" />
|
||||
android:text="1.0x"
|
||||
android:textColor="#CCCCCC"
|
||||
android:textSize="12sp"
|
||||
android:paddingHorizontal="4dp"
|
||||
android:minWidth="0dp"
|
||||
android:minHeight="0dp" />
|
||||
|
||||
<View
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginHorizontal="@dimen/game_menu_vseparator_horizontal_margin"
|
||||
android:layout_marginVertical="@dimen/game_menu_vseparator_vertical_margin"
|
||||
android:background="@color/game_menu_divider_color" />
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginHorizontal="4dp"
|
||||
android:background="#262626" />
|
||||
|
||||
<RadioGroup
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/button_group_background"
|
||||
android:padding="2dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/game_menu_unselect_nodes_button"
|
||||
style="?android:attr/borderlessButtonStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="48dp"
|
||||
android:background="@drawable/game_menu_button_bg"
|
||||
android:layout_height="32dp"
|
||||
android:background="@drawable/button_group_item_bg"
|
||||
android:button="@null"
|
||||
android:checked="true"
|
||||
android:textColor="@color/button_group_item_text"
|
||||
android:drawableStart="@drawable/input_event_joypad_motion"
|
||||
android:padding="5dp"
|
||||
android:drawablePadding="4dp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:textSize="12sp"
|
||||
android:text="@string/game_menu_input_event_joypad_motion_label" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/game_menu_select_2d_nodes_button"
|
||||
style="?android:attr/borderlessButtonStyle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="@drawable/game_menu_button_bg"
|
||||
android:layout_width="54dp"
|
||||
android:layout_height="32dp"
|
||||
android:background="@drawable/button_group_item_bg"
|
||||
android:button="@null"
|
||||
android:drawableStart="@drawable/nodes_2d"
|
||||
android:padding="5dp"
|
||||
android:text="@string/game_menu_nodes_2d_button_label" />
|
||||
android:drawablePadding="4dp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:text="@string/game_menu_nodes_2d_button_label"
|
||||
android:textColor="@color/button_group_item_text"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/game_menu_select_3d_nodes_button"
|
||||
style="?android:attr/borderlessButtonStyle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="@drawable/game_menu_button_bg"
|
||||
android:layout_width="54dp"
|
||||
android:layout_height="32dp"
|
||||
android:background="@drawable/button_group_item_bg"
|
||||
android:button="@null"
|
||||
android:textColor="@color/button_group_item_text"
|
||||
android:drawableStart="@drawable/node_3d"
|
||||
android:padding="5dp"
|
||||
android:drawablePadding="4dp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:textSize="12sp"
|
||||
android:text="@string/game_menu_node_3d_button_label" />
|
||||
</RadioGroup>
|
||||
|
||||
<View
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginHorizontal="@dimen/game_menu_vseparator_horizontal_margin"
|
||||
android:layout_marginVertical="@dimen/game_menu_vseparator_vertical_margin"
|
||||
android:background="@color/game_menu_divider_color" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/game_menu_gui_visibility_button"
|
||||
style="?android:attr/borderlessButtonStyle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="@drawable/game_menu_button_bg"
|
||||
android:src="@drawable/gui_visibility_selector" />
|
||||
|
||||
<View
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginHorizontal="@dimen/game_menu_vseparator_horizontal_margin"
|
||||
android:layout_marginVertical="@dimen/game_menu_vseparator_vertical_margin"
|
||||
android:background="@color/game_menu_divider_color" />
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginHorizontal="4dp"
|
||||
android:background="#262626" />
|
||||
|
||||
<RadioGroup
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/button_group_background"
|
||||
android:padding="2dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/game_menu_tool_select_button"
|
||||
style="?android:attr/borderlessButtonStyle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="@drawable/game_menu_button_bg"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:background="@drawable/button_group_item_bg"
|
||||
android:button="@null"
|
||||
android:checked="true"
|
||||
android:drawableStart="@drawable/tool_select"
|
||||
android:padding="15dp" />
|
||||
android:paddingHorizontal="8dp" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/game_menu_list_select_button"
|
||||
style="?android:attr/borderlessButtonStyle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="@drawable/game_menu_button_bg"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:background="@drawable/button_group_item_bg"
|
||||
android:button="@null"
|
||||
android:drawableStart="@drawable/list_select"
|
||||
android:padding="15dp" />
|
||||
android:paddingHorizontal="8dp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/game_menu_select_dropdown_button"
|
||||
style="?android:attr/borderlessButtonStyle"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="32dp"
|
||||
android:background="@drawable/game_menu_button_bg"
|
||||
android:src="@drawable/baseline_expand_more_16"
|
||||
android:padding="6dp" />
|
||||
</RadioGroup>
|
||||
|
||||
<View
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginHorizontal="4dp"
|
||||
android:background="#262626" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/game_menu_audio_mute_button"
|
||||
style="?android:attr/borderlessButtonStyle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:padding="6dp"
|
||||
android:background="@drawable/game_menu_button_bg"
|
||||
android:src="@drawable/audio_player_icon_selector" />
|
||||
|
||||
|
|
@ -159,8 +178,8 @@
|
|||
<ImageButton
|
||||
android:id="@+id/game_menu_options_button"
|
||||
style="?android:attr/borderlessButtonStyle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="@drawable/game_menu_button_bg"
|
||||
android:src="@drawable/gui_tab_menu" />
|
||||
|
||||
|
|
@ -175,41 +194,48 @@
|
|||
<ImageButton
|
||||
android:id="@+id/game_menu_collapse_button"
|
||||
style="?android:attr/borderlessButtonStyle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="@drawable/game_menu_selected_button_bg"
|
||||
android:src="@drawable/baseline_expand_less_24"
|
||||
/>
|
||||
android:src="@drawable/baseline_expand_less_24"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/game_menu_drag_button"
|
||||
style="?android:attr/borderlessButtonStyle"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="@drawable/game_menu_selected_button_bg"
|
||||
android:src="@drawable/drag_pan_24px"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/game_menu_minimize_button"
|
||||
style="?android:attr/borderlessButtonStyle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="@drawable/game_menu_button_bg"
|
||||
android:src="@drawable/baseline_minimize_24"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/game_menu_pip_button"
|
||||
style="?android:attr/borderlessButtonStyle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="@drawable/game_menu_selected_button_bg"
|
||||
android:src="@drawable/baseline_picture_in_picture_alt_24"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/game_menu_fullscreen_button"
|
||||
style="?android:attr/borderlessButtonStyle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="@drawable/game_menu_selected_button_bg"
|
||||
android:src="@drawable/baseline_fullscreen_selector"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/game_menu_close_button"
|
||||
style="?android:attr/borderlessButtonStyle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="@drawable/game_menu_button_bg"
|
||||
android:src="@drawable/baseline_close_24"/>
|
||||
</LinearLayout>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/game_menu_fragment_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/godot_fragment_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@+id/game_menu_fragment_container"/>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/lockAspectRatioCheckBox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="60dp"
|
||||
android:background="#CC000000"
|
||||
android:padding="8dp"
|
||||
android:text="Lock Aspect Ratio"
|
||||
android:textColor="#FFFFFF"
|
||||
android:buttonTint="#FFFFFF"
|
||||
android:checked="true"
|
||||
android:visibility="gone" />
|
||||
|
||||
<View
|
||||
android:id="@+id/handleTopLeft"
|
||||
android:layout_width="80px"
|
||||
android:layout_height="80px"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:background="@drawable/resize_handle"
|
||||
android:rotation="180"
|
||||
android:visibility="gone" />
|
||||
|
||||
<View
|
||||
android:id="@+id/handleTopRight"
|
||||
android:layout_width="80px"
|
||||
android:layout_height="80px"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:background="@drawable/resize_handle"
|
||||
android:rotation="270"
|
||||
android:visibility="gone" />
|
||||
|
||||
<View
|
||||
android:id="@+id/handleBottomLeft"
|
||||
android:layout_width="80px"
|
||||
android:layout_height="80px"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:background="@drawable/resize_handle"
|
||||
android:rotation="90"
|
||||
android:visibility="gone" />
|
||||
|
||||
<View
|
||||
android:id="@+id/handleBottomRight"
|
||||
android:layout_width="80px"
|
||||
android:layout_height="80px"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:background="@drawable/resize_handle"
|
||||
android:visibility="gone" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<group android:id="@+id/group_menu_selection_options"
|
||||
android:checkableBehavior="all">
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_show_selection_visibility"
|
||||
android:checkable="true"
|
||||
android:checked="true"
|
||||
android:title="@string/show_selection_visibility"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_dont_select_locked_nodes"
|
||||
android:checkable="true"
|
||||
android:checked="false"
|
||||
android:title="@string/don_t_select_locked_nodes" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_select_group_over_children"
|
||||
android:checkable="true"
|
||||
android:checked="false"
|
||||
android:title="@string/select_group_over_children" />
|
||||
|
||||
</group>
|
||||
|
||||
</menu>
|
||||
|
|
@ -20,4 +20,8 @@
|
|||
<string name="show_game_resume_hint">Tap on \'Game\' to resume</string>
|
||||
<string name="restart_embed_game_hint">Restart game to embed</string>
|
||||
<string name="restart_non_embedded_game_hint">Restart Game to disable embedding</string>
|
||||
<string name="lock_aspect_ratio_btn_text">Lock Aspect ratio (%1$d x %2$d)</string>
|
||||
<string name="don_t_select_locked_nodes">Don\'t Select Locked Nodes</string>
|
||||
<string name="select_group_over_children">Select Group Over Children</string>
|
||||
<string name="show_selection_visibility">Show Selection Visibility</string>
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue