Implement multi-session support and welcome screen in EditorManager
- Removed the homepage display logic and replaced it with a welcome screen that appears when no ROM is loaded or no active editors are present. - Enhanced session management by iterating through all sessions to check for active editors, allowing for better multi-session docking. - Introduced new methods for generating unique editor titles based on session context and added feature flags for per-session configurations. - Added safeguards for ROM loading state checks in editor methods to prevent operations on unloaded ROMs.
This commit is contained in:
@@ -46,6 +46,10 @@ void DungeonEditor::Initialize() {
|
||||
}
|
||||
|
||||
absl::Status DungeonEditor::Load() {
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return absl::FailedPreconditionError("ROM not loaded");
|
||||
}
|
||||
|
||||
auto dungeon_man_pal_group = rom()->palette_group().dungeon_main;
|
||||
|
||||
// Use room loader component for loading rooms
|
||||
|
||||
@@ -83,6 +83,14 @@ class DungeonEditor : public Editor {
|
||||
}
|
||||
Rom* rom() const { return rom_; }
|
||||
|
||||
// ROM state methods (from Editor base class)
|
||||
bool IsRomLoaded() const override { return rom_ && rom_->is_loaded(); }
|
||||
std::string GetRomStatus() const override {
|
||||
if (!rom_) return "No ROM loaded";
|
||||
if (!rom_->is_loaded()) return "ROM failed to load";
|
||||
return absl::StrFormat("ROM loaded: %s", rom_->title());
|
||||
}
|
||||
|
||||
private:
|
||||
absl::Status RefreshGraphics();
|
||||
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/editor/system/command_manager.h"
|
||||
#include "app/editor/system/extension_manager.h"
|
||||
#include "app/editor/system/history_manager.h"
|
||||
@@ -101,10 +104,30 @@ class Editor {
|
||||
bool* active() { return &active_; }
|
||||
void set_active(bool active) { active_ = active; }
|
||||
|
||||
// ROM loading state helpers (default implementations)
|
||||
virtual bool IsRomLoaded() const { return false; }
|
||||
virtual std::string GetRomStatus() const { return "ROM state not implemented"; }
|
||||
|
||||
protected:
|
||||
bool active_ = false;
|
||||
EditorType type_;
|
||||
EditorContext* context_ = nullptr;
|
||||
|
||||
// Helper method for ROM access with safety check
|
||||
template<typename T>
|
||||
absl::StatusOr<T> SafeRomAccess(std::function<T()> accessor, const std::string& operation = "") const {
|
||||
if (!IsRomLoaded()) {
|
||||
return absl::FailedPreconditionError(
|
||||
operation.empty() ? "ROM not loaded" :
|
||||
absl::StrFormat("%s: ROM not loaded", operation));
|
||||
}
|
||||
try {
|
||||
return accessor();
|
||||
} catch (const std::exception& e) {
|
||||
return absl::InternalError(absl::StrFormat(
|
||||
"%s: %s", operation.empty() ? "ROM access failed" : operation, e.what()));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
|
||||
@@ -423,8 +423,6 @@ void EditorManager::Initialize(const std::string &filename) {
|
||||
{},
|
||||
{},
|
||||
{
|
||||
{absl::StrCat(ICON_MD_HOME, " Home"), "",
|
||||
[&]() { show_homepage_ = true; }},
|
||||
{kAssemblyEditorName, "", [&]() { show_asm_editor_ = true; },
|
||||
[&]() { return show_asm_editor_; }},
|
||||
{kDungeonEditorName, "",
|
||||
@@ -734,37 +732,78 @@ absl::Status EditorManager::Update() {
|
||||
autosave_timer_ = 0.0f;
|
||||
}
|
||||
|
||||
if (show_homepage_) {
|
||||
ImGui::Begin("Home", &show_homepage_);
|
||||
DrawHomepage();
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
// Check if ROM is loaded before allowing editor updates
|
||||
if (!current_editor_set_) {
|
||||
// Show welcome screen when no session is active
|
||||
if (sessions_.empty()) {
|
||||
DrawWelcomeScreen();
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
for (auto editor : current_editor_set_->active_editors_) {
|
||||
if (*editor->active()) {
|
||||
if (editor->type() == EditorType::kOverworld) {
|
||||
auto &overworld_editor = static_cast<OverworldEditor &>(*editor);
|
||||
if (overworld_editor.jump_to_tab() != -1) {
|
||||
current_editor_set_->dungeon_editor_.set_active(true);
|
||||
// Set the dungeon editor to the jump to tab
|
||||
current_editor_set_->dungeon_editor_.add_room(
|
||||
overworld_editor.jump_to_tab());
|
||||
overworld_editor.jump_to_tab_ = -1;
|
||||
|
||||
// Check if current ROM is valid
|
||||
if (!current_rom_) {
|
||||
DrawWelcomeScreen();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
// Check if any editors are active across ALL sessions
|
||||
bool any_editor_active = false;
|
||||
for (const auto& session : sessions_) {
|
||||
if (!session.rom.is_loaded()) continue;
|
||||
for (auto editor : session.editors.active_editors_) {
|
||||
if (*editor->active()) {
|
||||
any_editor_active = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (any_editor_active) break;
|
||||
}
|
||||
|
||||
// Show welcome screen if no editors are active (ROM loaded but editors not opened)
|
||||
if (!any_editor_active) {
|
||||
DrawWelcomeScreen();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
// Iterate through ALL sessions to support multi-session docking
|
||||
for (size_t session_idx = 0; session_idx < sessions_.size(); ++session_idx) {
|
||||
auto& session = sessions_[session_idx];
|
||||
if (!session.rom.is_loaded()) continue; // Skip sessions with invalid ROMs
|
||||
|
||||
for (auto editor : session.editors.active_editors_) {
|
||||
if (*editor->active()) {
|
||||
if (editor->type() == EditorType::kOverworld) {
|
||||
auto &overworld_editor = static_cast<OverworldEditor &>(*editor);
|
||||
if (overworld_editor.jump_to_tab() != -1) {
|
||||
session.editors.dungeon_editor_.set_active(true);
|
||||
// Set the dungeon editor to the jump to tab
|
||||
session.editors.dungeon_editor_.add_room(overworld_editor.jump_to_tab());
|
||||
overworld_editor.jump_to_tab_ = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate unique window titles for multi-session support
|
||||
size_t session_index = GetCurrentSessionIndex();
|
||||
std::string window_title = GenerateUniqueEditorTitle(editor->type(), session_index);
|
||||
|
||||
if (ImGui::Begin(window_title.c_str(), editor->active())) {
|
||||
current_editor_ = editor;
|
||||
status_ = editor->Update();
|
||||
// Generate unique window titles for multi-session support
|
||||
std::string window_title = GenerateUniqueEditorTitle(editor->type(), session_idx);
|
||||
|
||||
if (ImGui::Begin(window_title.c_str(), editor->active())) {
|
||||
// Temporarily switch context for this editor's update
|
||||
Rom* prev_rom = current_rom_;
|
||||
EditorSet* prev_editor_set = current_editor_set_;
|
||||
|
||||
current_rom_ = &session.rom;
|
||||
current_editor_set_ = &session.editors;
|
||||
current_editor_ = editor;
|
||||
|
||||
status_ = editor->Update();
|
||||
|
||||
// Restore context
|
||||
current_rom_ = prev_rom;
|
||||
current_editor_set_ = prev_editor_set;
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
return absl::OkStatus();
|
||||
@@ -1424,6 +1463,26 @@ size_t EditorManager::GetCurrentSessionIndex() const {
|
||||
return 0; // Default to first session if not found
|
||||
}
|
||||
|
||||
std::string EditorManager::GenerateUniqueEditorTitle(EditorType type, size_t session_index) const {
|
||||
const char* base_name = kEditorNames[static_cast<int>(type)];
|
||||
|
||||
if (sessions_.size() <= 1) {
|
||||
// Single session - use simple name
|
||||
return std::string(base_name);
|
||||
}
|
||||
|
||||
// Multi-session - include session identifier
|
||||
const auto& session = sessions_[session_index];
|
||||
std::string session_name = session.GetDisplayName();
|
||||
|
||||
// Truncate long session names
|
||||
if (session_name.length() > 20) {
|
||||
session_name = session_name.substr(0, 17) + "...";
|
||||
}
|
||||
|
||||
return absl::StrFormat("%s - %s##session_%zu", base_name, session_name, session_index);
|
||||
}
|
||||
|
||||
// Layout Management Functions
|
||||
void EditorManager::ResetWorkspaceLayout() {
|
||||
// Show confirmation popup first
|
||||
@@ -1820,5 +1879,172 @@ void EditorManager::DrawSessionRenameDialog() {
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void EditorManager::DrawWelcomeScreen() {
|
||||
ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f));
|
||||
ImGui::SetNextWindowSize(ImVec2(680, 500), ImGuiCond_Always);
|
||||
|
||||
ImGuiWindowFlags flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar;
|
||||
|
||||
if (ImGui::Begin("Welcome to Yaze", nullptr, flags)) {
|
||||
// Header (reuse homepage style)
|
||||
TextWrapped("Welcome to the Yet Another Zelda3 Editor (yaze)!");
|
||||
TextWrapped("The Legend of Zelda: A Link to the Past.");
|
||||
|
||||
// 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", sessions_.size());
|
||||
} else {
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
TextWrapped("No ROM loaded.");
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
// Primary actions with material design icons
|
||||
ImGui::Text("Get Started:");
|
||||
ImGui::Spacing();
|
||||
|
||||
if (ImGui::Button(ICON_MD_FILE_OPEN " Open ROM File", ImVec2(180, 35))) {
|
||||
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(180, 35))) {
|
||||
auto file_name = core::FileDialogWrapper::ShowOpenFileDialog();
|
||||
if (!file_name.empty()) {
|
||||
status_ = OpenRomOrProject(file_name);
|
||||
if (!status_.ok()) {
|
||||
toast_manager_.Show(std::string(status_.message()), editor::ToastType::kError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
static RecentFilesManager manager("recent_files.txt");
|
||||
manager.Load();
|
||||
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");
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
|
||||
} // namespace editor
|
||||
} // namespace yaze
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "app/editor/system/toast_manager.h"
|
||||
#include "app/editor/system/settings_editor.h"
|
||||
#include "app/emu/emulator.h"
|
||||
#include "app/core/features.h"
|
||||
#include "app/rom.h"
|
||||
#include "yaze_config.h"
|
||||
|
||||
@@ -99,9 +100,19 @@ class EditorManager {
|
||||
absl::Status SetCurrentRom(Rom* rom);
|
||||
auto GetCurrentRom() -> Rom* { return current_rom_; }
|
||||
auto GetCurrentEditorSet() -> EditorSet* { return current_editor_set_; }
|
||||
|
||||
// Get current session's feature flags (falls back to global if no session)
|
||||
core::FeatureFlags::Flags* GetCurrentFeatureFlags() {
|
||||
size_t current_index = GetCurrentSessionIndex();
|
||||
if (current_index < sessions_.size()) {
|
||||
return &sessions_[current_index].feature_flags;
|
||||
}
|
||||
return &core::FeatureFlags::get(); // Fallback to global
|
||||
}
|
||||
|
||||
private:
|
||||
void DrawHomepage();
|
||||
void DrawWelcomeScreen();
|
||||
absl::Status DrawRomSelector();
|
||||
absl::Status LoadRom();
|
||||
absl::Status LoadAssets();
|
||||
@@ -160,11 +171,14 @@ class EditorManager {
|
||||
EditorSet editors;
|
||||
std::string custom_name; // User-defined session name
|
||||
std::string filepath; // ROM filepath for duplicate detection
|
||||
core::FeatureFlags::Flags feature_flags; // Per-session feature flags
|
||||
|
||||
RomSession() = default;
|
||||
explicit RomSession(Rom&& r)
|
||||
: rom(std::move(r)), editors(&rom) {
|
||||
filepath = rom.filename();
|
||||
// Initialize with default feature flags
|
||||
feature_flags = core::FeatureFlags::Flags{};
|
||||
}
|
||||
|
||||
// Get display name (custom name or ROM title)
|
||||
@@ -201,6 +215,9 @@ class EditorManager {
|
||||
void SwitchToSession(size_t index);
|
||||
size_t GetCurrentSessionIndex() const;
|
||||
void ResetWorkspaceLayout();
|
||||
|
||||
// Multi-session editor management
|
||||
std::string GenerateUniqueEditorTitle(EditorType type, size_t session_index) const;
|
||||
void SaveWorkspaceLayout();
|
||||
void LoadWorkspaceLayout();
|
||||
void ShowAllWindows();
|
||||
|
||||
42
src/app/editor/editor_safeguards.h
Normal file
42
src/app/editor/editor_safeguards.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#ifndef YAZE_APP_EDITOR_SAFEGUARDS_H
|
||||
#define YAZE_APP_EDITOR_SAFEGUARDS_H
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace editor {
|
||||
|
||||
// Macro for checking ROM loading state in editor methods
|
||||
#define REQUIRE_ROM_LOADED(rom_ptr, operation) \
|
||||
do { \
|
||||
if (!(rom_ptr) || !(rom_ptr)->is_loaded()) { \
|
||||
return absl::FailedPreconditionError( \
|
||||
absl::StrFormat("%s: ROM not loaded", (operation))); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
// Macro for ROM state checking with custom error message
|
||||
#define CHECK_ROM_STATE(rom_ptr, message) \
|
||||
do { \
|
||||
if (!(rom_ptr) || !(rom_ptr)->is_loaded()) { \
|
||||
return absl::FailedPreconditionError(message); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
// Helper function for generating consistent ROM status messages
|
||||
inline std::string GetRomStatusMessage(const Rom* rom) {
|
||||
if (!rom) return "No ROM loaded";
|
||||
if (!rom->is_loaded()) return "ROM failed to load";
|
||||
return absl::StrFormat("ROM loaded: %s", rom->title());
|
||||
}
|
||||
|
||||
// Helper function to check if ROM is in a valid state for editing
|
||||
inline bool IsRomReadyForEditing(const Rom* rom) {
|
||||
return rom && rom->is_loaded() && !rom->title().empty();
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EDITOR_SAFEGUARDS_H
|
||||
@@ -164,6 +164,10 @@ void OverworldEditor::Initialize() {
|
||||
}
|
||||
|
||||
absl::Status OverworldEditor::Load() {
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return absl::FailedPreconditionError("ROM not loaded");
|
||||
}
|
||||
|
||||
RETURN_IF_ERROR(LoadGraphics());
|
||||
RETURN_IF_ERROR(
|
||||
tile16_editor_.Initialize(tile16_blockset_bmp_, current_gfx_bmp_,
|
||||
|
||||
@@ -106,6 +106,14 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
|
||||
int jump_to_tab() { return jump_to_tab_; }
|
||||
int jump_to_tab_ = -1;
|
||||
|
||||
// ROM state methods (from Editor base class)
|
||||
bool IsRomLoaded() const override { return rom_ && rom_->is_loaded(); }
|
||||
std::string GetRomStatus() const override {
|
||||
if (!rom_) return "No ROM loaded";
|
||||
if (!rom_->is_loaded()) return "ROM failed to load";
|
||||
return absl::StrFormat("ROM loaded: %s", rom_->title());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Load the Bitmap objects for each OverworldMap.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user