Route brush preset list planning

This commit is contained in:
2026-06-04 15:15:01 +02:00
parent 79942113ef
commit 47c35fb859
9 changed files with 497 additions and 46 deletions

View File

@@ -28,6 +28,14 @@ enum class BrushTextureListOperation {
move_texture,
};
enum class BrushPresetListOperation {
add_current_brush,
remove_preset,
move_preset,
select_preset,
clear_presets,
};
enum class BrushStrokeControlOperation {
set_float,
set_bool,
@@ -139,6 +147,20 @@ struct BrushTextureListPlan {
bool no_op = false;
};
struct BrushPresetListPlan {
BrushPresetListOperation operation = BrushPresetListOperation::select_preset;
int item_count = 0;
int current_index = -1;
int target_index = -1;
int move_offset = 0;
bool saves_list = false;
bool updates_empty_notification = false;
bool selects_target = false;
bool clears_selection = false;
bool notifies_brush_changed = false;
bool no_op = false;
};
struct BrushStrokeControlPlan {
BrushStrokeControlOperation operation = BrushStrokeControlOperation::set_float;
BrushStrokeFloatSetting float_setting = BrushStrokeFloatSetting::tip_size;
@@ -474,6 +496,122 @@ public:
return pp::foundation::Result<BrushTextureListPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Result<BrushPresetListPlan> plan_brush_preset_list_add(
int item_count,
bool has_current_brush)
{
if (item_count < 0) {
return pp::foundation::Result<BrushPresetListPlan>::failure(
pp::foundation::Status::out_of_range("brush preset item count must not be negative"));
}
if (!has_current_brush) {
return pp::foundation::Result<BrushPresetListPlan>::failure(
pp::foundation::Status::invalid_argument("current brush must be available to add a preset"));
}
BrushPresetListPlan plan;
plan.operation = BrushPresetListOperation::add_current_brush;
plan.item_count = item_count;
plan.target_index = item_count;
plan.saves_list = true;
plan.updates_empty_notification = true;
return pp::foundation::Result<BrushPresetListPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Result<BrushPresetListPlan> plan_brush_preset_list_remove(
int item_count,
int current_index)
{
if (item_count <= 0) {
return pp::foundation::Result<BrushPresetListPlan>::failure(
pp::foundation::Status::invalid_argument("brush preset list must contain an item to remove"));
}
if (current_index < 0 || current_index >= item_count) {
return pp::foundation::Result<BrushPresetListPlan>::failure(
pp::foundation::Status::out_of_range("selected brush preset index is outside the list"));
}
BrushPresetListPlan plan;
plan.operation = BrushPresetListOperation::remove_preset;
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.saves_list = true;
plan.updates_empty_notification = true;
plan.selects_target = plan.target_index >= 0;
plan.clears_selection = plan.target_index < 0;
return pp::foundation::Result<BrushPresetListPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Result<BrushPresetListPlan> plan_brush_preset_list_move(
int item_count,
int current_index,
int offset)
{
if (item_count <= 0) {
return pp::foundation::Result<BrushPresetListPlan>::failure(
pp::foundation::Status::invalid_argument("brush preset list must contain an item to move"));
}
if (current_index < 0 || current_index >= item_count) {
return pp::foundation::Result<BrushPresetListPlan>::failure(
pp::foundation::Status::out_of_range("selected brush preset index is outside the list"));
}
if (offset == 0) {
return pp::foundation::Result<BrushPresetListPlan>::failure(
pp::foundation::Status::invalid_argument("brush preset move offset must not be zero"));
}
BrushPresetListPlan plan;
plan.operation = BrushPresetListOperation::move_preset;
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<BrushPresetListPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Result<BrushPresetListPlan> plan_brush_preset_list_select(
int item_count,
int index)
{
if (item_count <= 0) {
return pp::foundation::Result<BrushPresetListPlan>::failure(
pp::foundation::Status::invalid_argument("brush preset list must contain an item to select"));
}
if (index < 0 || index >= item_count) {
return pp::foundation::Result<BrushPresetListPlan>::failure(
pp::foundation::Status::out_of_range("selected brush preset index is outside the list"));
}
BrushPresetListPlan plan;
plan.operation = BrushPresetListOperation::select_preset;
plan.item_count = item_count;
plan.current_index = index;
plan.target_index = index;
plan.selects_target = true;
plan.notifies_brush_changed = true;
return pp::foundation::Result<BrushPresetListPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Result<BrushPresetListPlan> plan_brush_preset_list_clear(int item_count)
{
if (item_count < 0) {
return pp::foundation::Result<BrushPresetListPlan>::failure(
pp::foundation::Status::out_of_range("brush preset item count must not be negative"));
}
BrushPresetListPlan plan;
plan.operation = BrushPresetListOperation::clear_presets;
plan.item_count = item_count;
plan.saves_list = true;
plan.updates_empty_notification = true;
plan.clears_selection = true;
plan.no_op = item_count == 0;
return pp::foundation::Result<BrushPresetListPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Status execute_brush_ui_plan(
const BrushUiPlan& plan,
BrushUiServices& services)

View File

@@ -407,25 +407,97 @@ Node* NodePanelBrushPreset::clone_instantiate() const
return new NodePanelBrushPreset();
}
void NodePanelBrushPreset::execute_preset_list_plan(const pp::app::BrushPresetListPlan& plan)
{
switch (plan.operation)
{
case pp::app::BrushPresetListOperation::add_current_brush:
if (!Canvas::I || !Canvas::I->m_current_brush)
return;
for (auto p : s_panels)
p->add_brush(std::make_shared<Brush>(*Canvas::I->m_current_brush));
break;
case pp::app::BrushPresetListOperation::move_preset:
for (auto p : s_panels)
{
if (plan.current_index >= 0 && plan.current_index < static_cast<int>(p->m_container->m_children.size()))
p->m_container->move_child(p->m_container->m_children[plan.current_index].get(), plan.target_index);
}
break;
case pp::app::BrushPresetListOperation::remove_preset:
for (auto p : s_panels)
{
if (plan.current_index < 0 || plan.current_index >= static_cast<int>(p->m_container->m_children.size()))
continue;
bool new_current = !p->m_current || p->m_container->get_child_index(p->m_current) == plan.current_index;
p->m_container->m_children[plan.current_index]->destroy();
if (plan.clears_selection)
{
p->m_current = nullptr;
}
else if (new_current && plan.selects_target)
{
p->m_current = static_cast<NodeBrushPresetItem*>(p->m_container->m_children[plan.target_index].get());
p->m_current->m_selected = true;
}
p->m_notification->SetVisibility(p->m_container->m_children.size() == 0);
}
break;
case pp::app::BrushPresetListOperation::select_preset:
for (auto p : s_panels)
{
if (p->m_current)
p->m_current->m_selected = false;
p->m_current = static_cast<NodeBrushPresetItem*>(p->m_container->get_child_at(plan.target_index));
p->m_current->m_selected = true;
p->m_interacted = true;
}
if (plan.notifies_brush_changed && on_brush_changed)
on_brush_changed(this, m_current->m_brush);
break;
case pp::app::BrushPresetListOperation::clear_presets:
for (auto p : s_panels)
{
p->m_container->remove_all_children();
p->m_current = nullptr;
p->m_notification->SetVisibility(p->m_container->m_children.size() == 0);
}
break;
}
if (plan.saves_list)
save();
}
void NodePanelBrushPreset::init()
{
init_template_file("data/dialogs/panel-brushes.xml", "tpl-panel-brush-preset");
m_container = find<Node>("brushes");
m_btn_add = find<NodeButtonCustom>("btn-add");
m_btn_add->on_click = [this] (Node*) {
for (auto p : s_panels)
p->add_brush(std::make_shared<Brush>(*Canvas::I->m_current_brush));
save();
const auto plan = pp::app::plan_brush_preset_list_add(
static_cast<int>(m_container->m_children.size()),
Canvas::I && Canvas::I->m_current_brush);
if (plan) {
execute_preset_list_plan(plan.value());
}
};
m_btn_up = find<NodeButtonCustom>("btn-up");
m_btn_up->on_click = [this](Node*) {
if (m_current)
{
int idx = m_container->get_child_index(m_current);
int to = std::max(idx - 1, 0);
for (auto p : s_panels)
p->m_container->move_child(p->m_container->m_children[idx].get(), to);
save();
const auto plan = pp::app::plan_brush_preset_list_move(
static_cast<int>(m_container->m_children.size()),
idx,
-1);
if (plan) {
execute_preset_list_plan(plan.value());
}
}
};
m_btn_down = find<NodeButtonCustom>("btn-down");
@@ -433,10 +505,13 @@ void NodePanelBrushPreset::init()
if (m_current)
{
int idx = m_container->get_child_index(m_current);
int to = std::min(idx + 1, (int)m_container->m_children.size() - 1);
for (auto p : s_panels)
p->m_container->move_child(p->m_container->m_children[idx].get(), to);
save();
const auto plan = pp::app::plan_brush_preset_list_move(
static_cast<int>(m_container->m_children.size()),
idx,
1);
if (plan) {
execute_preset_list_plan(plan.value());
}
}
};
/*
@@ -456,23 +531,12 @@ void NodePanelBrushPreset::init()
if (!m_current)
return;
int index = m_container->get_child_index(m_current);
for (auto p : s_panels)
{
bool new_current = !p->m_current || p->m_container->get_child_index(p->m_current) == index;
p->m_container->m_children[index]->destroy();
if (p->m_container->m_children.empty())
{
p->m_current = nullptr;
}
else if (new_current)
{
int next = std::min<int>((int)p->m_container->m_children.size() - 1, index);
p->m_current = (NodeBrushPresetItem*)p->m_container->m_children[next].get();
p->m_current->m_selected = true;
}
p->m_notification->SetVisibility(p->m_container->m_children.size() == 0);
const auto plan = pp::app::plan_brush_preset_list_remove(
static_cast<int>(m_container->m_children.size()),
index);
if (plan) {
execute_preset_list_plan(plan.value());
}
save();
};
m_btn_menu = find<NodeButtonCustom>("btn-menu");
m_btn_menu->on_click = [this](Node* b) {
@@ -508,8 +572,11 @@ void NodePanelBrushPreset::init()
mb->btn_ok->m_text->set_text("Yes");
mb->btn_cancel->m_text->set_text("No");
mb->btn_ok->on_click = mb->on_submit = [this, mb](Node*) {
App::I->presets->clear_brushes();
App::I->presets->save();
const auto plan = pp::app::plan_brush_preset_list_clear(
static_cast<int>(m_container->m_children.size()));
if (plan) {
execute_preset_list_plan(plan.value());
}
mb->destroy();
};
break;
@@ -576,16 +643,12 @@ kEventResult NodePanelBrushPreset::handle_event(Event* e)
void NodePanelBrushPreset::handle_click(Node* target)
{
int idx = m_container->get_child_index(target);
for (auto p : s_panels)
{
if (p->m_current)
p->m_current->m_selected = false;
p->m_current = (NodeBrushPresetItem*)p->m_container->get_child_at(idx);
p->m_current->m_selected = true;
p->m_interacted = true;
const auto plan = pp::app::plan_brush_preset_list_select(
static_cast<int>(m_container->m_children.size()),
idx);
if (plan) {
execute_preset_list_plan(plan.value());
}
if (on_brush_changed)
on_brush_changed(this, m_current->m_brush);
}
bool NodePanelBrushPreset::save()
@@ -1067,10 +1130,10 @@ bool NodePanelBrushPreset::import_brush(const std::string& path)
void NodePanelBrushPreset::clear_brushes()
{
for (auto p : s_panels)
{
p->m_container->remove_all_children();
p->m_notification->SetVisibility(p->m_container->m_children.size() == 0);
const auto plan = pp::app::plan_brush_preset_list_clear(
static_cast<int>(m_container->m_children.size()));
if (plan) {
execute_preset_list_plan(plan.value());
}
}

View File

@@ -11,6 +11,7 @@
namespace pp::app {
struct BrushTextureListPlan;
struct BrushPresetListPlan;
}
namespace pp::panopainter {
class LegacyBrushTextureListServices;
@@ -99,6 +100,7 @@ class NodePanelBrushPreset : public Node
NodeButton* m_btn_import;
NodeButton* m_btn_download;
Node* m_notification;
void execute_preset_list_plan(const pp::app::BrushPresetListPlan& plan);
public:
struct PPBRInfo
{