Skip to content

Commit 0312a0c

Browse files
Android: Add export option for custom theme attributes
- Regenerates the `GodotAppMainTheme` and `GodotAppSplashTheme` during Android export. Any manual changes to these styles will be cleared and replaced with default theme attributes. - Adds a new export option `gradle_build/custom_theme_attributes` for injecting custom theme attributes directly via the export window, avoiding the need to manually modify themes.xml.
1 parent 4a44078 commit 0312a0c

File tree

3 files changed

+93
-36
lines changed

3 files changed

+93
-36
lines changed

platform/android/doc_classes/EditorExportPlatformAndroid.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@
5252
<member name="gradle_build/android_source_template" type="String" setter="" getter="">
5353
Path to a ZIP file holding the source for the export template used in a Gradle build. If left empty, the default template is used.
5454
</member>
55+
<member name="gradle_build/custom_theme_attributes" type="Dictionary" setter="" getter="">
56+
A dictionary of custom theme attributes to include in the exported Android project. Each entry defines a theme attribute name and its value, and will be added to the [b]GodotAppMainTheme[/b].
57+
For example, the key [code]android:windowSwipeToDismiss[/code] with the value [code]false[/code] is resolved to [code]&lt;item name="android:windowSwipeToDismiss"&gt;false&lt;/item&gt;[/code].
58+
[b]Note:[/b] To add a custom attribute to the [b]GodotAppSplashTheme[/b], prefix the attribute name with [code][splash][/code].
59+
[b]Note:[/b] Reserved attributes configured via other export options or project settings cannot be overridden by [code]custom_theme_attributes[/code] and are skipped during export.
60+
</member>
5561
<member name="gradle_build/export_format" type="int" setter="" getter="">
5662
Application export format (*.apk or *.aab).
5763
</member>

platform/android/export/export_plugin.cpp

Lines changed: 83 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,52 +1039,100 @@ void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref<EditorExportPres
10391039

10401040
void EditorExportPlatformAndroid::_fix_themes_xml(const Ref<EditorExportPreset> &p_preset) {
10411041
const String themes_xml_path = ExportTemplateManager::get_android_build_directory(p_preset).path_join("res/values/themes.xml");
1042-
bool enable_swipe_to_dismiss = p_preset->get("gesture/swipe_to_dismiss");
10431042

10441043
if (!FileAccess::exists(themes_xml_path)) {
10451044
print_error("res/values/themes.xml does not exist.");
10461045
return;
10471046
}
10481047

1049-
String xml_content;
1048+
// Default/Reserved theme attributes.
1049+
Dictionary main_theme_attributes;
1050+
main_theme_attributes["android:windowDrawsSystemBarBackgrounds"] = "false";
1051+
main_theme_attributes["android:windowSwipeToDismiss"] = bool_to_string(p_preset->get("gesture/swipe_to_dismiss"));
1052+
1053+
Dictionary splash_theme_attributes;
1054+
splash_theme_attributes["android:windowSplashScreenBackground"] = "@mipmap/icon_background";
1055+
splash_theme_attributes["windowSplashScreenAnimatedIcon"] = "@mipmap/icon_foreground";
1056+
splash_theme_attributes["postSplashScreenTheme"] = "@style/GodotAppMainTheme";
1057+
1058+
Dictionary custom_theme_attributes = p_preset->get("gradle_build/custom_theme_attributes");
1059+
1060+
// Does not override default/reserved theme attributes; skips any duplicates from custom_theme_attributes.
1061+
for (const Variant &k : custom_theme_attributes.keys()) {
1062+
String key = k;
1063+
String value = custom_theme_attributes[k];
1064+
if (key.begins_with("[splash]")) {
1065+
String splash_key = key.trim_prefix("[splash]");
1066+
if (splash_theme_attributes.has(splash_key)) {
1067+
WARN_PRINT(vformat("Skipped custom_theme_attribute '%s'; this is a reserved attribute configured via other export options or project settings.", splash_key));
1068+
} else {
1069+
splash_theme_attributes[splash_key] = value;
1070+
}
1071+
} else {
1072+
if (main_theme_attributes.has(key)) {
1073+
WARN_PRINT(vformat("Skipped custom_theme_attribute '%s'; this is a reserved attribute configured via other export options or project settings.", key));
1074+
} else {
1075+
main_theme_attributes[key] = value;
1076+
}
1077+
}
1078+
}
1079+
10501080
Ref<FileAccess> file = FileAccess::open(themes_xml_path, FileAccess::READ);
10511081
PackedStringArray lines = file->get_as_text().split("\n");
10521082
file->close();
10531083

1054-
// Check if the themes.xml already contains <item name="android:windowSwipeToDismiss"> element.
1055-
// If found, update its value based on `enable_swipe_to_dismiss`.
1056-
bool found = false;
1057-
bool modified = false;
1084+
PackedStringArray new_lines;
1085+
bool inside_main_theme = false;
1086+
bool inside_splash_theme = false;
1087+
10581088
for (int i = 0; i < lines.size(); i++) {
10591089
String line = lines[i];
1060-
if (line.contains("<item name") && line.contains("\"android:windowSwipeToDismiss\">")) {
1061-
lines.set(i, vformat(" <item name=\"android:windowSwipeToDismiss\">%s</item>", bool_to_string(enable_swipe_to_dismiss)));
1062-
found = true;
1063-
modified = true;
1064-
break;
1090+
1091+
if (line.contains("<style name=\"GodotAppMainTheme\"")) {
1092+
inside_main_theme = true;
1093+
new_lines.append(line);
1094+
continue;
1095+
}
1096+
if (line.contains("<style name=\"GodotAppSplashTheme\"")) {
1097+
inside_splash_theme = true;
1098+
new_lines.append(line);
1099+
continue;
10651100
}
1066-
}
10671101

1068-
// If <item name="android:windowSwipeToDismiss"> is not found and `enable_swipe_to_dismiss` is false:
1069-
// Add a new <item> element before the closing </style> tag.
1070-
if (!found && !enable_swipe_to_dismiss) {
1071-
for (int i = 0; i < lines.size(); i++) {
1072-
if (lines[i].contains("</style>")) {
1073-
lines.insert(i, " <item name=\"android:windowSwipeToDismiss\">false</item>");
1074-
modified = true;
1075-
break;
1102+
// Inject GodotAppMainTheme attributes.
1103+
if (inside_main_theme && line.contains("</style>")) {
1104+
for (const Variant &attribute : main_theme_attributes.keys()) {
1105+
String value = main_theme_attributes[attribute];
1106+
String item_line = vformat(" <item name=\"%s\">%s</item>", attribute, value);
1107+
new_lines.append(item_line);
1108+
}
1109+
new_lines.append(line); // Add </style> in the end.
1110+
inside_main_theme = false;
1111+
continue;
1112+
}
1113+
1114+
// Inject GodotAppSplashTheme attributes.
1115+
if (inside_splash_theme && line.contains("</style>")) {
1116+
for (const Variant &attribute : splash_theme_attributes.keys()) {
1117+
String value = splash_theme_attributes[attribute];
1118+
String item_line = vformat(" <item name=\"%s\">%s</item>", attribute, value);
1119+
new_lines.append(item_line);
10761120
}
1121+
new_lines.append(line); // Add </style> in the end.
1122+
inside_splash_theme = false;
1123+
continue;
1124+
}
1125+
1126+
// Add all other lines unchanged.
1127+
if (!inside_main_theme && !inside_splash_theme) {
1128+
new_lines.append(line);
10771129
}
10781130
}
10791131

10801132
// Reconstruct the XML content from the modified lines.
1081-
if (modified) {
1082-
xml_content = String("\n").join(lines);
1083-
store_string_at_path(themes_xml_path, xml_content);
1084-
print_verbose("Successfully modified " + themes_xml_path + ": " + "\n" + xml_content);
1085-
} else {
1086-
print_verbose("No changes needed for " + themes_xml_path);
1087-
}
1133+
String xml_content = String("\n").join(new_lines);
1134+
store_string_at_path(themes_xml_path, xml_content);
1135+
print_verbose("Successfully modified " + themes_xml_path + ": " + "\n" + xml_content);
10881136
}
10891137

10901138
void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_manifest, bool p_give_internet) {
@@ -1994,6 +2042,11 @@ String EditorExportPlatformAndroid::get_export_option_warning(const EditorExport
19942042
}
19952043
}
19962044
}
2045+
} else if (p_name == "gradle_build/custom_theme_attributes") {
2046+
bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build");
2047+
if (bool(p_preset->get("gradle_build/custom_theme_attributes")) && !gradle_build_enabled) {
2048+
return TTR("\"Use Gradle Build\" is required to add custom theme attributes.");
2049+
}
19972050
} else if (p_name == "package/show_in_android_tv") {
19982051
bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build");
19992052
if (bool(p_preset->get("package/show_in_android_tv")) && !gradle_build_enabled) {
@@ -2027,6 +2080,8 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio
20272080
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/min_sdk", PROPERTY_HINT_PLACEHOLDER_TEXT, vformat("%d (default)", DEFAULT_MIN_SDK_VERSION)), "", false, true));
20282081
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/target_sdk", PROPERTY_HINT_PLACEHOLDER_TEXT, vformat("%d (default)", DEFAULT_TARGET_SDK_VERSION)), "", false, true));
20292082

2083+
r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "gradle_build/custom_theme_attributes", PROPERTY_HINT_DICTIONARY_TYPE, "String;String"), Dictionary()));
2084+
20302085
#ifndef DISABLE_DEPRECATED
20312086
Vector<PluginConfigAndroid> plugins_configs = get_plugins();
20322087
for (int i = 0; i < plugins_configs.size(); i++) {
@@ -2108,6 +2163,7 @@ bool EditorExportPlatformAndroid::get_export_option_visibility(const EditorExpor
21082163

21092164
bool advanced_options_enabled = p_preset->are_advanced_options_enabled();
21102165
if (p_option == "graphics/opengl_debug" ||
2166+
p_option == "gradle_build/custom_theme_attributes" ||
21112167
p_option == "command_line/extra_args" ||
21122168
p_option == "permissions/custom_permissions" ||
21132169
p_option == "keystore/debug" ||
Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,17 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<resources>
3-
3+
<!-- GodotAppMainTheme is auto-generated during export. Manual changes will be overwritten.
4+
To add custom attributes, use the "gradle_build/custom_theme_attributes" Android export option. -->
45
<style name="GodotAppMainTheme" parent="@android:style/Theme.DeviceDefault.NoActionBar">
56
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
67
<item name="android:windowSwipeToDismiss">false</item>
78
</style>
89

10+
<!-- GodotAppSplashTheme is auto-generated during export. Manual changes will be overwritten.
11+
To add custom attributes, use the "gradle_build/custom_theme_attributes" Android export option. -->
912
<style name="GodotAppSplashTheme" parent="Theme.SplashScreen">
10-
<!-- Set the splash screen background, animated icon, and animation
11-
duration. -->
1213
<item name="android:windowSplashScreenBackground">@mipmap/icon_background</item>
13-
14-
<!-- Use windowSplashScreenAnimatedIcon to add a drawable or an animated
15-
drawable. One of these is required. -->
1614
<item name="windowSplashScreenAnimatedIcon">@mipmap/icon_foreground</item>
17-
18-
<!-- Set the theme of the Activity that directly follows your splash
19-
screen. This is required. -->
2015
<item name="postSplashScreenTheme">@style/GodotAppMainTheme</item>
2116
</style>
2217
</resources>

0 commit comments

Comments
 (0)