143 lines
5.8 KiB
C++
143 lines
5.8 KiB
C++
#pragma once
|
|
#include "Document/ProjectWorkspace.h"
|
|
#include "IO/ImageExporter.h"
|
|
#include "Rendering/EditorCanvas.h"
|
|
#include "UI/CompositorMenus.h"
|
|
#include "UI/OmarchyTheme.h"
|
|
#include "UI/ProjectWorkspaceView.h"
|
|
#include <QAbstractItemView>
|
|
#include <QComboBox>
|
|
#include <QDir>
|
|
#include <QPainter>
|
|
#include <QTemporaryDir>
|
|
#include <QtTest>
|
|
|
|
// The acceptance run drives the app's own window.
|
|
struct App {
|
|
QTemporaryDir files;
|
|
// As the app starts: the theme before the window.
|
|
OmarchyTheme theme{files.filePath(QStringLiteral("omarchy"))};
|
|
ProjectWorkspace workspace;
|
|
ProjectWorkspaceView window{workspace};
|
|
App()
|
|
{
|
|
window.resize(1180, 780);
|
|
window.show();
|
|
if (!QTest::qWaitForWindowActive(&window))
|
|
throw std::runtime_error("the window never became active");
|
|
}
|
|
EditorSession &session() { return workspace.current().session; }
|
|
CanvasView &canvas() { return *window.findChild<CanvasView *>(); }
|
|
QAction &action(const char *name)
|
|
{
|
|
QAction *found = window.findChild<CompositorMenus *>()->action(QString::fromLatin1(name));
|
|
if (!found)
|
|
throw std::runtime_error(std::string("no entry named ") + name);
|
|
return *found;
|
|
}
|
|
// A document point where the canvas shows it.
|
|
QPoint at(QPointF point)
|
|
{
|
|
return session().viewport.viewPoint(point, session().document().value().size()).toPoint();
|
|
}
|
|
void press(QPointF point, Qt::KeyboardModifiers modifiers = Qt::NoModifier)
|
|
{
|
|
QTest::mousePress(&canvas(), Qt::LeftButton, modifiers, at(point));
|
|
}
|
|
void move(QPointF point)
|
|
{
|
|
const QPointF view = at(point);
|
|
QMouseEvent event(QEvent::MouseMove, view, view, canvas().mapToGlobal(view.toPoint()), Qt::NoButton, Qt::LeftButton, Qt::NoModifier);
|
|
QApplication::sendEvent(&canvas(), &event);
|
|
}
|
|
void release(QPointF point) { QTest::mouseRelease(&canvas(), Qt::LeftButton, Qt::NoModifier, at(point)); }
|
|
// A drag in eight steps, as a hand makes it.
|
|
void drag(QPointF from, QPointF to)
|
|
{
|
|
press(from);
|
|
for (int step = 1; step <= 8; ++step)
|
|
move(from + (to - from) * (step / 8.0));
|
|
release(to);
|
|
}
|
|
// Waits for the project to be free again.
|
|
void settle() { QTRY_VERIFY_WITH_TIMEOUT(!session().isProjectBusy(), 30000); }
|
|
QString path(const QString &name) const { return files.filePath(name); }
|
|
// The window and its shown panels, saved when asked for.
|
|
void shot(const QString &name)
|
|
{
|
|
const QByteArray folder = qgetenv("ACCEPTANCE_SHOTS");
|
|
if (folder.isEmpty())
|
|
return;
|
|
// Posted geometry and previews land first.
|
|
QTest::qWait(300);
|
|
QImage image = window.grab().toImage();
|
|
QPainter painter(&image);
|
|
for (QWidget *panel : QApplication::topLevelWidgets())
|
|
if (panel != &window && panel->isVisible() && panel->parentWidget() && panel->parentWidget()->window() == &window)
|
|
painter.drawPixmap(panel->geometry().topLeft() - window.geometry().topLeft(), panel->grab());
|
|
painter.end();
|
|
QDir().mkpath(QString::fromLocal8Bit(folder));
|
|
if (!image.save(QDir(QString::fromLocal8Bit(folder)).filePath(name + QStringLiteral(".png"))))
|
|
throw std::runtime_error("the screenshot could not be written");
|
|
}
|
|
// The document as an export draws it.
|
|
QColor shown(int x, int y) { return ImageExporter::render(session().projectSnapshot().value()).image.pixelColor(x, y); }
|
|
void importFile(const QString &file)
|
|
{
|
|
bool done = false;
|
|
session().importImages({QUrl::fromLocalFile(file)}, std::nullopt, [&done] { done = true; });
|
|
QTRY_VERIFY_WITH_TIMEOUT(done, 30000);
|
|
}
|
|
};
|
|
|
|
// A combo box popup row, where the pointer finds it.
|
|
inline QPoint popupRow(QComboBox &box, const QString &text)
|
|
{
|
|
box.showPopup();
|
|
if (!QTest::qWaitFor([&box] { return box.view()->isVisible(); }, 2000))
|
|
throw std::runtime_error("the menu never opened");
|
|
const int row = box.findText(text);
|
|
if (row < 0)
|
|
throw std::runtime_error("no such entry");
|
|
return box.view()->visualRect(box.model()->index(row, 0)).center();
|
|
}
|
|
|
|
// Chooses an entry with a click in its popup.
|
|
inline void choose(QComboBox &box, const QString &text)
|
|
{
|
|
const QPoint row = popupRow(box, text);
|
|
QTest::mouseClick(box.view()->viewport(), Qt::LeftButton, Qt::NoModifier, row);
|
|
}
|
|
|
|
// A photograph's stand-in: sky, ground and a red disc.
|
|
inline QImage scene(int width, int height)
|
|
{
|
|
QImage image(width, height, QImage::Format_RGB32);
|
|
QPainter painter(&image);
|
|
QLinearGradient sky(0, 0, 0, height);
|
|
sky.setColorAt(0, QColor(40, 90, 200));
|
|
sky.setColorAt(1, QColor(170, 210, 250));
|
|
painter.fillRect(image.rect(), sky);
|
|
painter.fillRect(QRect(0, height * 3 / 4, width, height / 4), QColor(60, 140, 70));
|
|
painter.setRenderHint(QPainter::Antialiasing);
|
|
painter.setPen(Qt::NoPen);
|
|
painter.setBrush(QColor(220, 30, 30));
|
|
painter.drawEllipse(QPointF(width / 2.0, height / 2.0), height / 4.0, height / 4.0);
|
|
return image;
|
|
}
|
|
|
|
// A layer's pixel at a document point; clear outside it.
|
|
inline QColor pixel(const EditorSession &session, QUuid id, int x, int y)
|
|
{
|
|
for (const ImageLayer &layer : session.document().value().layers) {
|
|
if (layer.id != id)
|
|
continue;
|
|
const QImage image = layer.asset.value().image();
|
|
const QPointF unit = (QPointF(x + 0.5, y + 0.5) - layer.transform.origin);
|
|
const QPoint at(int(std::floor(unit.x() * image.width() / layer.transform.size.width())),
|
|
int(std::floor(unit.y() * image.height() / layer.transform.size.height())));
|
|
return image.rect().contains(at) ? image.pixelColor(at) : QColor(0, 0, 0, 0);
|
|
}
|
|
throw std::runtime_error("no such layer");
|
|
}
|