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, ImVec2(0.0f, 8.0f));
70 ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
72 if (ImGui::Begin(
"##ActivityBar",
nullptr, flags)) {
76 "Global Search (Ctrl+Shift+F)",
false)) {
82 ImVec2 sep_p1 = ImGui::GetCursorScreenPos();
83 ImVec2 sep_p2 = ImVec2(sep_p1.x + 48.0f, sep_p1.y);
84 ImGui::GetWindowDrawList()->AddLine(
90 bool rom_loaded = has_rom ? has_rom() :
false;
93 for (
const auto& cat : all_categories) {
96 bool has_active_editor = active_editor_categories.count(cat) > 0;
99 bool category_enabled = rom_loaded || (cat ==
"Emulator");
103 ImVec4 cat_color(cat_theme.r, cat_theme.g, cat_theme.b, cat_theme.a);
104 ImVec4 glow_color(cat_theme.glow_r, cat_theme.glow_g, cat_theme.glow_b, 1.0f);
107 if (is_selected && category_enabled) {
108 ImVec2 pos = ImGui::GetCursorScreenPos();
111 ImVec4 outer_glow = glow_color;
112 outer_glow.w = 0.15f;
113 ImGui::GetWindowDrawList()->AddRectFilled(
114 ImVec2(pos.x - 1.0f, pos.y - 1.0f),
115 ImVec2(pos.x + 49.0f, pos.y + 41.0f),
116 ImGui::ColorConvertFloat4ToU32(outer_glow), 4.0f);
119 ImVec4 highlight = glow_color;
121 ImGui::GetWindowDrawList()->AddRectFilled(
123 ImVec2(pos.x + 48.0f, pos.y + 40.0f),
124 ImGui::ColorConvertFloat4ToU32(highlight), 2.0f);
127 ImGui::GetWindowDrawList()->AddRectFilled(
129 ImVec2(pos.x + 4.0f, pos.y + 40.0f),
130 ImGui::ColorConvertFloat4ToU32(cat_color));
136 ImVec4 icon_color = is_selected ? cat_color : ImVec4(0, 0, 0, 0);
138 nullptr, is_selected, icon_color)) {
139 if (category_enabled) {
152 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
153 ImGui::BeginTooltip();
154 ImGui::Text(
"%s %s", icon.c_str(), cat.c_str());
155 if (!category_enabled) {
157 "Open ROM required");
158 }
else if (has_active_editor) {
159 ImGui::PushStyleColor(ImGuiCol_Text,
161 ImGui::TextUnformatted(
"Editor open");
162 ImGui::PopStyleColor();
165 ImGui::TextUnformatted(
"Click to view panels");
166 ImGui::PopStyleColor();
174 ImGui::SetCursorPosY(viewport_height - 48.0f);
177 ImGui::OpenPopup(
"ActivityBarMoreMenu");
180 if (ImGui::BeginPopup(
"ActivityBarMoreMenu")) {
195 if (ImGui::MenuItem(
"Reset Layout")) {
203 ImGui::PopStyleVar(3);
204 ImGui::PopStyleColor(2);
208 std::function<
bool()> has_rom) {
211 const ImGuiViewport* viewport = ImGui::GetMainViewport();
215 ImGui::SetNextWindowPos(
216 ImVec2(viewport->WorkPos.x + bar_width, viewport->WorkPos.y));
217 ImGui::SetNextWindowSize(ImVec2(panel_width, viewport->WorkSize.y));
221 ImGuiWindowFlags flags =
222 ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
223 ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse |
224 ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoFocusOnAppearing;
226 ImGui::PushStyleColor(ImGuiCol_WindowBg, panel_bg);
227 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
228 ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
229 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,
230 ImVec2(12.0f, 12.0f));
232 if (ImGui::Begin(
"##SidePanel",
nullptr, flags)) {
234 ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]);
235 ImGui::Text(
"%s", category.c_str());
239 float avail_width = ImGui::GetContentRegionAvail().x;
240 float button_size = 28.0f;
241 float spacing = 4.0f;
242 float current_x = ImGui::GetCursorPosX() + avail_width - button_size;
245 ImGui::SameLine(current_x);
246 ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 4.0f);
248 ImVec2(button_size, button_size))) {
251 if (ImGui::IsItemHovered()) {
252 ImGui::SetTooltip(
"Collapse Panel");
256 current_x -= (button_size + spacing);
257 ImGui::SameLine(current_x);
259 ImVec2(button_size, button_size))) {
262 if (ImGui::IsItemHovered()) {
263 ImGui::SetTooltip(
"Close All Panels");
267 current_x -= (button_size + spacing);
268 ImGui::SameLine(current_x);
272 if (ImGui::IsItemHovered()) {
273 ImGui::SetTooltip(
"Show All Panels");
281 static char sidebar_search[256] =
"";
282 ImGui::SetNextItemWidth(-1);
283 ImGui::InputTextWithHint(
"##SidebarSearch",
ICON_MD_SEARCH " Filter...",
284 sidebar_search,
sizeof(sidebar_search));
288 const bool rom_loaded = has_rom ? has_rom() :
true;
289 const bool disable_cards = !rom_loaded && category !=
"Emulator";
292 " Open a ROM to enable this category");
297 ImGui::BeginDisabled();
305 if (sidebar_search[0] ==
'\0' && !pinned_cards.empty()) {
306 bool has_pinned_in_category =
false;
307 for (
const auto& card_id : pinned_cards) {
310 if (card && card->category == category) {
311 has_pinned_in_category =
true;
316 if (has_pinned_in_category) {
318 ImGuiTreeNodeFlags_DefaultOpen)) {
319 for (
const auto& card_id : pinned_cards) {
322 if (!card || card->category != category)
329 ImGui::PushStyleColor(ImGuiCol_Text,
331 if (ImGui::SmallButton(
336 ImGui::PopStyleColor();
337 if (ImGui::IsItemHovered()) {
338 ImGui::SetTooltip(
"Unpin panel");
344 std::string label = absl::StrFormat(
"%s %s", card->icon.c_str(),
345 card->display_name.c_str());
346 if (ImGui::Selectable(label.c_str(), visible)) {
350 card->visibility_flag ? *card->visibility_flag :
false;
353 ImGui::SetWindowFocus(card->GetWindowTitle().c_str());
365 if (sidebar_search[0] ==
'\0' && !recent_cards.empty()) {
366 bool has_recents_in_category =
false;
367 for (
const auto& card_id : recent_cards) {
370 if (card && card->category == category) {
371 has_recents_in_category =
true;
376 if (has_recents_in_category) {
378 ImGuiTreeNodeFlags_DefaultOpen)) {
379 for (
const auto& card_id : recent_cards) {
382 if (!card || card->category != category)
390 ImGui::PushID((std::string(
"recent_") + card->card_id).c_str());
392 ImGui::PushStyleColor(ImGuiCol_Text,
397 ImGui::PopStyleColor();
400 theme.text_disabled));
404 ImGui::PopStyleColor();
410 std::string label = absl::StrFormat(
"%s %s", card->icon.c_str(),
411 card->display_name.c_str());
412 if (ImGui::Selectable(label.c_str(), visible)) {
416 card->visibility_flag ? *card->visibility_flag :
false;
420 ImGui::SetWindowFocus(card->GetWindowTitle().c_str());
435 float available_height = ImGui::GetContentRegionAvail().y;
438 has_file_browser ? available_height * 0.4f : available_height;
439 float file_browser_height = available_height - cards_height - 30.0f;
442 ImGui::BeginChild(
"##PanelContent", ImVec2(0, cards_height),
false,
443 ImGuiWindowFlags_None);
444 for (
const auto& card : cards) {
446 if (sidebar_search[0] !=
'\0') {
447 std::string search_str = sidebar_search;
448 std::string card_name = card.display_name;
449 std::transform(search_str.begin(), search_str.end(), search_str.begin(),
451 std::transform(card_name.begin(), card_name.end(), card_name.begin(),
453 if (card_name.find(search_str) == std::string::npos) {
458 bool visible = card.visibility_flag ? *card.visibility_flag :
false;
463 ImGui::PushID(card.card_id.c_str());
465 ImGui::PushStyleColor(ImGuiCol_Text,
470 ImGui::PopStyleColor();
471 if (ImGui::IsItemHovered()) {
472 ImGui::SetTooltip(
"Unpin - panel will hide when switching editors");
475 ImGui::PushStyleColor(ImGuiCol_Text,
480 ImGui::PopStyleColor();
481 if (ImGui::IsItemHovered()) {
482 ImGui::SetTooltip(
"Pin - keep visible across all editors");
489 std::string label = absl::StrFormat(
"%s %s", card.icon.c_str(),
490 card.display_name.c_str());
491 if (ImGui::Selectable(label.c_str(), visible)) {
496 bool new_visible = card.visibility_flag ? *card.visibility_flag :
false;
505 std::string window_title = card.GetWindowTitle();
506 ImGui::SetWindowFocus(window_title.c_str());
511 if (ImGui::IsItemHovered() && !card.shortcut_hint.empty()) {
512 ImGui::SetTooltip(
"%s", card.shortcut_hint.c_str());
518 if (has_file_browser) {
523 ImGui::PushStyleColor(ImGuiCol_Header,
525 ImGui::PushStyleColor(ImGuiCol_HeaderHovered,
527 bool files_expanded = ImGui::CollapsingHeader(
529 ImGui::PopStyleColor(2);
531 if (files_expanded) {
532 ImGui::BeginChild(
"##FileBrowser", ImVec2(0, file_browser_height),
533 false, ImGuiWindowFlags_None);
543 ImGui::EndDisabled();
548 ImGui::PopStyleVar(3);
549 ImGui::PopStyleColor(1);
553 ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver);
558 static char search_filter[256] =
"";
559 static std::string category_filter =
"All";
562 ImGui::SetNextItemWidth(300);
563 ImGui::InputTextWithHint(
566 search_filter,
sizeof(search_filter));
571 if (ImGui::BeginCombo(
"##CategoryFilter", category_filter.c_str())) {
572 if (ImGui::Selectable(
"All", category_filter ==
"All")) {
573 category_filter =
"All";
577 for (
const auto& cat : categories) {
578 if (ImGui::Selectable(cat.c_str(), category_filter == cat)) {
579 category_filter = cat;
588 if (ImGui::BeginTable(
"##PanelTable", 4,
589 ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg |
590 ImGuiTableFlags_Borders)) {
591 ImGui::TableSetupColumn(
"Visible", ImGuiTableColumnFlags_WidthFixed, 60);
592 ImGui::TableSetupColumn(
"Name", ImGuiTableColumnFlags_WidthStretch);
593 ImGui::TableSetupColumn(
"Category", ImGuiTableColumnFlags_WidthFixed,
595 ImGui::TableSetupColumn(
"Shortcut", ImGuiTableColumnFlags_WidthFixed,
597 ImGui::TableHeadersRow();
599 auto cards = (category_filter ==
"All")
601 : std::vector<std::string>{};
603 if (category_filter !=
"All") {
606 for (
const auto& card : cat_cards) {
607 cards.push_back(card.card_id);
611 for (
const auto& card_id : cards) {
618 std::string search_str = search_filter;
619 if (!search_str.empty()) {
621 std::transform(card_lower.begin(), card_lower.end(),
622 card_lower.begin(), ::tolower);
623 std::transform(search_str.begin(), search_str.end(),
624 search_str.begin(), ::tolower);
625 if (card_lower.find(search_str) == std::string::npos) {
630 ImGui::TableNextRow();
633 ImGui::TableNextColumn();
634 if (card->visibility_flag) {
635 bool visible = *card->visibility_flag;
637 absl::StrFormat(
"##vis_%s", card->card_id.c_str()).c_str(),
645 ImGui::TableNextColumn();
646 ImGui::Text(
"%s %s", card->icon.c_str(), card->display_name.c_str());
649 ImGui::TableNextColumn();
650 ImGui::Text(
"%s", card->category.c_str());
653 ImGui::TableNextColumn();
654 ImGui::TextDisabled(
"%s", card->shortcut_hint.c_str());