feat: Enhance EditorManager with Editor Selection and Shortcut Functionality
- Implemented an editor selection dialog with a callback for selecting various editor types, improving user interaction. - Added keyboard shortcuts for quick access to different editors, enhancing workflow efficiency. - Updated the menu structure to include options for the editor selection dialog and improved session management features. - Enhanced the welcome screen logic to optionally show the editor selection dialog after loading a ROM, streamlining the user experience.
This commit is contained in:
@@ -348,6 +348,48 @@ void EditorManager::Initialize(const std::string& filename) {
|
||||
welcome_screen_manually_closed_ = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize editor selection dialog callback
|
||||
editor_selection_dialog_.SetSelectionCallback([this](EditorType type) {
|
||||
if (!current_editor_set_) return;
|
||||
|
||||
editor_selection_dialog_.MarkRecentlyUsed(type);
|
||||
|
||||
switch (type) {
|
||||
case EditorType::kOverworld:
|
||||
current_editor_set_->overworld_editor_.set_active(true);
|
||||
break;
|
||||
case EditorType::kDungeon:
|
||||
current_editor_set_->dungeon_editor_.set_active(true);
|
||||
break;
|
||||
case EditorType::kGraphics:
|
||||
current_editor_set_->graphics_editor_.set_active(true);
|
||||
break;
|
||||
case EditorType::kSprite:
|
||||
current_editor_set_->sprite_editor_.set_active(true);
|
||||
break;
|
||||
case EditorType::kMessage:
|
||||
current_editor_set_->message_editor_.set_active(true);
|
||||
break;
|
||||
case EditorType::kMusic:
|
||||
current_editor_set_->music_editor_.set_active(true);
|
||||
break;
|
||||
case EditorType::kPalette:
|
||||
current_editor_set_->palette_editor_.set_active(true);
|
||||
break;
|
||||
case EditorType::kScreen:
|
||||
current_editor_set_->screen_editor_.set_active(true);
|
||||
break;
|
||||
case EditorType::kAssembly:
|
||||
show_asm_editor_ = true;
|
||||
break;
|
||||
case EditorType::kSettings:
|
||||
current_editor_set_->settings_editor_.set_active(true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
LoadUserSettings();
|
||||
|
||||
@@ -424,6 +466,50 @@ void EditorManager::Initialize(const std::string& filename) {
|
||||
context_.shortcut_manager.RegisterShortcut(
|
||||
"F1", ImGuiKey_F1, [this]() { popup_manager_->Show("About"); });
|
||||
|
||||
// Editor shortcuts (Ctrl+1-9, Ctrl+0)
|
||||
context_.shortcut_manager.RegisterShortcut(
|
||||
"Overworld Editor", {ImGuiKey_1, ImGuiMod_Ctrl},
|
||||
[this]() { if (current_editor_set_) current_editor_set_->overworld_editor_.set_active(true); });
|
||||
context_.shortcut_manager.RegisterShortcut(
|
||||
"Dungeon Editor", {ImGuiKey_2, ImGuiMod_Ctrl},
|
||||
[this]() { if (current_editor_set_) current_editor_set_->dungeon_editor_.set_active(true); });
|
||||
context_.shortcut_manager.RegisterShortcut(
|
||||
"Graphics Editor", {ImGuiKey_3, ImGuiMod_Ctrl},
|
||||
[this]() { if (current_editor_set_) current_editor_set_->graphics_editor_.set_active(true); });
|
||||
context_.shortcut_manager.RegisterShortcut(
|
||||
"Sprite Editor", {ImGuiKey_4, ImGuiMod_Ctrl},
|
||||
[this]() { if (current_editor_set_) current_editor_set_->sprite_editor_.set_active(true); });
|
||||
context_.shortcut_manager.RegisterShortcut(
|
||||
"Message Editor", {ImGuiKey_5, ImGuiMod_Ctrl},
|
||||
[this]() { if (current_editor_set_) current_editor_set_->message_editor_.set_active(true); });
|
||||
context_.shortcut_manager.RegisterShortcut(
|
||||
"Music Editor", {ImGuiKey_6, ImGuiMod_Ctrl},
|
||||
[this]() { if (current_editor_set_) current_editor_set_->music_editor_.set_active(true); });
|
||||
context_.shortcut_manager.RegisterShortcut(
|
||||
"Palette Editor", {ImGuiKey_7, ImGuiMod_Ctrl},
|
||||
[this]() { if (current_editor_set_) current_editor_set_->palette_editor_.set_active(true); });
|
||||
context_.shortcut_manager.RegisterShortcut(
|
||||
"Screen Editor", {ImGuiKey_8, ImGuiMod_Ctrl},
|
||||
[this]() { if (current_editor_set_) current_editor_set_->screen_editor_.set_active(true); });
|
||||
context_.shortcut_manager.RegisterShortcut(
|
||||
"Assembly Editor", {ImGuiKey_9, ImGuiMod_Ctrl},
|
||||
[this]() { show_asm_editor_ = true; });
|
||||
context_.shortcut_manager.RegisterShortcut(
|
||||
"Settings Editor", {ImGuiKey_0, ImGuiMod_Ctrl},
|
||||
[this]() { if (current_editor_set_) current_editor_set_->settings_editor_.set_active(true); });
|
||||
|
||||
// Editor Selection Dialog shortcut
|
||||
context_.shortcut_manager.RegisterShortcut(
|
||||
"Editor Selection", {ImGuiKey_E, ImGuiMod_Ctrl},
|
||||
[this]() { show_editor_selection_ = true; });
|
||||
|
||||
#ifdef YAZE_WITH_GRPC
|
||||
// Agent Editor shortcut
|
||||
context_.shortcut_manager.RegisterShortcut(
|
||||
"Agent Editor", {ImGuiKey_A, ImGuiMod_Ctrl, ImGuiMod_Shift},
|
||||
[this]() { agent_editor_.SetChatActive(true); });
|
||||
#endif
|
||||
|
||||
// Testing shortcuts (only when tests are enabled)
|
||||
#ifdef YAZE_ENABLE_TESTING
|
||||
context_.shortcut_manager.RegisterShortcut(
|
||||
@@ -453,481 +539,22 @@ void EditorManager::Initialize(const std::string& filename) {
|
||||
[this]() { LoadWorkspaceLayout(); });
|
||||
context_.shortcut_manager.RegisterShortcut(
|
||||
"Maximize Window", ImGuiKey_F11, [this]() { MaximizeCurrentWindow(); });
|
||||
|
||||
// Initialize menu items
|
||||
std::vector<gui::MenuItem> recent_files;
|
||||
auto& manager = core::RecentFilesManager::GetInstance();
|
||||
if (manager.GetRecentFiles().empty()) {
|
||||
recent_files.emplace_back("No Recent Files", "", nullptr);
|
||||
} else {
|
||||
for (const auto& filePath : manager.GetRecentFiles()) {
|
||||
recent_files.emplace_back(filePath, "", [filePath, this]() {
|
||||
status_ = OpenRomOrProject(filePath);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<gui::MenuItem> options_subitems;
|
||||
options_subitems.emplace_back(
|
||||
"Backup ROM", "", [this]() { backup_rom_ = !backup_rom_; },
|
||||
[this]() { return backup_rom_; });
|
||||
options_subitems.emplace_back(
|
||||
"Save New Auto", "", [this]() { save_new_auto_ = !save_new_auto_; },
|
||||
[this]() { return save_new_auto_; });
|
||||
options_subitems.emplace_back(
|
||||
"Autosave", "",
|
||||
[this]() {
|
||||
autosave_enabled_ = !autosave_enabled_;
|
||||
toast_manager_.Show(
|
||||
autosave_enabled_ ? "Autosave enabled" : "Autosave disabled",
|
||||
editor::ToastType::kInfo);
|
||||
},
|
||||
[this]() { return autosave_enabled_; });
|
||||
options_subitems.emplace_back(
|
||||
"Autosave Interval", "", [this]() {}, []() { return true; },
|
||||
std::vector<gui::MenuItem>{
|
||||
{"1 min", "",
|
||||
[this]() {
|
||||
autosave_interval_secs_ = 60.0f;
|
||||
SaveUserSettings();
|
||||
}},
|
||||
{"2 min", "",
|
||||
[this]() {
|
||||
autosave_interval_secs_ = 120.0f;
|
||||
SaveUserSettings();
|
||||
}},
|
||||
{"5 min", "",
|
||||
[this]() {
|
||||
autosave_interval_secs_ = 300.0f;
|
||||
SaveUserSettings();
|
||||
}},
|
||||
});
|
||||
|
||||
std::vector<gui::MenuItem> project_menu_subitems;
|
||||
project_menu_subitems.emplace_back(
|
||||
"New Project", "", [this]() { popup_manager_->Show("New Project"); });
|
||||
project_menu_subitems.emplace_back("Open Project", "",
|
||||
[this]() { status_ = OpenProject(); });
|
||||
project_menu_subitems.emplace_back(
|
||||
"Save Project", "", [this]() { status_ = SaveProject(); },
|
||||
[this]() { return current_project_.project_opened(); });
|
||||
project_menu_subitems.emplace_back(gui::kSeparator, "", nullptr, []() { return true; });
|
||||
project_menu_subitems.emplace_back(
|
||||
absl::StrCat(ICON_MD_EDIT, " Edit Project File"), "",
|
||||
[this]() {
|
||||
project_file_editor_.set_active(true);
|
||||
if (current_project_.project_opened() && !current_project_.filepath.empty()) {
|
||||
auto status = project_file_editor_.LoadFile(current_project_.filepath);
|
||||
if (!status.ok()) {
|
||||
toast_manager_.Show(std::string(status.message()), editor::ToastType::kError);
|
||||
}
|
||||
}
|
||||
},
|
||||
[this]() { return current_project_.project_opened(); });
|
||||
project_menu_subitems.emplace_back("Save Workspace Layout", "", [this]() {
|
||||
ImGuiID dockspace_id = ImGui::GetID("MyDockSpace");
|
||||
ImGui::SaveIniSettingsToDisk("yaze_workspace.ini");
|
||||
toast_manager_.Show("Workspace layout saved", editor::ToastType::kSuccess);
|
||||
});
|
||||
project_menu_subitems.emplace_back("Load Workspace Layout", "", [this]() {
|
||||
ImGui::LoadIniSettingsFromDisk("yaze_workspace.ini");
|
||||
toast_manager_.Show("Workspace layout loaded", editor::ToastType::kSuccess);
|
||||
});
|
||||
project_menu_subitems.emplace_back(
|
||||
"Workspace Presets", "", []() {}, []() { return true; },
|
||||
std::vector<gui::MenuItem>{
|
||||
{"Save Preset", "",
|
||||
[this]() {
|
||||
show_save_workspace_preset_ = true;
|
||||
}},
|
||||
{"Load Preset", "",
|
||||
[this]() {
|
||||
show_load_workspace_preset_ = true;
|
||||
}},
|
||||
});
|
||||
|
||||
gui::kMainMenu = {
|
||||
{"File",
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{
|
||||
{absl::StrCat(ICON_MD_FILE_OPEN, " Open"),
|
||||
context_.shortcut_manager.GetKeys("Open"),
|
||||
context_.shortcut_manager.GetCallback("Open")},
|
||||
{absl::StrCat(ICON_MD_HISTORY, " Open Recent"), "", []() {},
|
||||
[&manager]() { return !manager.GetRecentFiles().empty(); }, recent_files},
|
||||
{absl::StrCat(ICON_MD_FILE_DOWNLOAD, " Save"),
|
||||
context_.shortcut_manager.GetKeys("Save"),
|
||||
context_.shortcut_manager.GetCallback("Save")},
|
||||
{absl::StrCat(ICON_MD_SAVE_AS, " Save As.."), "",
|
||||
[this]() { popup_manager_->Show("Save As.."); }},
|
||||
{absl::StrCat(ICON_MD_BALLOT, " Project"), "", []() {},
|
||||
[]() { return true; }, project_menu_subitems},
|
||||
{absl::StrCat(ICON_MD_CLOSE, " Close"), "",
|
||||
[this]() {
|
||||
if (current_rom_) current_rom_->Close();
|
||||
}},
|
||||
{gui::kSeparator, "", nullptr, []() { return true; }},
|
||||
{absl::StrCat(ICON_MD_MISCELLANEOUS_SERVICES, " Options"), "",
|
||||
[]() {}, []() { return true; }, options_subitems},
|
||||
{absl::StrCat(ICON_MD_EXIT_TO_APP, " Quit"), "Ctrl+Q",
|
||||
[this]() { quit_ = true; }},
|
||||
}},
|
||||
{"Edit",
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{
|
||||
{absl::StrCat(ICON_MD_CONTENT_CUT, " Cut"),
|
||||
context_.shortcut_manager.GetKeys("Cut"),
|
||||
context_.shortcut_manager.GetCallback("Cut")},
|
||||
{absl::StrCat(ICON_MD_CONTENT_COPY, " Copy"),
|
||||
context_.shortcut_manager.GetKeys("Copy"),
|
||||
context_.shortcut_manager.GetCallback("Copy")},
|
||||
{absl::StrCat(ICON_MD_CONTENT_PASTE, " Paste"),
|
||||
context_.shortcut_manager.GetKeys("Paste"),
|
||||
context_.shortcut_manager.GetCallback("Paste")},
|
||||
{gui::kSeparator, "", nullptr, []() { return true; }},
|
||||
{absl::StrCat(ICON_MD_UNDO, " Undo"),
|
||||
context_.shortcut_manager.GetKeys("Undo"),
|
||||
context_.shortcut_manager.GetCallback("Undo")},
|
||||
{absl::StrCat(ICON_MD_REDO, " Redo"),
|
||||
context_.shortcut_manager.GetKeys("Redo"),
|
||||
context_.shortcut_manager.GetCallback("Redo")},
|
||||
{gui::kSeparator, "", nullptr, []() { return true; }},
|
||||
{absl::StrCat(ICON_MD_SEARCH, " Find"),
|
||||
context_.shortcut_manager.GetKeys("Find"),
|
||||
context_.shortcut_manager.GetCallback("Find")},
|
||||
{gui::kSeparator, "", nullptr, []() { return true; }},
|
||||
// Context-aware editor options
|
||||
{absl::StrCat(ICON_MD_REFRESH, " Refresh Data"), "F5",
|
||||
[this]() {
|
||||
if (current_editor_ && current_editor_->type() == EditorType::kOverworld) {
|
||||
// Refresh overworld data
|
||||
auto& ow_editor = static_cast<OverworldEditor&>(*current_editor_);
|
||||
[[maybe_unused]] auto load_status = ow_editor.Load();
|
||||
toast_manager_.Show("Overworld data refreshed", editor::ToastType::kInfo);
|
||||
} else if (current_editor_ && current_editor_->type() == EditorType::kDungeon) {
|
||||
// Refresh dungeon data
|
||||
toast_manager_.Show("Dungeon data refreshed", editor::ToastType::kInfo);
|
||||
}
|
||||
},
|
||||
[this]() { return current_editor_ != nullptr; }},
|
||||
{absl::StrCat(ICON_MD_MAP, " Load All Maps"), "",
|
||||
[this]() {
|
||||
if (current_editor_ && current_editor_->type() == EditorType::kOverworld) {
|
||||
toast_manager_.Show("Loading all overworld maps...", editor::ToastType::kInfo);
|
||||
}
|
||||
},
|
||||
[this]() { return current_editor_ && current_editor_->type() == EditorType::kOverworld; }},
|
||||
}},
|
||||
{"View",
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{
|
||||
{kAssemblyEditorName, "", [&]() { show_asm_editor_ = true; },
|
||||
[&]() { return show_asm_editor_; }},
|
||||
{kDungeonEditorName, "",
|
||||
[&]() { current_editor_set_->dungeon_editor_.set_active(true); },
|
||||
[&]() { return *current_editor_set_->dungeon_editor_.active(); }},
|
||||
{kGraphicsEditorName, "",
|
||||
[&]() { current_editor_set_->graphics_editor_.set_active(true); },
|
||||
[&]() { return *current_editor_set_->graphics_editor_.active(); }},
|
||||
{kMusicEditorName, "",
|
||||
[&]() { current_editor_set_->music_editor_.set_active(true); },
|
||||
[&]() { return *current_editor_set_->music_editor_.active(); }},
|
||||
{kOverworldEditorName, "",
|
||||
[&]() { current_editor_set_->overworld_editor_.set_active(true); },
|
||||
[&]() { return *current_editor_set_->overworld_editor_.active(); }},
|
||||
{kPaletteEditorName, "",
|
||||
[&]() { current_editor_set_->palette_editor_.set_active(true); },
|
||||
[&]() { return *current_editor_set_->palette_editor_.active(); }},
|
||||
{kScreenEditorName, "",
|
||||
[&]() { current_editor_set_->screen_editor_.set_active(true); },
|
||||
[&]() { return *current_editor_set_->screen_editor_.active(); }},
|
||||
{kSpriteEditorName, "",
|
||||
[&]() { current_editor_set_->sprite_editor_.set_active(true); },
|
||||
[&]() { return *current_editor_set_->sprite_editor_.active(); }},
|
||||
{kMessageEditorName, "",
|
||||
[&]() { current_editor_set_->message_editor_.set_active(true); },
|
||||
[&]() { return *current_editor_set_->message_editor_.active(); }},
|
||||
{kSettingsEditorName, "",
|
||||
[&]() { current_editor_set_->settings_editor_.set_active(true); },
|
||||
[&]() { return *current_editor_set_->settings_editor_.active(); }},
|
||||
{gui::kSeparator, "", nullptr, []() { return true; }},
|
||||
{absl::StrCat(ICON_MD_HOME, " Welcome Screen"), "",
|
||||
[&]() { show_welcome_screen_ = true; }},
|
||||
{absl::StrCat(ICON_MD_GAMEPAD, " Emulator"), "",
|
||||
[&]() { show_emulator_ = true; }},
|
||||
}},
|
||||
{"Workspace",
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{
|
||||
// Session Management
|
||||
{absl::StrCat(ICON_MD_TAB, " Sessions"), "", []() {}, []() { return true; },
|
||||
std::vector<gui::MenuItem>{
|
||||
{absl::StrCat(ICON_MD_ADD, " New Session"), "Ctrl+Shift+N",
|
||||
[this]() { CreateNewSession(); }},
|
||||
{absl::StrCat(ICON_MD_CONTENT_COPY, " Duplicate Session"), "",
|
||||
[this]() { DuplicateCurrentSession(); },
|
||||
[this]() { return current_rom_ != nullptr; }},
|
||||
{absl::StrCat(ICON_MD_CLOSE, " Close Session"), "Ctrl+Shift+W",
|
||||
[this]() { CloseCurrentSession(); },
|
||||
[this]() { return sessions_.size() > 1; }},
|
||||
{gui::kSeparator, "", nullptr, []() { return true; }},
|
||||
{absl::StrCat(ICON_MD_SWITCH_ACCOUNT, " Session Switcher"), "Ctrl+Tab",
|
||||
[this]() { show_session_switcher_ = true; },
|
||||
[this]() { return sessions_.size() > 1; }},
|
||||
{absl::StrCat(ICON_MD_VIEW_LIST, " Session Manager"), "",
|
||||
[this]() { show_session_manager_ = true; }},
|
||||
}},
|
||||
|
||||
// Layout & Docking
|
||||
{absl::StrCat(ICON_MD_DASHBOARD, " Layout"), "", []() {}, []() { return true; },
|
||||
std::vector<gui::MenuItem>{
|
||||
{absl::StrCat(ICON_MD_SPACE_DASHBOARD, " Layout Editor"), "",
|
||||
[this]() { show_workspace_layout = true; }},
|
||||
{absl::StrCat(ICON_MD_RESET_TV, " Reset Layout"), "",
|
||||
[this]() { ResetWorkspaceLayout(); }},
|
||||
{gui::kSeparator, "", nullptr, []() { return true; }},
|
||||
{absl::StrCat(ICON_MD_SAVE, " Save Layout"), "Ctrl+Shift+S",
|
||||
[this]() { SaveWorkspaceLayout(); }},
|
||||
{absl::StrCat(ICON_MD_FOLDER_OPEN, " Load Layout"), "Ctrl+Shift+O",
|
||||
[this]() { LoadWorkspaceLayout(); }},
|
||||
{gui::kSeparator, "", nullptr, []() { return true; }},
|
||||
{absl::StrCat(ICON_MD_BOOKMARK, " Layout Presets"), "",
|
||||
[this]() { show_layout_presets_ = true; }},
|
||||
}},
|
||||
|
||||
// Window Management
|
||||
{absl::StrCat(ICON_MD_WINDOW, " Windows"), "", []() {}, []() { return true; },
|
||||
std::vector<gui::MenuItem>{
|
||||
{absl::StrCat(ICON_MD_VISIBILITY, " Show All Windows"), "",
|
||||
[this]() { ShowAllWindows(); }},
|
||||
{absl::StrCat(ICON_MD_VISIBILITY_OFF, " Hide All Windows"), "",
|
||||
[this]() { HideAllWindows(); }},
|
||||
{gui::kSeparator, "", nullptr, []() { return true; }},
|
||||
{absl::StrCat(ICON_MD_FULLSCREEN, " Maximize Current"), "F11",
|
||||
[this]() { MaximizeCurrentWindow(); }},
|
||||
{absl::StrCat(ICON_MD_FULLSCREEN_EXIT, " Restore All"), "",
|
||||
[this]() { RestoreAllWindows(); }},
|
||||
{gui::kSeparator, "", nullptr, []() { return true; }},
|
||||
{absl::StrCat(ICON_MD_CLOSE_FULLSCREEN, " Close All Floating"), "",
|
||||
[this]() { CloseAllFloatingWindows(); }},
|
||||
}},
|
||||
|
||||
// Workspace Presets (Enhanced)
|
||||
{absl::StrCat(ICON_MD_BOOKMARK, " Presets"), "", []() {}, []() { return true; },
|
||||
std::vector<gui::MenuItem>{
|
||||
{absl::StrCat(ICON_MD_ADD, " Save Current as Preset"), "",
|
||||
[this]() { show_save_workspace_preset_ = true; }},
|
||||
{absl::StrCat(ICON_MD_FOLDER_OPEN, " Load Preset"), "",
|
||||
[this]() { show_load_workspace_preset_ = true; }},
|
||||
{gui::kSeparator, "", nullptr, []() { return true; }},
|
||||
{absl::StrCat(ICON_MD_DEVELOPER_MODE, " Developer Layout"), "",
|
||||
[this]() { LoadDeveloperLayout(); }},
|
||||
{absl::StrCat(ICON_MD_DESIGN_SERVICES, " Designer Layout"), "",
|
||||
[this]() { LoadDesignerLayout(); }},
|
||||
{absl::StrCat(ICON_MD_GAMEPAD, " Modder Layout"), "",
|
||||
[this]() { LoadModderLayout(); }},
|
||||
}},
|
||||
}},
|
||||
{"Debug",
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{
|
||||
// Testing and Validation (only when tests are enabled)
|
||||
{absl::StrCat(ICON_MD_SCIENCE, " Test Dashboard"), "Ctrl+T",
|
||||
[&]() { show_test_dashboard_ = true; }},
|
||||
{absl::StrCat(ICON_MD_PLAY_ARROW, " Run All Tests"), "",
|
||||
[&]() { [[maybe_unused]] auto status = test::TestManager::Get().RunAllTests(); }},
|
||||
{absl::StrCat(ICON_MD_INTEGRATION_INSTRUCTIONS, " Run Unit Tests"), "",
|
||||
[&]() { [[maybe_unused]] auto status = test::TestManager::Get().RunTestsByCategory(test::TestCategory::kUnit); }},
|
||||
{absl::StrCat(ICON_MD_MEMORY, " Run Integration Tests"), "",
|
||||
[&]() { [[maybe_unused]] auto status = test::TestManager::Get().RunTestsByCategory(test::TestCategory::kIntegration); }},
|
||||
{absl::StrCat(ICON_MD_CLEAR_ALL, " Clear Test Results"), "",
|
||||
[&]() { test::TestManager::Get().ClearResults(); }},
|
||||
{gui::kSeparator, "", nullptr, []() { return true; }},
|
||||
|
||||
// ROM and ASM Management
|
||||
{absl::StrCat(ICON_MD_STORAGE, " ROM Analysis"), "", []() {}, []() { return true; },
|
||||
std::vector<gui::MenuItem>{
|
||||
{absl::StrCat(ICON_MD_INFO, " ROM Information"), "",
|
||||
[&]() { popup_manager_->Show("ROM Information"); },
|
||||
[&]() { return current_rom_ && current_rom_->is_loaded(); }},
|
||||
{absl::StrCat(ICON_MD_ANALYTICS, " Data Integrity Check"), "",
|
||||
[&]() {
|
||||
if (current_rom_) {
|
||||
[[maybe_unused]] auto status = test::TestManager::Get().TestRomDataIntegrity(current_rom_);
|
||||
}
|
||||
},
|
||||
[&]() { return current_rom_ && current_rom_->is_loaded(); }},
|
||||
{absl::StrCat(ICON_MD_SAVE_ALT, " Test Save/Load"), "",
|
||||
[&]() {
|
||||
if (current_rom_) {
|
||||
[[maybe_unused]] auto status = test::TestManager::Get().TestRomSaveLoad(current_rom_);
|
||||
}
|
||||
},
|
||||
[&]() { return current_rom_ && current_rom_->is_loaded(); }},
|
||||
}},
|
||||
|
||||
{absl::StrCat(ICON_MD_CODE, " ZSCustomOverworld"), "", []() {}, []() { return true; },
|
||||
std::vector<gui::MenuItem>{
|
||||
{absl::StrCat(ICON_MD_INFO, " Check ROM Version"), "",
|
||||
[&]() {
|
||||
if (current_rom_) {
|
||||
uint8_t version = (*current_rom_)[zelda3::OverworldCustomASMHasBeenApplied];
|
||||
std::string version_str = (version == 0xFF) ? "Vanilla" : absl::StrFormat("v%d", version);
|
||||
toast_manager_.Show(absl::StrFormat("ROM: %s | ZSCustomOverworld: %s",
|
||||
current_rom_->title().c_str(), version_str.c_str()),
|
||||
editor::ToastType::kInfo, 5.0f);
|
||||
}
|
||||
},
|
||||
[&]() { return current_rom_ && current_rom_->is_loaded(); }},
|
||||
{absl::StrCat(ICON_MD_UPGRADE, " Upgrade ROM"), "",
|
||||
[&]() {
|
||||
// This would trigger the upgrade dialog from overworld editor
|
||||
if (current_rom_) {
|
||||
toast_manager_.Show("Use Overworld Editor to upgrade ROM version",
|
||||
editor::ToastType::kInfo, 4.0f);
|
||||
}
|
||||
},
|
||||
[&]() { return current_rom_ && current_rom_->is_loaded(); }},
|
||||
{absl::StrCat(ICON_MD_SETTINGS, " Feature Flags"), "",
|
||||
[&]() {
|
||||
// Toggle ZSCustomOverworld loading feature
|
||||
auto& flags = core::FeatureFlags::get();
|
||||
flags.overworld.kLoadCustomOverworld = !flags.overworld.kLoadCustomOverworld;
|
||||
toast_manager_.Show(absl::StrFormat("Custom Overworld Loading: %s",
|
||||
flags.overworld.kLoadCustomOverworld ? "Enabled" : "Disabled"),
|
||||
editor::ToastType::kInfo);
|
||||
}},
|
||||
}},
|
||||
|
||||
{absl::StrCat(ICON_MD_BUILD, " Asar Integration"), "", []() {}, []() { return true; },
|
||||
std::vector<gui::MenuItem>{
|
||||
{absl::StrCat(ICON_MD_INFO, " Asar Status"), "",
|
||||
[&]() { popup_manager_->Show("Asar Integration"); }},
|
||||
{absl::StrCat(ICON_MD_CODE, " Apply ASM Patch"), "",
|
||||
[&]() {
|
||||
if (current_rom_) {
|
||||
auto& flags = core::FeatureFlags::get();
|
||||
flags.overworld.kApplyZSCustomOverworldASM = !flags.overworld.kApplyZSCustomOverworldASM;
|
||||
toast_manager_.Show(absl::StrFormat("ZSCustomOverworld ASM Application: %s",
|
||||
flags.overworld.kApplyZSCustomOverworldASM ? "Enabled" : "Disabled"),
|
||||
editor::ToastType::kInfo);
|
||||
}
|
||||
},
|
||||
[&]() { return current_rom_ && current_rom_->is_loaded(); }},
|
||||
{absl::StrCat(ICON_MD_FOLDER_OPEN, " Load ASM File"), "",
|
||||
[&]() {
|
||||
// Show available ASM files or file dialog
|
||||
toast_manager_.Show("ASM file loading not yet implemented",
|
||||
editor::ToastType::kWarning);
|
||||
}},
|
||||
}},
|
||||
|
||||
{gui::kSeparator, "", nullptr, []() { return true; }},
|
||||
|
||||
// Development Tools
|
||||
{absl::StrCat(ICON_MD_MEMORY, " Memory Editor"), "",
|
||||
[&]() { show_memory_editor_ = true; }},
|
||||
{absl::StrCat(ICON_MD_CODE, " Assembly Editor"), "",
|
||||
[&]() { show_asm_editor_ = true; }},
|
||||
{absl::StrCat(ICON_MD_SETTINGS, " Feature Flags"), "",
|
||||
[&]() { popup_manager_->Show("Feature Flags"); }},
|
||||
|
||||
{gui::kSeparator, "", nullptr, []() { return true; }},
|
||||
|
||||
// Agent Proposals
|
||||
{absl::StrCat(ICON_MD_PREVIEW, " Agent Proposals"), "",
|
||||
[&]() { proposal_drawer_.Toggle(); }},
|
||||
#ifdef YAZE_WITH_GRPC
|
||||
{absl::StrCat(ICON_MD_CHAT, " Agent Chat"), "",
|
||||
[this]() {
|
||||
agent_editor_.ToggleChat();
|
||||
},
|
||||
[this]() { return agent_editor_.IsChatActive(); }},
|
||||
#endif
|
||||
|
||||
{gui::kSeparator, "", nullptr, []() { return true; }},
|
||||
|
||||
{absl::StrCat(ICON_MD_PALETTE, " Graphics Debugging"), "", []() {}, []() { return true; },
|
||||
std::vector<gui::MenuItem>{
|
||||
{absl::StrCat(ICON_MD_REFRESH, " Clear Graphics Cache"), "",
|
||||
[&]() {
|
||||
// Clear and reinitialize graphics cache
|
||||
if (current_rom_ && current_rom_->is_loaded()) {
|
||||
toast_manager_.Show("Graphics cache cleared - reload editors to refresh",
|
||||
editor::ToastType::kInfo, 4.0f);
|
||||
}
|
||||
},
|
||||
[&]() { return current_rom_ && current_rom_->is_loaded(); }},
|
||||
{absl::StrCat(ICON_MD_MEMORY, " Arena Statistics"), "",
|
||||
[&]() {
|
||||
auto& arena = gfx::Arena::Get();
|
||||
toast_manager_.Show(absl::StrFormat("Arena: %zu surfaces, %zu textures",
|
||||
arena.GetSurfaceCount(), arena.GetTextureCount()),
|
||||
editor::ToastType::kInfo, 4.0f);
|
||||
}},
|
||||
}},
|
||||
|
||||
// Performance Monitoring
|
||||
{absl::StrCat(ICON_MD_SPEED, " Performance Dashboard"), "Ctrl+Shift+P",
|
||||
[&]() { show_performance_dashboard_ = true; }},
|
||||
|
||||
{gui::kSeparator, "", nullptr, []() { return true; }},
|
||||
|
||||
// Development Helpers
|
||||
{absl::StrCat(ICON_MD_HELP, " ImGui Demo"), "",
|
||||
[&]() { show_imgui_demo_ = true; }},
|
||||
{absl::StrCat(ICON_MD_ANALYTICS, " ImGui Metrics"), "",
|
||||
[&]() { show_imgui_metrics_ = true; }},
|
||||
}},
|
||||
{"Help",
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{
|
||||
{absl::StrCat(ICON_MD_HELP, " Getting Started"), "",
|
||||
[&]() { popup_manager_->Show("Getting Started"); }},
|
||||
{absl::StrCat(ICON_MD_WORK_OUTLINE, " Workspace Help"), "",
|
||||
[&]() { popup_manager_->Show("Workspace Help"); }},
|
||||
{absl::StrCat(ICON_MD_INTEGRATION_INSTRUCTIONS, " Asar Integration Guide"), "",
|
||||
[&]() { popup_manager_->Show("Asar Integration"); }},
|
||||
{absl::StrCat(ICON_MD_BUILD, " Build Instructions"), "",
|
||||
[&]() { popup_manager_->Show("Build Instructions"); }},
|
||||
{gui::kSeparator, "", nullptr, []() { return true; }},
|
||||
{absl::StrCat(ICON_MD_FILE_OPEN, " How to open a ROM"), "",
|
||||
[&]() { popup_manager_->Show("Open a ROM"); }},
|
||||
{absl::StrCat(ICON_MD_LIST, " Supported Features"), "",
|
||||
[&]() { popup_manager_->Show("Supported Features"); }},
|
||||
{absl::StrCat(ICON_MD_FOLDER_OPEN, " How to manage a project"), "",
|
||||
[&]() { popup_manager_->Show("Manage Project"); }},
|
||||
{gui::kSeparator, "", nullptr, []() { return true; }},
|
||||
{absl::StrCat(ICON_MD_TERMINAL, " CLI Tool Usage"), "",
|
||||
[&]() { popup_manager_->Show("CLI Usage"); }},
|
||||
{absl::StrCat(ICON_MD_BUG_REPORT, " Troubleshooting"), "",
|
||||
[&]() { popup_manager_->Show("Troubleshooting"); }},
|
||||
{absl::StrCat(ICON_MD_CODE, " Contributing"), "",
|
||||
[&]() { popup_manager_->Show("Contributing"); }},
|
||||
{gui::kSeparator, "", nullptr, []() { return true; }},
|
||||
{absl::StrCat(ICON_MD_ANNOUNCEMENT, " What's New in v0.3"), "",
|
||||
[&]() { popup_manager_->Show("Whats New v03"); }},
|
||||
{absl::StrCat(ICON_MD_INFO, " About"), "F1",
|
||||
[&]() { popup_manager_->Show("About"); }},
|
||||
}}};
|
||||
}
|
||||
|
||||
absl::Status EditorManager::Update() {
|
||||
popup_manager_->DrawPopups();
|
||||
ExecuteShortcuts(context_.shortcut_manager);
|
||||
toast_manager_.Draw();
|
||||
|
||||
// Draw editor selection dialog
|
||||
if (show_editor_selection_) {
|
||||
editor_selection_dialog_.Show(&show_editor_selection_);
|
||||
}
|
||||
|
||||
#ifdef YAZE_WITH_GRPC
|
||||
// Draw agent editor (includes chat widget and collaboration UI)
|
||||
agent_editor_.Draw();
|
||||
#endif
|
||||
|
||||
// Draw background grid effects for the entire viewport
|
||||
if (ImGui::GetCurrentContext()) {
|
||||
@@ -1126,8 +753,8 @@ absl::Status EditorManager::DrawRomSelector() {
|
||||
void EditorManager::BuildModernMenu() {
|
||||
menu_builder_.Clear();
|
||||
|
||||
// File Menu
|
||||
menu_builder_.BeginMenu("File", ICON_MD_FOLDER)
|
||||
// File Menu (no icon to avoid cutoff)
|
||||
menu_builder_.BeginMenu("File")
|
||||
.Item("Open", ICON_MD_FILE_OPEN,
|
||||
[this]() { status_ = LoadRom(); }, "Ctrl+O")
|
||||
.Item("Save", ICON_MD_SAVE,
|
||||
@@ -1148,7 +775,7 @@ void EditorManager::BuildModernMenu() {
|
||||
.EndMenu();
|
||||
|
||||
// Edit Menu
|
||||
menu_builder_.BeginMenu("Edit", ICON_MD_EDIT)
|
||||
menu_builder_.BeginMenu("Edit")
|
||||
.Item("Undo", ICON_MD_UNDO,
|
||||
[this]() { if (current_editor_) status_ = current_editor_->Undo(); }, "Ctrl+Z")
|
||||
.Item("Redo", ICON_MD_REDO,
|
||||
@@ -1169,7 +796,7 @@ void EditorManager::BuildModernMenu() {
|
||||
.EndMenu();
|
||||
|
||||
// View Menu
|
||||
menu_builder_.BeginMenu("View", ICON_MD_VISIBILITY)
|
||||
menu_builder_.BeginMenu("View")
|
||||
.BeginSubMenu("Editors", ICON_MD_APPS)
|
||||
.Item("Show Editor Selection", ICON_MD_DASHBOARD,
|
||||
[this]() { show_editor_selection_ = true; }, "Ctrl+E")
|
||||
@@ -1192,6 +819,11 @@ void EditorManager::BuildModernMenu() {
|
||||
.Separator()
|
||||
.Item("Welcome Screen", ICON_MD_HOME,
|
||||
[this]() { show_welcome_screen_ = true; })
|
||||
.Item("Emulator", ICON_MD_GAMEPAD,
|
||||
[this]() { show_emulator_ = true; },
|
||||
nullptr,
|
||||
nullptr,
|
||||
[this]() { return show_emulator_; })
|
||||
.Item("Command Palette", ICON_MD_TERMINAL,
|
||||
[this]() { show_command_palette_ = true; }, "Ctrl+Shift+P")
|
||||
#ifdef YAZE_WITH_GRPC
|
||||
@@ -1204,7 +836,7 @@ void EditorManager::BuildModernMenu() {
|
||||
.EndMenu();
|
||||
|
||||
// Workspace Menu
|
||||
menu_builder_.BeginMenu("Workspace", ICON_MD_WORKSPACES)
|
||||
menu_builder_.BeginMenu("Workspace")
|
||||
.BeginSubMenu("Sessions", ICON_MD_TAB)
|
||||
.Item("New Session", ICON_MD_ADD,
|
||||
[this]() { CreateNewSession(); }, "Ctrl+Shift+N")
|
||||
@@ -1218,6 +850,34 @@ void EditorManager::BuildModernMenu() {
|
||||
.Item("Session Switcher", ICON_MD_SWITCH_ACCOUNT,
|
||||
[this]() { show_session_switcher_ = true; }, "Ctrl+Tab",
|
||||
[this]() { return sessions_.size() > 1; })
|
||||
.Item("Session Manager", ICON_MD_VIEW_LIST,
|
||||
[this]() { show_session_manager_ = true; })
|
||||
.EndMenu()
|
||||
.Separator()
|
||||
.BeginSubMenu("Layout", ICON_MD_DASHBOARD)
|
||||
.Item("Save Layout", ICON_MD_SAVE,
|
||||
[this]() { SaveWorkspaceLayout(); }, "Ctrl+Shift+S")
|
||||
.Item("Load Layout", ICON_MD_FOLDER_OPEN,
|
||||
[this]() { LoadWorkspaceLayout(); }, "Ctrl+Shift+O")
|
||||
.Item("Reset Layout", ICON_MD_RESET_TV,
|
||||
[this]() { ResetWorkspaceLayout(); })
|
||||
.Separator()
|
||||
.Item("Layout Presets", ICON_MD_BOOKMARK,
|
||||
[this]() { show_layout_presets_ = true; })
|
||||
.EndMenu()
|
||||
.BeginSubMenu("Windows", ICON_MD_WINDOW)
|
||||
.Item("Show All", ICON_MD_VISIBILITY,
|
||||
[this]() { ShowAllWindows(); })
|
||||
.Item("Hide All", ICON_MD_VISIBILITY_OFF,
|
||||
[this]() { HideAllWindows(); })
|
||||
.Separator()
|
||||
.Item("Maximize Current", ICON_MD_FULLSCREEN,
|
||||
[this]() { MaximizeCurrentWindow(); }, "F11")
|
||||
.Item("Restore All", ICON_MD_FULLSCREEN_EXIT,
|
||||
[this]() { RestoreAllWindows(); })
|
||||
.Separator()
|
||||
.Item("Close All Floating", ICON_MD_CLOSE_FULLSCREEN,
|
||||
[this]() { CloseAllFloatingWindows(); })
|
||||
.EndMenu()
|
||||
.Separator()
|
||||
.Item("Performance Dashboard", ICON_MD_SPEED,
|
||||
@@ -1226,7 +886,7 @@ void EditorManager::BuildModernMenu() {
|
||||
|
||||
#ifdef YAZE_WITH_GRPC
|
||||
// AI Agent Menu
|
||||
menu_builder_.BeginMenu("AI Agent", ICON_MD_SMART_TOY)
|
||||
menu_builder_.BeginMenu("Agent")
|
||||
.Item("Open Agent Chat", ICON_MD_CHAT,
|
||||
[this]() { agent_editor_.SetChatActive(true); }, "Ctrl+Shift+A")
|
||||
.Separator()
|
||||
@@ -1254,17 +914,17 @@ void EditorManager::BuildModernMenu() {
|
||||
.EndMenu();
|
||||
|
||||
// Collaboration Menu
|
||||
menu_builder_.BeginMenu("Collaboration", ICON_MD_PEOPLE)
|
||||
menu_builder_.BeginMenu("Network")
|
||||
.BeginSubMenu("Session", ICON_MD_MEETING_ROOM)
|
||||
.Item("Host Session", ICON_MD_ADD_CIRCLE,
|
||||
[this]() {
|
||||
auto result = agent_editor_.HostSession("New Session");
|
||||
if (result.ok()) {
|
||||
toast_manager_.AddToast(ToastType::Success,
|
||||
"Hosted session: " + result->session_name);
|
||||
toast_manager_.Show("Hosted session: " + result->session_name,
|
||||
ToastType::kSuccess);
|
||||
} else {
|
||||
toast_manager_.AddToast(ToastType::Error,
|
||||
"Failed to host session: " + std::string(result.status().message()));
|
||||
toast_manager_.Show("Failed to host session: " + std::string(result.status().message()),
|
||||
ToastType::kError);
|
||||
}
|
||||
})
|
||||
.Item("Join Session", ICON_MD_LOGIN,
|
||||
@@ -1274,7 +934,7 @@ void EditorManager::BuildModernMenu() {
|
||||
[this]() {
|
||||
status_ = agent_editor_.LeaveSession();
|
||||
if (status_.ok()) {
|
||||
toast_manager_.AddToast(ToastType::Info, "Left collaboration session");
|
||||
toast_manager_.Show("Left collaboration session", ToastType::kInfo);
|
||||
}
|
||||
},
|
||||
nullptr,
|
||||
@@ -1283,21 +943,21 @@ void EditorManager::BuildModernMenu() {
|
||||
[this]() {
|
||||
auto result = agent_editor_.RefreshSession();
|
||||
if (result.ok()) {
|
||||
toast_manager_.AddToast(ToastType::Success,
|
||||
"Session refreshed: " + std::to_string(result->participants.size()) + " participants");
|
||||
toast_manager_.Show("Session refreshed: " + std::to_string(result->participants.size()) + " participants",
|
||||
ToastType::kSuccess);
|
||||
}
|
||||
},
|
||||
nullptr,
|
||||
[this]() { return agent_editor_.IsInSession(); })
|
||||
.EndMenu()
|
||||
.Separator()
|
||||
.BeginSubMenu("Network", ICON_MD_CLOUD)
|
||||
.BeginSubMenu("Server", ICON_MD_CLOUD)
|
||||
.Item("Connect to Server", ICON_MD_CLOUD_UPLOAD,
|
||||
[this]() { popup_manager_->Show("Connect to Server"); })
|
||||
.Item("Disconnect", ICON_MD_CLOUD_OFF,
|
||||
[this]() {
|
||||
agent_editor_.DisconnectFromServer();
|
||||
toast_manager_.AddToast(ToastType::Info, "Disconnected from server");
|
||||
toast_manager_.Show("Disconnected from server", ToastType::kInfo);
|
||||
},
|
||||
nullptr,
|
||||
[this]() { return agent_editor_.IsConnectedToServer(); })
|
||||
@@ -1317,14 +977,25 @@ void EditorManager::BuildModernMenu() {
|
||||
#endif
|
||||
|
||||
// Debug Menu
|
||||
menu_builder_.BeginMenu("Debug", ICON_MD_BUG_REPORT)
|
||||
menu_builder_.BeginMenu("Debug")
|
||||
#ifdef YAZE_ENABLE_TESTING
|
||||
.Item("Test Dashboard", ICON_MD_SCIENCE,
|
||||
[this]() { show_test_dashboard_ = true; }, "Ctrl+T")
|
||||
.Separator()
|
||||
#endif
|
||||
.Item("Memory Editor", ICON_MD_MEMORY,
|
||||
[this]() { show_memory_editor_ = true; })
|
||||
.Item("Assembly Editor", ICON_MD_CODE,
|
||||
[this]() { show_asm_editor_ = true; })
|
||||
.Separator()
|
||||
.Item("ImGui Demo", ICON_MD_HELP,
|
||||
[this]() { show_imgui_demo_ = true; },
|
||||
nullptr, nullptr,
|
||||
[this]() { return show_imgui_demo_; })
|
||||
.Item("ImGui Metrics", ICON_MD_ANALYTICS,
|
||||
[this]() { show_imgui_metrics_ = true; },
|
||||
nullptr, nullptr,
|
||||
[this]() { return show_imgui_metrics_; })
|
||||
#ifdef YAZE_WITH_GRPC
|
||||
.Separator()
|
||||
.Item("Agent Chat", ICON_MD_CHAT,
|
||||
@@ -1335,7 +1006,7 @@ void EditorManager::BuildModernMenu() {
|
||||
.EndMenu();
|
||||
|
||||
// Help Menu
|
||||
menu_builder_.BeginMenu("Help", ICON_MD_HELP)
|
||||
menu_builder_.BeginMenu("Help")
|
||||
.Item("Getting Started", ICON_MD_PLAY_ARROW,
|
||||
[this]() { popup_manager_->Show("Getting Started"); })
|
||||
.Item("About", ICON_MD_INFO,
|
||||
@@ -1356,43 +1027,56 @@ void EditorManager::DrawMenuBar() {
|
||||
status_ = DrawRomSelector();
|
||||
|
||||
// Calculate proper right-side positioning
|
||||
// With the wider menu (8 top-level items), we need more compact right-side layout
|
||||
std::string version_text = absl::StrFormat("v%s", version_.c_str());
|
||||
float version_width = CalcTextSize(version_text.c_str()).x;
|
||||
float settings_width = CalcTextSize(ICON_MD_DISPLAY_SETTINGS).x + 16;
|
||||
float total_right_width =
|
||||
version_width + settings_width + 40; // Extra padding
|
||||
float total_right_width = version_width + settings_width + 30; // Increased padding for visibility
|
||||
|
||||
// Position for ROM status and sessions
|
||||
float session_rom_area_width = 250.0f; // Reduced width
|
||||
// Allocate space for ROM status and sessions
|
||||
float session_rom_area_width = 220.0f; // Increased slightly for better spacing
|
||||
SameLine(GetWindowWidth() - total_right_width - session_rom_area_width);
|
||||
|
||||
// Multi-session indicator
|
||||
if (sessions_.size() > 1) {
|
||||
if (SmallButton(absl::StrFormat("%s %zu", ICON_MD_TAB, sessions_.size())
|
||||
if (SmallButton(absl::StrFormat("%s%zu", ICON_MD_TAB, sessions_.size())
|
||||
.c_str())) {
|
||||
show_session_switcher_ = true;
|
||||
}
|
||||
if (IsItemHovered()) {
|
||||
SetTooltip("Sessions: %zu active\nClick to switch between sessions",
|
||||
sessions_.size());
|
||||
SetTooltip("Sessions: %zu active\nClick to switch", sessions_.size());
|
||||
}
|
||||
SameLine();
|
||||
}
|
||||
|
||||
// Editor selection quick button (when ROM loaded)
|
||||
if (current_rom_ && current_rom_->is_loaded()) {
|
||||
if (SmallButton(ICON_MD_APPS)) {
|
||||
show_editor_selection_ = true;
|
||||
}
|
||||
if (IsItemHovered()) {
|
||||
SetTooltip("Open Editor Selection (Ctrl+E)");
|
||||
}
|
||||
SameLine();
|
||||
ImGui::Separator();
|
||||
SameLine();
|
||||
}
|
||||
|
||||
// Enhanced ROM status with metadata popup
|
||||
// Compact ROM status with metadata popup
|
||||
if (current_rom_ && current_rom_->is_loaded()) {
|
||||
// Truncate long ROM titles for compact display
|
||||
std::string rom_display = current_rom_->title();
|
||||
if (rom_display.length() > 15) {
|
||||
rom_display = rom_display.substr(0, 12) + "...";
|
||||
}
|
||||
|
||||
ImVec4 status_color =
|
||||
current_rom_->dirty() ? ImVec4(1.0f, 0.5f, 0.0f, 1.0f)
|
||||
: // Orange for modified
|
||||
ImVec4(0.0f, 0.8f, 0.0f, 1.0f); // Green for clean
|
||||
|
||||
// Make ROM status clickable for detailed popup
|
||||
if (SmallButton(absl::StrFormat("%s %s%s", ICON_MD_STORAGE,
|
||||
rom_display.c_str(),
|
||||
// Compact ROM status button
|
||||
if (SmallButton(absl::StrFormat("%s%s", rom_display.c_str(),
|
||||
current_rom_->dirty() ? "*" : "")
|
||||
.c_str())) {
|
||||
ImGui::OpenPopup("ROM Details");
|
||||
@@ -1492,8 +1176,7 @@ void EditorManager::DrawMenuBar() {
|
||||
|
||||
SameLine();
|
||||
} else {
|
||||
TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "%s No ROM",
|
||||
ICON_MD_HELP_OUTLINE);
|
||||
TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "No ROM");
|
||||
SameLine();
|
||||
}
|
||||
|
||||
@@ -2158,6 +1841,10 @@ absl::Status EditorManager::LoadRom() {
|
||||
|
||||
// Hide welcome screen when ROM is successfully loaded - don't reset manual close state
|
||||
show_welcome_screen_ = false;
|
||||
|
||||
// Optionally show editor selection dialog after ROM loads
|
||||
// (Can be disabled via settings if users prefer to manually select editors)
|
||||
// show_editor_selection_ = true; // Uncomment to auto-show after ROM load
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
@@ -3326,7 +3013,7 @@ void EditorManager::DrawWelcomeScreen() {
|
||||
welcome_screen_.RefreshRecentProjects();
|
||||
bool was_open = show_welcome_screen_;
|
||||
bool action_taken = welcome_screen_.Show(&show_welcome_screen_);
|
||||
|
||||
|
||||
// Check if the welcome screen was manually closed via the close button
|
||||
if (was_open && !show_welcome_screen_) {
|
||||
welcome_screen_manually_closed_ = true;
|
||||
|
||||
@@ -55,7 +55,13 @@ EditorSelectionDialog::EditorSelectionDialog() {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -66,7 +72,8 @@ bool EditorSelectionDialog::Show(bool* p_open) {
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
ImGui::SetNextWindowSize(ImVec2(900, 600), ImGuiCond_Appearing);
|
||||
|
||||
if (ImGui::Begin("Editor Selection", p_open ? p_open : &is_open_,
|
||||
bool* window_open = p_open ? p_open : &is_open_;
|
||||
if (ImGui::Begin("Editor Selection", window_open,
|
||||
ImGuiWindowFlags_NoCollapse)) {
|
||||
DrawWelcomeHeader();
|
||||
|
||||
@@ -92,24 +99,32 @@ bool EditorSelectionDialog::Show(bool* p_open) {
|
||||
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));
|
||||
|
||||
// TODO: Fix this
|
||||
// if (selected_editor_ != EditorType::kNone) {
|
||||
// editor_selected = true;
|
||||
// MarkRecentlyUsed(selected_editor_);
|
||||
// if (selection_callback_) {
|
||||
// selection_callback_(selected_editor_);
|
||||
// }
|
||||
// }
|
||||
|
||||
// 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;
|
||||
|
||||
@@ -35,14 +35,21 @@ MenuBuilder& MenuBuilder::EndMenu() {
|
||||
if (!current_menu_) return *this;
|
||||
|
||||
// Check if we're ending a submenu or top-level menu
|
||||
// We need to track nesting depth to handle nested submenus correctly
|
||||
bool is_submenu = false;
|
||||
int depth = 0;
|
||||
|
||||
for (auto it = current_menu_->items.rbegin();
|
||||
it != current_menu_->items.rend(); ++it) {
|
||||
if (it->type == MenuItem::Type::kSubMenuBegin) {
|
||||
is_submenu = true;
|
||||
break;
|
||||
} else if (it->type == MenuItem::Type::kSubMenuEnd) {
|
||||
break;
|
||||
if (it->type == MenuItem::Type::kSubMenuEnd) {
|
||||
depth++; // Found an end, so we need to skip its matching begin
|
||||
} else if (it->type == MenuItem::Type::kSubMenuBegin) {
|
||||
if (depth == 0) {
|
||||
// Found an unmatched begin - this is what we're closing
|
||||
is_submenu = true;
|
||||
break;
|
||||
}
|
||||
depth--; // This begin matches a previous end
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,11 +114,12 @@ MenuBuilder& MenuBuilder::DisabledItem(const char* label, const char* icon) {
|
||||
|
||||
void MenuBuilder::Draw() {
|
||||
for (const auto& menu : menus_) {
|
||||
std::string menu_label = menu.icon.empty()
|
||||
? menu.label
|
||||
: absl::StrCat(menu.icon, " ", menu.label);
|
||||
// Don't add icons to top-level menus as they get cut off
|
||||
std::string menu_label = menu.label;
|
||||
|
||||
if (ImGui::BeginMenu(menu_label.c_str())) {
|
||||
submenu_stack_.clear(); // Reset submenu stack for each top-level menu
|
||||
skip_depth_ = 0; // Reset skip depth
|
||||
for (const auto& item : menu.items) {
|
||||
DrawMenuItem(item);
|
||||
}
|
||||
@@ -123,28 +131,16 @@ void MenuBuilder::Draw() {
|
||||
void MenuBuilder::DrawMenuItem(const MenuItem& item) {
|
||||
switch (item.type) {
|
||||
case MenuItem::Type::kSeparator:
|
||||
ImGui::Separator();
|
||||
if (skip_depth_ == 0) {
|
||||
ImGui::Separator();
|
||||
}
|
||||
break;
|
||||
|
||||
case MenuItem::Type::kDisabled: {
|
||||
std::string label = item.icon.empty()
|
||||
? item.label
|
||||
: absl::StrCat(item.icon, " ", item.label);
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::MenuItem(label.c_str(), nullptr, false, false);
|
||||
ImGui::EndDisabled();
|
||||
break;
|
||||
}
|
||||
|
||||
case MenuItem::Type::kSubMenuBegin: {
|
||||
std::string label = item.icon.empty()
|
||||
? item.label
|
||||
: absl::StrCat(item.icon, " ", item.label);
|
||||
|
||||
bool enabled = !item.enabled || item.enabled();
|
||||
if (enabled && ImGui::BeginMenu(label.c_str())) {
|
||||
// Submenu items will be drawn in subsequent calls
|
||||
} else if (!enabled) {
|
||||
if (skip_depth_ == 0) {
|
||||
std::string label = item.icon.empty()
|
||||
? item.label
|
||||
: absl::StrCat(item.icon, " ", item.label);
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::MenuItem(label.c_str(), nullptr, false, false);
|
||||
ImGui::EndDisabled();
|
||||
@@ -152,11 +148,60 @@ void MenuBuilder::DrawMenuItem(const MenuItem& item) {
|
||||
break;
|
||||
}
|
||||
|
||||
case MenuItem::Type::kSubMenuBegin: {
|
||||
// If we're already skipping, increment skip depth and continue
|
||||
if (skip_depth_ > 0) {
|
||||
skip_depth_++;
|
||||
submenu_stack_.push_back(false);
|
||||
break;
|
||||
}
|
||||
|
||||
std::string label = item.icon.empty()
|
||||
? item.label
|
||||
: absl::StrCat(item.icon, " ", item.label);
|
||||
|
||||
bool enabled = !item.enabled || item.enabled();
|
||||
bool opened = false;
|
||||
|
||||
if (!enabled) {
|
||||
// Disabled submenu - show as disabled item but don't open
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::MenuItem(label.c_str(), nullptr, false, false);
|
||||
ImGui::EndDisabled();
|
||||
submenu_stack_.push_back(false);
|
||||
skip_depth_++; // Skip contents of disabled submenu
|
||||
} else {
|
||||
// BeginMenu returns true if submenu is currently open/visible
|
||||
opened = ImGui::BeginMenu(label.c_str());
|
||||
submenu_stack_.push_back(opened);
|
||||
if (!opened) {
|
||||
skip_depth_++; // Skip contents of closed submenu
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MenuItem::Type::kSubMenuEnd:
|
||||
ImGui::EndMenu();
|
||||
// Decrement skip depth if we were skipping
|
||||
if (skip_depth_ > 0) {
|
||||
skip_depth_--;
|
||||
}
|
||||
|
||||
// Pop the stack and call EndMenu only if submenu was opened
|
||||
if (!submenu_stack_.empty()) {
|
||||
bool was_opened = submenu_stack_.back();
|
||||
submenu_stack_.pop_back();
|
||||
if (was_opened && skip_depth_ == 0) {
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MenuItem::Type::kItem: {
|
||||
if (skip_depth_ > 0) {
|
||||
break; // Skip items in closed submenus
|
||||
}
|
||||
|
||||
std::string label = item.icon.empty()
|
||||
? item.label
|
||||
: absl::StrCat(item.icon, " ", item.label);
|
||||
|
||||
@@ -110,6 +110,10 @@ class MenuBuilder {
|
||||
std::vector<Menu> menus_;
|
||||
Menu* current_menu_ = nullptr;
|
||||
|
||||
// Track which submenus are actually open during drawing
|
||||
mutable std::vector<bool> submenu_stack_;
|
||||
mutable int skip_depth_ = 0; // Track nesting depth when skipping closed submenus
|
||||
|
||||
void DrawMenuItem(const MenuItem& item);
|
||||
};
|
||||
|
||||
|
||||
@@ -91,35 +91,39 @@ std::string GetRelativeTimeString(const std::filesystem::file_time_type& ftime)
|
||||
}
|
||||
}
|
||||
|
||||
// Draw a pulsing triforce in the background
|
||||
// Draw a pixelated triforce in the background (ALTTP style)
|
||||
void DrawTriforceBackground(ImDrawList* draw_list, ImVec2 pos, float size, float alpha, float glow) {
|
||||
float height = size * 0.866f; // sqrt(3)/2 for equilateral triangle
|
||||
// Make it pixelated - round size to nearest 4 pixels
|
||||
size = std::round(size / 4.0f) * 4.0f;
|
||||
|
||||
// Calculate triangle points
|
||||
// Calculate triangle points with pixel-perfect positioning
|
||||
auto triangle = [&](ImVec2 center, float s, ImU32 color) {
|
||||
ImVec2 p1(center.x, center.y - height * s / size);
|
||||
ImVec2 p2(center.x - s / 2, center.y + height * s / (2 * size));
|
||||
ImVec2 p3(center.x + s / 2, center.y + height * s / (2 * size));
|
||||
// Round to pixel boundaries for crisp edges
|
||||
float half_s = s / 2.0f;
|
||||
float tri_h = s * 0.866f; // Height of equilateral triangle
|
||||
|
||||
// Fixed: Proper equilateral triangle with apex at top
|
||||
ImVec2 p1(std::round(center.x), std::round(center.y - tri_h / 2.0f)); // Top apex
|
||||
ImVec2 p2(std::round(center.x - half_s), std::round(center.y + tri_h / 2.0f)); // Bottom left
|
||||
ImVec2 p3(std::round(center.x + half_s), std::round(center.y + tri_h / 2.0f)); // Bottom right
|
||||
|
||||
draw_list->AddTriangleFilled(p1, p2, p3, color);
|
||||
|
||||
// Glow effect
|
||||
if (glow > 0.0f) {
|
||||
ImU32 glow_color = ImGui::GetColorU32(ImVec4(1.0f, 0.843f, 0.0f, glow * 0.3f));
|
||||
draw_list->AddTriangle(p1, p2, p3, glow_color, 2.0f + glow * 3.0f);
|
||||
}
|
||||
};
|
||||
|
||||
ImU32 gold = ImGui::GetColorU32(ImVec4(1.0f, 0.843f, 0.0f, alpha));
|
||||
|
||||
// Top triangle
|
||||
triangle(ImVec2(pos.x, pos.y), size, gold);
|
||||
// Proper triforce layout with three triangles
|
||||
float small_size = size / 2.0f;
|
||||
float small_height = small_size * 0.866f;
|
||||
|
||||
// Top triangle (centered above)
|
||||
triangle(ImVec2(pos.x, pos.y), small_size, gold);
|
||||
|
||||
// Bottom left triangle
|
||||
triangle(ImVec2(pos.x - size / 4, pos.y + height / 2), size / 2, gold);
|
||||
triangle(ImVec2(pos.x - small_size / 2.0f, pos.y + small_height), small_size, gold);
|
||||
|
||||
// Bottom right triangle
|
||||
triangle(ImVec2(pos.x + size / 4, pos.y + height / 2), size / 2, gold);
|
||||
triangle(ImVec2(pos.x + small_size / 2.0f, pos.y + small_height), small_size, gold);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -138,17 +142,26 @@ bool WelcomeScreen::Show(bool* p_open) {
|
||||
kSpiritOrange = GetThemedColor("spirit_orange", kSpiritOrangeFallback);
|
||||
|
||||
UpdateAnimations();
|
||||
|
||||
// Get mouse position for interactive triforce movement
|
||||
ImVec2 mouse_pos = ImGui::GetMousePos();
|
||||
|
||||
bool action_taken = false;
|
||||
|
||||
// Center the window with responsive size (80% of viewport, max 1400x900)
|
||||
ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
ImGui::SetNextWindowPos(viewport->WorkPos);
|
||||
ImGui::SetNextWindowSize(viewport->WorkSize);
|
||||
ImGui::SetNextWindowViewport(viewport->ID);
|
||||
ImVec2 center = viewport->GetCenter();
|
||||
ImVec2 viewport_size = viewport->Size;
|
||||
|
||||
ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration |
|
||||
ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoBringToFrontOnFocus |
|
||||
ImGuiWindowFlags_NoNavFocus;
|
||||
float width = std::min(viewport_size.x * 0.8f, 1400.0f);
|
||||
float height = std::min(viewport_size.y * 0.85f, 900.0f);
|
||||
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
|
||||
ImGui::SetNextWindowSize(ImVec2(width, height), ImGuiCond_Always);
|
||||
|
||||
ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove;
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20, 20));
|
||||
|
||||
@@ -157,15 +170,73 @@ bool WelcomeScreen::Show(bool* p_open) {
|
||||
ImVec2 window_pos = ImGui::GetWindowPos();
|
||||
ImVec2 window_size = ImGui::GetWindowSize();
|
||||
|
||||
// Dreamlike animated background with floating triforces
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
float offset = animation_time_ * 0.5f + i * 2.0f;
|
||||
float x = window_pos.x + window_size.x * (0.2f + 0.15f * i + sin(offset) * 0.1f);
|
||||
float y = window_pos.y + window_size.y * (0.3f + cos(offset * 0.7f) * 0.3f);
|
||||
float size = 40.0f + sin(offset * 1.3f) * 20.0f;
|
||||
float alpha = 0.05f + sin(offset) * 0.05f;
|
||||
// Interactive scattered triforces (react to mouse position)
|
||||
struct TriforceConfig {
|
||||
float x_pct, y_pct; // Base position (percentage of window)
|
||||
float size;
|
||||
float alpha;
|
||||
float repel_distance; // How far they move away from mouse
|
||||
};
|
||||
|
||||
TriforceConfig triforce_configs[] = {
|
||||
{0.10f, 0.15f, 32.0f, 0.035f, 60.0f}, // Top left corner
|
||||
{0.90f, 0.18f, 28.0f, 0.028f, 55.0f}, // Top right corner
|
||||
{0.08f, 0.82f, 24.0f, 0.022f, 50.0f}, // Bottom left
|
||||
{0.92f, 0.78f, 28.0f, 0.030f, 55.0f}, // Bottom right
|
||||
{0.18f, 0.45f, 24.0f, 0.020f, 45.0f}, // Mid left
|
||||
{0.82f, 0.52f, 24.0f, 0.020f, 45.0f}, // Mid right
|
||||
{0.50f, 0.88f, 20.0f, 0.018f, 40.0f}, // Bottom center
|
||||
{0.30f, 0.25f, 20.0f, 0.015f, 40.0f}, // Upper mid-left
|
||||
{0.70f, 0.28f, 20.0f, 0.015f, 40.0f}, // Upper mid-right
|
||||
};
|
||||
|
||||
// Initialize base positions on first frame
|
||||
if (!triforce_positions_initialized_) {
|
||||
for (int i = 0; i < kNumTriforces; ++i) {
|
||||
float x = window_pos.x + window_size.x * triforce_configs[i].x_pct;
|
||||
float y = window_pos.y + window_size.y * triforce_configs[i].y_pct;
|
||||
triforce_base_positions_[i] = ImVec2(x, y);
|
||||
triforce_positions_[i] = triforce_base_positions_[i];
|
||||
}
|
||||
triforce_positions_initialized_ = true;
|
||||
}
|
||||
|
||||
// Update triforce positions based on mouse interaction
|
||||
for (int i = 0; i < kNumTriforces; ++i) {
|
||||
// Update base position in case window moved/resized
|
||||
float base_x = window_pos.x + window_size.x * triforce_configs[i].x_pct;
|
||||
float base_y = window_pos.y + window_size.y * triforce_configs[i].y_pct;
|
||||
triforce_base_positions_[i] = ImVec2(base_x, base_y);
|
||||
|
||||
DrawTriforceBackground(bg_draw_list, ImVec2(x, y), size, alpha, 0.0f);
|
||||
// Calculate distance from mouse
|
||||
float dx = triforce_base_positions_[i].x - mouse_pos.x;
|
||||
float dy = triforce_base_positions_[i].y - mouse_pos.y;
|
||||
float dist = std::sqrt(dx * dx + dy * dy);
|
||||
|
||||
// Calculate repulsion offset
|
||||
ImVec2 target_pos = triforce_base_positions_[i];
|
||||
float repel_radius = 150.0f; // Start repelling within this radius
|
||||
|
||||
if (dist < repel_radius && dist > 0.1f) {
|
||||
// Normalize direction away from mouse
|
||||
float dir_x = dx / dist;
|
||||
float dir_y = dy / dist;
|
||||
|
||||
// Stronger repulsion when closer
|
||||
float repel_strength = (1.0f - dist / repel_radius) * triforce_configs[i].repel_distance;
|
||||
|
||||
target_pos.x += dir_x * repel_strength;
|
||||
target_pos.y += dir_y * repel_strength;
|
||||
}
|
||||
|
||||
// Smooth interpolation to target position
|
||||
float lerp_speed = 6.0f * ImGui::GetIO().DeltaTime;
|
||||
triforce_positions_[i].x += (target_pos.x - triforce_positions_[i].x) * lerp_speed;
|
||||
triforce_positions_[i].y += (target_pos.y - triforce_positions_[i].y) * lerp_speed;
|
||||
|
||||
// Draw at current position
|
||||
DrawTriforceBackground(bg_draw_list, triforce_positions_[i],
|
||||
triforce_configs[i].size, triforce_configs[i].alpha, 0.0f);
|
||||
}
|
||||
|
||||
DrawHeader();
|
||||
@@ -173,16 +244,20 @@ bool WelcomeScreen::Show(bool* p_open) {
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Main content area with gradient separator
|
||||
// Main content area with subtle gradient separator
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
ImVec2 separator_start = ImGui::GetCursorScreenPos();
|
||||
ImVec2 separator_end(separator_start.x + ImGui::GetContentRegionAvail().x, separator_start.y + 2);
|
||||
ImVec2 separator_end(separator_start.x + ImGui::GetContentRegionAvail().x, separator_start.y + 1);
|
||||
ImVec4 gold_faded = kTriforceGold;
|
||||
gold_faded.w = 0.3f;
|
||||
ImVec4 blue_faded = kMasterSwordBlue;
|
||||
blue_faded.w = 0.3f;
|
||||
draw_list->AddRectFilledMultiColor(
|
||||
separator_start, separator_end,
|
||||
ImGui::GetColorU32(kTriforceGold),
|
||||
ImGui::GetColorU32(kMasterSwordBlue),
|
||||
ImGui::GetColorU32(kMasterSwordBlue),
|
||||
ImGui::GetColorU32(kTriforceGold));
|
||||
ImGui::GetColorU32(gold_faded),
|
||||
ImGui::GetColorU32(blue_faded),
|
||||
ImGui::GetColorU32(blue_faded),
|
||||
ImGui::GetColorU32(gold_faded));
|
||||
|
||||
ImGui::Dummy(ImVec2(0, 10));
|
||||
|
||||
@@ -194,14 +269,13 @@ bool WelcomeScreen::Show(bool* p_open) {
|
||||
DrawQuickActions();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Animated separator
|
||||
// Subtle separator
|
||||
ImVec2 sep_start = ImGui::GetCursorScreenPos();
|
||||
float pulse = sin(animation_time_ * 2.0f) * 0.5f + 0.5f;
|
||||
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.3f + pulse * 0.3f)),
|
||||
2.0f);
|
||||
ImGui::GetColorU32(ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.2f)),
|
||||
1.0f);
|
||||
|
||||
ImGui::Dummy(ImVec2(0, 5));
|
||||
DrawTemplatesSection();
|
||||
@@ -214,13 +288,13 @@ bool WelcomeScreen::Show(bool* p_open) {
|
||||
DrawRecentProjects();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Another animated separator
|
||||
// Subtle separator
|
||||
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.3f + pulse * 0.3f)),
|
||||
2.0f);
|
||||
ImGui::GetColorU32(ImVec4(kMasterSwordBlue.x, kMasterSwordBlue.y, kMasterSwordBlue.z, 0.2f)),
|
||||
1.0f);
|
||||
|
||||
ImGui::Dummy(ImVec2(0, 5));
|
||||
DrawWhatsNew();
|
||||
@@ -228,15 +302,19 @@ bool WelcomeScreen::Show(bool* p_open) {
|
||||
|
||||
ImGui::EndChild();
|
||||
|
||||
// Footer with gradient
|
||||
// Footer with subtle gradient
|
||||
ImVec2 footer_start = ImGui::GetCursorScreenPos();
|
||||
ImVec2 footer_end(footer_start.x + ImGui::GetContentRegionAvail().x, footer_start.y + 2);
|
||||
ImVec2 footer_end(footer_start.x + ImGui::GetContentRegionAvail().x, footer_start.y + 1);
|
||||
ImVec4 red_faded = kHeartRed;
|
||||
red_faded.w = 0.3f;
|
||||
ImVec4 green_faded = kHyruleGreen;
|
||||
green_faded.w = 0.3f;
|
||||
draw_list->AddRectFilledMultiColor(
|
||||
footer_start, footer_end,
|
||||
ImGui::GetColorU32(kHeartRed),
|
||||
ImGui::GetColorU32(kHyruleGreen),
|
||||
ImGui::GetColorU32(kHyruleGreen),
|
||||
ImGui::GetColorU32(kHeartRed));
|
||||
ImGui::GetColorU32(red_faded),
|
||||
ImGui::GetColorU32(green_faded),
|
||||
ImGui::GetColorU32(green_faded),
|
||||
ImGui::GetColorU32(red_faded));
|
||||
|
||||
ImGui::Dummy(ImVec2(0, 5));
|
||||
DrawTipsSection();
|
||||
@@ -250,13 +328,14 @@ bool WelcomeScreen::Show(bool* p_open) {
|
||||
|
||||
void WelcomeScreen::UpdateAnimations() {
|
||||
animation_time_ += ImGui::GetIO().DeltaTime;
|
||||
header_glow_ = sin(animation_time_ * 2.0f) * 0.5f + 0.5f;
|
||||
|
||||
// Smooth card hover animations
|
||||
// Update hover scale for cards (smooth interpolation)
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
float target = (hovered_card_ == i) ? 1.05f : 1.0f;
|
||||
card_hover_scale_[i] += (target - card_hover_scale_[i]) * ImGui::GetIO().DeltaTime * 8.0f;
|
||||
float target = (hovered_card_ == i) ? 1.03f : 1.0f;
|
||||
card_hover_scale_[i] += (target - card_hover_scale_[i]) * ImGui::GetIO().DeltaTime * 10.0f;
|
||||
}
|
||||
|
||||
// Note: Triforce positions are updated in Show() based on mouse position
|
||||
}
|
||||
|
||||
void WelcomeScreen::RefreshRecentProjects() {
|
||||
@@ -294,7 +373,7 @@ void WelcomeScreen::DrawHeader() {
|
||||
|
||||
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[2]); // Large font
|
||||
|
||||
// Animated title with glow effect
|
||||
// Simple centered title
|
||||
const char* title = ICON_MD_CASTLE " yaze";
|
||||
auto windowWidth = ImGui::GetWindowSize().x;
|
||||
auto textWidth = ImGui::CalcTextSize(title).x;
|
||||
@@ -303,41 +382,32 @@ void WelcomeScreen::DrawHeader() {
|
||||
ImGui::SetCursorPosX(xPos);
|
||||
ImVec2 text_pos = ImGui::GetCursorScreenPos();
|
||||
|
||||
// Glow effect behind text
|
||||
float glow_size = 40.0f * header_glow_;
|
||||
ImU32 glow_color = ImGui::GetColorU32(ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.3f * header_glow_));
|
||||
// Subtle static glow behind text
|
||||
float glow_size = 30.0f;
|
||||
ImU32 glow_color = ImGui::GetColorU32(ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.15f));
|
||||
draw_list->AddCircleFilled(
|
||||
ImVec2(text_pos.x + textWidth / 2, text_pos.y + 15),
|
||||
glow_size,
|
||||
glow_color,
|
||||
32);
|
||||
|
||||
// Rainbow gradient on title
|
||||
ImVec4 color1 = kTriforceGold;
|
||||
ImVec4 color2 = kMasterSwordBlue;
|
||||
float t = sin(animation_time_ * 1.5f) * 0.5f + 0.5f;
|
||||
ImVec4 title_color(
|
||||
color1.x * (1 - t) + color2.x * t,
|
||||
color1.y * (1 - t) + color2.y * t,
|
||||
color1.z * (1 - t) + color2.z * t,
|
||||
1.0f);
|
||||
|
||||
ImGui::TextColored(title_color, "%s", title);
|
||||
// Simple gold color for title
|
||||
ImGui::TextColored(kTriforceGold, "%s", title);
|
||||
ImGui::PopFont();
|
||||
|
||||
// Animated subtitle
|
||||
// Static subtitle
|
||||
const char* subtitle = "Yet Another Zelda3 Editor";
|
||||
textWidth = ImGui::CalcTextSize(subtitle).x;
|
||||
ImGui::SetCursorPosX((windowWidth - textWidth) * 0.5f);
|
||||
|
||||
float subtitle_alpha = 0.6f + sin(animation_time_ * 3.0f) * 0.2f;
|
||||
ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.8f, subtitle_alpha), "%s", subtitle);
|
||||
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "%s", subtitle);
|
||||
|
||||
// Draw small decorative triforces on either side of title
|
||||
ImVec2 left_tri_pos(xPos - 50, text_pos.y + 10);
|
||||
ImVec2 right_tri_pos(xPos + textWidth + 20, text_pos.y + 10);
|
||||
DrawTriforceBackground(draw_list, left_tri_pos, 30, 0.5f, header_glow_);
|
||||
DrawTriforceBackground(draw_list, right_tri_pos, 30, 0.5f, header_glow_);
|
||||
// Small decorative triforces flanking the title (static, transparent)
|
||||
// Positioned well away from text to avoid crowding
|
||||
ImVec2 left_tri_pos(xPos - 80, text_pos.y + 20);
|
||||
ImVec2 right_tri_pos(xPos + textWidth + 50, text_pos.y + 20);
|
||||
DrawTriforceBackground(draw_list, left_tri_pos, 20, 0.12f, 0.0f);
|
||||
DrawTriforceBackground(draw_list, right_tri_pos, 20, 0.12f, 0.0f);
|
||||
|
||||
ImGui::Spacing();
|
||||
}
|
||||
@@ -404,13 +474,12 @@ void WelcomeScreen::DrawRecentProjects() {
|
||||
ImGui::Spacing();
|
||||
|
||||
if (recent_projects_.empty()) {
|
||||
// Draw a cute "empty state" with animated icons
|
||||
// Simple empty state
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.6f, 0.6f, 1.0f));
|
||||
|
||||
float pulse = sin(animation_time_ * 2.0f) * 0.3f + 0.7f;
|
||||
ImVec2 cursor = ImGui::GetCursorPos();
|
||||
ImGui::SetCursorPosX(cursor.x + ImGui::GetContentRegionAvail().x * 0.3f);
|
||||
ImGui::TextColored(ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, pulse),
|
||||
ImGui::TextColored(ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.8f),
|
||||
ICON_MD_EXPLORE);
|
||||
ImGui::SetCursorPosX(cursor.x);
|
||||
|
||||
@@ -438,7 +507,7 @@ void WelcomeScreen::DrawProjectCard(const RecentProject& project, int index) {
|
||||
ImVec2 card_size(220, 140);
|
||||
ImVec2 cursor_pos = ImGui::GetCursorScreenPos();
|
||||
|
||||
// Apply hover scale
|
||||
// Subtle hover scale (only on actual hover, no animation)
|
||||
float scale = card_hover_scale_[index];
|
||||
if (scale != 1.0f) {
|
||||
ImVec2 center(cursor_pos.x + card_size.x / 2, cursor_pos.y + card_size.y / 2);
|
||||
@@ -448,7 +517,7 @@ void WelcomeScreen::DrawProjectCard(const RecentProject& project, int index) {
|
||||
card_size.y *= scale;
|
||||
}
|
||||
|
||||
// Draw card background with Zelda-themed gradient
|
||||
// Draw card background with subtle gradient
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
|
||||
// Gradient background
|
||||
@@ -459,12 +528,11 @@ void WelcomeScreen::DrawProjectCard(const RecentProject& project, int index) {
|
||||
ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y),
|
||||
color_top, color_top, color_bottom, color_bottom);
|
||||
|
||||
// Border with animated color
|
||||
float border_t = (sin(animation_time_ + index) * 0.5f + 0.5f);
|
||||
// Static themed border
|
||||
ImVec4 border_color_base = (index % 3 == 0) ? kHyruleGreen :
|
||||
(index % 3 == 1) ? kMasterSwordBlue : kTriforceGold;
|
||||
ImU32 border_color = ImGui::GetColorU32(
|
||||
ImVec4(border_color_base.x, border_color_base.y, border_color_base.z, 0.4f + border_t * 0.3f));
|
||||
ImVec4(border_color_base.x, border_color_base.y, border_color_base.z, 0.5f));
|
||||
|
||||
draw_list->AddRect(cursor_pos,
|
||||
ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y),
|
||||
@@ -478,24 +546,12 @@ void WelcomeScreen::DrawProjectCard(const RecentProject& project, int index) {
|
||||
|
||||
hovered_card_ = is_hovered ? index : (hovered_card_ == index ? -1 : hovered_card_);
|
||||
|
||||
// Hover glow effect
|
||||
// Subtle hover glow (no particles)
|
||||
if (is_hovered) {
|
||||
ImU32 hover_color = ImGui::GetColorU32(ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.2f));
|
||||
ImU32 hover_color = ImGui::GetColorU32(ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.15f));
|
||||
draw_list->AddRectFilled(cursor_pos,
|
||||
ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y),
|
||||
hover_color, 6.0f);
|
||||
|
||||
// Draw small particles around the card
|
||||
for (int p = 0; p < 5; ++p) {
|
||||
float angle = animation_time_ * 2.0f + p * M_PI * 0.4f;
|
||||
float radius = 10.0f + sin(animation_time_ * 3.0f + p) * 5.0f;
|
||||
ImVec2 particle_pos(
|
||||
cursor_pos.x + card_size.x / 2 + cos(angle) * (card_size.x / 2 + radius),
|
||||
cursor_pos.y + card_size.y / 2 + sin(angle) * (card_size.y / 2 + radius));
|
||||
float particle_alpha = 0.3f + sin(animation_time_ * 4.0f + p) * 0.2f;
|
||||
draw_list->AddCircleFilled(particle_pos, 2.0f,
|
||||
ImGui::GetColorU32(ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, particle_alpha)));
|
||||
}
|
||||
}
|
||||
|
||||
// Draw content
|
||||
@@ -576,12 +632,11 @@ void WelcomeScreen::DrawTemplatesSection() {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
bool is_selected = (selected_template_ == i);
|
||||
|
||||
// Animated selection glow
|
||||
// Subtle selection highlight (no animation)
|
||||
if (is_selected) {
|
||||
float glow = sin(animation_time_ * 3.0f) * 0.3f + 0.5f;
|
||||
ImGui::PushStyleColor(ImGuiCol_Header,
|
||||
ImVec4(templates[i].color.x * glow, templates[i].color.y * glow,
|
||||
templates[i].color.z * glow, 0.8f));
|
||||
ImVec4(templates[i].color.x * 0.6f, templates[i].color.y * 0.6f,
|
||||
templates[i].color.z * 0.6f, 0.6f));
|
||||
}
|
||||
|
||||
if (ImGui::Selectable(absl::StrFormat("%s %s", templates[i].icon, templates[i].name).c_str(),
|
||||
@@ -609,7 +664,7 @@ void WelcomeScreen::DrawTemplatesSection() {
|
||||
}
|
||||
|
||||
void WelcomeScreen::DrawTipsSection() {
|
||||
// Rotating tips
|
||||
// Static tip (or could rotate based on session start time rather than animation)
|
||||
const char* tips[] = {
|
||||
"Press Ctrl+P to open the command palette",
|
||||
"Use z3ed agent for AI-powered ROM editing",
|
||||
@@ -617,7 +672,7 @@ void WelcomeScreen::DrawTipsSection() {
|
||||
"Check the Performance Dashboard for optimization insights",
|
||||
"Collaborate in real-time with yaze-server"
|
||||
};
|
||||
int tip_index = ((int)(animation_time_ / 5.0f)) % 5;
|
||||
int tip_index = 0; // Show first tip, or could be random on screen open
|
||||
|
||||
ImGui::Text(ICON_MD_LIGHTBULB);
|
||||
ImGui::SameLine();
|
||||
@@ -637,11 +692,8 @@ void WelcomeScreen::DrawWhatsNew() {
|
||||
ImGui::TextColored(kHeartRed, ICON_MD_NEW_RELEASES " What's New");
|
||||
ImGui::Spacing();
|
||||
|
||||
// Version badge
|
||||
float pulse = sin(animation_time_ * 2.0f) * 0.2f + 0.8f;
|
||||
ImGui::TextColored(ImVec4(kMasterSwordBlue.x * pulse, kMasterSwordBlue.y * pulse,
|
||||
kMasterSwordBlue.z * pulse, 1.0f),
|
||||
ICON_MD_VERIFIED " yaze v0.2.0-alpha");
|
||||
// Version badge (no animation)
|
||||
ImGui::TextColored(kMasterSwordBlue, ICON_MD_VERIFIED " yaze v0.2.0-alpha");
|
||||
ImGui::Spacing();
|
||||
|
||||
// Feature list with icons and colors
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace editor {
|
||||
|
||||
@@ -100,9 +102,14 @@ class WelcomeScreen {
|
||||
|
||||
// Animation state
|
||||
float animation_time_ = 0.0f;
|
||||
float header_glow_ = 0.0f;
|
||||
float card_hover_scale_[6] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f};
|
||||
int hovered_card_ = -1;
|
||||
|
||||
// Interactive triforce positions (smooth interpolation)
|
||||
static constexpr int kNumTriforces = 9;
|
||||
ImVec2 triforce_positions_[kNumTriforces] = {};
|
||||
ImVec2 triforce_base_positions_[kNumTriforces] = {};
|
||||
bool triforce_positions_initialized_ = false;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
|
||||
Reference in New Issue
Block a user