#include "app/editor/ui/editor_selection_dialog.h" #include #include #include #include "absl/strings/str_cat.h" #include "imgui/imgui.h" #include "app/gui/core/icons.h" #include "app/gui/core/style.h" #include "util/file_util.h" namespace yaze { namespace editor { EditorSelectionDialog::EditorSelectionDialog() { // Initialize editor metadata with distinct colors editors_ = { {EditorType::kOverworld, "Overworld", ICON_MD_MAP, "Edit overworld maps, entrances, and properties", "Ctrl+1", false, true, ImVec4(0.133f, 0.545f, 0.133f, 1.0f)}, // Hyrule green {EditorType::kDungeon, "Dungeon", ICON_MD_CASTLE, "Design dungeon rooms, layouts, and mechanics", "Ctrl+2", false, true, ImVec4(0.502f, 0.0f, 0.502f, 1.0f)}, // Ganon purple {EditorType::kGraphics, "Graphics", ICON_MD_PALETTE, "Modify tiles, palettes, and graphics sets", "Ctrl+3", false, true, ImVec4(1.0f, 0.843f, 0.0f, 1.0f)}, // Triforce gold {EditorType::kSprite, "Sprites", ICON_MD_EMOJI_EMOTIONS, "Edit sprite graphics and properties", "Ctrl+4", false, true, ImVec4(1.0f, 0.647f, 0.0f, 1.0f)}, // Spirit orange {EditorType::kMessage, "Messages", ICON_MD_CHAT_BUBBLE, "Edit dialogue, signs, and text", "Ctrl+5", false, true, ImVec4(0.196f, 0.6f, 0.8f, 1.0f)}, // Master sword blue {EditorType::kMusic, "Music", ICON_MD_MUSIC_NOTE, "Configure music and sound effects", "Ctrl+6", false, true, ImVec4(0.416f, 0.353f, 0.804f, 1.0f)}, // Shadow purple {EditorType::kPalette, "Palettes", ICON_MD_COLOR_LENS, "Edit color palettes and animations", "Ctrl+7", false, true, ImVec4(0.863f, 0.078f, 0.235f, 1.0f)}, // Heart red {EditorType::kScreen, "Screens", ICON_MD_TV, "Edit title screen and ending screens", "Ctrl+8", false, true, ImVec4(0.4f, 0.8f, 1.0f, 1.0f)}, // Sky blue {EditorType::kAssembly, "Assembly", ICON_MD_CODE, "Write and edit assembly code", "Ctrl+9", false, false, ImVec4(0.8f, 0.8f, 0.8f, 1.0f)}, // Silver {EditorType::kHex, "Hex Editor", ICON_MD_DATA_ARRAY, "Direct ROM memory editing and comparison", "Ctrl+0", false, true, ImVec4(0.2f, 0.8f, 0.4f, 1.0f)}, // Matrix green {EditorType::kEmulator, "Emulator", ICON_MD_VIDEOGAME_ASSET, "Test and debug your ROM in real-time with live debugging", "Ctrl+Shift+E", false, true, ImVec4(0.2f, 0.6f, 1.0f, 1.0f)}, // Emulator blue {EditorType::kAgent, "AI Agent", ICON_MD_SMART_TOY, "Configure AI agent, collaboration, and automation", "Ctrl+Shift+A", false, false, ImVec4(0.8f, 0.4f, 1.0f, 1.0f)}, // Purple/magenta {EditorType::kSettings, "Settings", ICON_MD_SETTINGS, "Configure ROM and project settings", "", false, true, ImVec4(0.6f, 0.6f, 0.6f, 1.0f)}, // Gray }; LoadRecentEditors(); } bool EditorSelectionDialog::Show(bool* p_open) { // Sync internal state with external flag if (p_open && *p_open && !is_open_) { is_open_ = true; } if (!is_open_) { if (p_open) *p_open = false; return false; } bool editor_selected = false; bool* window_open = p_open ? p_open : &is_open_; // Set window properties immediately before Begin to prevent them from affecting tooltips ImVec2 center = ImGui::GetMainViewport()->GetCenter(); ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); ImGui::SetNextWindowSize(ImVec2(950, 650), ImGuiCond_Appearing); // Slightly larger for better layout if (ImGui::Begin("Editor Selection", window_open, ImGuiWindowFlags_NoCollapse)) { DrawWelcomeHeader(); ImGui::Separator(); ImGui::Spacing(); // Quick access buttons for recently used if (!recent_editors_.empty()) { DrawQuickAccessButtons(); ImGui::Separator(); ImGui::Spacing(); } // Main editor grid ImGui::Text(ICON_MD_APPS " All Editors"); ImGui::Spacing(); float button_size = 200.0f; int columns = static_cast(ImGui::GetContentRegionAvail().x / button_size); columns = std::max(columns, 1); if (ImGui::BeginTable("##EditorGrid", columns, ImGuiTableFlags_None)) { for (size_t i = 0; i < editors_.size(); ++i) { ImGui::TableNextColumn(); EditorType prev_selection = selected_editor_; DrawEditorCard(editors_[i], static_cast(i)); // Check if an editor was just selected if (selected_editor_ != prev_selection) { editor_selected = true; MarkRecentlyUsed(selected_editor_); if (selection_callback_) { selection_callback_(selected_editor_); } } } ImGui::EndTable(); } } ImGui::End(); // Sync state back if (p_open && !(*p_open)) { is_open_ = false; } if (editor_selected) { is_open_ = false; if (p_open) *p_open = false; } return editor_selected; } void EditorSelectionDialog::DrawWelcomeHeader() { ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 header_start = ImGui::GetCursorScreenPos(); ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[2]); // Large font // Colorful gradient title 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(); // Subtitle with gradient separator 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. " "You can open multiple editors simultaneously."); } void EditorSelectionDialog::DrawQuickAccessButtons() { ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_HISTORY " Recently Used"); ImGui::Spacing(); for (EditorType type : recent_editors_) { // Find editor info auto it = std::find_if(editors_.begin(), editors_.end(), [type](const EditorInfo& info) { return info.type == type; }); if (it != editors_.end()) { // Use editor's theme color for button 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); if (ImGui::Button(absl::StrCat(it->icon, " ", it->name).c_str(), ImVec2(150, 35))) { selected_editor_ = type; } ImGui::PopStyleColor(3); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", it->description); } ImGui::SameLine(); } } ImGui::NewLine(); } void EditorSelectionDialog::DrawEditorCard(const EditorInfo& info, int index) { ImGui::PushID(index); ImVec2 button_size(180, 120); ImVec2 cursor_pos = ImGui::GetCursorScreenPos(); ImDrawList* draw_list = ImGui::GetWindowDrawList(); // Card styling with gradients bool is_recent = std::find(recent_editors_.begin(), recent_editors_.end(), info.type) != recent_editors_.end(); // Create gradient background ImVec4 base_color = info.color; ImU32 color_top = ImGui::GetColorU32(ImVec4(base_color.x * 0.4f, base_color.y * 0.4f, base_color.z * 0.4f, 0.8f)); 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_list->AddRectFilledMultiColor( cursor_pos, ImVec2(cursor_pos.x + button_size.x, cursor_pos.y + button_size.y), color_top, color_top, color_bottom, color_bottom); // Colored border ImU32 border_color = is_recent ? ImGui::GetColorU32(ImVec4(base_color.x, base_color.y, base_color.z, 1.0f)) : ImGui::GetColorU32(ImVec4(base_color.x * 0.6f, base_color.y * 0.6f, base_color.z * 0.6f, 0.7f)); draw_list->AddRect(cursor_pos, ImVec2(cursor_pos.x + button_size.x, cursor_pos.y + button_size.y), border_color, 4.0f, 0, is_recent ? 3.0f : 2.0f); // Recent indicator badge if (is_recent) { ImVec2 badge_pos(cursor_pos.x + button_size.x - 25, cursor_pos.y + 5); draw_list->AddCircleFilled(badge_pos, 12, ImGui::GetColorU32(base_color), 16); ImGui::SetCursorScreenPos(ImVec2(badge_pos.x - 6, badge_pos.y - 8)); ImGui::TextColored(ImVec4(1, 1, 1, 1), ICON_MD_STAR); } // Make button transparent (we draw our own background) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(base_color.x * 0.3f, base_color.y * 0.3f, base_color.z * 0.3f, 0.5f)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(base_color.x * 0.5f, base_color.y * 0.5f, base_color.z * 0.5f, 0.7f)); ImGui::SetCursorScreenPos(cursor_pos); bool clicked = ImGui::Button(absl::StrCat("##", info.name).c_str(), button_size); bool is_hovered = ImGui::IsItemHovered(); ImGui::PopStyleColor(3); // 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); draw_list->AddCircleFilled(icon_center, 22, icon_bg, 32); // Draw icon ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[2]); // Larger font for icon ImVec2 icon_size = ImGui::CalcTextSize(info.icon); ImGui::SetCursorScreenPos(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::PopFont(); // Draw name ImGui::SetCursorScreenPos(ImVec2(cursor_pos.x + 10, cursor_pos.y + 65)); ImGui::PushTextWrapPos(cursor_pos.x + button_size.x - 10); ImVec2 name_size = ImGui::CalcTextSize(info.name); ImGui::SetCursorScreenPos(ImVec2(cursor_pos.x + (button_size.x - name_size.x) / 2, cursor_pos.y + 65)); ImGui::TextColored(base_color, "%s", info.name); ImGui::PopTextWrapPos(); // Draw shortcut hint if available if (info.shortcut && info.shortcut[0]) { ImGui::SetCursorScreenPos(ImVec2(cursor_pos.x + 10, cursor_pos.y + button_size.y - 20)); ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "%s", info.shortcut); } // Hover glow effect if (is_hovered) { ImU32 glow_color = ImGui::GetColorU32(ImVec4(base_color.x, base_color.y, base_color.z, 0.2f)); draw_list->AddRectFilled(cursor_pos, ImVec2(cursor_pos.x + button_size.x, cursor_pos.y + button_size.y), glow_color, 4.0f); } // Enhanced tooltip with fixed sizing if (is_hovered) { // Force tooltip to have a fixed max width to prevent flickering ImGui::SetNextWindowSize(ImVec2(300, 0), ImGuiCond_Always); ImGui::BeginTooltip(); ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[1]); // Medium font ImGui::TextColored(base_color, "%s %s", info.icon, info.name); ImGui::PopFont(); ImGui::Separator(); ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + 280); ImGui::TextWrapped("%s", info.description); ImGui::PopTextWrapPos(); if (info.shortcut && info.shortcut[0]) { ImGui::Spacing(); ImGui::TextColored(base_color, ICON_MD_KEYBOARD " %s", info.shortcut); } if (is_recent) { ImGui::Spacing(); ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_STAR " Recently used"); } ImGui::EndTooltip(); } if (clicked) { selected_editor_ = info.type; } ImGui::PopID(); } void EditorSelectionDialog::MarkRecentlyUsed(EditorType type) { // Remove if already in list auto it = std::find(recent_editors_.begin(), recent_editors_.end(), type); if (it != recent_editors_.end()) { recent_editors_.erase(it); } // Add to front recent_editors_.insert(recent_editors_.begin(), type); // Limit size if (recent_editors_.size() > kMaxRecentEditors) { recent_editors_.resize(kMaxRecentEditors); } SaveRecentEditors(); } void EditorSelectionDialog::LoadRecentEditors() { try { auto data = util::LoadFileFromConfigDir("recent_editors.txt"); if (!data.empty()) { std::istringstream ss(data); std::string line; while (std::getline(ss, line) && recent_editors_.size() < kMaxRecentEditors) { int type_int = std::stoi(line); if (type_int >= 0 && type_int < static_cast(EditorType::kSettings)) { recent_editors_.push_back(static_cast(type_int)); } } } } catch (...) { // Ignore errors, just start with empty recent list } } void EditorSelectionDialog::SaveRecentEditors() { try { std::ostringstream ss; for (EditorType type : recent_editors_) { ss << static_cast(type) << "\n"; } util::SaveFile("recent_editors.txt", ss.str()); } catch (...) { // Ignore save errors } } } // namespace editor } // namespace yaze