Merge pull request #119156 from syntaxerror247/game-menu-bar

Android editor: Update game menu bar to match desktop editor
This commit is contained in:
Thaddeus Crews
2026-06-22 08:29:49 -05:00
14 changed files with 277 additions and 108 deletions
@@ -147,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"
@@ -980,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)
@@ -1040,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 {
@@ -130,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)
@@ -40,6 +40,7 @@ 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
@@ -98,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)
@@ -142,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)
}
@@ -154,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)
}
@@ -192,6 +192,30 @@ class GameMenuFragment : Fragment(), PopupMenu.OnMenuItemClickListener {
}
}
private val selectDropdownMenu: PopupMenu by lazy {
PopupMenu(context, selectDropdownButton).apply {
inflate(R.menu.select_dropdown_menu)
menu.setGroupDividerEnabled(true)
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)
@@ -335,13 +359,6 @@ class GameMenuFragment : Fragment(), PopupMenu.OnMenuItemClickListener {
}
}
resetTimeScaleButton?.apply {
setOnClickListener {
menuListener?.resetTimeScale()
setTimeScaleButton?.text = "1.0x"
}
}
unselectNodesButton?.apply{
setOnCheckedChangeListener { buttonView, isChecked ->
if (isChecked) {
@@ -363,13 +380,6 @@ class GameMenuFragment : Fragment(), PopupMenu.OnMenuItemClickListener {
}
}
}
guiVisibilityButton?.apply{
setOnClickListener {
val isActivated = !it.isActivated
menuListener?.toggleSelectionVisibility(!isActivated)
it.isActivated = isActivated
}
}
toolSelectButton?.apply{
setOnCheckedChangeListener { buttonView, isChecked ->
@@ -385,6 +395,9 @@ class GameMenuFragment : Fragment(), PopupMenu.OnMenuItemClickListener {
}
}
}
selectDropdownButton?.setOnClickListener {
selectDropdownMenu.show()
}
audioMuteButton?.apply{
setOnClickListener {
val isActivated = !it.isActivated
@@ -418,12 +431,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 {
@@ -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>
@@ -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>
@@ -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,50 +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="48dp"
android:layout_height="48dp"
android:layout_width="36dp"
android:layout_height="36dp"
android:background="@drawable/game_menu_selected_button_bg"
android:src="@drawable/drag_pan_24px"
/>
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,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,5 +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 (%d x %d)</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>