imgui-frontend-engineer: refresh editor UI layout

This commit is contained in:
scawful
2025-12-28 10:51:47 -06:00
parent 8d011bf094
commit 14a3084c7f
13 changed files with 1097 additions and 328 deletions

View File

@@ -47,7 +47,12 @@ float LayoutCoordinator::GetLeftLayoutOffset() const {
// Add Side Panel width if expanded // Add Side Panel width if expanded
if (panel_manager_->IsPanelExpanded()) { if (panel_manager_->IsPanelExpanded()) {
width += PanelManager::GetSidePanelWidth(); float viewport_width = 0.0f;
if (ImGui::GetCurrentContext()) {
const ImGuiViewport* viewport = ImGui::GetMainViewport();
viewport_width = viewport ? viewport->WorkSize.x : 0.0f;
}
width += PanelManager::GetSidePanelWidthForViewport(viewport_width);
} }
return width; return width;

View File

@@ -210,7 +210,8 @@ void ActivityBar::DrawSidePanel(size_t session_id, const std::string& category,
const auto& theme = gui::ThemeManager::Get().GetCurrentTheme(); const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
const ImGuiViewport* viewport = ImGui::GetMainViewport(); const ImGuiViewport* viewport = ImGui::GetMainViewport();
const float bar_width = PanelManager::GetSidebarWidth(); const float bar_width = PanelManager::GetSidebarWidth();
const float panel_width = PanelManager::GetSidePanelWidth(); const float panel_width =
PanelManager::GetSidePanelWidthForViewport(viewport->WorkSize.x);
ImGui::SetNextWindowPos( ImGui::SetNextWindowPos(
ImVec2(viewport->WorkPos.x + bar_width, viewport->WorkPos.y)); ImVec2(viewport->WorkPos.x + bar_width, viewport->WorkPos.y));

View File

@@ -1,6 +1,7 @@
#include "app/editor/menu/right_panel_manager.h" #include "app/editor/menu/right_panel_manager.h"
#include <chrono> #include <chrono>
#include <filesystem>
#include "absl/strings/str_format.h" #include "absl/strings/str_format.h"
#include "app/editor/agent/agent_chat.h" #include "app/editor/agent/agent_chat.h"
@@ -14,10 +15,32 @@
#include "app/gui/core/style.h" #include "app/gui/core/style.h"
#include "app/gui/core/theme_manager.h" #include "app/gui/core/theme_manager.h"
#include "imgui/imgui.h" #include "imgui/imgui.h"
#include "util/platform_paths.h"
namespace yaze { namespace yaze {
namespace editor { namespace editor {
namespace {
std::string ResolveAgentChatHistoryPath() {
auto agent_dir = util::PlatformPaths::GetAppDataSubdirectory("agent");
if (agent_dir.ok()) {
return (*agent_dir / "agent_chat_history.json").string();
}
auto docs_dir = util::PlatformPaths::GetUserDocumentsSubdirectory("agent");
if (docs_dir.ok()) {
return (*docs_dir / "agent_chat_history.json").string();
}
auto temp_dir = util::PlatformPaths::GetTempDirectory();
if (temp_dir.ok()) {
return (*temp_dir / "agent_chat_history.json").string();
}
return (std::filesystem::current_path() / "agent_chat_history.json")
.string();
}
} // namespace
const char* GetPanelTypeName(RightPanelManager::PanelType type) { const char* GetPanelTypeName(RightPanelManager::PanelType type) {
switch (type) { switch (type) {
case RightPanelManager::PanelType::kNone: case RightPanelManager::PanelType::kNone:
@@ -89,24 +112,50 @@ float RightPanelManager::GetPanelWidth() const {
return 0.0f; return 0.0f;
} }
float width = 0.0f;
switch (active_panel_) { switch (active_panel_) {
case PanelType::kAgentChat: case PanelType::kAgentChat:
return agent_chat_width_; width = agent_chat_width_;
break;
case PanelType::kProposals: case PanelType::kProposals:
return proposals_width_; width = proposals_width_;
break;
case PanelType::kSettings: case PanelType::kSettings:
return settings_width_; width = settings_width_;
break;
case PanelType::kHelp: case PanelType::kHelp:
return help_width_; width = help_width_;
break;
case PanelType::kNotifications: case PanelType::kNotifications:
return notifications_width_; width = notifications_width_;
break;
case PanelType::kProperties: case PanelType::kProperties:
return properties_width_; width = properties_width_;
break;
case PanelType::kProject: case PanelType::kProject:
return project_width_; width = project_width_;
break;
default: default:
return 0.0f; width = 0.0f;
break;
} }
ImGuiContext* context = ImGui::GetCurrentContext();
if (!context) {
return width;
}
const ImGuiViewport* viewport = ImGui::GetMainViewport();
if (!viewport) {
return width;
}
const float max_width = viewport->WorkSize.x * 0.35f;
if (max_width > 0.0f && width > max_width) {
width = max_width;
}
return width;
} }
void RightPanelManager::SetPanelWidth(PanelType type, float width) { void RightPanelManager::SetPanelWidth(PanelType type, float width) {
@@ -503,7 +552,7 @@ void RightPanelManager::DrawAgentChatPanel() {
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button(ICON_MD_FILE_DOWNLOAD " Save", half_width)) { if (ImGui::Button(ICON_MD_FILE_DOWNLOAD " Save", half_width)) {
agent_chat_->SaveHistory(".yaze/agent_chat_history.json"); agent_chat_->SaveHistory(ResolveAgentChatHistoryPath());
} }
ImGui::PopStyleColor(3); ImGui::PopStyleColor(3);
ImGui::PopStyleVar(); ImGui::PopStyleVar();

View File

@@ -214,6 +214,14 @@ class PanelManager {
static constexpr float GetSidebarWidth() { return 48.0f; } static constexpr float GetSidebarWidth() { return 48.0f; }
static constexpr float GetSidePanelWidth() { return 250.0f; } static constexpr float GetSidePanelWidth() { return 250.0f; }
static float GetSidePanelWidthForViewport(float viewport_width) {
float width = GetSidePanelWidth();
const float max_width = viewport_width * 0.28f;
if (viewport_width > 0.0f && max_width > 0.0f && width > max_width) {
width = max_width;
}
return width;
}
static constexpr float GetCollapsedSidebarWidth() { return 16.0f; } static constexpr float GetCollapsedSidebarWidth() { return 16.0f; }
static std::string GetCategoryIcon(const std::string& category); static std::string GetCategoryIcon(const std::string& category);

View File

@@ -10,6 +10,8 @@
#include "app/gui/core/icons.h" #include "app/gui/core/icons.h"
#include "app/gui/core/platform_keys.h" #include "app/gui/core/platform_keys.h"
#include "app/gui/core/style.h" #include "app/gui/core/style.h"
#include "app/gui/core/theme_manager.h"
#include "app/gui/core/color.h"
#include "app/gui/widgets/themed_widgets.h" #include "app/gui/widgets/themed_widgets.h"
#include "imgui/imgui.h" #include "imgui/imgui.h"
#include "imgui/imgui_internal.h" #include "imgui/imgui_internal.h"
@@ -18,52 +20,163 @@
namespace yaze { namespace yaze {
namespace editor { namespace editor {
namespace {
constexpr float kDashboardCardBaseWidth = 180.0f;
constexpr float kDashboardCardBaseHeight = 120.0f;
constexpr float kDashboardCardWidthMaxFactor = 1.35f;
constexpr float kDashboardCardHeightMaxFactor = 1.35f;
constexpr float kDashboardCardMinWidthFactor = 0.9f;
constexpr float kDashboardCardMinHeightFactor = 0.9f;
constexpr int kDashboardMaxColumns = 5;
constexpr float kDashboardRecentBaseWidth = 150.0f;
constexpr float kDashboardRecentBaseHeight = 35.0f;
constexpr float kDashboardRecentWidthMaxFactor = 1.3f;
constexpr int kDashboardMaxRecentColumns = 4;
struct FlowLayout {
int columns = 1;
float item_width = 0.0f;
float item_height = 0.0f;
float spacing = 0.0f;
};
FlowLayout ComputeFlowLayout(float avail_width, float min_width,
float max_width, float min_height,
float max_height, float aspect_ratio,
float spacing, int max_columns,
int item_count) {
FlowLayout layout;
layout.spacing = spacing;
if (avail_width <= 0.0f) {
layout.columns = 1;
layout.item_width = min_width;
layout.item_height = min_height;
return layout;
}
const int clamped_max =
std::max(1, max_columns > 0 ? max_columns : item_count);
const auto width_for_columns = [avail_width, spacing](int columns) {
return (avail_width - spacing * static_cast<float>(columns - 1)) /
static_cast<float>(columns);
};
int columns =
std::max(1, static_cast<int>((avail_width + spacing) /
(min_width + spacing)));
columns = std::min(columns, clamped_max);
if (item_count > 0) {
columns = std::min(columns, item_count);
}
columns = std::max(columns, 1);
float width = width_for_columns(columns);
while (columns < clamped_max && width > max_width) {
columns += 1;
width = width_for_columns(columns);
}
const float clamped_max_width = std::min(max_width, avail_width);
const float clamped_min_width = std::min(min_width, clamped_max_width);
layout.item_width = std::clamp(width, clamped_min_width, clamped_max_width);
layout.item_height =
std::clamp(layout.item_width * aspect_ratio, min_height, max_height);
layout.columns = columns;
return layout;
}
ImVec4 ScaleColor(const ImVec4& color, float scale, float alpha) {
return ImVec4(color.x * scale, color.y * scale, color.z * scale, alpha);
}
ImVec4 ScaleColor(const ImVec4& color, float scale) {
return ScaleColor(color, scale, color.w);
}
ImVec4 WithAlpha(ImVec4 color, float alpha) {
color.w = alpha;
return color;
}
ImVec4 GetEditorAccentColor(EditorType type, const gui::Theme& theme) {
switch (type) {
case EditorType::kOverworld:
return gui::ConvertColorToImVec4(theme.success);
case EditorType::kDungeon:
return gui::ConvertColorToImVec4(theme.secondary);
case EditorType::kGraphics:
return gui::ConvertColorToImVec4(theme.warning);
case EditorType::kSprite:
return gui::ConvertColorToImVec4(theme.info);
case EditorType::kMessage:
return gui::ConvertColorToImVec4(theme.primary);
case EditorType::kMusic:
return gui::ConvertColorToImVec4(theme.accent);
case EditorType::kPalette:
return gui::ConvertColorToImVec4(theme.error);
case EditorType::kScreen:
return gui::ConvertColorToImVec4(theme.info);
case EditorType::kAssembly:
return gui::ConvertColorToImVec4(theme.text_secondary);
case EditorType::kHex:
return gui::ConvertColorToImVec4(theme.success);
case EditorType::kEmulator:
return gui::ConvertColorToImVec4(theme.info);
case EditorType::kAgent:
return gui::ConvertColorToImVec4(theme.accent);
case EditorType::kSettings:
case EditorType::kUnknown:
default:
return gui::ConvertColorToImVec4(theme.text_primary);
}
}
} // namespace
DashboardPanel::DashboardPanel(EditorManager* editor_manager) DashboardPanel::DashboardPanel(EditorManager* editor_manager)
: editor_manager_(editor_manager), : editor_manager_(editor_manager),
window_("Dashboard", ICON_MD_DASHBOARD) { window_("Dashboard", ICON_MD_DASHBOARD) {
window_.SetDefaultSize(950, 650); window_.SetDefaultSize(950, 650);
window_.SetPosition(gui::PanelWindow::Position::Center); window_.SetPosition(gui::PanelWindow::Position::Center);
// Initialize editor list with colors matching EditorSelectionDialog
// Use platform-aware shortcut strings (Cmd on macOS, Ctrl elsewhere) // Use platform-aware shortcut strings (Cmd on macOS, Ctrl elsewhere)
const char* ctrl = gui::GetCtrlDisplayName(); const char* ctrl = gui::GetCtrlDisplayName();
editors_ = { editors_ = {
{"Overworld", ICON_MD_MAP, "Edit overworld maps, entrances, and properties", {"Overworld", ICON_MD_MAP,
absl::StrFormat("%s+1", ctrl), EditorType::kOverworld, "Edit overworld maps, entrances, and properties",
ImVec4(0.133f, 0.545f, 0.133f, 1.0f)}, // Hyrule green absl::StrFormat("%s+1", ctrl), EditorType::kOverworld},
{"Dungeon", ICON_MD_CASTLE, "Design dungeon rooms, layouts, and mechanics", {"Dungeon", ICON_MD_CASTLE,
absl::StrFormat("%s+2", ctrl), EditorType::kDungeon, "Design dungeon rooms, layouts, and mechanics",
ImVec4(0.502f, 0.0f, 0.502f, 1.0f)}, // Ganon purple absl::StrFormat("%s+2", ctrl), EditorType::kDungeon},
{"Graphics", ICON_MD_PALETTE, "Modify tiles, palettes, and graphics sets", {"Graphics", ICON_MD_PALETTE,
absl::StrFormat("%s+3", ctrl), EditorType::kGraphics, "Modify tiles, palettes, and graphics sets",
ImVec4(1.0f, 0.843f, 0.0f, 1.0f)}, // Triforce gold absl::StrFormat("%s+3", ctrl), EditorType::kGraphics},
{"Sprites", ICON_MD_EMOJI_EMOTIONS, "Edit sprite graphics and properties", {"Sprites", ICON_MD_EMOJI_EMOTIONS,
absl::StrFormat("%s+4", ctrl), EditorType::kSprite, "Edit sprite graphics and properties",
ImVec4(1.0f, 0.647f, 0.0f, 1.0f)}, // Spirit orange absl::StrFormat("%s+4", ctrl), EditorType::kSprite},
{"Messages", ICON_MD_CHAT_BUBBLE, "Edit dialogue, signs, and text", {"Messages", ICON_MD_CHAT_BUBBLE, "Edit dialogue, signs, and text",
absl::StrFormat("%s+5", ctrl), EditorType::kMessage, absl::StrFormat("%s+5", ctrl), EditorType::kMessage},
ImVec4(0.196f, 0.6f, 0.8f, 1.0f)}, // Master sword blue
{"Music", ICON_MD_MUSIC_NOTE, "Configure music and sound effects", {"Music", ICON_MD_MUSIC_NOTE, "Configure music and sound effects",
absl::StrFormat("%s+6", ctrl), EditorType::kMusic, absl::StrFormat("%s+6", ctrl), EditorType::kMusic},
ImVec4(0.416f, 0.353f, 0.804f, 1.0f)}, // Shadow purple {"Palettes", ICON_MD_COLOR_LENS,
{"Palettes", ICON_MD_COLOR_LENS, "Edit color palettes and animations", "Edit color palettes and animations",
absl::StrFormat("%s+7", ctrl), EditorType::kPalette, absl::StrFormat("%s+7", ctrl), EditorType::kPalette},
ImVec4(0.863f, 0.078f, 0.235f, 1.0f)}, // Heart red
{"Screens", ICON_MD_TV, "Edit title screen and ending screens", {"Screens", ICON_MD_TV, "Edit title screen and ending screens",
absl::StrFormat("%s+8", ctrl), EditorType::kScreen, absl::StrFormat("%s+8", ctrl), EditorType::kScreen},
ImVec4(0.4f, 0.8f, 1.0f, 1.0f)}, // Sky blue
{"Assembly", ICON_MD_CODE, "Write and edit assembly code", {"Assembly", ICON_MD_CODE, "Write and edit assembly code",
absl::StrFormat("%s+9", ctrl), EditorType::kAssembly, absl::StrFormat("%s+9", ctrl), EditorType::kAssembly},
ImVec4(0.8f, 0.8f, 0.8f, 1.0f)}, // Silver {"Hex Editor", ICON_MD_DATA_ARRAY,
{"Hex Editor", ICON_MD_DATA_ARRAY, "Direct ROM memory editing and comparison", "Direct ROM memory editing and comparison",
absl::StrFormat("%s+0", ctrl), EditorType::kHex, absl::StrFormat("%s+0", ctrl), EditorType::kHex},
ImVec4(0.2f, 0.8f, 0.4f, 1.0f)}, // Matrix green {"Emulator", ICON_MD_VIDEOGAME_ASSET,
{"Emulator", ICON_MD_VIDEOGAME_ASSET, "Test and debug your ROM in real-time", "Test and debug your ROM in real-time",
absl::StrFormat("%s+Shift+E", ctrl), EditorType::kEmulator, absl::StrFormat("%s+Shift+E", ctrl), EditorType::kEmulator},
ImVec4(0.2f, 0.6f, 1.0f, 1.0f)}, // Emulator blue {"AI Agent", ICON_MD_SMART_TOY,
{"AI Agent", ICON_MD_SMART_TOY, "Configure AI agent, collaboration, and automation", "Configure AI agent, collaboration, and automation",
absl::StrFormat("%s+Shift+A", ctrl), EditorType::kAgent, absl::StrFormat("%s+Shift+A", ctrl), EditorType::kAgent},
ImVec4(0.8f, 0.4f, 1.0f, 1.0f)}, // Purple/magenta
}; };
LoadRecentEditors(); LoadRecentEditors();
@@ -87,19 +200,21 @@ void DashboardPanel::Draw() {
ImGui::Separator(); ImGui::Separator();
ImGui::Spacing(); ImGui::Spacing();
} }
DrawEditorGrid(); DrawEditorGrid();
} }
window_.End(); window_.End();
} }
void DashboardPanel::DrawWelcomeHeader() { void DashboardPanel::DrawWelcomeHeader() {
const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
const ImVec4 accent = gui::ConvertColorToImVec4(theme.accent);
const ImVec4 text_secondary = gui::ConvertColorToImVec4(theme.text_secondary);
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[2]); // Large font ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[2]); // Large font
ImVec4 title_color = ImVec4(1.0f, 0.843f, 0.0f, 1.0f); // Triforce gold ImGui::TextColored(accent, ICON_MD_EDIT " Select an Editor");
ImGui::TextColored(title_color, ICON_MD_EDIT " Select an Editor");
ImGui::PopFont(); ImGui::PopFont();
ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.8f, 1.0f), ImGui::TextColored(text_secondary,
"Choose an editor to begin working on your ROM. " "Choose an editor to begin working on your ROM. "
"You can open multiple editors simultaneously."); "You can open multiple editors simultaneously.");
} }
@@ -107,29 +222,65 @@ void DashboardPanel::DrawWelcomeHeader() {
void DashboardPanel::DrawRecentEditors() { void DashboardPanel::DrawRecentEditors() {
if (recent_editors_.empty()) return; if (recent_editors_.empty()) return;
ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
ICON_MD_HISTORY " Recently Used"); const ImVec4 accent = gui::ConvertColorToImVec4(theme.accent);
ImGui::TextColored(accent, ICON_MD_HISTORY " Recently Used");
ImGui::Spacing(); ImGui::Spacing();
const ImGuiStyle& style = ImGui::GetStyle();
const float avail_width = ImGui::GetContentRegionAvail().x;
const float min_width = kDashboardRecentBaseWidth;
const float max_width = kDashboardRecentBaseWidth *
kDashboardRecentWidthMaxFactor;
const float height = std::max(kDashboardRecentBaseHeight,
ImGui::GetFrameHeight());
const float spacing = style.ItemSpacing.x;
const bool stack_items = avail_width < min_width * 1.6f;
FlowLayout row_layout{};
if (stack_items) {
row_layout.columns = 1;
row_layout.item_width = avail_width;
row_layout.item_height = height;
row_layout.spacing = spacing;
} else {
row_layout = ComputeFlowLayout(
avail_width, min_width, max_width, height, height,
height / std::max(min_width, 1.0f), spacing, kDashboardMaxRecentColumns,
static_cast<int>(recent_editors_.size()));
}
ImGuiTableFlags table_flags = ImGuiTableFlags_SizingFixedFit |
ImGuiTableFlags_NoPadOuterX;
const ImVec2 cell_padding(row_layout.spacing * 0.5f,
style.ItemSpacing.y * 0.4f);
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, cell_padding);
if (ImGui::BeginTable("DashboardRecentGrid", row_layout.columns,
table_flags)) {
for (EditorType type : recent_editors_) { for (EditorType type : recent_editors_) {
// Find editor info // Find editor info
auto it = std::find_if( auto it = std::find_if(
editors_.begin(), editors_.end(), editors_.begin(), editors_.end(),
[type](const EditorInfo& info) { return info.type == type; }); [type](const EditorInfo& info) { return info.type == type; });
if (it != editors_.end()) { if (it == editors_.end()) {
// Use editor's theme color for button continue;
ImVec4 color = it->color; }
ImGui::PushStyleColor(
ImGuiCol_Button,
ImVec4(color.x * 0.5f, color.y * 0.5f, color.z * 0.5f, 0.7f));
ImGui::PushStyleColor(
ImGuiCol_ButtonHovered,
ImVec4(color.x * 0.7f, color.y * 0.7f, color.z * 0.7f, 0.9f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, color);
ImGui::TableNextColumn();
const ImVec4 base_color = GetEditorAccentColor(it->type, theme);
ImGui::PushStyleColor(ImGuiCol_Button,
ScaleColor(base_color, 0.5f, 0.7f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
ScaleColor(base_color, 0.7f, 0.9f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive,
WithAlpha(base_color, 1.0f));
ImVec2 button_size(stack_items ? avail_width : row_layout.item_width,
row_layout.item_height > 0.0f ? row_layout.item_height
: height);
if (ImGui::Button(absl::StrCat(it->icon, " ", it->name).c_str(), if (ImGui::Button(absl::StrCat(it->icon, " ", it->name).c_str(),
ImVec2(150, 35))) { button_size)) {
if (editor_manager_) { if (editor_manager_) {
MarkRecentlyUsed(type); MarkRecentlyUsed(type);
editor_manager_->SwitchToEditor(type); editor_manager_->SwitchToEditor(type);
@@ -142,151 +293,212 @@ void DashboardPanel::DrawRecentEditors() {
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", it->description.c_str()); ImGui::SetTooltip("%s", it->description.c_str());
} }
ImGui::SameLine();
} }
ImGui::EndTable();
} }
ImGui::PopStyleVar();
ImGui::NewLine();
} }
void DashboardPanel::DrawEditorGrid() { void DashboardPanel::DrawEditorGrid() {
ImGui::Text(ICON_MD_APPS " All Editors"); ImGui::Text(ICON_MD_APPS " All Editors");
ImGui::Spacing(); ImGui::Spacing();
const float card_width = 180.0f; const ImGuiStyle& style = ImGui::GetStyle();
const float spacing = ImGui::GetStyle().ItemSpacing.x; const float avail_width = ImGui::GetContentRegionAvail().x;
const float window_width = ImGui::GetContentRegionAvail().x; const float min_width = kDashboardCardBaseWidth * kDashboardCardMinWidthFactor;
int columns = static_cast<int>(window_width / (card_width + spacing)); const float max_width =
columns = std::max(columns, 1); kDashboardCardBaseWidth * kDashboardCardWidthMaxFactor;
const float min_height =
std::max(kDashboardCardBaseHeight * kDashboardCardMinHeightFactor,
ImGui::GetFrameHeight() * 3.2f);
const float max_height =
kDashboardCardBaseHeight * kDashboardCardHeightMaxFactor;
const float aspect_ratio =
kDashboardCardBaseHeight / std::max(kDashboardCardBaseWidth, 1.0f);
const float spacing = style.ItemSpacing.x;
if (ImGui::BeginTable("EditorGrid", columns)) { FlowLayout layout = ComputeFlowLayout(
avail_width, min_width, max_width, min_height, max_height,
aspect_ratio, spacing, kDashboardMaxColumns,
static_cast<int>(editors_.size()));
ImGuiTableFlags table_flags = ImGuiTableFlags_SizingFixedFit |
ImGuiTableFlags_NoPadOuterX;
const ImVec2 cell_padding(layout.spacing * 0.5f,
style.ItemSpacing.y * 0.5f);
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, cell_padding);
if (ImGui::BeginTable("DashboardEditorGrid", layout.columns, table_flags)) {
for (size_t i = 0; i < editors_.size(); ++i) { for (size_t i = 0; i < editors_.size(); ++i) {
ImGui::TableNextColumn(); ImGui::TableNextColumn();
DrawEditorPanel(editors_[i], static_cast<int>(i)); DrawEditorPanel(editors_[i], static_cast<int>(i),
ImVec2(layout.item_width, layout.item_height));
} }
ImGui::EndTable(); ImGui::EndTable();
} }
ImGui::PopStyleVar();
} }
void DashboardPanel::DrawEditorPanel(const EditorInfo& info, int index) { void DashboardPanel::DrawEditorPanel(const EditorInfo& info, int index,
const ImVec2& card_size) {
ImGui::PushID(index); ImGui::PushID(index);
ImVec2 button_size(180, 120); const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
ImVec2 cursor_pos = ImGui::GetCursorScreenPos(); const ImVec4 base_color = GetEditorAccentColor(info.type, theme);
const ImVec4 text_primary = gui::ConvertColorToImVec4(theme.text_primary);
const ImVec4 text_secondary = gui::ConvertColorToImVec4(theme.text_secondary);
const ImVec4 accent = gui::ConvertColorToImVec4(theme.accent);
const ImGuiStyle& style = ImGui::GetStyle();
const float line_height = ImGui::GetTextLineHeight();
const float padding_x = std::max(style.FramePadding.x, card_size.x * 0.06f);
const float padding_y = std::max(style.FramePadding.y, card_size.y * 0.08f);
const float footer_height = info.shortcut.empty() ? 0.0f : line_height;
const float footer_spacing = info.shortcut.empty() ? 0.0f : style.ItemSpacing.y;
const float available_icon_height =
card_size.y - padding_y * 2.0f - line_height - footer_height - footer_spacing;
const float min_icon_radius = line_height * 0.9f;
float max_icon_radius = card_size.y * 0.24f;
max_icon_radius = std::max(max_icon_radius, min_icon_radius);
const float icon_radius =
std::clamp(available_icon_height * 0.5f, min_icon_radius, max_icon_radius);
const ImVec2 cursor_pos = ImGui::GetCursorScreenPos();
ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImDrawList* draw_list = ImGui::GetWindowDrawList();
const ImVec2 icon_center(cursor_pos.x + card_size.x * 0.5f,
cursor_pos.y + padding_y + icon_radius);
float title_y = icon_center.y + icon_radius + style.ItemSpacing.y;
const float footer_y = cursor_pos.y + card_size.y - padding_y - footer_height;
if (title_y + line_height > footer_y - style.ItemSpacing.y) {
title_y = footer_y - line_height - style.ItemSpacing.y;
}
bool is_recent = std::find(recent_editors_.begin(), recent_editors_.end(), bool is_recent = std::find(recent_editors_.begin(), recent_editors_.end(),
info.type) != recent_editors_.end(); info.type) != recent_editors_.end();
// Create gradient background // Create gradient background
ImVec4 base_color = info.color; ImU32 color_top = ImGui::GetColorU32(ScaleColor(base_color, 0.4f, 0.85f));
ImU32 color_top = ImGui::GetColorU32(ImVec4( ImU32 color_bottom =
base_color.x * 0.4f, base_color.y * 0.4f, base_color.z * 0.4f, 0.8f)); ImGui::GetColorU32(ScaleColor(base_color, 0.2f, 0.9f));
ImU32 color_bottom = ImGui::GetColorU32(ImVec4(
base_color.x * 0.2f, base_color.y * 0.2f, base_color.z * 0.2f, 0.9f));
// Draw gradient card background // Draw gradient card background
draw_list->AddRectFilledMultiColor( draw_list->AddRectFilledMultiColor(
cursor_pos, cursor_pos,
ImVec2(cursor_pos.x + button_size.x, cursor_pos.y + button_size.y), ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y),
color_top, color_top, color_bottom, color_bottom); color_top, color_top, color_bottom, color_bottom);
// Colored border // Colored border
ImU32 border_color = ImU32 border_color =
is_recent is_recent
? ImGui::GetColorU32( ? ImGui::GetColorU32(
ImVec4(base_color.x, base_color.y, base_color.z, 1.0f)) WithAlpha(base_color, 1.0f))
: ImGui::GetColorU32(ImVec4(base_color.x * 0.6f, base_color.y * 0.6f, : ImGui::GetColorU32(ScaleColor(base_color, 0.6f, 0.7f));
base_color.z * 0.6f, 0.7f)); const float rounding = std::max(style.FrameRounding, card_size.y * 0.05f);
const float border_thickness =
is_recent ? std::max(2.0f, style.FrameBorderSize + 1.0f)
: std::max(1.0f, style.FrameBorderSize);
draw_list->AddRect( draw_list->AddRect(
cursor_pos, cursor_pos,
ImVec2(cursor_pos.x + button_size.x, cursor_pos.y + button_size.y), ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y),
border_color, 4.0f, 0, is_recent ? 3.0f : 2.0f); border_color, rounding, 0, border_thickness);
// Recent indicator badge // Recent indicator badge
if (is_recent) { if (is_recent) {
ImVec2 badge_pos(cursor_pos.x + button_size.x - 25, cursor_pos.y + 5); const float badge_radius =
draw_list->AddCircleFilled(badge_pos, 12, ImGui::GetColorU32(base_color), std::clamp(line_height * 0.6f, line_height * 0.4f, line_height);
16); ImVec2 badge_pos(cursor_pos.x + card_size.x - padding_x - badge_radius,
ImGui::SetCursorScreenPos(ImVec2(badge_pos.x - 6, badge_pos.y - 8)); cursor_pos.y + padding_y + badge_radius);
ImGui::TextColored(ImVec4(1, 1, 1, 1), ICON_MD_STAR); draw_list->AddCircleFilled(badge_pos, badge_radius,
ImGui::GetColorU32(base_color), 16);
ImVec2 star_size = ImGui::CalcTextSize(ICON_MD_STAR);
ImGui::SetCursorScreenPos(
ImVec2(badge_pos.x - star_size.x * 0.5f,
badge_pos.y - star_size.y * 0.5f));
ImGui::TextColored(text_primary, ICON_MD_STAR);
} }
// Make button transparent (we draw our own background) // Make button transparent (we draw our own background)
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); ImVec4 button_bg = ImGui::GetStyleColorVec4(ImGuiCol_Button);
button_bg.w = 0.0f;
ImGui::PushStyleColor(ImGuiCol_Button, button_bg);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
ImVec4(base_color.x * 0.3f, base_color.y * 0.3f, ScaleColor(base_color, 0.3f, 0.5f));
base_color.z * 0.3f, 0.5f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::PushStyleColor(ImGuiCol_ButtonActive,
ImVec4(base_color.x * 0.5f, base_color.y * 0.5f, ScaleColor(base_color, 0.5f, 0.7f));
base_color.z * 0.5f, 0.7f));
ImGui::SetCursorScreenPos(cursor_pos); ImGui::SetCursorScreenPos(cursor_pos);
bool clicked = bool clicked =
ImGui::Button(absl::StrCat("##", info.name).c_str(), button_size); ImGui::Button(absl::StrCat("##", info.name).c_str(), card_size);
bool is_hovered = ImGui::IsItemHovered(); bool is_hovered = ImGui::IsItemHovered();
ImGui::PopStyleColor(3); ImGui::PopStyleColor(3);
// Draw icon with colored background circle // Draw icon with colored background circle
ImVec2 icon_center(cursor_pos.x + button_size.x / 2, cursor_pos.y + 30);
ImU32 icon_bg = ImGui::GetColorU32(base_color); ImU32 icon_bg = ImGui::GetColorU32(base_color);
draw_list->AddCircleFilled(icon_center, 22, icon_bg, 32); draw_list->AddCircleFilled(icon_center, icon_radius, icon_bg, 32);
// Draw icon // Draw icon
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[2]); // Larger font for icon ImFont* icon_font = ImGui::GetFont();
if (ImGui::GetIO().Fonts->Fonts.size() > 2 &&
card_size.y >= kDashboardCardBaseHeight) {
icon_font = ImGui::GetIO().Fonts->Fonts[2];
} else if (ImGui::GetIO().Fonts->Fonts.size() > 1) {
icon_font = ImGui::GetIO().Fonts->Fonts[1];
}
ImGui::PushFont(icon_font);
ImVec2 icon_size = ImGui::CalcTextSize(info.icon.c_str()); ImVec2 icon_size = ImGui::CalcTextSize(info.icon.c_str());
ImGui::SetCursorScreenPos( ImGui::SetCursorScreenPos(
ImVec2(icon_center.x - icon_size.x / 2, icon_center.y - icon_size.y / 2)); ImVec2(icon_center.x - icon_size.x / 2, icon_center.y - icon_size.y / 2));
ImGui::TextColored(ImVec4(1, 1, 1, 1), "%s", info.icon.c_str()); ImGui::TextColored(text_primary, "%s", info.icon.c_str());
ImGui::PopFont(); ImGui::PopFont();
// Draw name // Draw name
ImGui::SetCursorScreenPos(ImVec2(cursor_pos.x + 10, cursor_pos.y + 65)); const float name_wrap_width = card_size.x - padding_x * 2.0f;
ImGui::PushTextWrapPos(cursor_pos.x + button_size.x - 10); ImGui::PushTextWrapPos(cursor_pos.x + card_size.x - padding_x);
ImVec2 name_size = ImGui::CalcTextSize(info.name.c_str()); ImVec2 name_size =
ImGui::CalcTextSize(info.name.c_str(), nullptr, false, name_wrap_width);
ImGui::SetCursorScreenPos(ImVec2( ImGui::SetCursorScreenPos(ImVec2(
cursor_pos.x + (button_size.x - name_size.x) / 2, cursor_pos.y + 65)); cursor_pos.x + (card_size.x - name_size.x) / 2.0f, title_y));
ImGui::TextColored(base_color, "%s", info.name.c_str()); ImGui::TextColored(base_color, "%s", info.name.c_str());
ImGui::PopTextWrapPos(); ImGui::PopTextWrapPos();
// Draw shortcut hint if available // Draw shortcut hint if available
if (!info.shortcut.empty()) { if (!info.shortcut.empty()) {
ImGui::SetCursorScreenPos( ImGui::SetCursorScreenPos(
ImVec2(cursor_pos.x + 10, cursor_pos.y + button_size.y - 20)); ImVec2(cursor_pos.x + padding_x, footer_y));
ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "%s", info.shortcut.c_str()); ImGui::TextColored(text_secondary, "%s", info.shortcut.c_str());
} }
// Hover glow effect // Hover glow effect
if (is_hovered) { if (is_hovered) {
ImU32 glow_color = ImGui::GetColorU32( ImU32 glow_color =
ImVec4(base_color.x, base_color.y, base_color.z, 0.2f)); ImGui::GetColorU32(ScaleColor(base_color, 1.0f, 0.18f));
draw_list->AddRectFilled( draw_list->AddRectFilled(
cursor_pos, cursor_pos,
ImVec2(cursor_pos.x + button_size.x, cursor_pos.y + button_size.y), ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y),
glow_color, 4.0f); glow_color, rounding);
} }
// Enhanced tooltip // Enhanced tooltip
if (is_hovered) { if (is_hovered) {
ImGui::SetNextWindowSize(ImVec2(300, 0), ImGuiCond_Always); const float tooltip_width = std::clamp(card_size.x * 1.4f, 240.0f, 340.0f);
ImGui::SetNextWindowSize(ImVec2(tooltip_width, 0), ImGuiCond_Always);
ImGui::BeginTooltip(); ImGui::BeginTooltip();
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[1]); // Medium font ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[1]); // Medium font
ImGui::TextColored(base_color, "%s %s", info.icon.c_str(), info.name.c_str()); ImGui::TextColored(base_color, "%s %s", info.icon.c_str(), info.name.c_str());
ImGui::PopFont(); ImGui::PopFont();
ImGui::Separator(); ImGui::Separator();
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + 280); ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + tooltip_width - 20.0f);
ImGui::TextWrapped("%s", info.description.c_str()); ImGui::TextWrapped("%s", info.description.c_str());
ImGui::PopTextWrapPos(); ImGui::PopTextWrapPos();
if (!info.shortcut.empty()) { if (!info.shortcut.empty()) {
ImGui::Spacing(); ImGui::Spacing();
ImGui::TextColored(base_color, ICON_MD_KEYBOARD " %s", info.shortcut.c_str()); ImGui::TextColored(base_color, ICON_MD_KEYBOARD " %s",
info.shortcut.c_str());
} }
if (is_recent) { if (is_recent) {
ImGui::Spacing(); ImGui::Spacing();
ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ImGui::TextColored(accent, ICON_MD_STAR " Recently used");
ICON_MD_STAR " Recently used");
} }
ImGui::EndTooltip(); ImGui::EndTooltip();
} }

View File

@@ -38,14 +38,14 @@ class DashboardPanel {
std::string description; std::string description;
std::string shortcut; std::string shortcut;
EditorType type; EditorType type;
ImVec4 color;
bool recently_used = false; bool recently_used = false;
}; };
void DrawWelcomeHeader(); void DrawWelcomeHeader();
void DrawRecentEditors(); void DrawRecentEditors();
void DrawEditorGrid(); void DrawEditorGrid();
void DrawEditorPanel(const EditorInfo& info, int index); void DrawEditorPanel(const EditorInfo& info, int index,
const ImVec2& card_size);
EditorManager* editor_manager_; EditorManager* editor_manager_;
gui::PanelWindow window_; gui::PanelWindow window_;

View File

@@ -9,76 +9,177 @@
#include "app/gui/core/icons.h" #include "app/gui/core/icons.h"
#include "app/gui/core/platform_keys.h" #include "app/gui/core/platform_keys.h"
#include "app/gui/core/style.h" #include "app/gui/core/style.h"
#include "app/gui/core/theme_manager.h"
#include "app/gui/core/color.h"
#include "imgui/imgui.h" #include "imgui/imgui.h"
#include "util/file_util.h" #include "util/file_util.h"
namespace yaze { namespace yaze {
namespace editor { namespace editor {
namespace {
constexpr float kEditorSelectBaseFontSize = 16.0f;
constexpr float kEditorSelectCardBaseWidth = 180.0f;
constexpr float kEditorSelectCardBaseHeight = 120.0f;
constexpr float kEditorSelectCardWidthMaxFactor = 1.35f;
constexpr float kEditorSelectCardHeightMaxFactor = 1.35f;
constexpr float kEditorSelectRecentBaseWidth = 150.0f;
constexpr float kEditorSelectRecentBaseHeight = 35.0f;
constexpr float kEditorSelectRecentWidthMaxFactor = 1.3f;
struct GridLayout {
int columns = 1;
float item_width = 0.0f;
float item_height = 0.0f;
float spacing = 0.0f;
float row_start_x = 0.0f;
};
float GetEditorSelectScale() {
const float font_size = ImGui::GetFontSize();
if (font_size <= 0.0f) {
return 1.0f;
}
return font_size / kEditorSelectBaseFontSize;
}
GridLayout ComputeGridLayout(float avail_width, float min_width,
float max_width, float min_height,
float max_height, float preferred_width,
float aspect_ratio, float spacing) {
GridLayout layout;
layout.spacing = spacing;
const auto width_for_columns = [avail_width, spacing](int columns) {
return (avail_width - spacing * static_cast<float>(columns - 1)) /
static_cast<float>(columns);
};
layout.columns =
std::max(1, static_cast<int>((avail_width + spacing) /
(preferred_width + spacing)));
layout.item_width = width_for_columns(layout.columns);
while (layout.columns > 1 && layout.item_width < min_width) {
layout.columns -= 1;
layout.item_width = width_for_columns(layout.columns);
}
layout.item_width = std::min(layout.item_width, max_width);
layout.item_width = std::min(layout.item_width, avail_width);
layout.item_height =
std::clamp(layout.item_width * aspect_ratio, min_height, max_height);
const float row_width =
layout.item_width * static_cast<float>(layout.columns) +
spacing * static_cast<float>(layout.columns - 1);
layout.row_start_x = ImGui::GetCursorPosX();
if (row_width < avail_width) {
layout.row_start_x += (avail_width - row_width) * 0.5f;
}
return layout;
}
ImVec4 ScaleColor(const ImVec4& color, float scale, float alpha) {
return ImVec4(color.x * scale, color.y * scale, color.z * scale, alpha);
}
ImVec4 ScaleColor(const ImVec4& color, float scale) {
return ScaleColor(color, scale, color.w);
}
ImVec4 WithAlpha(ImVec4 color, float alpha) {
color.w = alpha;
return color;
}
ImVec4 GetEditorAccentColor(EditorType type, const gui::Theme& theme) {
switch (type) {
case EditorType::kOverworld:
return gui::ConvertColorToImVec4(theme.success);
case EditorType::kDungeon:
return gui::ConvertColorToImVec4(theme.secondary);
case EditorType::kGraphics:
return gui::ConvertColorToImVec4(theme.warning);
case EditorType::kSprite:
return gui::ConvertColorToImVec4(theme.info);
case EditorType::kMessage:
return gui::ConvertColorToImVec4(theme.primary);
case EditorType::kMusic:
return gui::ConvertColorToImVec4(theme.accent);
case EditorType::kPalette:
return gui::ConvertColorToImVec4(theme.error);
case EditorType::kScreen:
return gui::ConvertColorToImVec4(theme.info);
case EditorType::kAssembly:
return gui::ConvertColorToImVec4(theme.text_secondary);
case EditorType::kHex:
return gui::ConvertColorToImVec4(theme.success);
case EditorType::kEmulator:
return gui::ConvertColorToImVec4(theme.info);
case EditorType::kAgent:
return gui::ConvertColorToImVec4(theme.accent);
case EditorType::kSettings:
case EditorType::kUnknown:
default:
return gui::ConvertColorToImVec4(theme.text_primary);
}
}
} // namespace
EditorSelectionDialog::EditorSelectionDialog() { EditorSelectionDialog::EditorSelectionDialog() {
// Initialize editor metadata with distinct colors
// Use platform-aware shortcut strings (Cmd on macOS, Ctrl elsewhere) // Use platform-aware shortcut strings (Cmd on macOS, Ctrl elsewhere)
const char* ctrl = gui::GetCtrlDisplayName(); const char* ctrl = gui::GetCtrlDisplayName();
editors_ = { editors_ = {
{EditorType::kOverworld, "Overworld", ICON_MD_MAP, {EditorType::kOverworld, "Overworld", ICON_MD_MAP,
"Edit overworld maps, entrances, and properties", "Edit overworld maps, entrances, and properties",
absl::StrFormat("%s+1", ctrl), false, true, absl::StrFormat("%s+1", ctrl), false, true},
ImVec4(0.133f, 0.545f, 0.133f, 1.0f)}, // Hyrule green
{EditorType::kDungeon, "Dungeon", ICON_MD_CASTLE, {EditorType::kDungeon, "Dungeon", ICON_MD_CASTLE,
"Design dungeon rooms, layouts, and mechanics", "Design dungeon rooms, layouts, and mechanics",
absl::StrFormat("%s+2", ctrl), false, true, absl::StrFormat("%s+2", ctrl), false, true},
ImVec4(0.502f, 0.0f, 0.502f, 1.0f)}, // Ganon purple
{EditorType::kGraphics, "Graphics", ICON_MD_PALETTE, {EditorType::kGraphics, "Graphics", ICON_MD_PALETTE,
"Modify tiles, palettes, and graphics sets", "Modify tiles, palettes, and graphics sets",
absl::StrFormat("%s+3", ctrl), false, true, absl::StrFormat("%s+3", ctrl), false, true},
ImVec4(1.0f, 0.843f, 0.0f, 1.0f)}, // Triforce gold
{EditorType::kSprite, "Sprites", ICON_MD_EMOJI_EMOTIONS, {EditorType::kSprite, "Sprites", ICON_MD_EMOJI_EMOTIONS,
"Edit sprite graphics and properties", "Edit sprite graphics and properties",
absl::StrFormat("%s+4", ctrl), false, true, absl::StrFormat("%s+4", ctrl), false, true},
ImVec4(1.0f, 0.647f, 0.0f, 1.0f)}, // Spirit orange
{EditorType::kMessage, "Messages", ICON_MD_CHAT_BUBBLE, {EditorType::kMessage, "Messages", ICON_MD_CHAT_BUBBLE,
"Edit dialogue, signs, and text", "Edit dialogue, signs, and text",
absl::StrFormat("%s+5", ctrl), false, true, absl::StrFormat("%s+5", ctrl), false, true},
ImVec4(0.196f, 0.6f, 0.8f, 1.0f)}, // Master sword blue
{EditorType::kMusic, "Music", ICON_MD_MUSIC_NOTE, {EditorType::kMusic, "Music", ICON_MD_MUSIC_NOTE,
"Configure music and sound effects", "Configure music and sound effects",
absl::StrFormat("%s+6", ctrl), false, true, absl::StrFormat("%s+6", ctrl), false, true},
ImVec4(0.416f, 0.353f, 0.804f, 1.0f)}, // Shadow purple
{EditorType::kPalette, "Palettes", ICON_MD_COLOR_LENS, {EditorType::kPalette, "Palettes", ICON_MD_COLOR_LENS,
"Edit color palettes and animations", "Edit color palettes and animations",
absl::StrFormat("%s+7", ctrl), false, true, absl::StrFormat("%s+7", ctrl), false, true},
ImVec4(0.863f, 0.078f, 0.235f, 1.0f)}, // Heart red
{EditorType::kScreen, "Screens", ICON_MD_TV, {EditorType::kScreen, "Screens", ICON_MD_TV,
"Edit title screen and ending screens", "Edit title screen and ending screens",
absl::StrFormat("%s+8", ctrl), false, true, absl::StrFormat("%s+8", ctrl), false, true},
ImVec4(0.4f, 0.8f, 1.0f, 1.0f)}, // Sky blue
{EditorType::kAssembly, "Assembly", ICON_MD_CODE, {EditorType::kAssembly, "Assembly", ICON_MD_CODE,
"Write and edit assembly code", "Write and edit assembly code",
absl::StrFormat("%s+9", ctrl), false, false, absl::StrFormat("%s+9", ctrl), false, false},
ImVec4(0.8f, 0.8f, 0.8f, 1.0f)}, // Silver
{EditorType::kHex, "Hex Editor", ICON_MD_DATA_ARRAY, {EditorType::kHex, "Hex Editor", ICON_MD_DATA_ARRAY,
"Direct ROM memory editing and comparison", "Direct ROM memory editing and comparison",
absl::StrFormat("%s+0", ctrl), false, true, absl::StrFormat("%s+0", ctrl), false, true},
ImVec4(0.2f, 0.8f, 0.4f, 1.0f)}, // Matrix green
{EditorType::kEmulator, "Emulator", ICON_MD_VIDEOGAME_ASSET, {EditorType::kEmulator, "Emulator", ICON_MD_VIDEOGAME_ASSET,
"Test and debug your ROM in real-time with live debugging", "Test and debug your ROM in real-time with live debugging",
absl::StrFormat("%s+Shift+E", ctrl), false, true, absl::StrFormat("%s+Shift+E", ctrl), false, true},
ImVec4(0.2f, 0.6f, 1.0f, 1.0f)}, // Emulator blue
{EditorType::kAgent, "AI Agent", ICON_MD_SMART_TOY, {EditorType::kAgent, "AI Agent", ICON_MD_SMART_TOY,
"Configure AI agent, collaboration, and automation", "Configure AI agent, collaboration, and automation",
absl::StrFormat("%s+Shift+A", ctrl), false, false, absl::StrFormat("%s+Shift+A", ctrl), false, false},
ImVec4(0.8f, 0.4f, 1.0f, 1.0f)}, // Purple/magenta
}; };
LoadRecentEditors(); LoadRecentEditors();
@@ -125,17 +226,29 @@ bool EditorSelectionDialog::Show(bool* p_open) {
ImGui::Text(ICON_MD_APPS " All Editors"); ImGui::Text(ICON_MD_APPS " All Editors");
ImGui::Spacing(); ImGui::Spacing();
float button_size = 200.0f; const float scale = GetEditorSelectScale();
int columns = const float min_width = kEditorSelectCardBaseWidth * scale;
static_cast<int>(ImGui::GetContentRegionAvail().x / button_size); const float max_width =
columns = std::max(columns, 1); kEditorSelectCardBaseWidth * kEditorSelectCardWidthMaxFactor * scale;
const float min_height = kEditorSelectCardBaseHeight * scale;
const float max_height =
kEditorSelectCardBaseHeight * kEditorSelectCardHeightMaxFactor * scale;
const float spacing = ImGui::GetStyle().ItemSpacing.x;
const float aspect_ratio = min_height / std::max(min_width, 1.0f);
GridLayout layout = ComputeGridLayout(ImGui::GetContentRegionAvail().x,
min_width, max_width, min_height,
max_height, min_width, aspect_ratio,
spacing);
if (ImGui::BeginTable("##EditorGrid", columns, ImGuiTableFlags_None)) { int column = 0;
for (size_t i = 0; i < editors_.size(); ++i) { for (size_t i = 0; i < editors_.size(); ++i) {
ImGui::TableNextColumn(); if (column == 0) {
ImGui::SetCursorPosX(layout.row_start_x);
}
EditorType prev_selection = selected_editor_; EditorType prev_selection = selected_editor_;
DrawEditorPanel(editors_[i], static_cast<int>(i)); DrawEditorPanel(editors_[i], static_cast<int>(i),
ImVec2(layout.item_width, layout.item_height));
// Check if an editor was just selected // Check if an editor was just selected
if (selected_editor_ != prev_selection) { if (selected_editor_ != prev_selection) {
@@ -150,8 +263,18 @@ bool EditorSelectionDialog::Show(bool* p_open) {
*p_open = false; *p_open = false;
} }
} }
column += 1;
if (column < layout.columns) {
ImGui::SameLine(0.0f, layout.spacing);
} else {
column = 0;
ImGui::Spacing();
} }
ImGui::EndTable(); }
if (column != 0) {
ImGui::NewLine();
} }
} }
ImGui::End(); ImGui::End();
@@ -168,29 +291,38 @@ bool EditorSelectionDialog::Show(bool* p_open) {
} }
void EditorSelectionDialog::DrawWelcomeHeader() { void EditorSelectionDialog::DrawWelcomeHeader() {
ImDrawList* draw_list = ImGui::GetWindowDrawList(); const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
ImVec2 header_start = ImGui::GetCursorScreenPos(); const ImVec4 accent = gui::ConvertColorToImVec4(theme.accent);
const ImVec4 text_secondary = gui::ConvertColorToImVec4(theme.text_secondary);
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[2]); // Large font ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[2]); // Large font
// Colorful gradient title ImGui::TextColored(accent, ICON_MD_EDIT " Select an Editor");
ImVec4 title_color = ImVec4(1.0f, 0.843f, 0.0f, 1.0f); // Triforce gold
ImGui::TextColored(title_color, ICON_MD_EDIT " Select an Editor");
ImGui::PopFont(); ImGui::PopFont();
// Subtitle with gradient separator ImGui::TextColored(text_secondary,
ImVec2 subtitle_pos = ImGui::GetCursorScreenPos();
ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.8f, 1.0f),
"Choose an editor to begin working on your ROM. " "Choose an editor to begin working on your ROM. "
"You can open multiple editors simultaneously."); "You can open multiple editors simultaneously.");
} }
void EditorSelectionDialog::DrawQuickAccessButtons() { void EditorSelectionDialog::DrawQuickAccessButtons() {
ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
ICON_MD_HISTORY " Recently Used"); const ImVec4 accent = gui::ConvertColorToImVec4(theme.accent);
ImGui::TextColored(accent, ICON_MD_HISTORY " Recently Used");
ImGui::Spacing(); ImGui::Spacing();
const float scale = GetEditorSelectScale();
const float min_width = kEditorSelectRecentBaseWidth * scale;
const float max_width =
kEditorSelectRecentBaseWidth * kEditorSelectRecentWidthMaxFactor * scale;
const float height = kEditorSelectRecentBaseHeight * scale;
const float spacing = ImGui::GetStyle().ItemSpacing.x;
GridLayout layout = ComputeGridLayout(
ImGui::GetContentRegionAvail().x, min_width, max_width, height, height,
min_width, height / std::max(min_width, 1.0f), spacing);
int column = 0;
for (EditorType type : recent_editors_) { for (EditorType type : recent_editors_) {
// Find editor info // Find editor info
auto it = std::find_if( auto it = std::find_if(
@@ -198,18 +330,21 @@ void EditorSelectionDialog::DrawQuickAccessButtons() {
[type](const EditorInfo& info) { return info.type == type; }); [type](const EditorInfo& info) { return info.type == type; });
if (it != editors_.end()) { if (it != editors_.end()) {
// Use editor's theme color for button if (column == 0) {
ImVec4 color = it->color; ImGui::SetCursorPosX(layout.row_start_x);
}
const ImVec4 base_color = GetEditorAccentColor(it->type, theme);
ImGui::PushStyleColor( ImGui::PushStyleColor(
ImGuiCol_Button, ImGuiCol_Button,
ImVec4(color.x * 0.5f, color.y * 0.5f, color.z * 0.5f, 0.7f)); ScaleColor(base_color, 0.5f, 0.7f));
ImGui::PushStyleColor( ImGui::PushStyleColor(
ImGuiCol_ButtonHovered, ImGuiCol_ButtonHovered,
ImVec4(color.x * 0.7f, color.y * 0.7f, color.z * 0.7f, 0.9f)); ScaleColor(base_color, 0.7f, 0.9f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, color); ImGui::PushStyleColor(ImGuiCol_ButtonActive, WithAlpha(base_color, 1.0f));
if (ImGui::Button(absl::StrCat(it->icon, " ", it->name).c_str(), if (ImGui::Button(absl::StrCat(it->icon, " ", it->name).c_str(),
ImVec2(150, 35))) { ImVec2(layout.item_width, layout.item_height))) {
selected_editor_ = type; selected_editor_ = type;
} }
@@ -219,123 +354,166 @@ void EditorSelectionDialog::DrawQuickAccessButtons() {
ImGui::SetTooltip("%s", it->description); ImGui::SetTooltip("%s", it->description);
} }
ImGui::SameLine(); column += 1;
if (column < layout.columns) {
ImGui::SameLine(0.0f, layout.spacing);
} else {
column = 0;
ImGui::Spacing();
}
} }
} }
if (column != 0) {
ImGui::NewLine(); ImGui::NewLine();
} }
}
void EditorSelectionDialog::DrawEditorPanel(const EditorInfo& info, int index) { void EditorSelectionDialog::DrawEditorPanel(const EditorInfo& info, int index,
const ImVec2& card_size) {
ImGui::PushID(index); ImGui::PushID(index);
ImVec2 button_size(180, 120); const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
ImVec2 cursor_pos = ImGui::GetCursorScreenPos(); const ImVec4 base_color = GetEditorAccentColor(info.type, theme);
const ImVec4 text_primary = gui::ConvertColorToImVec4(theme.text_primary);
const ImVec4 text_secondary = gui::ConvertColorToImVec4(theme.text_secondary);
const ImVec4 accent = gui::ConvertColorToImVec4(theme.accent);
const ImGuiStyle& style = ImGui::GetStyle();
const float line_height = ImGui::GetTextLineHeight();
const float padding_x = std::max(style.FramePadding.x, card_size.x * 0.06f);
const float padding_y = std::max(style.FramePadding.y, card_size.y * 0.08f);
const float footer_height = info.shortcut.empty() ? 0.0f : line_height;
const float footer_spacing = info.shortcut.empty() ? 0.0f : style.ItemSpacing.y;
const float available_icon_height =
card_size.y - padding_y * 2.0f - line_height - footer_height - footer_spacing;
const float min_icon_radius = line_height * 0.9f;
float max_icon_radius = card_size.y * 0.24f;
max_icon_radius = std::max(max_icon_radius, min_icon_radius);
const float icon_radius =
std::clamp(available_icon_height * 0.5f, min_icon_radius, max_icon_radius);
const ImVec2 cursor_pos = ImGui::GetCursorScreenPos();
ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImDrawList* draw_list = ImGui::GetWindowDrawList();
const ImVec2 icon_center(cursor_pos.x + card_size.x * 0.5f,
cursor_pos.y + padding_y + icon_radius);
float title_y = icon_center.y + icon_radius + style.ItemSpacing.y;
const float footer_y = cursor_pos.y + card_size.y - padding_y - footer_height;
if (title_y + line_height > footer_y - style.ItemSpacing.y) {
title_y = footer_y - line_height - style.ItemSpacing.y;
}
// Panel styling with gradients // Panel styling with gradients
bool is_recent = std::find(recent_editors_.begin(), recent_editors_.end(), bool is_recent = std::find(recent_editors_.begin(), recent_editors_.end(),
info.type) != recent_editors_.end(); info.type) != recent_editors_.end();
// Create gradient background // Create gradient background
ImVec4 base_color = info.color; ImU32 color_top = ImGui::GetColorU32(ScaleColor(base_color, 0.4f, 0.85f));
ImU32 color_top = ImGui::GetColorU32(ImVec4( ImU32 color_bottom =
base_color.x * 0.4f, base_color.y * 0.4f, base_color.z * 0.4f, 0.8f)); ImGui::GetColorU32(ScaleColor(base_color, 0.2f, 0.9f));
ImU32 color_bottom = ImGui::GetColorU32(ImVec4(
base_color.x * 0.2f, base_color.y * 0.2f, base_color.z * 0.2f, 0.9f));
// Draw gradient card background // Draw gradient card background
draw_list->AddRectFilledMultiColor( draw_list->AddRectFilledMultiColor(
cursor_pos, cursor_pos,
ImVec2(cursor_pos.x + button_size.x, cursor_pos.y + button_size.y), ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y),
color_top, color_top, color_bottom, color_bottom); color_top, color_top, color_bottom, color_bottom);
// Colored border // Colored border
ImU32 border_color = ImU32 border_color =
is_recent is_recent
? ImGui::GetColorU32( ? ImGui::GetColorU32(
ImVec4(base_color.x, base_color.y, base_color.z, 1.0f)) WithAlpha(base_color, 1.0f))
: ImGui::GetColorU32(ImVec4(base_color.x * 0.6f, base_color.y * 0.6f, : ImGui::GetColorU32(ScaleColor(base_color, 0.6f, 0.7f));
base_color.z * 0.6f, 0.7f)); const float rounding = std::max(style.FrameRounding, card_size.y * 0.05f);
const float border_thickness =
is_recent ? std::max(2.0f, style.FrameBorderSize + 1.0f)
: std::max(1.0f, style.FrameBorderSize);
draw_list->AddRect( draw_list->AddRect(
cursor_pos, cursor_pos,
ImVec2(cursor_pos.x + button_size.x, cursor_pos.y + button_size.y), ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y),
border_color, 4.0f, 0, is_recent ? 3.0f : 2.0f); border_color, rounding, 0, border_thickness);
// Recent indicator badge // Recent indicator badge
if (is_recent) { if (is_recent) {
ImVec2 badge_pos(cursor_pos.x + button_size.x - 25, cursor_pos.y + 5); const float badge_radius =
draw_list->AddCircleFilled(badge_pos, 12, ImGui::GetColorU32(base_color), std::clamp(line_height * 0.6f, line_height * 0.4f, line_height);
16); ImVec2 badge_pos(cursor_pos.x + card_size.x - padding_x - badge_radius,
ImGui::SetCursorScreenPos(ImVec2(badge_pos.x - 6, badge_pos.y - 8)); cursor_pos.y + padding_y + badge_radius);
ImGui::TextColored(ImVec4(1, 1, 1, 1), ICON_MD_STAR); draw_list->AddCircleFilled(badge_pos, badge_radius,
ImGui::GetColorU32(base_color), 16);
ImVec2 star_size = ImGui::CalcTextSize(ICON_MD_STAR);
ImGui::SetCursorScreenPos(
ImVec2(badge_pos.x - star_size.x * 0.5f,
badge_pos.y - star_size.y * 0.5f));
ImGui::TextColored(text_primary, ICON_MD_STAR);
} }
// Make button transparent (we draw our own background) // Make button transparent (we draw our own background)
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); ImVec4 button_bg = ImGui::GetStyleColorVec4(ImGuiCol_Button);
button_bg.w = 0.0f;
ImGui::PushStyleColor(ImGuiCol_Button, button_bg);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
ImVec4(base_color.x * 0.3f, base_color.y * 0.3f, ScaleColor(base_color, 0.3f, 0.5f));
base_color.z * 0.3f, 0.5f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::PushStyleColor(ImGuiCol_ButtonActive,
ImVec4(base_color.x * 0.5f, base_color.y * 0.5f, ScaleColor(base_color, 0.5f, 0.7f));
base_color.z * 0.5f, 0.7f));
ImGui::SetCursorScreenPos(cursor_pos); ImGui::SetCursorScreenPos(cursor_pos);
bool clicked = bool clicked =
ImGui::Button(absl::StrCat("##", info.name).c_str(), button_size); ImGui::Button(absl::StrCat("##", info.name).c_str(), card_size);
bool is_hovered = ImGui::IsItemHovered(); bool is_hovered = ImGui::IsItemHovered();
ImGui::PopStyleColor(3); ImGui::PopStyleColor(3);
// Draw icon with colored background circle // Draw icon with colored background circle
ImVec2 icon_center(cursor_pos.x + button_size.x / 2, cursor_pos.y + 30);
ImU32 icon_bg = ImGui::GetColorU32(base_color); ImU32 icon_bg = ImGui::GetColorU32(base_color);
draw_list->AddCircleFilled(icon_center, 22, icon_bg, 32); draw_list->AddCircleFilled(icon_center, icon_radius, icon_bg, 32);
// Draw icon // Draw icon
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[2]); // Larger font for icon ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[2]); // Larger font for icon
ImVec2 icon_size = ImGui::CalcTextSize(info.icon); ImVec2 icon_size = ImGui::CalcTextSize(info.icon);
ImGui::SetCursorScreenPos( ImGui::SetCursorScreenPos(
ImVec2(icon_center.x - icon_size.x / 2, icon_center.y - icon_size.y / 2)); ImVec2(icon_center.x - icon_size.x / 2, icon_center.y - icon_size.y / 2));
ImGui::TextColored(ImVec4(1, 1, 1, 1), "%s", info.icon); ImGui::TextColored(text_primary, "%s", info.icon);
ImGui::PopFont(); ImGui::PopFont();
// Draw name // Draw name
ImGui::SetCursorScreenPos(ImVec2(cursor_pos.x + 10, cursor_pos.y + 65)); const float name_wrap_width = card_size.x - padding_x * 2.0f;
ImGui::PushTextWrapPos(cursor_pos.x + button_size.x - 10); ImGui::PushTextWrapPos(cursor_pos.x + card_size.x - padding_x);
ImVec2 name_size = ImGui::CalcTextSize(info.name); ImVec2 name_size =
ImGui::CalcTextSize(info.name, nullptr, false, name_wrap_width);
ImGui::SetCursorScreenPos(ImVec2( ImGui::SetCursorScreenPos(ImVec2(
cursor_pos.x + (button_size.x - name_size.x) / 2, cursor_pos.y + 65)); cursor_pos.x + (card_size.x - name_size.x) / 2.0f, title_y));
ImGui::TextColored(base_color, "%s", info.name); ImGui::TextColored(base_color, "%s", info.name);
ImGui::PopTextWrapPos(); ImGui::PopTextWrapPos();
// Draw shortcut hint if available // Draw shortcut hint if available
if (!info.shortcut.empty()) { if (!info.shortcut.empty()) {
ImGui::SetCursorScreenPos( ImGui::SetCursorScreenPos(
ImVec2(cursor_pos.x + 10, cursor_pos.y + button_size.y - 20)); ImVec2(cursor_pos.x + padding_x, footer_y));
ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "%s", info.shortcut.c_str()); ImGui::TextColored(text_secondary, "%s", info.shortcut.c_str());
} }
// Hover glow effect // Hover glow effect
if (is_hovered) { if (is_hovered) {
ImU32 glow_color = ImGui::GetColorU32( ImU32 glow_color =
ImVec4(base_color.x, base_color.y, base_color.z, 0.2f)); ImGui::GetColorU32(ScaleColor(base_color, 1.0f, 0.18f));
draw_list->AddRectFilled( draw_list->AddRectFilled(
cursor_pos, cursor_pos,
ImVec2(cursor_pos.x + button_size.x, cursor_pos.y + button_size.y), ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y),
glow_color, 4.0f); glow_color, rounding);
} }
// Enhanced tooltip with fixed sizing // Enhanced tooltip with fixed sizing
if (is_hovered) { if (is_hovered) {
// Force tooltip to have a fixed max width to prevent flickering const float tooltip_width = std::clamp(card_size.x * 1.4f, 240.0f, 340.0f);
ImGui::SetNextWindowSize(ImVec2(300, 0), ImGuiCond_Always); ImGui::SetNextWindowSize(ImVec2(tooltip_width, 0), ImGuiCond_Always);
ImGui::BeginTooltip(); ImGui::BeginTooltip();
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[1]); // Medium font ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[1]); // Medium font
ImGui::TextColored(base_color, "%s %s", info.icon, info.name); ImGui::TextColored(base_color, "%s %s", info.icon, info.name);
ImGui::PopFont(); ImGui::PopFont();
ImGui::Separator(); ImGui::Separator();
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + 280); ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + tooltip_width - 20.0f);
ImGui::TextWrapped("%s", info.description); ImGui::TextWrapped("%s", info.description);
ImGui::PopTextWrapPos(); ImGui::PopTextWrapPos();
if (!info.shortcut.empty()) { if (!info.shortcut.empty()) {
@@ -344,8 +522,7 @@ void EditorSelectionDialog::DrawEditorPanel(const EditorInfo& info, int index) {
} }
if (is_recent) { if (is_recent) {
ImGui::Spacing(); ImGui::Spacing();
ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ImGui::TextColored(accent, ICON_MD_STAR " Recently used");
ICON_MD_STAR " Recently used");
} }
ImGui::EndTooltip(); ImGui::EndTooltip();
} }

View File

@@ -23,7 +23,6 @@ struct EditorInfo {
std::string shortcut; // Platform-aware shortcut string std::string shortcut; // Platform-aware shortcut string
bool recently_used = false; bool recently_used = false;
bool requires_rom = true; bool requires_rom = true;
ImVec4 color = ImVec4(0.5f, 0.5f, 0.5f, 1.0f); // Theme color for this editor
}; };
/** /**
@@ -94,7 +93,8 @@ class EditorSelectionDialog {
} }
private: private:
void DrawEditorPanel(const EditorInfo& info, int index); void DrawEditorPanel(const EditorInfo& info, int index,
const ImVec2& card_size);
void DrawWelcomeHeader(); void DrawWelcomeHeader();
void DrawQuickAccessButtons(); void DrawQuickAccessButtons();

View File

@@ -9,6 +9,7 @@
#include "app/gui/core/icons.h" #include "app/gui/core/icons.h"
#include "app/gui/core/style.h" #include "app/gui/core/style.h"
#include "imgui/misc/cpp/imgui_stdlib.h" #include "imgui/misc/cpp/imgui_stdlib.h"
#include "util/file_util.h"
#include "util/hex.h" #include "util/hex.h"
namespace yaze { namespace yaze {
@@ -351,7 +352,8 @@ void PopupManager::DrawNewProjectPopup() {
if (Button(absl::StrFormat("%s ROM File", ICON_MD_VIDEOGAME_ASSET).c_str(), if (Button(absl::StrFormat("%s ROM File", ICON_MD_VIDEOGAME_ASSET).c_str(),
gui::kDefaultModalSize)) { gui::kDefaultModalSize)) {
rom_filename = util::FileDialogWrapper::ShowOpenFileDialog(); rom_filename = util::FileDialogWrapper::ShowOpenFileDialog(
util::MakeRomFileDialogOptions(false));
} }
SameLine(); SameLine();
Text("%s", rom_filename.empty() ? "(Not set)" : rom_filename.c_str()); Text("%s", rom_filename.empty() ? "(Not set)" : rom_filename.c_str());

View File

@@ -11,6 +11,12 @@
#ifdef __EMSCRIPTEN__ #ifdef __EMSCRIPTEN__
#include <emscripten.h> #include <emscripten.h>
#endif #endif
#ifdef __APPLE__
#include <TargetConditionals.h>
#endif
#if defined(__APPLE__) && TARGET_OS_IOS == 1
#include "app/platform/ios/ios_platform_state.h"
#endif
#include "app/editor/editor.h" #include "app/editor/editor.h"
#include "app/editor/editor_manager.h" #include "app/editor/editor_manager.h"
#include "app/editor/system/editor_registry.h" #include "app/editor/system/editor_registry.h"
@@ -74,10 +80,13 @@ UICoordinator::UICoordinator(
toast_manager_.Show( toast_manager_.Show(
absl::StrFormat("Failed to load ROM: %s", status.message()), absl::StrFormat("Failed to load ROM: %s", status.message()),
ToastType::kError); ToastType::kError);
} else { }
#if !(defined(__APPLE__) && TARGET_OS_IOS == 1)
else {
// Transition to Dashboard on successful ROM load // Transition to Dashboard on successful ROM load
SetStartupSurface(StartupSurface::kDashboard); SetStartupSurface(StartupSurface::kDashboard);
} }
#endif
} }
#endif #endif
}); });
@@ -188,6 +197,147 @@ void UICoordinator::DrawAllUI() {
// Draw popups and toasts // Draw popups and toasts
DrawAllPopups(); DrawAllPopups();
toast_manager_.Draw(); toast_manager_.Draw();
DrawMobileNavigation();
}
bool UICoordinator::IsCompactLayout() const {
const ImGuiViewport* viewport = ImGui::GetMainViewport();
if (!viewport) {
return false;
}
const float width = viewport->WorkSize.x;
#if defined(__APPLE__) && TARGET_OS_IOS == 1
return true;
#else
return width < 900.0f;
#endif
}
void UICoordinator::DrawMobileNavigation() {
if (!IsCompactLayout()) {
return;
}
const ImGuiViewport* viewport = ImGui::GetMainViewport();
if (!viewport) {
return;
}
const ImGuiStyle& style = ImGui::GetStyle();
ImVec2 safe = style.DisplaySafeAreaPadding;
#if defined(__APPLE__) && TARGET_OS_IOS == 1
const auto safe_area = ::yaze::platform::ios::GetSafeAreaInsets();
if (safe_area.left != 0.0f || safe_area.right != 0.0f ||
safe_area.top != 0.0f || safe_area.bottom != 0.0f) {
safe = ImVec2(safe_area.right, safe_area.bottom);
}
#endif
const float button_size = std::max(44.0f, ImGui::GetFontSize() * 2.1f);
const float padding = style.WindowPadding.x;
const ImVec2 pos(viewport->WorkPos.x + viewport->WorkSize.x - safe.x - padding -
button_size,
viewport->WorkPos.y + viewport->WorkSize.y - safe.y - padding -
button_size);
const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
const ImVec4 button_color = gui::ConvertColorToImVec4(theme.button);
const ImVec4 button_hovered = gui::ConvertColorToImVec4(theme.button_hovered);
const ImVec4 button_active = gui::ConvertColorToImVec4(theme.button_active);
const ImVec4 button_text = gui::ConvertColorToImVec4(theme.accent);
ImGui::SetNextWindowPos(pos, ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(button_size, button_size), ImGuiCond_Always);
ImGuiWindowFlags flags =
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoFocusOnAppearing |
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoBackground;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
if (ImGui::Begin("##MobileNavButton", nullptr, flags)) {
ImGui::PushStyleColor(ImGuiCol_Button, button_color);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, button_hovered);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, button_active);
ImGui::PushStyleColor(ImGuiCol_Text, button_text);
if (ImGui::Button(ICON_MD_APPS, ImVec2(button_size, button_size))) {
ImGui::OpenPopup("##MobileNavPopup");
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Navigation");
}
ImGui::PopStyleColor(4);
}
ImGui::End();
ImGui::PopStyleVar();
ImGui::PushStyleColor(ImGuiCol_PopupBg,
gui::ConvertColorToImVec4(theme.surface));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12.0f, 10.0f));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.0f, 6.0f));
if (ImGui::BeginPopup("##MobileNavPopup")) {
bool has_rom = false;
if (editor_manager_) {
auto* current_rom = editor_manager_->GetCurrentRom();
has_rom = current_rom && current_rom->is_loaded();
}
if (ImGui::MenuItem(ICON_MD_FOLDER_OPEN " Open ROM")) {
if (editor_manager_) {
auto status = editor_manager_->LoadRom();
if (!status.ok()) {
toast_manager_.Show(
absl::StrFormat("Failed to load ROM: %s", status.message()),
ToastType::kError);
}
#if !(defined(__APPLE__) && TARGET_OS_IOS == 1)
else {
SetStartupSurface(StartupSurface::kDashboard);
}
#endif
}
}
if (has_rom) {
if (ImGui::MenuItem(ICON_MD_DASHBOARD " Dashboard", nullptr,
current_startup_surface_ ==
StartupSurface::kDashboard)) {
SetStartupSurface(StartupSurface::kDashboard);
}
if (ImGui::MenuItem(
ICON_MD_EDIT " Editor", nullptr,
current_startup_surface_ == StartupSurface::kEditor)) {
SetStartupSurface(StartupSurface::kEditor);
}
}
const bool sidebar_visible = panel_manager_.IsSidebarVisible();
if (ImGui::MenuItem(ICON_MD_VIEW_SIDEBAR " Toggle Sidebar", nullptr,
sidebar_visible)) {
TogglePanelSidebar();
}
if (editor_manager_) {
auto* right_panel = editor_manager_->right_panel_manager();
if (right_panel) {
const bool settings_active = right_panel->IsPanelActive(
RightPanelManager::PanelType::kSettings);
if (ImGui::MenuItem(ICON_MD_SETTINGS " Settings", nullptr,
settings_active)) {
right_panel->TogglePanel(RightPanelManager::PanelType::kSettings);
}
}
}
ImGui::EndPopup();
}
ImGui::PopStyleVar(2);
ImGui::PopStyleColor();
} }
// ============================================================================= // =============================================================================

View File

@@ -273,6 +273,10 @@ class UICoordinator {
const ImVec4& color, std::function<void()> callback, const ImVec4& color, std::function<void()> callback,
bool enabled = true); bool enabled = true);
// Mobile helpers
bool IsCompactLayout() const;
void DrawMobileNavigation();
// Layout and positioning helpers // Layout and positioning helpers
void CenterWindow(const std::string& window_name); void CenterWindow(const std::string& window_name);
void PositionWindow(const std::string& window_name, float x, float y); void PositionWindow(const std::string& window_name, float x, float y);

View File

@@ -5,6 +5,7 @@
#include <cmath> #include <cmath>
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <string_view>
#include "absl/strings/str_format.h" #include "absl/strings/str_format.h"
#include "absl/time/clock.h" #include "absl/time/clock.h"
@@ -31,21 +32,33 @@ ImVec4 GetThemedColor(const char* color_name, const ImVec4& fallback) {
auto& theme_mgr = gui::ThemeManager::Get(); auto& theme_mgr = gui::ThemeManager::Get();
const auto& theme = theme_mgr.GetCurrentTheme(); const auto& theme = theme_mgr.GetCurrentTheme();
// TODO: Fix this if (!color_name) {
// Map color names to theme colors return fallback;
// if (strcmp(color_name, "triforce_gold") == 0) { }
// return theme.accent.to_im_vec4();
// } else if (strcmp(color_name, "hyrule_green") == 0) { const std::string_view name(color_name);
// return theme.success.to_im_vec4(); if (name == "triforce_gold") {
// } else if (strcmp(color_name, "master_sword_blue") == 0) { return gui::ConvertColorToImVec4(theme.accent);
// return theme.info.to_im_vec4(); }
// } else if (strcmp(color_name, "ganon_purple") == 0) { if (name == "hyrule_green") {
// return theme.secondary.to_im_vec4(); return gui::ConvertColorToImVec4(theme.success);
// } else if (strcmp(color_name, "heart_red") == 0) { }
// return theme.error.to_im_vec4(); if (name == "master_sword_blue") {
// } else if (strcmp(color_name, "spirit_orange") == 0) { return gui::ConvertColorToImVec4(theme.info);
// return theme.warning.to_im_vec4(); }
// } if (name == "ganon_purple") {
return gui::ConvertColorToImVec4(theme.secondary);
}
if (name == "heart_red") {
return gui::ConvertColorToImVec4(theme.error);
}
if (name == "spirit_orange") {
return gui::ConvertColorToImVec4(theme.warning);
}
if (name == "shadow_purple") {
return ImLerp(gui::ConvertColorToImVec4(theme.secondary),
gui::GetSurfaceVec4(), 0.4f);
}
return fallback; return fallback;
} }
@@ -59,6 +72,11 @@ const ImVec4 kHeartRedFallback = ImVec4(0.863f, 0.078f, 0.235f, 1.0f);
const ImVec4 kSpiritOrangeFallback = ImVec4(1.0f, 0.647f, 0.0f, 1.0f); const ImVec4 kSpiritOrangeFallback = ImVec4(1.0f, 0.647f, 0.0f, 1.0f);
const ImVec4 kShadowPurpleFallback = ImVec4(0.416f, 0.353f, 0.804f, 1.0f); const ImVec4 kShadowPurpleFallback = ImVec4(0.416f, 0.353f, 0.804f, 1.0f);
constexpr float kRecentCardBaseWidth = 220.0f;
constexpr float kRecentCardBaseHeight = 95.0f;
constexpr float kRecentCardWidthMaxFactor = 1.25f;
constexpr float kRecentCardHeightMaxFactor = 1.25f;
// Active colors (updated each frame from theme) // Active colors (updated each frame from theme)
ImVec4 kTriforceGold = kTriforceGoldFallback; ImVec4 kTriforceGold = kTriforceGoldFallback;
ImVec4 kHyruleGreen = kHyruleGreenFallback; ImVec4 kHyruleGreen = kHyruleGreenFallback;
@@ -116,7 +134,9 @@ void DrawTriforceBackground(ImDrawList* draw_list, ImVec2 pos, float size,
draw_list->AddTriangleFilled(p1, p2, p3, color); draw_list->AddTriangleFilled(p1, p2, p3, color);
}; };
ImU32 gold = ImGui::GetColorU32(ImVec4(1.0f, 0.843f, 0.0f, alpha)); ImVec4 gold_color = kTriforceGold;
gold_color.w = alpha;
ImU32 gold = ImGui::GetColorU32(gold_color);
// Proper triforce layout with three triangles // Proper triforce layout with three triangles
float small_size = size / 2.0f; float small_size = size / 2.0f;
@@ -134,6 +154,51 @@ void DrawTriforceBackground(ImDrawList* draw_list, ImVec2 pos, float size,
gold); gold);
} }
struct GridLayout {
int columns = 1;
float item_width = 0.0f;
float item_height = 0.0f;
float spacing = 0.0f;
float row_start_x = 0.0f;
};
GridLayout ComputeGridLayout(float avail_width, float min_width,
float max_width, float min_height,
float max_height, float preferred_width,
float aspect_ratio, float spacing) {
GridLayout layout;
layout.spacing = spacing;
const auto width_for_columns = [avail_width, spacing](int columns) {
return (avail_width - spacing * static_cast<float>(columns - 1)) /
static_cast<float>(columns);
};
layout.columns =
std::max(1, static_cast<int>((avail_width + spacing) /
(preferred_width + spacing)));
layout.item_width = width_for_columns(layout.columns);
while (layout.columns > 1 && layout.item_width < min_width) {
layout.columns -= 1;
layout.item_width = width_for_columns(layout.columns);
}
layout.item_width = std::min(layout.item_width, max_width);
layout.item_width = std::min(layout.item_width, avail_width);
layout.item_height =
std::clamp(layout.item_width * aspect_ratio, min_height, max_height);
const float row_width =
layout.item_width * static_cast<float>(layout.columns) +
spacing * static_cast<float>(layout.columns - 1);
layout.row_start_x = ImGui::GetCursorPosX();
if (row_width < avail_width) {
layout.row_start_x += (avail_width - row_width) * 0.5f;
}
return layout;
}
} // namespace } // namespace
WelcomeScreen::WelcomeScreen() { WelcomeScreen::WelcomeScreen() {
@@ -380,15 +445,53 @@ bool WelcomeScreen::Show(bool* p_open) {
ImGui::Dummy(ImVec2(0, 10)); ImGui::Dummy(ImVec2(0, 10));
ImGui::BeginChild("WelcomeContent", ImVec2(0, -60), false); ImGui::BeginChild("WelcomeContent", ImVec2(0, -60), false);
const float content_width = ImGui::GetContentRegionAvail().x;
const float content_height = ImGui::GetContentRegionAvail().y;
const bool narrow_layout = content_width < 900.0f;
// Left side - Quick Actions & Templates if (narrow_layout) {
float left_height = std::clamp(content_height * 0.45f, 260.0f, content_height);
ImGui::BeginChild("LeftPanel", ImVec2(0, left_height), true,
ImGuiWindowFlags_NoScrollbar);
DrawQuickActions();
ImGui::Spacing();
ImVec2 sep_start = ImGui::GetCursorScreenPos();
draw_list->AddLine(
sep_start,
ImVec2(sep_start.x + ImGui::GetContentRegionAvail().x, sep_start.y),
ImGui::GetColorU32(
ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.2f)),
1.0f);
ImGui::Dummy(ImVec2(0, 5));
DrawTemplatesSection();
ImGui::EndChild();
ImGui::Spacing();
ImGui::BeginChild("RightPanel", ImVec2(0, 0), true);
DrawRecentProjects();
ImGui::Spacing();
sep_start = ImGui::GetCursorScreenPos();
draw_list->AddLine(
sep_start,
ImVec2(sep_start.x + ImGui::GetContentRegionAvail().x, sep_start.y),
ImGui::GetColorU32(ImVec4(kMasterSwordBlue.x, kMasterSwordBlue.y,
kMasterSwordBlue.z, 0.2f)),
1.0f);
ImGui::Dummy(ImVec2(0, 5));
DrawWhatsNew();
ImGui::EndChild();
} else {
ImGui::BeginChild("LeftPanel", ImGui::BeginChild("LeftPanel",
ImVec2(ImGui::GetContentRegionAvail().x * 0.3f, 0), true, ImVec2(ImGui::GetContentRegionAvail().x * 0.3f, 0), true,
ImGuiWindowFlags_NoScrollbar); ImGuiWindowFlags_NoScrollbar);
DrawQuickActions(); DrawQuickActions();
ImGui::Spacing(); ImGui::Spacing();
// Subtle separator
ImVec2 sep_start = ImGui::GetCursorScreenPos(); ImVec2 sep_start = ImGui::GetCursorScreenPos();
draw_list->AddLine( draw_list->AddLine(
sep_start, sep_start,
@@ -403,12 +506,10 @@ bool WelcomeScreen::Show(bool* p_open) {
ImGui::SameLine(); ImGui::SameLine();
// Right side - Recent Projects & What's New
ImGui::BeginChild("RightPanel", ImVec2(0, 0), true); ImGui::BeginChild("RightPanel", ImVec2(0, 0), true);
DrawRecentProjects(); DrawRecentProjects();
ImGui::Spacing(); ImGui::Spacing();
// Subtle separator
sep_start = ImGui::GetCursorScreenPos(); sep_start = ImGui::GetCursorScreenPos();
draw_list->AddLine( draw_list->AddLine(
sep_start, sep_start,
@@ -420,6 +521,7 @@ bool WelcomeScreen::Show(bool* p_open) {
ImGui::Dummy(ImVec2(0, 5)); ImGui::Dummy(ImVec2(0, 5));
DrawWhatsNew(); DrawWhatsNew();
ImGui::EndChild(); ImGui::EndChild();
}
ImGui::EndChild(); ImGui::EndChild();
@@ -629,7 +731,8 @@ void WelcomeScreen::DrawRecentProjects() {
if (recent_projects_.empty()) { if (recent_projects_.empty()) {
// Simple empty state // Simple empty state
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.6f, 0.6f, 1.0f)); const ImVec4 text_secondary = gui::GetTextSecondaryVec4();
ImGui::PushStyleColor(ImGuiCol_Text, text_secondary);
ImVec2 cursor = ImGui::GetCursorPos(); ImVec2 cursor = ImGui::GetCursorPos();
ImGui::SetCursorPosX(cursor.x + ImGui::GetContentRegionAvail().x * 0.3f); ImGui::SetCursorPosX(cursor.x + ImGui::GetContentRegionAvail().x * 0.3f);
@@ -644,47 +747,89 @@ void WelcomeScreen::DrawRecentProjects() {
return; return;
} }
// Grid layout for project cards (compact) const float scale = ImGui::GetFontSize() / 16.0f;
float card_width = 220.0f; // Reduced for compactness const float min_width = kRecentCardBaseWidth * scale;
float card_height = 95.0f; // Reduced for less scrolling const float max_width = kRecentCardBaseWidth * kRecentCardWidthMaxFactor * scale;
int columns = const float min_height = kRecentCardBaseHeight * scale;
std::max(1, (int)(ImGui::GetContentRegionAvail().x / (card_width + 12))); const float max_height = kRecentCardBaseHeight * kRecentCardHeightMaxFactor * scale;
const float spacing = ImGui::GetStyle().ItemSpacing.x;
const float aspect_ratio = min_height / std::max(min_width, 1.0f);
GridLayout layout = ComputeGridLayout(ImGui::GetContentRegionAvail().x,
min_width, max_width, min_height,
max_height, min_width, aspect_ratio,
spacing);
int column = 0;
for (size_t i = 0; i < recent_projects_.size(); ++i) { for (size_t i = 0; i < recent_projects_.size(); ++i) {
if (i % columns != 0) { if (column == 0) {
ImGui::SameLine(); ImGui::SetCursorPosX(layout.row_start_x);
} }
DrawProjectPanel(recent_projects_[i], i);
DrawProjectPanel(recent_projects_[i], static_cast<int>(i),
ImVec2(layout.item_width, layout.item_height));
column += 1;
if (column < layout.columns) {
ImGui::SameLine(0.0f, layout.spacing);
} else {
column = 0;
ImGui::Spacing();
} }
} }
void WelcomeScreen::DrawProjectPanel(const RecentProject& project, int index) { if (column != 0) {
ImGui::NewLine();
}
}
void WelcomeScreen::DrawProjectPanel(const RecentProject& project, int index,
const ImVec2& card_size) {
ImGui::BeginGroup(); ImGui::BeginGroup();
ImVec2 card_size(200, 95); // Compact size const ImVec4 surface = gui::GetSurfaceVec4();
const ImVec4 surface_variant = gui::GetSurfaceVariantVec4();
const ImVec4 text_primary = gui::GetOnSurfaceVec4();
const ImVec4 text_secondary = gui::GetTextSecondaryVec4();
const ImVec4 text_disabled = gui::GetTextDisabledVec4();
ImVec2 resolved_card_size = card_size;
ImVec2 cursor_pos = ImGui::GetCursorScreenPos(); ImVec2 cursor_pos = ImGui::GetCursorScreenPos();
// Subtle hover scale (only on actual hover, no animation) // Subtle hover scale (only on actual hover, no animation)
float scale = card_hover_scale_[index]; float hover_scale = card_hover_scale_[index];
if (scale != 1.0f) { if (hover_scale != 1.0f) {
ImVec2 center(cursor_pos.x + card_size.x / 2, ImVec2 center(cursor_pos.x + resolved_card_size.x / 2,
cursor_pos.y + card_size.y / 2); cursor_pos.y + resolved_card_size.y / 2);
cursor_pos.x = center.x - (card_size.x * scale) / 2; cursor_pos.x = center.x - (resolved_card_size.x * hover_scale) / 2;
cursor_pos.y = center.y - (card_size.y * scale) / 2; cursor_pos.y = center.y - (resolved_card_size.y * hover_scale) / 2;
card_size.x *= scale; resolved_card_size.x *= hover_scale;
card_size.y *= scale; resolved_card_size.y *= hover_scale;
} }
const float layout_scale = resolved_card_size.y / kRecentCardBaseHeight;
const float padding = 8.0f * layout_scale;
const float icon_radius = 15.0f * layout_scale;
const float icon_offset = 13.0f * layout_scale;
const float text_offset = 32.0f * layout_scale;
const float name_offset_y = 8.0f * layout_scale;
const float rom_offset_y = 35.0f * layout_scale;
const float path_offset_y = 58.0f * layout_scale;
// Draw card background with subtle gradient // Draw card background with subtle gradient
ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImDrawList* draw_list = ImGui::GetWindowDrawList();
// Gradient background // Gradient background
ImU32 color_top = ImGui::GetColorU32(ImVec4(0.15f, 0.20f, 0.25f, 1.0f)); ImVec4 color_top = ImLerp(surface_variant, surface, 0.7f);
ImU32 color_bottom = ImGui::GetColorU32(ImVec4(0.10f, 0.15f, 0.20f, 1.0f)); ImVec4 color_bottom = ImLerp(surface_variant, surface, 0.3f);
ImU32 color_top_u32 = ImGui::GetColorU32(color_top);
ImU32 color_bottom_u32 = ImGui::GetColorU32(color_bottom);
draw_list->AddRectFilledMultiColor( draw_list->AddRectFilledMultiColor(
cursor_pos, cursor_pos,
ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y), color_top, ImVec2(cursor_pos.x + resolved_card_size.x,
color_top, color_bottom, color_bottom); cursor_pos.y + resolved_card_size.y),
color_top_u32,
color_top_u32, color_bottom_u32, color_bottom_u32);
// Static themed border // Static themed border
ImVec4 border_color_base = (index % 3 == 0) ? kHyruleGreen ImVec4 border_color_base = (index % 3 == 0) ? kHyruleGreen
@@ -695,13 +840,14 @@ void WelcomeScreen::DrawProjectPanel(const RecentProject& project, int index) {
draw_list->AddRect( draw_list->AddRect(
cursor_pos, cursor_pos,
ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y), ImVec2(cursor_pos.x + resolved_card_size.x,
cursor_pos.y + resolved_card_size.y),
border_color, 6.0f, 0, 2.0f); border_color, 6.0f, 0, 2.0f);
// Make the card clickable // Make the card clickable
ImGui::SetCursorScreenPos(cursor_pos); ImGui::SetCursorScreenPos(cursor_pos);
ImGui::InvisibleButton(absl::StrFormat("ProjectPanel_%d", index).c_str(), ImGui::InvisibleButton(absl::StrFormat("ProjectPanel_%d", index).c_str(),
card_size); resolved_card_size);
bool is_hovered = ImGui::IsItemHovered(); bool is_hovered = ImGui::IsItemHovered();
bool is_clicked = ImGui::IsItemClicked(); bool is_clicked = ImGui::IsItemClicked();
@@ -714,50 +860,64 @@ void WelcomeScreen::DrawProjectPanel(const RecentProject& project, int index) {
ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.15f)); ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.15f));
draw_list->AddRectFilled( draw_list->AddRectFilled(
cursor_pos, cursor_pos,
ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y), ImVec2(cursor_pos.x + resolved_card_size.x,
cursor_pos.y + resolved_card_size.y),
hover_color, 6.0f); hover_color, 6.0f);
} }
// Draw content (tighter layout) // Draw content (tighter layout)
ImVec2 content_pos(cursor_pos.x + 8, cursor_pos.y + 8); ImVec2 content_pos(cursor_pos.x + padding, cursor_pos.y + padding);
// Icon with colored background circle (compact) // Icon with colored background circle (compact)
ImVec2 icon_center(content_pos.x + 13, content_pos.y + 13); ImVec2 icon_center(content_pos.x + icon_offset, content_pos.y + icon_offset);
ImU32 icon_bg = ImGui::GetColorU32(border_color_base); ImU32 icon_bg = ImGui::GetColorU32(border_color_base);
draw_list->AddCircleFilled(icon_center, 15, icon_bg, 24); draw_list->AddCircleFilled(icon_center, icon_radius, icon_bg, 24);
// Center the icon properly // Center the icon properly
ImVec2 icon_size = ImGui::CalcTextSize(ICON_MD_VIDEOGAME_ASSET); ImVec2 icon_size = ImGui::CalcTextSize(ICON_MD_VIDEOGAME_ASSET);
ImGui::SetCursorScreenPos( ImGui::SetCursorScreenPos(
ImVec2(icon_center.x - icon_size.x / 2, icon_center.y - icon_size.y / 2)); ImVec2(icon_center.x - icon_size.x / 2, icon_center.y - icon_size.y / 2));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1, 1, 1, 1)); ImGui::PushStyleColor(ImGuiCol_Text, text_primary);
ImGui::Text(ICON_MD_VIDEOGAME_ASSET); ImGui::Text(ICON_MD_VIDEOGAME_ASSET);
ImGui::PopStyleColor(); ImGui::PopStyleColor();
// Project name (compact, shorten if too long) // Project name (compact, shorten if too long)
ImGui::SetCursorScreenPos(ImVec2(content_pos.x + 32, content_pos.y + 8)); ImGui::SetCursorScreenPos(
ImGui::PushTextWrapPos(cursor_pos.x + card_size.x - 8); ImVec2(content_pos.x + text_offset, content_pos.y + name_offset_y));
ImGui::PushTextWrapPos(cursor_pos.x + resolved_card_size.x - padding);
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]); // Default font ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]); // Default font
std::string short_name = project.name; std::string short_name = project.name;
if (short_name.length() > 22) { const float approx_char_width = ImGui::CalcTextSize("A").x;
short_name = short_name.substr(0, 19) + "..."; const float name_max_width =
std::max(120.0f, resolved_card_size.x - text_offset - padding);
size_t max_chars = static_cast<size_t>(
std::max(12.0f, name_max_width / std::max(approx_char_width, 1.0f)));
if (short_name.length() > max_chars) {
short_name = short_name.substr(0, max_chars - 3) + "...";
} }
ImGui::TextColored(kTriforceGold, "%s", short_name.c_str()); ImGui::TextColored(kTriforceGold, "%s", short_name.c_str());
ImGui::PopFont(); ImGui::PopFont();
ImGui::PopTextWrapPos(); ImGui::PopTextWrapPos();
// ROM title (compact) // ROM title (compact)
ImGui::SetCursorScreenPos(ImVec2(content_pos.x + 4, content_pos.y + 35)); ImGui::SetCursorScreenPos(
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.65f, 0.65f, 0.65f, 1.0f)); ImVec2(content_pos.x + padding * 0.5f, content_pos.y + rom_offset_y));
ImGui::PushStyleColor(ImGuiCol_Text, text_secondary);
ImGui::Text(ICON_MD_GAMEPAD " %s", project.rom_title.c_str()); ImGui::Text(ICON_MD_GAMEPAD " %s", project.rom_title.c_str());
ImGui::PopStyleColor(); ImGui::PopStyleColor();
// Path in card (compact) // Path in card (compact)
ImGui::SetCursorScreenPos(ImVec2(content_pos.x + 4, content_pos.y + 58)); ImGui::SetCursorScreenPos(
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.4f, 0.4f, 0.4f, 1.0f)); ImVec2(content_pos.x + padding * 0.5f, content_pos.y + path_offset_y));
ImGui::PushStyleColor(ImGuiCol_Text, text_disabled);
std::string short_path = project.filepath; std::string short_path = project.filepath;
if (short_path.length() > 26) { const float path_max_width =
short_path = "..." + short_path.substr(short_path.length() - 23); std::max(120.0f, resolved_card_size.x - padding * 2.0f);
size_t max_path_chars = static_cast<size_t>(
std::max(18.0f, path_max_width / std::max(approx_char_width, 1.0f)));
if (short_path.length() > max_path_chars) {
short_path =
"..." + short_path.substr(short_path.length() - (max_path_chars - 3));
} }
ImGui::Text(ICON_MD_FOLDER " %s", short_path.c_str()); ImGui::Text(ICON_MD_FOLDER " %s", short_path.c_str());
ImGui::PopStyleColor(); ImGui::PopStyleColor();

View File

@@ -114,7 +114,8 @@ class WelcomeScreen {
void DrawHeader(); void DrawHeader();
void DrawQuickActions(); void DrawQuickActions();
void DrawRecentProjects(); void DrawRecentProjects();
void DrawProjectPanel(const RecentProject& project, int index); void DrawProjectPanel(const RecentProject& project, int index,
const ImVec2& card_size);
void DrawTemplatesSection(); void DrawTemplatesSection();
void DrawTipsSection(); void DrawTipsSection();
void DrawWhatsNew(); void DrawWhatsNew();