#include "Platform/Win32/EditorWindowScreenshotController.h" #include "Support/ExecutablePath.h" #include #include #include #include namespace XCEngine::UI::Editor::App { namespace { std::filesystem::path ResolveBuildCaptureRoot(const std::filesystem::path& requestedCaptureRoot) { std::filesystem::path captureRoot = App::GetExecutableDirectory() / "captures"; const std::filesystem::path scenarioPath = requestedCaptureRoot.parent_path().filename(); if (!scenarioPath.empty() && scenarioPath != "captures") { captureRoot /= scenarioPath; } return captureRoot.lexically_normal(); } } // namespace void EditorWindowScreenshotController::Initialize(const std::filesystem::path& captureRoot) { m_captureRoot = ResolveBuildCaptureRoot(captureRoot); m_historyRoot = (m_captureRoot / "history").lexically_normal(); m_latestCapturePath = (m_captureRoot / "latest.png").lexically_normal(); m_activeHistoryCapturePath.clear(); m_captureCount = 0; m_capturePending = false; m_pendingReason.clear(); m_lastCaptureSummary = "Output: " + m_captureRoot.string(); m_lastCaptureError.clear(); } void EditorWindowScreenshotController::Shutdown() { m_activeHistoryCapturePath.clear(); m_capturePending = false; m_pendingReason.clear(); } void EditorWindowScreenshotController::RequestCapture(std::string reason) { m_pendingReason = reason.empty() ? "capture" : std::move(reason); m_capturePending = true; } bool EditorWindowScreenshotController::TryBeginCapture(std::filesystem::path& outHistoryPath) { outHistoryPath.clear(); if (!m_capturePending) { return false; } std::error_code errorCode = {}; std::filesystem::create_directories(m_captureRoot, errorCode); if (errorCode) { m_lastCaptureError = "Failed to create screenshot directory: " + m_captureRoot.string(); m_lastCaptureSummary = "AutoShot failed"; ResetPendingRequest(); return false; } std::filesystem::create_directories(m_historyRoot, errorCode); if (errorCode) { m_lastCaptureError = "Failed to create screenshot directory: " + m_historyRoot.string(); m_lastCaptureSummary = "AutoShot failed"; ResetPendingRequest(); return false; } m_activeHistoryCapturePath = BuildHistoryCapturePath(m_pendingReason); outHistoryPath = m_activeHistoryCapturePath; return true; } void EditorWindowScreenshotController::CompleteCaptureSuccess( const std::filesystem::path& historyPath) { const std::filesystem::path resolvedHistoryPath = historyPath.empty() ? m_activeHistoryCapturePath : historyPath; if (resolvedHistoryPath.empty()) { CompleteCaptureFailure("Capture completed without a valid history path."); return; } std::error_code errorCode = {}; const std::uintmax_t historyFileSize = std::filesystem::file_size(resolvedHistoryPath, errorCode); if (errorCode || historyFileSize == 0u) { CompleteCaptureFailure("Capture completed without a valid PNG payload."); return; } errorCode.clear(); std::filesystem::copy_file( resolvedHistoryPath, m_latestCapturePath, std::filesystem::copy_options::overwrite_existing, errorCode); if (errorCode) { m_lastCaptureError = "Failed to update latest screenshot: " + m_latestCapturePath.string(); m_lastCaptureSummary = "AutoShot failed"; ResetPendingRequest(); return; } ++m_captureCount; m_lastCaptureError.clear(); m_lastCaptureSummary = "Shot: latest.png | " + resolvedHistoryPath.filename().string(); ResetPendingRequest(); } void EditorWindowScreenshotController::CompleteCaptureFailure(std::string error) { if (!m_activeHistoryCapturePath.empty()) { std::error_code deleteError = {}; std::filesystem::remove(m_activeHistoryCapturePath, deleteError); } m_lastCaptureError = error.empty() ? "Screenshot capture failed." : std::move(error); m_lastCaptureSummary = "AutoShot failed"; ResetPendingRequest(); } bool EditorWindowScreenshotController::HasPendingCapture() const { return m_capturePending; } const std::filesystem::path& EditorWindowScreenshotController::GetLatestCapturePath() const { return m_latestCapturePath; } const std::string& EditorWindowScreenshotController::GetLastCaptureSummary() const { return m_lastCaptureSummary; } const std::string& EditorWindowScreenshotController::GetLastCaptureError() const { return m_lastCaptureError; } void EditorWindowScreenshotController::ResetPendingRequest() { m_capturePending = false; m_pendingReason.clear(); m_activeHistoryCapturePath.clear(); } std::filesystem::path EditorWindowScreenshotController::BuildHistoryCapturePath( std::string_view reason) const { std::ostringstream filename; filename << BuildTimestampString() << '_' << (m_captureCount + 1u) << '_' << SanitizeReason(reason) << ".png"; return (m_historyRoot / filename.str()).lexically_normal(); } std::string EditorWindowScreenshotController::BuildTimestampString() { const auto now = std::chrono::system_clock::now(); const std::time_t currentTime = std::chrono::system_clock::to_time_t(now); std::tm localTime = {}; localtime_s(&localTime, ¤tTime); char buffer[32] = {}; std::snprintf( buffer, sizeof(buffer), "%04d%02d%02d_%02d%02d%02d", localTime.tm_year + 1900, localTime.tm_mon + 1, localTime.tm_mday, localTime.tm_hour, localTime.tm_min, localTime.tm_sec); return buffer; } std::string EditorWindowScreenshotController::SanitizeReason(std::string_view reason) { std::string sanitized = {}; sanitized.reserve(reason.size()); bool lastWasSeparator = false; for (const unsigned char value : reason) { if (std::isalnum(value)) { sanitized.push_back(static_cast(std::tolower(value))); lastWasSeparator = false; continue; } if (!lastWasSeparator) { sanitized.push_back('_'); lastWasSeparator = true; } } while (!sanitized.empty() && sanitized.front() == '_') { sanitized.erase(sanitized.begin()); } while (!sanitized.empty() && sanitized.back() == '_') { sanitized.pop_back(); } return sanitized.empty() ? "capture" : sanitized; } } // namespace XCEngine::UI::Editor::App