68 const std::vector<std::string>& input,
69 const std::vector<std::string>& order,
70 const std::unordered_set<std::string>& pinned,
71 const std::unordered_set<std::string>& hidden) {
73 std::vector<std::string> visible;
74 visible.reserve(input.size());
75 std::unordered_set<std::string> visible_set;
76 visible_set.reserve(input.size());
77 for (
const auto& c : input) {
81 visible_set.insert(c);
85 std::vector<std::string> pinned_visible;
86 std::unordered_set<std::string> pinned_visible_set;
87 for (
const auto& c : input) {
88 if (visible_set.count(c) && pinned.count(c)) {
89 pinned_visible.push_back(c);
90 pinned_visible_set.insert(c);
97 std::vector<std::string> ordered;
98 std::vector<std::string> newcomers;
101 for (
const auto& c : visible) {
102 if (pinned_visible_set.count(c))
104 ordered.push_back(c);
107 std::unordered_set<std::string> ordered_set;
108 for (
const auto& c : order) {
109 if (!visible_set.count(c))
111 if (pinned_visible_set.count(c))
113 ordered.push_back(c);
114 ordered_set.insert(c);
116 std::unordered_set<std::string> order_set(order.begin(), order.end());
117 for (
const auto& c : visible) {
118 if (pinned_visible_set.count(c))
120 if (order_set.count(c))
122 newcomers.push_back(c);
124 std::sort(newcomers.begin(), newcomers.end());
127 std::vector<std::string> result;
128 result.reserve(pinned_visible.size() + ordered.size() + newcomers.size());
129 result.insert(result.end(), pinned_visible.begin(), pinned_visible.end());
130 result.insert(result.end(), ordered.begin(), ordered.end());
131 result.insert(result.end(), newcomers.begin(), newcomers.end());
257 size_t session_id,
const std::string& active_category,
258 const std::vector<std::string>& all_categories,
259 const std::unordered_set<std::string>& active_editor_categories,
260 std::function<
bool()> has_rom, std::function<
bool()> is_rom_dirty,
261 std::function<
int()> pending_dungeon_rooms) {
264 const ImGuiViewport* viewport = ImGui::GetMainViewport();
268 const float viewport_height =
269 std::max(0.0f, viewport->WorkSize.y - top_inset - safe_area.bottom -
273 constexpr ImGuiWindowFlags kExtraFlags =
274 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoFocusOnAppearing |
275 ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoBringToFrontOnFocus;
279 ImVec2(viewport->WorkPos.x, viewport->WorkPos.y + top_inset),
280 ImVec2(bar_width, viewport_height),
283 .padding = {0.0f, 6.0f},
284 .spacing = {0.0f, 4.0f},
285 .border_size = 0.0f},
292 "Global Search (Ctrl+Shift+F)",
false,
293 ImVec4(0, 0, 0, 0),
"activity_bar",
300 ImVec2 sep_p1 = ImGui::GetCursorScreenPos();
303 ImGui::GetWindowDrawList()->AddLine(
309 bool rom_loaded = has_rom ? has_rom() :
false;
313 std::vector<std::string> filtered_input;
314 filtered_input.reserve(all_categories.size());
315 for (
const auto& cat : all_categories) {
318 filtered_input.push_back(cat);
321 std::vector<std::string> effective = filtered_input;
325 prefs.sidebar_pinned, prefs.sidebar_hidden);
329 if (effective.empty() && !filtered_input.empty()) {
332 effective = filtered_input;
337 for (
const auto& cat : effective) {
338 bool is_selected = (cat == active_category);
340 bool has_active_editor = active_editor_categories.count(cat) > 0;
343 bool category_enabled = rom_loaded || (cat ==
"Emulator") ||
344 (cat ==
"Agent") || (cat ==
"Planning");
348 const bool experiments_enabled =
350 const bool experimental_warning =
352 editor_type, experiments_enabled);
356 ImVec4 cat_color(cat_theme.r, cat_theme.g, cat_theme.b, cat_theme.a);
357 ImVec4 glow_color(cat_theme.glow_r, cat_theme.glow_g, cat_theme.glow_b,
361 if (is_selected && category_enabled && panel_expanded) {
362 ImVec2 pos = ImGui::GetCursorScreenPos();
365 ImVec4 outer_glow = glow_color;
366 outer_glow.w = 0.15f;
367 ImGui::GetWindowDrawList()->AddRectFilled(
368 ImVec2(pos.x - 1.0f, pos.y - 1.0f),
369 ImVec2(pos.x + 49.0f, pos.y + 41.0f),
370 ImGui::ColorConvertFloat4ToU32(outer_glow), 4.0f);
373 ImVec4 highlight = glow_color;
375 ImGui::GetWindowDrawList()->AddRectFilled(
376 pos, ImVec2(pos.x + 48.0f, pos.y + 40.0f),
377 ImGui::ColorConvertFloat4ToU32(highlight), 2.0f);
380 ImGui::GetWindowDrawList()->AddRectFilled(
381 pos, ImVec2(pos.x + 4.0f, pos.y + 40.0f),
382 ImGui::ColorConvertFloat4ToU32(cat_color));
388 if (is_selected && category_enabled && !panel_expanded) {
389 ImVec2 pos = ImGui::GetCursorScreenPos();
390 ImVec4 highlight = glow_color;
392 ImGui::GetWindowDrawList()->AddRectFilled(
393 pos, ImVec2(pos.x + 48.0f, pos.y + 40.0f),
394 ImGui::ColorConvertFloat4ToU32(highlight), 2.0f);
395 ImVec4 accent = cat_color;
397 ImGui::GetWindowDrawList()->AddRectFilled(
398 pos, ImVec2(pos.x + 3.0f, pos.y + 40.0f),
399 ImGui::ColorConvertFloat4ToU32(accent));
405 if (!is_selected && category_enabled && has_active_editor) {
406 ImVec2 pos = ImGui::GetCursorScreenPos();
407 ImVec4 dim_accent = cat_color;
408 dim_accent.w = 0.35f;
409 ImGui::GetWindowDrawList()->AddRectFilled(
410 ImVec2(pos.x + 45.0f, pos.y + 8.0f),
411 ImVec2(pos.x + 48.0f, pos.y + 32.0f),
412 ImGui::ColorConvertFloat4ToU32(dim_accent), 1.5f);
420 ImVec4 icon_color = cat_color;
421 if (!category_enabled) {
422 ImGui::BeginDisabled();
425 nullptr, is_selected, icon_color,
426 "activity_bar", cat.c_str())) {
427 if (category_enabled) {
428 if (cat == active_category) {
438 if (!category_enabled) {
439 ImGui::EndDisabled();
447 ImVec2 pin_min = ImGui::GetItemRectMin();
448 ImVec4 pin_color = cat_color;
450 ImGui::GetWindowDrawList()->AddCircleFilled(
451 ImVec2(pin_min.x + 6.0f, pin_min.y + 6.0f), 2.5f,
452 ImGui::ColorConvertFloat4ToU32(pin_color));
455 const int pending_rooms =
456 pending_dungeon_rooms ? pending_dungeon_rooms() : 0;
457 const bool dungeon_pending = cat ==
"Dungeon" && pending_rooms > 0;
461 const bool rom_dirty = is_rom_dirty ? is_rom_dirty() :
false;
462 if (is_selected && category_enabled && rom_dirty) {
463 ImVec2 last_min = ImGui::GetItemRectMin();
464 ImVec2 last_max = ImGui::GetItemRectMax();
465 ImVec2 dot_center(last_max.x - 7.0f, last_min.y + 7.0f);
467 ImGui::GetWindowDrawList()->AddCircleFilled(
468 dot_center, 3.5f, ImGui::ColorConvertFloat4ToU32(dot_color));
471 if (category_enabled && dungeon_pending) {
472 ImVec2 last_min = ImGui::GetItemRectMin();
473 ImVec2 pending_center(last_min.x + 7.0f, last_min.y + 7.0f);
475 ImGui::GetWindowDrawList()->AddCircleFilled(
476 pending_center, 3.0f,
477 ImGui::ColorConvertFloat4ToU32(pending_color));
481 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
482 ImGui::BeginTooltip();
483 ImGui::Text(
"%s %s", icon.c_str(), cat.c_str());
484 if (!category_enabled) {
487 }
else if (experimental_warning) {
489 "Experimental — available with defensive save safeguards",
491 }
else if (has_active_editor) {
493 is_selected ?
"Active editor" :
"Editor open (click to focus)",
506 if (is_selected && rom_dirty) {
510 if (dungeon_pending) {
512 "%d dungeon room%s pending apply", pending_rooms,
513 pending_rooms == 1 ?
"" :
"s");
521 ImGui::SetCursorPosY(viewport_height - 48.0f);
524 nullptr,
false, ImVec4(0, 0, 0, 0),
525 "activity_bar",
"more_actions")) {
526 ImGui::OpenPopup(
"ActivityBarMoreMenu");
529 if (ImGui::BeginPopup(
"ActivityBarMoreMenu")) {
533 if (action.
icon !=
nullptr) {
534 label = absl::StrFormat(
"%s %s", action.icon, action.label);
536 label = action.label;
539 if (ImGui::MenuItem(label.c_str(),
nullptr,
546 ImGui::TextDisabled(tr(
"No actions available"));