Files
panopainter/src/app_core/document_layer.h

332 lines
11 KiB
C++

#pragma once
#include "foundation/result.h"
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <string>
#include <string_view>
#include <utility>
namespace pp::app {
inline constexpr std::size_t document_layer_name_max_length = 128;
inline constexpr int document_layer_legacy_blend_mode_count = 5;
enum class DocumentLayerRenameAction {
no_op_same_name,
rename_and_record_undo,
};
enum class DocumentLayerOperation {
add,
duplicate,
select,
reorder,
remove,
set_opacity,
set_visibility,
set_alpha_lock,
set_blend_mode,
set_highlight,
};
struct DocumentLayerRenamePlan {
std::string old_name;
std::string new_name;
DocumentLayerRenameAction action = DocumentLayerRenameAction::no_op_same_name;
};
struct DocumentLayerOperationPlan {
DocumentLayerOperation operation = DocumentLayerOperation::select;
int index = 0;
int from_index = 0;
int to_index = 0;
int insert_index = 0;
int source_index = 0;
std::string name;
float opacity = 1.0F;
bool flag = false;
int blend_mode = 0;
bool mutates_document = false;
bool marks_unsaved = false;
bool reloads_animation_layers = false;
bool updates_title = false;
};
[[nodiscard]] inline pp::foundation::Status validate_layer_index(
int layer_count,
int index) noexcept
{
if (layer_count <= 0) {
return pp::foundation::Status::invalid_argument("document must contain at least one layer");
}
if (index < 0 || index >= layer_count) {
return pp::foundation::Status::out_of_range("layer index is outside the document");
}
return pp::foundation::Status::success();
}
[[nodiscard]] inline pp::foundation::Status validate_layer_insert_index(
int layer_count,
int index) noexcept
{
if (layer_count < 0) {
return pp::foundation::Status::invalid_argument("layer count must not be negative");
}
if (index < 0 || index > layer_count) {
return pp::foundation::Status::out_of_range("layer insert index is outside the document");
}
return pp::foundation::Status::success();
}
[[nodiscard]] inline pp::foundation::Result<DocumentLayerRenamePlan> plan_document_layer_rename(
std::string_view old_name,
std::string_view requested_name)
{
if (requested_name.empty()) {
return pp::foundation::Result<DocumentLayerRenamePlan>::failure(
pp::foundation::Status::invalid_argument("layer name must not be empty"));
}
if (requested_name.size() > document_layer_name_max_length) {
return pp::foundation::Result<DocumentLayerRenamePlan>::failure(
pp::foundation::Status::out_of_range("layer name length exceeds the configured limit"));
}
DocumentLayerRenamePlan plan;
plan.old_name = std::string(old_name);
plan.new_name = std::string(requested_name);
plan.action = old_name == requested_name
? DocumentLayerRenameAction::no_op_same_name
: DocumentLayerRenameAction::rename_and_record_undo;
return pp::foundation::Result<DocumentLayerRenamePlan>::success(std::move(plan));
}
[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_add(
int layer_count,
int insert_index,
std::string_view name)
{
const auto index_status = validate_layer_insert_index(layer_count, insert_index);
if (!index_status.ok()) {
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
}
const auto rename = plan_document_layer_rename({}, name);
if (!rename) {
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(rename.status());
}
DocumentLayerOperationPlan plan;
plan.operation = DocumentLayerOperation::add;
plan.insert_index = insert_index;
plan.name = std::string(name);
plan.mutates_document = true;
plan.marks_unsaved = true;
plan.reloads_animation_layers = true;
plan.updates_title = true;
return pp::foundation::Result<DocumentLayerOperationPlan>::success(std::move(plan));
}
[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_duplicate(
int layer_count,
int source_index)
{
const auto index_status = validate_layer_index(layer_count, source_index);
if (!index_status.ok()) {
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
}
DocumentLayerOperationPlan plan;
plan.operation = DocumentLayerOperation::duplicate;
plan.source_index = source_index;
plan.insert_index = source_index + 1;
plan.mutates_document = true;
plan.marks_unsaved = true;
plan.reloads_animation_layers = true;
plan.updates_title = true;
return pp::foundation::Result<DocumentLayerOperationPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_select(
int layer_count,
int index)
{
const auto index_status = validate_layer_index(layer_count, index);
if (!index_status.ok()) {
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
}
DocumentLayerOperationPlan plan;
plan.operation = DocumentLayerOperation::select;
plan.index = index;
plan.reloads_animation_layers = true;
return pp::foundation::Result<DocumentLayerOperationPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_reorder(
int layer_count,
int from_index,
int to_index)
{
auto index_status = validate_layer_index(layer_count, from_index);
if (!index_status.ok()) {
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
}
index_status = validate_layer_index(layer_count, to_index);
if (!index_status.ok()) {
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
}
DocumentLayerOperationPlan plan;
plan.operation = DocumentLayerOperation::reorder;
plan.from_index = from_index;
plan.to_index = to_index;
plan.mutates_document = from_index != to_index;
plan.marks_unsaved = plan.mutates_document;
plan.reloads_animation_layers = plan.mutates_document;
plan.updates_title = plan.mutates_document;
return pp::foundation::Result<DocumentLayerOperationPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_remove(
int layer_count,
int index)
{
const auto index_status = validate_layer_index(layer_count, index);
if (!index_status.ok()) {
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
}
if (layer_count <= 1) {
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(
pp::foundation::Status::invalid_argument("document must keep at least one layer"));
}
DocumentLayerOperationPlan plan;
plan.operation = DocumentLayerOperation::remove;
plan.index = index;
plan.mutates_document = true;
plan.marks_unsaved = true;
plan.reloads_animation_layers = true;
plan.updates_title = true;
return pp::foundation::Result<DocumentLayerOperationPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_opacity(
int layer_count,
int index,
float opacity)
{
const auto index_status = validate_layer_index(layer_count, index);
if (!index_status.ok()) {
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
}
if (!std::isfinite(opacity) || opacity < 0.0F || opacity > 1.0F) {
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(
pp::foundation::Status::out_of_range("layer opacity must be finite and within 0..1"));
}
DocumentLayerOperationPlan plan;
plan.operation = DocumentLayerOperation::set_opacity;
plan.index = index;
plan.opacity = opacity;
plan.mutates_document = true;
plan.marks_unsaved = true;
plan.updates_title = true;
return pp::foundation::Result<DocumentLayerOperationPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_visibility(
int layer_count,
int index,
bool visible)
{
const auto index_status = validate_layer_index(layer_count, index);
if (!index_status.ok()) {
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
}
DocumentLayerOperationPlan plan;
plan.operation = DocumentLayerOperation::set_visibility;
plan.index = index;
plan.flag = visible;
plan.mutates_document = true;
plan.marks_unsaved = true;
plan.reloads_animation_layers = true;
plan.updates_title = true;
return pp::foundation::Result<DocumentLayerOperationPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_alpha_lock(
int layer_count,
int index,
bool locked)
{
const auto index_status = validate_layer_index(layer_count, index);
if (!index_status.ok()) {
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
}
DocumentLayerOperationPlan plan;
plan.operation = DocumentLayerOperation::set_alpha_lock;
plan.index = index;
plan.flag = locked;
plan.mutates_document = true;
plan.marks_unsaved = true;
plan.updates_title = true;
return pp::foundation::Result<DocumentLayerOperationPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_blend_mode(
int layer_count,
int index,
int blend_mode)
{
const auto index_status = validate_layer_index(layer_count, index);
if (!index_status.ok()) {
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
}
if (blend_mode < 0 || blend_mode >= document_layer_legacy_blend_mode_count) {
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(
pp::foundation::Status::out_of_range("layer blend mode is outside the supported range"));
}
DocumentLayerOperationPlan plan;
plan.operation = DocumentLayerOperation::set_blend_mode;
plan.index = index;
plan.blend_mode = blend_mode;
plan.mutates_document = true;
plan.marks_unsaved = true;
plan.updates_title = true;
return pp::foundation::Result<DocumentLayerOperationPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_highlight(
int layer_count,
int index,
bool highlight)
{
const auto index_status = validate_layer_index(layer_count, index);
if (!index_status.ok()) {
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
}
DocumentLayerOperationPlan plan;
plan.operation = DocumentLayerOperation::set_highlight;
plan.index = index;
plan.flag = highlight;
return pp::foundation::Result<DocumentLayerOperationPlan>::success(plan);
}
}