Files
yaze/src/app/editor/ui/editor_selection_dialog.cc
scawful 58f3213c62 refactor(gui): reorganize GUI includes and introduce new components
- Updated include paths for various GUI-related headers to improve organization and clarity.
- Introduced new components for better modularity, including PaletteEditorWidget and EditorCardManager.
- Refactored existing code to utilize the new components, ensuring consistency across the GUI subsystem.

Benefits:
- Enhances maintainability and readability of the GUI code.
- Facilitates future enhancements and optimizations within the GUI subsystem.
2025-10-13 10:21:03 -04:00

378 lines
13 KiB
C++

#include "app/editor/ui/editor_selection_dialog.h"
#include <sstream>
#include <fstream>
#include <algorithm>
#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<int>(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<int>(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<int>(EditorType::kSettings)) {
recent_editors_.push_back(static_cast<EditorType>(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<int>(type) << "\n";
}
util::SaveFile("recent_editors.txt", ss.str());
} catch (...) {
// Ignore save errors
}
}
} // namespace editor
} // namespace yaze