new_editor: close startup screenshot ownership
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
#include "Platform/Win32/EditorWindowScreenshotController.h"
|
||||
|
||||
#include "Support/ExecutablePath.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <sstream>
|
||||
#include <system_error>
|
||||
|
||||
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<char>(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
|
||||
Reference in New Issue
Block a user