10#include "absl/strings/str_format.h"
14#include "imgui/imgui.h"
24constexpr ImVec4
kColorDirty(0.9f, 0.7f, 0.1f, 1.0f);
25constexpr ImVec4
kColorInfo(0.6f, 0.6f, 0.6f, 1.0f);
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) {
38 auto it = std::search(haystack.begin(), haystack.end(), needle.begin(),
39 needle.end(), [](
char a,
char b) {
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_) {
109 std::optional<gui::StyleColorGuard> dirty_guard;
110 if (dirty_count > 0) {
111 dirty_guard.emplace(ImGuiCol_Button, kColorDirty);
116 dirty_count > 0 ? absl::StrFormat(
" (%d)", dirty_count) :
"")
122 for (
auto& flag :
flags_) {
148 ImGuiTableFlags table_flags =
149 ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersInnerH |
150 ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_Resizable;
152 if (ImGui::BeginTable(
"FeatureFlagsTable", 5, table_flags)) {
153 ImGui::TableSetupColumn(
"Toggle", ImGuiTableColumnFlags_WidthFixed, 40.0f);
154 ImGui::TableSetupColumn(
"Flag Name", ImGuiTableColumnFlags_WidthStretch);
155 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthFixed, 50.0f);
156 ImGui::TableSetupColumn(
"Status", ImGuiTableColumnFlags_WidthFixed, 70.0f);
157 ImGui::TableSetupColumn(
"Source", ImGuiTableColumnFlags_WidthStretch, 0.4f);
158 ImGui::TableHeadersRow();
162 for (
int i = 0; i < static_cast<int>(
flags_.size()); ++i) {
172 ImGui::TableNextRow();
175 ImGui::TableSetColumnIndex(0);
176 bool enabled = flag.enabled;
177 if (ImGui::Checkbox(
"##toggle", &enabled)) {
178 flag.enabled = enabled;
179 flag.value = enabled ? 1 : 0;
184 ImGui::TableSetColumnIndex(1);
186 ImGui::TextColored(kColorDirty,
"%s", flag.name.c_str());
188 ImGui::TextUnformatted(flag.name.c_str());
192 ImGui::TableSetColumnIndex(2);
193 ImGui::Text(
"%d", flag.value);
196 ImGui::TableSetColumnIndex(3);
198 ImGui::TextColored(kColorEnabled,
"ON");
200 ImGui::TextColored(kColorDisabled,
"OFF");
204 ImGui::TextColored(kColorDirty,
"*");
208 ImGui::TableSetColumnIndex(4);
209 ImGui::TextColored(kColorInfo,
"%s", flag.source.c_str());
218 int enabled_count = 0;
219 for (
const auto& flag :
flags_) {
224 ImGui::TextDisabled(
"%d/%d flags enabled", enabled_count,
225 static_cast<int>(
flags_.size()));
226 if (dirty_count > 0) {
228 ImGui::TextColored(kColorDirty,
"(%d unsaved changes)", dirty_count);
240 flags_.reserve(manifest_flags.size());
242 for (
const auto& mf : manifest_flags) {
246 ef.enabled = mf.enabled;
247 ef.source = mf.source;
249 flags_.push_back(std::move(ef));
263 std::filesystem::path(code_path) /
"Config" /
"feature_flags.asm";
264 return candidate.string();
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", parent.string());
283 std::string tmp_path = config_path +
".tmp";
285 std::ofstream out(tmp_path);
286 if (!out.is_open()) {
288 absl::StrFormat(
"Failed to open temp file: %s", tmp_path);
295 size_t max_name_len = 0;
296 for (
const auto& flag :
flags_) {
297 max_name_len = std::max(max_name_len, flag.name.size());
301 for (
const auto& flag :
flags_) {
303 std::string padded_name = flag.name;
304 while (padded_name.size() < max_name_len) {
307 out << padded_name <<
" = " << flag.value <<
"\n";
313 std::filesystem::rename(tmp_path, config_path, ec);
316 absl::StrFormat(
"Failed to rename temp file: %s", ec.message());
318 std::filesystem::remove(tmp_path, ec);
const std::vector< FeatureFlag > & feature_flags() const
bool loaded() const
Check if the manifest has been loaded.
project::YazeProject * project_
void RefreshFromManifest()
Refresh the local flags list from the hack manifest.
std::string status_message_
bool SaveToFile()
Write the current flag state to Config/feature_flags.asm.
void Draw()
Draw the panel content (no ImGui::Begin/End).
std::vector< EditableFlag > flags_
std::string ResolveConfigPath() const
Resolve the absolute path to Config/feature_flags.asm.
~FeatureFlagEditorPanel()
constexpr ImVec4 kColorDisabled(0.8f, 0.2f, 0.2f, 1.0f)
constexpr ImVec4 kColorEnabled(0.2f, 0.8f, 0.2f, 1.0f)
bool ContainsCaseInsensitive(const std::string &haystack, const std::string &needle)
constexpr const char * kFileHeader
constexpr ImVec4 kColorDirty(0.9f, 0.7f, 0.1f, 1.0f)
constexpr ImVec4 kColorInfo(0.6f, 0.6f, 0.6f, 1.0f)
core::HackManifest hack_manifest
std::string GetAbsolutePath(const std::string &relative_path) const