Shipwright/soh/soh/UIWidgets.cpp
Malkierian 70a83f647f
Check Tracker Update (#2668)
* Initial commit. What works so far:

Data file loads, saves, deletes, and is created properly.
 - Need to run `HasItemBeenCollected` when creating in case of mid-seed regen.
Organized checks into maps set by area.
Areas show in tracker.

 - Checks pulled from checkObjectsByArea are not currently listed.
 - Areas are being assumed completed and hidden at the start.

* Checks now populate. Still not counting area totals properly.

* Don't track RC_LINKS_POCKET in the data file, and instead manually add it in every time a save is load. rcObjects doesn't contain Link's Pocket location (duh).

* Moved ItemReceive hook to randomizer_check_tracker.cpp.

Skipped items are properly sorted and formatted.

Saved items contribute to an area's gotten/skipped checks on load.

Changed skipped items default main color to gray.

General code cleanup.

* Skipped checks now contribute to area totals for area completion logic.

* Mid-menu rework transfer.

* Started check lookup on item receive.
Tried to enable scum checking on save.

* Fixed scum check on save.

* More code cleanup, which revealed some issues with loading and saving. Loading now works 100%, but entries in checkTrackerData are disappearing in code somewhere, causing issues with later checks.

Scummed color now displays properly.

Collected but unsaved displays properly.

Unchecked, Skipped, Seen and Scummed checks all have arrows for skipping/unskipping.

Counts a little messed up, probably double adding skips.

* Fixed item counts being off across resets. Restarts were fine.

* Beginning of rework for individual check updates. Adds `Actor lastCheck` to PlayState to allow assigning of last actor to give an item. This allows precise tracking of GS, freestanding PoH, etc. Works for chests too. Currently does not work for shops, unknown for normal NPC gives. Doesn't track last gives for non-checks, like drops from grass, pots, crates, etc.

* Actor-based checks fully fleshed out, but can't help with sale-based checks. Implemented `pendingSaleCheck` to back out of an RC check cycle if `gSaveContext->pendingSale` is not NONE when `OnItemReceive` is called, processed through `OnSaleEnd`. This should be the final change necessary to streamline the checks.

* Temporary hook to message box close to tie in trackers for non-gs/chest actors that aren't added to `GetCheckFromActor`. This triggers a 2-frame countdown to check with `HasItemBeenCollected` since half of the methods in there aren't updated until after a text box closes.

Added scene tracking to get last scene so that checks in scenes that span several overworld areas could be tied to their areas properly.

Modified tracker data creation to apply Link's Pocket and Song from Impa conditionally based on randomizer settings instead of just being assumed to be done. May need modification for checking click and drag spoiler loading.

Removed the scene equivalence check from CheckChecks so that an entire area is checked each time to avoid issues with, e.g., leaving a shop before a pending sale finishes.

* Changed check data creation to use RSKs from gRandomizer to account for settings loaded from drag and drop spoilers.

* Beginning of tracker code organization.

Changed check color defaults.

* Reverted moving tracker colors to header.

Some individual check fixes.

* Missed something from previous merge?

* Removed last remnant of removed performance mode.

* Better handle checking when skullsanity is off.

* Song checks sceneIDs don't match the scenes they're actually in for some reason. Removed that check.

* SaleEnd checks don't need the GetItemEntry parameter. Also set a flag that should prevent the check tracker from running during vanilla saves should that not be resolved by the time it's merged.

* Attempting to have only the relevant checks added to the tracker data, as well as showing vanilla checks for non-rando saves. Not working yet (everything is displayed in vanilla).

* Added vanilla check tracker population via `vanillaCheck` bool in `RandomizerCheckObject`. This is also added to `IsVisibleInImGui` to handle file loading and saving to eliminate checks that aren't part of vanilla or the rando seed.

Implemented deleted tracker data file recreation.

Added some extra item name text checks. Songs are still... tricky, so they stay blank.

Utilize local copies of the randomizer check objects that are applicable for performance purposes.

Fixed vanilla item-giving cutscene crashes when triggered mid-transition.

* Apparently not all toolchains can handle macro instantiation without all parameters, like VS can.

* Fixed scummed detail color not showing properly on tracker.

* Fixed dungeon area totals and GS checks not functioning properly.

* Major revamp of checking code. Doesn't rely on `GetCheckFromActor` at all anymore, but instead simply sets a flag to evaluate a number of checks in an area every frame until the check that was gotten is identified. With the new setup, it's much less hardware intensive, and at 60fps 6 checks per frame shows no noticeable effect on framerate. I can envision needing to add a collectedCount in order to account for times when two checks are gotten one right after the other.

Changed area scroll to happen from `OnTransitionEnd`, and allow for scrolling in bazaar (was disabled previously because it would jump back and forth between Kak and Market while you were in one).

Also setup area check order updating by area instead of all at the same time.

* Fixed shooting gallery not reporting proper area.

Prevent checks from actually happening if area is RCAREA_INVALID, now that all areas are reporting something if they have checks.

Fixed `GetCheckArea()` not updating checks for standard scenes.

Lots of vanilla tracking updates, mainly manual checks for gems, medallions and songs, since the often don't have proper scenes in the data.

Prevent vanilla from triggering check loops if junk items are collected.

Fixed sorting based on saved vs collected.

Prevent item name display for vanilla runs.

Change coloring of checks so the check name reflects status in vanilla runs.

* Fixed "Recheck Area" button erroneously adding to an area's check totals?

* Fix DMC vanilla checks.

* Fix check tracker data recreation on data file loss.

Removed redundant file exists check from SaveTrackerDataHook, as it doesn't matter if it exists or not at that point.

Limited check loops to 3 to avoid infinite check loops on non-check/junk item pickup.

Added more checks to disable tracker operations if a save is not running.

Changed check ordering to put boss reward and heart container checks at the end of whatever RCSHOW group they belong to.

* Fixed IsRunning calculation.

Further improving vanilla checking.

Starting framework for checking medallions, stones, and songs on data file recreation.

Fixed medallion check collection.

Added GIFT_FROM_SAGES to check list for vanilla file for tracking light medallion.

Added check loop limitation to prevent infinite checking after picking up junk item from the ground.

* Finished vanilla file recreation and recheck (songs and dungeon rewards all check properly now). This includes deku shield with KF shop item 3.

Finished Gift from Raoru light medallion tracking for vanilla.

Commented all CheckByScene functionality for now.

* Fixed new save data file creation.

Disabled entrance area calculation for now, as it only tracks the previous entrance for some reason.

Fixed area detection for ToT checks.

* GetCheckArea() now utilizes EntranceData almost exclusively, via either `gSaveContext.entranceIndex` or the entrance tracker's `currentGrottoId` in the case of grottos.

This also means that EVERYTHING CAN AUTOSCROLL NOW. Entrance shuffles are now much easier to track.

Autoscroll is now also triggered on save, just in case someone isn't autosaving and has a lot of checks that get converted to saved.

* Fixed missing GS check in LW.

* Added area scroll on toggling Show Hidden Items. May configure differently later.

Fixed area detection for entrances in Gerudo Fortress/Valley and Collossus. Haunted wasteland doesn't autoscroll due to a bug in `OnTransitionEnd` hook, but checks still evaluate properly there.

Rely on scene-based area detection if scene is a main overworld or dungeon scene.

* Fixed grotto detection when shuffle is off.

* Small code cleanup.

Fixed Colossus hand chest checks.

* Missing lus bump from merge conflict resolution

* Fixed Colossus Grotto making `GetCheckArea` return Wasteland.

* Improved OnItemReceive processing for non-token GS checks, since they can sometimes take long enough for the checking process to time out before the item is registered as received.

* First attempt at thread safety for tracker data file writing. Seems to work, but might need more testing.

* Fixed Recheck Area not unskipping items that register as saved.

Improved delayed saving with autosaving, as the autosave triggered quite often before the tracker data save, making things not properly register as saved.

* merge cleanup

* Converted check tracker data to the sectional saves, adding `CheckTrackerData` to `SaveContext`.

Implemented section ID returning and fullSave boolean passing to section functions from my PR temporarily for it to work properly.

Moved `RandomizerCheckTrackerData` enum to `randomizerTypes.h` to accommodate that.

Changed `Randomizer_SaveInit` to a `SaveManager::InitFunc` to allow for other randomizer-dependent sections to be initialized after that. Required a little refactoring to maintain intro cutscene skip when starting a randomizer file.

* Revert section index return and randomizer init changes. Will need to wait on yet more changes to main.

* Fixed check tracker initialization (shouldn't be tied to window initialization), restored saving/loading functionality.

* Removed `CheckTracker::Init` and put SaveManager calls in `CheckTrackerWindow::InitElement`. Also a bit of cleanup from transition back to save file use.

* Fixed tracker displaying check categories while file not loaded.

* Fix Darunia's Joy check not marking in vanilla.

* Fix autosave not triggering change from collected to saved in tracker data.

* Changed default colors for scummed and collected display.

* Merge upgrade code cleanup.

* Add `OnTransitionEnd` calls to sandstorm transitions both to and from Wasteland. Also improved `gSaveContext.lastScene` assignment for both transition types. Allowed a bit of cleanup in `GetCheckArea`.

* Added `StateButton`, a button like `ArrowButton` but that allows text instead of arrow icons. Apparently the changes from ArrowButton to StateButton happened in a previous commit...

* Changed section name to `trackerDataCheck` to force SoH to load it after the randomizer section, as it required some randoSettings to be loaded first, and nothing else I tried to make the randomizer section load before it worked. This hacks a solution to checks not displaying on fast file load to a specific slot.

F*** you, SaveManager, and f*** you too, JSON.

* Forgot to change the section string for loading with the name change.

* Fix check ordering for checks that trigger the autosave.

* Adds option to remove right-side shop items (slots 1-4) from the tracker list. Enabled by default.

* Fix default state of Hide Shop Right Checks checkbox.

* Fixes grotto and great fairy scrolling and checks.

Fixes array overflow from `checkTrackerData` which was creating the issue trying to load the base and randomizer sections first, among other things.

That also fixed the massive file loads that were being exhibited in debug mode.

* Fix shooting galleries being set as collected again when being played a second time after getting the checks.

* Fix Bazaar autoscroll.

* Add Saria's Song to `GetCheckFromActor` and removed some limitations from the messageCloseCheck function to make that check track properly.

* Fix Song from Impa check.

Implemented prevention for multiple "collections" of great fairies, just in case getting the health refill would trigger it with the previous setup.

* Fix ice traps on GS tokens not triggering OnItemReceive.

* Complete fix for ice trap collection from GS.

Add autoscroll when clicking "Expand All".

* Add `OnShopSlotChange` with cursorSlot and basePrice parameters.

* Fixed include in en_ossan for shop slot hook.

Added registration for `OnShopSlotChange` in the tracker, storing the price in a new `price` field in `CheckTrackerData`.

Added "seen" functionality to shop checks. Displays the model item name upon first entering a shop, adding the price and switching to trickName (if it's an ice trap) upon navigating to the slot in buy mode, triggered by an invisible "identified" status that mirrors "seen" in every other way.

Added tooltips to most options for check tracker color picking to describe what each status actually means.

* `std::format` pls

* So apparently std::format just decided to break with the latest merge from develop, but fmt::format exists and works?

* Removed the last vestiges of `locationsSkipped`. Other general code and formatting cleanups.

Moved `IsGameRunning` to `OTRGlobals` so the item tracker could also access it.

Used preceding to "fix" item and bottle display in the item tracker on startup.

* Some more code cleanup.

Removed "Recheck Area" button and relevant code.

Backported changes to Anchor branch applicable for single-player, including making a checkAreas vector and structuring the frame by frame checks around that. Also includes fix for Silver Gauntlets and Mirror Shield check collection crash associated with those changes.

Fixed Kakariko Bazaar "seen" updates.

Fixed tracker window not showing on initial load like it should.

* Forgot 1 formatting fix.

* Removed conditions for showing Song from Impa (isn't junk under certain conditions, so should show all the time).

* Fix vanilla checks, add Zelda's Letter and Malon's Egg to manual check collection.

Fix autoscroll while in child stealth section.

* Fix crash in Happy Mask Shop in OnSlotChange (referenced non-existent shop id in a tracker-specific enum).

* General code cleanup.

* Missed one reversion.

* One more.

* Fix column alignment in `randomizer_check_objects`.

* Fix file encoding on `randomizer_check_tracker`. Again.

* Fix indentation for `actualItemtrackerItemMap`. Also removed unnecessary parts of the map.

* Rename `HasEqItem` to `HasEquipment`.

* Slightly better indentation for `actualItemTrackerItemMap`.

* Add magic bean salesman to vanilla check tracking, and genericized deku shield to trigger KF shop item 3 wherever you get the shield.

Renamed `vanillaCheck` to `vanillaHundoCheck` to (supposedly) clarify the meaning of the usage.

* One more rename to `vanillaCompletion` to avoid possible confusion with 100% speedrun conditions.

* give me a break XD

Co-authored-by: briaguya <70942617+briaguya-ai@users.noreply.github.com>

* Changes suggested by briaguya (rename `RandomizerCheckShow` to `Status`, unused code, newline formatting)

* Remove unused `itemNames` table.

* Remove `IsGameRunning` in favor of `GameInteractor::IsSaveLoaded`.

* Restore anti-spoiler functionality for dungeons with dungeon maps.

* Review cleanup.

* Fix prices not showing for Kak bazaar items.

---------

Co-authored-by: briaguya <70942617+briaguya-ai@users.noreply.github.com>
2023-10-04 10:03:36 -05:00

738 lines
28 KiB
C++

//
// UIWidgets.cpp
// soh
//
// Created by David Chavez on 25.08.22.
//
#include "UIWidgets.hpp"
#define IMGUI_DEFINE_MATH_OPERATORS
#include <ImGui/imgui_internal.h>
#include <libultraship/libultraship.h>
#include <libultraship/libultra/types.h>
#include "soh/Enhancements/cosmetics/CosmeticsEditor.h"
namespace UIWidgets {
// MARK: - Layout Helper
// Automatically adds newlines to break up text longer than a specified number of characters
// Manually included newlines will still be respected and reset the line length
// If line is midword when it hits the limit, text should break at the last encountered space
char* WrappedText(const char* text, unsigned int charactersPerLine) {
std::string newText(text);
const size_t tipLength = newText.length();
int lastSpace = -1;
int currentLineLength = 0;
for (unsigned int currentCharacter = 0; currentCharacter < tipLength; currentCharacter++) {
if (newText[currentCharacter] == '\n') {
currentLineLength = 0;
lastSpace = -1;
continue;
} else if (newText[currentCharacter] == ' ') {
lastSpace = currentCharacter;
}
if ((currentLineLength >= charactersPerLine) && (lastSpace >= 0)) {
newText[lastSpace] = '\n';
currentLineLength = currentCharacter - lastSpace - 1;
lastSpace = -1;
}
currentLineLength++;
}
return strdup(newText.c_str());
}
char* WrappedText(const std::string& text, unsigned int charactersPerLine) {
return WrappedText(text.c_str(), charactersPerLine);
}
void SetLastItemHoverText(const std::string& text) {
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::Text("%s", WrappedText(text, 60));
ImGui::EndTooltip();
}
}
void SetLastItemHoverText(const char* text) {
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::Text("%s", WrappedText(text, 60));
ImGui::EndTooltip();
}
}
// Adds a "?" next to the previous ImGui item with a custom tooltip
void InsertHelpHoverText(const std::string& text) {
ImGui::SameLine();
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "?");
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::Text("%s", WrappedText(text, 60));
ImGui::EndTooltip();
}
}
void InsertHelpHoverText(const char* text) {
ImGui::SameLine();
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "?");
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::Text("%s", WrappedText(text, 60));
ImGui::EndTooltip();
}
}
// MARK: - UI Elements
void Tooltip(const char* text) {
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", WrappedText(text));
}
}
void Spacer(float height) {
ImGui::Dummy(ImVec2(0.0f, height));
}
void PaddedSeparator(bool padTop, bool padBottom, float extraVerticalTopPadding, float extraVerticalBottomPadding) {
if (padTop) {
Spacer(extraVerticalTopPadding);
}
ImGui::Separator();
if (padBottom) {
Spacer(extraVerticalBottomPadding);
}
}
void RenderCross(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float sz) {
float thickness = ImMax(sz / 5.0f, 1.0f);
sz -= thickness * 0.5f;
pos += ImVec2(thickness * 0.25f, thickness * 0.25f);
draw_list->PathLineTo(ImVec2(pos.x, pos.y));
draw_list->PathLineTo(ImVec2(pos.x + sz, pos.y + sz));
draw_list->PathStroke(col, 0, thickness);
draw_list->PathLineTo(ImVec2(pos.x + sz, pos.y));
draw_list->PathLineTo(ImVec2(pos.x, pos.y + sz));
draw_list->PathStroke(col, 0, thickness);
}
bool CustomCheckbox(const char* label, bool* v, bool disabled, CheckboxGraphics disabledGraphic) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems) {
return false;
}
ImGuiContext& g = *GImGui;
const ImGuiStyle& style = g.Style;
const ImGuiID id = window->GetID(label);
const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true);
const float square_sz = ImGui::GetFrameHeight();
const ImVec2 pos = window->DC.CursorPos;
const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + style.FramePadding.y * 2.0f));
ImGui::ItemSize(total_bb, style.FramePadding.y);
if (!ImGui::ItemAdd(total_bb, id)) {
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
return false;
}
bool hovered, held;
bool pressed = ImGui::ButtonBehavior(total_bb, id, &hovered, &held);
if (pressed) {
*v = !(*v);
ImGui::MarkItemEdited(id);
}
const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
ImGui::RenderNavHighlight(total_bb, id);
ImGui::RenderFrame(check_bb.Min, check_bb.Max, ImGui::GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding);
ImU32 check_col = ImGui::GetColorU32(ImGuiCol_CheckMark);
ImU32 cross_col = ImGui::GetColorU32(ImVec4(0.50f, 0.50f, 0.50f, 1.00f));
bool mixed_value = (g.LastItemData.InFlags & ImGuiItemFlags_MixedValue) != 0;
if (mixed_value) {
// Undocumented tristate/mixed/indeterminate checkbox (#2644)
// This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all widgets (not just checkbox)
ImVec2 pad(ImMax(1.0f, IM_FLOOR(square_sz / 3.6f)), ImMax(1.0f, IM_FLOOR(square_sz / 3.6f)));
window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding);
} 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) {
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);
}
ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);
if (g.LogEnabled) {
ImGui::LogRenderedText(&label_pos, mixed_value ? "[~]" : *v ? "[x]" : "[ ]");
}
if (label_size.x > 0.0f) {
ImGui::RenderText(label_pos, label);
}
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
return pressed;
}
void ReEnableComponent(const char* disabledTooltipText) {
// End of disable region of previous component
ImGui::PopStyleVar(1);
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(disabledTooltipText, "") != 0) {
ImGui::SetTooltip("%s", disabledTooltipText);
}
ImGui::PopItemFlag();
}
void DisableComponent(const float alpha) {
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha);
}
bool EnhancementCheckbox(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);
}
bool val = (bool)CVarGetInteger(cvarName, defaultValue);
if (CustomCheckbox(text, &val, disabled, disabledGraphic)) {
CVarSetInteger(cvarName, val);
LUS::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);
bool changed = EnhancementCheckbox(text, cvarName, disabled, disabledTooltipText, disabledGraphic, defaultValue);
if (padBottom) Spacer(0);
ImGui::EndGroup();
return changed;
}
bool EnhancementCombobox(const char* cvarName, std::span<const char*, std::dynamic_extent> comboArray, uint8_t defaultIndex, bool disabled, const char* disabledTooltipText, uint8_t disabledValue) {
bool changed = false;
if (defaultIndex <= 0) {
defaultIndex = 0;
}
if (disabled) {
DisableComponent(ImGui::GetStyle().Alpha * 0.5f);
}
uint8_t selected = CVarGetInteger(cvarName, defaultIndex);
std::string comboName = std::string("##") + std::string(cvarName);
if (ImGui::BeginCombo(comboName.c_str(), comboArray[selected])) {
for (uint8_t i = 0; i < comboArray.size(); i++) {
if (strlen(comboArray[i]) > 0) {
if (ImGui::Selectable(comboArray[i], i == selected)) {
CVarSetInteger(cvarName, i);
selected = i;
changed = true;
LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
}
}
}
ImGui::EndCombo();
}
if (disabled) {
ReEnableComponent(disabledTooltipText);
if (disabledValue >= 0 && selected != disabledValue) {
CVarSetInteger(cvarName, disabledValue);
changed = true;
LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
}
}
return changed;
}
bool LabeledRightAlignedEnhancementCombobox(const char* label, const char* cvarName, std::span<const char*, std::dynamic_extent> comboArray, uint8_t defaultIndex, bool disabled, const char* disabledTooltipText, uint8_t disabledValue) {
ImGui::Text("%s", label);
s32 currentValue = CVarGetInteger(cvarName, defaultIndex);
#ifdef __WIIU__
ImGui::SameLine(ImGui::GetContentRegionAvail().x - (ImGui::CalcTextSize(comboArray[currentValue]).x + 40.0f));
ImGui::PushItemWidth(ImGui::CalcTextSize(comboArray[currentValue]).x + 60.0f);
#else
ImGui::SameLine(ImGui::GetContentRegionAvail().x - (ImGui::CalcTextSize(comboArray[currentValue]).x + 20.0f));
ImGui::PushItemWidth(ImGui::CalcTextSize(comboArray[currentValue]).x + 30.0f);
#endif
bool changed = EnhancementCombobox(cvarName, comboArray, defaultIndex, disabled, disabledTooltipText, disabledValue);
ImGui::PopItemWidth();
return changed;
}
void PaddedText(const char* text, bool padTop, bool padBottom) {
if (padTop) Spacer(0);
ImGui::Text("%s", text);
if (padBottom) Spacer(0);
}
bool EnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max, const char* format, int defaultValue, bool PlusMinusButton, bool disabled, const char* disabledTooltipText) {
bool changed = false;
int val = CVarGetInteger(cvarName, defaultValue);
if (disabled) {
DisableComponent(ImGui::GetStyle().Alpha * 0.5f);
}
ImGui::Text(text, val);
Spacer(0);
ImGui::BeginGroup();
if (PlusMinusButton) {
std::string MinusBTNName = " - ##" + std::string(cvarName);
if (ImGui::Button(MinusBTNName.c_str())) {
val--;
changed = true;
}
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f);
}
ImGui::PushItemWidth(std::min((ImGui::GetContentRegionAvail().x - (PlusMinusButton ? sliderButtonWidth : 0.0f)), maxSliderWidth));
if (ImGui::SliderInt(id, &val, min, max, format, ImGuiSliderFlags_AlwaysClamp))
{
changed = true;
}
ImGui::PopItemWidth();
if (PlusMinusButton) {
std::string PlusBTNName = " + ##" + std::string(cvarName);
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f);
if (ImGui::Button(PlusBTNName.c_str())) {
val++;
changed = true;
}
}
ImGui::EndGroup();
if (disabled) {
ReEnableComponent(disabledTooltipText);
}
if (val < min) {
val = min;
changed = true;
}
if (val > max) {
val = max;
changed = true;
}
if (changed) {
CVarSetInteger(cvarName, val);
LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
}
return changed;
}
bool EnhancementSliderFloat(const char* text, const char* id, const char* cvarName, float min, float max, const char* format, float defaultValue, bool isPercentage, bool PlusMinusButton, bool disabled, const char* disabledTooltipText) {
bool changed = false;
float val = CVarGetFloat(cvarName, defaultValue);
if (disabled) {
DisableComponent(ImGui::GetStyle().Alpha * 0.5f);
}
if (!isPercentage) {
ImGui::Text(text, val);
} else {
ImGui::Text(text, static_cast<int>(100 * val));
}
Spacer(0);
ImGui::BeginGroup();
if (PlusMinusButton) {
std::string MinusBTNName = " - ##" + std::string(cvarName);
if (ImGui::Button(MinusBTNName.c_str())) {
if (isPercentage) {
val -= 0.01f;
} else {
val -= 0.1f;
}
changed = true;
}
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f);
}
ImGui::PushItemWidth(std::min((ImGui::GetContentRegionAvail().x - (PlusMinusButton ? sliderButtonWidth : 0.0f)), maxSliderWidth));
if (ImGui::SliderFloat(id, &val, min, max, format, ImGuiSliderFlags_AlwaysClamp)) {
if (isPercentage) {
val = roundf(val * 100) / 100;
}
changed = true;
}
ImGui::PopItemWidth();
if (PlusMinusButton) {
std::string PlusBTNName = " + ##" + std::string(cvarName);
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f);
if (ImGui::Button(PlusBTNName.c_str())) {
if (isPercentage) {
val += 0.01f;
} else {
val += 0.1f;
}
changed = true;
}
}
ImGui::EndGroup();
if (disabled) {
ReEnableComponent(disabledTooltipText);
}
if (val < min) {
val = min;
changed = true;
}
if (val > max) {
val = max;
changed = true;
}
if (changed) {
CVarSetFloat(cvarName, val);
LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
}
return changed;
}
bool PaddedEnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max, const char* format, int defaultValue, bool PlusMinusButton, bool padTop, bool padBottom, bool disabled, const char* disabledTooltipText) {
bool changed = false;
ImGui::BeginGroup();
if (padTop) Spacer(0);
changed = EnhancementSliderInt(text, id, cvarName, min, max, format, defaultValue, PlusMinusButton, disabled, disabledTooltipText);
if (padBottom) Spacer(0);
ImGui::EndGroup();
return changed;
}
bool PaddedEnhancementSliderFloat(const char* text, const char* id, const char* cvarName, float min, float max, const char* format, float defaultValue, bool isPercentage, bool PlusMinusButton, bool padTop, bool padBottom, bool disabled, const char* disabledTooltipText) {
bool changed = false;
ImGui::BeginGroup();
if (padTop) Spacer(0);
changed = EnhancementSliderFloat(text, id, cvarName, min, max, format, defaultValue, isPercentage, PlusMinusButton, disabled, disabledTooltipText);
if (padBottom) Spacer(0);
ImGui::EndGroup();
return changed;
}
bool EnhancementRadioButton(const char* text, const char* cvarName, int id) {
/*Usage :
EnhancementRadioButton("My Visible Name","gMyCVarName", MyID);
First arg is the visible name of the Radio button
Second is the cvar name where MyID will be saved.
Note: the CVar name should be the same to each Buddies.
Example :
EnhancementRadioButton("English", "gLanguages", LANGUAGE_ENG);
EnhancementRadioButton("German", "gLanguages", LANGUAGE_GER);
EnhancementRadioButton("French", "gLanguages", LANGUAGE_FRA);
*/
std::string make_invisible = "##" + std::string(text) + std::string(cvarName);
bool ret = false;
int val = CVarGetInteger(cvarName, 0);
if (ImGui::RadioButton(make_invisible.c_str(), id == val)) {
CVarSetInteger(cvarName, id);
LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
ret = true;
}
ImGui::SameLine();
ImGui::Text("%s", text);
return ret;
}
bool DrawResetColorButton(const char* cvarName, ImVec4* colors, ImVec4 defaultcolors, bool has_alpha) {
bool changed = false;
std::string Cvar_RBM = std::string(cvarName) + "RBM";
std::string MakeInvisible = "Reset##" + std::string(cvarName) + "Reset";
if (ImGui::Button(MakeInvisible.c_str())) {
colors->x = defaultcolors.x;
colors->y = defaultcolors.y;
colors->z = defaultcolors.z;
colors->w = has_alpha ? defaultcolors.w : 255.0f;
Color_RGBA8 colorsRGBA;
colorsRGBA.r = defaultcolors.x;
colorsRGBA.g = defaultcolors.y;
colorsRGBA.b = defaultcolors.z;
colorsRGBA.a = has_alpha ? defaultcolors.w : 255.0f;
CVarSetColor(cvarName, colorsRGBA);
CVarSetInteger(Cvar_RBM.c_str(), 0); //On click disable rainbow mode.
LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
changed = true;
}
Tooltip("Revert colors to the game's original colors (GameCube version)\nOverwrites previously chosen color");
return changed;
}
bool DrawRandomizeColorButton(const char* cvarName, ImVec4* colors) {
bool changed = false;
Color_RGBA8 NewColors = {0,0,0,255};
std::string Cvar_RBM = std::string(cvarName) + "RBM";
std::string FullName = "Random##" + std::string(cvarName) + "Random";
if (ImGui::Button(FullName.c_str())) {
#if defined(__SWITCH__) || defined(__WIIU__)
srand(time(NULL));
#endif
ImVec4 color = GetRandomValue(255);
colors->x = color.x;
colors->y = color.y;
colors->z = color.z;
NewColors.r = fmin(fmax(colors->x * 255, 0), 255);
NewColors.g = fmin(fmax(colors->y * 255, 0), 255);
NewColors.b = fmin(fmax(colors->z * 255, 0), 255);
CVarSetColor(cvarName, NewColors);
CVarSetInteger(Cvar_RBM.c_str(), 0); // On click disable rainbow mode.
LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
changed = true;
}
Tooltip("Chooses a random color\nOverwrites previously chosen color");
return changed;
}
void DrawLockColorCheckbox(const char* cvarName) {
std::string Cvar_Lock = std::string(cvarName) + "Lock";
s32 lock = CVarGetInteger(Cvar_Lock.c_str(), 0);
std::string FullName = "Lock##" + Cvar_Lock;
EnhancementCheckbox(FullName.c_str(), Cvar_Lock.c_str());
Tooltip("Prevents this color from being changed upon selecting \"Randomize all\"");
}
void RainbowColor(const char* cvarName, ImVec4* colors) {
std::string Cvar_RBM = std::string(cvarName) + "RBM";
std::string MakeInvisible = "Rainbow##" + std::string(cvarName) + "Rainbow";
EnhancementCheckbox(MakeInvisible.c_str(), Cvar_RBM.c_str());
Tooltip("Cycles through colors on a timer\nOverwrites previously chosen color");
}
void LoadPickersColors(ImVec4& ColorArray, const char* cvarname, const ImVec4& default_colors, bool has_alpha) {
Color_RGBA8 defaultColors;
defaultColors.r = default_colors.x;
defaultColors.g = default_colors.y;
defaultColors.b = default_colors.z;
defaultColors.a = default_colors.w;
Color_RGBA8 cvarColor = CVarGetColor(cvarname, defaultColors);
ColorArray.x = cvarColor.r / 255.0;
ColorArray.y = cvarColor.g / 255.0;
ColorArray.z = cvarColor.b / 255.0;
ColorArray.w = cvarColor.a / 255.0;
}
bool EnhancementColor(const char* text, const char* cvarName, ImVec4 ColorRGBA, ImVec4 default_colors, bool allow_rainbow, bool has_alpha, bool TitleSameLine) {
bool changed = false;
LoadPickersColors(ColorRGBA, cvarName, default_colors, has_alpha);
ImGuiColorEditFlags flags = ImGuiColorEditFlags_None;
if (!TitleSameLine) {
ImGui::Text("%s", text);
flags = ImGuiColorEditFlags_NoLabel;
}
ImGui::PushID(cvarName);
if (!has_alpha) {
if (ImGui::ColorEdit3(text, (float*)&ColorRGBA, flags))
{
Color_RGBA8 colors;
colors.r = ColorRGBA.x * 255.0;
colors.g = ColorRGBA.y * 255.0;
colors.b = ColorRGBA.z * 255.0;
colors.a = 255.0;
CVarSetColor(cvarName, colors);
LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
changed = true;
}
}
else
{
flags |= ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_AlphaPreview;
if (ImGui::ColorEdit4(text, (float*)&ColorRGBA, flags))
{
Color_RGBA8 colors;
colors.r = ColorRGBA.x * 255.0;
colors.g = ColorRGBA.y * 255.0;
colors.b = ColorRGBA.z * 255.0;
colors.a = ColorRGBA.w * 255.0;
CVarSetColor(cvarName, colors);
LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
changed = true;
}
}
ImGui::PopID();
//ImGui::SameLine(); // Removing that one to gain some width spacing on the HUD editor
ImGui::PushItemWidth(-FLT_MIN);
if (DrawResetColorButton(cvarName, &ColorRGBA, default_colors, has_alpha)) {
changed = true;
}
ImGui::SameLine();
if (DrawRandomizeColorButton(cvarName, &ColorRGBA)) {
changed = true;
}
if (allow_rainbow) {
if (ImGui::GetContentRegionAvail().x > 185) {
ImGui::SameLine();
}
RainbowColor(cvarName, &ColorRGBA);
}
DrawLockColorCheckbox(cvarName);
ImGui::NewLine();
ImGui::PopItemWidth();
return changed;
}
void DrawFlagArray32(const std::string& name, uint32_t& flags) {
ImGui::PushID(name.c_str());
for (int32_t flagIndex = 0; flagIndex < 32; flagIndex++) {
if ((flagIndex % 8) != 0) {
ImGui::SameLine();
}
ImGui::PushID(flagIndex);
uint32_t bitMask = 1 << flagIndex;
bool flag = (flags & bitMask) != 0;
if (ImGui::Checkbox("##check", &flag)) {
if (flag) {
flags |= bitMask;
} else {
flags &= ~bitMask;
}
}
ImGui::PopID();
}
ImGui::PopID();
}
void DrawFlagArray16(const std::string& name, uint16_t& flags) {
ImGui::PushID(name.c_str());
for (int16_t flagIndex = 0; flagIndex < 16; flagIndex++) {
if ((flagIndex % 8) != 0) {
ImGui::SameLine();
}
ImGui::PushID(flagIndex);
uint16_t bitMask = 1 << flagIndex;
bool flag = (flags & bitMask) != 0;
if (ImGui::Checkbox("##check", &flag)) {
if (flag) {
flags |= bitMask;
} else {
flags &= ~bitMask;
}
}
ImGui::PopID();
}
ImGui::PopID();
}
void DrawFlagArray8(const std::string& name, uint8_t& flags) {
ImGui::PushID(name.c_str());
for (int8_t flagIndex = 0; flagIndex < 8; flagIndex++) {
if ((flagIndex % 8) != 0) {
ImGui::SameLine();
}
ImGui::PushID(flagIndex);
uint8_t bitMask = 1 << flagIndex;
bool flag = (flags & bitMask) != 0;
if (ImGui::Checkbox("##check", &flag)) {
if (flag) {
flags |= bitMask;
} else {
flags &= ~bitMask;
}
}
ImGui::PopID();
}
ImGui::PopID();
}
bool StateButtonEx(const char* str_id, const char* label, ImVec2 size, ImGuiButtonFlags flags) {
ImGuiContext& g = *GImGui;
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
const ImGuiStyle& style = g.Style;
const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true);
const ImGuiID id = window->GetID(str_id);
const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
const float default_size = ImGui::GetFrameHeight();
ImGui::ItemSize(size, (size.y >= default_size) ? g.Style.FramePadding.y : -1.0f);
if (!ImGui::ItemAdd(bb, id))
return false;
if (g.LastItemData.InFlags & ImGuiItemFlags_ButtonRepeat)
flags |= ImGuiButtonFlags_Repeat;
bool hovered, held;
bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, flags);
// Render
const ImU32 bg_col = ImGui::GetColorU32((held && hovered) ? ImGuiCol_ButtonActive
: hovered ? ImGuiCol_ButtonHovered
: ImGuiCol_Button);
//const ImU32 text_col = ImGui::GetColorU32(ImGuiCol_Text);
ImGui::RenderNavHighlight(bb, id);
ImGui::RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding);
ImGui::RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, {0.55f, 0.45f}, &bb);
/*ImGui::RenderArrow(window->DrawList,
bb.Min +
ImVec2(ImMax(0.0f, (size.x - g.FontSize) * 0.5f), ImMax(0.0f, (size.y - g.FontSize) * 0.5f)),
text_col, dir);*/
IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags);
return pressed;
}
bool StateButton(const char* str_id, const char* label) {
float sz = ImGui::GetFrameHeight();
return StateButtonEx(str_id, label, ImVec2(sz, sz), ImGuiButtonFlags_None);
}
}