From 81c67728a942f6d8c721df749c105e718430d321 Mon Sep 17 00:00:00 2001 From: scawful Date: Sun, 5 Oct 2025 02:59:06 -0400 Subject: [PATCH] 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. --- src/app/editor/editor_manager.cc | 689 +++++-------------- src/app/editor/ui/editor_selection_dialog.cc | 35 +- src/app/editor/ui/menu_builder.cc | 101 ++- src/app/editor/ui/menu_builder.h | 4 + src/app/editor/ui/welcome_screen.cc | 272 +++++--- src/app/editor/ui/welcome_screen.h | 9 +- 6 files changed, 460 insertions(+), 650 deletions(-) diff --git a/src/app/editor/editor_manager.cc b/src/app/editor/editor_manager.cc index afa97693..a43927a3 100644 --- a/src/app/editor/editor_manager.cc +++ b/src/app/editor/editor_manager.cc @@ -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 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 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{ - {"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 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{ - {"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(*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{ - {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{ - {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{ - {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{ - {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{ - {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{ - {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{ - {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{ - {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; diff --git a/src/app/editor/ui/editor_selection_dialog.cc b/src/app/editor/ui/editor_selection_dialog.cc index 79af5e8a..d34e8442 100644 --- a/src/app/editor/ui/editor_selection_dialog.cc +++ b/src/app/editor/ui/editor_selection_dialog.cc @@ -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(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; diff --git a/src/app/editor/ui/menu_builder.cc b/src/app/editor/ui/menu_builder.cc index d1ed6748..629f7495 100644 --- a/src/app/editor/ui/menu_builder.cc +++ b/src/app/editor/ui/menu_builder.cc @@ -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); diff --git a/src/app/editor/ui/menu_builder.h b/src/app/editor/ui/menu_builder.h index f2dc6f2a..f429f942 100644 --- a/src/app/editor/ui/menu_builder.h +++ b/src/app/editor/ui/menu_builder.h @@ -110,6 +110,10 @@ class MenuBuilder { std::vector menus_; Menu* current_menu_ = nullptr; + // Track which submenus are actually open during drawing + mutable std::vector submenu_stack_; + mutable int skip_depth_ = 0; // Track nesting depth when skipping closed submenus + void DrawMenuItem(const MenuItem& item); }; diff --git a/src/app/editor/ui/welcome_screen.cc b/src/app/editor/ui/welcome_screen.cc index 2bf9eca4..2e7db518 100644 --- a/src/app/editor/ui/welcome_screen.cc +++ b/src/app/editor/ui/welcome_screen.cc @@ -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 diff --git a/src/app/editor/ui/welcome_screen.h b/src/app/editor/ui/welcome_screen.h index 9cf31f77..2bf85936 100644 --- a/src/app/editor/ui/welcome_screen.h +++ b/src/app/editor/ui/welcome_screen.h @@ -5,6 +5,8 @@ #include #include +#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