51 const std::vector<std::string>& input,
52 const std::vector<std::string>& order,
53 const std::unordered_set<std::string>& pinned,
54 const std::unordered_set<std::string>& hidden) {
56 std::vector<std::string> visible;
57 visible.reserve(input.size());
58 std::unordered_set<std::string> visible_set;
59 visible_set.reserve(input.size());
60 for (
const auto& c : input) {
61 if (hidden.count(c))
continue;
63 visible_set.insert(c);
67 std::vector<std::string> pinned_visible;
68 std::unordered_set<std::string> pinned_visible_set;
69 for (
const auto& c : input) {
70 if (visible_set.count(c) && pinned.count(c)) {
71 pinned_visible.push_back(c);
72 pinned_visible_set.insert(c);
79 std::vector<std::string> ordered;
80 std::vector<std::string> newcomers;
83 for (
const auto& c : visible) {
84 if (pinned_visible_set.count(c))
continue;
88 std::unordered_set<std::string> ordered_set;
89 for (
const auto& c : order) {
90 if (!visible_set.count(c))
continue;
91 if (pinned_visible_set.count(c))
continue;
93 ordered_set.insert(c);
95 std::unordered_set<std::string> order_set(order.begin(), order.end());
96 for (
const auto& c : visible) {
97 if (pinned_visible_set.count(c))
continue;
98 if (order_set.count(c))
continue;
99 newcomers.push_back(c);
101 std::sort(newcomers.begin(), newcomers.end());
104 std::vector<std::string> result;
105 result.reserve(pinned_visible.size() + ordered.size() + newcomers.size());
106 result.insert(result.end(), pinned_visible.begin(), pinned_visible.end());
107 result.insert(result.end(), ordered.begin(), ordered.end());
108 result.insert(result.end(), newcomers.begin(), newcomers.end());
190 if (!is_pinned && ImGui::BeginDragDropSource(
191 ImGuiDragDropFlags_SourceAllowNullID)) {
192 ImGui::SetDragDropPayload(kSidebarDragPayload, category.data(),
194 ImGui::TextUnformatted(category.c_str());
195 ImGui::EndDragDropSource();
198 if (ImGui::BeginDragDropTarget()) {
199 const ImGuiPayload* payload =
200 ImGui::AcceptDragDropPayload(kSidebarDragPayload);
201 if (payload !=
nullptr && payload->Data !=
nullptr) {
202 std::string src(
static_cast<const char*
>(payload->Data),
203 static_cast<size_t>(payload->DataSize));
204 if (src != category && !prefs.sidebar_pinned.count(src) &&
205 !prefs.sidebar_pinned.count(category)) {
206 auto& order = prefs.sidebar_order;
207 auto rm = std::remove(order.begin(), order.end(), src);
208 if (rm != order.end()) {
209 order.erase(rm, order.end());
211 auto dst = std::find(order.begin(), order.end(), category);
212 if (dst == order.end()) {
214 order.push_back(src);
216 order.insert(dst, src);
221 ImGui::EndDragDropTarget();
226 size_t session_id,
const std::string& active_category,
227 const std::vector<std::string>& all_categories,
228 const std::unordered_set<std::string>& active_editor_categories,
229 std::function<
bool()> has_rom, std::function<
bool()> is_rom_dirty) {
232 const ImGuiViewport* viewport = ImGui::GetMainViewport();
235 const float viewport_height =
236 std::max(0.0f, viewport->WorkSize.y - top_inset - safe_area.bottom);
239 constexpr ImGuiWindowFlags kExtraFlags =
240 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoFocusOnAppearing |
241 ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoBringToFrontOnFocus;
245 ImVec2(viewport->WorkPos.x, viewport->WorkPos.y + top_inset),
246 ImVec2(bar_width, viewport_height),
249 .padding = {0.0f, 8.0f},
250 .spacing = {0.0f, 8.0f},
251 .border_size = 1.0f},
258 "Global Search (Ctrl+Shift+F)",
false,
259 ImVec4(0, 0, 0, 0),
"activity_bar",
266 ImVec2 sep_p1 = ImGui::GetCursorScreenPos();
269 ImGui::GetWindowDrawList()->AddLine(
275 bool rom_loaded = has_rom ? has_rom() :
false;
279 std::vector<std::string> filtered_input;
280 filtered_input.reserve(all_categories.size());
281 for (
const auto& cat : all_categories) {
283 filtered_input.push_back(cat);
286 std::vector<std::string> effective = filtered_input;
290 prefs.sidebar_pinned, prefs.sidebar_hidden);
294 if (effective.empty() && !filtered_input.empty()) {
297 effective = filtered_input;
302 for (
const auto& cat : effective) {
303 bool is_selected = (cat == active_category);
305 bool has_active_editor = active_editor_categories.count(cat) > 0;
308 bool category_enabled =
309 rom_loaded || (cat ==
"Emulator") || (cat ==
"Agent");
313 ImVec4 cat_color(cat_theme.r, cat_theme.g, cat_theme.b, cat_theme.a);
314 ImVec4 glow_color(cat_theme.glow_r, cat_theme.glow_g, cat_theme.glow_b,
318 if (is_selected && category_enabled && panel_expanded) {
319 ImVec2 pos = ImGui::GetCursorScreenPos();
322 ImVec4 outer_glow = glow_color;
323 outer_glow.w = 0.15f;
324 ImGui::GetWindowDrawList()->AddRectFilled(
325 ImVec2(pos.x - 1.0f, pos.y - 1.0f),
326 ImVec2(pos.x + 49.0f, pos.y + 41.0f),
327 ImGui::ColorConvertFloat4ToU32(outer_glow), 4.0f);
330 ImVec4 highlight = glow_color;
332 ImGui::GetWindowDrawList()->AddRectFilled(
333 pos, ImVec2(pos.x + 48.0f, pos.y + 40.0f),
334 ImGui::ColorConvertFloat4ToU32(highlight), 2.0f);
337 ImGui::GetWindowDrawList()->AddRectFilled(
338 pos, ImVec2(pos.x + 4.0f, pos.y + 40.0f),
339 ImGui::ColorConvertFloat4ToU32(cat_color));
345 if (is_selected && category_enabled && !panel_expanded) {
346 ImVec2 pos = ImGui::GetCursorScreenPos();
347 ImVec4 highlight = glow_color;
349 ImGui::GetWindowDrawList()->AddRectFilled(
350 pos, ImVec2(pos.x + 48.0f, pos.y + 40.0f),
351 ImGui::ColorConvertFloat4ToU32(highlight), 2.0f);
352 ImVec4 accent = cat_color;
354 ImGui::GetWindowDrawList()->AddRectFilled(
355 pos, ImVec2(pos.x + 3.0f, pos.y + 40.0f),
356 ImGui::ColorConvertFloat4ToU32(accent));
362 if (!is_selected && category_enabled && has_active_editor) {
363 ImVec2 pos = ImGui::GetCursorScreenPos();
364 ImVec4 dim_accent = cat_color;
365 dim_accent.w = 0.35f;
366 ImGui::GetWindowDrawList()->AddRectFilled(
367 ImVec2(pos.x + 45.0f, pos.y + 8.0f),
368 ImVec2(pos.x + 48.0f, pos.y + 32.0f),
369 ImGui::ColorConvertFloat4ToU32(dim_accent), 1.5f);
378 ImVec4 icon_color = cat_color;
379 if (!category_enabled) {
380 ImGui::BeginDisabled();
383 nullptr, is_selected, icon_color,
384 "activity_bar", cat.c_str())) {
385 if (category_enabled) {
386 if (cat == active_category && panel_expanded) {
396 if (!category_enabled) {
397 ImGui::EndDisabled();
405 ImVec2 pin_min = ImGui::GetItemRectMin();
406 ImVec4 pin_color = cat_color;
408 ImGui::GetWindowDrawList()->AddCircleFilled(
409 ImVec2(pin_min.x + 6.0f, pin_min.y + 6.0f), 2.5f,
410 ImGui::ColorConvertFloat4ToU32(pin_color));
415 const bool rom_dirty = is_rom_dirty ? is_rom_dirty() :
false;
416 if (is_selected && category_enabled && rom_dirty) {
417 ImVec2 last_min = ImGui::GetItemRectMin();
418 ImVec2 last_max = ImGui::GetItemRectMax();
419 ImVec2 dot_center(last_max.x - 7.0f, last_min.y + 7.0f);
421 ImGui::GetWindowDrawList()->AddCircleFilled(
422 dot_center, 3.5f, ImGui::ColorConvertFloat4ToU32(dot_color));
426 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
427 ImGui::BeginTooltip();
428 ImGui::Text(
"%s %s", icon.c_str(), cat.c_str());
429 if (!category_enabled) {
432 }
else if (has_active_editor) {
434 :
"Editor open (click to focus)",
447 if (is_selected && rom_dirty) {
457 ImGui::SetCursorPosY(viewport_height - 48.0f);
460 nullptr,
false, ImVec4(0, 0, 0, 0),
461 "activity_bar",
"more_actions")) {
462 ImGui::OpenPopup(
"ActivityBarMoreMenu");
465 if (ImGui::BeginPopup(
"ActivityBarMoreMenu")) {
469 if (action.
icon !=
nullptr) {
470 label = absl::StrFormat(
"%s %s", action.icon, action.label);
472 label = action.label;
475 if (ImGui::MenuItem(label.c_str(),
nullptr,
481 ImGui::TextDisabled(
"No actions available");