Language System Basis (#6172)

Introduces the basis for a language system to allow the UI to be translated to any language and/or have the text changed by mods.
A lot of things would require more work but, for a proof of concept, this PR makes all randomizer trick names & descriptions translatable (currently not re-loadable at runtime as that would require deeper changes and this is already merge conflict hell every time a trick is touched).

The system works by passing it a "translation path" which is resolved in the .json including object indentation. If the resulting json object is a list of strings, instead of a string, they get concatenated (purely for organization/QoL).
This commit is contained in:
Pepe20129 2026-03-29 04:27:30 +02:00 committed by GitHub
parent 3228843886
commit e0a1b23525
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 1525 additions and 910 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,143 @@
#include "Lang.h"
#include "soh/SohGui/MenuTypes.h"
#include "soh/SohGui/SohGui.hpp"
#include "soh/SohGui/SohMenu.h"
#include "soh/util.h"
#include "ship/Context.h"
#include "ship/resource/File.h"
#include "ship/resource/ResourceManager.h"
#include "ship/resource/type/Json.h"
#include "ship/utils/StringHelper.h"
#include "spdlog/spdlog.h"
#include <memory>
namespace SohGui {
extern std::shared_ptr<SohMenu> mSohMenu;
}
static bool initialized = false;
static std::map<std::string, nlohmann::json> langs;
#define LANGUAGE_CVAR CVAR_SETTING("Language")
#define DEFAULT_LANGUAGE "en_US"
std::string Lang::Translate(const char* path) {
if (!initialized) {
SPDLOG_ERROR("Tried to obtain a translation before the translation data is initialized");
assert(false);
return "ERROR: Language data not initialized yet";
}
std::string currentLang = CVarGetString(LANGUAGE_CVAR, DEFAULT_LANGUAGE);
if (!langs.contains(currentLang)) {
SPDLOG_WARN("Current language ({}) doesn't exist, trying to fall back to default language ({})",
currentLang.c_str(), DEFAULT_LANGUAGE);
currentLang = DEFAULT_LANGUAGE;
if (!langs.contains(currentLang)) {
SPDLOG_ERROR("Default language ({}) doesn't exist", DEFAULT_LANGUAGE);
assert(false);
return "ERROR: Language data not found";
}
CVarSetString(LANGUAGE_CVAR, DEFAULT_LANGUAGE);
SPDLOG_WARN("Fallback to default language ({}) was succesful", DEFAULT_LANGUAGE);
}
nlohmann::json currentLangData = langs[currentLang];
std::vector<std::string> segments = SohUtils::StringSplit(std::string(path), ".");
std::string lastSegment = segments[segments.size() - 1];
segments.pop_back();
for (const auto& segment : segments) {
if (!currentLangData.contains(segment)) {
SPDLOG_WARN("Current language ({}) doesn't have data for the requested path ({})", currentLang.c_str(),
path);
return std::string(path);
}
currentLangData = currentLangData[segment];
}
if (!currentLangData.contains(lastSegment)) {
SPDLOG_WARN("Current language ({}) doesn't have data for the requested path ({})", currentLang.c_str(), path);
return std::string(path);
}
if (currentLangData[lastSegment].is_string()) {
return currentLangData[lastSegment].get<std::string>();
}
if (currentLangData[lastSegment].is_array()) {
std::string translatedString = "";
for (const auto& item : currentLangData[lastSegment]) {
if (!item.is_string()) {
SPDLOG_WARN("Current language ({}) has an array with a non-string at the requested path ({})",
currentLang.c_str(), path);
return std::string(path);
}
translatedString += item.get<std::string>();
}
return translatedString;
}
SPDLOG_WARN("Current language ({}) doesn't have either a string or an array at the requested path ({})",
currentLang.c_str(), path);
return std::string(path);
}
void Lang::LoadLangs() {
auto initData = std::make_shared<Ship::ResourceInitData>();
initData->Format = RESOURCE_FORMAT_BINARY;
initData->Type = static_cast<uint32_t>(Ship::ResourceType::Json);
initData->ResourceVersion = 0;
const static std::string folder = "lang/*";
auto langFiles = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->ListFiles(folder);
size_t start = std::string(folder).size() - 1;
for (size_t i = 0; i < langFiles->size(); i++) {
std::string filePath = langFiles->at(i);
auto json = std::static_pointer_cast<Ship::Json>(
Ship::Context::GetInstance()->GetResourceManager()->LoadResource(filePath, true, initData));
std::string fileName = filePath.substr(start, filePath.size() - start - 5); // 5 for length of ".json"
langs.insert_or_assign(fileName, json->Data);
}
initialized = true;
}
void LanguageCustomWidget(WidgetInfo& info) {
ImGui::Text("Select Language:");
for (const auto& [id, data] : langs) {
if (ImGui::Button(StringHelper::Sprintf("%s [%s]", data["language_name"].get_ref<const std::string&>().c_str(),
id.c_str())
.c_str())) {
CVarSetString(LANGUAGE_CVAR, id.c_str());
}
}
}
void RegisterLangWidgets() {
return;
// TODO: Improve & enable this when everything is set up
SohGui::mSohMenu->AddSidebarEntry("Settings", "Language", 1);
WidgetPath path = { "Settings", "Language", SECTION_COLUMN_1 };
SohGui::mSohMenu->AddWidget(path, "LanguageWidget", WIDGET_CUSTOM)
.CustomFunction(LanguageCustomWidget)
.HideInSearch(true);
}
static RegisterMenuInitFunc menuInitFunc(RegisterLangWidgets);

View file

@ -0,0 +1,8 @@
#pragma once
#include <string>
namespace Lang {
std::string Translate(const char* path);
void LoadLangs();
} // namespace Lang

View file

@ -6,6 +6,7 @@
#include "soh/SohGui/SohGui.hpp"
#include "soh/SohGui/SohMenu.h"
#include "soh/SohGui/UIWidgets.hpp"
#include "soh/Enhancements/Lang/Lang.h"
#include <soh/cvar_prefixes.h>
namespace SohGui {
@ -357,18 +358,42 @@ RandomizerCheck LocationOption::GetKey() const {
return static_cast<RandomizerCheck>(key);
}
#define RANDO_ENUM_ITEM(enum) { enum, #enum },
std::unordered_map<RandomizerTrick, std::string> trickNames = {
#include "randomizerEnums/RandomizerTrick.h"
};
#undef RANDO_ENUM_ITEM
const static std::string trickPrefix = "randomizer.tricks.";
static std::string MakeTrickName(RandomizerTrick key) {
const static std::string namePostfix = ".name";
std::string trickNamePart = trickNames[key].substr(3);
std::transform(trickNamePart.begin(), trickNamePart.end(), trickNamePart.begin(), ::tolower);
return Lang::Translate((trickPrefix + trickNamePart + namePostfix).c_str());
}
static std::string MakeTrickDescription(RandomizerTrick key) {
const static std::string descriptionPostfix = ".description";
std::string trickNamePart = trickNames[key].substr(3);
std::transform(trickNamePart.begin(), trickNamePart.end(), trickNamePart.begin(), ::tolower);
return Lang::Translate((trickPrefix + trickNamePart + descriptionPostfix).c_str());
}
TrickSetting::TrickSetting(RandomizerTrick key_, const RandomizerCheckQuest quest_, const RandomizerArea area_,
std::set<Tricks::Tag> tags_, const std::string& name_, const std::string nameTag_,
std::string description_)
: Option(key_, name_, { "Disabled", "Enabled" }, OptionCategory::Setting, "", std::move(description_),
WIDGET_CVAR_CHECKBOX, 0, false, nullptr, IMFLAG_NONE),
std::set<Tricks::Tag> tags_, const std::string nameTag_)
: Option(key_, std::move(MakeTrickName(key_)), { "Disabled", "Enabled" }, OptionCategory::Setting, "",
std::move(MakeTrickDescription(key_)), WIDGET_CVAR_CHECKBOX, 0, false, nullptr, IMFLAG_NONE),
mQuest(quest_), mArea(area_), mNameTag(nameTag_), mTags(std::move(tags_)) {
}
TrickSetting TrickSetting::LogicTrick(RandomizerTrick key_, RandomizerCheckQuest quest_, RandomizerArea area_,
std::set<Tricks::Tag> tags_, const std::string& name_, const std::string nameTag_,
std::string description_) {
return { key_, quest_, area_, std::move(tags_), name_, nameTag_, std::move(description_) };
std::set<Tricks::Tag> tags_, const std::string nameTag_) {
return { key_, quest_, area_, std::move(tags_), nameTag_ };
}
RandomizerTrick TrickSetting::GetKey() const {

View file

@ -356,14 +356,11 @@ class TrickSetting : public Option {
* @param quest_ MQ, Vanilla, or Both.
* @param area_ The area the trick is relevant for.
* @param tags_ The set of RandomizerTrickTags for this trick.
* @param name_ The name of the trick. Appears in the menus and spoiler
* @param nameTag_ The 3-8 long name tag of the trick. Appears in the settings and presets file.
* @param description_ A brief description of the trick.
* @param nameTag_ The 3-8 character long name tag of the trick. Appears in the settings and presets file.
* @return Option
*/
static TrickSetting LogicTrick(RandomizerTrick key_, RandomizerCheckQuest quest_, RandomizerArea area_,
std::set<Tricks::Tag> tags_, const std::string& name_, const std::string nameTag_,
std::string description_);
std::set<Tricks::Tag> tags_, const std::string nameTag_);
RandomizerTrick GetKey() const;
@ -400,7 +397,7 @@ class TrickSetting : public Option {
private:
TrickSetting(RandomizerTrick key_, RandomizerCheckQuest quest_, RandomizerArea area_, std::set<Tricks::Tag> tags_,
const std::string& name_, const std::string nameTag_, std::string description_);
const std::string nameTag_);
RandomizerCheckQuest mQuest;
RandomizerArea mArea;
std::string mNameTag;

File diff suppressed because it is too large Load diff

View file

@ -65,6 +65,8 @@
#include <functions.h>
#include "Enhancements/item-tables/ItemTableManager.h"
#include "Enhancements/Lang/Lang.h"
#include "soh/SohGui/SohGui.hpp"
#include "soh/SohGui/ImGuiUtils.h"
#include "ActorDB.h"
#include "SaveManager.h"
@ -886,6 +888,8 @@ void OTRGlobals::Initialize() {
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryBinaryBackgroundV0>(), RESOURCE_FORMAT_BINARY,
"Background", static_cast<uint32_t>(SOH::ResourceType::SOH_Background), 0);
Lang::LoadLangs();
gSaveStateMgr = std::make_shared<SaveStateMgr>();
gRandoContext->InitStaticData();
gRandoContext = Rando::Context::CreateInstance();

View file

@ -208,8 +208,7 @@ uint32_t Menu::DrawSearchResults(std::string& menuSearchText) {
info.type == WIDGET_SEPARATOR_TEXT || info.isHidden || info.hideInSearch) {
continue;
}
const char* tooltip = info.options->tooltip;
std::string widgetStr = std::string(info.name) + std::string(tooltip != NULL ? tooltip : "");
std::string widgetStr = std::string(info.name) + info.options->tooltip;
std::transform(widgetStr.begin(), widgetStr.end(), widgetStr.begin(), ::tolower);
widgetStr.erase(std::remove(widgetStr.begin(), widgetStr.end(), ' '), widgetStr.end());
if (widgetStr.find(menuSearchText) != std::string::npos) {

View file

@ -59,6 +59,12 @@ void Tooltip(const char* text) {
}
}
void Tooltip(std::string text) {
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", WrappedText(text).c_str());
}
}
void PushStyleMenu(const ImVec4& color) {
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(color.x, color.y, color.z, 0.5f));
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(color.x, color.y, color.z, 1.0f));
@ -174,9 +180,9 @@ bool Button(const char* label, const ButtonOptions& options) {
PopStyleButton();
ImGui::EndDisabled();
if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) &&
!Ship_IsCStringEmpty(options.disabledTooltip)) {
!options.disabledTooltip.empty()) {
ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str());
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) {
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !options.tooltip.empty()) {
ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str());
}
return dirty;
@ -365,9 +371,9 @@ bool Checkbox(const char* _label, bool* value, const CheckboxOptions& options) {
PopStyleCheckbox();
ImGui::EndDisabled();
if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) &&
!Ship_IsCStringEmpty(options.disabledTooltip)) {
!options.disabledTooltip.empty()) {
ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str());
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) {
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !options.tooltip.empty()) {
ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str());
}
return pressed;
@ -595,9 +601,9 @@ bool SliderInt(const char* label, int32_t* value, const IntSliderOptions& option
ImGui::EndDisabled();
ImGui::EndGroup();
if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) &&
!Ship_IsCStringEmpty(options.disabledTooltip)) {
!options.disabledTooltip.empty()) {
ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str());
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) {
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !options.tooltip.empty()) {
ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str());
}
ImGui::PopID();
@ -726,9 +732,9 @@ bool SliderFloat(const char* label, float* value, const FloatSliderOptions& opti
ImGui::EndDisabled();
ImGui::EndGroup();
if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) &&
!Ship_IsCStringEmpty(options.disabledTooltip)) {
!options.disabledTooltip.empty()) {
ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str());
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) {
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !options.tooltip.empty()) {
ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str());
}
ImGui::PopID();
@ -799,13 +805,12 @@ bool InputString(const char* label, std::string* value, const InputOptions& opti
PopStyleInput();
ImGui::EndDisabled();
ImGui::EndGroup();
if (options.hasError && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) &&
!Ship_IsCStringEmpty(options.errorText)) {
if (options.hasError && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !options.errorText.empty()) {
ImGui::SetTooltip("%s", WrappedText(options.errorText).c_str());
} else if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) &&
!Ship_IsCStringEmpty(options.disabledTooltip)) {
!options.disabledTooltip.empty()) {
ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str());
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) {
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !options.tooltip.empty()) {
ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str());
}
ImGui::PopID();
@ -854,9 +859,9 @@ bool InputInt(const char* label, int32_t* value, const InputOptions& options) {
ImGui::EndDisabled();
ImGui::EndGroup();
if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) &&
!Ship_IsCStringEmpty(options.disabledTooltip)) {
!options.disabledTooltip.empty()) {
ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str());
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) {
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !options.tooltip.empty()) {
ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str());
}
ImGui::PopID();
@ -1040,7 +1045,7 @@ bool CVarRadioButton(const char* text, const char* cvarName, int32_t id, const R
ImGui::SameLine();
ImGui::Text("%s", text);
PopStyleCheckbox();
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) {
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !options.tooltip.empty()) {
ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str());
}

View file

@ -36,7 +36,7 @@ std::string WrappedText(const char* text, unsigned int charactersPerLine = 80);
std::string WrappedText(const std::string& text, unsigned int charactersPerLine = 80);
void PaddedSeparator(bool padTop = true, bool padBottom = true, float extraVerticalTopPadding = 0.0f,
float extraVerticalBottomPadding = 0.0f);
void Tooltip(const char* text);
void Tooltip(std::string text);
typedef enum ColorPickerModifiers {
ColorPickerResetButton = 1,
@ -106,11 +106,11 @@ enum ComponentAlignments {
};
struct WidgetOptions {
const char* tooltip = "";
std::string tooltip = "";
bool disabled = false;
const char* disabledTooltip = "";
std::string disabledTooltip = "";
WidgetOptions& Tooltip(const char* tooltip_) {
WidgetOptions& Tooltip(std::string tooltip_) {
tooltip = tooltip_;
return *this;
}
@ -120,7 +120,7 @@ struct WidgetOptions {
return *this;
}
WidgetOptions& DisabledTooltip(const char* disabledTooltip_) {
WidgetOptions& DisabledTooltip(std::string disabledTooltip_) {
disabledTooltip = disabledTooltip_;
return *this;
}
@ -150,7 +150,7 @@ struct ButtonOptions : WidgetOptions {
return *this;
}
ButtonOptions& Tooltip(const char* tooltip_) {
ButtonOptions& Tooltip(std::string tooltip_) {
WidgetOptions::tooltip = tooltip_;
return *this;
}
@ -182,7 +182,7 @@ struct ColorPickerOptions : WidgetOptions {
return *this;
}
ColorPickerOptions& Tooltip(const char* tooltip_) {
ColorPickerOptions& Tooltip(std::string tooltip_) {
WidgetOptions::tooltip = tooltip_;
return *this;
}
@ -240,7 +240,7 @@ struct WindowButtonOptions : WidgetOptions {
return *this;
}
WindowButtonOptions& Tooltip(const char* tooltip_) {
WindowButtonOptions& Tooltip(std::string tooltip_) {
WidgetOptions::tooltip = tooltip_;
return *this;
}
@ -283,7 +283,7 @@ struct CheckboxOptions : WidgetOptions {
return *this;
}
CheckboxOptions& Tooltip(const char* tooltip_) {
CheckboxOptions& Tooltip(std::string tooltip_) {
WidgetOptions::tooltip = tooltip_;
return *this;
}
@ -293,7 +293,7 @@ struct CheckboxOptions : WidgetOptions {
return *this;
}
CheckboxOptions& DisabledTooltip(const char* disabledTooltip_) {
CheckboxOptions& DisabledTooltip(std::string disabledTooltip_) {
WidgetOptions::disabledTooltip = disabledTooltip_;
return *this;
}
@ -332,7 +332,7 @@ struct ComboboxOptions : WidgetOptions {
return *this;
}
ComboboxOptions& Tooltip(const char* tooltip_) {
ComboboxOptions& Tooltip(std::string tooltip_) {
WidgetOptions::tooltip = tooltip_;
return *this;
}
@ -397,7 +397,7 @@ struct IntSliderOptions : WidgetOptions {
return *this;
}
IntSliderOptions& Tooltip(const char* tooltip_) {
IntSliderOptions& Tooltip(std::string tooltip_) {
WidgetOptions::tooltip = tooltip_;
return *this;
}
@ -481,7 +481,7 @@ struct FloatSliderOptions : WidgetOptions {
return *this;
}
FloatSliderOptions& Tooltip(const char* tooltip_) {
FloatSliderOptions& Tooltip(std::string tooltip_) {
WidgetOptions::tooltip = tooltip_;
return *this;
}
@ -544,7 +544,7 @@ struct RadioButtonsOptions : WidgetOptions {
return *this;
}
RadioButtonsOptions& Tooltip(const char* tooltip_) {
RadioButtonsOptions& Tooltip(std::string tooltip_) {
WidgetOptions::tooltip = tooltip_;
return *this;
}
@ -571,9 +571,9 @@ struct InputOptions : WidgetOptions {
bool secret = false;
ImGuiInputFlags addedFlags = 0;
bool hasError = false;
const char* errorText = "";
std::string errorText = "";
InputOptions& Tooltip(const char* tooltip_) {
InputOptions& Tooltip(std::string tooltip_) {
WidgetOptions::tooltip = tooltip_;
return *this;
}
@ -628,7 +628,7 @@ struct InputOptions : WidgetOptions {
return *this;
}
InputOptions& ErrorText(const char* errorText_) {
InputOptions& ErrorText(std::string errorText_) {
errorText = errorText_;
return *this;
}
@ -754,9 +754,9 @@ bool Combobox(std::string label, T* value, const std::map<T, const char*>& combo
ImGui::EndDisabled();
ImGui::EndGroup();
if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) &&
!Ship_IsCStringEmpty(options.disabledTooltip)) {
!options.disabledTooltip.empty()) {
ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str());
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) {
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !options.tooltip.empty()) {
ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str());
}
ImGui::PopID();
@ -839,9 +839,9 @@ bool Combobox(std::string label, T* value, const std::vector<const char*>& combo
ImGui::EndDisabled();
ImGui::EndGroup();
if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) &&
!Ship_IsCStringEmpty(options.disabledTooltip)) {
!options.disabledTooltip.empty()) {
ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str());
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) {
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !options.tooltip.empty()) {
ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str());
}
ImGui::PopID();
@ -925,9 +925,9 @@ bool Combobox(std::string label, T* value, const std::vector<std::string>& combo
ImGui::EndDisabled();
ImGui::EndGroup();
if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) &&
!Ship_IsCStringEmpty(options.disabledTooltip)) {
!options.disabledTooltip.empty()) {
ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str());
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) {
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !options.tooltip.empty()) {
ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str());
}
ImGui::PopID();
@ -1011,9 +1011,9 @@ bool Combobox(std::string label, T* value, const char* (&comboArray)[N], const C
ImGui::EndDisabled();
ImGui::EndGroup();
if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) &&
!Ship_IsCStringEmpty(options.disabledTooltip)) {
!options.disabledTooltip.empty()) {
ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str());
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) {
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !options.tooltip.empty()) {
ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str());
}
ImGui::PopID();

View file

@ -796,3 +796,20 @@ uint32_t SohUtils::Hash(std::string str) {
}
return hval;
}
std::vector<std::string> SohUtils::StringSplit(const std::string& str, const std::string& delimiter) {
std::vector<std::string> tokens;
size_t pos = str.find(delimiter, 0);
size_t prevpos = 0;
while (pos != std::string::npos) {
std::string token = str.substr(prevpos, pos - prevpos);
tokens.push_back(token);
prevpos = pos + 1;
pos = str.find(delimiter, prevpos);
}
tokens.push_back(str.substr(prevpos));
return tokens;
}

View file

@ -1,5 +1,6 @@
#pragma once
#include <string>
#include <vector>
#include <stdint.h>
typedef enum FileType { FILE_TYPE_SAVE_VANILLA, FILE_TYPE_SAVE_RANDO, FILE_TYPE_PRESET, FILE_TYPE_SPOILER } FileType;
@ -25,4 +26,6 @@ size_t CopyStringToCharBuffer(char* buffer, const std::string& source, size_t ma
bool IsStringEmpty(std::string str);
uint32_t Hash(std::string str);
std::vector<std::string> StringSplit(const std::string& str, const std::string& delimiter);
} // namespace SohUtils