42 size_t session_id,
const std::string& active_category,
43 const std::vector<std::string>& all_categories,
44 const std::unordered_set<std::string>& active_editor_categories,
45 std::function<
bool()> has_rom) {
48 const ImGuiViewport* viewport = ImGui::GetMainViewport();
49 const float viewport_height = viewport->WorkSize.y;
50 const float bar_width = 48.0f;
53 ImGui::SetNextWindowPos(ImVec2(viewport->WorkPos.x, viewport->WorkPos.y));
54 ImGui::SetNextWindowSize(ImVec2(bar_width, viewport_height));
59 ImGuiWindowFlags flags =
60 ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
61 ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse |
62 ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoScrollbar |
63 ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNavFocus |
64 ImGuiWindowFlags_NoBringToFrontOnFocus;
66 ImGui::PushStyleColor(ImGuiCol_WindowBg, bar_bg);
67 ImGui::PushStyleColor(ImGuiCol_Border, bar_border);
68 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 8.0f));
69 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,
71 ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
73 if (ImGui::Begin(
"##ActivityBar",
nullptr, flags)) {
77 "Global Search (Ctrl+Shift+F)",
false)) {
83 ImVec2 sep_p1 = ImGui::GetCursorScreenPos();
84 ImVec2 sep_p2 = ImVec2(sep_p1.x + 48.0f, sep_p1.y);
85 ImGui::GetWindowDrawList()->AddLine(
91 bool rom_loaded = has_rom ? has_rom() :
false;
94 for (
const auto& cat : all_categories) {
97 bool has_active_editor = active_editor_categories.count(cat) > 0;
100 bool category_enabled = rom_loaded || (cat ==
"Emulator");
104 ImVec4 cat_color(cat_theme.r, cat_theme.g, cat_theme.b, cat_theme.a);
105 ImVec4 glow_color(cat_theme.glow_r, cat_theme.glow_g, cat_theme.glow_b,
109 if (is_selected && category_enabled) {
110 ImVec2 pos = ImGui::GetCursorScreenPos();
113 ImVec4 outer_glow = glow_color;
114 outer_glow.w = 0.15f;
115 ImGui::GetWindowDrawList()->AddRectFilled(
116 ImVec2(pos.x - 1.0f, pos.y - 1.0f),
117 ImVec2(pos.x + 49.0f, pos.y + 41.0f),
118 ImGui::ColorConvertFloat4ToU32(outer_glow), 4.0f);
121 ImVec4 highlight = glow_color;
123 ImGui::GetWindowDrawList()->AddRectFilled(
124 pos, ImVec2(pos.x + 48.0f, pos.y + 40.0f),
125 ImGui::ColorConvertFloat4ToU32(highlight), 2.0f);
128 ImGui::GetWindowDrawList()->AddRectFilled(
129 pos, ImVec2(pos.x + 4.0f, pos.y + 40.0f),
130 ImGui::ColorConvertFloat4ToU32(cat_color));
137 is_selected ? cat_color : ImVec4(0, 0, 0, 0);
139 nullptr, is_selected, icon_color)) {
140 if (category_enabled) {
153 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
154 ImGui::BeginTooltip();
155 ImGui::Text(
"%s %s", icon.c_str(), cat.c_str());
156 if (!category_enabled) {
158 "Open ROM required");
159 }
else if (has_active_editor) {
160 ImGui::PushStyleColor(ImGuiCol_Text,
162 ImGui::TextUnformatted(
"Editor open");
163 ImGui::PopStyleColor();
166 ImGui::TextUnformatted(
"Click to view panels");
167 ImGui::PopStyleColor();
175 ImGui::SetCursorPosY(viewport_height - 48.0f);
178 ImGui::OpenPopup(
"ActivityBarMoreMenu");
181 if (ImGui::BeginPopup(
"ActivityBarMoreMenu")) {
196 if (ImGui::MenuItem(
"Reset Layout")) {
204 ImGui::PopStyleVar(3);
205 ImGui::PopStyleColor(2);
209 std::function<
bool()> has_rom) {
212 const ImGuiViewport* viewport = ImGui::GetMainViewport();
214 const float panel_width =
217 ImGui::SetNextWindowPos(
218 ImVec2(viewport->WorkPos.x + bar_width, viewport->WorkPos.y));
219 ImGui::SetNextWindowSize(ImVec2(panel_width, viewport->WorkSize.y));
223 ImGuiWindowFlags flags =
224 ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
225 ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse |
226 ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoFocusOnAppearing;
228 ImGui::PushStyleColor(ImGuiCol_WindowBg, panel_bg);
229 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
230 ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
231 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,
232 ImVec2(12.0f, 12.0f));
234 if (ImGui::Begin(
"##SidePanel",
nullptr, flags)) {
236 ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]);
237 ImGui::Text(
"%s", category.c_str());
241 float avail_width = ImGui::GetContentRegionAvail().x;
242 float button_size = 28.0f;
243 float spacing = 4.0f;
244 float current_x = ImGui::GetCursorPosX() + avail_width - button_size;
247 ImGui::SameLine(current_x);
248 ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 4.0f);
250 ImVec2(button_size, button_size))) {
253 if (ImGui::IsItemHovered()) {
254 ImGui::SetTooltip(
"Collapse Panel");
258 current_x -= (button_size + spacing);
259 ImGui::SameLine(current_x);
261 ImVec2(button_size, button_size))) {
264 if (ImGui::IsItemHovered()) {
265 ImGui::SetTooltip(
"Close All Panels");
269 current_x -= (button_size + spacing);
270 ImGui::SameLine(current_x);
274 if (ImGui::IsItemHovered()) {
275 ImGui::SetTooltip(
"Show All Panels");
283 static char sidebar_search[256] =
"";
284 ImGui::SetNextItemWidth(-1);
285 ImGui::InputTextWithHint(
"##SidebarSearch",
ICON_MD_SEARCH " Filter...",
286 sidebar_search,
sizeof(sidebar_search));
290 const bool rom_loaded = has_rom ? has_rom() :
true;
291 const bool disable_cards = !rom_loaded && category !=
"Emulator";
294 " Open a ROM to enable this category");
299 ImGui::BeginDisabled();
307 if (sidebar_search[0] ==
'\0' && !pinned_cards.empty()) {
308 bool has_pinned_in_category =
false;
309 for (
const auto& card_id : pinned_cards) {
312 if (card && card->category == category) {
313 has_pinned_in_category =
true;
318 if (has_pinned_in_category) {
320 ImGuiTreeNodeFlags_DefaultOpen)) {
321 for (
const auto& card_id : pinned_cards) {
324 if (!card || card->category != category)
331 ImGui::PushStyleColor(ImGuiCol_Text,
333 if (ImGui::SmallButton(
338 ImGui::PopStyleColor();
339 if (ImGui::IsItemHovered()) {
340 ImGui::SetTooltip(
"Unpin panel");
346 std::string label = absl::StrFormat(
"%s %s", card->icon.c_str(),
347 card->display_name.c_str());
348 if (ImGui::Selectable(label.c_str(), visible)) {
352 card->visibility_flag ? *card->visibility_flag :
false;
355 ImGui::SetWindowFocus(card->GetWindowTitle().c_str());
367 if (sidebar_search[0] ==
'\0' && !recent_cards.empty()) {
368 bool has_recents_in_category =
false;
369 for (
const auto& card_id : recent_cards) {
372 if (card && card->category == category) {
373 has_recents_in_category =
true;
378 if (has_recents_in_category) {
380 ImGuiTreeNodeFlags_DefaultOpen)) {
381 for (
const auto& card_id : recent_cards) {
384 if (!card || card->category != category)
392 ImGui::PushID((std::string(
"recent_") + card->card_id).c_str());
394 ImGui::PushStyleColor(ImGuiCol_Text,
399 ImGui::PopStyleColor();
402 theme.text_disabled));
406 ImGui::PopStyleColor();
412 std::string label = absl::StrFormat(
"%s %s", card->icon.c_str(),
413 card->display_name.c_str());
414 if (ImGui::Selectable(label.c_str(), visible)) {
418 card->visibility_flag ? *card->visibility_flag :
false;
422 ImGui::SetWindowFocus(card->GetWindowTitle().c_str());
437 float available_height = ImGui::GetContentRegionAvail().y;
440 has_file_browser ? available_height * 0.4f : available_height;
441 float file_browser_height = available_height - cards_height - 30.0f;
444 ImGui::BeginChild(
"##PanelContent", ImVec2(0, cards_height),
false,
445 ImGuiWindowFlags_None);
446 for (
const auto& card : cards) {
448 if (sidebar_search[0] !=
'\0') {
449 std::string search_str = sidebar_search;
450 std::string card_name = card.display_name;
451 std::transform(search_str.begin(), search_str.end(), search_str.begin(),
453 std::transform(card_name.begin(), card_name.end(), card_name.begin(),
455 if (card_name.find(search_str) == std::string::npos) {
460 bool visible = card.visibility_flag ? *card.visibility_flag :
false;
465 ImGui::PushID(card.card_id.c_str());
467 ImGui::PushStyleColor(ImGuiCol_Text,
472 ImGui::PopStyleColor();
473 if (ImGui::IsItemHovered()) {
474 ImGui::SetTooltip(
"Unpin - panel will hide when switching editors");
477 ImGui::PushStyleColor(ImGuiCol_Text,
482 ImGui::PopStyleColor();
483 if (ImGui::IsItemHovered()) {
484 ImGui::SetTooltip(
"Pin - keep visible across all editors");
491 std::string label = absl::StrFormat(
"%s %s", card.icon.c_str(),
492 card.display_name.c_str());
493 if (ImGui::Selectable(label.c_str(), visible)) {
498 bool new_visible = card.visibility_flag ? *card.visibility_flag :
false;
507 std::string window_title = card.GetWindowTitle();
508 ImGui::SetWindowFocus(window_title.c_str());
513 if (ImGui::IsItemHovered() && !card.shortcut_hint.empty()) {
514 ImGui::SetTooltip(
"%s", card.shortcut_hint.c_str());
520 if (has_file_browser) {
525 ImGui::PushStyleColor(ImGuiCol_Header,
527 ImGui::PushStyleColor(ImGuiCol_HeaderHovered,
529 bool files_expanded = ImGui::CollapsingHeader(
531 ImGui::PopStyleColor(2);
533 if (files_expanded) {
534 ImGui::BeginChild(
"##FileBrowser", ImVec2(0, file_browser_height),
535 false, ImGuiWindowFlags_None);
545 ImGui::EndDisabled();
550 ImGui::PopStyleVar(3);
551 ImGui::PopStyleColor(1);
555 ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver);
560 static char search_filter[256] =
"";
561 static std::string category_filter =
"All";
564 ImGui::SetNextItemWidth(300);
565 ImGui::InputTextWithHint(
568 search_filter,
sizeof(search_filter));
573 if (ImGui::BeginCombo(
"##CategoryFilter", category_filter.c_str())) {
574 if (ImGui::Selectable(
"All", category_filter ==
"All")) {
575 category_filter =
"All";
579 for (
const auto& cat : categories) {
580 if (ImGui::Selectable(cat.c_str(), category_filter == cat)) {
581 category_filter = cat;
590 if (ImGui::BeginTable(
"##PanelTable", 4,
591 ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg |
592 ImGuiTableFlags_Borders)) {
593 ImGui::TableSetupColumn(
"Visible", ImGuiTableColumnFlags_WidthFixed, 60);
594 ImGui::TableSetupColumn(
"Name", ImGuiTableColumnFlags_WidthStretch);
595 ImGui::TableSetupColumn(
"Category", ImGuiTableColumnFlags_WidthFixed,
597 ImGui::TableSetupColumn(
"Shortcut", ImGuiTableColumnFlags_WidthFixed,
599 ImGui::TableHeadersRow();
601 auto cards = (category_filter ==
"All")
603 : std::vector<std::string>{};
605 if (category_filter !=
"All") {
608 for (
const auto& card : cat_cards) {
609 cards.push_back(card.card_id);
613 for (
const auto& card_id : cards) {
620 std::string search_str = search_filter;
621 if (!search_str.empty()) {
623 std::transform(card_lower.begin(), card_lower.end(),
624 card_lower.begin(), ::tolower);
625 std::transform(search_str.begin(), search_str.end(),
626 search_str.begin(), ::tolower);
627 if (card_lower.find(search_str) == std::string::npos) {
632 ImGui::TableNextRow();
635 ImGui::TableNextColumn();
636 if (card->visibility_flag) {
637 bool visible = *card->visibility_flag;
639 absl::StrFormat(
"##vis_%s", card->card_id.c_str()).c_str(),
647 ImGui::TableNextColumn();
648 ImGui::Text(
"%s %s", card->icon.c_str(), card->display_name.c_str());
651 ImGui::TableNextColumn();
652 ImGui::Text(
"%s", card->category.c_str());
655 ImGui::TableNextColumn();
656 ImGui::TextDisabled(
"%s", card->shortcut_hint.c_str());