diff --git a/librtt/Rtt_HTTPClientCommon.cpp b/librtt/Rtt_HTTPClientCommon.cpp index 2ee9f93c0..b0ee45738 100644 --- a/librtt/Rtt_HTTPClientCommon.cpp +++ b/librtt/Rtt_HTTPClientCommon.cpp @@ -23,6 +23,7 @@ namespace Rtt { int luaload_json(lua_State *L); int luaload_dkjson(lua_State *L); int luaload_BuilderPluginDownloader(lua_State *L); +int luaload_CoronaIconComposerSupport(lua_State *L); int luaload_CoronaPListSupport(lua_State *L); int luaload_CoronaBuilderPluginCollector(lua_State *L); int luaload_dkjson(lua_State* L); diff --git a/platform/mac/CoronaBuilder.xcodeproj/project.pbxproj b/platform/mac/CoronaBuilder.xcodeproj/project.pbxproj index 18f40467f..72ec6ee5c 100644 --- a/platform/mac/CoronaBuilder.xcodeproj/project.pbxproj +++ b/platform/mac/CoronaBuilder.xcodeproj/project.pbxproj @@ -101,6 +101,8 @@ C29CB61219525CD800D1F011 /* re.lua in Sources */ = {isa = PBXBuildFile; fileRef = C29CB60219525BFA00D1F011 /* re.lua */; }; C2A2E8031EC2637300900729 /* Rtt_LuaLibBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C2A2E8011EC2637300900729 /* Rtt_LuaLibBuilder.cpp */; }; C2AD19CE1C40391300217E3E /* CoronaLog.c in Sources */ = {isa = PBXBuildFile; fileRef = C2AD19CD1C40391300217E3E /* CoronaLog.c */; }; + DBBD8F902EA9A8AA00E890BB /* CoronaIconComposerSupport.lua in Resources */ = {isa = PBXBuildFile; fileRef = DBBD8F8F2EA9A8AA00E890BB /* CoronaIconComposerSupport.lua */; }; + DBBD8FA92EA9B35700E890BB /* CoronaIconComposerSupport.lua in Sources */ = {isa = PBXBuildFile; fileRef = DBBD8F8F2EA9A8AA00E890BB /* CoronaIconComposerSupport.lua */; }; F53C329F2404E30900BC2BED /* CoronaBuilderPluginCollector.lua in Sources */ = {isa = PBXBuildFile; fileRef = F53C328E2404E2C000BC2BED /* CoronaBuilderPluginCollector.lua */; }; F547C7F61ED28A2700C4ED92 /* Rtt_DownloadPluginsMain.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F547C7F41ED28A2700C4ED92 /* Rtt_DownloadPluginsMain.cpp */; }; F547C7F71ED28BA600C4ED92 /* BuilderPluginDownloader.lua in Sources */ = {isa = PBXBuildFile; fileRef = F547C7ED1ED289EA00C4ED92 /* BuilderPluginDownloader.lua */; }; @@ -527,6 +529,7 @@ C2AD19CD1C40391300217E3E /* CoronaLog.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = CoronaLog.c; path = ../../librtt/Corona/CoronaLog.c; sourceTree = ""; }; C2F1BAF71EF48EF500D77BD7 /* CoronaShell.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CoronaShell.xcodeproj; path = CoronaShell/CoronaShell.xcodeproj; sourceTree = ""; }; C2F1BAFD1EF48F2F00D77BD7 /* ratatouille.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = ratatouille.xcodeproj; sourceTree = ""; }; + DBBD8F8F2EA9A8AA00E890BB /* CoronaIconComposerSupport.lua */ = {isa = PBXFileReference; lastKnownFileType = text; name = CoronaIconComposerSupport.lua; path = ../resources/CoronaIconComposerSupport.lua; sourceTree = SOURCE_ROOT; }; F53C328E2404E2C000BC2BED /* CoronaBuilderPluginCollector.lua */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = CoronaBuilderPluginCollector.lua; path = ../resources/CoronaBuilderPluginCollector.lua; sourceTree = ""; }; F547C7ED1ED289EA00C4ED92 /* BuilderPluginDownloader.lua */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = BuilderPluginDownloader.lua; path = ../../tools/CoronaBuilder/BuilderPluginDownloader.lua; sourceTree = ""; }; F547C7F41ED28A2700C4ED92 /* Rtt_DownloadPluginsMain.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Rtt_DownloadPluginsMain.cpp; path = ../../tools/CoronaBuilder/Rtt_DownloadPluginsMain.cpp; sourceTree = ""; }; @@ -929,6 +932,7 @@ C2947E151EB29AC5007AFD48 /* Lua */ = { isa = PBXGroup; children = ( + DBBD8F8F2EA9A8AA00E890BB /* CoronaIconComposerSupport.lua */, F5DB5B202113701300EC5CBC /* linuxPackageApp.lua */, C205D56E1C7D2204000A8EE1 /* CoronaBuilder.lua */, F547C7ED1ED289EA00C4ED92 /* BuilderPluginDownloader.lua */, @@ -1231,6 +1235,7 @@ buildActionMask = 2147483647; files = ( C2471D7819882B260076CA1F /* CoronaSimulator.icns in Resources */, + DBBD8F902EA9A8AA00E890BB /* CoronaIconComposerSupport.lua in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1264,6 +1269,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DBBD8FA92EA9B35700E890BB /* CoronaIconComposerSupport.lua in Sources */, F53C329F2404E30900BC2BED /* CoronaBuilderPluginCollector.lua in Sources */, F5BEA4B823FFE98200206A6A /* CoronaOfflineiOSPackager.lua in Sources */, F5DB5B312113702800EC5CBC /* linuxPackageApp.lua in Sources */, diff --git a/platform/mac/Rtt_IOSAppPackager.mm b/platform/mac/Rtt_IOSAppPackager.mm index 82a216dca..871301c16 100644 --- a/platform/mac/Rtt_IOSAppPackager.mm +++ b/platform/mac/Rtt_IOSAppPackager.mm @@ -58,6 +58,7 @@ // following function which loads the bytecodes via luaL_loadbuffer. int luaload_iPhonePackageApp(lua_State* L); int luaload_CoronaPListSupport(lua_State* L); + int luaload_CoronaIconComposerSupport(lua_State* L); int luaload_CoronaOfflineiOSPackager(lua_State* L); // ---------------------------------------------------------------------------- @@ -98,6 +99,7 @@ #endif Lua::RegisterModuleLoader( L, "CoronaPListSupport", Lua::Open< luaload_CoronaPListSupport > ); + Lua::RegisterModuleLoader( L, "CoronaIconComposerSupport", Lua::Open< luaload_CoronaIconComposerSupport > ); HTTPClient::registerFetcherModuleLoaders(L); Lua::DoBuffer( fVM, & luaload_iPhonePackageApp, NULL); diff --git a/platform/mac/Rtt_OSXAppPackager.mm b/platform/mac/Rtt_OSXAppPackager.mm index d23ab7aa5..7cfc2a8b6 100644 --- a/platform/mac/Rtt_OSXAppPackager.mm +++ b/platform/mac/Rtt_OSXAppPackager.mm @@ -49,6 +49,8 @@ // following function which loads the bytecodes via luaL_loadbuffer. int luaload_OSXPackageApp(lua_State* L); int luaload_CoronaPListSupport(lua_State* L); + int luaload_CoronaIconComposerSupport(lua_State* L); + // ---------------------------------------------------------------------------- @@ -88,6 +90,7 @@ #endif Lua::RegisterModuleLoader( L, "CoronaPListSupport", Lua::Open< luaload_CoronaPListSupport > ); + Lua::RegisterModuleLoader( L, "CoronaIconComposerSupport", Lua::Open< luaload_CoronaIconComposerSupport> ); Lua::RegisterModuleLoader( L, "dkjson", Lua::Open< luaload_dkjson > ); Lua::RegisterModuleLoader( L, "json", Lua::Open< luaload_json > ); Lua::RegisterModuleLoader( L, "lpeg", luaopen_lpeg ); diff --git a/platform/mac/Rtt_TVOSAppPackager.mm b/platform/mac/Rtt_TVOSAppPackager.mm index ef467ec2d..b58cee3e0 100644 --- a/platform/mac/Rtt_TVOSAppPackager.mm +++ b/platform/mac/Rtt_TVOSAppPackager.mm @@ -59,6 +59,7 @@ // following function which loads the bytecodes via luaL_loadbuffer. int luaload_tvosPackageApp(lua_State* L); int luaload_CoronaPListSupport(lua_State* L); + int luaload_CoronaIconComposerSupport(lua_State* L); int luaload_CoronaOfflineiOSPackager(lua_State* L); // ---------------------------------------------------------------------------- @@ -99,6 +100,8 @@ #endif Lua::RegisterModuleLoader( L, "CoronaPListSupport", Lua::Open< luaload_CoronaPListSupport > ); + Lua::RegisterModuleLoader( L, "CoronaIconComposerSupport", Lua::Open< luaload_CoronaIconComposerSupport> ); + HTTPClient::registerFetcherModuleLoaders(L); Lua::DoBuffer( fVM, & luaload_tvosPackageApp, NULL); diff --git a/platform/mac/ratatouille.xcodeproj/project.pbxproj b/platform/mac/ratatouille.xcodeproj/project.pbxproj index 0025bbd10..fb360b246 100644 --- a/platform/mac/ratatouille.xcodeproj/project.pbxproj +++ b/platform/mac/ratatouille.xcodeproj/project.pbxproj @@ -1612,6 +1612,8 @@ DB56B4CA284C2E7D00EC8B14 /* Rtt_AppleKeyServices.h in Headers */ = {isa = PBXBuildFile; fileRef = DB56B4C8284C2E7C00EC8B14 /* Rtt_AppleKeyServices.h */; }; DB56B4CB284C2E7D00EC8B14 /* Rtt_AppleKeyServices.mm in Sources */ = {isa = PBXBuildFile; fileRef = DB56B4C9284C2E7D00EC8B14 /* Rtt_AppleKeyServices.mm */; }; DB56B4CC284C2E7D00EC8B14 /* Rtt_AppleKeyServices.mm in Sources */ = {isa = PBXBuildFile; fileRef = DB56B4C9284C2E7D00EC8B14 /* Rtt_AppleKeyServices.mm */; }; + DBBD8FAB2EA9B4AE00E890BB /* CoronaIconComposerSupport.lua in Sources */ = {isa = PBXBuildFile; fileRef = DBBD8FA02EA9A90F00E890BB /* CoronaIconComposerSupport.lua */; }; + DBBD8FAC2EA9B4BD00E890BB /* CoronaIconComposerSupport.lua in Sources */ = {isa = PBXBuildFile; fileRef = DBBD8FA02EA9A90F00E890BB /* CoronaIconComposerSupport.lua */; }; E23CD30D147C725200BC9038 /* BuildSessionState.mm in Sources */ = {isa = PBXBuildFile; fileRef = E23CD30C147C725200BC9038 /* BuildSessionState.mm */; }; F500FB401FED5CE20050B28B /* webtemplate.zip in Resources */ = {isa = PBXBuildFile; fileRef = F500FB3F1FED5CE20050B28B /* webtemplate.zip */; }; F50F51B81B700C04006FEF92 /* Rtt_TextureResourceBitmap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F50F51B61B700C04006FEF92 /* Rtt_TextureResourceBitmap.cpp */; }; @@ -3362,6 +3364,8 @@ C2FCEBA51B701A1800587F1E /* gameoverlayrenderer.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = gameoverlayrenderer.dylib; path = /Applications/Steam.app/Contents/MacOS/gameoverlayrenderer.dylib; sourceTree = ""; }; DB56B4C8284C2E7C00EC8B14 /* Rtt_AppleKeyServices.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Rtt_AppleKeyServices.h; path = ../apple/Rtt_AppleKeyServices.h; sourceTree = ""; }; DB56B4C9284C2E7D00EC8B14 /* Rtt_AppleKeyServices.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = Rtt_AppleKeyServices.mm; path = ../apple/Rtt_AppleKeyServices.mm; sourceTree = ""; }; + DBBD8FA02EA9A90F00E890BB /* CoronaIconComposerSupport.lua */ = {isa = PBXFileReference; lastKnownFileType = text; name = CoronaIconComposerSupport.lua; path = ../resources/CoronaIconComposerSupport.lua; sourceTree = SOURCE_ROOT; }; + DBBD8FAA2EA9B48400E890BB /* CoronaIconComposerSupport.lua */ = {isa = PBXFileReference; lastKnownFileType = text; name = CoronaIconComposerSupport.lua; path = ../resources/CoronaIconComposerSupport.lua; sourceTree = ""; }; E23CD30B147C725200BC9038 /* BuildSessionState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BuildSessionState.h; sourceTree = ""; }; E23CD30C147C725200BC9038 /* BuildSessionState.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BuildSessionState.mm; sourceTree = ""; }; F500FB3F1FED5CE20050B28B /* webtemplate.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; name = webtemplate.zip; path = ../resources/webtemplate.zip; sourceTree = ""; }; @@ -4136,6 +4140,7 @@ 00B73BAF12B71BDA0057F594 /* Resources */ = { isa = PBXGroup; children = ( + DBBD8FA02EA9A90F00E890BB /* CoronaIconComposerSupport.lua */, 1F46A96521136B51009CC875 /* linuxPackageApp.lua */, C2AF1E531DA6F0ED00907A65 /* valid_build_settings.lua */, C2AF1E541DA6F0ED00907A65 /* valid_config_lua.lua */, @@ -4495,6 +4500,7 @@ 29B97314FDCFA39411CA2CEA /* ratatouille */ = { isa = PBXGroup; children = ( + DBBD8FAA2EA9B48400E890BB /* CoronaIconComposerSupport.lua */, C21995A81C189E0100E88FD0 /* ios-syslog-helper.xcodeproj */, C26DD10A1BD9508800EC4F44 /* CoronaConsole.xcodeproj */, C2EF85011B336B6300731A45 /* CoronaShell.xcodeproj */, @@ -6916,6 +6922,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DBBD8FAB2EA9B4AE00E890BB /* CoronaIconComposerSupport.lua in Sources */, F53C328C2404E2A300BC2BED /* CoronaBuilderPluginCollector.lua in Sources */, F5FFADBC23FFC7DB004E61A9 /* CoronaOfflineiOSPackager.lua in Sources */, C2AF1E571DA6F24500907A65 /* valid_build_settings.lua in Sources */, @@ -7524,6 +7531,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DBBD8FAC2EA9B4BD00E890BB /* CoronaIconComposerSupport.lua in Sources */, F53C328D2404E2AB00BC2BED /* CoronaBuilderPluginCollector.lua in Sources */, F5FFADBE23FFC80E004E61A9 /* CoronaOfflineiOSPackager.lua in Sources */, F5DB5B1A21136F9D00EC5CBC /* linuxPackageApp.lua in Sources */, diff --git a/platform/resources/CoronaIconComposerSupport.lua b/platform/resources/CoronaIconComposerSupport.lua new file mode 100644 index 000000000..7bcbc363d --- /dev/null +++ b/platform/resources/CoronaIconComposerSupport.lua @@ -0,0 +1,394 @@ +------------------------------------------------------------------------------ +-- +-- CoronaIconComposerSupport.lua +-- Handles Icon Composer .icon files for iOS, tvOS, and macOS builds +-- Uses actool to compile .icon files with Liquid Glass effects +-- +------------------------------------------------------------------------------ + +local lfs = require('lfs') +local json = require('json') + +local CoronaIconComposerSupport = {} + +-- Utility: Quote string for shell +local function quoteString(str) + str = str:gsub('\\', '\\\\') + str = str:gsub('"', '\\"') + return "\"" .. str .. "\"" +end + +-- Find ictool executable (for legacy/fallback) +local function findIconTool() + local paths = { + "/Applications/Xcode.app/Contents/Applications/Icon Composer.app/Contents/Executables/ictool" + } + + -- Check multiple Xcode versions + for i = 10, 30 do + table.insert(paths, "/Applications/Xcode_" .. i .. ".app/Contents/Applications/Icon Composer.app/Contents/Executables/ictool") + end + + for _, path in ipairs(paths) do + if os.execute("test -x " .. quoteString(path)) == 0 then + return path + end + end + + -- Try Spotlight search + local handle = io.popen("mdfind 'kMDItemFSName == ictool' 2>/dev/null | head -1") + if handle then + local path = handle:read("*l") + handle:close() + if path and path ~= "" and os.execute("test -x " .. quoteString(path)) == 0 then + return path + end + end + + return nil +end + +-- Platform configuration +local function getPlatformConfig(platform) + local configs = { + ios = { + targetDevice = "iphone", + platformName = "iphoneos", + minDeployment = "11.0" + }, + tvos = { + targetDevice = "tv", + platformName = "appletvos", + minDeployment = "11.0" + }, + macos = { + targetDevice = "mac", + platformName = "macosx", + minDeployment = "10.15" + } + } + + -- Normalize platform names + if platform == "iphone" then platform = "ios" end + if platform == "appletvos" then platform = "tvos" end + if platform == "osx" then platform = "macos" end + + return configs[platform] +end + +-- Build actool command +local function buildActoolCommand(iconFilePath, outputPath, platform) + local config = getPlatformConfig(platform) + if not config then + return nil, "Unsupported platform: " .. tostring(platform) + end + + local plistPath = outputPath .. "/assetcatalog_info.plist" + local iconBaseName = iconFilePath:match("([^/]+)%.icon/?$") or "AppIcon" + + local cmd = "xcrun actool " .. quoteString(iconFilePath) .. + " --compile " .. quoteString(outputPath) .. + " --output-format human-readable-text" .. + " --notices --warnings --errors" .. + " --output-partial-info-plist " .. quoteString(plistPath) .. + " --app-icon " .. iconBaseName .. + " --enable-on-demand-resources NO" .. + " --development-region en" .. + " --target-device " .. config.targetDevice .. + " --minimum-deployment-target " .. config.minDeployment .. + " --platform " .. config.platformName + + return cmd, plistPath +end + +-- Validate .icon file structure +local function validateIconFile(iconFilePath) + local attr = lfs.attributes(iconFilePath) + if not attr or attr.mode ~= "directory" then + return false, "Not a directory: " .. iconFilePath + end + + -- Check for icon.json (Icon Composer project) + if os.execute("test -f " .. quoteString(iconFilePath .. "/icon.json")) == 0 then + return true, "icon_composer" + end + + -- Check for Contents.json (Asset Catalog) + if os.execute("test -f " .. quoteString(iconFilePath .. "/Contents.json")) == 0 then + return true, "asset_catalog" + end + + -- Check for PNG files + for file in lfs.dir(iconFilePath) do + if file:match("%.png$") then + return true, "png_bundle" + end + end + + return false, "Invalid .icon file (missing icon.json, Contents.json, or PNG files)" +end + +-- Compile .icon file with actool +local function compileIconWithActool(iconFilePath, tmpDir, platform, debugBuildProcess) + if debugBuildProcess and debugBuildProcess > 0 then + print("Using actool to compile .icon file with Liquid Glass effects...") + end + + -- Create output directory + local outputPath = tmpDir .. "/CompiledAssets_" .. os.time() + os.execute("mkdir -p " .. quoteString(outputPath)) + + -- Build and execute actool command + local actoolCmd, plistPath = buildActoolCommand(iconFilePath, outputPath, platform) + if not actoolCmd then + return nil, plistPath -- plistPath contains error message + end + + if debugBuildProcess and debugBuildProcess > 1 then + print("actool command: " .. actoolCmd) + end + + local result = os.execute(actoolCmd .. " 2>&1") + + -- Check if Assets.car was created + local iconBaseName = iconFilePath:match("([^/]+)%.icon/?$") or "AppIcon" + local assetsCarPath = outputPath .. "/Assets.car" + local testFile = io.open(assetsCarPath, "r") + + if testFile then + testFile:close() + + if debugBuildProcess and debugBuildProcess > 0 then + print("✓ Successfully compiled Assets.car") + print(" Location: " .. assetsCarPath) + end + + -- Clean up plist + os.execute("rm -f " .. quoteString(plistPath)) + + return outputPath, assetsCarPath + else + return nil, "actool failed to compile .icon file" + end +end + +-- Copy asset catalog structure +local function copyAssetCatalog(iconFilePath, tmpDir, debugBuildProcess) + local tempXCAssets = tmpDir .. "/TempAssets.xcassets" + local tempAppIconSet = tempXCAssets .. "/AppIcon.appiconset" + + os.execute("mkdir -p " .. quoteString(tempAppIconSet)) + + local copyCmd = "cp -R " .. quoteString(iconFilePath) .. "/* " .. quoteString(tempAppIconSet) .. "/" + os.execute(copyCmd) + + if debugBuildProcess and debugBuildProcess > 0 then + print("Copied asset catalog structure") + end + + return tempXCAssets, nil +end + +-- Process PNG bundle (simple PNG files) +local function processPngBundle(iconFilePath, tmpDir, platform, debugBuildProcess) + if debugBuildProcess and debugBuildProcess > 0 then + print("Processing PNG bundle...") + end + + local tempXCAssets = tmpDir .. "/TempAssets.xcassets" + local tempAppIconSet = tempXCAssets .. "/AppIcon.appiconset" + + os.execute("mkdir -p " .. quoteString(tempAppIconSet)) + + -- Copy all PNG files + local copiedFiles = {} + for file in lfs.dir(iconFilePath) do + if file:match("%.png$") then + local srcPath = iconFilePath .. "/" .. file + local dstPath = tempAppIconSet .. "/" .. file + os.execute("cp " .. quoteString(srcPath) .. " " .. quoteString(dstPath)) + table.insert(copiedFiles, file) + end + end + + if #copiedFiles == 0 then + return nil, "No PNG files found in bundle" + end + + -- Generate Contents.json for iOS universal format + local contentsImages = {} + + if platform == "ios" or platform == "iphone" then + local variants = { + {pattern = "1024.*[Ll]ight", appearance = nil}, + {pattern = "1024.*[Dd]ark", appearance = "dark"}, + {pattern = "1024.*[Tt]inted", appearance = "tinted"} + } + + for _, variant in ipairs(variants) do + for _, file in ipairs(copiedFiles) do + if file:match(variant.pattern) then + local entry = { + filename = file, + idiom = "universal", + platform = "ios", + size = "1024x1024" + } + + if variant.appearance then + entry.appearances = {{ + appearance = "luminosity", + value = variant.appearance + }} + end + + table.insert(contentsImages, entry) + break + end + end + end + end + + -- Write Contents.json + local contentsFile = io.open(tempAppIconSet .. "/Contents.json", "w") + if not contentsFile then + return nil, "Failed to create Contents.json" + end + + contentsFile:write('{\n "images" : [\n') + for i, img in ipairs(contentsImages) do + contentsFile:write(' {\n') + + if img.appearances then + contentsFile:write(' "appearances" : [\n') + contentsFile:write(' {\n') + contentsFile:write(' "appearance" : "luminosity",\n') + contentsFile:write(' "value" : "' .. img.appearances[1].value .. '"\n') + contentsFile:write(' }\n') + contentsFile:write(' ],\n') + end + + contentsFile:write(' "filename" : "' .. img.filename .. '",\n') + contentsFile:write(' "idiom" : "' .. img.idiom .. '",\n') + contentsFile:write(' "platform" : "' .. img.platform .. '",\n') + contentsFile:write(' "size" : "' .. img.size .. '"\n') + contentsFile:write(' }') + + if i < #contentsImages then + contentsFile:write(',') + end + contentsFile:write('\n') + end + contentsFile:write(' ],\n') + contentsFile:write(' "info" : {\n') + contentsFile:write(' "author" : "xcode",\n') + contentsFile:write(' "version" : 1\n') + contentsFile:write(' }\n') + contentsFile:write('}\n') + contentsFile:close() + + if debugBuildProcess and debugBuildProcess > 0 then + print("Successfully processed " .. #copiedFiles .. " PNG files") + end + + return tempXCAssets, nil +end + +-- Main: Convert .icon file to xcassets or Assets.car +function CoronaIconComposerSupport.convertIconFileToXCAssets(iconFilePath, tmpDir, platform, debugBuildProcess) + -- Validate icon file + local isValid, iconType = validateIconFile(iconFilePath) + if not isValid then + return nil, iconType + end + + if debugBuildProcess and debugBuildProcess > 0 then + print("========================================") + print("Converting .icon file") + print("Source: " .. iconFilePath) + print("Type: " .. iconType) + print("Platform: " .. platform) + print("========================================") + end + + -- Route to appropriate handler + if iconType == "icon_composer" then + return compileIconWithActool(iconFilePath, tmpDir, platform, debugBuildProcess) + elseif iconType == "asset_catalog" then + return copyAssetCatalog(iconFilePath, tmpDir, debugBuildProcess) + elseif iconType == "png_bundle" then + return processPngBundle(iconFilePath, tmpDir, platform, debugBuildProcess) + end + + return nil, "Unknown icon type: " .. iconType +end + +-- Convert alternate icon +function CoronaIconComposerSupport.convertAlternateIconToXCAssets(iconFilePath, iconName, tmpDir, platform, debugBuildProcess) + if debugBuildProcess and debugBuildProcess > 0 then + print("Converting alternate icon: " .. iconName) + end + + return CoronaIconComposerSupport.convertIconFileToXCAssets(iconFilePath, tmpDir, platform, debugBuildProcess) +end + +-- Process all alternate icons +function CoronaIconComposerSupport.processAlternateIcons(alternateIconsConfig, srcAssets, tmpDir, platform, debugBuildProcess) + if not alternateIconsConfig or type(alternateIconsConfig) ~= "table" then + return nil, nil + end + + if debugBuildProcess and debugBuildProcess > 0 then + print("========================================") + print("Processing alternate icons") + print("========================================") + end + + local alternateIconNames = {} + local alternateIconAssets = {} + + for iconName, iconConfig in pairs(alternateIconsConfig) do + if type(iconConfig) == "string" then + -- Legacy format: just icon name + table.insert(alternateIconNames, iconConfig) + elseif type(iconConfig) == "table" and iconConfig.iconFile then + -- New format: .icon file reference + local iconFilePath = srcAssets .. "/" .. iconConfig.iconFile + + if lfs.attributes(iconFilePath, "mode") == "directory" then + local outputPath, assetsCarPath = CoronaIconComposerSupport.convertAlternateIconToXCAssets( + iconFilePath, + iconName, + tmpDir, + platform, + debugBuildProcess + ) + + if outputPath then + table.insert(alternateIconNames, iconName) + alternateIconAssets[iconName] = { + outputPath = outputPath, + assetsCarPath = assetsCarPath + } + + if debugBuildProcess and debugBuildProcess > 0 then + print("✓ Converted: " .. iconName) + end + else + print("WARNING: Failed to convert " .. iconName .. ": " .. tostring(assetsCarPath)) + end + else + print("WARNING: Icon file not found: " .. iconFilePath) + end + end + end + + if debugBuildProcess and debugBuildProcess > 0 then + print("Total alternate icons: " .. #alternateIconNames) + print("========================================") + end + + return alternateIconNames, alternateIconAssets +end + +return CoronaIconComposerSupport \ No newline at end of file diff --git a/platform/resources/iPhonePackageApp.lua b/platform/resources/iPhonePackageApp.lua index 4b6513738..b38da4c34 100644 --- a/platform/resources/iPhonePackageApp.lua +++ b/platform/resources/iPhonePackageApp.lua @@ -18,6 +18,7 @@ local simAvail, simulator = pcall(require, "simulator") -- defines modifyPlist() local CoronaPListSupport = require("CoronaPListSupport") +local CoronaIconComposerSupport = require("CoronaIconComposerSupport") local captureCommandOutput = CoronaPListSupport.captureCommandOutput local coronaLiveBuildAppDir = "_corona_live_build_app" -- "PackageApp" -- @@ -1681,18 +1682,56 @@ function iPhonePostPackage( params ) -- compile Xcode assets for icon if options.settings and options.settings.iphone then setStatus("Compiling Xcode assets catalog") - local xcassetPlatformOptions = { - { "target-device", "iphone" }, - { "target-device", "ipad" }, - { "minimum-deployment-target", "8.0" }, - { "platform", options.signingIdentity and "iphoneos" or "iphonesimulator" }, - - {"app-icon", "AppIcon"}, - } - err = CoronaPListSupport.compileXcassets(options, tmpDir, srcAssets, xcassetPlatformOptions, options.settings.iphone) - if err then - return err + + -- Check if using Icon Composer .icon file + local iconFile = options.settings.iphone.iconFile + local xcassetsPath = srcAssets .. "/Assets.xcassets" + local alternateIconsXCAssets = nil + + + -- Process primary app icon + if iconFile and iconFile:match("%.icon/?$") then + local iconPath = srcAssets .. "/" .. iconFile + + if lfs.attributes(iconPath, "mode") == "directory" then + local outputPath, assetsCarPath = CoronaIconComposerSupport.convertIconFileToXCAssets( + iconPath, + tmpDir, + "ios", + debugBuildProcess + ) + + if outputPath then + local copyCmd = "cp -R -f " .. outputPath .. "/* " .. quoteString(options.dstDir .. '/' .. options.dstFile .. ".app") + runScript(copyCmd) + + options.settings.iphone.plist.CFBundleIcons = { CFBundlePrimaryIcon = { CFBundleIconFiles = {"AppIcon60x60"}, CFBundleIconName = "AppIcon", CFBundleIconLiquidGlass = true }} + options.settings.iphone.plist["CFBundleIcons~ipad"] = { CFBundlePrimaryIcon = { CFBundleIconFiles = {"AppIcon60x60", "AppIcon76x76"}, CFBundleIconName = "AppIcon", CFBundleIconLiquidGlass = true }} + options.settings.iphone.plist.UIPrerenderedIcon = true + else + print("ERROR: Failed to convert .icon file: " .. tostring(errMsg)) + return errMsg + end + else + print("WARNING: iconFile specified but not found: " .. iconPath) + end + else + -- Compile primary app icon xcassets + local xcassetPlatformOptions = { + { "target-device", "iphone" }, + { "target-device", "ipad" }, + { "minimum-deployment-target", "8.0" }, + { "platform", options.signingIdentity and "iphoneos" or "iphonesimulator" }, + {"app-icon", "AppIcon"}, + } + + err = CoronaPListSupport.compileXcassets(options, tmpDir, srcAssets, xcassetPlatformOptions, options.settings.iphone) + if err then + return err + end end + + end setStatus("Packaging app") diff --git a/tools/CoronaBuilder/Rtt_CoronaBuilder.cpp b/tools/CoronaBuilder/Rtt_CoronaBuilder.cpp index b668fe655..e501d85ff 100644 --- a/tools/CoronaBuilder/Rtt_CoronaBuilder.cpp +++ b/tools/CoronaBuilder/Rtt_CoronaBuilder.cpp @@ -58,6 +58,7 @@ int luaload_json(lua_State *L); int luaload_dkjson(lua_State *L); int luaload_CoronaBuilder(lua_State *L); int luaload_CoronaPListSupport(lua_State *L); +int luaload_CoronaIconComposerSupport(lua_State *L); #ifdef Rtt_DEBUG void lua_stackdump(lua_State* L); @@ -229,6 +230,7 @@ CoronaBuilder::CoronaBuilder( #if defined(CORONABUILDER_IOS) || defined(CORONABUILDER_TVOS) || defined(CORONABUILDER_OSX) Lua::RegisterModuleLoader( fL, "CoronaPListSupport", Lua::Open< luaload_CoronaPListSupport > ); + Lua::RegisterModuleLoader( fL, "CoronaIconComposerSupport", Lua::Open< luaload_CoronaIconComposerSupport > ); #endif Lua::RegisterModuleLoader( fL, "dkjson", Lua::Open< luaload_dkjson >, 0 ); Lua::RegisterModuleLoader( fL, "json", Lua::Open< luaload_json >, 0 );