167 lines
5.0 KiB
C++
167 lines
5.0 KiB
C++
#include "AutoScreenshot.h"
|
|
|
|
#include "NativeRenderer.h"
|
|
|
|
#include <chrono>
|
|
#include <cctype>
|
|
#include <cstdio>
|
|
#include <sstream>
|
|
#include <system_error>
|
|
|
|
namespace XCEngine::UI::Editor::Host {
|
|
|
|
void AutoScreenshotController::Initialize(const std::filesystem::path& captureRoot) {
|
|
m_captureRoot = captureRoot.lexically_normal();
|
|
m_historyRoot = (m_captureRoot / "history").lexically_normal();
|
|
m_latestCapturePath = (m_captureRoot / "latest.png").lexically_normal();
|
|
m_captureCount = 0;
|
|
m_capturePending = false;
|
|
m_pendingReason.clear();
|
|
m_lastCaptureSummary.clear();
|
|
m_lastCaptureError.clear();
|
|
}
|
|
|
|
void AutoScreenshotController::Shutdown() {
|
|
m_capturePending = false;
|
|
m_pendingReason.clear();
|
|
}
|
|
|
|
void AutoScreenshotController::RequestCapture(std::string reason) {
|
|
m_pendingReason = reason.empty() ? "capture" : std::move(reason);
|
|
m_capturePending = true;
|
|
}
|
|
|
|
void AutoScreenshotController::CaptureIfRequested(
|
|
NativeRenderer& renderer,
|
|
const ::XCEngine::UI::UIDrawData& drawData,
|
|
unsigned int width,
|
|
unsigned int height,
|
|
bool framePresented) {
|
|
if (!m_capturePending || !framePresented || drawData.Empty() || width == 0u || height == 0u) {
|
|
return;
|
|
}
|
|
|
|
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";
|
|
m_capturePending = false;
|
|
return;
|
|
}
|
|
|
|
std::filesystem::create_directories(m_historyRoot, errorCode);
|
|
if (errorCode) {
|
|
m_lastCaptureError = "Failed to create screenshot directory: " + m_historyRoot.string();
|
|
m_lastCaptureSummary = "AutoShot failed";
|
|
m_capturePending = false;
|
|
return;
|
|
}
|
|
|
|
std::string captureError = {};
|
|
const std::filesystem::path historyPath = BuildHistoryCapturePath(m_pendingReason);
|
|
if (!renderer.CaptureToPng(drawData, width, height, historyPath, captureError)) {
|
|
m_lastCaptureError = std::move(captureError);
|
|
m_lastCaptureSummary = "AutoShot failed";
|
|
m_capturePending = false;
|
|
return;
|
|
}
|
|
|
|
errorCode.clear();
|
|
std::filesystem::copy_file(
|
|
historyPath,
|
|
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";
|
|
m_capturePending = false;
|
|
return;
|
|
}
|
|
|
|
++m_captureCount;
|
|
m_lastCaptureError.clear();
|
|
m_lastCaptureSummary =
|
|
"Shot: latest.png | " + historyPath.filename().string();
|
|
m_capturePending = false;
|
|
m_pendingReason.clear();
|
|
}
|
|
|
|
bool AutoScreenshotController::HasPendingCapture() const {
|
|
return m_capturePending;
|
|
}
|
|
|
|
const std::filesystem::path& AutoScreenshotController::GetLatestCapturePath() const {
|
|
return m_latestCapturePath;
|
|
}
|
|
|
|
const std::string& AutoScreenshotController::GetLastCaptureSummary() const {
|
|
return m_lastCaptureSummary;
|
|
}
|
|
|
|
const std::string& AutoScreenshotController::GetLastCaptureError() const {
|
|
return m_lastCaptureError;
|
|
}
|
|
|
|
std::filesystem::path AutoScreenshotController::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 AutoScreenshotController::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 AutoScreenshotController::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::Host
|