The actions and settings split is reverted. Sketch2DRoot::mainMenu is the single menu structure: on PC it fills the menu bar and the studio gear is hidden, on the tablet the gear opens it as pages.
159 lines
5.1 KiB
C++
159 lines
5.1 KiB
C++
#include "Sketch2DUI.hpp"
|
|
#include "StudioUI.hpp"
|
|
#include "Ui.hpp"
|
|
#include "UiDriver.hpp"
|
|
|
|
#include "UnitTest++/UnitTest++.h"
|
|
|
|
using namespace tp;
|
|
using namespace tp::sketch2d;
|
|
using namespace tp::ui;
|
|
using namespace tp::ui::testing;
|
|
|
|
namespace {
|
|
class FilterViewport : public ViewportRenderer {
|
|
public:
|
|
void render(Document&, const ViewSettings&, Vec2F) override {}
|
|
[[nodiscard]] ImageId image() const override { return { 1 }; }
|
|
void setDebugOverlay(bool) override {}
|
|
[[nodiscard]] bool debugOverlay() const override { return false; }
|
|
};
|
|
|
|
class FilterIcons : public IconProvider {
|
|
public:
|
|
[[nodiscard]] ImageId icon(const std::string& name) const override { return { (ualni) name.size() + 1 }; }
|
|
};
|
|
|
|
void put(Document& document, int4 x, int4 y, const RGBA& color) {
|
|
uint1* pixel = document.activeLayer()->tileAt({ 0, 0 })->pixels() + ((size_t) y * kTileSize + (size_t) x) * 4;
|
|
pixel[0] = (uint1) std::lround(color.r * color.a * 255);
|
|
pixel[1] = (uint1) std::lround(color.g * color.a * 255);
|
|
pixel[2] = (uint1) std::lround(color.b * color.a * 255);
|
|
pixel[3] = (uint1) std::lround(color.a * 255);
|
|
}
|
|
|
|
struct FilterScene {
|
|
Document document;
|
|
Brush brush;
|
|
FilterViewport viewport;
|
|
FilterIcons icons;
|
|
Sketch2DRoot root{ document, viewport, brush, icons };
|
|
UiDriver driver{ root, { 1280, 800 } };
|
|
|
|
FilterScene() {
|
|
put(document, 1, 1, { 1, 0, 0, 1 });
|
|
put(document, 2, 1, { 0.2f, 0.4f, 0.6f, 0.5f });
|
|
put(document, 3, 1, { 1, 1, 1, 1 });
|
|
put(document, 4, 1, { 0, 0, 0, 1 });
|
|
document.commit("Pixels");
|
|
}
|
|
|
|
RGBA at(int4 x, int4 y) { return document.sampleColor({ (halnf) x + 0.5f, (halnf) y + 0.5f }, false); }
|
|
};
|
|
|
|
void checkRgba(const RGBA& expected, const RGBA& actual) {
|
|
CHECK_CLOSE(expected.r, actual.r, 0.02f);
|
|
CHECK_CLOSE(expected.g, actual.g, 0.02f);
|
|
CHECK_CLOSE(expected.b, actual.b, 0.02f);
|
|
CHECK_CLOSE(expected.a, actual.a, 0.02f);
|
|
}
|
|
}
|
|
|
|
SUITE(PixelFilters) {
|
|
TEST(InvertFlipsColorsAndKeepsAlpha) {
|
|
FilterScene scene;
|
|
scene.root.commands.run(Command::Invert);
|
|
checkRgba({ 0, 1, 1, 1 }, scene.at(1, 1));
|
|
checkRgba({ 0.8f, 0.6f, 0.4f, 0.5f }, scene.at(2, 1));
|
|
CHECK_CLOSE(0.0f, scene.at(10, 10).a, 1e-6f);
|
|
scene.root.commands.run(Command::Undo);
|
|
checkRgba({ 1, 0, 0, 1 }, scene.at(1, 1));
|
|
}
|
|
|
|
TEST(DesaturateUsesLuma) {
|
|
FilterScene scene;
|
|
scene.root.commands.run(Command::Desaturate);
|
|
checkRgba({ 0.299f, 0.299f, 0.299f, 1 }, scene.at(1, 1));
|
|
const halnf value = 0.299f * 0.2f + 0.587f * 0.4f + 0.114f * 0.6f;
|
|
checkRgba({ value, value, value, 0.5f }, scene.at(2, 1));
|
|
}
|
|
|
|
TEST(ValueToAlphaTurnsWhiteTransparent) {
|
|
FilterScene scene;
|
|
scene.root.commands.run(Command::ValueToAlpha);
|
|
CHECK(scene.at(3, 1).a < 0.01f);
|
|
checkRgba({ 0, 0, 0, 1 }, scene.at(4, 1));
|
|
checkRgba({ 0, 0, 0, 0.701f }, scene.at(1, 1));
|
|
}
|
|
|
|
TEST(ValueToAlphaInvertedTurnsBlackTransparent) {
|
|
FilterScene scene;
|
|
scene.root.commands.run(Command::ValueToAlphaInverted);
|
|
CHECK(scene.at(4, 1).a < 0.01f);
|
|
checkRgba({ 1, 1, 1, 1 }, scene.at(3, 1));
|
|
checkRgba({ 1, 1, 1, 0.299f }, scene.at(1, 1));
|
|
}
|
|
|
|
TEST(FiltersRespectTheSelection) {
|
|
FilterScene scene;
|
|
scene.document.editSelection().addRectangle(0, 0, 2, 2, SelectionMode::Replace);
|
|
scene.root.commands.run(Command::Invert);
|
|
checkRgba({ 0, 1, 1, 1 }, scene.at(1, 1));
|
|
checkRgba({ 1, 1, 1, 1 }, scene.at(3, 1));
|
|
}
|
|
|
|
TEST(AlphaLockRefusesValueToAlphaButAllowsInvert) {
|
|
FilterScene scene;
|
|
scene.document.toggleAlphaLock();
|
|
scene.root.commands.run(Command::ValueToAlpha);
|
|
CHECK_EQUAL(std::string("You can't change alpha on a alpha locked layer"), scene.root.lastNotice());
|
|
checkRgba({ 1, 1, 1, 1 }, scene.at(3, 1));
|
|
scene.root.commands.run(Command::Invert);
|
|
checkRgba({ 0, 0, 0, 1 }, scene.at(3, 1));
|
|
}
|
|
|
|
TEST(EmptyLayerExplains) {
|
|
FilterScene scene;
|
|
scene.document.addLayer("Empty");
|
|
const size_t revision = scene.document.history().revision();
|
|
scene.root.commands.run(Command::Desaturate);
|
|
CHECK_EQUAL(std::string("The layer is empty"), scene.root.lastNotice());
|
|
CHECK_EQUAL(revision, scene.document.history().revision());
|
|
}
|
|
|
|
TEST(EditMenuListsFiltersAndPixelEdits) {
|
|
FilterScene scene;
|
|
std::vector<std::string> texts;
|
|
for (const auto& item : scene.root.editMenu.items())
|
|
texts.push_back(item->entry().text);
|
|
for (const char* text : { "Fill color",
|
|
"Clear",
|
|
"Crop",
|
|
"Free Transform",
|
|
"Flip horizontal",
|
|
"Flip vertical",
|
|
"Invert",
|
|
"Desaturate",
|
|
"Copy value to alpha",
|
|
"Copy value to alpha inverted" }) {
|
|
bool found = false;
|
|
for (const std::string& existing : texts)
|
|
found = found || existing == text;
|
|
CHECK(found);
|
|
}
|
|
}
|
|
|
|
TEST(TabletQuickMenuHasTheEditMenu) {
|
|
Document document;
|
|
Brush brush;
|
|
FilterViewport viewport;
|
|
FilterIcons icons;
|
|
Sketch2DRoot classic{ document, viewport, brush, icons };
|
|
StudioRoot studio{ classic, brush, icons };
|
|
UiDriver driver{ studio, { 1280, 800 } };
|
|
bool found = false;
|
|
for (const auto& item : classic.mainMenu.items())
|
|
if (item->entry().text == "Edit" && item->entry().submenu == &classic.editMenu) found = true;
|
|
CHECK(found);
|
|
}
|
|
}
|