29 "; Feature override flags (for isolation testing).\n"
30 "; Set to 1 to enable a feature, 0 to disable it.\n"
31 "; This file is included after Util/macros.asm and overrides defaults.\n"
32 "; Generated by yaze Feature Flag Editor.\n\n";
35 const std::string& needle) {
36 if (needle.empty())
return true;
37 auto it = std::search(
38 haystack.begin(), haystack.end(), needle.begin(), needle.end(),
40 return std::tolower(static_cast<unsigned char>(a)) ==
41 std::tolower(static_cast<unsigned char>(b));
43 return it != haystack.end();
53 ImGui::TextDisabled(
"No project loaded.");
55 "Open a yaze project with a hack_manifest.json to view feature flags.");
60 if (!manifest.loaded()) {
61 ImGui::TextDisabled(
"No hack manifest loaded.");
63 "The project does not have a hack_manifest.json, or it failed to "
64 "load. Check the project settings to configure the manifest path.");
75 ImGui::TextColored(ImVec4(0.7f, 0.7f, 1.0f, 1.0f),
ICON_MD_FLAG " %s",
76 manifest.hack_name().c_str());
78 ImGui::TextDisabled(
"(%d flags)",
static_cast<int>(
flags_.size()));
82 if (!config_path.empty()) {
83 ImGui::TextDisabled(
"Config: %s", config_path.c_str());
89 float save_width = ImGui::CalcTextSize(
ICON_MD_SAVE " Save").x +
90 ImGui::GetStyle().FramePadding.x * 2.0f;
91 float refresh_width = ImGui::CalcTextSize(
ICON_MD_REFRESH " Refresh").x +
92 ImGui::GetStyle().FramePadding.x * 2.0f;
93 float filter_width = ImGui::GetContentRegionAvail().x - save_width -
94 refresh_width - ImGui::GetStyle().ItemSpacing.x * 2.0f;
96 ImGui::SetNextItemWidth(std::max(100.0f, filter_width));
97 ImGui::InputTextWithHint(
"##flag_filter",
"Filter flags...",
filter_text_,
103 for (
const auto& flag :
flags_) {
104 if (flag.dirty) dirty_count++;
108 std::optional<gui::StyleColorGuard> dirty_guard;
109 if (dirty_count > 0) {
110 dirty_guard.emplace(ImGuiCol_Button, kColorDirty);
115 ? absl::StrFormat(
" (%d)", dirty_count)
122 for (
auto& flag :
flags_) {
148 ImGuiTableFlags table_flags = ImGuiTableFlags_RowBg |
149 ImGuiTableFlags_BordersInnerH |
150 ImGuiTableFlags_SizingStretchProp |
151 ImGuiTableFlags_Resizable;
153 if (ImGui::BeginTable(
"FeatureFlagsTable", 5, table_flags)) {
154 ImGui::TableSetupColumn(
"Toggle", ImGuiTableColumnFlags_WidthFixed, 40.0f);
155 ImGui::TableSetupColumn(
"Flag Name", ImGuiTableColumnFlags_WidthStretch);
156 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthFixed, 50.0f);
157 ImGui::TableSetupColumn(
"Status", ImGuiTableColumnFlags_WidthFixed, 70.0f);
158 ImGui::TableSetupColumn(
"Source", ImGuiTableColumnFlags_WidthStretch,
160 ImGui::TableHeadersRow();
164 for (
int i = 0; i < static_cast<int>(
flags_.size()); ++i) {
168 if (!filter.empty() &&
169 !ContainsCaseInsensitive(flag.name, filter) &&
170 !ContainsCaseInsensitive(flag.source, filter)) {
175 ImGui::TableNextRow();
178 ImGui::TableSetColumnIndex(0);
179 bool enabled = flag.enabled;
180 if (ImGui::Checkbox(
"##toggle", &enabled)) {
181 flag.enabled = enabled;
182 flag.value = enabled ? 1 : 0;
187 ImGui::TableSetColumnIndex(1);
189 ImGui::TextColored(kColorDirty,
"%s", flag.name.c_str());
191 ImGui::TextUnformatted(flag.name.c_str());
195 ImGui::TableSetColumnIndex(2);
196 ImGui::Text(
"%d", flag.value);
199 ImGui::TableSetColumnIndex(3);
201 ImGui::TextColored(kColorEnabled,
"ON");
203 ImGui::TextColored(kColorDisabled,
"OFF");
207 ImGui::TextColored(kColorDirty,
"*");
211 ImGui::TableSetColumnIndex(4);
212 ImGui::TextColored(kColorInfo,
"%s", flag.source.c_str());
221 int enabled_count = 0;
222 for (
const auto& flag :
flags_) {
223 if (flag.enabled) enabled_count++;
226 ImGui::TextDisabled(
"%d/%d flags enabled", enabled_count,
227 static_cast<int>(
flags_.size()));
228 if (dirty_count > 0) {
230 ImGui::TextColored(kColorDirty,
"(%d unsaved changes)", dirty_count);
269 if (config_path.empty()) {
270 status_message_ =
"Cannot determine config file path (no code folder set).";
275 auto parent = std::filesystem::path(config_path).parent_path();
276 if (!std::filesystem::exists(parent)) {
278 absl::StrFormat(
"Config directory does not exist: %s",
284 std::string tmp_path = config_path +
".tmp";
286 std::ofstream out(tmp_path);
287 if (!out.is_open()) {
289 absl::StrFormat(
"Failed to open temp file: %s", tmp_path);
296 size_t max_name_len = 0;
297 for (
const auto& flag :
flags_) {
298 max_name_len = std::max(max_name_len, flag.name.size());
302 for (
const auto& flag :
flags_) {
304 std::string padded_name = flag.name;
305 while (padded_name.size() < max_name_len) {
308 out << padded_name <<
" = " << flag.value <<
"\n";
314 std::filesystem::rename(tmp_path, config_path, ec);
317 absl::StrFormat(
"Failed to rename temp file: %s", ec.message());
319 std::filesystem::remove(tmp_path, ec);