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.
390 lines
14 KiB
C++
390 lines
14 KiB
C++
#include "FakeWindowHost.hpp"
|
|
#include "Sketch2DUI.hpp"
|
|
#include "StudioUI.hpp"
|
|
#include "Ui.hpp"
|
|
#include "UiDriver.hpp"
|
|
|
|
#include "UnitTest++/UnitTest++.h"
|
|
|
|
#include <cmath>
|
|
|
|
using namespace tp;
|
|
using namespace tp::sketch2d;
|
|
using namespace tp::ui;
|
|
using namespace tp::ui::testing;
|
|
|
|
namespace {
|
|
class ToolsViewport : 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 ToolsIcons : public IconProvider {
|
|
public:
|
|
[[nodiscard]] ImageId icon(const std::string& name) const override { return { (ualni) name.size() + 1 }; }
|
|
};
|
|
|
|
struct ToolsScene {
|
|
Document document;
|
|
Brush brush;
|
|
ToolsViewport viewport;
|
|
ToolsIcons icons;
|
|
Sketch2DRoot root{ document, viewport, brush, icons };
|
|
|
|
ToolsScene() {
|
|
root.canvas.settings().viewStiffness = 1;
|
|
brush.apply(*findBrushPreset(BrushType::Round));
|
|
}
|
|
};
|
|
|
|
struct TabletScene {
|
|
Document document;
|
|
Brush brush;
|
|
ToolsViewport viewport;
|
|
ToolsIcons icons;
|
|
Sketch2DRoot classic{ document, viewport, brush, icons };
|
|
StudioRoot studio{ classic, brush, icons };
|
|
|
|
TabletScene() {
|
|
classic.canvas.settings().viewStiffness = 1;
|
|
brush.apply(*findBrushPreset(BrushType::Round));
|
|
}
|
|
};
|
|
|
|
const MenuItem* findItem(Menu& menu, const std::string& text) {
|
|
for (const auto& item : menu.items())
|
|
if (item->text() == text) return item.get();
|
|
return nullptr;
|
|
}
|
|
|
|
void choose(Menu& menu, const std::string& text) {
|
|
const MenuItem* item = findItem(menu, text);
|
|
CHECK(item != nullptr);
|
|
if (item) item->entry().action();
|
|
}
|
|
|
|
size_t strokesOf(Document& document) { return document.history().revision(); }
|
|
}
|
|
|
|
SUITE(BrushReset) {
|
|
TEST(ResetEverythingRestoresThePresetAndKeepsColor) {
|
|
Brush brush;
|
|
brush.apply(*findBrushPreset(BrushType::Ink));
|
|
const Brush original = brush;
|
|
brush.color = { 0.1f, 0.2f, 0.3f, 1 };
|
|
brush.setSize(321);
|
|
brush.set(BrushParameter::Texture, 0.9f);
|
|
brush.setStabilizer(0.9f);
|
|
brush.resetEverything();
|
|
CHECK_CLOSE(original.size(), brush.size(), 1e-4f);
|
|
CHECK_CLOSE(original.get(BrushParameter::Texture), brush.get(BrushParameter::Texture), 1e-4f);
|
|
CHECK_CLOSE(Brush::kDefaultStabilizer, brush.stabilizer(), 1e-4f);
|
|
CHECK_CLOSE(0.1f, brush.color.r, 1e-6f);
|
|
CHECK_EQUAL(std::string(original.presetName()), brush.presetName());
|
|
}
|
|
|
|
TEST(ResetParametersKeepsTexturesAndStabilization) {
|
|
Brush brush;
|
|
brush.apply(*findBrushPreset(BrushType::Pencil));
|
|
const Brush original = brush;
|
|
brush.setSize(555);
|
|
brush.setOpacity(0.2f);
|
|
brush.set(BrushParameter::Texture, 0.77f);
|
|
brush.set(BrushParameter::GrainScale, 4.5f);
|
|
brush.setStabilizer(0.8f);
|
|
brush.resetParameters();
|
|
CHECK_CLOSE(original.size(), brush.size(), 1e-4f);
|
|
CHECK_CLOSE(original.opacity(), brush.opacity(), 1e-4f);
|
|
CHECK_CLOSE(0.77f, brush.get(BrushParameter::Texture), 1e-4f);
|
|
CHECK_CLOSE(4.5f, brush.get(BrushParameter::GrainScale), 1e-4f);
|
|
CHECK_CLOSE(0.8f, brush.stabilizer(), 1e-4f);
|
|
}
|
|
|
|
TEST(ResetTexturesOnlyTouchesTextureValues) {
|
|
Brush brush;
|
|
brush.apply(*findBrushPreset(BrushType::Pencil));
|
|
const Brush original = brush;
|
|
brush.setSize(555);
|
|
brush.set(BrushParameter::Texture, 0.11f);
|
|
brush.set(BrushParameter::DeckleScale, 9);
|
|
brush.resetTextures();
|
|
CHECK_CLOSE(555.0f, brush.size(), 1e-4f);
|
|
CHECK_CLOSE(original.get(BrushParameter::Texture), brush.get(BrushParameter::Texture), 1e-4f);
|
|
CHECK_CLOSE(original.get(BrushParameter::DeckleScale), brush.get(BrushParameter::DeckleScale), 1e-4f);
|
|
}
|
|
|
|
TEST(ResetStabilizationOnlyTouchesTheStabilizer) {
|
|
Brush brush;
|
|
brush.apply(*findBrushPreset(BrushType::Round));
|
|
brush.setSize(77);
|
|
brush.setStabilizer(0.9f);
|
|
brush.resetStabilization();
|
|
CHECK_CLOSE(Brush::kDefaultStabilizer, brush.stabilizer(), 1e-4f);
|
|
CHECK_CLOSE(77.0f, brush.size(), 1e-4f);
|
|
}
|
|
|
|
TEST(SizeStepsAlwaysMoveAndStayWithinLimits) {
|
|
Brush brush;
|
|
brush.setSize(1);
|
|
brush.scaleSize(1.2f);
|
|
CHECK_CLOSE(2.0f, brush.size(), 1e-4f);
|
|
brush.setSize(100);
|
|
brush.scaleSize(1.2f);
|
|
CHECK_CLOSE(120.0f, brush.size(), 1e-4f);
|
|
brush.scaleSize(1.0f / 1.2f);
|
|
CHECK_CLOSE(100.0f, brush.size(), 1e-4f);
|
|
brush.setSize(1);
|
|
brush.scaleSize(1.0f / 1.2f);
|
|
CHECK_CLOSE(1.0f, brush.size(), 1e-4f);
|
|
brush.setSize(Brush::kMaximumSize);
|
|
brush.scaleSize(1.2f);
|
|
CHECK_CLOSE(Brush::kMaximumSize, brush.size(), 1e-4f);
|
|
}
|
|
|
|
TEST(AngleWrapsAroundAFullTurn) {
|
|
Brush brush;
|
|
brush.set(BrushParameter::Angle, 350);
|
|
brush.adjust(BrushParameter::Angle, 15);
|
|
CHECK_CLOSE(5.0f, brush.get(BrushParameter::Angle), 1e-3f);
|
|
brush.adjust(BrushParameter::Angle, -15);
|
|
CHECK_CLOSE(350.0f, brush.get(BrushParameter::Angle), 1e-3f);
|
|
}
|
|
}
|
|
|
|
SUITE(BrushCommands) {
|
|
TEST(CommandIdsMatchTheReference) {
|
|
CHECK_EQUAL(1000178, (int4) Command::ToolBrushAngle);
|
|
CHECK_EQUAL(1000179, (int4) Command::ToolBrushSize);
|
|
CHECK_EQUAL(1000183, (int4) Command::HoldBrushFlow);
|
|
CHECK_EQUAL(1000185, (int4) Command::HoldBrushOpacity);
|
|
CHECK_EQUAL(1000202, (int4) Command::ResetBrush);
|
|
CHECK_EQUAL(1000206, (int4) Command::BrushAngleDecrease);
|
|
CHECK_EQUAL(1000209, (int4) Command::BrushFlowIncrease);
|
|
CHECK_EQUAL(1000211, (int4) Command::BrushOpacityIncrease);
|
|
CHECK_EQUAL(std::string("Reset brush"), commandInfo(Command::ResetBrush).label);
|
|
CHECK_EQUAL(std::string("Brush flow tool"), commandInfo(Command::HoldBrushFlow).label);
|
|
CHECK_EQUAL(std::string("Opacity increase"), commandInfo(Command::BrushOpacityIncrease).label);
|
|
}
|
|
|
|
TEST(BracketKeysResizeTheBrush) {
|
|
ToolsScene scene;
|
|
UiDriver driver(scene.root, { 1280, 800 });
|
|
scene.brush.setSize(100);
|
|
driver.key(InputID::RIGHT_BRACKET);
|
|
CHECK_CLOSE(115.0f, scene.brush.size(), 1e-3f);
|
|
driver.holdKey(InputID::LEFT_BRACKET, 2);
|
|
CHECK_CLOSE(71.0f, scene.brush.size(), 1e-3f);
|
|
CHECK_EQUAL(std::string("71 px"), scene.root.lastNotice());
|
|
CHECK_EQUAL(std::string("71 px"), scene.root.toolBar.brushSize.text());
|
|
}
|
|
|
|
TEST(DigitKeysSetBrushOpacity) {
|
|
ToolsScene scene;
|
|
UiDriver driver(scene.root, { 1280, 800 });
|
|
driver.key(InputID::N3);
|
|
CHECK_CLOSE(0.3f, scene.brush.opacity(), 1e-4f);
|
|
driver.key(InputID::N0);
|
|
CHECK_CLOSE(1.0f, scene.brush.opacity(), 1e-4f);
|
|
driver.key(InputID::N1);
|
|
CHECK_CLOSE(0.1f, scene.brush.opacity(), 1e-4f);
|
|
}
|
|
|
|
TEST(StepCommandsAdjustFlowOpacityAndAngle) {
|
|
ToolsScene scene;
|
|
UiDriver driver(scene.root, { 1280, 800 });
|
|
scene.brush.setFlow(0.5f);
|
|
scene.brush.setOpacity(0.5f);
|
|
scene.root.commands.run(Command::BrushFlowIncrease);
|
|
scene.root.commands.run(Command::BrushOpacityDecrease);
|
|
scene.root.commands.run(Command::BrushAngleIncrease);
|
|
CHECK_CLOSE(0.55f, scene.brush.flow(), 1e-4f);
|
|
CHECK_CLOSE(0.45f, scene.brush.opacity(), 1e-4f);
|
|
CHECK_CLOSE(kBrushAngleStep, scene.brush.get(BrushParameter::Angle), 1e-4f);
|
|
}
|
|
|
|
TEST(BrushMenuListsTheBrushCommands) {
|
|
ToolsScene scene;
|
|
UiDriver driver(scene.root, { 1280, 800 });
|
|
Menu& menu = scene.root.brushMenu;
|
|
for (const char* text : { "Reset all brushes",
|
|
"Smaller brush",
|
|
"Bigger brush",
|
|
"Reset brush",
|
|
"Show Brush list",
|
|
"Show Brush options",
|
|
"Adjust",
|
|
"Opacity",
|
|
"Stabilizer" })
|
|
CHECK(findItem(menu, text) != nullptr);
|
|
for (const char* text : { "Reset all brushes (everything)",
|
|
"Reset all brush parameters",
|
|
"Reset all brush stabilization",
|
|
"Reset all brush textures" })
|
|
CHECK(findItem(scene.root.resetMenu, text) != nullptr);
|
|
CHECK_EQUAL((size_t) 10, scene.root.brushOpacityMenu.items().size());
|
|
CHECK_EQUAL(std::string("]"), findItem(menu, "Bigger brush")->entry().shortcut);
|
|
}
|
|
|
|
TEST(ResetMenuResetsTheCurrentBrush) {
|
|
ToolsScene scene;
|
|
UiDriver driver(scene.root, { 1280, 800 });
|
|
const halnf original = scene.brush.size();
|
|
scene.brush.setSize(999);
|
|
scene.brush.setStabilizer(0.95f);
|
|
choose(scene.root.resetMenu, "Reset all brush parameters");
|
|
CHECK_CLOSE(original, scene.brush.size(), 1e-4f);
|
|
CHECK_CLOSE(0.95f, scene.brush.stabilizer(), 1e-4f);
|
|
choose(scene.root.resetMenu, "Reset all brush stabilization");
|
|
CHECK_CLOSE(Brush::kDefaultStabilizer, scene.brush.stabilizer(), 1e-4f);
|
|
}
|
|
|
|
TEST(ResetAllBrushesRestoresThePaintingBrush) {
|
|
ToolsScene scene;
|
|
UiDriver driver(scene.root, { 1280, 800 });
|
|
scene.brush.apply(*findBrushPreset(BrushType::Ink));
|
|
scene.brush.setSize(800);
|
|
driver.click(scene.root.toolStrip.button("eraser"));
|
|
scene.root.commands.run(Command::ResetAllBrushes);
|
|
driver.click(scene.root.toolStrip.button("brush"));
|
|
CHECK(scene.brush.type == BrushType::Round);
|
|
CHECK(scene.brush.size() < 800);
|
|
}
|
|
|
|
TEST(OptionsPanelResetButtonResetsTheBrush) {
|
|
ToolsScene scene;
|
|
UiDriver driver(scene.root, { 1280, 800 });
|
|
const halnf original = scene.brush.size();
|
|
scene.brush.setSize(444);
|
|
scene.root.refreshFromState();
|
|
driver.layout();
|
|
driver.click(scene.root.toolBar.brushSize);
|
|
REQUIRE CHECK(driver.ui().isPopupOpen(scene.root.toolBar.brushPopup));
|
|
driver.click(scene.root.toolBar.brushPopup.reset);
|
|
CHECK_CLOSE(original, scene.brush.size(), 1e-4f);
|
|
CHECK_CLOSE(original, scene.root.toolBar.brushPopup.size.value(), 0.5f);
|
|
}
|
|
|
|
TEST(PanelShortcutsOpenTheBrushPopups) {
|
|
ToolsScene scene;
|
|
UiDriver driver(scene.root, { 1280, 800 });
|
|
driver.moveTo(driver.centerOf(scene.root.canvas));
|
|
driver.key(InputID::F4);
|
|
CHECK(driver.ui().isPopupOpen(scene.root.toolBar.brushListPopup));
|
|
driver.key(InputID::ESCAPE);
|
|
driver.key(InputID::F5);
|
|
CHECK(driver.ui().isPopupOpen(scene.root.toolBar.brushPopup));
|
|
}
|
|
}
|
|
|
|
SUITE(BrushDragTools) {
|
|
TEST(DragValuesFollowTheReferenceFeel) {
|
|
CHECK_CLOSE(
|
|
200.0f, CanvasWidget::adjustedValue(BrushParameter::Size, 100, CanvasWidget::kSizeDoublingPixels), 0.5f
|
|
);
|
|
CHECK_CLOSE(
|
|
50.0f, CanvasWidget::adjustedValue(BrushParameter::Size, 100, -CanvasWidget::kSizeDoublingPixels), 0.5f
|
|
);
|
|
CHECK_CLOSE(10.0f, CanvasWidget::adjustedValue(BrushParameter::Angle, 350, 20), 1e-3f);
|
|
CHECK_CLOSE(0.75f, CanvasWidget::adjustedValue(BrushParameter::Opacity, 0.5f, 50), 1e-4f);
|
|
}
|
|
|
|
TEST(HoldingDAndDraggingResizesWithoutPainting) {
|
|
ToolsScene scene;
|
|
UiDriver driver(scene.root, { 1280, 800 });
|
|
scene.brush.setSize(50);
|
|
const Vec2F center = driver.centerOf(scene.root.canvas);
|
|
driver.moveTo(center);
|
|
const size_t before = strokesOf(scene.document);
|
|
driver.pressKey(InputID::D);
|
|
driver.press();
|
|
CHECK(scene.root.canvas.isAdjustingBrush());
|
|
driver.moveTo(center + Vec2F{ CanvasWidget::kSizeDoublingPixels, 0.0f });
|
|
CHECK_CLOSE(100.0f, scene.brush.size(), 0.5f);
|
|
CHECK_EQUAL(std::string("100 px"), scene.root.toolBar.brushSize.text());
|
|
CHECK(scene.root.canvas.brushCursor().contour);
|
|
bool labelled = false;
|
|
for (const PaintCommand& command : driver.paint().commands())
|
|
if (command.kind == PaintCommand::Kind::Text && command.text == "Size 100 px") labelled = true;
|
|
CHECK(labelled);
|
|
driver.release();
|
|
driver.releaseKey(InputID::D);
|
|
CHECK(!scene.root.canvas.isAdjustingBrush());
|
|
CHECK_EQUAL(before, strokesOf(scene.document));
|
|
|
|
driver.drag(center, center + Vec2F{ 40, 0 });
|
|
CHECK_CLOSE(100.0f, scene.brush.size(), 0.5f);
|
|
CHECK(strokesOf(scene.document) > before);
|
|
}
|
|
|
|
TEST(HoldingADragsTheAngle) {
|
|
ToolsScene scene;
|
|
UiDriver driver(scene.root, { 1280, 800 });
|
|
const Vec2F center = driver.centerOf(scene.root.canvas);
|
|
driver.moveTo(center);
|
|
driver.pressKey(InputID::A);
|
|
driver.press();
|
|
driver.moveTo(center + Vec2F{ 30, 0 });
|
|
driver.release();
|
|
driver.releaseKey(InputID::A);
|
|
CHECK_CLOSE(30.0f, scene.brush.get(BrushParameter::Angle), 0.01f);
|
|
}
|
|
|
|
TEST(FlowAndOpacityHoldsDragTheirAmounts) {
|
|
ToolsScene scene;
|
|
UiDriver driver(scene.root, { 1280, 800 });
|
|
scene.root.commands.shortcuts().bind(plain(InputID::F), Command::HoldBrushFlow);
|
|
scene.root.commands.shortcuts().bind(plain(InputID::O), Command::HoldBrushOpacity);
|
|
scene.brush.setFlow(0.5f);
|
|
scene.brush.setOpacity(0.5f);
|
|
const Vec2F center = driver.centerOf(scene.root.canvas);
|
|
driver.moveTo(center);
|
|
driver.pressKey(InputID::F);
|
|
driver.press();
|
|
driver.moveTo(center + Vec2F{ -40, 0 });
|
|
driver.release();
|
|
driver.releaseKey(InputID::F);
|
|
CHECK_CLOSE(0.3f, scene.brush.flow(), 1e-3f);
|
|
CHECK_CLOSE(0.5f, scene.brush.opacity(), 1e-4f);
|
|
scene.brush.setOpacity(0.2f);
|
|
|
|
driver.moveTo(center);
|
|
driver.pressKey(InputID::O);
|
|
driver.press();
|
|
driver.moveTo(center + Vec2F{ 60, 0 });
|
|
driver.release();
|
|
driver.releaseKey(InputID::O);
|
|
CHECK_CLOSE(0.5f, scene.brush.opacity(), 1e-3f);
|
|
}
|
|
|
|
TEST(BrushSizeToolStaysOnUntilToggledOff) {
|
|
ToolsScene scene;
|
|
UiDriver driver(scene.root, { 1280, 800 });
|
|
scene.brush.setSize(40);
|
|
scene.root.commands.run(Command::ToolBrushSize);
|
|
CHECK(scene.root.commands.isChecked(Command::ToolBrushSize));
|
|
const Vec2F center = driver.centerOf(scene.root.canvas);
|
|
driver.drag(center, center + Vec2F{ CanvasWidget::kSizeDoublingPixels, 0.0f });
|
|
CHECK_CLOSE(80.0f, scene.brush.size(), 0.5f);
|
|
driver.drag(center, center + Vec2F{ CanvasWidget::kSizeDoublingPixels, 0.0f });
|
|
CHECK_CLOSE(160.0f, scene.brush.size(), 0.5f);
|
|
scene.root.commands.run(Command::ToolBrushSize);
|
|
CHECK(!scene.root.commands.isChecked(Command::ToolBrushSize));
|
|
CHECK(!scene.root.canvas.adjustTool().has_value());
|
|
}
|
|
}
|
|
|
|
SUITE(TabletBrushTools) {
|
|
TEST(TabletSettingsReachTheBrushMenu) {
|
|
TabletScene scene;
|
|
UiDriver driver(scene.studio, { 1280, 800 });
|
|
driver.layout();
|
|
bool found = false;
|
|
for (const auto& entry : scene.classic.mainMenu.items())
|
|
if (entry->entry().text == "Brush" && entry->entry().submenu == &scene.classic.brushMenu) found = true;
|
|
CHECK(found);
|
|
CHECK(findItem(scene.classic.brushMenu, "Reset brush") != nullptr);
|
|
}
|
|
}
|