feat: Enhance Welcome Screen and Menu Structure in EditorManager

- Integrated a new `WelcomeScreen` class for a modernized user experience, featuring callbacks for opening ROMs, creating new projects, and opening existing projects.
- Updated the menu structure to include new items for message and music editors, as well as an AI Agent menu for collaboration features.
- Added functionality for hosting and joining collaboration sessions, along with options for network settings and session management.
- Improved the welcome screen display logic to handle recent projects and user interactions more effectively.
This commit is contained in:
scawful
2025-10-05 02:28:46 -04:00
parent 499550f628
commit 576229f7c3
2 changed files with 139 additions and 383 deletions

View File

@@ -324,6 +324,31 @@ void EditorManager::Initialize(const std::string& filename) {
#endif
// Load critical user settings first
// Initialize welcome screen callbacks
welcome_screen_.SetOpenRomCallback([this]() {
status_ = LoadRom();
if (status_.ok()) {
show_welcome_screen_ = false;
welcome_screen_manually_closed_ = true;
}
});
welcome_screen_.SetNewProjectCallback([this]() {
status_ = CreateNewProject();
if (status_.ok()) {
show_welcome_screen_ = false;
welcome_screen_manually_closed_ = true;
}
});
welcome_screen_.SetOpenProjectCallback([this](const std::string& filepath) {
status_ = OpenRomOrProject(filepath);
if (status_.ok()) {
show_welcome_screen_ = false;
welcome_screen_manually_closed_ = true;
}
});
LoadUserSettings();
// Defer workspace presets loading to avoid initialization crashes
@@ -1159,12 +1184,23 @@ void EditorManager::BuildModernMenu() {
[this]() { current_editor_set_->sprite_editor_.set_active(true); })
.Item("Palette", ICON_MD_PALETTE,
[this]() { current_editor_set_->palette_editor_.set_active(true); })
.Item("Message", ICON_MD_CHAT_BUBBLE,
[this]() { current_editor_set_->message_editor_.set_active(true); })
.Item("Music", ICON_MD_MUSIC_NOTE,
[this]() { current_editor_set_->music_editor_.set_active(true); })
.EndMenu()
.Separator()
.Item("Welcome Screen", ICON_MD_HOME,
[this]() { show_welcome_screen_ = true; })
.Item("Command Palette", ICON_MD_TERMINAL,
[this]() { show_command_palette_ = true; }, "Ctrl+Shift+P")
#ifdef YAZE_WITH_GRPC
.Separator()
.Item("AI Agent Editor", ICON_MD_SMART_TOY,
[this]() { agent_editor_.SetChatActive(true); }, "Ctrl+Shift+A",
nullptr,
[this]() { return agent_editor_.IsChatActive(); })
#endif
.EndMenu();
// Workspace Menu
@@ -1188,6 +1224,98 @@ void EditorManager::BuildModernMenu() {
[this]() { show_performance_dashboard_ = true; }, "Ctrl+Shift+P")
.EndMenu();
#ifdef YAZE_WITH_GRPC
// AI Agent Menu
menu_builder_.BeginMenu("AI Agent", ICON_MD_SMART_TOY)
.Item("Open Agent Chat", ICON_MD_CHAT,
[this]() { agent_editor_.SetChatActive(true); }, "Ctrl+Shift+A")
.Separator()
.BeginSubMenu("Vision & Multimodal", ICON_MD_CAMERA)
.Item("Capture Active Editor", ICON_MD_SCREENSHOT,
[this]() {
std::filesystem::path output;
AgentEditor::CaptureConfig config;
config.mode = AgentEditor::CaptureConfig::CaptureMode::kActiveEditor;
status_ = agent_editor_.CaptureSnapshot(&output, config);
})
.Item("Capture Full Window", ICON_MD_FULLSCREEN,
[this]() {
std::filesystem::path output;
AgentEditor::CaptureConfig config;
config.mode = AgentEditor::CaptureConfig::CaptureMode::kFullWindow;
status_ = agent_editor_.CaptureSnapshot(&output, config);
})
.EndMenu()
.Separator()
.Item("Proposal Drawer", ICON_MD_RATE_REVIEW,
[this]() { show_proposal_drawer_ = !show_proposal_drawer_; },
nullptr, nullptr,
[this]() { return show_proposal_drawer_; })
.EndMenu();
// Collaboration Menu
menu_builder_.BeginMenu("Collaboration", ICON_MD_PEOPLE)
.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);
} else {
toast_manager_.AddToast(ToastType::Error,
"Failed to host session: " + std::string(result.status().message()));
}
})
.Item("Join Session", ICON_MD_LOGIN,
[this]() { popup_manager_->Show("Join Collaboration Session"); })
.Separator()
.Item("Leave Session", ICON_MD_LOGOUT,
[this]() {
status_ = agent_editor_.LeaveSession();
if (status_.ok()) {
toast_manager_.AddToast(ToastType::Info, "Left collaboration session");
}
},
nullptr,
[this]() { return agent_editor_.IsInSession(); })
.Item("Refresh Session", ICON_MD_REFRESH,
[this]() {
auto result = agent_editor_.RefreshSession();
if (result.ok()) {
toast_manager_.AddToast(ToastType::Success,
"Session refreshed: " + std::to_string(result->participants.size()) + " participants");
}
},
nullptr,
[this]() { return agent_editor_.IsInSession(); })
.EndMenu()
.Separator()
.BeginSubMenu("Network", 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");
},
nullptr,
[this]() { return agent_editor_.IsConnectedToServer(); })
.EndMenu()
.Separator()
.BeginSubMenu("Mode", ICON_MD_SETTINGS_ETHERNET)
.Item("Local (Filesystem)", ICON_MD_FOLDER,
[this]() { /* Set local mode */ },
nullptr, nullptr,
[this]() { return agent_editor_.GetCurrentMode() == AgentEditor::CollaborationMode::kLocal; })
.Item("Network (WebSocket)", ICON_MD_WIFI,
[this]() { /* Set network mode */ },
nullptr, nullptr,
[this]() { return agent_editor_.GetCurrentMode() == AgentEditor::CollaborationMode::kNetwork; })
.EndMenu()
.EndMenu();
#endif
// Debug Menu
menu_builder_.BeginMenu("Debug", ICON_MD_BUG_REPORT)
.Item("Test Dashboard", ICON_MD_SCIENCE,
@@ -3194,391 +3322,15 @@ void EditorManager::DrawSessionRenameDialog() {
}
void EditorManager::DrawWelcomeScreen() {
// Make welcome screen moveable but with a good default position
ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f));
ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver);
// Create a subtle animated background effect
static float animation_time = 0.0f;
animation_time += ImGui::GetIO().DeltaTime;
// Make it moveable and resizable but keep the custom styling
ImGuiWindowFlags flags =
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBackground;
// Use a unique window name to prevent stacking
static int welcome_window_id = 0;
std::string window_name =
absl::StrFormat("Welcome to YAZE##welcome_%d", welcome_window_id);
bool welcome_was_open = show_welcome_screen_;
if (ImGui::Begin(window_name.c_str(), &show_welcome_screen_, flags)) {
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImVec2 window_pos = ImGui::GetWindowPos();
ImVec2 window_size = ImGui::GetWindowSize();
// Get theme colors for welcome screen
auto& theme_manager = gui::ThemeManager::Get();
auto bg_color = theme_manager.GetWelcomeScreenBackground();
auto border_color = theme_manager.GetWelcomeScreenBorder();
auto accent_color = theme_manager.GetWelcomeScreenAccent();
// Draw themed gradient background
ImU32 bg_top = ImGui::ColorConvertFloat4ToU32(
ImVec4(bg_color.red, bg_color.green, bg_color.blue, bg_color.alpha));
ImU32 bg_bottom = ImGui::ColorConvertFloat4ToU32(
ImVec4(bg_color.red * 0.8f, bg_color.green * 0.8f, bg_color.blue * 0.8f,
bg_color.alpha));
draw_list->AddRectFilledMultiColor(
window_pos,
ImVec2(window_pos.x + window_size.x, window_pos.y + window_size.y),
bg_top, bg_top, bg_bottom, bg_bottom);
// Themed animated border
float border_thickness = 3.0f;
float pulse = 0.8f + 0.2f * sinf(animation_time * 2.0f);
ImU32 themed_border = ImGui::ColorConvertFloat4ToU32(
ImVec4(border_color.red, border_color.green, border_color.blue,
pulse * border_color.alpha));
draw_list->AddRect(
window_pos,
ImVec2(window_pos.x + window_size.x, window_pos.y + window_size.y),
themed_border, 12.0f, 0, border_thickness);
// Enhanced floating particles effect with multiple layers
for (int layer = 0; layer < 2; ++layer) {
int particle_count = layer == 0 ? 12 : 8;
float layer_speed = layer == 0 ? 1.0f : 0.6f;
float layer_alpha = layer == 0 ? 0.4f : 0.2f;
for (int i = 0; i < particle_count; ++i) {
float time_offset = layer * 3.14159f + i * 0.8f;
float offset_x =
sinf(animation_time * 0.5f * layer_speed + time_offset) *
(30.0f + layer * 10.0f);
float offset_y =
cosf(animation_time * 0.3f * layer_speed + time_offset) *
(20.0f + layer * 8.0f);
// Distribute particles across the window
float base_x = window_pos.x + (window_size.x / particle_count) * i + 40;
float base_y = window_pos.y + 80 + layer * 30;
ImVec2 particle_pos = ImVec2(base_x + offset_x, base_y + offset_y);
// Pulsing alpha effect
float alpha =
layer_alpha + 0.3f * sinf(animation_time * 1.5f + time_offset);
ImU32 particle_color = ImGui::ColorConvertFloat4ToU32(ImVec4(
accent_color.red, accent_color.green, accent_color.blue, alpha));
// Varying particle sizes
float radius = 1.5f + layer * 0.5f +
sinf(animation_time * 2.0f + time_offset) * 0.8f;
draw_list->AddCircleFilled(particle_pos, radius, particle_color);
// Add subtle glow effect for layer 0
if (layer == 0) {
ImU32 glow_color = ImGui::ColorConvertFloat4ToU32(
ImVec4(accent_color.red, accent_color.green, accent_color.blue,
alpha * 0.3f));
draw_list->AddCircleFilled(particle_pos, radius + 1.0f, glow_color);
}
}
}
// Header with themed styling
ImGui::Spacing();
ImGui::SetCursorPosX(
(window_size.x -
ImGui::CalcTextSize("Welcome to Yet Another Zelda3 Editor").x) *
0.5f);
auto text_color = theme_manager.GetCurrentTheme().text_primary;
ImGui::PushStyleColor(
ImGuiCol_Text, ImVec4(text_color.red, text_color.green, text_color.blue,
text_color.alpha));
ImGui::Text("Welcome to Yet Another Zelda3 Editor");
ImGui::PopStyleColor();
ImGui::SetCursorPosX(
(window_size.x -
ImGui::CalcTextSize("The Legend of Zelda: A Link to the Past").x) *
0.5f);
auto subtitle_color = theme_manager.GetCurrentTheme().text_secondary;
ImGui::PushStyleColor(
ImGuiCol_Text,
ImVec4(subtitle_color.red, subtitle_color.green, subtitle_color.blue,
subtitle_color.alpha * 0.9f));
ImGui::Text("The Legend of Zelda: A Link to the Past");
ImGui::PopStyleColor();
ImGui::Spacing();
// Themed decorative line with glow effect (positioned closer to header)
float line_y =
window_pos.y + 30; // Move even higher for tighter header integration
float line_margin = 80; // Maintain good horizontal balance
ImVec2 line_start = ImVec2(window_pos.x + line_margin, line_y);
ImVec2 line_end =
ImVec2(window_pos.x + window_size.x - line_margin, line_y);
// Enhanced glow effect with multiple line layers for depth
float glow_alpha = 0.6f + 0.4f * sinf(animation_time * 1.5f);
ImU32 line_color = ImGui::ColorConvertFloat4ToU32(ImVec4(
accent_color.red, accent_color.green, accent_color.blue, glow_alpha));
// Draw main line with glow effect
draw_list->AddLine(
line_start, line_end,
ImGui::ColorConvertFloat4ToU32(ImVec4(
accent_color.red, accent_color.green, accent_color.blue, 0.3f)),
4.0f); // Glow layer
draw_list->AddLine(line_start, line_end, line_color, 2.0f); // Main line
ImGui::Spacing();
ImGui::Spacing();
// Show different messages based on state
if (!sessions_.empty() && !current_rom_) {
ImGui::Separator();
ImGui::Spacing();
ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.0f, 1.0f),
ICON_MD_WARNING " ROM Loading Required");
TextWrapped(
"A session exists but no ROM is loaded. Please load a ROM file to "
"continue editing.");
ImGui::Text("Active Sessions: %zu", GetActiveSessionCount());
} else {
ImGui::Separator();
ImGui::Spacing();
TextWrapped("No ROM loaded.");
}
ImGui::Spacing();
// Enhanced primary actions with glowing buttons
ImGui::Text("Get Started:");
ImGui::Spacing();
// Themed primary buttons with enhanced effects
auto current_theme = theme_manager.GetCurrentTheme();
ImGui::PushStyleColor(ImGuiCol_Button,
ConvertColorToImVec4(current_theme.primary));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
ConvertColorToImVec4(current_theme.accent));
ImGui::PushStyleColor(ImGuiCol_ButtonActive,
ConvertColorToImVec4(current_theme.secondary));
if (ImGui::Button(ICON_MD_FILE_OPEN " Open ROM File", ImVec2(200, 40))) {
status_ = LoadRom();
if (!status_.ok()) {
toast_manager_.Show(std::string(status_.message()),
editor::ToastType::kError);
}
}
ImGui::SameLine();
if (ImGui::Button(ICON_MD_FOLDER_OPEN " Open Project", ImVec2(200, 40))) {
auto file_name = util::FileDialogWrapper::ShowOpenFileDialog();
if (!file_name.empty()) {
status_ = OpenRomOrProject(file_name);
if (!status_.ok()) {
toast_manager_.Show(std::string(status_.message()),
editor::ToastType::kError);
}
}
}
ImGui::PopStyleColor(3);
ImGui::Spacing();
// Feature flags section (per-session)
ImGui::Text("Options:");
auto* flags = GetCurrentFeatureFlags();
Checkbox("Load custom overworld features",
&flags->overworld.kLoadCustomOverworld);
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
// Recent files section (reuse homepage logic)
ImGui::Text("Recent Files:");
ImGui::BeginChild("RecentFiles", ImVec2(0, 100), true);
auto& manager = core::RecentFilesManager::GetInstance();
for (const auto& file : manager.GetRecentFiles()) {
if (gui::ClickableText(file.c_str())) {
status_ = OpenRomOrProject(file);
if (!status_.ok()) {
toast_manager_.Show(std::string(status_.message()),
editor::ToastType::kError);
}
}
}
ImGui::EndChild();
ImGui::Spacing();
// Show editor access buttons for loaded sessions
bool has_loaded_sessions = false;
for (const auto& session : sessions_) {
if (session.rom.is_loaded()) {
has_loaded_sessions = true;
break;
}
}
if (has_loaded_sessions) {
ImGui::Spacing();
ImGui::Separator();
ImGui::Text("Available Editors:");
ImGui::Text(
"Click to open editor windows that can be docked side by side");
ImGui::Spacing();
// Show sessions and their editors
for (size_t session_idx = 0; session_idx < sessions_.size();
++session_idx) {
const auto& session = sessions_[session_idx];
if (!session.rom.is_loaded())
continue;
ImGui::Text("Session: %s", session.GetDisplayName().c_str());
// Editor buttons in a grid layout for this session
if (ImGui::BeginTable(
absl::StrFormat("EditorsTable##%zu", session_idx).c_str(), 4,
ImGuiTableFlags_SizingFixedFit |
ImGuiTableFlags_NoHostExtendX)) {
// Row 1: Primary editors
ImGui::TableNextColumn();
if (ImGui::Button(
absl::StrFormat(ICON_MD_MAP " Overworld##%zu", session_idx)
.c_str(),
ImVec2(120, 30))) {
sessions_[session_idx].editors.overworld_editor_.set_active(true);
}
ImGui::TableNextColumn();
if (ImGui::Button(
absl::StrFormat(ICON_MD_DOMAIN " Dungeon##%zu", session_idx)
.c_str(),
ImVec2(120, 30))) {
sessions_[session_idx].editors.dungeon_editor_.set_active(true);
}
ImGui::TableNextColumn();
if (ImGui::Button(
absl::StrFormat(ICON_MD_IMAGE " Graphics##%zu", session_idx)
.c_str(),
ImVec2(120, 30))) {
sessions_[session_idx].editors.graphics_editor_.set_active(true);
}
ImGui::TableNextColumn();
if (ImGui::Button(
absl::StrFormat(ICON_MD_PALETTE " Palette##%zu", session_idx)
.c_str(),
ImVec2(120, 30))) {
sessions_[session_idx].editors.palette_editor_.set_active(true);
}
// Row 2: Secondary editors
ImGui::TableNextColumn();
if (ImGui::Button(
absl::StrFormat(ICON_MD_MESSAGE " Message##%zu", session_idx)
.c_str(),
ImVec2(120, 30))) {
sessions_[session_idx].editors.message_editor_.set_active(true);
}
ImGui::TableNextColumn();
if (ImGui::Button(
absl::StrFormat(ICON_MD_PERSON " Sprite##%zu", session_idx)
.c_str(),
ImVec2(120, 30))) {
sessions_[session_idx].editors.sprite_editor_.set_active(true);
}
ImGui::TableNextColumn();
if (ImGui::Button(
absl::StrFormat(ICON_MD_MUSIC_NOTE " Music##%zu", session_idx)
.c_str(),
ImVec2(120, 30))) {
sessions_[session_idx].editors.music_editor_.set_active(true);
}
ImGui::TableNextColumn();
if (ImGui::Button(
absl::StrFormat(ICON_MD_MONITOR " Screen##%zu", session_idx)
.c_str(),
ImVec2(120, 30))) {
sessions_[session_idx].editors.screen_editor_.set_active(true);
}
ImGui::EndTable();
}
if (session_idx < sessions_.size() - 1) {
ImGui::Spacing();
}
}
}
// Links section
ImGui::Spacing();
ImGui::Separator();
ImGui::Text("Help & Support:");
if (gui::ClickableText(ICON_MD_HELP " Getting Started Guide")) {
gui::OpenUrl(
"https://github.com/scawful/yaze/blob/master/docs/"
"01-getting-started.md");
}
if (gui::ClickableText(ICON_MD_BUG_REPORT " Report Issues")) {
gui::OpenUrl("https://github.com/scawful/yaze/issues");
}
// Show tip about drag and drop
ImGui::Spacing();
ImGui::TextColored(ImVec4(0.6f, 0.8f, 1.0f, 1.0f), ICON_MD_TIPS_AND_UPDATES
" Tip: Drag and drop ROM files onto the window");
// Add settings and customization section (accessible before ROM loading)
ImGui::Spacing();
ImGui::Separator();
ImGui::Text("%s Customization & Settings", ICON_MD_SETTINGS);
// Theme and display settings buttons (always accessible)
static bool show_welcome_theme_selector = false;
if (ImGui::Button(
absl::StrFormat("%s Theme Settings", ICON_MD_PALETTE).c_str(),
ImVec2(180, 35))) {
show_welcome_theme_selector = true;
}
// Show theme selector if requested
if (show_welcome_theme_selector) {
auto& theme_manager = gui::ThemeManager::Get();
theme_manager.ShowThemeSelector(&show_welcome_theme_selector);
}
ImGui::SameLine();
if (ImGui::Button(
absl::StrFormat("%s Display Settings", ICON_MD_DISPLAY_SETTINGS)
.c_str(),
ImVec2(180, 35))) {
// Open display settings popup (make it accessible without ROM)
popup_manager_->Show("Display Settings");
}
ImGui::SameLine();
if (ImGui::Button(
absl::StrFormat("%s Command Palette", ICON_MD_TERMINAL).c_str(),
ImVec2(180, 35))) {
show_command_palette_ = true;
}
}
ImGui::End();
// Use the new WelcomeScreen class for a modern, feature-rich experience
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 (welcome_was_open && !show_welcome_screen_) {
if (was_open && !show_welcome_screen_) {
welcome_screen_manually_closed_ = true;
welcome_screen_.MarkManuallyClosed();
}
}