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