Files
yaze/src/app/controller.cc

239 lines
7.8 KiB
C++

#include "controller.h"
#include "app/platform/sdl_compat.h"
#include <string>
#include "absl/status/status.h"
#include "app/editor/editor_manager.h"
#include "app/gfx/backend/renderer_factory.h"
#include "app/gfx/resource/arena.h"
#include "app/gui/automation/widget_id_registry.h"
#include "app/gui/core/background_renderer.h"
#include "app/gui/core/theme_manager.h"
#include "app/emu/emulator.h"
#include "app/platform/iwindow.h"
#include "app/platform/timing.h"
#include "app/service/screenshot_utils.h"
#include "imgui/imgui.h"
namespace yaze {
absl::Status Controller::OnEntry(std::string filename) {
// Create window backend using factory (auto-selects SDL2 or SDL3)
window_backend_ = platform::WindowBackendFactory::Create(
platform::WindowBackendFactory::GetDefaultType());
platform::WindowConfig config;
config.title = "Yet Another Zelda3 Editor";
config.resizable = true;
config.high_dpi = false; // Disabled to match legacy behavior (SDL_WINDOW_RESIZABLE only)
RETURN_IF_ERROR(window_backend_->Initialize(config));
// Create renderer via factory (auto-selects SDL2 or SDL3)
renderer_ = gfx::RendererFactory::Create();
if (!window_backend_->InitializeRenderer(renderer_.get())) {
return absl::InternalError("Failed to initialize renderer");
}
// Initialize ImGui via backend (handles SDL2/SDL3 automatically)
RETURN_IF_ERROR(window_backend_->InitializeImGui(renderer_.get()));
// Initialize the graphics Arena with the renderer
gfx::Arena::Get().Initialize(renderer_.get());
// Set up audio for emulator (using backend's audio resources)
auto audio_buffer = window_backend_->GetAudioBuffer();
if (audio_buffer) {
editor_manager_.emulator().set_audio_buffer(audio_buffer.get());
}
editor_manager_.emulator().set_audio_device_id(window_backend_->GetAudioDevice());
// Initialize editor manager with renderer
editor_manager_.Initialize(renderer_.get(), filename);
active_ = true;
return absl::OkStatus();
}
void Controller::SetStartupEditor(const std::string& editor_name,
const std::string& panels) {
// Process command-line flags for editor and panels
// Example: --editor=Dungeon --open_panels="dungeon.room_list,Room 0"
if (!editor_name.empty()) {
editor_manager_.OpenEditorAndPanelsFromFlags(editor_name, panels);
}
}
void Controller::OnInput() {
if (!window_backend_) return;
platform::WindowEvent event;
while (window_backend_->PollEvent(event)) {
switch (event.type) {
case platform::WindowEventType::Quit:
case platform::WindowEventType::Close:
active_ = false;
break;
default:
// Other events are handled by ImGui via ProcessNativeEvent
// which is called inside PollEvent
break;
}
// Forward native SDL events to emulator input for event-based paths
if (event.has_native_event) {
editor_manager_.emulator().input_manager().ProcessEvent(
static_cast<void*>(&event.native_event));
}
}
}
absl::Status Controller::OnLoad() {
if (!window_backend_) {
return absl::InternalError("Window backend not initialized");
}
if (editor_manager_.quit() || !window_backend_->IsActive()) {
active_ = false;
return absl::OkStatus();
}
#if TARGET_OS_IPHONE != 1
// Start new ImGui frame via backend (handles SDL2/SDL3 automatically)
window_backend_->NewImGuiFrame();
ImGui::NewFrame();
const ImGuiViewport* viewport = ImGui::GetMainViewport();
// Calculate layout offsets for sidebars and status bar
const float left_offset = editor_manager_.GetLeftLayoutOffset();
const float right_offset = editor_manager_.GetRightLayoutOffset();
const float bottom_offset = editor_manager_.GetBottomLayoutOffset();
// Adjust dockspace position and size for sidebars and status bar
ImVec2 dockspace_pos = viewport->WorkPos;
ImVec2 dockspace_size = viewport->WorkSize;
dockspace_pos.x += left_offset;
dockspace_size.x -= (left_offset + right_offset);
dockspace_size.y -= bottom_offset; // Reserve space for status bar at bottom
ImGui::SetNextWindowPos(dockspace_pos);
ImGui::SetNextWindowSize(dockspace_size);
ImGui::SetNextWindowViewport(viewport->ID);
// Check if menu bar should be visible (WASM can hide it for clean UI)
bool show_menu_bar = true;
if (editor_manager_.ui_coordinator()) {
show_menu_bar = editor_manager_.ui_coordinator()->IsMenuBarVisible();
}
ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDocking;
if (show_menu_bar) {
window_flags |= ImGuiWindowFlags_MenuBar;
}
window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
window_flags |=
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus |
ImGuiWindowFlags_NoBackground;
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::Begin("DockSpaceWindow", nullptr, window_flags);
ImGui::PopStyleVar(3);
// Create DockSpace with adjusted size
ImGuiID dockspace_id = ImGui::GetID("MainDockSpace");
gui::DockSpaceRenderer::BeginEnhancedDockSpace(
dockspace_id, ImVec2(0.0f, 0.0f), ImGuiDockNodeFlags_PassthruCentralNode);
if (show_menu_bar) {
editor_manager_.DrawMenuBar(); // Draw the fixed menu bar at the top
}
gui::DockSpaceRenderer::EndEnhancedDockSpace();
ImGui::End();
// Draw menu bar restore button when menu is hidden (WASM)
if (!show_menu_bar && editor_manager_.ui_coordinator()) {
editor_manager_.ui_coordinator()->DrawMenuBarRestoreButton();
}
#endif
gui::WidgetIdRegistry::Instance().BeginFrame();
absl::Status update_status = editor_manager_.Update();
gui::WidgetIdRegistry::Instance().EndFrame();
RETURN_IF_ERROR(update_status);
return absl::OkStatus();
}
void Controller::DoRender() const {
if (!window_backend_ || !renderer_) return;
// Process pending texture commands (max 8 per frame for consistent performance)
gfx::Arena::Get().ProcessTextureQueue(renderer_.get());
renderer_->Clear();
// Render ImGui draw data and handle viewports via backend
window_backend_->RenderImGui(renderer_.get());
renderer_->Present();
// Process any pending screenshot requests on the main thread after present
ProcessScreenshotRequests();
// Get delta time AFTER render for accurate measurement
float delta_time = TimingManager::Get().Update();
// Gentle frame rate cap to prevent excessive CPU usage
// Only delay if we're rendering faster than 144 FPS (< 7ms per frame)
if (delta_time < 0.007f) {
SDL_Delay(1); // Tiny delay to yield CPU without affecting ImGui timing
}
}
void Controller::OnExit() {
if (renderer_) {
renderer_->Shutdown();
}
if (window_backend_) {
window_backend_->Shutdown();
}
}
absl::Status Controller::LoadRomForTesting(const std::string& rom_path) {
// Use EditorManager's OpenRomOrProject which handles the full initialization:
// 1. Load ROM file into session
// 2. ConfigureEditorDependencies()
// 3. LoadAssets() - initializes all editors and loads graphics
// 4. Updates UI state (hides welcome screen, etc.)
return editor_manager_.OpenRomOrProject(rom_path);
}
void Controller::RequestScreenshot(const ScreenshotRequest& request) {
std::lock_guard<std::mutex> lock(screenshot_mutex_);
screenshot_requests_.push(request);
}
void Controller::ProcessScreenshotRequests() const {
#ifdef YAZE_WITH_GRPC
std::lock_guard<std::mutex> lock(screenshot_mutex_);
while (!screenshot_requests_.empty()) {
auto request = screenshot_requests_.front();
screenshot_requests_.pop();
// Perform capture on main thread
auto result = test::CaptureHarnessScreenshot(request.preferred_path);
if (request.callback) {
request.callback(result);
}
}
#endif
}
} // namespace yaze