patcher: make the checkbox label toggleable (#882)

Requested by a certain sea creature. Don't know why ImGui doesn't do
this by default.

Also, use ImGui internal mixed state (tri-state) checkbox instead
rendering our own.
This commit is contained in:
bicarus
2026-08-20 23:48:11 -07:00
committed by GitHub
parent 4c2a70bab6
commit 2dc3c0cbe4
+40 -37
View File
@@ -7,6 +7,7 @@
#include <unordered_set> #include <unordered_set>
#include "avs/game.h" #include "avs/game.h"
#include "cfg/configurator.h" #include "cfg/configurator.h"
#include "external/imgui/imgui_internal.h"
#include "games/io.h" #include "games/io.h"
#include "launcher/launcher.h" #include "launcher/launcher.h"
#include "misc/clipboard.h" #include "misc/clipboard.h"
@@ -215,19 +216,14 @@ namespace overlay::windows {
return name; return name;
} }
// draw a square checkmark for mixed-state groups (neither checked or unchecked) // the state text next to a toggle is the checkbox label, so it has to dim itself instead
void render_patch_group_mixed_checkbox_mark() { // of being wrapped in BeginDisabled (which would also kill the click area)
const auto check_min = ImGui::GetItemRectMin(); void push_toggle_label_color(bool checked) {
const float check_size = ImGui::GetFrameHeight(); auto color = ImGui::GetStyleColorVec4(ImGuiCol_Text);
const float calculated_padding = check_size / 3.6f; if (!checked) {
const float padding = calculated_padding < 1.0f ? 1.0f : calculated_padding; color.w *= ImGui::GetStyle().DisabledAlpha;
ImGui::GetWindowDrawList()->AddRectFilled( }
ImVec2(check_min.x + padding, check_min.y + padding), ImGui::PushStyleColor(ImGuiCol_Text, color);
ImVec2(
check_min.x + check_size - padding,
check_min.y + check_size - padding),
ImGui::GetColorU32(ImGuiCol_CheckMark),
ImGui::GetStyle().FrameRounding);
} }
// render a tri-state checkbox for groups // render a tri-state checkbox for groups
@@ -237,18 +233,24 @@ namespace overlay::windows {
const std::vector<size_t>& members, const std::vector<size_t>& members,
bool& checked) { bool& checked) {
const bool mixed = state.status == PatchGroupStatus::Mixed; const bool mixed = state.status == PatchGroupStatus::Mixed;
ImGui::BeginDisabled(state.status == PatchGroupStatus::Error); const bool error = state.status == PatchGroupStatus::Error;
ImGui::BeginDisabled(error);
if (mixed) { if (mixed) {
ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(0.f, 0.f, 0.f, 0.f)); ImGui::PushItemFlag(ImGuiItemFlags_MixedValue, true);
} }
const bool changed = ImGui::Checkbox("##group_checked_checkbox", &checked); // the state text doubles as the checkbox label so clicking it toggles the group;
if (mixed) { // errored groups show the failing patch there instead
const char *label = "##group_checked_checkbox";
if (!error) {
label = mixed ? "mixed" : (checked ? "ON" : "off");
}
push_toggle_label_color(checked);
const bool changed = ImGui::Checkbox(label, &checked);
ImGui::PopStyleColor(); ImGui::PopStyleColor();
if (mixed) {
ImGui::PopItemFlag();
} }
ImGui::EndDisabled(); ImGui::EndDisabled();
if (mixed && !changed) {
render_patch_group_mixed_checkbox_mark();
}
if (!changed) { if (!changed) {
return false; return false;
} }
@@ -266,11 +268,11 @@ namespace overlay::windows {
return true; return true;
} }
// render aggregate status or error text beside the group checkbox // render error text beside the group checkbox; other states are shown as its label
void render_patch_group_status(const PatchGroupState& state, bool checked) { void render_patch_group_status(const PatchGroupState& state) {
if (state.status == PatchGroupStatus::Error) {
ImGui::SameLine(); ImGui::SameLine();
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
if (state.status == PatchGroupStatus::Error) {
const auto& error_patch = patcher::patches[state.first_error_index]; const auto& error_patch = patcher::patches[state.first_error_index];
const auto error_reason = error_patch.error_reason.empty() const auto error_reason = error_patch.error_reason.empty()
? "Unknown error" ? "Unknown error"
@@ -279,12 +281,6 @@ namespace overlay::windows {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 0.f, 0.f, 1.f)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 0.f, 0.f, 1.f));
ImGui::TextUnformatted(error_text.c_str()); ImGui::TextUnformatted(error_text.c_str());
ImGui::PopStyleColor(); ImGui::PopStyleColor();
} else if (state.status == PatchGroupStatus::Mixed) {
ImGui::TextUnformatted("mixed");
} else {
ImGui::BeginDisabled(!checked);
ImGui::TextUnformatted(checked ? "ON" : "off");
ImGui::EndDisabled();
} }
} }
@@ -311,7 +307,7 @@ namespace overlay::windows {
if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) { if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
show_patch_group_tooltip(group); show_patch_group_tooltip(group);
} }
render_patch_group_status(group_state, group_checked); render_patch_group_status(group_state);
ImGui::TableSetColumnIndex(0); ImGui::TableSetColumnIndex(0);
const auto group_name = get_patch_group_display_name( const auto group_name = get_patch_group_display_name(
@@ -874,8 +870,18 @@ namespace overlay::windows {
render_patch_group_child_gutter(last_group_child); render_patch_group_child_gutter(last_group_child);
ImGui::SameLine(); ImGui::SameLine();
} }
// plain on/off patches have no extra widget, so the state text doubles as the
// checkbox label - clicking the text toggles it, like the Options tab
const bool has_extra_widget =
patch_status == patcher::PatchStatus::Error
|| patch.type == patcher::PatchType::Union
|| patch.type == patcher::PatchType::Integer;
const char *checkbox_label = has_extra_widget
? "##patch_checked_checkbox"
: (patch_checked ? "ON" : "off");
push_toggle_label_color(patch_checked);
ImGui::BeginDisabled(patch_status == patcher::PatchStatus::Error); ImGui::BeginDisabled(patch_status == patcher::PatchStatus::Error);
if (ImGui::Checkbox("##patch_checked_checkbox", &patch_checked)) { if (ImGui::Checkbox(checkbox_label, &patch_checked)) {
patcher::config_dirty = true; patcher::config_dirty = true;
switch (patch_status) { switch (patch_status) {
case patcher::PatchStatus::Enabled: case patcher::PatchStatus::Enabled:
@@ -901,13 +907,14 @@ namespace overlay::windows {
patch.last_status = patcher::is_patch_active(patch); patch.last_status = patcher::is_patch_active(patch);
} }
ImGui::EndDisabled(); ImGui::EndDisabled();
ImGui::PopStyleColor();
if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) { if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
show_patch_tooltip(patch); show_patch_tooltip(patch);
} }
// second column, part 2: additional options UI (dropdown, text input) // second column, part 2: additional options UI (dropdown, text input)
ImGui::SameLine();
if (patch_status == patcher::PatchStatus::Error){ if (patch_status == patcher::PatchStatus::Error){
ImGui::SameLine();
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 0.f, 0.f, 1.f)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 0.f, 0.f, 1.f));
if (patch.error_reason.empty()) { if (patch.error_reason.empty()) {
@@ -917,6 +924,7 @@ namespace overlay::windows {
} }
ImGui::PopStyleColor(); ImGui::PopStyleColor();
} else if (patch.type == patcher::PatchType::Union || patch.type == patcher::PatchType::Integer) { } else if (patch.type == patcher::PatchType::Union || patch.type == patcher::PatchType::Integer) {
ImGui::SameLine();
if (patch_status == patcher::PatchStatus::Enabled) { if (patch_status == patcher::PatchStatus::Enabled) {
if (patch.type == patcher::PatchType::Union) { if (patch.type == patcher::PatchType::Union) {
set_patch_option_width(); set_patch_option_width();
@@ -970,11 +978,6 @@ namespace overlay::windows {
show_patch_tooltip(patch); show_patch_tooltip(patch);
} }
} }
} else {
ImGui::AlignTextToFramePadding();
ImGui::BeginDisabled(!patch_checked);
ImGui::TextUnformatted(patch_checked ? "ON" : "off");
ImGui::EndDisabled();
} }
ImGui::HighlightTableRowOnHover(); ImGui::HighlightTableRowOnHover();