A layered image editor for Linux and Omarchy, ported from Compositor (github.com/robbietilton/Compositor), whose Swift source is its specification. It reads and writes Compositor's .comp projects. Packages for Ubuntu, Fedora and Arch come from the release workflow; scripts/install/ holds one install script per distribution family, and flake.nix serves NixOS. Co-Authored-By: Claude <noreply@anthropic.com>
161 lines
6.3 KiB
C++
161 lines
6.3 KiB
C++
#include "UI/BlendModePicker.h"
|
|
#include "UI/LayerMaskMenu.h"
|
|
#include <QAbstractItemView>
|
|
#include <QtTest>
|
|
|
|
// The blend menu and the add-mask button follow the session.
|
|
namespace {
|
|
ImportedImage white()
|
|
{
|
|
QImage image(2, 2, QImage::Format_RGBA8888_Premultiplied);
|
|
image.fill(Qt::white);
|
|
return ImportedImage(image, image, "White");
|
|
}
|
|
}
|
|
|
|
class BlendModePickerTests : public QObject {
|
|
Q_OBJECT
|
|
private slots:
|
|
void theMenuListsEveryModeAndFollowsTheActiveLayer();
|
|
void hoveringTriesAModeOnAndChoosingKeepsIt();
|
|
void aMenuClosedWithoutAChoiceLeavesTheLayerAlone();
|
|
void theMaskButtonAddsAWhiteMaskOnce();
|
|
};
|
|
|
|
void BlendModePickerTests::theMenuListsEveryModeAndFollowsTheActiveLayer()
|
|
{
|
|
EditorSession session;
|
|
BlendModePicker picker(session);
|
|
QCOMPARE(picker.count(), int(allLayerBlendModes.size()));
|
|
QStringList titles;
|
|
for (int index = 0; index < picker.count(); ++index)
|
|
titles << picker.itemText(index);
|
|
QCOMPARE(titles.first(), QString("Normal"));
|
|
QCOMPARE(titles.last(), QString("Luminosity"));
|
|
QCOMPARE(titles[1], QString("Multiply"));
|
|
QVERIFY(!picker.isEnabled());
|
|
session.createDocument(4, 4);
|
|
session.insert(white());
|
|
QVERIFY(picker.isEnabled());
|
|
QCOMPARE(picker.currentText(), QString("Normal"));
|
|
session.setLayerBlendMode(LayerBlendMode::screen);
|
|
QCOMPARE(picker.currentText(), QString("Screen"));
|
|
// A second layer's own mode; a folder disables the menu.
|
|
session.insert(white());
|
|
session.setLayerBlendMode(LayerBlendMode::hue);
|
|
QCOMPARE(picker.currentText(), QString("Hue"));
|
|
session.selectLayer(session.document().value().layers.front().id);
|
|
QCOMPARE(picker.currentText(), QString("Screen"));
|
|
session.groupSelectedLayers();
|
|
QVERIFY(!picker.isEnabled());
|
|
QCOMPARE(picker.currentText(), QString("Normal"));
|
|
}
|
|
|
|
void BlendModePickerTests::hoveringTriesAModeOnAndChoosingKeepsIt()
|
|
{
|
|
EditorSession session;
|
|
session.createDocument(4, 4);
|
|
session.insert(white());
|
|
const QUuid layer = session.activeLayerID().value();
|
|
BlendModePicker picker(session);
|
|
picker.show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(&picker));
|
|
picker.showPopup();
|
|
QTRY_VERIFY(picker.view()->isVisible());
|
|
// Hovering previews on the layer the menu opened for.
|
|
emit picker.highlighted(1);
|
|
QCOMPARE(session.blendPreview().value().mode, LayerBlendMode::multiply);
|
|
QCOMPARE(session.blendPreview().value().layerID, layer);
|
|
QCOMPARE(session.activeLayer().value().blendMode, LayerBlendMode::normal);
|
|
emit picker.highlighted(2);
|
|
QCOMPARE(session.blendPreview().value().mode, LayerBlendMode::screen);
|
|
// The session's news leaves an open menu alone.
|
|
picker.view()->setCurrentIndex(picker.model()->index(2, 0));
|
|
session.setLayerOpacity(0.5);
|
|
QVERIFY(session.blendPreview().has_value());
|
|
QCOMPARE(picker.view()->currentIndex().row(), 2);
|
|
// A click closes the menu, then chooses; preview clears after.
|
|
picker.hidePopup();
|
|
emit picker.activated(2);
|
|
QCOMPARE(session.activeLayer().value().blendMode, LayerBlendMode::screen);
|
|
QCOMPARE(picker.currentText(), QString("Screen"));
|
|
QCOMPARE(session.history.undoName(), QString("Layer Blend Mode"));
|
|
QTRY_VERIFY(!session.blendPreview().has_value());
|
|
QCOMPARE(picker.currentText(), QString("Screen"));
|
|
// Keys on the closed menu choose for the active layer.
|
|
picker.setFocus();
|
|
QTest::keyClick(&picker, Qt::Key_Down);
|
|
QCOMPARE(session.activeLayer().value().blendMode, LayerBlendMode::overlay);
|
|
QCOMPARE(picker.currentText(), QString("Overlay"));
|
|
// Closed, the menu follows the session again.
|
|
session.undo();
|
|
QCOMPARE(picker.currentText(), QString("Screen"));
|
|
}
|
|
|
|
void BlendModePickerTests::aMenuClosedWithoutAChoiceLeavesTheLayerAlone()
|
|
{
|
|
EditorSession session;
|
|
session.createDocument(4, 4);
|
|
session.insert(white());
|
|
session.setLayerBlendMode(LayerBlendMode::multiply);
|
|
session.insert(white());
|
|
const QUuid top = session.activeLayerID().value();
|
|
BlendModePicker picker(session);
|
|
picker.show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(&picker));
|
|
picker.showPopup();
|
|
emit picker.highlighted(4);
|
|
picker.hidePopup();
|
|
QCOMPARE(session.activeLayer().value().blendMode, LayerBlendMode::normal);
|
|
QTRY_VERIFY(!session.blendPreview().has_value());
|
|
QCOMPARE(picker.currentText(), QString("Normal"));
|
|
// Another layer active at the choice: nothing changes.
|
|
picker.showPopup();
|
|
emit picker.highlighted(4);
|
|
session.selectLayer(session.document().value().layers.front().id);
|
|
// Open, the menu keeps its text; closed, the active layer.
|
|
QCOMPARE(picker.currentText(), QString("Normal"));
|
|
picker.hidePopup();
|
|
emit picker.activated(4);
|
|
QCOMPARE(session.document().value().layers.front().blendMode, LayerBlendMode::multiply);
|
|
QCOMPARE(session.document().value().layers.back().blendMode, LayerBlendMode::normal);
|
|
QCOMPARE(session.history.undoName(), QString("Import Image"));
|
|
QCOMPARE(picker.currentText(), QString("Multiply"));
|
|
QTRY_VERIFY(!session.blendPreview().has_value());
|
|
session.selectLayer(top);
|
|
QCOMPARE(picker.currentText(), QString("Normal"));
|
|
// A cancelled menu keeps no target: keys choose.
|
|
picker.showPopup();
|
|
picker.hidePopup();
|
|
QCoreApplication::processEvents();
|
|
session.selectLayer(session.document().value().layers.front().id);
|
|
picker.setFocus();
|
|
QTest::keyClick(&picker, Qt::Key_Down);
|
|
QCOMPARE(session.activeLayer().value().blendMode, LayerBlendMode::screen);
|
|
}
|
|
|
|
void BlendModePickerTests::theMaskButtonAddsAWhiteMaskOnce()
|
|
{
|
|
EditorSession session;
|
|
LayerMaskMenu button(session);
|
|
QCOMPARE(button.accessibleName(), QString("Add layer mask"));
|
|
QVERIFY(!button.isEnabled());
|
|
session.createDocument(4, 4);
|
|
session.insert(white());
|
|
QVERIFY(button.isEnabled());
|
|
button.click();
|
|
const ImageLayer layer = session.activeLayer().value();
|
|
QVERIFY(layer.mask.has_value());
|
|
QCOMPARE(layer.mask.value().asset.image().pixelColor(0, 0), QColor(255, 255, 255));
|
|
QVERIFY(!button.isEnabled());
|
|
session.undo();
|
|
QVERIFY(!session.activeLayer().value().mask.has_value() && button.isEnabled());
|
|
session.setIsProjectBusy(true);
|
|
QVERIFY(!button.isEnabled());
|
|
session.setIsProjectBusy(false);
|
|
QVERIFY(button.isEnabled());
|
|
}
|
|
|
|
QTEST_MAIN(BlendModePickerTests)
|
|
#include "BlendModePickerTests.moc"
|