Extract tools menu planning

This commit is contained in:
2026-06-03 12:15:57 +02:00
parent fb844f79fd
commit c640519772
8 changed files with 651 additions and 51 deletions

141
src/app_core/tools_menu.h Normal file
View File

@@ -0,0 +1,141 @@
#pragma once
#include <string_view>
namespace pp::app {
enum class ToolsMenuCommand {
panels,
options,
clear_grids,
reset_camera,
shortcuts,
sonarpen,
};
enum class ToolsMenuAction {
show_panels_submenu,
show_options_submenu,
clear_grid_overlays,
reset_camera,
show_shortcuts_dialog,
start_sonarpen,
no_op_unavailable,
};
enum class ToolsPanel {
presets,
color,
color_advanced,
layers,
brush,
grids,
animation,
};
enum class ToolsPanelAction {
open_floating_panel,
no_op_already_visible,
};
struct ToolsMenuPlan {
ToolsMenuCommand command = ToolsMenuCommand::panels;
ToolsMenuAction action = ToolsMenuAction::show_panels_submenu;
std::string_view label;
bool closes_root_popup = false;
};
struct ToolsPanelPlan {
ToolsPanel panel = ToolsPanel::presets;
ToolsPanelAction action = ToolsPanelAction::open_floating_panel;
std::string_view title;
int width = 0;
int height = 0;
int min_width = 0;
int min_height = 0;
bool droppable = true;
bool hides_embedded_title = false;
};
[[nodiscard]] constexpr ToolsMenuPlan plan_tools_menu_command(
ToolsMenuCommand command,
bool sonarpen_available = false) noexcept
{
switch (command) {
case ToolsMenuCommand::panels:
return { command, ToolsMenuAction::show_panels_submenu, "Panels", false };
case ToolsMenuCommand::options:
return { command, ToolsMenuAction::show_options_submenu, "Options", false };
case ToolsMenuCommand::clear_grids:
return { command, ToolsMenuAction::clear_grid_overlays, "Clear Grids", true };
case ToolsMenuCommand::reset_camera:
return { command, ToolsMenuAction::reset_camera, "Reset Camera", true };
case ToolsMenuCommand::shortcuts:
return { command, ToolsMenuAction::show_shortcuts_dialog, "Shortcuts", true };
case ToolsMenuCommand::sonarpen:
return {
command,
sonarpen_available ? ToolsMenuAction::start_sonarpen : ToolsMenuAction::no_op_unavailable,
"SonarPen",
sonarpen_available,
};
}
return { command, ToolsMenuAction::no_op_unavailable, "", false };
}
[[nodiscard]] constexpr ToolsPanelPlan plan_tools_panel(
ToolsPanel panel,
bool already_visible) noexcept
{
ToolsPanelPlan plan;
plan.panel = panel;
plan.action = already_visible
? ToolsPanelAction::no_op_already_visible
: ToolsPanelAction::open_floating_panel;
switch (panel) {
case ToolsPanel::presets:
plan.title = "Brushes";
plan.height = 300;
plan.min_height = 300;
plan.min_width = 100;
break;
case ToolsPanel::color:
plan.title = "Color Picker";
plan.height = 300;
plan.hides_embedded_title = true;
break;
case ToolsPanel::color_advanced:
plan.title = "Color Picker";
plan.width = 300;
plan.height = 300;
break;
case ToolsPanel::layers:
plan.title = "Layers";
plan.height = 300;
plan.min_height = 100;
plan.hides_embedded_title = true;
break;
case ToolsPanel::brush:
plan.title = "Brush Settings";
plan.height = 300;
plan.hides_embedded_title = true;
break;
case ToolsPanel::grids:
plan.title = "Grid";
plan.height = 300;
plan.hides_embedded_title = true;
break;
case ToolsPanel::animation:
plan.title = "Animation";
plan.width = 500;
plan.height = 300;
plan.droppable = false;
break;
}
return plan;
}
}

View File

@@ -16,6 +16,7 @@
#include "app_core/file_menu.h"
#include "app_core/app_status.h"
#include "app_core/history_ui.h"
#include "app_core/tools_menu.h"
#include "settings.h"
#include "serializer.h"
#include "font.h"
@@ -223,6 +224,29 @@ pp::app::DocumentLayerMenuPlan make_layer_menu_plan(
return {};
}
[[nodiscard]] bool should_open_tools_panel(const pp::app::ToolsPanelPlan& plan) noexcept
{
return plan.action == pp::app::ToolsPanelAction::open_floating_panel;
}
void apply_tools_panel_chrome(NodePanelFloating& panel, const pp::app::ToolsPanelPlan& plan)
{
if (plan.width > 0 && plan.height > 0) {
panel.SetSize(static_cast<float>(plan.width), static_cast<float>(plan.height));
} else {
if (plan.width > 0)
panel.SetWidth(static_cast<float>(plan.width));
if (plan.height > 0)
panel.SetHeight(static_cast<float>(plan.height));
}
if (plan.min_width > 0)
panel.SetMinWidth(static_cast<float>(plan.min_width));
if (plan.min_height > 0)
panel.SetMinHeight(static_cast<float>(plan.min_height));
panel.m_title->set_text(plan.title.data());
panel.m_droppable = plan.droppable;
}
} // namespace
void App::title_update()
@@ -1052,6 +1076,10 @@ void App::init_menu_tools()
if (auto tick = popup_exp->find<NodeButtonCustom>("tools-panels")) tick->on_click = [this, popup_exp](Node* b)
{
const auto menu_plan = pp::app::plan_tools_menu_command(pp::app::ToolsMenuCommand::panels);
if (menu_plan.action != pp::app::ToolsMenuAction::show_panels_submenu)
return;
glm::vec2 pos = b->m_pos + glm::vec2(b->m_size.x, 0);
auto popup_time = layout[const_hash("panels-menu")]->m_children[0]->clone<NodePopupMenu>();
popup_time->update();
@@ -1076,14 +1104,14 @@ void App::init_menu_tools()
};
popup_time->find<NodeButtonCustom>("panel-presets")->on_click = [this, popup_time, popup_exp, visible](Node*) {
if (visible(floating_presets.get()))
const auto plan = pp::app::plan_tools_panel(
pp::app::ToolsPanel::presets,
visible(floating_presets.get()));
if (!should_open_tools_panel(plan))
return;
auto fpanel = floatings_container->add_child<NodePanelFloating>();
fpanel->m_class = NodePanelFloating::kClass::Presets;
fpanel->SetHeight(300);
fpanel->SetMinHeight(300);
fpanel->SetMinWidth(100);
fpanel->m_title->set_text("Brushes");
apply_tools_panel_chrome(*fpanel, plan);
if (!floating_presets)
{
floating_presets = fpanel->m_container->add_child_ref<NodePanelBrushPreset>();
@@ -1103,19 +1131,21 @@ void App::init_menu_tools()
};
popup_time->find<NodeButtonCustom>("panel-color")->on_click = [this, popup_time, popup_exp, visible](Node*) {
if (visible(floating_color.get()))
const auto plan = pp::app::plan_tools_panel(
pp::app::ToolsPanel::color,
visible(floating_color.get()));
if (!should_open_tools_panel(plan))
return;
auto fpanel = floatings_container->add_child<NodePanelFloating>();
fpanel->m_class = NodePanelFloating::kClass::Color;
fpanel->SetHeight(300);
fpanel->SetMinHeight(300);
fpanel->m_title->set_text("Color Picker");
apply_tools_panel_chrome(*fpanel, plan);
if (!floating_color)
{
floating_color = fpanel->m_container->add_child_ref<NodePanelColor>();
floating_color->SetHeightP(100);
//floating_color->SetMinHeight(300);
floating_color->find("title")->SetVisibility(false);
if (plan.hides_embedded_title)
floating_color->find("title")->SetVisibility(false);
floating_color->on_color_changed = [this](Node* target, glm::vec4 color) {
apply_brush_color_plan(*this, color, false, false);
};
@@ -1128,13 +1158,14 @@ void App::init_menu_tools()
popup_time->destroy();
};
popup_time->find<NodeButtonCustom>("panel-color-adv")->on_click = [this, popup_time, popup_exp, visible](Node*) {
if (visible(floating_picker.get()))
const auto plan = pp::app::plan_tools_panel(
pp::app::ToolsPanel::color_advanced,
visible(floating_picker.get()));
if (!should_open_tools_panel(plan))
return;
auto fpanel = floatings_container->add_child<NodePanelFloating>();
fpanel->m_class = NodePanelFloating::kClass::ColorAdv;
fpanel->SetHeight(300);
fpanel->SetWidth(300);
fpanel->m_title->set_text("Color Picker");
apply_tools_panel_chrome(*fpanel, plan);
if (!floating_picker)
{
floating_picker = fpanel->m_container->add_child_ref<NodeColorPicker>();
@@ -1154,72 +1185,80 @@ void App::init_menu_tools()
popup_time->destroy();
};
popup_time->find<NodeButtonCustom>("panel-layers")->on_click = [this, popup_time, popup_exp, visible](Node*) {
if (visible(layers.get()))
const auto plan = pp::app::plan_tools_panel(
pp::app::ToolsPanel::layers,
visible(layers.get()));
if (!should_open_tools_panel(plan))
return;
auto fpanel = floatings_container->add_child<NodePanelFloating>();
fpanel->m_class = NodePanelFloating::kClass::Layers;
fpanel->SetMinHeight(100);
fpanel->SetHeight(300);
apply_tools_panel_chrome(*fpanel, plan);
fpanel->m_container->add_child(layers);
fpanel->m_title->set_text("Layers");
layers->SetPositioning(YGPositionTypeRelative);
layers->SetPosition(0, 0);
layers->SetWidthP(100);
layers->SetHeightP(100);
layers->SetFlexShrink(0);
layers->find("title")->SetVisibility(false);
if (plan.hides_embedded_title)
layers->find("title")->SetVisibility(false);
popup_exp->destroy();
popup_time->destroy();
};
popup_time->find<NodeButtonCustom>("panel-brush")->on_click = [this, popup_time, popup_exp, visible](Node*) {
if (visible(stroke.get()))
const auto plan = pp::app::plan_tools_panel(
pp::app::ToolsPanel::brush,
visible(stroke.get()));
if (!should_open_tools_panel(plan))
return;
auto fpanel = floatings_container->add_child<NodePanelFloating>();
fpanel->m_class = NodePanelFloating::kClass::Brush;
fpanel->m_container->add_child(stroke);
fpanel->SetHeight(300);
fpanel->m_title->set_text("Brush Settings");
apply_tools_panel_chrome(*fpanel, plan);
stroke->SetPositioning(YGPositionTypeRelative);
stroke->SetPosition(0, 0);
stroke->SetWidthP(100);
stroke->SetHeightP(100);
stroke->find("title")->SetVisibility(false);
if (plan.hides_embedded_title)
stroke->find("title")->SetVisibility(false);
popup_exp->destroy();
popup_time->destroy();
};
popup_time->find<NodeButtonCustom>("panel-grids")->on_click = [this, popup_time, popup_exp, visible](Node*) {
if (visible(grid.get()))
const auto plan = pp::app::plan_tools_panel(
pp::app::ToolsPanel::grids,
visible(grid.get()));
if (!should_open_tools_panel(plan))
return;
auto fpanel = floatings_container->add_child<NodePanelFloating>();
fpanel->m_class = NodePanelFloating::kClass::Grids;
fpanel->m_container->add_child(grid);
fpanel->SetHeight(300);
fpanel->m_title->set_text("Grid");
apply_tools_panel_chrome(*fpanel, plan);
grid->SetPositioning(YGPositionTypeRelative);
grid->SetPosition(0, 0);
grid->SetWidthP(100);
grid->SetHeightP(100);
grid->find("title")->SetVisibility(false);
if (plan.hides_embedded_title)
grid->find("title")->SetVisibility(false);
popup_exp->destroy();
popup_time->destroy();
};
popup_time->find<NodeButtonCustom>("panel-animation")->on_click = [this, popup_time, popup_exp, visible](Node*) {
if (visible(grid.get()))
const auto plan = pp::app::plan_tools_panel(
pp::app::ToolsPanel::animation,
visible(animation.get()));
if (!should_open_tools_panel(plan))
return;
auto fpanel = floatings_container->add_child<NodePanelFloating>();
fpanel->m_class = NodePanelFloating::kClass::Animation;
fpanel->m_container->add_child(animation);
fpanel->SetSize(500, 300);
fpanel->m_title->set_text("Animation");
fpanel->m_droppable = false;
grid->SetPositioning(YGPositionTypeRelative);
grid->SetPosition(0, 0);
grid->SetWidthP(100);
grid->SetHeightP(100);
grid->find("title")->SetVisibility(false);
apply_tools_panel_chrome(*fpanel, plan);
animation->SetPositioning(YGPositionTypeRelative);
animation->SetPosition(0, 0);
animation->SetWidthP(100);
animation->SetHeightP(100);
popup_exp->destroy();
popup_time->destroy();
@@ -1228,6 +1267,10 @@ void App::init_menu_tools()
if (auto options = popup_exp->find<NodeButtonCustom>("tools-options")) options->on_click = [this, options, main](Node* b)
{
const auto menu_plan = pp::app::plan_tools_menu_command(pp::app::ToolsMenuCommand::options);
if (menu_plan.action != pp::app::ToolsMenuAction::show_options_submenu)
return;
glm::vec2 pos = b->m_pos + glm::vec2(b->m_size.x, 0);
auto popup_time = layout[const_hash("options-menu")]->m_children[0]->clone<NodePopupMenu>();
popup_time->update();
@@ -1378,22 +1421,39 @@ void App::init_menu_tools()
};
popup_exp->find<NodeButtonCustom>("clear-grids")->on_click = [this, popup_exp](Node*) {
CanvasModeGrid* mode = (CanvasModeGrid*)Canvas::modes[(int)kCanvasMode::Grid][0];
mode->clear();
popup_exp->mouse_release();
popup_exp->destroy();
const auto plan = pp::app::plan_tools_menu_command(pp::app::ToolsMenuCommand::clear_grids);
if (plan.action == pp::app::ToolsMenuAction::clear_grid_overlays)
{
CanvasModeGrid* mode = (CanvasModeGrid*)Canvas::modes[(int)kCanvasMode::Grid][0];
mode->clear();
}
if (plan.closes_root_popup)
{
popup_exp->mouse_release();
popup_exp->destroy();
}
};
popup_exp->find<NodeButtonCustom>("camera-reset")->on_click = [this, popup_exp](Node*) {
canvas->reset_camera();
popup_exp->mouse_release();
popup_exp->destroy();
const auto plan = pp::app::plan_tools_menu_command(pp::app::ToolsMenuCommand::reset_camera);
if (plan.action == pp::app::ToolsMenuAction::reset_camera)
canvas->reset_camera();
if (plan.closes_root_popup)
{
popup_exp->mouse_release();
popup_exp->destroy();
}
};
popup_exp->find<NodeButtonCustom>("shortcuts")->on_click = [this, popup_exp](Node*) {
dialog_shortcuts();
popup_exp->mouse_release();
popup_exp->destroy();
const auto plan = pp::app::plan_tools_menu_command(pp::app::ToolsMenuCommand::shortcuts);
if (plan.action == pp::app::ToolsMenuAction::show_shortcuts_dialog)
dialog_shortcuts();
if (plan.closes_root_popup)
{
popup_exp->mouse_release();
popup_exp->destroy();
}
};
/*
@@ -1406,9 +1466,14 @@ void App::init_menu_tools()
#if __IOS__
popup_exp->find<NodeButtonCustom>("sonarpen")->on_click = [this, popup_exp](Node*) {
[ios_app sonarpen_start];
popup_exp->mouse_release();
popup_exp->destroy();
const auto plan = pp::app::plan_tools_menu_command(pp::app::ToolsMenuCommand::sonarpen, true);
if (plan.action == pp::app::ToolsMenuAction::start_sonarpen)
[ios_app sonarpen_start];
if (plan.closes_root_popup)
{
popup_exp->mouse_release();
popup_exp->destroy();
}
};
#endif
};