OmaPhoto 1.0.0: the Linux port of Compositor
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>
This commit is contained in:
commit
c864304695
564 files changed
+100804
No files matched your search
@@ -0,0 +1,466 @@
|
||||
#include "Document/EditorSession.h"
|
||||
#include "SessionFixtures.h"
|
||||
#include "SessionRecord.h"
|
||||
#include <QSignalSpy>
|
||||
#include <QtTest>
|
||||
|
||||
// Swift's LayerEffects session extension, driven as its panel drives it.
|
||||
namespace {
|
||||
ImportedImage filled(int width, int height, const QColor &colour)
|
||||
{
|
||||
QImage pixels(width, height, QImage::Format_RGBA8888_Premultiplied);
|
||||
pixels.fill(colour);
|
||||
return ImportedImage(pixels, pixels, QStringLiteral("Filled"));
|
||||
}
|
||||
|
||||
// Two pixel layers, bottom then top, the top one active.
|
||||
struct Two {
|
||||
EditorSession session;
|
||||
QUuid bottom, top;
|
||||
Two()
|
||||
{
|
||||
session.createDocument(20, 10);
|
||||
session.insert(filled(20, 10, Qt::blue));
|
||||
bottom = session.activeLayerID().value();
|
||||
session.insert(filled(8, 4, Qt::red));
|
||||
top = session.activeLayerID().value();
|
||||
session.setBackgroundColor(PaletteColor{0.2, 0.4, 0.6});
|
||||
}
|
||||
std::optional<LayerEffects> effects(QUuid id) const { return layerWith(session, id).effects; }
|
||||
};
|
||||
|
||||
const LayerEffectSelection strokeOf(QUuid id)
|
||||
{
|
||||
return {id, LayerEffectKind::stroke};
|
||||
}
|
||||
}
|
||||
|
||||
class LayerEffectsSessionTests : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void addingAnEffectTakesItsDefaultsAndOpensItsPanel();
|
||||
void onlyPixelLayersTakeEffects();
|
||||
void cancelPutsBackThisPanelsEffectAlone();
|
||||
void setEffectsIsOneStepOnAValidChange();
|
||||
void panelEditsStayWithTheirLayer();
|
||||
void copyingAnEffectClosesTheTargetsPanelFirst();
|
||||
void togglingAndRemovingAnEffect();
|
||||
void choosingLayersDropsTheChosenEffect();
|
||||
void thePickerEditsTheOpenEffect();
|
||||
void everyChangeIsAnnouncedLast();
|
||||
};
|
||||
|
||||
void LayerEffectsSessionTests::addingAnEffectTakesItsDefaultsAndOpensItsPanel()
|
||||
{
|
||||
Two two;
|
||||
EditorSession &session = two.session;
|
||||
QVERIFY(session.canEditEffects() && session.activeEffects().isEmpty());
|
||||
session.addEffect(LayerEffectKind::stroke);
|
||||
// A stroke takes the background; its panel opens on it.
|
||||
const StrokeEffect stroke = two.effects(two.top).value().stroke.value();
|
||||
QVERIFY((stroke == StrokeEffect{.red = 0.2, .green = 0.4, .blue = 0.6}));
|
||||
QCOMPARE(session.history.undoName(), QString("Add Stroke"));
|
||||
QVERIFY(session.effectsEditing() == strokeOf(two.top) && session.effectSelection() == strokeOf(two.top));
|
||||
QVERIFY(session.selectedEffect() == strokeOf(two.top) && session.effectsEditingOriginal() == LayerEffects());
|
||||
QVERIFY(session.editingEffects() == two.effects(two.top).value() && session.activeEffects() == two.effects(two.top).value());
|
||||
// The same panel again does nothing; another closes it, Cancel.
|
||||
const int steps = session.history.undoCount();
|
||||
session.addEffect(LayerEffectKind::stroke);
|
||||
QCOMPARE(session.history.undoCount(), steps);
|
||||
session.addEffect(LayerEffectKind::shadow);
|
||||
const LayerEffects added = two.effects(two.top).value();
|
||||
QVERIFY(!added.stroke && added.shadow == ShadowEffect());
|
||||
QCOMPARE(session.history.undoName(), QString("Add Drop Shadow"));
|
||||
QVERIFY(session.effectsEditing() == (LayerEffectSelection{two.top, LayerEffectKind::shadow}) && session.effectsEditingOriginal() == LayerEffects());
|
||||
session.addEffect(LayerEffectKind::colorOverlay);
|
||||
QVERIFY((two.effects(two.top).value().colorOverlay == ColorOverlayEffect{.red = 0.2, .green = 0.4, .blue = 0.6}));
|
||||
session.finishEffectsEditing(true);
|
||||
session.addEffect(LayerEffectKind::innerShadow);
|
||||
QVERIFY(two.effects(two.top).value().innerShadow == InnerShadowEffect());
|
||||
session.finishEffectsEditing(true);
|
||||
// One already there opens its panel with no step.
|
||||
LayerEffects present = two.effects(two.top).value();
|
||||
present.shadow = ShadowEffect{.distance = 33};
|
||||
session.setEffects(present);
|
||||
session.addEffect(LayerEffectKind::shadow);
|
||||
QVERIFY(two.effects(two.top).value().shadow->distance == 33);
|
||||
session.finishEffectsEditing(true);
|
||||
const int kept = session.history.undoCount();
|
||||
session.addEffect(LayerEffectKind::colorOverlay);
|
||||
QVERIFY(session.history.undoCount() == kept && session.effectsEditingOriginal() == two.effects(two.top));
|
||||
QVERIFY(session.effectsEditing() == (LayerEffectSelection{two.top, LayerEffectKind::colorOverlay}));
|
||||
}
|
||||
|
||||
void LayerEffectsSessionTests::onlyPixelLayersTakeEffects()
|
||||
{
|
||||
Two two;
|
||||
EditorSession &session = two.session;
|
||||
session.addBlankLayer();
|
||||
QVERIFY(!session.canEditEffects());
|
||||
session.addEffect(LayerEffectKind::stroke);
|
||||
QVERIFY(!session.activeLayer().value().effects && !session.effectsEditing());
|
||||
LayerEffects effects;
|
||||
effects.shadow = ShadowEffect();
|
||||
session.setEffects(effects);
|
||||
QVERIFY(!session.activeLayer().value().effects);
|
||||
session.selectLayer(two.top);
|
||||
session.groupSelectedLayers();
|
||||
QVERIFY(!session.canEditEffects());
|
||||
session.setEffects(effects, session.activeLayerID());
|
||||
QVERIFY(!session.activeLayer().value().effects);
|
||||
session.selectLayer(two.top);
|
||||
session.setIsProjectBusy(true);
|
||||
QVERIFY(!session.canEditEffects());
|
||||
session.setEffects(effects);
|
||||
QVERIFY(!two.effects(two.top));
|
||||
session.setIsProjectBusy(false);
|
||||
session.setEffects(effects);
|
||||
QVERIFY(two.effects(two.top) == effects);
|
||||
}
|
||||
|
||||
void LayerEffectsSessionTests::cancelPutsBackThisPanelsEffectAlone()
|
||||
{
|
||||
Two two;
|
||||
EditorSession &session = two.session;
|
||||
LayerEffects start;
|
||||
start.stroke = StrokeEffect{.size = 3};
|
||||
start.shadow = ShadowEffect{.distance = 5};
|
||||
session.setEffects(start);
|
||||
session.selectEffect(LayerEffectKind::stroke, two.top, true);
|
||||
QVERIFY(session.effectsEditingOriginal() == start);
|
||||
session.changeEffects([](LayerEffects &effects) { effects.stroke->size = 9; });
|
||||
QCOMPARE(session.history.undoName(), QString("Edit Stroke"));
|
||||
// Another effect changed meanwhile keeps its change.
|
||||
LayerEffects other = two.effects(two.top).value();
|
||||
other.shadow->distance = 30;
|
||||
session.setEffects(other);
|
||||
session.finishEffectsEditing(false);
|
||||
const LayerEffects after = two.effects(two.top).value();
|
||||
QVERIFY(after.stroke->size == 3 && after.shadow->distance == 30);
|
||||
QCOMPARE(session.history.undoName(), QString("Cancel Stroke"));
|
||||
QVERIFY(!session.effectsEditing() && !session.effectsEditingOriginal());
|
||||
// The chosen effect stays chosen while its layer shows it.
|
||||
QVERIFY(session.effectSelection() == strokeOf(two.top));
|
||||
// A new effect cancelled goes again; OK keeps a change.
|
||||
session.addEffect(LayerEffectKind::innerShadow);
|
||||
session.finishEffectsEditing(false);
|
||||
QVERIFY(!two.effects(two.top).value().innerShadow);
|
||||
QVERIFY(!session.effectSelection());
|
||||
session.selectEffect(LayerEffectKind::shadow, two.top, true);
|
||||
session.changeEffects([](LayerEffects &effects) { effects.shadow->blur = 2; });
|
||||
const int steps = session.history.undoCount();
|
||||
session.finishEffectsEditing(true);
|
||||
QVERIFY(session.history.undoCount() == steps && two.effects(two.top).value().shadow->blur == 2);
|
||||
// Nothing open, nothing to finish.
|
||||
session.finishEffectsEditing(false);
|
||||
QCOMPARE(session.history.undoCount(), steps);
|
||||
// Opening another panel cancels the open one's changes.
|
||||
session.selectEffect(LayerEffectKind::stroke, two.top, true);
|
||||
session.changeEffects([](LayerEffects &effects) { effects.stroke->size = 40; });
|
||||
session.selectEffect(LayerEffectKind::shadow, two.top, true);
|
||||
QVERIFY(two.effects(two.top).value().stroke->size == 3 && session.history.undoName() == QString("Cancel Stroke"));
|
||||
session.finishEffectsEditing(true);
|
||||
// An overlay's Cancel puts back the overlay alone.
|
||||
session.addEffect(LayerEffectKind::colorOverlay);
|
||||
session.finishEffectsEditing(true);
|
||||
session.selectEffect(LayerEffectKind::colorOverlay, two.top, true);
|
||||
session.changeEffects([](LayerEffects &effects) { effects.colorOverlay->opacity = 0.25; });
|
||||
session.finishEffectsEditing(false);
|
||||
QCOMPARE(two.effects(two.top).value().colorOverlay->opacity, 1.0);
|
||||
// Its layer's effects all gone, Cancel has nothing to restore.
|
||||
session.selectEffect(LayerEffectKind::shadow, two.top, true);
|
||||
session.setEffects(LayerEffects(), two.top);
|
||||
session.finishEffectsEditing(false);
|
||||
QVERIFY(!two.effects(two.top) && !session.effectsEditing() && !session.effectSelection());
|
||||
// Choosing an effect takes the target off the mask.
|
||||
LayerEffects stroke;
|
||||
stroke.stroke = StrokeEffect();
|
||||
session.setEffects(stroke, two.top);
|
||||
session.addLayerMask(true);
|
||||
QVERIFY(session.isMaskSelected());
|
||||
session.selectEffect(LayerEffectKind::stroke, two.top);
|
||||
QVERIFY(!session.isMaskSelected() && session.effectSelection() == strokeOf(two.top));
|
||||
}
|
||||
|
||||
void LayerEffectsSessionTests::setEffectsIsOneStepOnAValidChange()
|
||||
{
|
||||
Two two;
|
||||
EditorSession &session = two.session;
|
||||
LayerEffects effects;
|
||||
effects.stroke = StrokeEffect();
|
||||
const int steps = session.history.undoCount();
|
||||
session.setEffects(effects, two.bottom, QStringLiteral("Named"));
|
||||
QVERIFY(session.history.undoCount() == steps + 1 && session.history.undoName() == QString("Named"));
|
||||
QVERIFY(two.effects(two.bottom) == effects && !two.effects(two.top));
|
||||
// The same again, an invalid one, a missing layer: nothing.
|
||||
session.setEffects(effects, two.bottom);
|
||||
LayerEffects wild = effects;
|
||||
wild.stroke->size = 501;
|
||||
session.setEffects(wild, two.bottom);
|
||||
session.setEffects(effects, QUuid::createUuid());
|
||||
QCOMPARE(session.history.undoCount(), steps + 1);
|
||||
// Empty is none at all.
|
||||
session.setEffects(LayerEffects(), two.bottom);
|
||||
QVERIFY(!two.effects(two.bottom) && session.history.undoName() == QString("Layer Effects"));
|
||||
// The same effects again leave an opacity drag alone.
|
||||
session.beginOpacityEdit();
|
||||
session.setLayerOpacity(0.5);
|
||||
session.setEffects(LayerEffects());
|
||||
QVERIFY(!session.canUndo());
|
||||
// A real change ends the drag first, its own step.
|
||||
session.setEffects(effects);
|
||||
QVERIFY(session.canUndo() && session.history.undoName() == QString("Layer Effects"));
|
||||
session.undo();
|
||||
QVERIFY(!two.effects(two.top) && session.activeLayer().value().opacity == 0.5);
|
||||
}
|
||||
|
||||
void LayerEffectsSessionTests::panelEditsStayWithTheirLayer()
|
||||
{
|
||||
Two two;
|
||||
EditorSession &session = two.session;
|
||||
session.addEffect(LayerEffectKind::stroke);
|
||||
session.selectLayer(two.bottom);
|
||||
session.changeEffects([](LayerEffects &effects) { effects.stroke->size = 12; });
|
||||
QVERIFY(two.effects(two.top).value().stroke->size == 12 && !two.effects(two.bottom));
|
||||
// Once its effect is gone, edits have nowhere to go.
|
||||
LayerEffects without = two.effects(two.top).value();
|
||||
without.stroke.reset();
|
||||
without.shadow = ShadowEffect();
|
||||
session.setEffects(without, two.top);
|
||||
const int steps = session.history.undoCount();
|
||||
session.changeEffects([](LayerEffects &effects) { effects.shadow->blur = 1; });
|
||||
QCOMPARE(session.history.undoCount(), steps);
|
||||
session.finishEffectsEditing(true);
|
||||
session.changeEffects([](LayerEffects &effects) { effects.shadow->blur = 1; });
|
||||
QCOMPARE(session.history.undoCount(), steps);
|
||||
}
|
||||
|
||||
void LayerEffectsSessionTests::copyingAnEffectClosesTheTargetsPanelFirst()
|
||||
{
|
||||
Two two;
|
||||
EditorSession &session = two.session;
|
||||
LayerEffects source;
|
||||
source.shadow = ShadowEffect{.distance = 44};
|
||||
session.setEffects(source, two.top);
|
||||
QVERIFY(session.canCopyEffect(LayerEffectKind::shadow, two.top, two.bottom));
|
||||
QVERIFY(!session.canCopyEffect(LayerEffectKind::shadow, two.top, two.top) && !session.canCopyEffect(LayerEffectKind::stroke, two.top, two.bottom));
|
||||
QVERIFY(!session.canCopyEffect(LayerEffectKind::shadow, two.top, QUuid::createUuid()));
|
||||
// The target's own shadow is open: OK, then the copy.
|
||||
LayerEffects target;
|
||||
target.shadow = ShadowEffect{.distance = 2};
|
||||
target.stroke = StrokeEffect();
|
||||
session.setEffects(target, two.bottom);
|
||||
session.selectEffect(LayerEffectKind::shadow, two.bottom, true);
|
||||
session.changeEffects([](LayerEffects &effects) { effects.shadow->blur = 7; });
|
||||
session.copyEffect(LayerEffectKind::shadow, two.top, two.bottom);
|
||||
QCOMPARE(session.history.undoName(), QString("Copy Drop Shadow"));
|
||||
QVERIFY(!session.effectsEditing() && two.effects(two.bottom).value().shadow == source.shadow);
|
||||
QVERIFY(two.effects(two.bottom).value().stroke == StrokeEffect());
|
||||
QVERIFY(session.effectSelection() == (LayerEffectSelection{two.bottom, LayerEffectKind::shadow}) && session.activeLayerID() == two.bottom);
|
||||
// The open edit was kept: one undo brings it back.
|
||||
session.undo();
|
||||
QVERIFY(two.effects(two.bottom).value().shadow->blur == 7);
|
||||
session.redo();
|
||||
// No source, a blank or folder target, busy: no copy.
|
||||
QVERIFY(!session.canCopyEffect(LayerEffectKind::shadow, QUuid::createUuid(), two.bottom));
|
||||
session.addBlankLayer();
|
||||
QVERIFY(!session.canCopyEffect(LayerEffectKind::shadow, two.top, session.activeLayerID().value()));
|
||||
session.groupSelectedLayers();
|
||||
QVERIFY(!session.canCopyEffect(LayerEffectKind::shadow, two.top, session.activeLayerID().value()));
|
||||
session.setIsProjectBusy(true);
|
||||
QVERIFY(!session.canCopyEffect(LayerEffectKind::shadow, two.top, two.bottom));
|
||||
}
|
||||
|
||||
void LayerEffectsSessionTests::togglingAndRemovingAnEffect()
|
||||
{
|
||||
Two two;
|
||||
EditorSession &session = two.session;
|
||||
session.addEffect(LayerEffectKind::stroke);
|
||||
session.finishEffectsEditing(true);
|
||||
session.toggleEffect(LayerEffectKind::stroke, two.top);
|
||||
QVERIFY(two.effects(two.top).value().stroke->enabled == false && session.history.undoName() == QString("Hide Stroke"));
|
||||
session.toggleEffect(LayerEffectKind::stroke, two.top);
|
||||
QVERIFY(two.effects(two.top).value().stroke->enabled == true && session.history.undoName() == QString("Show Stroke"));
|
||||
// Toggling a missing effect, or none at all, does nothing.
|
||||
const int steps = session.history.undoCount();
|
||||
session.toggleEffect(LayerEffectKind::shadow, two.top);
|
||||
session.toggleEffect(LayerEffectKind::stroke, two.bottom);
|
||||
QCOMPARE(session.history.undoCount(), steps);
|
||||
// Delete takes the chosen effect first, panel and picker too.
|
||||
session.selectEffect(LayerEffectKind::stroke, two.top, true);
|
||||
session.openEffectColorPicker(LayerEffectKind::stroke);
|
||||
QVERIFY(session.colorPicker());
|
||||
session.deleteLayerOrMask();
|
||||
QVERIFY(!two.effects(two.top) && session.history.undoName() == QString("Remove Stroke"));
|
||||
QVERIFY(!session.effectsEditing() && !session.effectSelection() && !session.colorPicker());
|
||||
QCOMPARE(session.document().value().layers.size(), size_t(2));
|
||||
// With none chosen, Delete takes the layer; the key likewise.
|
||||
session.addEffect(LayerEffectKind::shadow);
|
||||
session.finishEffectsEditing(true);
|
||||
session.selectAll();
|
||||
const ImageIdentity pixels = layerWith(session, two.top).asset.value().identity();
|
||||
session.deleteKeyPressed();
|
||||
QVERIFY(!two.effects(two.top).value_or(LayerEffects()).shadow && session.history.undoName() == QString("Remove Drop Shadow"));
|
||||
QVERIFY(layerWith(session, two.top).asset.value().identity() == pixels && !session.isProjectBusy());
|
||||
session.deselect();
|
||||
session.deleteKeyPressed();
|
||||
QCOMPARE(session.document().value().layers.size(), size_t(1));
|
||||
session.removeSelectedEffect();
|
||||
QCOMPARE(session.document().value().layers.size(), size_t(1));
|
||||
// Busy, a chosen effect stays.
|
||||
session.setEffects(LayerEffects{.stroke = StrokeEffect()}, two.bottom);
|
||||
session.selectEffect(LayerEffectKind::stroke, two.bottom);
|
||||
session.setIsProjectBusy(true);
|
||||
session.removeSelectedEffect();
|
||||
QVERIFY(two.effects(two.bottom) && session.selectedEffect());
|
||||
}
|
||||
|
||||
void LayerEffectsSessionTests::choosingLayersDropsTheChosenEffect()
|
||||
{
|
||||
for (int way = 0; way < 3; ++way) {
|
||||
Two two;
|
||||
EditorSession &session = two.session;
|
||||
session.addEffect(LayerEffectKind::stroke);
|
||||
session.finishEffectsEditing(true);
|
||||
QVERIFY(session.effectSelection());
|
||||
// Swift clears it first, even when the choice is refused.
|
||||
session.setIsProjectBusy(way == 2);
|
||||
if (way == 0)
|
||||
session.selectLayer(two.top);
|
||||
else if (way == 1)
|
||||
session.selectLayers({two.top}, two.top);
|
||||
else
|
||||
session.selectLayerTarget(two.top, false);
|
||||
QVERIFY2(!session.effectSelection(), qPrintable(QString::number(way)));
|
||||
QVERIFY(!session.selectedEffect() && two.effects(two.top));
|
||||
}
|
||||
// The chosen effect needs its layer active and holding it.
|
||||
Two two;
|
||||
LayerEffects stroke;
|
||||
stroke.stroke = StrokeEffect();
|
||||
two.session.setEffects(stroke, two.bottom);
|
||||
two.session.addEffect(LayerEffectKind::stroke);
|
||||
two.session.finishEffectsEditing(true);
|
||||
two.session.setActiveLayerID(two.bottom);
|
||||
QVERIFY(two.session.effectSelection() && !two.session.selectedEffect());
|
||||
}
|
||||
|
||||
void LayerEffectsSessionTests::thePickerEditsTheOpenEffect()
|
||||
{
|
||||
Two two;
|
||||
EditorSession &session = two.session;
|
||||
// Without an open panel no effect takes a picker.
|
||||
session.openEffectColorPicker(LayerEffectKind::stroke);
|
||||
QVERIFY(!session.colorPicker());
|
||||
session.addEffect(LayerEffectKind::stroke);
|
||||
session.openEffectColorPicker(LayerEffectKind::stroke);
|
||||
QCOMPARE(session.colorPicker().value().target.title(), QString("Color Picker (Stroke Color)"));
|
||||
QVERIFY(session.colorPicker().value().original == (PaletteColor{0.2, 0.4, 0.6}));
|
||||
// Its working colour previews on the layer, one step each.
|
||||
session.setColorPickerHSB(PickerHSB(PaletteColor{1, 0, 0}));
|
||||
session.previewEffectColor();
|
||||
QVERIFY(two.effects(two.top).value().color(LayerEffectKind::stroke) == (PaletteColor{1, 0, 0}));
|
||||
QCOMPARE(session.history.undoName(), QString("Edit Stroke"));
|
||||
// Cancel puts the original back; OK keeps the choice.
|
||||
session.closeColorPicker(false);
|
||||
QVERIFY(two.effects(two.top).value().color(LayerEffectKind::stroke) == (PaletteColor{0.2, 0.4, 0.6}));
|
||||
session.openEffectColorPicker(LayerEffectKind::stroke);
|
||||
session.setColorPickerHSB(PickerHSB(PaletteColor{0, 1, 0}));
|
||||
session.closeColorPicker(true);
|
||||
QVERIFY(two.effects(two.top).value().color(LayerEffectKind::stroke) == (PaletteColor{0, 1, 0}));
|
||||
// The panel's Cancel takes the picker with it and restores.
|
||||
session.openEffectColorPicker(LayerEffectKind::stroke);
|
||||
session.setColorPickerHSB(PickerHSB(PaletteColor{0, 0, 1}));
|
||||
session.previewEffectColor();
|
||||
session.finishEffectsEditing(false);
|
||||
QVERIFY(!session.colorPicker() && !two.effects(two.top));
|
||||
// Another picker's preview recolours no effect.
|
||||
session.setEffects(LayerEffects{.stroke = StrokeEffect{.red = 1}});
|
||||
session.addEffect(LayerEffectKind::shadow);
|
||||
session.openColorPicker(false);
|
||||
QVERIFY(session.colorPicker());
|
||||
const int steps = session.history.undoCount();
|
||||
session.previewEffectColor();
|
||||
QVERIFY(session.history.undoCount() == steps && two.effects(two.top).value().stroke->red == 1);
|
||||
session.openEffectColorPicker(LayerEffectKind::shadow);
|
||||
QVERIFY(session.colorPicker().value().target.kind == ColorPickerTarget::Kind::palette);
|
||||
session.closeColorPicker(false);
|
||||
// A palette picker outlives the panel's close.
|
||||
session.openColorPicker(false);
|
||||
session.finishEffectsEditing(true);
|
||||
QVERIFY(session.colorPicker().value().target.kind == ColorPickerTarget::Kind::palette);
|
||||
session.closeColorPicker(false);
|
||||
session.selectEffect(LayerEffectKind::shadow, two.top, true);
|
||||
// A kind the layer lacks starts black, writes nothing.
|
||||
session.openEffectColorPicker(LayerEffectKind::innerShadow);
|
||||
QVERIFY(session.colorPicker().value().original == PaletteColor::black());
|
||||
const int unchanged = session.history.undoCount();
|
||||
session.setColorPickerHSB(PickerHSB(PaletteColor{0, 1, 1}));
|
||||
session.closeColorPicker(true);
|
||||
QVERIFY(session.history.undoCount() == unchanged && !two.effects(two.top).value().innerShadow);
|
||||
// Busy, no picker opens.
|
||||
session.setIsProjectBusy(true);
|
||||
session.openEffectColorPicker(LayerEffectKind::shadow);
|
||||
QVERIFY(!session.colorPicker());
|
||||
session.setIsProjectBusy(false);
|
||||
// Opening another panel closes its effect picker, Cancel.
|
||||
session.openEffectColorPicker(LayerEffectKind::shadow);
|
||||
session.setColorPickerHSB(PickerHSB(PaletteColor{1, 1, 0}));
|
||||
session.previewEffectColor();
|
||||
session.addEffect(LayerEffectKind::stroke);
|
||||
QVERIFY(!session.colorPicker() && two.effects(two.top).value().shadow->color() == PaletteColor::black());
|
||||
}
|
||||
|
||||
void LayerEffectsSessionTests::everyChangeIsAnnouncedLast()
|
||||
{
|
||||
QStringList seen;
|
||||
Two two;
|
||||
EditorSession &session = two.session;
|
||||
connect(&session, &EditorSession::changed, this, [&] { seen = described(session); });
|
||||
const auto stale = [&](const std::function<void()> &change) {
|
||||
seen.clear();
|
||||
change();
|
||||
const QStringList left = described(session);
|
||||
return seen == left ? QString() : seen.join("; ") + " != " + left.join("; ");
|
||||
};
|
||||
QCOMPARE(stale([&] { session.addEffect(LayerEffectKind::stroke); }), QString());
|
||||
QCOMPARE(stale([&] { session.changeEffects([](LayerEffects &effects) { effects.stroke->size = 6; }); }), QString());
|
||||
QCOMPARE(stale([&] { session.openEffectColorPicker(LayerEffectKind::stroke); }), QString());
|
||||
QCOMPARE(stale([&] { session.setColorPickerHSB(PickerHSB(PaletteColor{1, 0, 0})); }), QString());
|
||||
QCOMPARE(stale([&] { session.previewEffectColor(); }), QString());
|
||||
QCOMPARE(stale([&] { session.finishEffectsEditing(false); }), QString());
|
||||
QCOMPARE(stale([&] { session.addEffect(LayerEffectKind::shadow); }), QString());
|
||||
QCOMPARE(stale([&] { session.finishEffectsEditing(true); }), QString());
|
||||
QCOMPARE(stale([&] { session.toggleEffect(LayerEffectKind::shadow, two.top); }), QString());
|
||||
QCOMPARE(stale([&] { session.selectEffect(LayerEffectKind::shadow, two.top); }), QString());
|
||||
QCOMPARE(stale([&] { session.copyEffect(LayerEffectKind::shadow, two.top, two.bottom); }), QString());
|
||||
QCOMPARE(stale([&] { session.removeSelectedEffect(); }), QString());
|
||||
QCOMPARE(stale([&] { session.selectEffect(LayerEffectKind::shadow, two.top); }), QString());
|
||||
QCOMPARE(stale([&] { session.selectLayer(two.bottom); }), QString());
|
||||
// The writers the window uses since 9.6d.
|
||||
QCOMPARE(stale([&] { session.selectEffect(LayerEffectKind::shadow, two.top); }), QString());
|
||||
QCOMPARE(stale([&] { session.dropEffectSelection(); }), QString());
|
||||
QCOMPARE(stale([&] { session.setEffectsEditing(strokeOf(two.top)); }), QString());
|
||||
QCOMPARE(stale([&] { session.setEffectsEditingOriginal(LayerEffects()); }), QString());
|
||||
QCOMPARE(stale([&] { session.setEffectsEditing(std::nullopt); }), QString());
|
||||
QCOMPARE(stale([&] { session.setEffectsEditingOriginal(std::nullopt); }), QString());
|
||||
// Refusals stay silent.
|
||||
QSignalSpy changed(&session, &EditorSession::changed);
|
||||
session.removeSelectedEffect();
|
||||
session.finishEffectsEditing(true);
|
||||
session.selectEffect(LayerEffectKind::colorOverlay, two.top);
|
||||
session.toggleEffect(LayerEffectKind::stroke, two.bottom);
|
||||
session.setEffects(LayerEffects(), two.bottom);
|
||||
session.previewEffectColor();
|
||||
session.dropEffectSelection();
|
||||
session.setEffectsEditing(std::nullopt);
|
||||
session.setEffectsEditingOriginal(std::nullopt);
|
||||
QCOMPARE(changed.count(), 0);
|
||||
session.beginLevels();
|
||||
changed.clear();
|
||||
session.selectLayer(two.top);
|
||||
QCOMPARE(changed.count(), 0);
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(LayerEffectsSessionTests)
|
||||
#include "LayerEffectsSessionTests.moc"
|
||||
Reference in new issue
Block a user