Add canvas tool service boundary

This commit is contained in:
2026-06-03 13:42:34 +02:00
parent 45a7d49d40
commit 9c7c89fed4
6 changed files with 309 additions and 50 deletions

View File

@@ -59,6 +59,17 @@ struct CanvasToolButtonState {
bool flood_fill_active = false;
};
class CanvasToolServices {
public:
virtual ~CanvasToolServices() = default;
virtual void select_toolbar_button(CanvasToolMode mode) = 0;
virtual void set_transform_action(CanvasToolTransformAction action) = 0;
virtual void set_canvas_mode(CanvasToolMode mode) = 0;
virtual void toggle_picking() = 0;
virtual void toggle_touch_lock() = 0;
};
[[nodiscard]] inline constexpr CanvasToolTransformAction transform_action_for_mode(CanvasToolMode mode) noexcept
{
if (mode == CanvasToolMode::copy) {
@@ -123,4 +134,47 @@ struct CanvasToolButtonState {
return state;
}
[[nodiscard]] inline pp::foundation::Status execute_canvas_tool_plan(
const CanvasToolPlan& plan,
CanvasToolServices& services)
{
switch (plan.operation) {
case CanvasToolOperation::select_mode:
if (!plan.selects_toolbar_button || !plan.updates_canvas_mode) {
return pp::foundation::Status::invalid_argument("canvas tool select plan must select toolbar and update mode");
}
if (plan.transform_action != transform_action_for_mode(plan.mode)) {
return pp::foundation::Status::invalid_argument("canvas tool select plan has mismatched transform action");
}
services.select_toolbar_button(plan.mode);
if (plan.transform_action != CanvasToolTransformAction::none) {
services.set_transform_action(plan.transform_action);
}
services.set_canvas_mode(plan.mode);
return pp::foundation::Status::success();
case CanvasToolOperation::toggle_picking:
if (!plan.requires_draw_mode) {
return pp::foundation::Status::invalid_argument("canvas pick plan must require draw mode");
}
if (plan.no_op) {
return pp::foundation::Status::success();
}
if (!plan.toggles_picking) {
return pp::foundation::Status::invalid_argument("canvas pick plan must toggle picking or be a no-op");
}
services.toggle_picking();
return pp::foundation::Status::success();
case CanvasToolOperation::toggle_touch_lock:
if (!plan.toggles_touch_lock || plan.no_op) {
return pp::foundation::Status::invalid_argument("canvas touch-lock plan must toggle touch lock");
}
services.toggle_touch_lock();
return pp::foundation::Status::success();
}
return pp::foundation::Status::invalid_argument("unknown canvas tool operation");
}
} // namespace pp::app

View File

@@ -1136,8 +1136,19 @@ void App::init_sidebar()
};
}
}
template<class T>
void select_button(Node* main, T* button) {
void set_canvas_tool_button_active(Node* button, bool active)
{
if (auto* custom = dynamic_cast<NodeButtonCustom*>(button)) {
custom->set_active(active);
return;
}
if (auto* regular = dynamic_cast<NodeButton*>(button)) {
regular->set_active(active);
}
}
void select_canvas_tool_button(Node* main, Node* button)
{
main->find<NodeButtonCustom>("btn-pen")->set_active(false);
main->find<NodeButtonCustom>("btn-erase")->set_active(false);
main->find<NodeButtonCustom>("btn-line")->set_active(false);
@@ -1145,12 +1156,11 @@ void select_button(Node* main, T* button) {
main->find<NodeButton>("btn-grid")->set_active(false);
main->find<NodeButton>("btn-copy")->set_active(false);
main->find<NodeButton>("btn-cut")->set_active(false);
//main->find<NodeButton>("btn-fill")->set_color(color_button_normal);
main->find<NodeButtonCustom>("btn-mask-free")->set_active(false);
main->find<NodeButtonCustom>("btn-mask-line")->set_active(false);
main->find<NodeButtonCustom>("btn-bucket")->set_active(false);
button->set_active(false);
};
set_canvas_tool_button_active(button, false);
}
kCanvasMode canvas_mode_from_tool(pp::app::CanvasToolMode mode)
{
@@ -1181,25 +1191,73 @@ kCanvasMode canvas_mode_from_tool(pp::app::CanvasToolMode mode)
return kCanvasMode::Draw;
}
class LegacyCanvasToolServices final : public pp::app::CanvasToolServices {
public:
LegacyCanvasToolServices(App& app, Node* toolbar_button = nullptr) noexcept
: app_(app)
, toolbar_button_(toolbar_button)
{
}
void select_toolbar_button(pp::app::CanvasToolMode) override
{
if (toolbar_button_)
select_canvas_tool_button(app_.layout[app_.main_id], toolbar_button_);
}
void set_transform_action(pp::app::CanvasToolTransformAction action) override
{
if (!app_.canvas || !app_.canvas->m_canvas)
return;
if (action == pp::app::CanvasToolTransformAction::copy) {
auto* transform = static_cast<CanvasModeTransform*>(
app_.canvas->m_canvas->modes[(int)kCanvasMode::Copy][0]);
transform->m_action = CanvasModeTransform::ActionType::Copy;
} else if (action == pp::app::CanvasToolTransformAction::cut) {
auto* transform = static_cast<CanvasModeTransform*>(
app_.canvas->m_canvas->modes[(int)kCanvasMode::Cut][0]);
transform->m_action = CanvasModeTransform::ActionType::Cut;
}
}
void set_canvas_mode(pp::app::CanvasToolMode mode) override
{
Canvas::set_mode(canvas_mode_from_tool(mode));
}
void toggle_picking() override
{
if (!app_.canvas || !app_.canvas->m_canvas)
return;
auto* mode = static_cast<CanvasModePen*>(
app_.canvas->m_canvas->modes[(int)kCanvasMode::Draw][0]);
if (mode)
mode->m_picking = !mode->m_picking;
}
void toggle_touch_lock() override
{
if (!app_.canvas || !app_.canvas->m_canvas)
return;
app_.canvas->m_canvas->m_touch_lock = !app_.canvas->m_canvas->m_touch_lock;
}
private:
App& app_;
Node* toolbar_button_ = nullptr;
};
template<class T>
void apply_canvas_tool_select(App& app, T* button, pp::app::CanvasToolMode mode)
{
const auto plan = pp::app::plan_canvas_tool_select(mode);
if (plan.selects_toolbar_button)
select_button(app.layout[app.main_id], button);
if (plan.transform_action == pp::app::CanvasToolTransformAction::copy) {
auto* transform = static_cast<CanvasModeTransform*>(
app.canvas->m_canvas->modes[(int)kCanvasMode::Copy][0]);
transform->m_action = CanvasModeTransform::ActionType::Copy;
} else if (plan.transform_action == pp::app::CanvasToolTransformAction::cut) {
auto* transform = static_cast<CanvasModeTransform*>(
app.canvas->m_canvas->modes[(int)kCanvasMode::Cut][0]);
transform->m_action = CanvasModeTransform::ActionType::Cut;
}
if (plan.updates_canvas_mode)
Canvas::set_mode(canvas_mode_from_tool(plan.mode));
LegacyCanvasToolServices services(app, button);
const auto status = pp::app::execute_canvas_tool_plan(plan, services);
if (!status.ok())
LOG("Canvas tool select action failed: %s", status.message);
}
void App::init_toolbar_draw()
@@ -1211,27 +1269,30 @@ void App::init_toolbar_draw()
};
//button->set_active(true);
const auto plan = pp::app::plan_canvas_tool_select(pp::app::CanvasToolMode::draw);
if (plan.updates_canvas_mode)
Canvas::set_mode(canvas_mode_from_tool(plan.mode));
LegacyCanvasToolServices services(*this);
const auto status = pp::app::execute_canvas_tool_plan(plan, services);
if (!status.ok())
LOG("Canvas default tool action failed: %s", status.message);
}
if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-pick"))
{
button->on_click = [this](Node*) {
CanvasModePen* mode = (CanvasModePen*)canvas->m_canvas->modes[(int)kCanvasMode::Draw][0];
const auto plan = pp::app::plan_canvas_tool_pick_toggle(
canvas->m_canvas->m_current_mode == kCanvasMode::Draw);
if (mode && plan.toggles_picking)
{
mode->m_picking = !mode->m_picking;
}
LegacyCanvasToolServices services(*this);
const auto status = pp::app::execute_canvas_tool_plan(plan, services);
if (!status.ok())
LOG("Canvas pick action failed: %s", status.message);
};
}
if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-touchlock"))
{
button->on_click = [this](Node*) {
const auto plan = pp::app::plan_canvas_tool_touch_lock_toggle();
if (plan.toggles_touch_lock)
canvas->m_canvas->m_touch_lock = !canvas->m_canvas->m_touch_lock;
LegacyCanvasToolServices services(*this);
const auto status = pp::app::execute_canvas_tool_plan(plan, services);
if (!status.ok())
LOG("Canvas touch-lock action failed: %s", status.message);
};
}
if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-erase"))

View File

@@ -37,22 +37,46 @@ void run_history_redo_if_available()
ActionManager::redo();
}
class LegacyNodeCanvasToolServices final : public pp::app::CanvasToolServices {
public:
void select_toolbar_button(pp::app::CanvasToolMode) override
{
}
void set_transform_action(pp::app::CanvasToolTransformAction) override
{
}
void set_canvas_mode(pp::app::CanvasToolMode mode) override
{
switch (mode) {
case pp::app::CanvasToolMode::draw:
Canvas::set_mode(kCanvasMode::Draw);
return;
case pp::app::CanvasToolMode::erase:
Canvas::set_mode(kCanvasMode::Erase);
return;
default:
return;
}
}
void toggle_picking() override
{
}
void toggle_touch_lock() override
{
}
};
void run_canvas_tool_mode(pp::app::CanvasToolMode mode)
{
const auto plan = pp::app::plan_canvas_tool_select(mode);
if (!plan.updates_canvas_mode)
return;
switch (plan.mode) {
case pp::app::CanvasToolMode::draw:
Canvas::set_mode(kCanvasMode::Draw);
return;
case pp::app::CanvasToolMode::erase:
Canvas::set_mode(kCanvasMode::Erase);
return;
default:
return;
}
LegacyNodeCanvasToolServices services;
const auto status = pp::app::execute_canvas_tool_plan(plan, services);
if (!status.ok())
LOG("Canvas input tool action failed: %s", status.message);
}
}