Add brush texture list boundary
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -21,6 +22,12 @@ enum class BrushUiOperation {
|
||||
stroke_settings_changed,
|
||||
};
|
||||
|
||||
enum class BrushTextureListOperation {
|
||||
add_texture,
|
||||
remove_texture,
|
||||
move_texture,
|
||||
};
|
||||
|
||||
struct BrushUiPlan {
|
||||
BrushUiOperation operation = BrushUiOperation::stroke_settings_changed;
|
||||
BrushUiTextureSlot texture_slot = BrushUiTextureSlot::tip;
|
||||
@@ -37,6 +44,24 @@ struct BrushUiPlan {
|
||||
bool update_brush_ui = false;
|
||||
};
|
||||
|
||||
struct BrushTextureListPlan {
|
||||
BrushTextureListOperation operation = BrushTextureListOperation::add_texture;
|
||||
int item_count = 0;
|
||||
int current_index = -1;
|
||||
int target_index = -1;
|
||||
int move_offset = 0;
|
||||
std::string source_path;
|
||||
std::string high_path;
|
||||
std::string thumbnail_path;
|
||||
std::string brush_name;
|
||||
bool user_texture = false;
|
||||
bool deletes_texture_files = false;
|
||||
bool saves_list = false;
|
||||
bool notifies_selection = false;
|
||||
bool converts_brush_alpha = false;
|
||||
bool no_op = false;
|
||||
};
|
||||
|
||||
class BrushUiServices {
|
||||
public:
|
||||
virtual ~BrushUiServices() = default;
|
||||
@@ -47,6 +72,41 @@ public:
|
||||
virtual void refresh_brush_ui(bool update_color_ui, bool update_brush_ui) = 0;
|
||||
};
|
||||
|
||||
class BrushTextureListServices {
|
||||
public:
|
||||
virtual ~BrushTextureListServices() = default;
|
||||
|
||||
virtual 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) = 0;
|
||||
virtual void remove_texture(int index, bool delete_texture_files) = 0;
|
||||
virtual void move_texture(int from_index, int to_index) = 0;
|
||||
virtual void select_texture(int index) = 0;
|
||||
virtual void save_texture_list() = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<std::string_view> brush_texture_source_stem(
|
||||
std::string_view source_path) noexcept
|
||||
{
|
||||
const auto slash = source_path.find_last_of("/\\");
|
||||
const auto name_begin = slash == std::string_view::npos ? 0U : slash + 1U;
|
||||
if (name_begin >= source_path.size()) {
|
||||
return pp::foundation::Result<std::string_view>::failure(
|
||||
pp::foundation::Status::invalid_argument("brush texture source path must contain a file name"));
|
||||
}
|
||||
|
||||
const auto dot = source_path.find_last_of('.');
|
||||
if (dot == std::string_view::npos || dot <= name_begin || dot + 1U >= source_path.size()) {
|
||||
return pp::foundation::Result<std::string_view>::failure(
|
||||
pp::foundation::Status::invalid_argument("brush texture source path must include a file extension"));
|
||||
}
|
||||
|
||||
return pp::foundation::Result<std::string_view>::success(source_path.substr(name_begin, dot - name_begin));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_brush_ui_color_channel(float value) noexcept
|
||||
{
|
||||
if (!std::isfinite(value) || value < 0.0F || value > 1.0F) {
|
||||
@@ -129,6 +189,93 @@ public:
|
||||
return plan;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<BrushTextureListPlan> plan_brush_texture_list_add(
|
||||
std::string_view directory_name,
|
||||
std::string_view data_path,
|
||||
std::string_view source_path)
|
||||
{
|
||||
if (directory_name.empty()) {
|
||||
return pp::foundation::Result<BrushTextureListPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("brush texture directory must not be empty"));
|
||||
}
|
||||
if (data_path.empty()) {
|
||||
return pp::foundation::Result<BrushTextureListPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("brush texture data path must not be empty"));
|
||||
}
|
||||
|
||||
const auto stem = brush_texture_source_stem(source_path);
|
||||
if (!stem) {
|
||||
return pp::foundation::Result<BrushTextureListPlan>::failure(stem.status());
|
||||
}
|
||||
|
||||
BrushTextureListPlan plan;
|
||||
plan.operation = BrushTextureListOperation::add_texture;
|
||||
plan.source_path = std::string(source_path);
|
||||
plan.brush_name = std::string(stem.value());
|
||||
plan.high_path = std::string(data_path) + "/" + std::string(directory_name) + "/" + plan.brush_name + ".png";
|
||||
plan.thumbnail_path = std::string(data_path) + "/" + std::string(directory_name) + "/thumbs/"
|
||||
+ plan.brush_name + ".png";
|
||||
plan.user_texture = true;
|
||||
plan.saves_list = true;
|
||||
plan.converts_brush_alpha = directory_name == "brushes";
|
||||
return pp::foundation::Result<BrushTextureListPlan>::success(std::move(plan));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<BrushTextureListPlan> plan_brush_texture_list_remove(
|
||||
int item_count,
|
||||
int current_index,
|
||||
bool current_is_user_texture)
|
||||
{
|
||||
if (item_count <= 0) {
|
||||
return pp::foundation::Result<BrushTextureListPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("brush texture list must contain an item to remove"));
|
||||
}
|
||||
if (current_index < 0 || current_index >= item_count) {
|
||||
return pp::foundation::Result<BrushTextureListPlan>::failure(
|
||||
pp::foundation::Status::out_of_range("selected brush texture index is outside the list"));
|
||||
}
|
||||
|
||||
BrushTextureListPlan plan;
|
||||
plan.operation = BrushTextureListOperation::remove_texture;
|
||||
plan.item_count = item_count;
|
||||
plan.current_index = current_index;
|
||||
plan.target_index = item_count > 1 ? std::min(current_index, item_count - 2) : -1;
|
||||
plan.user_texture = current_is_user_texture;
|
||||
plan.deletes_texture_files = current_is_user_texture;
|
||||
plan.saves_list = true;
|
||||
plan.notifies_selection = plan.target_index >= 0;
|
||||
return pp::foundation::Result<BrushTextureListPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<BrushTextureListPlan> plan_brush_texture_list_move(
|
||||
int item_count,
|
||||
int current_index,
|
||||
int offset)
|
||||
{
|
||||
if (item_count <= 0) {
|
||||
return pp::foundation::Result<BrushTextureListPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("brush texture list must contain an item to move"));
|
||||
}
|
||||
if (current_index < 0 || current_index >= item_count) {
|
||||
return pp::foundation::Result<BrushTextureListPlan>::failure(
|
||||
pp::foundation::Status::out_of_range("selected brush texture index is outside the list"));
|
||||
}
|
||||
if (offset == 0) {
|
||||
return pp::foundation::Result<BrushTextureListPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("brush texture move offset must not be zero"));
|
||||
}
|
||||
|
||||
BrushTextureListPlan plan;
|
||||
plan.operation = BrushTextureListOperation::move_texture;
|
||||
plan.item_count = item_count;
|
||||
plan.current_index = current_index;
|
||||
plan.target_index = std::clamp(current_index + offset, 0, item_count - 1);
|
||||
plan.move_offset = offset;
|
||||
plan.saves_list = true;
|
||||
plan.no_op = plan.target_index == current_index;
|
||||
return pp::foundation::Result<BrushTextureListPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_brush_ui_plan(
|
||||
const BrushUiPlan& plan,
|
||||
BrushUiServices& services)
|
||||
@@ -168,4 +315,59 @@ public:
|
||||
return pp::foundation::Status::invalid_argument("unknown brush UI operation");
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_brush_texture_list_plan(
|
||||
const BrushTextureListPlan& plan,
|
||||
BrushTextureListServices& services)
|
||||
{
|
||||
switch (plan.operation) {
|
||||
case BrushTextureListOperation::add_texture:
|
||||
{
|
||||
if (plan.source_path.empty() || plan.high_path.empty() || plan.thumbnail_path.empty()
|
||||
|| plan.brush_name.empty()) {
|
||||
return pp::foundation::Status::invalid_argument("brush texture add plan has incomplete paths");
|
||||
}
|
||||
|
||||
const auto add_status = services.add_texture_from_source(
|
||||
plan.source_path,
|
||||
plan.high_path,
|
||||
plan.thumbnail_path,
|
||||
plan.brush_name,
|
||||
plan.converts_brush_alpha);
|
||||
if (!add_status.ok()) {
|
||||
return add_status;
|
||||
}
|
||||
if (plan.saves_list) {
|
||||
services.save_texture_list();
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
case BrushTextureListOperation::remove_texture:
|
||||
if (plan.item_count <= 0 || plan.current_index < 0 || plan.current_index >= plan.item_count) {
|
||||
return pp::foundation::Status::out_of_range("brush texture remove plan has invalid selection");
|
||||
}
|
||||
services.remove_texture(plan.current_index, plan.deletes_texture_files);
|
||||
if (plan.notifies_selection && plan.target_index >= 0) {
|
||||
services.select_texture(plan.target_index);
|
||||
}
|
||||
if (plan.saves_list) {
|
||||
services.save_texture_list();
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
|
||||
case BrushTextureListOperation::move_texture:
|
||||
if (plan.item_count <= 0 || plan.current_index < 0 || plan.current_index >= plan.item_count
|
||||
|| plan.target_index < 0 || plan.target_index >= plan.item_count) {
|
||||
return pp::foundation::Status::out_of_range("brush texture move plan has invalid indices");
|
||||
}
|
||||
services.move_texture(plan.current_index, plan.target_index);
|
||||
if (plan.saves_list) {
|
||||
services.save_texture_list();
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown brush texture list operation");
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
|
||||
Reference in New Issue
Block a user