From 8eb63049992f9dda531a0bc236161cbd1dae2821 Mon Sep 17 00:00:00 2001 From: Pepe20129 <72659707+Pepe20129@users.noreply.github.com> Date: Wed, 17 Jul 2024 05:25:06 +0200 Subject: [PATCH] Rando: Keyring tristate (#3960) * Initial implementation * Fix build * Update UIWidgets.cpp * Update option.cpp * Update settings.cpp * Update settings.cpp * Apply Pepper0ni's patch Co-Authored-By: Pepper0ni <93387759+Pepper0ni@users.noreply.github.com> * Update option_descriptions.cpp --------- Co-authored-by: Pepper0ni <93387759+Pepper0ni@users.noreply.github.com> --- soh/soh/Enhancements/randomizer/option.cpp | 23 +++++ soh/soh/Enhancements/randomizer/option.h | 2 + .../randomizer/option_descriptions.cpp | 3 +- .../Enhancements/randomizer/randomizerTypes.h | 6 ++ soh/soh/Enhancements/randomizer/settings.cpp | 93 +++++++++++-------- soh/soh/UIWidgets.cpp | 55 ++++++++++- soh/soh/UIWidgets.hpp | 4 +- 7 files changed, 141 insertions(+), 45 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/option.cpp b/soh/soh/Enhancements/randomizer/option.cpp index acccb9c5b..36fdd1f8b 100644 --- a/soh/soh/Enhancements/randomizer/option.cpp +++ b/soh/soh/Enhancements/randomizer/option.cpp @@ -140,6 +140,9 @@ bool Option::RenderImGui() const { case WidgetType::Checkbox: changed = RenderCheckbox(); break; + case WidgetType::TristateCheckbox: + changed = RenderTristateCheckbox(); + break; case WidgetType::Combobox: changed = RenderCombobox(); break; @@ -209,6 +212,26 @@ bool Option::RenderCheckbox() const { return changed; } +bool Option::RenderTristateCheckbox() const { + bool changed = false; + if (disabled) { + UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); + } + int val = CVarGetInteger(cvarName.c_str(), defaultOption); + if (CustomCheckboxTristate(name.c_str(), &val, disabled, disabledGraphic)) { + CVarSetInteger(cvarName.c_str(), val); + changed = true; + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + } + if (!description.empty()) { + UIWidgets::InsertHelpHoverText(description.c_str()); + } + if (disabled) { + UIWidgets::ReEnableComponent(disabledText.c_str()); + } + return changed; +} + bool Option::RenderCombobox() const { bool changed = false; if (disabled) { diff --git a/soh/soh/Enhancements/randomizer/option.h b/soh/soh/Enhancements/randomizer/option.h index 8ec4a32b1..57b3dae2c 100644 --- a/soh/soh/Enhancements/randomizer/option.h +++ b/soh/soh/Enhancements/randomizer/option.h @@ -34,6 +34,7 @@ enum class OptionCategory { */ enum class WidgetType { Checkbox, /** Default for Bools, not compatible if options.size() > 2. */ + TristateCheckbox, /** Compatible with U8s, not compatible if options.size() != 3. */ Combobox, /** Default for U8s, works with U8s and Bools. */ Slider, /** Compatible with U8s. If constructed with NumOpts, consider using this. Technically can be used for Bool or non-NumOpts options but it would be a bit weird semantically. */ }; @@ -317,6 +318,7 @@ protected: private: bool RenderCheckbox() const; + bool RenderTristateCheckbox() const; bool RenderCombobox() const; bool RenderSlider() const; std::variant var; diff --git a/soh/soh/Enhancements/randomizer/option_descriptions.cpp b/soh/soh/Enhancements/randomizer/option_descriptions.cpp index 522007403..784d9d1f3 100644 --- a/soh/soh/Enhancements/randomizer/option_descriptions.cpp +++ b/soh/soh/Enhancements/randomizer/option_descriptions.cpp @@ -370,7 +370,8 @@ void Settings::CreateOptionDescriptions() { "\n" "Count - A specified amount of randomly selected dungeons will have their keys replaced with keyrings.\n" "\n" - "Selection - Hand select which dungeons will have their keys replaced with keyrings.\n" + "Selection - Hand select which dungeons will have their keys replaced with keyrings\n" + "(can also be left as random, in which case each one will have a 50% chance of being a keyring).\n" "\n" "Selecting key ring for dungeons will have no effect if Small Keys are set to Start With or Vanilla.\n" "\n" diff --git a/soh/soh/Enhancements/randomizer/randomizerTypes.h b/soh/soh/Enhancements/randomizer/randomizerTypes.h index 43fddacea..80d62859c 100644 --- a/soh/soh/Enhancements/randomizer/randomizerTypes.h +++ b/soh/soh/Enhancements/randomizer/randomizerTypes.h @@ -3876,6 +3876,12 @@ typedef enum { RO_KEYRINGS_SELECTION, } RandoOptionKeyrings; +typedef enum { + RO_KEYRING_FOR_DUNGEON_OFF, + RO_KEYRING_FOR_DUNGEON_RANDOM, + RO_KEYRING_FOR_DUNGEON_ON, +} RandoOptionKeyringForDungeon; + //Ganon's Boss Key Settings (vanilla, own dungeon, start with, //overworld, anywhere, 100 GS reward) typedef enum { diff --git a/soh/soh/Enhancements/randomizer/settings.cpp b/soh/soh/Enhancements/randomizer/settings.cpp index 0d702e480..883a7c208 100644 --- a/soh/soh/Enhancements/randomizer/settings.cpp +++ b/soh/soh/Enhancements/randomizer/settings.cpp @@ -136,15 +136,15 @@ void Settings::CreateOptions() { mOptions[RSK_LACS_OPTIONS] = Option::U8("LACS Reward Options", {"Standard Reward", "Greg as Reward", "Greg as Wildcard"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("LacsRewardOptions"), "", WidgetType::Combobox, RO_LACS_STANDARD_REWARD); mOptions[RSK_KEYRINGS] = Option::U8("Key Rings", {"Off", "Random", "Count", "Selection"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRings"), mOptionDescriptions[RSK_KEYRINGS], WidgetType::Combobox, RO_KEYRINGS_OFF); mOptions[RSK_KEYRINGS_RANDOM_COUNT] = Option::U8("Keyring Dungeon Count", {NumOpts(0, 9)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsRandomCount"), "", WidgetType::Slider, 8); - mOptions[RSK_KEYRINGS_GERUDO_FORTRESS] = Option::Bool("Gerudo Fortress", CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsGerudoFortress"), "", IMFLAG_NONE); - mOptions[RSK_KEYRINGS_FOREST_TEMPLE] = Option::Bool("Forest Temple", CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsForestTemple"), "", IMFLAG_NONE); - mOptions[RSK_KEYRINGS_FIRE_TEMPLE] = Option::Bool("Fire Temple", CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsFireTemple"), "", IMFLAG_NONE); - mOptions[RSK_KEYRINGS_WATER_TEMPLE] = Option::Bool("Water Temple", CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsWaterTemple"), "", IMFLAG_NONE); - mOptions[RSK_KEYRINGS_SPIRIT_TEMPLE] = Option::Bool("Spirit Temple", CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsSpiritTemple"), "", IMFLAG_NONE); - mOptions[RSK_KEYRINGS_SHADOW_TEMPLE] = Option::Bool("Shadow Temple", CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsShadowTemple"), "", IMFLAG_NONE); - mOptions[RSK_KEYRINGS_BOTTOM_OF_THE_WELL] = Option::Bool("Bottom of the Well", CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsBottomOfTheWell"), "", IMFLAG_NONE); - mOptions[RSK_KEYRINGS_GTG] = Option::Bool("Gerudo Training Grounds", CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsGTG"), "", IMFLAG_NONE); - mOptions[RSK_KEYRINGS_GANONS_CASTLE] = Option::Bool("Ganon's Castle", CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsGanonsCastle")); + mOptions[RSK_KEYRINGS_GERUDO_FORTRESS] = Option::U8("Gerudo Fortress Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsGerudoFortress"), "", WidgetType::TristateCheckbox, 0); + mOptions[RSK_KEYRINGS_FOREST_TEMPLE] = Option::U8("Forest Temple Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsForestTemple"), "", WidgetType::TristateCheckbox, 0); + mOptions[RSK_KEYRINGS_FIRE_TEMPLE] = Option::U8("Fire Temple Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsFireTemple"), "", WidgetType::TristateCheckbox, 0); + mOptions[RSK_KEYRINGS_WATER_TEMPLE] = Option::U8("Water Temple Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsWaterTemple"), "", WidgetType::TristateCheckbox, 0); + mOptions[RSK_KEYRINGS_SPIRIT_TEMPLE] = Option::U8("Spirit Temple Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsSpiritTemple"), "", WidgetType::TristateCheckbox, 0); + mOptions[RSK_KEYRINGS_SHADOW_TEMPLE] = Option::U8("Shadow Temple Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsShadowTemple"), "", WidgetType::TristateCheckbox, 0); + mOptions[RSK_KEYRINGS_BOTTOM_OF_THE_WELL] = Option::U8("Bottom of the Well Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsBottomOfTheWell"), "", WidgetType::TristateCheckbox, 0); + mOptions[RSK_KEYRINGS_GTG] = Option::U8("Gerudo Training Grounds Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsGTG"), "", WidgetType::TristateCheckbox, 0); + mOptions[RSK_KEYRINGS_GANONS_CASTLE] = Option::U8("Ganon's Castle Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsGanonsCastle"), "", WidgetType::TristateCheckbox, 0); mOptions[RSK_SKIP_CHILD_STEALTH] = Option::Bool("Skip Child Stealth", {"Don't Skip", "Skip"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("SkipChildStealth"), mOptionDescriptions[RSK_SKIP_CHILD_STEALTH], WidgetType::Checkbox, RO_GENERIC_DONT_SKIP); mOptions[RSK_SKIP_CHILD_ZELDA] = Option::Bool("Skip Child Zelda", {"Don't Skip", "Skip"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("SkipChildZelda"), mOptionDescriptions[RSK_SKIP_CHILD_ZELDA], WidgetType::Checkbox, RO_GENERIC_DONT_SKIP); mOptions[RSK_SKIP_EPONA_RACE] = Option::Bool("Skip Epona Race", {"Don't Skip", "Skip"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("SkipEponaRace"), mOptionDescriptions[RSK_SKIP_EPONA_RACE], WidgetType::Checkbox, RO_GENERIC_DONT_SKIP); @@ -1145,15 +1145,15 @@ void Settings::CreateOptions() { { "Shuffle Dungeon Items:LACS Reward Options", RSK_LACS_OPTIONS }, { "Shuffle Dungeon Items:Key Rings", RSK_KEYRINGS }, { "Shuffle Dungeon Items:Keyring Dungeon Count", RSK_KEYRINGS_RANDOM_COUNT }, - { "Shuffle Dungeon Items:Gerudo Fortress", RSK_KEYRINGS_GERUDO_FORTRESS }, - { "Shuffle Dungeon Items:Forest Temple", RSK_KEYRINGS_FOREST_TEMPLE }, - { "Shuffle Dungeon Items:Fire Temple", RSK_KEYRINGS_FIRE_TEMPLE }, - { "Shuffle Dungeon Items:Water Temple", RSK_KEYRINGS_WATER_TEMPLE }, - { "Shuffle Dungeon Items:Spirit Temple", RSK_KEYRINGS_SPIRIT_TEMPLE }, - { "Shuffle Dungeon Items:Shadow Temple", RSK_KEYRINGS_SHADOW_TEMPLE }, - { "Shuffle Dungeon Items:Bottom of the Well", RSK_KEYRINGS_BOTTOM_OF_THE_WELL }, - { "Shuffle Dungeon Items:GTG", RSK_KEYRINGS_GTG }, - { "Shuffle Dungeon Items:Ganon's Castle", RSK_KEYRINGS_GANONS_CASTLE }, + { "Shuffle Dungeon Items:Gerudo Fortress Keyring", RSK_KEYRINGS_GERUDO_FORTRESS }, + { "Shuffle Dungeon Items:Forest Temple Keyring", RSK_KEYRINGS_FOREST_TEMPLE }, + { "Shuffle Dungeon Items:Fire Temple Keyring", RSK_KEYRINGS_FIRE_TEMPLE }, + { "Shuffle Dungeon Items:Water Temple Keyring", RSK_KEYRINGS_WATER_TEMPLE }, + { "Shuffle Dungeon Items:Spirit Temple Keyring", RSK_KEYRINGS_SPIRIT_TEMPLE }, + { "Shuffle Dungeon Items:Shadow Temple Keyring", RSK_KEYRINGS_SHADOW_TEMPLE }, + { "Shuffle Dungeon Items:Bottom of the Well Keyring", RSK_KEYRINGS_BOTTOM_OF_THE_WELL }, + { "Shuffle Dungeon Items:GTG Keyring", RSK_KEYRINGS_GTG }, + { "Shuffle Dungeon Items:Ganon's Castle Keyring", RSK_KEYRINGS_GANONS_CASTLE }, { "World Settings:Starting Age", RSK_STARTING_AGE }, // TODO: Ammo Drop settings { "World Settings:Bombchu Drops", RSK_ENABLE_BOMBCHU_DROPS }, @@ -1952,39 +1952,44 @@ void Settings::FinalizeSettings(const std::set& excludedLocatio if (mOptions[RSK_KEYRINGS]) { // Random Key Rings + auto keyrings = keyRingOptions; + if (mOptions[RSK_GERUDO_FORTRESS].Is(RO_GF_NORMAL) && mOptions[RSK_GERUDO_KEYS].IsNot(RO_GERUDO_KEYS_VANILLA)) { + keyrings.push_back(&mOptions[RSK_KEYRINGS_GERUDO_FORTRESS]); + } else { + mOptions[RSK_KEYRINGS_GERUDO_FORTRESS].SetSelectedIndex(RO_KEYRING_FOR_DUNGEON_OFF); + } if (mOptions[RSK_KEYRINGS].Is(RO_KEYRINGS_RANDOM) || mOptions[RSK_KEYRINGS].Is(RO_KEYRINGS_COUNT)) { - auto keyrings = keyRingOptions; - if (mOptions[RSK_GERUDO_FORTRESS].Is(RO_GF_NORMAL) && mOptions[RSK_GERUDO_KEYS].IsNot(RO_GERUDO_KEYS_VANILLA)) { - keyrings.push_back(&mOptions[RSK_KEYRINGS_GERUDO_FORTRESS]); - } const uint32_t keyRingCount = mOptions[RSK_KEYRINGS].Is(RO_KEYRINGS_COUNT) ? mOptions[RSK_KEYRINGS_RANDOM_COUNT].Value() : Random(0, static_cast(keyrings.size())); Shuffle(keyrings); for (size_t i = 0; i < keyRingCount; i++) { - keyrings[i]->SetSelectedIndex(RO_GENERIC_ON); + keyrings[i]->SetSelectedIndex(RO_KEYRING_FOR_DUNGEON_ON); + } + for (size_t i = keyRingCount; i < keyrings.size(); i++) { + keyrings[i]->SetSelectedIndex(RO_KEYRING_FOR_DUNGEON_OFF); } } - if (mOptions[RSK_KEYRINGS_BOTTOM_OF_THE_WELL]) { + if (mOptions[RSK_KEYRINGS_BOTTOM_OF_THE_WELL].Is(RO_KEYRING_FOR_DUNGEON_ON) || (mOptions[RSK_KEYRINGS_BOTTOM_OF_THE_WELL].Is(RO_KEYRING_FOR_DUNGEON_RANDOM) && Random(0, 2) == 1)) { ctx->GetDungeon(BOTTOM_OF_THE_WELL)->SetKeyRing(); } - if (mOptions[RSK_KEYRINGS_FOREST_TEMPLE]) { + if (mOptions[RSK_KEYRINGS_FOREST_TEMPLE].Is(RO_KEYRING_FOR_DUNGEON_ON) || (mOptions[RSK_KEYRINGS_FOREST_TEMPLE].Is(RO_KEYRING_FOR_DUNGEON_RANDOM) && Random(0, 2) == 1)) { ctx->GetDungeon(FOREST_TEMPLE)->SetKeyRing(); } - if (mOptions[RSK_KEYRINGS_FIRE_TEMPLE]) { + if (mOptions[RSK_KEYRINGS_FIRE_TEMPLE].Is(RO_KEYRING_FOR_DUNGEON_ON) || (mOptions[RSK_KEYRINGS_FIRE_TEMPLE].Is(RO_KEYRING_FOR_DUNGEON_RANDOM) && Random(0, 2) == 1)) { ctx->GetDungeon(FIRE_TEMPLE)->SetKeyRing(); } - if (mOptions[RSK_KEYRINGS_WATER_TEMPLE]) { + if (mOptions[RSK_KEYRINGS_WATER_TEMPLE].Is(RO_KEYRING_FOR_DUNGEON_ON) || (mOptions[RSK_KEYRINGS_WATER_TEMPLE].Is(RO_KEYRING_FOR_DUNGEON_RANDOM) && Random(0, 2) == 1)) { ctx->GetDungeon(WATER_TEMPLE)->SetKeyRing(); } - if (mOptions[RSK_KEYRINGS_SPIRIT_TEMPLE]) { + if (mOptions[RSK_KEYRINGS_SPIRIT_TEMPLE].Is(RO_KEYRING_FOR_DUNGEON_ON) || (mOptions[RSK_KEYRINGS_SPIRIT_TEMPLE].Is(RO_KEYRING_FOR_DUNGEON_RANDOM) && Random(0, 2) == 1)) { ctx->GetDungeon(SPIRIT_TEMPLE)->SetKeyRing(); } - if (mOptions[RSK_KEYRINGS_SHADOW_TEMPLE]) { + if (mOptions[RSK_KEYRINGS_SHADOW_TEMPLE].Is(RO_KEYRING_FOR_DUNGEON_ON) || (mOptions[RSK_KEYRINGS_SHADOW_TEMPLE].Is(RO_KEYRING_FOR_DUNGEON_RANDOM) && Random(0, 2) == 1)) { ctx->GetDungeon(SHADOW_TEMPLE)->SetKeyRing(); } - if (mOptions[RSK_KEYRINGS_GTG]) { + if (mOptions[RSK_KEYRINGS_GTG].Is(RO_KEYRING_FOR_DUNGEON_ON) || (mOptions[RSK_KEYRINGS_GTG].Is(RO_KEYRING_FOR_DUNGEON_RANDOM) && Random(0, 2) == 1)) { ctx->GetDungeon(GERUDO_TRAINING_GROUNDS)->SetKeyRing(); } - if (mOptions[RSK_KEYRINGS_GANONS_CASTLE]) { + if (mOptions[RSK_KEYRINGS_GANONS_CASTLE].Is(RO_KEYRING_FOR_DUNGEON_ON) || (mOptions[RSK_KEYRINGS_GANONS_CASTLE].Is(RO_KEYRING_FOR_DUNGEON_RANDOM) && Random(0, 2) == 1)) { ctx->GetDungeon(GANONS_CASTLE)->SetKeyRing(); } } @@ -2322,15 +2327,6 @@ void Settings::ParseJson(nlohmann::json spoilerFileJson) { case RSK_HBA_HINT: case RSK_WARP_SONG_HINTS: case RSK_SCRUB_TEXT_HINT: - case RSK_KEYRINGS_GERUDO_FORTRESS: - case RSK_KEYRINGS_FOREST_TEMPLE: - case RSK_KEYRINGS_FIRE_TEMPLE: - case RSK_KEYRINGS_WATER_TEMPLE: - case RSK_KEYRINGS_SHADOW_TEMPLE: - case RSK_KEYRINGS_SPIRIT_TEMPLE: - case RSK_KEYRINGS_BOTTOM_OF_THE_WELL: - case RSK_KEYRINGS_GTG: - case RSK_KEYRINGS_GANONS_CASTLE: case RSK_SHUFFLE_ENTRANCES: case RSK_SHUFFLE_OVERWORLD_ENTRANCES: case RSK_SHUFFLE_GROTTO_ENTRANCES: @@ -2365,6 +2361,23 @@ void Settings::ParseJson(nlohmann::json spoilerFileJson) { mOptions[index].SetSelectedIndex(RO_KEYRINGS_SELECTION); } break; + case RSK_KEYRINGS_GERUDO_FORTRESS: + case RSK_KEYRINGS_FOREST_TEMPLE: + case RSK_KEYRINGS_FIRE_TEMPLE: + case RSK_KEYRINGS_WATER_TEMPLE: + case RSK_KEYRINGS_SHADOW_TEMPLE: + case RSK_KEYRINGS_SPIRIT_TEMPLE: + case RSK_KEYRINGS_BOTTOM_OF_THE_WELL: + case RSK_KEYRINGS_GTG: + case RSK_KEYRINGS_GANONS_CASTLE: + if (it.value() == "No") { + mOptions[index].SetSelectedIndex(RO_KEYRING_FOR_DUNGEON_OFF); + } else if (it.value() == "Random") { + mOptions[index].SetSelectedIndex(RO_KEYRING_FOR_DUNGEON_RANDOM); + } else if (it.value() == "Yes") { + mOptions[index].SetSelectedIndex(RO_KEYRING_FOR_DUNGEON_ON); + } + break; case RSK_SHUFFLE_MERCHANTS: if (it.value() == "Off") { mOptions[index].SetSelectedIndex(RO_SHUFFLE_MERCHANTS_OFF); diff --git a/soh/soh/UIWidgets.cpp b/soh/soh/UIWidgets.cpp index d1bf0967a..5ceaa0012 100644 --- a/soh/soh/UIWidgets.cpp +++ b/soh/soh/UIWidgets.cpp @@ -127,7 +127,7 @@ namespace UIWidgets { draw_list->PathStroke(col, 0, thickness); } - bool CustomCheckbox(const char* label, bool* v, bool disabled, CheckboxGraphics disabledGraphic) { + bool CustomCheckbox(const char* label, bool* v, bool disabled, CheckboxGraphics disabledGraphic, bool renderCrossWhenOff) { ImGuiWindow* window = ImGui::GetCurrentWindow(); if (window->SkipItems) { return false; @@ -168,9 +168,9 @@ namespace UIWidgets { } else if ((!disabled && *v) || (disabled && disabledGraphic == CheckboxGraphics::Checkmark)) { const float pad = ImMax(1.0f, IM_FLOOR(square_sz / 6.0f)); ImGui::RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad * 2.0f); - } else if (disabled && disabledGraphic == CheckboxGraphics::Cross) { + } else if ((!disabled && !*v && renderCrossWhenOff) || (disabled && disabledGraphic == CheckboxGraphics::Cross)) { const float pad = ImMax(1.0f, IM_FLOOR(square_sz / 6.0f)); - RenderCross(window->DrawList, check_bb.Min + ImVec2(pad, pad), cross_col, square_sz - pad * 2.0f); + RenderCross(window->DrawList, check_bb.Min + ImVec2(pad, pad), disabled ? cross_col : check_col, square_sz - pad * 2.0f); } ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y); @@ -185,6 +185,36 @@ namespace UIWidgets { return pressed; } + bool CustomCheckboxTristate(const char* label, int* v, bool disabled, CheckboxGraphics disabledGraphic) { + bool ret; + if (*v == 0) { + bool b = false; + ret = CustomCheckbox(label, &b, disabled, disabledGraphic, true); + if (ret) { + *v = 1; + } + } else if (*v == 1) { + ImGui::PushItemFlag(ImGuiItemFlags_MixedValue, true); + bool b = true; + ret = CustomCheckbox(label, &b, disabled, disabledGraphic, true); + if (ret) { + *v = 2; + } + ImGui::PopItemFlag(); + } else if (*v == 2) { + bool b = true; + ret = CustomCheckbox(label, &b, disabled, disabledGraphic, true); + if (ret) { + *v = 0; + } + } else { + SPDLOG_INFO("Invalid CheckBoxTristate value: {}", *v); + *v = 0; + return false; + } + return ret; + } + void ReEnableComponent(const char* disabledTooltipText) { // End of disable region of previous component ImGui::PopStyleVar(1); @@ -218,6 +248,25 @@ namespace UIWidgets { return changed; } + bool EnhancementCheckboxTristate(const char* text, const char* cvarName, bool disabled, const char* disabledTooltipText, CheckboxGraphics disabledGraphic, bool defaultValue) { + bool changed = false; + if (disabled) { + DisableComponent(ImGui::GetStyle().Alpha * 0.5f); + } + + int val = CVarGetInteger(cvarName, defaultValue); + if (CustomCheckboxTristate(text, &val, disabled, disabledGraphic)) { + CVarSetInteger(cvarName, val); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + changed = true; + } + + if (disabled) { + ReEnableComponent(disabledTooltipText); + } + return changed; + } + bool PaddedEnhancementCheckbox(const char* text, const char* cvarName, bool padTop, bool padBottom, bool disabled, const char* disabledTooltipText, CheckboxGraphics disabledGraphic, bool defaultValue) { ImGui::BeginGroup(); if (padTop) Spacer(0); diff --git a/soh/soh/UIWidgets.hpp b/soh/soh/UIWidgets.hpp index 70580340c..809c4dd2d 100644 --- a/soh/soh/UIWidgets.hpp +++ b/soh/soh/UIWidgets.hpp @@ -68,12 +68,14 @@ namespace UIWidgets { void PaddedSeparator(bool padTop = true, bool padBottom = true, float extraVerticalTopPadding = 0.0f, float extraVerticalBottomPadding = 0.0f); void RenderCross(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float sz); - bool CustomCheckbox(const char* label, bool* v, bool disabled, CheckboxGraphics disabledGraphic); + bool CustomCheckbox(const char* label, bool* v, bool disabled, CheckboxGraphics disabledGraphic, bool renderCrossWhenOff = false); + bool CustomCheckboxTristate(const char* label, int* v, bool disabled, CheckboxGraphics disabledGraphic); void ReEnableComponent(const char* disabledTooltipText); void DisableComponent(const float alpha); bool EnhancementCheckbox(const char* text, const char* cvarName, bool disabled = false, const char* disabledTooltipText = "", CheckboxGraphics disabledGraphic = CheckboxGraphics::Cross, bool defaultValue = false); + bool EnhancementCheckboxTristate(const char* text, const char* cvarName, bool disabled = false, const char* disabledTooltipText = "", CheckboxGraphics disabledGraphic = CheckboxGraphics::Cross, bool defaultValue = false); bool PaddedEnhancementCheckbox(const char* text, const char* cvarName, bool padTop = true, bool padBottom = true, bool disabled = false, const char* disabledTooltipText = "", CheckboxGraphics disabledGraphic = CheckboxGraphics::Cross, bool defaultValue = false); bool EnhancementCombobox(const char* cvarName, std::span comboArray, uint8_t defaultIndex, bool disabled = false, const char* disabledTooltipText = "", uint8_t disabledValue = -1);