From 5b4e77d0c6aac6056bb1bef6bf1f91057426a417 Mon Sep 17 00:00:00 2001 From: Dan Rosser Date: Fri, 16 Oct 2026 16:07:58 +1300 Subject: [PATCH 1/7] App version / build: --app-version / --app-build + GUI Version / Build fields; config.make, CMake, Xcode target + Project.xcconfig, Android build.gradle; read back on update --- commandLine/src/main.cpp | 28 +++- commandLine/src/projects/baseProject.cpp | 153 ++++++++++++++++++++++ commandLine/src/projects/baseProject.h | 5 + commandLine/src/projects/xcodeProject.cpp | 17 +++ frontend/app.js | 20 ++- frontend/index.html | 11 ++ frontend/index.js | 51 ++++++++ frontend/locales/de.json | 7 +- frontend/locales/en.json | 7 +- frontend/locales/es.json | 7 +- frontend/locales/fr.json | 7 +- frontend/locales/ja.json | 7 +- frontend/locales/ko.json | 7 +- frontend/locales/pt.json | 7 +- frontend/locales/ru.json | 7 +- frontend/locales/zh-CN.json | 7 +- 16 files changed, 337 insertions(+), 11 deletions(-) diff --git a/commandLine/src/main.cpp b/commandLine/src/main.cpp index 76eac63f..28e9c4d1 100644 --- a/commandLine/src/main.cpp +++ b/commandLine/src/main.cpp @@ -3,6 +3,7 @@ #include "Utils.h" #include "defines.h" +#include #include "ofUtils.h" #include "ofFileUtils.h" #include "ofSystemUtils.h" @@ -35,7 +36,9 @@ enum optionIndex { UNKNOWN, DEFINES, POSTBUILD, MSYS2ENV, - EMSCRIPTEN_WASM + EMSCRIPTEN_WASM, + APP_VERSION, + APP_BUILD }; constexpr option::Descriptor usage[] = { @@ -67,6 +70,8 @@ constexpr option::Descriptor usage[] = { { MSYS2ENV, 0, "k", "msys2env", option::Arg::Optional, " --msys2env, -k \tMSYS2 environment for the msys2 / VSCode targets: ucrt64 (default) | mingw64 | clang64 | clangarm64" }, { EMSCRIPTEN_WASM, 0, "W", "emscripten-wasm", option::Arg::Optional, " --emscripten-wasm, -W \temscripten WebAssembly: 32 (default) | 64 (wasm64, MEMORY64), written to config.make PROJECT_EMSCRIPTEN_WASM" }, + { APP_VERSION, 0, "", "app-version", option::Arg::Optional, " --app-version \tapp version, eg 1.2.3 (default 1.0): config.make / CMake APP_VERSION, Xcode MARKETING_VERSION, Android versionName" }, + { APP_BUILD, 0, "", "app-build", option::Arg::Optional, " --app-build \tbuild number, eg 42 (default 1): config.make / CMake APP_BUILD, Xcode CURRENT_PROJECT_VERSION, Android versionCode" }, { 0, 0, 0, 0, 0, 0 } }; @@ -96,6 +101,8 @@ vector frameworks; vector defines; string postBuildScript; string emscriptenWasm; // empty keeps the project's config.make value +string appVersion; // empty keeps the project's value +string appBuild; string ofPathEnv; string templateName; string msys2Env = "ucrt64"; @@ -268,6 +275,7 @@ void updateProject(const fs::path & path, const string & target, bool bConsiderP auto project = getTargetProject(target); project->setMsys2Environment(msys2Env); project->setEmscriptenWasm(emscriptenWasm); + project->setAppVersion(appVersion, appBuild); project->create(path, templateName); if (bConsiderParameterAddons && bAddonsPassedIn) { @@ -628,6 +636,23 @@ int pgMain(int argc, char ** argv) { } } + if (options[APP_VERSION].count() > 0 && options[APP_VERSION].arg != NULL) { + std::string v = ofTrim(options[APP_VERSION].arg); + if (std::regex_match(v, std::regex(R"(\d+(\.\d+)*)"))) { + appVersion = v; + } else { + ofLogWarning() << "app version " << v << " should be digits and dots (eg 1.2.3) - keeping the project's version"; + } + } + if (options[APP_BUILD].count() > 0 && options[APP_BUILD].arg != NULL) { + std::string b = ofTrim(options[APP_BUILD].arg); + if (std::regex_match(b, std::regex(R"([1-9]\d*)"))) { + appBuild = b; + } else { + ofLogWarning() << "app build " << b << " should be a positive integer - keeping the project's build"; + } + } + if (options[POSTBUILD].count() > 0 && options[POSTBUILD].arg != NULL) { postBuildScript = ofTrim(options[POSTBUILD].arg); } @@ -773,6 +798,7 @@ int pgMain(int argc, char ** argv) { auto project = getTargetProject(t); project->setMsys2Environment(msys2Env); project->setEmscriptenWasm(emscriptenWasm); + project->setAppVersion(appVersion, appBuild); project->create(projectPath, templateName); if(bAddonsPassedIn){ for (auto & addon : addons) { diff --git a/commandLine/src/projects/baseProject.cpp b/commandLine/src/projects/baseProject.cpp index 42ad3f10..8d97c6ba 100644 --- a/commandLine/src/projects/baseProject.cpp +++ b/commandLine/src/projects/baseProject.cpp @@ -142,6 +142,65 @@ static std::string readCMakeEmscriptenWasm(const fs::path & cmakeLists) { return ""; } +// APP_VERSION / APP_BUILD in config.make (openFrameworks#8592) +static std::pair readMakeAppVersion(const fs::path & configMake) { + std::string version, build; + if (!fs::exists(configMake)) return { version, build }; + for (auto & line : fileToStrings(configMake)) { + auto config = ofTrim(line); + if (config.empty() || config[0] == '#') continue; + auto eq = config.find('='); + if (eq == std::string::npos) continue; + auto var = ofTrim(config.substr(0, eq)); + if (!var.empty() && (var.back() == '?' || var.back() == '+' || var.back() == ':')) var = ofTrim(var.substr(0, var.size() - 1)); + if (var == "APP_VERSION") version = ofTrim(config.substr(eq + 1)); + if (var == "APP_BUILD") build = ofTrim(config.substr(eq + 1)); + } + return { version, build }; +} + +// set(APP_VERSION ...) / set(APP_BUILD ...) in an openFrameworks CMakeLists.txt (openFrameworks#8586) +static std::pair readCMakeAppVersion(const fs::path & cmakeLists) { + static const std::regex versionSet(R"re(^\s*set\(\s*APP_VERSION\s+"?([0-9.]+)"?\s*\))re"); + static const std::regex buildSet(R"re(^\s*set\(\s*APP_BUILD\s+"?([0-9]+)"?\s*\))re"); + std::string version, build; + std::smatch m; + if (!fs::exists(cmakeLists)) return { version, build }; + for (auto & line : fileToStrings(cmakeLists)) { + if (std::regex_search(line, m, versionSet)) version = m[1]; + if (std::regex_search(line, m, buildSet)) build = m[1]; + } + return { version, build }; +} + +// MARKETING_VERSION / CURRENT_PROJECT_VERSION in the .xcodeproj target settings +static std::pair readXcodeAppVersion(const fs::path & pbxproj) { + static const std::regex versionSet(R"re("?MARKETING_VERSION"?\s*[:=]\s*"?([0-9.]+)"?)re"); + static const std::regex buildSet(R"re("?CURRENT_PROJECT_VERSION"?\s*[:=]\s*"?([0-9]+)"?)re"); + std::string version, build; + std::smatch m; + if (!fs::exists(pbxproj)) return { version, build }; + for (auto & line : fileToStrings(pbxproj)) { + if (version.empty() && std::regex_search(line, m, versionSet)) version = m[1]; + if (build.empty() && std::regex_search(line, m, buildSet)) build = m[1]; + } + return { version, build }; +} + +// versionName / versionCode in the android ofApp/build.gradle +static std::pair readGradleAppVersion(const fs::path & gradle) { + static const std::regex versionSet(R"re(^\s*versionName\s+['"]([0-9.]+)['"])re"); + static const std::regex buildSet(R"re(^\s*versionCode\s+([0-9]+))re"); + std::string version, build; + std::smatch m; + if (!fs::exists(gradle)) return { version, build }; + for (auto & line : fileToStrings(gradle)) { + if (std::regex_search(line, m, versionSet)) version = m[1]; + if (std::regex_search(line, m, buildSet)) build = m[1]; + } + return { version, build }; +} + bool baseProject::create(const fs::path & _path, string templateName){ // alert("baseProject::create " + path.string() + " : " + templateName, 35); auto path = _path; // just because it is const @@ -192,6 +251,32 @@ bool baseProject::create(const fs::path & _path, string templateName){ if (!emscriptenWasm.empty()) { ofLogNotice() << "{ \"emscriptenWasm\": \"" << emscriptenWasm << "\" }"; } + + // app version / build: config.make first, then CMakeLists, Xcode, Android; the CLI value wins + { + std::vector>> found { + { "config.make", readMakeAppVersion(projectDir / "config.make") }, + { "CMakeLists.txt", readCMakeAppVersion(projectDir / "CMakeLists.txt") }, + { "xcodeproj", readXcodeAppVersion(projectDir / (projectName + ".xcodeproj") / "project.pbxproj") }, + { "build.gradle", readGradleAppVersion(projectDir / "ofApp" / "build.gradle") }, + }; + std::string version, build, versionFrom, buildFrom; + for (auto & f : found) { + if (version.empty() && !f.second.first.empty()) { version = f.second.first; versionFrom = f.first; } + if (build.empty() && !f.second.second.empty()) { build = f.second.second; buildFrom = f.first; } + } + for (auto & f : found) { + if (!f.second.first.empty() && f.second.first != version) { + ofLogWarning() << "app version " << f.second.first << " in " << f.first << " differs from " << version << " in " << versionFrom << " - using " << versionFrom; + } + if (!f.second.second.empty() && f.second.second != build) { + ofLogWarning() << "app build " << f.second.second << " in " << f.first << " differs from " << build << " in " << buildFrom << " - using " << buildFrom; + } + } + appVersion = !appVersionOption.empty() ? appVersionOption : (!version.empty() ? version : "1.0"); + appBuild = !appBuildOption.empty() ? appBuildOption : (!build.empty() ? build : "1"); + ofLogNotice() << "{ \"appVersion\": \"" << appVersion << "\", \"appBuild\": \"" << appBuild << "\" }"; + } if (emscriptenWasm == "64") { bool bHasWasm64Libs = false; if (fs::exists(getOFRoot() / "libs")) { @@ -338,6 +423,8 @@ bool baseProject::save(){ std::ofstream saveConfig(projectDir / "config.make"); bool bWroteDefines = false; bool bWroteWasm = false; + bool bWroteAppVersion = false; + bool bWroteAppBuild = false; for (auto & str : lines) { //add the of root path @@ -370,6 +457,19 @@ bool baseProject::save(){ bWroteWasm = true; } } + // app version / build (openFrameworks#8592): the defaults leave the section commented + else if( str.rfind("# APP_VERSION =", 0) == 0 || str.rfind("APP_VERSION =", 0) == 0 ){ + if (!bWroteAppVersion) { + saveConfig << (isDefaultAppVersion() ? "# " : "") << "APP_VERSION = " << appVersion << std::endl; + bWroteAppVersion = true; + } + } + else if( str.rfind("# APP_BUILD =", 0) == 0 || str.rfind("APP_BUILD =", 0) == 0 ){ + if (!bWroteAppBuild) { + saveConfig << (isDefaultAppVersion() ? "# " : "") << "APP_BUILD = " << appBuild << std::endl; + bWroteAppBuild = true; + } + } else if( emscriptenWasm == "32" && str.rfind("EMSCRIPTEN_MEMORY64", 0) == 0 ){ saveConfig << "# " << str << std::endl; } else { @@ -383,7 +483,14 @@ bool baseProject::save(){ saveConfig << "# emscripten: 32 (default) or 64 (wasm64, MEMORY64)" << std::endl; saveConfig << "PROJECT_EMSCRIPTEN_WASM = 64" << std::endl; } + if (!isDefaultAppVersion() && lines.size() && (!bWroteAppVersion || !bWroteAppBuild)) { + saveConfig << std::endl << "# app version and build number" << std::endl; + if (!bWroteAppVersion) saveConfig << "APP_VERSION = " << appVersion << std::endl; + if (!bWroteAppBuild) saveConfig << "APP_BUILD = " << appBuild << std::endl; + } + saveConfig.close(); saveCMakeLists(); + saveAppVersionFiles(); #ifdef OFADDON_OUTPUT_JSON_DEBUG saveAddonsToJson(); @@ -980,6 +1087,34 @@ bool baseProject::copyTemplateFile::run() { return true; } +// app version / build in Project.xcconfig (Xcode) and ofApp/build.gradle (Android) +static void replaceLines(const fs::path & file, const std::vector> & replacements) { + if (!fs::exists(file)) return; + auto lines = fileToStrings(file); + std::ofstream out(file); + for (auto & line : lines) { + // regex group 1 is kept, the rest of the match becomes the value ("$1" + "42" would read as group 14) + for (auto & r : replacements) { + std::smatch m; + if (std::regex_search(line, m, r.first)) { + line = m.prefix().str() + m[1].str() + r.second + m.suffix().str(); + } + } + out << line << std::endl; + } +} + +void baseProject::saveAppVersionFiles() { + replaceLines(projectDir / "Project.xcconfig", { + { std::regex(R"re(^(\s*MARKETING_VERSION\s*=\s*)[^\s/]+)re"), appVersion }, + { std::regex(R"re(^(\s*CURRENT_PROJECT_VERSION\s*=\s*)[^\s/]+)re"), appBuild }, + }); + replaceLines(projectDir / "ofApp" / "build.gradle", { + { std::regex(R"re(^(\s*versionCode\s+)[0-9]+)re"), appBuild }, + { std::regex(R"re(^(\s*versionName\s+)['"][^'"]*['"])re"), "'" + appVersion + "'" }, + }); +} + // OF_ROOT and PROJECT_EMSCRIPTEN_WASM in an openFrameworks app CMakeLists.txt, like config.make void baseProject::saveCMakeLists() { fs::path cmakeLists { projectDir / "CMakeLists.txt" }; @@ -995,9 +1130,27 @@ void baseProject::saveCMakeLists() { } static const std::regex ofRootSet(R"re(^(\s*)set\(OF_ROOT\s+"[^"]*"\))re"); static const std::regex wasmSet(R"re(^\s*set\(\s*PROJECT_EMSCRIPTEN_WASM\b)re"); + static const std::regex appVersionSet(R"re(^\s*#?\s*set\(\s*APP_(VERSION|BUILD)\b)re"); + static const std::regex addApp(R"re(^\s*of_add_app\s*\()re"); + string comment = isDefaultAppVersion() ? "# " : ""; + bool bWroteAppVersion = false; + auto writeAppVersion = [&](std::ofstream & o) { + if (bWroteAppVersion) return; + o << comment << "set(APP_VERSION " << appVersion << ")" << std::endl; + o << comment << "set(APP_BUILD " << appBuild << ")" << std::endl; + bWroteAppVersion = true; + }; std::ofstream out(cmakeLists); for (auto & line : lines) { + if (std::regex_search(line, appVersionSet)) { + writeAppVersion(out); + continue; + } + if (std::regex_search(line, addApp) && !bWroteAppVersion) { + if (!isDefaultAppVersion()) writeAppVersion(out); + bWroteAppVersion = true; + } if (!emscriptenWasm.empty() && std::regex_search(line, wasmSet)) continue; if (line.find(OF_CMAKE_INCLUDE) != std::string::npos && emscriptenWasm == "64") { out << "# emscripten: 32 (default) or 64 (wasm64, MEMORY64)" << std::endl; diff --git a/commandLine/src/projects/baseProject.h b/commandLine/src/projects/baseProject.h index fe28e1e2..baa4c2df 100644 --- a/commandLine/src/projects/baseProject.h +++ b/commandLine/src/projects/baseProject.h @@ -112,6 +112,7 @@ class baseProject { virtual void setMsys2Environment(const std::string & env) {} // emscripten wasm32 / wasm64 (-W/--emscripten-wasm), written to config.make PROJECT_EMSCRIPTEN_WASM; empty keeps the project's value void setEmscriptenWasm(const std::string & bits) { emscriptenWasmOption = bits; } + void setAppVersion(const std::string & version, const std::string & build) { appVersionOption = version; appBuildOption = build; } #ifdef OFADDON_OUTPUT_JSON_DEBUG @@ -186,6 +187,10 @@ class baseProject { std::string emscriptenWasmOption; // from the CLI, empty = keep std::string emscriptenWasm; // value for this project: "", "32" or "64" void saveCMakeLists(); + void saveAppVersionFiles(); + std::string appVersionOption, appBuildOption; // from the CLI, empty = keep + std::string appVersion { "1.0" }, appBuild { "1" }; // value for this project + bool isDefaultAppVersion() const { return appVersion == "1.0" && appBuild == "1"; } //cached addons - if an addon is requested more than once, avoid loading from disk as it's quite slow std::map> addonsCache; //indexed by [platform][supplied path] diff --git a/commandLine/src/projects/xcodeProject.cpp b/commandLine/src/projects/xcodeProject.cpp index 800eef20..2846f2cb 100644 --- a/commandLine/src/projects/xcodeProject.cpp +++ b/commandLine/src/projects/xcodeProject.cpp @@ -988,6 +988,23 @@ bool xcodeProject::saveProjectFile() { } } + // app version / build on every target configuration (target settings override Project.xcconfig) + try { + auto & objects = j["objects"]; + for (auto & target : objects) { + if (!target.is_object() || target.value("isa", "") != "PBXNativeTarget" || !target.contains("buildConfigurationList")) continue; + auto & configList = objects[target["buildConfigurationList"].get()]; + if (!configList.contains("buildConfigurations")) continue; + for (auto & configUUID : configList["buildConfigurations"]) { + auto & config = objects[configUUID.get()]; + config["buildSettings"]["MARKETING_VERSION"] = appVersion; + config["buildSettings"]["CURRENT_PROJECT_VERSION"] = appBuild; + } + } + } catch (std::exception & e) { + ofLogError(xcodeProject::LOG_NAME) << "app version: " << e.what(); + } + std::ofstream jsonFile(fileName); // This is not pretty but address some differences in nlohmann json 3.11.2 to 3.11.3 diff --git a/frontend/app.js b/frontend/app.js index b842085b..732ef2d8 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -210,9 +210,21 @@ ipcRenderer.on('setGenerateMode', (event, arg) => { if (arg === 'createMode') { $('#wasmDropdown').dropdown('set selected', '32'); $('#wasmNote').hide(); + $('#appVersion').val(''); + $('#appBuild').val(''); + $('#appVersionNote').hide(); } }); +//------------------------------------------- +// existing project's app version / build, so updating keeps them +ipcRenderer.on('setAppVersion', (event, { version, build, differs }) => { + $('#appVersion').val(version || ''); + $('#appBuild').val(build || ''); + const note = (differs || []).map((d) => t('note.appVersionDiffers', { file: d.file, value: d.value })).join(' '); + $('#appVersionNote').text(note).toggle(note.length > 0); +}); + //------------------------------------------- // existing project's PROJECT_EMSCRIPTEN_WASM, so updating keeps it ipcRenderer.on('setEmscriptenWasm', (event, bits) => { @@ -1240,7 +1252,9 @@ function generate() { defines: definesArr.join(','), postBuild: $('#postBuildScript').val().trim(), msys2Env: $('#msys2EnvField').css('display') !== 'none' ? $('#msys2EnvDropdown').dropdown('get value') : '', - emscriptenWasm: $('#wasmField').css('display') !== 'none' ? $('#wasmDropdown').dropdown('get value') : '' + emscriptenWasm: $('#wasmField').css('display') !== 'none' ? $('#wasmDropdown').dropdown('get value') : '', + appVersion: $('#appVersion').val().trim(), + appBuild: $('#appBuild').val().trim() }; // console.log(gen); @@ -1250,6 +1264,10 @@ function generate() { $("#projectPath").oneTimeTooltip(t('tooltip.projectPathEmpty')); } else if (gen.platformList == null || lengthOfPlatforms == 0) { $("#platformsDropdown").oneTimeTooltip(t('tooltip.selectPlatformFirst')); + } else if (gen.appVersion !== '' && !/^\d+(\.\d+)*$/.test(gen.appVersion)) { + $("#appVersion").oneTimeTooltip(t('tooltip.appVersionInvalid')); + } else if (gen.appBuild !== '' && !/^[1-9]\d*$/.test(gen.appBuild)) { + $("#appBuild").oneTimeTooltip(t('tooltip.appBuildInvalid')); } else { // the template pick is cleared in the #projectName change handler instead, // as soon as the user points at a different project - not here, so a plain diff --git a/frontend/index.html b/frontend/index.html index 1e80ea3d..f33d1fe4 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -291,6 +291,17 @@ +
+
+ + +
+
+ + +
+
+ -
+
- +
diff --git a/frontend/index.js b/frontend/index.js index 5183464f..d858179b 100644 --- a/frontend/index.js +++ b/frontend/index.js @@ -2448,6 +2448,20 @@ ipcMain.on('getOFPath', (event) => { }); }); +/** + * Settings label for the host platform the commandLine reports, eg osx -> "macOS (Apple) (arm64)". + * @param {string} hostPlatform - The commandLine's ofHostPlatform. + * @returns {string} The label to show. + */ +function hostPlatformLabel(hostPlatform) { + if (hostPlatform === 'osx') { + // Apple silicon even when this app runs under Rosetta + const arm = process.arch === 'arm64' || (os.cpus()[0] && /Apple/.test(os.cpus()[0].model)); + return `macOS (Apple) (${arm ? 'arm64' : 'x86_64'})`; + } + return hostPlatform; +} + ipcMain.on('getHostType', (event) => { execPG(['-i'], (error, stdout, stderr) => { if (error) { @@ -2465,7 +2479,7 @@ ipcMain.on('getHostType', (event) => { console.log(data); event.sender.send('ofPlatformResult', { success: true, - message: data.ofHostPlatform + message: hostPlatformLabel(data.ofHostPlatform) }); } else { throw new Error('No JSON output found'); diff --git a/frontend/locales/de.json b/frontend/locales/de.json index 9770d4c9..92f13ac3 100644 --- a/frontend/locales/de.json +++ b/frontend/locales/de.json @@ -45,6 +45,7 @@ "field.ofPath": "openFrameworks-Pfad:", "placeholder.ofPath": "openFrameworks-Pfad...", "field.platform": "Plattform:", + "field.hostPlatform": "Host-Plattform:", "field.cmdLineVersion": "commandLine PG-Version:", "field.guiVersion": "GUI-Version:", "field.language": "Sprache (Language):", diff --git a/frontend/locales/en.json b/frontend/locales/en.json index 14759758..ed9cb2f3 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -45,6 +45,7 @@ "field.ofPath": "openFrameworks path:", "placeholder.ofPath": "openFrameworks path...", "field.platform": "Platform:", + "field.hostPlatform": "Host Platform:", "field.cmdLineVersion": "commandLine PG Version:", "field.guiVersion": "GUI Version:", "field.language": "Language:", diff --git a/frontend/locales/es.json b/frontend/locales/es.json index 29b76a06..95034eef 100644 --- a/frontend/locales/es.json +++ b/frontend/locales/es.json @@ -45,6 +45,7 @@ "field.ofPath": "Ruta de openFrameworks:", "placeholder.ofPath": "Ruta de openFrameworks...", "field.platform": "Plataforma:", + "field.hostPlatform": "Plataforma anfitriona:", "field.cmdLineVersion": "Versión de PG (commandLine):", "field.guiVersion": "Versión de la interfaz:", "field.language": "Idioma (Language):", diff --git a/frontend/locales/fr.json b/frontend/locales/fr.json index 7ad0570e..e82ea6e2 100644 --- a/frontend/locales/fr.json +++ b/frontend/locales/fr.json @@ -45,6 +45,7 @@ "field.ofPath": "Chemin openFrameworks :", "placeholder.ofPath": "Chemin openFrameworks...", "field.platform": "Plateforme :", + "field.hostPlatform": "Plateforme hôte :", "field.cmdLineVersion": "Version PG (commandLine) :", "field.guiVersion": "Version de l'interface :", "field.language": "Langue (Language) :", diff --git a/frontend/locales/ja.json b/frontend/locales/ja.json index 976dbeb6..017e37f9 100644 --- a/frontend/locales/ja.json +++ b/frontend/locales/ja.json @@ -45,6 +45,7 @@ "field.ofPath": "openFrameworksパス:", "placeholder.ofPath": "openFrameworksパス...", "field.platform": "プラットフォーム:", + "field.hostPlatform": "ホストプラットフォーム:", "field.cmdLineVersion": "commandLine PGバージョン:", "field.guiVersion": "GUIバージョン:", "field.language": "言語 (Language):", diff --git a/frontend/locales/ko.json b/frontend/locales/ko.json index e72120a2..76fd653a 100644 --- a/frontend/locales/ko.json +++ b/frontend/locales/ko.json @@ -45,6 +45,7 @@ "field.ofPath": "openFrameworks 경로:", "placeholder.ofPath": "openFrameworks 경로...", "field.platform": "플랫폼:", + "field.hostPlatform": "호스트 플랫폼:", "field.cmdLineVersion": "commandLine PG 버전:", "field.guiVersion": "GUI 버전:", "field.language": "언어 (Language):", diff --git a/frontend/locales/pt.json b/frontend/locales/pt.json index e02949c9..bbab1632 100644 --- a/frontend/locales/pt.json +++ b/frontend/locales/pt.json @@ -45,6 +45,7 @@ "field.ofPath": "Caminho do openFrameworks:", "placeholder.ofPath": "Caminho do openFrameworks...", "field.platform": "Plataforma:", + "field.hostPlatform": "Plataforma host:", "field.cmdLineVersion": "Versão do PG (commandLine):", "field.guiVersion": "Versão da interface:", "field.language": "Idioma (Language):", diff --git a/frontend/locales/ru.json b/frontend/locales/ru.json index a506f4b5..0ef62a1c 100644 --- a/frontend/locales/ru.json +++ b/frontend/locales/ru.json @@ -45,6 +45,7 @@ "field.ofPath": "Путь к openFrameworks:", "placeholder.ofPath": "Путь к openFrameworks...", "field.platform": "Платформа:", + "field.hostPlatform": "Платформа хоста:", "field.cmdLineVersion": "Версия PG (commandLine):", "field.guiVersion": "Версия интерфейса:", "field.language": "Язык (Language):", diff --git a/frontend/locales/zh-CN.json b/frontend/locales/zh-CN.json index 8e085a70..6c6d2f3a 100644 --- a/frontend/locales/zh-CN.json +++ b/frontend/locales/zh-CN.json @@ -45,6 +45,7 @@ "field.ofPath": "openFrameworks 路径:", "placeholder.ofPath": "openFrameworks 路径...", "field.platform": "平台:", + "field.hostPlatform": "主机平台:", "field.cmdLineVersion": "commandLine PG 版本:", "field.guiVersion": "GUI 版本:", "field.language": "语言(Language):", diff --git a/frontend/static/css/index.css b/frontend/static/css/index.css index 8f26bfb5..6c8763ff 100644 --- a/frontend/static/css/index.css +++ b/frontend/static/css/index.css @@ -177,3 +177,18 @@ body.showConsole #consoleContainer { padding: 0 0.5em !important; margin: 0 !important; } + +/* platform pickers: less room reserved for the dropdown arrow, and the empty search box takes no + space until it's focused, so a long label like "macOS (including iOS, tvOS, catOS, macOS) (Xcode) (Apple)" + stays on one line instead of the search box wrapping onto a second */ +.ui.form #platformsDropdown.ui.multiple.dropdown, +.ui.form #platformsDropdownMulti.ui.multiple.dropdown { + padding-right: 28px; +} +.ui.form #platformsDropdown.ui.multiple.search.dropdown > input.search:not(:focus), +.ui.form #platformsDropdownMulti.ui.multiple.search.dropdown > input.search:not(:focus) { + position: absolute; + width: 1px !important; + margin: 0; + padding: 0; +}