7#include <TargetConditionals.h>
16#include "absl/status/status.h"
30#include "imgui/imgui.h"
31#include "imgui/imgui_internal.h"
33#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE
35#include "imgui_test_engine/imgui_te_engine.h"
37#if defined(__APPLE__) && \
38 (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
69 std::chrono::steady_clock::time_point
start;
77 if (
const char* path = std::getenv(
"YAZE_DEBUG_INPUT_LOG");
78 path !=
nullptr && path[0] !=
'\0') {
79 d->log = std::fopen(path,
"a");
81 if (
const char* path = std::getenv(
"YAZE_DEBUG_INPUT_SCRIPT");
82 path !=
nullptr && path[0] !=
'\0') {
83 if (FILE* f = std::fopen(path,
"r")) {
87 while (std::fscanf(f,
"%d %31s %f %f", &at, action, &x, &y) == 4) {
88 d->script.push_back({at, action, x, y});
100 return std::chrono::duration<double, std::milli>(
101 std::chrono::steady_clock::now() - d.start)
108 if (d.log ==
nullptr) {
112 case SDL_MOUSEBUTTONDOWN:
113 case SDL_MOUSEBUTTONUP:
114 std::fprintf(d.log,
"%9.1f sdl %s btn=%d x=%d y=%d win=%u clicks=%d\n",
116 e.type == SDL_MOUSEBUTTONDOWN ?
"mousedown" :
"mouseup",
117 e.button.button, e.button.x, e.button.y, e.button.windowID,
122 std::fprintf(d.log,
"%9.1f sdl %s sym=0x%x mod=0x%x\n",
InputDebugMs(),
123 e.type == SDL_KEYDOWN ?
"keydown" :
"keyup",
124 e.key.keysym.sym, e.key.keysym.mod);
126 case SDL_WINDOWEVENT:
127 std::fprintf(d.log,
"%9.1f sdl window event=%d\n",
InputDebugMs(),
137 if (d.next_step >= d.script.size()) {
140 const ImGuiViewport* vp = ImGui::GetMainViewport();
141 const Uint32 window_id =
142 static_cast<Uint32
>(
reinterpret_cast<intptr_t
>(vp->PlatformHandle));
143 while (d.next_step < d.script.size() &&
145 const auto& step = d.script[d.next_step++];
147 const int x =
static_cast<int>(step.x);
148 const int y =
static_cast<int>(step.y);
149 if (step.action ==
"move") {
150 ev.type = SDL_MOUSEMOTION;
151 ev.motion.windowID = window_id;
155 const bool down = step.action ==
"ldown" || step.action ==
"rdown";
156 ev.type = down ? SDL_MOUSEBUTTONDOWN : SDL_MOUSEBUTTONUP;
157 ev.button.windowID = window_id;
159 step.action[0] ==
'r' ? SDL_BUTTON_RIGHT : SDL_BUTTON_LEFT;
160 ev.button.state = down ? SDL_PRESSED : SDL_RELEASED;
161 ev.button.clicks = 1;
166 if (d.log !=
nullptr) {
167 std::fprintf(d.log,
"%9.1f script %s %d %d\n",
InputDebugMs(),
168 step.action.c_str(), x, y);
176 if (d.log ==
nullptr) {
179 const ImGuiContext& g = *ImGui::GetCurrentContext();
180 const ImGuiIO& io = g.IO;
182 for (
const ImGuiPopupData& p : g.OpenPopupStack) {
184 std::snprintf(
id,
sizeof(
id),
"(pending id=%08X parent=%08X f=%d)",
185 p.PopupId, p.OpenParentId, p.OpenFrameCount);
186 popups += p.Window ? p.Window->Name : id;
192 "down=%d%d%d clk=%d%d rel=%d%d pos=%.0f,%.0f hovwin=%s hovid=%08X "
193 "active=%08X(%s) nav=%s text=%d mods=0x%x focus=%d popups=[%s]",
194 io.MouseDown[0], io.MouseDown[1], io.MouseDown[2], io.MouseClicked[0],
195 io.MouseClicked[1], io.MouseReleased[0], io.MouseReleased[1],
196 io.MousePos.x, io.MousePos.y,
197 g.HoveredWindow ? g.HoveredWindow->Name :
"-", g.HoveredIdPreviousFrame,
198 g.ActiveId, g.ActiveIdWindow ? g.ActiveIdWindow->Name :
"-",
199 g.NavWindow ? g.NavWindow->Name :
"-", io.WantTextInput ? 1 : 0,
200 static_cast<unsigned>(io.KeyMods), io.AppFocusLost ? 0 : 1,
202 const bool edge = io.MouseClicked[0] || io.MouseClicked[1] ||
203 io.MouseReleased[0] || io.MouseReleased[1];
204 if (edge || d.last_frame_line != line) {
205 std::fprintf(d.log,
"%9.1f frame=%d %s\n",
InputDebugMs(), g.FrameCount,
208 d.last_frame_line = line;
221 if (app_config.headless) {
222 LOG_INFO(
"Controller",
"Using Null Window Backend (Headless Mode)");
227#if defined(__APPLE__) && \
228 (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
238 return absl::InternalError(
"Failed to create window backend");
242 config.
title =
"Yet Another Zelda3 Editor";
247 if (app_config.service_mode) {
248 LOG_INFO(
"Controller",
"Starting in Service Mode (Hidden Window)");
259 ui_queue.BindToCurrentThread();
266 return absl::InternalError(
"Failed to initialize renderer");
276#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE && \
277 !defined(YAZE_GUI_TEST_TARGET)
301 mgr->RegisterLayoutIniHandler();
305 return absl::OkStatus();
309 const std::string& panels) {
312 if (!editor_name.empty()) {
336#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE
338 busy = busy || tests.IsTestRunning();
341 if (
auto* engine = tests.GetUITestEngine()) {
342 busy = busy || !ImGuiTestEngine_IsTestQueueEmpty(engine);
362 auto& debug = GetInputDebug();
363 if (!debug.started) {
364 debug.started =
true;
365 debug.start = std::chrono::steady_clock::now();
368 if (!debug.script.empty() && ImGui::GetCurrentContext() !=
nullptr) {
369 InputDebugRunScript();
388 switch (event.
type) {
430 const std::string& json) {
436 return absl::InternalError(
"Window backend not initialized");
445 return absl::OkStatus();
458 InputDebugLogFrame();
466 const ImGuiViewport* viewport = ImGui::GetMainViewport();
473 float top_offset = 0.0f;
474#if defined(__APPLE__) && \
475 (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
481 bottom_offset += safe.bottom;
486 ImVec2 dockspace_pos = viewport->WorkPos;
487 ImVec2 dockspace_size = viewport->WorkSize;
489 dockspace_pos.x += left_offset;
490 dockspace_pos.y += top_offset;
491 dockspace_size.x -= (left_offset + right_offset);
492 dockspace_size.y -= (bottom_offset + top_offset);
494 ImGui::SetNextWindowPos(dockspace_pos);
495 ImGui::SetNextWindowSize(dockspace_size);
496 ImGui::SetNextWindowViewport(viewport->ID);
499 bool show_menu_bar =
true;
503#if defined(__APPLE__) && \
504 (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
505 show_menu_bar =
false;
508 ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDocking;
510 window_flags |= ImGuiWindowFlags_MenuBar;
512 window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse |
513 ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
514 window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus |
515 ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoBackground;
517 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
518 ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
519 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
520 ImGui::Begin(
"DockSpaceWindow",
nullptr, window_flags);
521 ImGui::PopStyleVar(3);
528 if (
auto* mgr = layout_manager) {
529 mgr->SetMainDockspaceId(dockspace_id);
536 (void)mgr->MaybeReapplyStartupLayout(
542 dockspace_id, ImVec2(0.0f, 0.0f),
543 ImGuiDockNodeFlags_PassthruCentralNode |
544 ImGuiDockNodeFlags_NoDockingSplit);
557 if (layout_manager) {
561#if !defined(__APPLE__) || \
562 (TARGET_OS_IPHONE != 1 && TARGET_IPHONE_SIMULATOR != 1)
572#if defined(__APPLE__) && \
573 (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
579 snap.
can_undo = editor->undo_manager().CanUndo();
580 snap.
can_redo = editor->undo_manager().CanRedo();
593 return absl::OkStatus();
607 sync_state.pending_layout_actions > 0;
608 if (in_transition && (sync_state.frame_id % 30 != 0)) {
635#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE
643 const auto frame_end = std::chrono::steady_clock::now();
646 if (frame_time < std::chrono::milliseconds(7)) {
647#if TARGET_OS_IPHONE != 1
654#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE && \
655 !defined(YAZE_GUI_TEST_TARGET)
673 ui_queue.SetWaker({});
679#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE && \
680 !defined(YAZE_GUI_TEST_TARGET)
702 std::queue<ScreenshotRequest> requests;
707 while (!requests.empty()) {
708 auto request = std::move(requests.front());
711 absl::StatusOr<test::ScreenshotArtifact> result =
712 absl::FailedPreconditionError(
713 "Screenshots require a rendered GUI frame; use service mode "
714 "instead of headless mode");
716 result = request.window_title.empty()
717 ? test::CaptureHarnessScreenshot(request.preferred_path,
718 request.reveal_to_user,
720 : test::CaptureWindowByName(
721 request.window_title, request.preferred_path,
722 request.reveal_to_user, request.format);
724 if (request.callback) {
725 request.callback(std::move(result));
static Application & Instance()
const AppConfig & GetConfig() const
std::chrono::steady_clock::time_point last_frame_end_
std::queue< ScreenshotRequest > screenshot_requests_
editor::EditorManager editor_manager_
absl::Status LoadRomForTesting(const std::string &rom_path)
absl::Status OnEntry(std::string filename="")
absl::StatusOr< std::string > RequestEditorControl(const std::string &request)
app::EditorControlQueue editor_control_queue_
std::chrono::steady_clock::time_point last_event_time_
void SetStartupEditor(const std::string &editor_name, const std::string &cards)
std::unique_ptr< platform::IWindowBackend > window_backend_
void ProcessScreenshotRequests() const
std::unique_ptr< gfx::IRenderer > renderer_
std::mutex screenshot_mutex_
void ProcessOne(const std::function< std::string(const std::string &)> &action)
absl::StatusOr< std::string > Submit(std::string json, std::chrono::milliseconds queue_timeout=std::chrono::seconds(5))
float GetRightLayoutOffset() const
Rom * GetCurrentRom() const override
UICoordinator * ui_coordinator()
void Initialize(gfx::IRenderer *renderer, const std::string &filename="")
float GetBottomLayoutOffset() const
auto GetCurrentEditor() const -> Editor *override
absl::Status OpenRomOrProject(const std::string &filename, LoadOptionsPrompt prompt=LoadOptionsPrompt::kAsk)
void HandleHostVisibilityChanged(bool visible)
bool WantsContinuousFrames() const
void SetAssetLoadMode(AssetLoadMode mode)
void OpenEditorAndPanelsFromFlags(const std::string &editor_name, const std::string &panels_str)
absl::Status Update()
Main update loop for the editor application.
std::string ExecuteEditorControl(const std::string &request)
AssetLoadMode asset_load_mode() const
auto emulator() -> emu::Emulator &
UiSyncState GetUiSyncStateSnapshot() const
float GetLeftLayoutOffset() const
void DrawEditorHost()
Draw the editor host window and enforce the pin node.
static ImGuiID OuterDockspaceId()
Outer dockspace hosted by DockSpaceWindow.
bool IsMenuBarVisible() const
void DrawMenuBarRestoreButton()
void Initialize(IRenderer *renderer)
bool ProcessTextureQueueWithBudget(IRenderer *renderer, float budget_ms)
Process texture queue with a time budget.
size_t DrainRetiredBitmaps(IRenderer *renderer)
Destroy texture handles retired earlier in the frame.
void ProcessTextureQueue(IRenderer *renderer)
static RendererBackendType GetDefaultBackendType()
static std::unique_ptr< IRenderer > Create(RendererBackendType type=RendererBackendType::kDefault)
static void BeginEnhancedDockSpace(ImGuiID dockspace_id, const ImVec2 &size=ImVec2(0, 0), ImGuiDockNodeFlags flags=0)
static void EndEnhancedDockSpace()
static ThemeManager & Get()
void DestroyUITestingContext()
static TestManager & Get()
void InitializeUITesting()
static LogManager & instance()
void Flush()
Writes buffered file output now. log() flushes warnings and errors at once but batches other lines fo...
static UiThreadQueue & Get()
bool HasPendingTasks() const
void SetWaker(std::function< void()> waker)
#define LOG_INFO(category, format,...)
void InputDebugLogSdlEvent(const SDL_Event &e)
InputDebug & GetInputDebug()
void InputDebugRunScript()
void InputDebugLogFrame()
::yaze::EventBus * event_bus()
Get the current EventBus instance.
UserSettings * user_settings()
Get the current UserSettings instance.
LayoutManager * layout_manager()
Get the shared LayoutManager instance.
constexpr std::array< const char *, 15 > kEditorNames
size_t EditorTypeIndex(EditorType type)
@ Null
Null renderer for headless/server mode.
@ Metal
Metal renderer backend (Apple platforms)
int MainLoopEventWaitMs(const MainLoopIdleState &state)
Returns how long the frame loop may block waiting for an event.
SDL2/SDL3 compatibility layer.
#define RETURN_IF_ERROR(expr)
bool layout_rebuild_pending
static FrameGuiBeginEvent Create(float dt)
Inputs for the frame loop's idle decision.
std::chrono::steady_clock::duration quiet_for