2021-07-13 22:46:21 +03:00
/**************************************************************************/
/* export_plugin.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
# include "export_plugin.h"
2023-06-19 11:28:22 +02:00
# include "logo_svg.gen.h"
2022-12-10 18:59:09 +02:00
# include "run_icon_svg.gen.h"
2023-06-08 14:51:32 +02:00
2025-05-14 12:04:10 +01:00
EditorExportPlatformIOS : : EditorExportPlatformIOS ( ) :
EditorExportPlatformAppleEmbedded ( _ios_logo_svg , _ios_run_icon_svg ) {
2024-02-01 16:02:39 +01:00
}
2025-05-14 12:04:10 +01:00
EditorExportPlatformIOS : : ~ EditorExportPlatformIOS ( ) {
2023-03-09 10:41:52 +02:00
}
void EditorExportPlatformIOS : : get_export_options ( List < ExportOption > * r_options ) const {
2025-05-14 12:04:10 +01:00
EditorExportPlatformAppleEmbedded : : get_export_options ( r_options ) ;
2021-07-13 22:46:21 +03:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : INT , " application/targeted_device_family " , PROPERTY_HINT_ENUM , " iPhone,iPad,iPhone & iPad " ) , 2 ) ) ;
2025-05-14 12:13:10 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " application/min_ios_version " ) , get_minimum_deployment_target ( ) ) ) ;
2023-10-29 20:17:58 +02:00
2025-05-14 12:04:10 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : INT , " storyboard/image_scale_mode " , PROPERTY_HINT_ENUM , " Same as Logo,Center,Scale to Fit,Scale to Fill,Scale " ) , 0 ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " storyboard/custom_image@2x " , PROPERTY_HINT_FILE , " *.png,*.jpg,*.jpeg " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " storyboard/custom_image@3x " , PROPERTY_HINT_FILE , " *.png,*.jpg,*.jpeg " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " storyboard/use_custom_bg_color " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : COLOR , " storyboard/custom_bg_color " ) , Color ( ) ) ) ;
}
2021-07-13 22:46:21 +03:00
2025-05-14 12:04:10 +01:00
bool EditorExportPlatformIOS : : has_valid_export_configuration ( const Ref < EditorExportPreset > & p_preset , String & r_error , bool & r_missing_templates , bool p_debug ) const {
bool valid = EditorExportPlatformAppleEmbedded : : has_valid_export_configuration ( p_preset , r_error , r_missing_templates , p_debug ) ;
2021-07-13 22:46:21 +03:00
2025-05-14 12:04:10 +01:00
String err ;
String rendering_method = get_project_setting ( p_preset , " rendering/renderer/rendering_method.mobile " ) ;
String rendering_driver = get_project_setting ( p_preset , " rendering/rendering_device/driver. " + get_platform_name ( ) ) ;
if ( ( rendering_method = = " forward_plus " | | rendering_method = = " mobile " ) & & rendering_driver = = " metal " ) {
float version = p_preset - > get ( " application/min_ios_version " ) . operator String ( ) . to_float ( ) ;
if ( version < 14.0 ) {
err + = TTR ( " Metal renderer require iOS 14+. " ) + " \n " ;
2024-04-08 10:18:24 +03:00
}
}
2025-05-14 12:04:10 +01:00
if ( ! err . is_empty ( ) ) {
if ( ! r_error . is_empty ( ) ) {
r_error + = err ;
} else {
r_error = err ;
2024-04-08 10:18:24 +03:00
}
}
2025-05-14 12:04:10 +01:00
return valid ;
2021-07-13 22:46:21 +03:00
}
2025-02-10 00:29:29 +01:00
HashMap < String , Variant > EditorExportPlatformIOS : : get_custom_project_settings ( const Ref < EditorExportPreset > & p_preset ) const {
HashMap < String , Variant > settings ;
int image_scale_mode = p_preset - > get ( " storyboard/image_scale_mode " ) ;
String value ;
switch ( image_scale_mode ) {
case 0 : {
2023-01-17 09:52:17 +02:00
String logo_path = get_project_setting ( p_preset , " application/boot_splash/image " ) ;
bool is_on = get_project_setting ( p_preset , " application/boot_splash/fullsize " ) ;
2025-02-10 00:29:29 +01:00
// If custom logo is not specified, Godot does not scale default one, so we should do the same.
value = ( is_on & & logo_path . length ( ) > 0 ) ? " scaleAspectFit " : " center " ;
} break ;
default : {
value = storyboard_image_scale_mode [ image_scale_mode - 1 ] ;
}
}
settings [ " ios/launch_screen_image_mode " ] = value ;
return settings ;
}
2025-05-14 12:04:10 +01:00
Error EditorExportPlatformIOS : : _export_loading_screen_file ( const Ref < EditorExportPreset > & p_preset , const String & p_dest_dir ) {
const String custom_launch_image_2x = p_preset - > get ( " storyboard/custom_image@2x " ) ;
const String custom_launch_image_3x = p_preset - > get ( " storyboard/custom_image@3x " ) ;
2021-07-13 22:46:21 +03:00
2025-05-14 12:04:10 +01:00
if ( custom_launch_image_2x . length ( ) > 0 & & custom_launch_image_3x . length ( ) > 0 ) {
String image_path = p_dest_dir . path_join ( " splash@2x.png " ) ;
Error err = OK ;
Ref < Image > image = _load_icon_or_splash_image ( custom_launch_image_2x , & err ) ;
2021-07-13 22:46:21 +03:00
2025-05-14 12:04:10 +01:00
if ( err ! = OK | | image . is_null ( ) | | image - > is_empty ( ) ) {
return err ;
}
2021-07-13 22:46:21 +03:00
2025-05-14 12:04:10 +01:00
if ( image - > save_png ( image_path ) ! = OK ) {
return ERR_FILE_CANT_WRITE ;
}
2021-07-13 22:46:21 +03:00
2025-05-14 12:04:10 +01:00
image_path = p_dest_dir . path_join ( " splash@3x.png " ) ;
image = _load_icon_or_splash_image ( custom_launch_image_3x , & err ) ;
2024-04-23 12:59:24 +03:00
2025-05-14 12:04:10 +01:00
if ( err ! = OK | | image . is_null ( ) | | image - > is_empty ( ) ) {
return err ;
}
2024-04-23 12:59:24 +03:00
2025-05-14 12:04:10 +01:00
if ( image - > save_png ( image_path ) ! = OK ) {
return ERR_FILE_CANT_WRITE ;
}
} else {
Error err = OK ;
Ref < Image > splash ;
2021-07-13 22:46:21 +03:00
2025-05-14 12:04:10 +01:00
const String splash_path = get_project_setting ( p_preset , " application/boot_splash/image " ) ;
2021-07-13 22:46:21 +03:00
2025-05-14 12:04:10 +01:00
if ( ! splash_path . is_empty ( ) ) {
splash = _load_icon_or_splash_image ( splash_path , & err ) ;
}
2021-07-13 22:46:21 +03:00
2025-05-14 12:04:10 +01:00
if ( err ! = OK | | splash . is_null ( ) | | splash - > is_empty ( ) ) {
splash . instantiate ( boot_splash_png ) ;
}
2021-07-13 22:46:21 +03:00
2025-05-14 12:04:10 +01:00
// Using same image for both @2x and @3x
// because Godot's own boot logo uses single image for all resolutions.
// Also not using @1x image, because devices using this image variant
// are not supported by iOS 9, which is minimal target.
const String splash_png_path_2x = p_dest_dir . path_join ( " splash@2x.png " ) ;
const String splash_png_path_3x = p_dest_dir . path_join ( " splash@3x.png " ) ;
2022-08-17 16:11:56 +03:00
2025-05-14 12:04:10 +01:00
if ( splash - > save_png ( splash_png_path_2x ) ! = OK ) {
return ERR_FILE_CANT_WRITE ;
}
2022-08-17 16:11:56 +03:00
2025-05-14 12:04:10 +01:00
if ( splash - > save_png ( splash_png_path_3x ) ! = OK ) {
return ERR_FILE_CANT_WRITE ;
2021-07-13 22:46:21 +03:00
}
}
2025-05-14 12:04:10 +01:00
return OK ;
2021-07-13 22:46:21 +03:00
}
2025-05-14 12:04:10 +01:00
Vector < EditorExportPlatformAppleEmbedded : : IconInfo > EditorExportPlatformIOS : : get_icon_infos ( ) const {
Vector < EditorExportPlatformAppleEmbedded : : IconInfo > icon_infos ;
return {
// Settings on iPhone, iPad Pro, iPad, iPad mini
{ PNAME ( " icons/settings_58x58 " ) , " universal " , " Icon-58 " , " 58 " , " 2x " , " 29x29 " , false } ,
{ PNAME ( " icons/settings_87x87 " ) , " universal " , " Icon-87 " , " 87 " , " 3x " , " 29x29 " , false } ,
2021-07-13 22:46:21 +03:00
2025-05-14 12:04:10 +01:00
// Notifications on iPhone, iPad Pro, iPad, iPad mini
{ PNAME ( " icons/notification_40x40 " ) , " universal " , " Icon-40 " , " 40 " , " 2x " , " 20x20 " , false } ,
{ PNAME ( " icons/notification_60x60 " ) , " universal " , " Icon-60 " , " 60 " , " 3x " , " 20x20 " , false } ,
{ PNAME ( " icons/notification_76x76 " ) , " universal " , " Icon-76 " , " 76 " , " 2x " , " 38x38 " , false } ,
{ PNAME ( " icons/notification_114x114 " ) , " universal " , " Icon-114 " , " 114 " , " 3x " , " 38x38 " , false } ,
2021-07-13 22:46:21 +03:00
2025-05-14 12:04:10 +01:00
// Spotlight on iPhone, iPad Pro, iPad, iPad mini
{ PNAME ( " icons/spotlight_80x80 " ) , " universal " , " Icon-80 " , " 80 " , " 2x " , " 40x40 " , false } ,
{ PNAME ( " icons/spotlight_120x120 " ) , " universal " , " Icon-120 " , " 120 " , " 3x " , " 40x40 " , false } ,
2021-07-13 22:46:21 +03:00
2025-05-14 12:04:10 +01:00
// Home Screen on iPhone
{ PNAME ( " icons/iphone_120x120 " ) , " universal " , " Icon-120-1 " , " 120 " , " 2x " , " 60x60 " , false } ,
{ PNAME ( " icons/iphone_180x180 " ) , " universal " , " Icon-180 " , " 180 " , " 3x " , " 60x60 " , false } ,
2021-07-13 22:46:21 +03:00
2025-05-14 12:04:10 +01:00
// Home Screen on iPad Pro
{ PNAME ( " icons/ipad_167x167 " ) , " universal " , " Icon-167 " , " 167 " , " 2x " , " 83.5x83.5 " , false } ,
2021-07-13 22:46:21 +03:00
2025-05-14 12:04:10 +01:00
// Home Screen on iPad, iPad mini
{ PNAME ( " icons/ipad_152x152 " ) , " universal " , " Icon-152 " , " 152 " , " 2x " , " 76x76 " , false } ,
2021-07-13 22:46:21 +03:00
2025-05-14 12:04:10 +01:00
{ PNAME ( " icons/ios_128x128 " ) , " universal " , " Icon-128 " , " 128 " , " 2x " , " 64x64 " , false } ,
{ PNAME ( " icons/ios_192x192 " ) , " universal " , " Icon-192 " , " 192 " , " 3x " , " 64x64 " , false } ,
2021-07-13 22:46:21 +03:00
2025-05-14 12:04:10 +01:00
{ PNAME ( " icons/ios_136x136 " ) , " universal " , " Icon-136 " , " 136 " , " 2x " , " 68x68 " , false } ,
2021-07-13 22:46:21 +03:00
2025-05-14 12:04:10 +01:00
// App Store
{ PNAME ( " icons/app_store_1024x1024 " ) , " universal " , " Icon-1024 " , " 1024 " , " 1x " , " 1024x1024 " , true } ,
} ;
2021-07-13 22:46:21 +03:00
}
Error EditorExportPlatformIOS : : _export_icons ( const Ref < EditorExportPreset > & p_preset , const String & p_iconset_dir ) {
String json_description = " { \" images \" :[ " ;
String sizes ;
2022-03-23 11:08:58 +02:00
Ref < DirAccess > da = DirAccess : : open ( p_iconset_dir ) ;
2023-12-06 15:18:35 +01:00
if ( da . is_null ( ) ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export Icons " ) , vformat ( TTR ( " Could not open a directory at path \" %s \" . " ) , p_iconset_dir ) ) ;
return ERR_CANT_OPEN ;
}
2021-07-13 22:46:21 +03:00
2023-01-17 09:52:17 +02:00
Color boot_bg_color = get_project_setting ( p_preset , " application/boot_splash/bg_color " ) ;
2022-11-17 11:53:04 +02:00
2024-10-03 23:03:19 +03:00
enum IconColorMode {
ICON_NORMAL ,
ICON_DARK ,
ICON_TINTED ,
ICON_MAX ,
} ;
2025-05-14 12:04:10 +01:00
Vector < IconInfo > icon_infos = get_icon_infos ( ) ;
2024-10-03 23:03:19 +03:00
bool first_icon = true ;
2025-05-14 12:04:10 +01:00
for ( int i = 0 ; i < icon_infos . size ( ) ; + + i ) {
2024-10-03 23:03:19 +03:00
for ( int color_mode = ICON_NORMAL ; color_mode < ICON_MAX ; color_mode + + ) {
IconInfo info = icon_infos [ i ] ;
int side_size = String ( info . actual_size_side ) . to_int ( ) ;
String key = info . preset_key ;
String exp_name = info . export_name ;
if ( color_mode = = ICON_DARK ) {
key + = " _dark " ;
exp_name + = " _dark " ;
} else if ( color_mode = = ICON_TINTED ) {
key + = " _tinted " ;
exp_name + = " _tinted " ;
2022-08-19 11:51:06 -05:00
}
2024-10-03 23:03:19 +03:00
exp_name + = " .png " ;
String icon_path = p_preset - > get ( key ) ;
bool resize_waning = true ;
if ( icon_path . is_empty ( ) ) {
// Load and resize base icon.
key = " icons/icon_1024x1024 " ;
if ( color_mode = = ICON_DARK ) {
key + = " _dark " ;
} else if ( color_mode = = ICON_TINTED ) {
key + = " _tinted " ;
}
icon_path = p_preset - > get ( key ) ;
resize_waning = false ;
2021-07-13 22:46:21 +03:00
}
2024-10-03 23:03:19 +03:00
if ( icon_path . is_empty ( ) ) {
if ( color_mode ! = ICON_NORMAL ) {
continue ;
}
// Resize main app icon.
2023-01-17 09:52:17 +02:00
icon_path = get_project_setting ( p_preset , " application/config/icon " ) ;
2025-01-28 08:07:20 +02:00
Error err = OK ;
Ref < Image > img = _load_icon_or_splash_image ( icon_path , & err ) ;
if ( err ! = OK | | img . is_null ( ) | | img - > is_empty ( ) ) {
2024-10-03 23:03:19 +03:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export Icons " ) , vformat ( " Invalid icon (%s): '%s'. " , info . preset_key , icon_path ) ) ;
return ERR_UNCONFIGURED ;
} else if ( info . force_opaque & & img - > detect_alpha ( ) ! = Image : : ALPHA_NONE ) {
img - > resize ( side_size , side_size , ( Image : : Interpolation ) ( p_preset - > get ( " application/icon_interpolation " ) . operator int ( ) ) ) ;
Ref < Image > new_img = Image : : create_empty ( side_size , side_size , false , Image : : FORMAT_RGBA8 ) ;
new_img - > fill ( boot_bg_color ) ;
_blend_and_rotate ( new_img , img , false ) ;
err = new_img - > save_png ( p_iconset_dir + exp_name ) ;
} else {
img - > resize ( side_size , side_size , ( Image : : Interpolation ) ( p_preset - > get ( " application/icon_interpolation " ) . operator int ( ) ) ) ;
err = img - > save_png ( p_iconset_dir + exp_name ) ;
}
if ( err ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export Icons " ) , vformat ( " Failed to export icon (%s): '%s'. " , info . preset_key , icon_path ) ) ;
return err ;
}
2022-01-17 11:42:31 +02:00
} else {
2024-10-03 23:03:19 +03:00
// Load custom icon and resize if required.
2025-01-28 08:07:20 +02:00
Error err = OK ;
Ref < Image > img = _load_icon_or_splash_image ( icon_path , & err ) ;
if ( err ! = OK | | img . is_null ( ) | | img - > is_empty ( ) ) {
2024-10-03 23:03:19 +03:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export Icons " ) , vformat ( " Invalid icon (%s): '%s'. " , info . preset_key , icon_path ) ) ;
return ERR_UNCONFIGURED ;
} else if ( info . force_opaque & & img - > detect_alpha ( ) ! = Image : : ALPHA_NONE ) {
if ( resize_waning ) {
add_message ( EXPORT_MESSAGE_WARNING , TTR ( " Export Icons " ) , vformat ( " Icon (%s) must be opaque. " , info . preset_key ) ) ;
}
img - > resize ( side_size , side_size , ( Image : : Interpolation ) ( p_preset - > get ( " application/icon_interpolation " ) . operator int ( ) ) ) ;
Ref < Image > new_img = Image : : create_empty ( side_size , side_size , false , Image : : FORMAT_RGBA8 ) ;
new_img - > fill ( boot_bg_color ) ;
_blend_and_rotate ( new_img , img , false ) ;
err = new_img - > save_png ( p_iconset_dir + exp_name ) ;
} else if ( img - > get_width ( ) ! = side_size | | img - > get_height ( ) ! = side_size ) {
if ( resize_waning ) {
add_message ( EXPORT_MESSAGE_WARNING , TTR ( " Export Icons " ) , vformat ( " Icon (%s): '%s' has incorrect size %s and was automatically resized to %s. " , info . preset_key , icon_path , img - > get_size ( ) , Vector2i ( side_size , side_size ) ) ) ;
}
img - > resize ( side_size , side_size , ( Image : : Interpolation ) ( p_preset - > get ( " application/icon_interpolation " ) . operator int ( ) ) ) ;
err = img - > save_png ( p_iconset_dir + exp_name ) ;
} else if ( ! icon_path . ends_with ( " .png " ) ) {
err = img - > save_png ( p_iconset_dir + exp_name ) ;
} else {
err = da - > copy ( icon_path , p_iconset_dir + exp_name ) ;
}
2021-07-13 22:46:21 +03:00
2024-10-03 23:03:19 +03:00
if ( err ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export Icons " ) , vformat ( " Failed to export icon (%s): '%s'. " , info . preset_key , icon_path ) ) ;
return err ;
}
2021-07-13 22:46:21 +03:00
}
2024-10-03 23:03:19 +03:00
sizes + = String ( info . actual_size_side ) + " \n " ;
if ( first_icon ) {
first_icon = false ;
} else {
json_description + = " , " ;
}
json_description + = String ( " { " ) ;
if ( color_mode ! = ICON_NORMAL ) {
json_description + = String ( " \" appearances \" :[{ " ) ;
json_description + = String ( " \" appearance \" : \" luminosity \" , " ) ;
if ( color_mode = = ICON_DARK ) {
json_description + = String ( " \" value \" : \" dark \" " ) ;
} else if ( color_mode = = ICON_TINTED ) {
json_description + = String ( " \" value \" : \" tinted \" " ) ;
}
json_description + = String ( " }], " ) ;
}
json_description + = String ( " \" idiom \" : " ) + " \" " + info . idiom + " \" , " ;
2025-05-14 12:04:10 +01:00
json_description + = String ( " \" platform \" : \" " + get_platform_name ( ) + " \" , " ) ;
2024-10-03 23:03:19 +03:00
json_description + = String ( " \" size \" : " ) + " \" " + info . unscaled_size + " \" , " ;
if ( String ( info . scale ) ! = " 1x " ) {
json_description + = String ( " \" scale \" : " ) + " \" " + info . scale + " \" , " ;
}
json_description + = String ( " \" filename \" : " ) + " \" " + exp_name + " \" " ;
json_description + = String ( " } " ) ;
2021-07-13 22:46:21 +03:00
}
}
2024-10-03 23:03:19 +03:00
json_description + = " ], \" info \" :{ \" author \" : \" xcode \" , \" version \" :1}} " ;
2021-07-13 22:46:21 +03:00
2022-03-23 11:08:58 +02:00
Ref < FileAccess > json_file = FileAccess : : open ( p_iconset_dir + " Contents.json " , FileAccess : : WRITE ) ;
2023-12-06 15:18:35 +01:00
if ( json_file . is_null ( ) ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export Icons " ) , vformat ( TTR ( " Could not write to a file at path \" %s \" . " ) , p_iconset_dir + " Contents.json " ) ) ;
return ERR_CANT_CREATE ;
}
2021-07-13 22:46:21 +03:00
CharString json_utf8 = json_description . utf8 ( ) ;
json_file - > store_buffer ( ( const uint8_t * ) json_utf8 . get_data ( ) , json_utf8 . length ( ) ) ;
2022-03-23 11:08:58 +02:00
Ref < FileAccess > sizes_file = FileAccess : : open ( p_iconset_dir + " sizes " , FileAccess : : WRITE ) ;
2023-12-06 15:18:35 +01:00
if ( sizes_file . is_null ( ) ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export Icons " ) , vformat ( TTR ( " Could not write to a file at path \" %s \" . " ) , p_iconset_dir + " sizes " ) ) ;
return ERR_CANT_CREATE ;
}
2021-07-13 22:46:21 +03:00
CharString sizes_utf8 = sizes . utf8 ( ) ;
sizes_file - > store_buffer ( ( const uint8_t * ) sizes_utf8 . get_data ( ) , sizes_utf8 . length ( ) ) ;
return OK ;
}