63 const std::vector<std::string>& input,
64 const std::vector<std::string>& order,
65 const std::unordered_set<std::string>& pinned,
66 const std::unordered_set<std::string>& hidden) {
68 std::vector<std::string> visible;
69 visible.reserve(input.size());
70 std::unordered_set<std::string> visible_set;
71 visible_set.reserve(input.size());
72 for (
const auto& c : input) {
76 visible_set.insert(c);
80 std::vector<std::string> pinned_visible;
81 std::unordered_set<std::string> pinned_visible_set;
82 for (
const auto& c : input) {
83 if (visible_set.count(c) && pinned.count(c)) {
84 pinned_visible.push_back(c);
85 pinned_visible_set.insert(c);
92 std::vector<std::string> ordered;
93 std::vector<std::string> newcomers;
96 for (
const auto& c : visible) {
97 if (pinned_visible_set.count(c))
102 std::unordered_set<std::string> ordered_set;
103 for (
const auto& c : order) {
104 if (!visible_set.count(c))
106 if (pinned_visible_set.count(c))
108 ordered.push_back(c);
109 ordered_set.insert(c);
111 std::unordered_set<std::string> order_set(order.begin(), order.end());
112 for (
const auto& c : visible) {
113 if (pinned_visible_set.count(c))
115 if (order_set.count(c))
117 newcomers.push_back(c);
119 std::sort(newcomers.begin(), newcomers.end());
122 std::vector<std::string> result;
123 result.reserve(pinned_visible.size() + ordered.size() + newcomers.size());
124 result.insert(result.end(), pinned_visible.begin(), pinned_visible.end());
125 result.insert(result.end(), ordered.begin(), ordered.end());
126 result.insert(result.end(), newcomers.begin(), newcomers.end());
248 size_t session_id,
const std::string& active_category,
249 const std::vector<std::string>& all_categories,
250 const std::unordered_set<std::string>& active_editor_categories,
251 std::function<
bool()> has_rom, std::function<
bool()> is_rom_dirty,
252 std::function<
int()> pending_dungeon_rooms) {
255 const ImGuiViewport* viewport = ImGui::GetMainViewport();
259 const float viewport_height =
260 std::max(0.0f, viewport->WorkSize.y - top_inset - safe_area.bottom -
264 constexpr ImGuiWindowFlags kExtraFlags =
265 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoFocusOnAppearing |
266 ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoBringToFrontOnFocus;
270 ImVec2(viewport->WorkPos.x, viewport->WorkPos.y + top_inset),
271 ImVec2(bar_width, viewport_height),
274 .padding = {0.0f, 8.0f},
275 .spacing = {0.0f, 8.0f},
276 .border_size = 1.0f},
283 "Global Search (Ctrl+Shift+F)",
false,
284 ImVec4(0, 0, 0, 0),
"activity_bar",
291 ImVec2 sep_p1 = ImGui::GetCursorScreenPos();
294 ImGui::GetWindowDrawList()->AddLine(
300 bool rom_loaded = has_rom ? has_rom() :
false;
304 std::vector<std::string> filtered_input;
305 filtered_input.reserve(all_categories.size());
306 for (
const auto& cat : all_categories) {
309 filtered_input.push_back(cat);
312 std::vector<std::string> effective = filtered_input;
316 prefs.sidebar_pinned, prefs.sidebar_hidden);
320 if (effective.empty() && !filtered_input.empty()) {
323 effective = filtered_input;
328 for (
const auto& cat : effective) {
329 bool is_selected = (cat == active_category);
331 bool has_active_editor = active_editor_categories.count(cat) > 0;
334 bool category_enabled =
335 rom_loaded || (cat ==
"Emulator") || (cat ==
"Agent");
339 const bool experimental =
341 const bool allow_experimental =
343 const bool blocked_experimental = experimental && !allow_experimental;
344 if (blocked_experimental) {
345 category_enabled =
false;
350 ImVec4 cat_color(cat_theme.r, cat_theme.g, cat_theme.b, cat_theme.a);
351 ImVec4 glow_color(cat_theme.glow_r, cat_theme.glow_g, cat_theme.glow_b,
355 if (is_selected && category_enabled && panel_expanded) {
356 ImVec2 pos = ImGui::GetCursorScreenPos();
359 ImVec4 outer_glow = glow_color;
360 outer_glow.w = 0.15f;
361 ImGui::GetWindowDrawList()->AddRectFilled(
362 ImVec2(pos.x - 1.0f, pos.y - 1.0f),
363 ImVec2(pos.x + 49.0f, pos.y + 41.0f),
364 ImGui::ColorConvertFloat4ToU32(outer_glow), 4.0f);
367 ImVec4 highlight = glow_color;
369 ImGui::GetWindowDrawList()->AddRectFilled(
370 pos, ImVec2(pos.x + 48.0f, pos.y + 40.0f),
371 ImGui::ColorConvertFloat4ToU32(highlight), 2.0f);
374 ImGui::GetWindowDrawList()->AddRectFilled(
375 pos, ImVec2(pos.x + 4.0f, pos.y + 40.0f),
376 ImGui::ColorConvertFloat4ToU32(cat_color));
382 if (is_selected && category_enabled && !panel_expanded) {
383 ImVec2 pos = ImGui::GetCursorScreenPos();
384 ImVec4 highlight = glow_color;
386 ImGui::GetWindowDrawList()->AddRectFilled(
387 pos, ImVec2(pos.x + 48.0f, pos.y + 40.0f),
388 ImGui::ColorConvertFloat4ToU32(highlight), 2.0f);
389 ImVec4 accent = cat_color;
391 ImGui::GetWindowDrawList()->AddRectFilled(
392 pos, ImVec2(pos.x + 3.0f, pos.y + 40.0f),
393 ImGui::ColorConvertFloat4ToU32(accent));
399 if (!is_selected && category_enabled && has_active_editor) {
400 ImVec2 pos = ImGui::GetCursorScreenPos();
401 ImVec4 dim_accent = cat_color;
402 dim_accent.w = 0.35f;
403 ImGui::GetWindowDrawList()->AddRectFilled(
404 ImVec2(pos.x + 45.0f, pos.y + 8.0f),
405 ImVec2(pos.x + 48.0f, pos.y + 32.0f),
406 ImGui::ColorConvertFloat4ToU32(dim_accent), 1.5f);
414 ImVec4 icon_color = cat_color;
415 if (!category_enabled) {
416 ImGui::BeginDisabled();
419 nullptr, is_selected, icon_color,
420 "activity_bar", cat.c_str())) {
421 if (category_enabled) {
422 if (cat == active_category) {
432 if (!category_enabled) {
433 ImGui::EndDisabled();
441 ImVec2 pin_min = ImGui::GetItemRectMin();
442 ImVec4 pin_color = cat_color;
444 ImGui::GetWindowDrawList()->AddCircleFilled(
445 ImVec2(pin_min.x + 6.0f, pin_min.y + 6.0f), 2.5f,
446 ImGui::ColorConvertFloat4ToU32(pin_color));
449 const int pending_rooms =
450 pending_dungeon_rooms ? pending_dungeon_rooms() : 0;
451 const bool dungeon_pending = cat ==
"Dungeon" && pending_rooms > 0;
455 const bool rom_dirty = is_rom_dirty ? is_rom_dirty() :
false;
456 if (is_selected && category_enabled && rom_dirty) {
457 ImVec2 last_min = ImGui::GetItemRectMin();
458 ImVec2 last_max = ImGui::GetItemRectMax();
459 ImVec2 dot_center(last_max.x - 7.0f, last_min.y + 7.0f);
461 ImGui::GetWindowDrawList()->AddCircleFilled(
462 dot_center, 3.5f, ImGui::ColorConvertFloat4ToU32(dot_color));
465 if (category_enabled && dungeon_pending) {
466 ImVec2 last_min = ImGui::GetItemRectMin();
467 ImVec2 pending_center(last_min.x + 7.0f, last_min.y + 7.0f);
469 ImGui::GetWindowDrawList()->AddCircleFilled(
470 pending_center, 3.0f,
471 ImGui::ColorConvertFloat4ToU32(pending_color));
475 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
476 ImGui::BeginTooltip();
477 ImGui::Text(
"%s %s", icon.c_str(), cat.c_str());
478 if (blocked_experimental) {
480 "In development — enable Experimental Editors in Settings",
482 }
else if (!category_enabled) {
485 }
else if (has_active_editor) {
487 is_selected ?
"Active editor" :
"Editor open (click to focus)",
500 if (is_selected && rom_dirty) {
504 if (dungeon_pending) {
506 "%d dungeon room%s pending apply", pending_rooms,
507 pending_rooms == 1 ?
"" :
"s");
515 ImGui::SetCursorPosY(viewport_height - 48.0f);
518 nullptr,
false, ImVec4(0, 0, 0, 0),
519 "activity_bar",
"more_actions")) {
520 ImGui::OpenPopup(
"ActivityBarMoreMenu");
523 if (ImGui::BeginPopup(
"ActivityBarMoreMenu")) {
527 if (action.
icon !=
nullptr) {
528 label = absl::StrFormat(
"%s %s", action.icon, action.label);
530 label = action.label;
533 if (ImGui::MenuItem(label.c_str(),
nullptr,
540 ImGui::TextDisabled(tr(
"No actions available"));