305 const ImGuiStyle& style = ImGui::GetStyle();
306 const float avail_width = ImGui::GetContentRegionAvail().x;
307 const float min_width = kDashboardRecentBaseWidth;
308 const float max_width =
309 kDashboardRecentBaseWidth * kDashboardRecentWidthMaxFactor;
313 const float height = ImGui::GetFrameHeight() + 10.0f * DensityFactor();
314 const float spacing = style.ItemSpacing.x;
315 const bool stack_items = avail_width < min_width * 1.6f;
316 FlowLayout row_layout{};
318 row_layout.columns = 1;
319 row_layout.item_width = avail_width;
320 row_layout.item_height = height;
321 row_layout.spacing = spacing;
323 row_layout = ComputeFlowLayout(avail_width, min_width, max_width, height,
324 height, height / std::max(min_width, 1.0f),
325 spacing, kDashboardMaxRecentColumns,
329 ImGuiTableFlags table_flags =
330 ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoPadOuterX;
331 const ImVec2 cell_padding(row_layout.spacing * 0.5f,
332 style.ItemSpacing.y * 0.4f);
334 if (ImGui::BeginTable(
"DashboardRecentGrid", row_layout.columns,
338 auto it = std::find_if(
340 [type](
const EditorInfo& info) { return info.type == type; });
346 ImGui::TableNextColumn();
348 const bool enabled =
has_rom_ || !it->requires_rom;
349 const float alpha = enabled ? 1.0f : 0.35f;
350 const ImVec4 base_color = GetEditorAccentColor(it->type, theme);
352 {{ImGuiCol_Button, ScaleColor(base_color, 0.5f, 0.7f * alpha)},
353 {ImGuiCol_ButtonHovered, ScaleColor(base_color, 0.7f, 0.9f * alpha)},
354 {ImGuiCol_ButtonActive, WithAlpha(base_color, 1.0f * alpha)}});
357 stack_items ? avail_width : row_layout.item_width,
358 row_layout.item_height > 0.0f ? row_layout.item_height : height);
360 ImGui::BeginDisabled();
362 if (ImGui::Button(absl::StrCat(it->icon,
" ", it->name).c_str(),
372 ImGui::EndDisabled();
375 if (ImGui::IsItemHovered()) {
377 ImGui::SetTooltip(tr(
"Load a ROM to open %s"), it->name.c_str());
379 ImGui::SetTooltip(
"%s", it->description.c_str());
391 const ImGuiStyle& style = ImGui::GetStyle();
392 const float avail_width = ImGui::GetContentRegionAvail().x;
393 const float scale = ImGui::GetFontSize() / 16.0f;
396 const float compact_scale =
397 (avail_width < 620.0f ? 0.85f : 1.0f) * DensityFactor();
398 const float min_width = kDashboardCardBaseWidth *
399 kDashboardCardMinWidthFactor * scale * compact_scale;
400 const float max_width = kDashboardCardBaseWidth *
401 kDashboardCardWidthMaxFactor * scale * compact_scale;
402 const float min_height =
403 std::max(kDashboardCardBaseHeight * kDashboardCardMinHeightFactor *
404 scale * compact_scale,
405 ImGui::GetFrameHeight() * 3.2f);
406 const float max_height = kDashboardCardBaseHeight *
407 kDashboardCardHeightMaxFactor * scale *
409 const float aspect_ratio =
410 kDashboardCardBaseHeight / std::max(kDashboardCardBaseWidth, 1.0f);
411 const float spacing = style.ItemSpacing.x;
413 FlowLayout layout = ComputeFlowLayout(
414 avail_width, min_width, max_width, min_height, max_height, aspect_ratio,
415 spacing, kDashboardMaxColumns,
static_cast<int>(
editors_.size()));
417 ImGuiTableFlags table_flags =
418 ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoPadOuterX;
419 const ImVec2 cell_padding(layout.spacing * 0.5f, style.ItemSpacing.y * 0.5f);
421 if (ImGui::BeginTable(
"DashboardEditorGrid", layout.columns, table_flags)) {
422 for (
size_t i = 0; i <
editors_.size(); ++i) {
423 ImGui::TableNextColumn();
426 ImVec2(layout.item_width, layout.item_height), enabled);
433 const ImVec2& card_size,
bool enabled) {
434 ImGui::PushID(index);
437 const float disabled_alpha = enabled ? 1.0f : 0.35f;
438 const ImVec4 base_color = GetEditorAccentColor(info.
type, theme);
442 text_primary.w *= enabled ? 1.0f : 0.5f;
443 text_secondary.w *= enabled ? 1.0f : 0.5f;
444 ImFont* text_font = ImGui::GetFont();
445 const float text_font_size = ImGui::GetFontSize();
447 const ImGuiStyle& style = ImGui::GetStyle();
448 const float line_height = ImGui::GetTextLineHeight();
449 const float padding_x = std::max(style.FramePadding.x, card_size.x * 0.06f);
450 const float padding_y = std::max(style.FramePadding.y, card_size.y * 0.08f);
452 const float footer_height = info.
shortcut.empty() ? 0.0f : line_height;
453 const float footer_spacing =
454 info.
shortcut.empty() ? 0.0f : style.ItemSpacing.y;
455 const float available_icon_height = card_size.y - padding_y * 2.0f -
456 line_height - footer_height -
458 const float min_icon_radius = line_height * 0.9f;
459 float max_icon_radius = card_size.y * 0.24f;
460 max_icon_radius = std::max(max_icon_radius, min_icon_radius);
461 const float icon_radius = std::clamp(available_icon_height * 0.5f,
462 min_icon_radius, max_icon_radius);
464 const ImVec2 cursor_pos = ImGui::GetCursorScreenPos();
465 ImDrawList* draw_list = ImGui::GetWindowDrawList();
466 const ImVec2 icon_center(cursor_pos.x + card_size.x * 0.5f,
467 cursor_pos.y + padding_y + icon_radius);
468 float title_y = icon_center.y + icon_radius + style.ItemSpacing.y;
469 const float footer_y = cursor_pos.y + card_size.y - padding_y - footer_height;
470 if (title_y + line_height > footer_y - style.ItemSpacing.y) {
471 title_y = footer_y - line_height - style.ItemSpacing.y;
479 ImGui::GetColorU32(ScaleColor(base_color, 0.4f, 0.85f * disabled_alpha));
481 ImGui::GetColorU32(ScaleColor(base_color, 0.2f, 0.9f * disabled_alpha));
484 draw_list->AddRectFilledMultiColor(
486 ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y), color_top,
487 color_top, color_bottom, color_bottom);
492 ? ImGui::GetColorU32(WithAlpha(base_color, 1.0f * disabled_alpha))
493 : ImGui::GetColorU32(
494 ScaleColor(base_color, 0.6f, 0.7f * disabled_alpha));
495 const float rounding = std::max(style.FrameRounding, card_size.y * 0.05f);
496 const float border_thickness =
497 is_recent ? std::max(2.0f, style.FrameBorderSize + 1.0f)
498 : std::max(1.0f, style.FrameBorderSize);
501 ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y),
502 border_color, rounding, 0, border_thickness);
506 const float badge_radius =
507 std::clamp(line_height * 0.6f, line_height * 0.4f, line_height);
508 ImVec2 badge_pos(cursor_pos.x + card_size.x - padding_x - badge_radius,
509 cursor_pos.y + padding_y + badge_radius);
510 draw_list->AddCircleFilled(badge_pos, badge_radius,
511 ImGui::GetColorU32(base_color), 16);
512 const ImU32 star_color = ImGui::GetColorU32(text_primary);
513 const ImVec2 star_size =
514 text_font->CalcTextSizeA(text_font_size, FLT_MAX, 0.0f,
ICON_MD_STAR);
515 const ImVec2 star_pos(badge_pos.x - star_size.x * 0.5f,
516 badge_pos.y - star_size.y * 0.5f);
517 draw_list->AddText(text_font, text_font_size, star_pos, star_color,
522 ImVec4 button_bg = ImGui::GetStyleColorVec4(ImGuiCol_Button);
525 {{ImGuiCol_Button, button_bg},
526 {ImGuiCol_ButtonHovered,
527 ScaleColor(base_color, 0.3f, enabled ? 0.5f : 0.2f)},
528 {ImGuiCol_ButtonActive,
529 ScaleColor(base_color, 0.5f, enabled ? 0.7f : 0.2f)}});
532 ImGui::BeginDisabled();
535 ImGui::Button(absl::StrCat(
"##", info.
name).c_str(), card_size);
537 ImGui::EndDisabled();
539 bool is_hovered = ImGui::IsItemHovered();
543 ImGui::GetColorU32(WithAlpha(base_color, 1.0f * disabled_alpha));
544 draw_list->AddCircleFilled(icon_center, icon_radius, icon_bg, 32);
547 ImFont* icon_font = ImGui::GetFont();
548 if (ImGui::GetIO().Fonts->Fonts.size() > 2 &&
549 card_size.y >= kDashboardCardBaseHeight) {
550 icon_font = ImGui::GetIO().Fonts->Fonts[2];
551 }
else if (ImGui::GetIO().Fonts->Fonts.size() > 1) {
552 icon_font = ImGui::GetIO().Fonts->Fonts[1];
554 ImGui::PushFont(icon_font);
555 const float icon_font_size = ImGui::GetFontSize();
556 const ImVec2 icon_size = icon_font->CalcTextSizeA(icon_font_size, FLT_MAX,
557 0.0f, info.
icon.c_str());
559 const ImVec2 icon_text_pos(icon_center.x - icon_size.x * 0.5f,
560 icon_center.y - icon_size.y * 0.5f);
561 draw_list->AddText(icon_font, icon_font_size, icon_text_pos,
562 ImGui::GetColorU32(text_primary), info.
icon.c_str());
565 const ImVec2 name_size = text_font->CalcTextSizeA(text_font_size, FLT_MAX,
566 0.0f, info.
name.c_str());
567 float name_x = cursor_pos.x + (card_size.x - name_size.x) * 0.5f;
568 const float name_min_x = cursor_pos.x + padding_x;
569 const float name_max_x = cursor_pos.x + card_size.x - padding_x;
570 name_x = std::clamp(name_x, name_min_x, name_max_x);
571 const ImVec2 name_pos(name_x, title_y);
572 const ImVec4 name_clip(name_min_x, cursor_pos.y + padding_y, name_max_x,
574 draw_list->AddText(text_font, text_font_size, name_pos,
575 ImGui::GetColorU32(WithAlpha(base_color, disabled_alpha)),
576 info.
name.c_str(),
nullptr, 0.0f, &name_clip);
580 const ImVec2 shortcut_pos(cursor_pos.x + padding_x, footer_y);
581 const ImVec4 shortcut_clip(cursor_pos.x + padding_x, footer_y,
582 cursor_pos.x + card_size.x - padding_x,
583 cursor_pos.y + card_size.y - padding_y);
584 draw_list->AddText(text_font, text_font_size, shortcut_pos,
585 ImGui::GetColorU32(text_secondary),
586 info.
shortcut.c_str(),
nullptr, 0.0f, &shortcut_clip);
591 ImU32 glow_color = ImGui::GetColorU32(
592 ScaleColor(base_color, 1.0f, enabled ? 0.18f : 0.08f));
593 draw_list->AddRectFilled(
595 ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y),
596 glow_color, rounding);
601 const float tooltip_width = std::clamp(card_size.x * 1.4f, 240.0f, 340.0f);
602 ImGui::SetNextWindowSize(ImVec2(tooltip_width, 0), ImGuiCond_Always);
603 ImGui::BeginTooltip();
604 ImGui::PushFont(
nullptr, HeadingFontSize(1.15f));
605 ImGui::TextColored(WithAlpha(base_color, disabled_alpha),
"%s %s",
606 info.
icon.c_str(), info.
name.c_str());
609 ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + tooltip_width - 20.0f);
611 ImGui::TextWrapped(tr(
"Load a ROM to open this editor."));
613 ImGui::TextWrapped(
"%s", info.
description.c_str());
615 ImGui::PopTextWrapPos();
616 if (enabled && !info.
shortcut.empty()) {
618 ImGui::TextColored(WithAlpha(base_color, disabled_alpha),
623 ImGui::TextColored(accent,
ICON_MD_STAR " Recently used");
628 if (clicked && enabled) {