446 lines
17 KiB
C++
446 lines
17 KiB
C++
#include "app_core/brush_ui.h"
|
|
#include "test_harness.h"
|
|
|
|
#include <cmath>
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
namespace {
|
|
|
|
class FakeBrushUiServices final : public pp::app::BrushUiServices {
|
|
public:
|
|
void set_tip_color(float r, float g, float b, float a) override
|
|
{
|
|
color_sets += 1;
|
|
last_r = r;
|
|
last_g = g;
|
|
last_b = b;
|
|
last_a = a;
|
|
call_order += "color;";
|
|
}
|
|
|
|
void set_texture(
|
|
pp::app::BrushUiTextureSlot slot,
|
|
std::string_view path,
|
|
std::string_view thumbnail_path) override
|
|
{
|
|
texture_sets += 1;
|
|
last_slot = slot;
|
|
last_path = std::string(path);
|
|
last_thumbnail_path = std::string(thumbnail_path);
|
|
call_order += "texture;";
|
|
}
|
|
|
|
void replace_brush_from_preset(bool preserve_existing_color, bool load_resources) override
|
|
{
|
|
preset_replacements += 1;
|
|
preserved_color = preserve_existing_color;
|
|
loaded_resources = load_resources;
|
|
call_order += "preset;";
|
|
}
|
|
|
|
void refresh_brush_ui(bool update_color_ui, bool update_brush_ui) override
|
|
{
|
|
refreshes += 1;
|
|
last_update_color_ui = update_color_ui;
|
|
last_update_brush_ui = update_brush_ui;
|
|
call_order += "refresh;";
|
|
}
|
|
|
|
int color_sets = 0;
|
|
int texture_sets = 0;
|
|
int preset_replacements = 0;
|
|
int refreshes = 0;
|
|
float last_r = 0.0F;
|
|
float last_g = 0.0F;
|
|
float last_b = 0.0F;
|
|
float last_a = 0.0F;
|
|
pp::app::BrushUiTextureSlot last_slot = pp::app::BrushUiTextureSlot::tip;
|
|
std::string last_path;
|
|
std::string last_thumbnail_path;
|
|
bool preserved_color = false;
|
|
bool loaded_resources = false;
|
|
bool last_update_color_ui = false;
|
|
bool last_update_brush_ui = false;
|
|
std::string call_order;
|
|
};
|
|
|
|
class FakeBrushTextureListServices final : public pp::app::BrushTextureListServices {
|
|
public:
|
|
pp::foundation::Status add_texture_from_source(
|
|
std::string_view source_path,
|
|
std::string_view high_path,
|
|
std::string_view thumbnail_path,
|
|
std::string_view brush_name,
|
|
bool converts_brush_alpha) override
|
|
{
|
|
if (fail_add) {
|
|
call_order += "add-failed;";
|
|
return pp::foundation::Status::invalid_argument("fake add failure");
|
|
}
|
|
|
|
adds += 1;
|
|
last_source_path = std::string(source_path);
|
|
last_high_path = std::string(high_path);
|
|
last_thumbnail_path = std::string(thumbnail_path);
|
|
last_brush_name = std::string(brush_name);
|
|
last_converts_brush_alpha = converts_brush_alpha;
|
|
call_order += "add;";
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
void remove_texture(int index, bool delete_texture_files) override
|
|
{
|
|
removes += 1;
|
|
last_index = index;
|
|
last_deletes_texture_files = delete_texture_files;
|
|
call_order += "remove;";
|
|
}
|
|
|
|
void move_texture(int from_index, int to_index) override
|
|
{
|
|
moves += 1;
|
|
last_index = from_index;
|
|
last_target_index = to_index;
|
|
call_order += "move;";
|
|
}
|
|
|
|
void select_texture(int index) override
|
|
{
|
|
selections += 1;
|
|
last_target_index = index;
|
|
call_order += "select;";
|
|
}
|
|
|
|
void save_texture_list() override
|
|
{
|
|
saves += 1;
|
|
call_order += "save;";
|
|
}
|
|
|
|
int adds = 0;
|
|
int removes = 0;
|
|
int moves = 0;
|
|
int selections = 0;
|
|
int saves = 0;
|
|
int last_index = -1;
|
|
int last_target_index = -1;
|
|
std::string last_source_path;
|
|
std::string last_high_path;
|
|
std::string last_thumbnail_path;
|
|
std::string last_brush_name;
|
|
bool last_converts_brush_alpha = false;
|
|
bool last_deletes_texture_files = false;
|
|
bool fail_add = false;
|
|
std::string call_order;
|
|
};
|
|
|
|
void color_plan_validates_all_channels(pp::tests::Harness& harness)
|
|
{
|
|
const auto plan = pp::app::plan_brush_ui_color(0.25F, 0.5F, 0.75F, 1.0F);
|
|
PP_EXPECT(harness, plan);
|
|
if (plan) {
|
|
PP_EXPECT(harness, plan.value().operation == pp::app::BrushUiOperation::set_tip_color);
|
|
PP_EXPECT(harness, plan.value().r == 0.25F);
|
|
PP_EXPECT(harness, plan.value().g == 0.5F);
|
|
PP_EXPECT(harness, plan.value().b == 0.75F);
|
|
PP_EXPECT(harness, plan.value().a == 1.0F);
|
|
PP_EXPECT(harness, plan.value().mutates_brush);
|
|
PP_EXPECT(harness, plan.value().update_color_ui);
|
|
PP_EXPECT(harness, !plan.value().update_brush_ui);
|
|
}
|
|
|
|
PP_EXPECT(harness, !pp::app::plan_brush_ui_color(-0.01F, 0.5F, 0.5F, 1.0F));
|
|
PP_EXPECT(harness, !pp::app::plan_brush_ui_color(0.5F, 1.01F, 0.5F, 1.0F));
|
|
PP_EXPECT(harness, !pp::app::plan_brush_ui_color(0.5F, 0.5F, std::nanf(""), 1.0F));
|
|
}
|
|
|
|
void texture_plan_validates_path_and_slot(pp::tests::Harness& harness)
|
|
{
|
|
const auto plan = pp::app::plan_brush_ui_texture(
|
|
pp::app::BrushUiTextureSlot::pattern,
|
|
"data/patterns/noise.png",
|
|
"data/patterns/thumbs/noise.png");
|
|
PP_EXPECT(harness, plan);
|
|
if (plan) {
|
|
PP_EXPECT(harness, plan.value().operation == pp::app::BrushUiOperation::set_texture);
|
|
PP_EXPECT(harness, plan.value().texture_slot == pp::app::BrushUiTextureSlot::pattern);
|
|
PP_EXPECT(harness, plan.value().path == "data/patterns/noise.png");
|
|
PP_EXPECT(harness, plan.value().thumbnail_path == "data/patterns/thumbs/noise.png");
|
|
PP_EXPECT(harness, plan.value().loads_brush_resources);
|
|
PP_EXPECT(harness, plan.value().update_color_ui);
|
|
PP_EXPECT(harness, plan.value().update_brush_ui);
|
|
}
|
|
|
|
PP_EXPECT(
|
|
harness,
|
|
!pp::app::plan_brush_ui_texture(pp::app::BrushUiTextureSlot::tip, "", "thumb.png"));
|
|
}
|
|
|
|
void preset_plan_preserves_color_and_requires_brush(pp::tests::Harness& harness)
|
|
{
|
|
const auto plan = pp::app::plan_brush_ui_preset_replace(true);
|
|
PP_EXPECT(harness, plan);
|
|
if (plan) {
|
|
PP_EXPECT(harness, plan.value().operation == pp::app::BrushUiOperation::replace_brush_from_preset);
|
|
PP_EXPECT(harness, plan.value().preserves_existing_color);
|
|
PP_EXPECT(harness, plan.value().loads_brush_resources);
|
|
PP_EXPECT(harness, plan.value().update_color_ui);
|
|
PP_EXPECT(harness, plan.value().update_brush_ui);
|
|
}
|
|
|
|
PP_EXPECT(harness, !pp::app::plan_brush_ui_preset_replace(false));
|
|
}
|
|
|
|
void stroke_settings_plan_updates_brush_preview(pp::tests::Harness& harness)
|
|
{
|
|
const auto plan = pp::app::plan_brush_ui_stroke_settings_changed();
|
|
PP_EXPECT(harness, plan.operation == pp::app::BrushUiOperation::stroke_settings_changed);
|
|
PP_EXPECT(harness, plan.mutates_brush);
|
|
PP_EXPECT(harness, plan.update_color_ui);
|
|
PP_EXPECT(harness, plan.update_brush_ui);
|
|
PP_EXPECT(harness, !plan.loads_brush_resources);
|
|
}
|
|
|
|
void texture_list_add_plans_target_paths_and_rejects_bad_input(pp::tests::Harness& harness)
|
|
{
|
|
const auto plan = pp::app::plan_brush_texture_list_add(
|
|
"brushes",
|
|
"D:/Paint/data",
|
|
"C:/Users/artist/My Brush.JPG");
|
|
PP_EXPECT(harness, plan);
|
|
if (plan) {
|
|
PP_EXPECT(harness, plan.value().operation == pp::app::BrushTextureListOperation::add_texture);
|
|
PP_EXPECT(harness, plan.value().source_path == "C:/Users/artist/My Brush.JPG");
|
|
PP_EXPECT(harness, plan.value().brush_name == "My Brush");
|
|
PP_EXPECT(harness, plan.value().high_path == "D:/Paint/data/brushes/My Brush.png");
|
|
PP_EXPECT(harness, plan.value().thumbnail_path == "D:/Paint/data/brushes/thumbs/My Brush.png");
|
|
PP_EXPECT(harness, plan.value().user_texture);
|
|
PP_EXPECT(harness, plan.value().saves_list);
|
|
PP_EXPECT(harness, plan.value().converts_brush_alpha);
|
|
}
|
|
|
|
const auto pattern = pp::app::plan_brush_texture_list_add(
|
|
"patterns",
|
|
"D:/Paint/data",
|
|
R"(C:\Textures\noise.png)");
|
|
PP_EXPECT(harness, pattern);
|
|
if (pattern) {
|
|
PP_EXPECT(harness, !pattern.value().converts_brush_alpha);
|
|
PP_EXPECT(harness, pattern.value().brush_name == "noise");
|
|
}
|
|
|
|
PP_EXPECT(harness, !pp::app::plan_brush_texture_list_add("", "D:/Paint/data", "a.png"));
|
|
PP_EXPECT(harness, !pp::app::plan_brush_texture_list_add("brushes", "", "a.png"));
|
|
PP_EXPECT(harness, !pp::app::plan_brush_texture_list_add("brushes", "D:/Paint/data", "no-extension"));
|
|
PP_EXPECT(harness, !pp::app::plan_brush_texture_list_add("brushes", "D:/Paint/data", "C:/dir/"));
|
|
}
|
|
|
|
void texture_list_remove_and_move_plans_handle_edges(pp::tests::Harness& harness)
|
|
{
|
|
const auto remove_middle = pp::app::plan_brush_texture_list_remove(3, 1, true);
|
|
PP_EXPECT(harness, remove_middle);
|
|
if (remove_middle) {
|
|
PP_EXPECT(harness, remove_middle.value().operation == pp::app::BrushTextureListOperation::remove_texture);
|
|
PP_EXPECT(harness, remove_middle.value().current_index == 1);
|
|
PP_EXPECT(harness, remove_middle.value().target_index == 1);
|
|
PP_EXPECT(harness, remove_middle.value().deletes_texture_files);
|
|
PP_EXPECT(harness, remove_middle.value().notifies_selection);
|
|
PP_EXPECT(harness, remove_middle.value().saves_list);
|
|
}
|
|
|
|
const auto remove_last = pp::app::plan_brush_texture_list_remove(1, 0, false);
|
|
PP_EXPECT(harness, remove_last);
|
|
if (remove_last) {
|
|
PP_EXPECT(harness, remove_last.value().target_index == -1);
|
|
PP_EXPECT(harness, !remove_last.value().deletes_texture_files);
|
|
PP_EXPECT(harness, !remove_last.value().notifies_selection);
|
|
}
|
|
|
|
const auto move_up_edge = pp::app::plan_brush_texture_list_move(3, 0, -1);
|
|
PP_EXPECT(harness, move_up_edge);
|
|
if (move_up_edge) {
|
|
PP_EXPECT(harness, move_up_edge.value().operation == pp::app::BrushTextureListOperation::move_texture);
|
|
PP_EXPECT(harness, move_up_edge.value().target_index == 0);
|
|
PP_EXPECT(harness, move_up_edge.value().no_op);
|
|
}
|
|
|
|
const auto move_down = pp::app::plan_brush_texture_list_move(3, 1, 1);
|
|
PP_EXPECT(harness, move_down);
|
|
if (move_down) {
|
|
PP_EXPECT(harness, move_down.value().target_index == 2);
|
|
PP_EXPECT(harness, !move_down.value().no_op);
|
|
}
|
|
|
|
PP_EXPECT(harness, !pp::app::plan_brush_texture_list_remove(0, 0, true));
|
|
PP_EXPECT(harness, !pp::app::plan_brush_texture_list_remove(2, 2, true));
|
|
PP_EXPECT(harness, !pp::app::plan_brush_texture_list_move(2, 0, 0));
|
|
PP_EXPECT(harness, !pp::app::plan_brush_texture_list_move(2, -1, 1));
|
|
}
|
|
|
|
void executor_dispatches_color_and_refresh(pp::tests::Harness& harness)
|
|
{
|
|
FakeBrushUiServices services;
|
|
const auto plan = pp::app::plan_brush_ui_color(0.25F, 0.5F, 0.75F, 1.0F);
|
|
PP_EXPECT(harness, plan);
|
|
if (plan) {
|
|
PP_EXPECT(harness, pp::app::execute_brush_ui_plan(plan.value(), services).ok());
|
|
}
|
|
|
|
PP_EXPECT(harness, services.color_sets == 1);
|
|
PP_EXPECT(harness, services.last_r == 0.25F);
|
|
PP_EXPECT(harness, services.last_g == 0.5F);
|
|
PP_EXPECT(harness, services.last_b == 0.75F);
|
|
PP_EXPECT(harness, services.last_a == 1.0F);
|
|
PP_EXPECT(harness, services.refreshes == 1);
|
|
PP_EXPECT(harness, services.last_update_color_ui);
|
|
PP_EXPECT(harness, !services.last_update_brush_ui);
|
|
PP_EXPECT(harness, services.call_order == "color;refresh;");
|
|
}
|
|
|
|
void executor_dispatches_texture_and_preset(pp::tests::Harness& harness)
|
|
{
|
|
FakeBrushUiServices services;
|
|
|
|
const auto texture = pp::app::plan_brush_ui_texture(
|
|
pp::app::BrushUiTextureSlot::dual,
|
|
"data/brushes/dual.png",
|
|
"data/brushes/thumbs/dual.png");
|
|
PP_EXPECT(harness, texture);
|
|
if (texture) {
|
|
PP_EXPECT(harness, pp::app::execute_brush_ui_plan(texture.value(), services).ok());
|
|
}
|
|
|
|
const auto preset = pp::app::plan_brush_ui_preset_replace(true);
|
|
PP_EXPECT(harness, preset);
|
|
if (preset) {
|
|
PP_EXPECT(harness, pp::app::execute_brush_ui_plan(preset.value(), services).ok());
|
|
}
|
|
|
|
PP_EXPECT(harness, services.texture_sets == 1);
|
|
PP_EXPECT(harness, services.last_slot == pp::app::BrushUiTextureSlot::dual);
|
|
PP_EXPECT(harness, services.last_path == "data/brushes/dual.png");
|
|
PP_EXPECT(harness, services.last_thumbnail_path == "data/brushes/thumbs/dual.png");
|
|
PP_EXPECT(harness, services.preset_replacements == 1);
|
|
PP_EXPECT(harness, services.preserved_color);
|
|
PP_EXPECT(harness, services.loaded_resources);
|
|
PP_EXPECT(harness, services.refreshes == 2);
|
|
PP_EXPECT(harness, services.call_order == "texture;refresh;preset;refresh;");
|
|
}
|
|
|
|
void executor_dispatches_stroke_refresh_only(pp::tests::Harness& harness)
|
|
{
|
|
FakeBrushUiServices services;
|
|
const auto plan = pp::app::plan_brush_ui_stroke_settings_changed();
|
|
PP_EXPECT(harness, pp::app::execute_brush_ui_plan(plan, services).ok());
|
|
|
|
PP_EXPECT(harness, services.color_sets == 0);
|
|
PP_EXPECT(harness, services.texture_sets == 0);
|
|
PP_EXPECT(harness, services.preset_replacements == 0);
|
|
PP_EXPECT(harness, services.refreshes == 1);
|
|
PP_EXPECT(harness, services.last_update_color_ui);
|
|
PP_EXPECT(harness, services.last_update_brush_ui);
|
|
PP_EXPECT(harness, services.call_order == "refresh;");
|
|
}
|
|
|
|
void texture_list_executor_dispatches_and_preserves_failure(pp::tests::Harness& harness)
|
|
{
|
|
FakeBrushTextureListServices services;
|
|
|
|
const auto add = pp::app::plan_brush_texture_list_add("brushes", "D:/Paint/data", "C:/Temp/soft.png");
|
|
PP_EXPECT(harness, add);
|
|
if (add) {
|
|
PP_EXPECT(harness, pp::app::execute_brush_texture_list_plan(add.value(), services).ok());
|
|
}
|
|
|
|
const auto remove = pp::app::plan_brush_texture_list_remove(3, 2, true);
|
|
PP_EXPECT(harness, remove);
|
|
if (remove) {
|
|
PP_EXPECT(harness, pp::app::execute_brush_texture_list_plan(remove.value(), services).ok());
|
|
}
|
|
|
|
const auto move = pp::app::plan_brush_texture_list_move(3, 1, -1);
|
|
PP_EXPECT(harness, move);
|
|
if (move) {
|
|
PP_EXPECT(harness, pp::app::execute_brush_texture_list_plan(move.value(), services).ok());
|
|
}
|
|
|
|
PP_EXPECT(harness, services.adds == 1);
|
|
PP_EXPECT(harness, services.last_source_path == "C:/Temp/soft.png");
|
|
PP_EXPECT(harness, services.last_high_path == "D:/Paint/data/brushes/soft.png");
|
|
PP_EXPECT(harness, services.last_thumbnail_path == "D:/Paint/data/brushes/thumbs/soft.png");
|
|
PP_EXPECT(harness, services.last_brush_name == "soft");
|
|
PP_EXPECT(harness, services.last_converts_brush_alpha);
|
|
PP_EXPECT(harness, services.removes == 1);
|
|
PP_EXPECT(harness, services.moves == 1);
|
|
PP_EXPECT(harness, services.selections == 1);
|
|
PP_EXPECT(harness, services.saves == 3);
|
|
PP_EXPECT(harness, services.last_deletes_texture_files);
|
|
PP_EXPECT(harness, services.call_order == "add;save;remove;select;save;move;save;");
|
|
|
|
FakeBrushTextureListServices failing_services;
|
|
failing_services.fail_add = true;
|
|
PP_EXPECT(harness, add);
|
|
if (add) {
|
|
PP_EXPECT(harness, !pp::app::execute_brush_texture_list_plan(add.value(), failing_services).ok());
|
|
}
|
|
PP_EXPECT(harness, failing_services.saves == 0);
|
|
PP_EXPECT(harness, failing_services.call_order == "add-failed;");
|
|
}
|
|
|
|
void executor_rejects_invalid_plan_payloads(pp::tests::Harness& harness)
|
|
{
|
|
FakeBrushUiServices services;
|
|
|
|
pp::app::BrushUiPlan color;
|
|
color.operation = pp::app::BrushUiOperation::set_tip_color;
|
|
color.r = std::nanf("");
|
|
color.g = 0.0F;
|
|
color.b = 0.0F;
|
|
color.a = 1.0F;
|
|
PP_EXPECT(harness, !pp::app::execute_brush_ui_plan(color, services).ok());
|
|
|
|
pp::app::BrushUiPlan texture;
|
|
texture.operation = pp::app::BrushUiOperation::set_texture;
|
|
texture.texture_slot = pp::app::BrushUiTextureSlot::tip;
|
|
texture.path.clear();
|
|
PP_EXPECT(harness, !pp::app::execute_brush_ui_plan(texture, services).ok());
|
|
|
|
PP_EXPECT(harness, services.color_sets == 0);
|
|
PP_EXPECT(harness, services.texture_sets == 0);
|
|
PP_EXPECT(harness, services.refreshes == 0);
|
|
|
|
FakeBrushTextureListServices list_services;
|
|
pp::app::BrushTextureListPlan add;
|
|
add.operation = pp::app::BrushTextureListOperation::add_texture;
|
|
add.source_path = "source.png";
|
|
PP_EXPECT(harness, !pp::app::execute_brush_texture_list_plan(add, list_services).ok());
|
|
|
|
pp::app::BrushTextureListPlan move;
|
|
move.operation = pp::app::BrushTextureListOperation::move_texture;
|
|
move.item_count = 1;
|
|
move.current_index = 0;
|
|
move.target_index = 1;
|
|
PP_EXPECT(harness, !pp::app::execute_brush_texture_list_plan(move, list_services).ok());
|
|
PP_EXPECT(harness, list_services.call_order.empty());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main()
|
|
{
|
|
pp::tests::Harness harness;
|
|
harness.run("color plan validates all channels", color_plan_validates_all_channels);
|
|
harness.run("texture plan validates path and slot", texture_plan_validates_path_and_slot);
|
|
harness.run("preset plan preserves color and requires brush", preset_plan_preserves_color_and_requires_brush);
|
|
harness.run("stroke settings plan updates brush preview", stroke_settings_plan_updates_brush_preview);
|
|
harness.run("texture list add plans target paths and rejects bad input", texture_list_add_plans_target_paths_and_rejects_bad_input);
|
|
harness.run("texture list remove and move plans handle edges", texture_list_remove_and_move_plans_handle_edges);
|
|
harness.run("executor dispatches color and refresh", executor_dispatches_color_and_refresh);
|
|
harness.run("executor dispatches texture and preset", executor_dispatches_texture_and_preset);
|
|
harness.run("executor dispatches stroke refresh only", executor_dispatches_stroke_refresh_only);
|
|
harness.run("texture list executor dispatches and preserves failure", texture_list_executor_dispatches_and_preserves_failure);
|
|
harness.run("executor rejects invalid plan payloads", executor_rejects_invalid_plan_payloads);
|
|
return harness.finish();
|
|
}
|