Files
panopainter/src/app_layout.cpp

2698 lines
102 KiB
C++

#include "pch.h"
#include "app.h"
#include "node_icon.h"
#include "node_dialog_open.h"
#include "node_text.h"
#include "node_progress_bar.h"
#include "node_dialog_picker.h"
#include "node_panel_floating.h"
#include "app_core/about_menu.h"
#include "app_core/app_preferences.h"
#include "app_core/brush_ui.h"
#include "app_core/canvas_tool_ui.h"
#include "app_core/document_layer.h"
#include "app_core/document_canvas.h"
#include "app_core/document_export.h"
#include "app_core/document_import.h"
#include "app_core/file_menu.h"
#include "app_core/app_status.h"
#include "app_core/history_ui.h"
#include "app_core/main_toolbar.h"
#include "app_core/tools_menu.h"
#include "settings.h"
#include "serializer.h"
#include "font.h"
#include "node_remote_page.h"
#include "node_shorcuts.h"
#include <vector>
namespace {
class LegacyBrushUiServices final : public pp::app::BrushUiServices {
public:
LegacyBrushUiServices(
App& app,
bool update_quick = false,
bool update_color_panel = false,
const std::shared_ptr<Brush>& preset_brush = nullptr) noexcept
: app_(app)
, update_quick_(update_quick)
, update_color_panel_(update_color_panel)
, preset_brush_(preset_brush)
{
}
void set_tip_color(float r, float g, float b, float a) override
{
if (!Canvas::I || !Canvas::I->m_current_brush)
return;
Canvas::I->m_current_brush->m_tip_color = glm::vec4(r, g, b, a);
if (update_quick_ && app_.quick)
app_.quick->set_color(Canvas::I->m_current_brush->m_tip_color);
if (update_color_panel_ && app_.color)
app_.color->set_color(Canvas::I->m_current_brush->m_tip_color);
}
void set_texture(
pp::app::BrushUiTextureSlot slot,
std::string_view path,
std::string_view thumbnail_path) override
{
if (!Canvas::I || !Canvas::I->m_current_brush)
return;
const std::string texture_path(path);
const std::string thumbnail(thumbnail_path);
switch (slot)
{
case pp::app::BrushUiTextureSlot::tip:
Canvas::I->m_current_brush->load_tip(texture_path, thumbnail);
break;
case pp::app::BrushUiTextureSlot::pattern:
Canvas::I->m_current_brush->load_pattern(texture_path, thumbnail);
break;
case pp::app::BrushUiTextureSlot::dual:
Canvas::I->m_current_brush->load_dual(texture_path, thumbnail);
break;
}
}
void replace_brush_from_preset(bool preserve_existing_color, bool load_resources) override
{
if (!Canvas::I || !Canvas::I->m_current_brush || !preset_brush_)
return;
const auto color = Canvas::I->m_current_brush->m_tip_color;
*Canvas::I->m_current_brush = *preset_brush_;
if (preserve_existing_color)
Canvas::I->m_current_brush->m_tip_color = color;
if (load_resources)
Canvas::I->m_current_brush->load();
}
void refresh_brush_ui(bool update_color_ui, bool update_brush_ui) override
{
app_.brush_update(update_color_ui, update_brush_ui);
}
private:
App& app_;
bool update_quick_ = false;
bool update_color_panel_ = false;
std::shared_ptr<Brush> preset_brush_;
};
bool apply_brush_color_plan(App& app, glm::vec4 color, bool update_quick, bool update_color_panel)
{
const auto plan = pp::app::plan_brush_ui_color(color.r, color.g, color.b, color.a);
if (!plan)
return false;
LegacyBrushUiServices services(app, update_quick, update_color_panel);
const auto status = pp::app::execute_brush_ui_plan(plan.value(), services);
if (!status.ok())
LOG("Brush color action failed: %s", status.message);
return status.ok();
}
bool apply_brush_texture_plan(pp::app::BrushUiTextureSlot slot, const std::string& path, const std::string& thumb)
{
const auto plan = pp::app::plan_brush_ui_texture(slot, path, thumb);
if (!plan)
return false;
LegacyBrushUiServices services(*App::I);
const auto status = pp::app::execute_brush_ui_plan(plan.value(), services);
if (!status.ok())
LOG("Brush texture action failed: %s", status.message);
return status.ok();
}
bool apply_brush_preset_plan(App& app, const std::shared_ptr<Brush>& brush)
{
const auto plan = pp::app::plan_brush_ui_preset_replace(static_cast<bool>(brush));
if (!plan)
return false;
LegacyBrushUiServices services(app, false, false, brush);
const auto status = pp::app::execute_brush_ui_plan(plan.value(), services);
if (!status.ok())
LOG("Brush preset action failed: %s", status.message);
return status.ok();
}
class LegacyDocumentCanvasClearServices final : public pp::app::DocumentCanvasClearServices {
public:
explicit LegacyDocumentCanvasClearServices(App& app) noexcept
: app_(app)
{
}
void clear_current_canvas(float r, float g, float b, float a) override
{
if (!app_.canvas || !app_.canvas->m_canvas)
return;
app_.canvas->m_canvas->clear({ r, g, b, a });
}
private:
App& app_;
};
void execute_document_canvas_clear_plan(App& app, const pp::app::DocumentCanvasClearPlan& plan)
{
LegacyDocumentCanvasClearServices services(app);
const auto status = pp::app::execute_document_canvas_clear_plan(plan, services);
if (!status.ok())
LOG("Canvas clear failed: %s", status.message);
}
bool apply_document_export_menu_plan(App& app, pp::app::DocumentExportMenuKind kind)
{
class LegacyDocumentExportMenuServices final : public pp::app::DocumentExportMenuServices {
public:
explicit LegacyDocumentExportMenuServices(App& app) noexcept
: app_(app)
{
}
void show_jpeg_dialog() override { app_.dialog_export(".jpg"); }
void show_png_dialog() override { app_.dialog_export(".png"); }
void show_layers_dialog() override { app_.dialog_export_layers(); }
void show_cube_faces_dialog() override { app_.dialog_export_cube_faces(); }
void show_depth_dialog() override { app_.dialog_export_depth(); }
void show_animation_frames_dialog() override { app_.dialog_export_anim_frames(); }
void show_animation_mp4_dialog() override { app_.dialog_export_mp4(); }
void show_timelapse_dialog() override { app_.dialog_timelapse_export(); }
void show_license_disabled() override
{
app_.message_box("License", "This function is disabled in demo mode.");
}
private:
App& app_;
};
const auto requires_license = pp::app::document_export_menu_requires_license(kind);
const auto plan = pp::app::plan_document_export_menu_action(
kind,
app.canvas != nullptr,
!requires_license || app.check_license());
LegacyDocumentExportMenuServices services(app);
const auto status = pp::app::execute_document_export_menu_plan(plan, services);
if (!status.ok())
LOG("Document export menu action failed: %s", status.message);
return status.ok() && plan.opens_dialog;
}
class LegacyFileMenuServices final : public pp::app::FileMenuServices {
public:
explicit LegacyFileMenuServices(App& app) noexcept
: app_(app)
{
}
void show_new_document_dialog() override
{
app_.dialog_newdoc();
}
void pick_image_for_import() override
{
auto* app_ptr = &app_;
app_.pick_image([app_ptr](std::string path) {
Image img;
img.load_file(path);
const auto import_plan = pp::app::plan_document_image_import(img.width, img.height);
if (!import_plan)
return;
class LegacyDocumentImageImportServices final : public pp::app::DocumentImageImportServices {
public:
LegacyDocumentImageImportServices(App& app, Image& image) noexcept
: app_(app)
, image_(image)
{
}
void import_equirectangular(std::string_view import_path) override
{
if (Canvas::I)
Canvas::I->import_equirectangular(std::string(import_path));
}
void enter_transform_import(std::string_view) override
{
if (!app_.canvas || !app_.canvas->m_canvas)
return;
auto* mode = static_cast<CanvasModeTransform*>(
app_.canvas->m_canvas->modes[(int)kCanvasMode::Import][0]);
mode->m_action = CanvasModeTransform::ActionType::Import;
mode->m_source_image = std::move(image_);
Canvas::set_mode(kCanvasMode::Import);
}
private:
App& app_;
Image& image_;
};
LegacyDocumentImageImportServices services(*app_ptr, img);
const auto status = pp::app::execute_document_image_import_plan(
import_plan.value(),
path,
services);
if (!status.ok())
LOG("Image import failed: %s", status.message);
});
}
void pick_project_file() override
{
auto* app_ptr = &app_;
app_.pick_file({ "ppi" }, [app_ptr](std::string path) {
app_ptr->open_document(path);
});
}
void show_cloud_browser_dialog() override
{
app_.dialog_browse();
}
void save_document(pp::app::DocumentSaveIntent intent) override
{
app_.save_document(intent);
}
void show_export_jpeg_dialog(pp::app::DocumentExportMenuKind kind) override
{
apply_document_export_menu_plan(app_, kind);
}
void show_export_submenu() override
{
}
void share_document() override
{
app_.share_file(app_.doc_path);
}
void show_resize_dialog() override
{
app_.dialog_resize();
}
void upload_to_cloud() override
{
app_.cloud_upload();
}
void browse_cloud_documents() override
{
app_.cloud_browse();
}
private:
App& app_;
};
void apply_file_menu_plan(App& app, pp::app::FileMenuCommand command)
{
const auto plan = pp::app::plan_file_menu_command(command);
LegacyFileMenuServices services(app);
const auto status = pp::app::execute_file_menu_plan(plan, services);
if (!status.ok())
LOG("File menu action failed: %s", status.message);
}
pp::app::DocumentLayerMenuPlan make_layer_menu_plan(
pp::app::DocumentLayerMenuCommand command,
App& app)
{
const bool has_current_layer = app.layers && app.layers->m_current_layer;
const int current_index = app.canvas && app.canvas->m_canvas
? app.canvas->m_canvas->m_current_layer_idx
: 0;
const int animation_duration = Canvas::I
? Canvas::I->anim_duration()
: 0;
const std::string current_name = has_current_layer
? app.layers->m_current_layer->m_label_text
: std::string {};
std::string lower_name;
if (app.canvas && app.canvas->m_canvas && current_index > 0
&& current_index - 1 < static_cast<int>(app.canvas->m_canvas->m_layers.size()))
{
lower_name = app.canvas->m_canvas->m_layers[current_index - 1]->m_name;
}
const auto plan = pp::app::plan_document_layer_menu(
command,
has_current_layer,
current_index,
animation_duration,
current_name,
lower_name);
if (plan)
return plan.value();
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;
}
class LegacyMainToolbarServices final : public pp::app::MainToolbarServices {
public:
explicit LegacyMainToolbarServices(App& app) noexcept
: app_(app)
{
}
void show_open_dialog() override
{
app_.dialog_open();
}
void show_save_dialog() override
{
app_.dialog_save();
}
void invoke_undo(const pp::app::HistoryUiPlan& plan) override
{
execute_history_plan(plan);
}
void invoke_redo(const pp::app::HistoryUiPlan& plan) override
{
execute_history_plan(plan);
}
void clear_history(const pp::app::HistoryUiPlan& plan) override
{
execute_history_plan(plan);
}
void clear_canvas(const pp::app::DocumentCanvasClearPlan& plan) override
{
execute_document_canvas_clear_plan(app_, plan);
}
void show_message_box() override
{
app_.msgbox = new NodeMessageBox();
app_.msgbox->set_manager(&app_.layout);
app_.msgbox->init();
app_.layout[app_.main_id]->add_child(app_.msgbox);
}
void show_settings_dialog() override
{
app_.settings = new NodeSettings();
app_.settings->set_manager(&app_.layout);
app_.settings->init();
app_.layout[app_.main_id]->add_child(app_.settings);
}
private:
class LegacyHistoryUiServices final : public pp::app::HistoryUiServices {
public:
void invoke_undo() override
{
ActionManager::undo();
}
void invoke_redo() override
{
ActionManager::redo();
}
void clear_history() override
{
ActionManager::clear();
}
};
void execute_history_plan(const pp::app::HistoryUiPlan& plan)
{
LegacyHistoryUiServices services;
const auto status = pp::app::execute_history_ui_plan(plan, services);
if (!status.ok())
LOG("History action failed: %s", status.message);
}
App& app_;
};
class LegacyAboutMenuServices final : public pp::app::AboutMenuServices {
public:
explicit LegacyAboutMenuServices(App& app) noexcept
: app_(app)
{
}
void show_user_manual() override
{
app_.dialog_usermanual();
}
void show_about_dialog() override
{
app_.dialog_about();
}
void show_whats_new_dialog() override
{
app_.dialog_whatsnew(true);
}
void trigger_crash_test() override
{
LOG("crashing");
app_.crash_test();
}
void run_performance_test(const pp::app::AboutMenuPlan& plan) override
{
if (!Canvas::I)
return;
LOG("perf");
std::string message;
const int performance_iterations = plan.performance_iterations;
app_.render_task([&]
{
auto start = std::chrono::high_resolution_clock::now();
Canvas::I->stroke_start({ 0, 0, 0 }, 0.9f);
for (int i = 0; i < performance_iterations; i++)
{
Canvas::I->stroke_update({ 100, 100, 0 }, 0.9f);
Canvas::I->stroke_update({ 200, 200, 0 }, 0.9f);
Canvas::I->stroke_update({ 200, 100, 0 }, 0.9f);
Canvas::I->stroke_update({ 100, 200, 0 }, 0.9f);
Canvas::I->stroke_update({ 300, 300, 0 }, 0.9f);
Canvas::I->stroke_update({ 200, 500, 0 }, 0.9f);
Canvas::I->stroke_update({ 500, 500, 0 }, 0.9f);
Canvas::I->stroke_update({ 400, 400, 0 }, 0.9f);
Canvas::I->stroke_update({ 0, 200, 0 }, 0.9f);
Canvas::I->stroke_update({ 200, 0, 0 }, 0.9f);
Canvas::I->stroke_draw();
}
Canvas::I->stroke_end();
auto diff = std::chrono::high_resolution_clock::now() - start;
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(diff).count();
LOG("%lld ms", ms);
message = "Time " + std::to_string(ms) + " ms";
});
app_.message_box("Performance test", message);
}
private:
App& app_;
};
class LegacyToolsMenuServices final : public pp::app::ToolsMenuServices {
public:
explicit LegacyToolsMenuServices(App& app) noexcept
: app_(app)
{
}
void show_panels_submenu() override
{
}
void show_options_submenu() override
{
}
void clear_grid_overlays() override
{
auto* mode = static_cast<CanvasModeGrid*>(Canvas::modes[(int)kCanvasMode::Grid][0]);
mode->clear();
}
void reset_camera() override
{
if (app_.canvas)
app_.canvas->reset_camera();
}
void show_shortcuts_dialog() override
{
app_.dialog_shortcuts();
}
void start_sonarpen() override
{
#if __IOS__
[app_.ios_app sonarpen_start];
#endif
}
private:
App& app_;
};
class LegacyDocumentLayerMenuServices final : public pp::app::DocumentLayerMenuServices {
public:
explicit LegacyDocumentLayerMenuServices(App& app) noexcept
: app_(app)
{
}
void clear_current_layer() override
{
const auto plan = pp::app::plan_document_canvas_clear(
app_.canvas && app_.canvas->m_canvas,
1.0F,
1.0F,
1.0F,
0.0F);
if (!plan)
return;
execute_document_canvas_clear_plan(app_, plan.value());
}
void show_rename_dialog() override
{
app_.dialog_layer_rename();
}
void merge_with_lower_layer(int from_index, int to_index) override
{
if (app_.layers)
app_.layers->merge(from_index, to_index, true);
}
void show_merge_animated_not_supported() override
{
app_.message_box("Not supported", "Merging animated layers is not supported yet.");
}
private:
App& app_;
};
class LegacyDocumentLayerOperationServices final : public pp::app::DocumentLayerOperationServices {
public:
LegacyDocumentLayerOperationServices(
App& app,
const std::shared_ptr<class Layer>& pending_layer = nullptr) noexcept
: app_(app)
, pending_layer_(pending_layer)
{
}
void add_layer(std::string_view name, int insert_index) override
{
auto* canvas = legacy_canvas();
if (!canvas)
return;
canvas->layer_add(std::string(name), pending_layer_, insert_index);
canvas->anim_update();
}
void duplicate_layer(int source_index, int insert_index) override
{
auto* canvas = legacy_canvas();
if (!canvas)
return;
std::string duplicated_name = "Layer";
if (app_.layers && !app_.layers->m_layers.empty())
duplicated_name = app_.layers->m_layers.back()->m_label_text;
canvas->layer_add(duplicated_name, nullptr, insert_index);
auto& dst = canvas->m_layers[insert_index];
auto& src = canvas->m_layers[source_index];
for (int i = 1; i < src->frames_count(); i++)
dst->add_frame();
canvas->anim_update();
for (int frame = 0; frame < src->frames_count(); frame++)
{
for (int i = 0; i < 6; i++)
{
if (!src->face(i))
continue;
bool loaded = src->frame(frame).gpu_load();
dst->frame(frame).gpu_load();
dst->rtt(i, frame).copy(src->rtt(i));
dst->face(i, frame) = src->face(i);
dst->box(i, frame) = src->box(i);
if (!loaded)
{
dst->frame(frame).gpu_unload();
src->frame(frame).gpu_unload();
}
}
}
dst->m_opacity = src->m_opacity;
dst->m_blend_mode = src->m_blend_mode;
dst->m_alpha_locked = src->m_alpha_locked;
}
void select_layer(int index) override
{
if (auto* canvas = legacy_canvas())
canvas->m_current_layer_idx = index;
}
void reorder_layer(int from_index, int to_index) override
{
if (auto* canvas = legacy_canvas())
canvas->layer_order(from_index, to_index);
}
void remove_layer(int index) override
{
if (auto* canvas = legacy_canvas())
canvas->layer_remove(index);
}
void set_layer_opacity(int index, float opacity) override
{
if (auto* canvas = legacy_canvas())
canvas->m_layers[index]->m_opacity = opacity;
}
void set_layer_visibility(int index, bool visible) override
{
if (auto* canvas = legacy_canvas())
canvas->m_layers[index]->m_visible = visible;
}
void set_layer_alpha_lock(int index, bool locked) override
{
if (auto* canvas = legacy_canvas())
canvas->m_layers[index]->m_alpha_locked = locked;
}
void set_layer_blend_mode(int index, int blend_mode) override
{
if (auto* canvas = legacy_canvas())
canvas->m_layers[index]->m_blend_mode = blend_mode;
}
void set_layer_highlight(int index, bool highlighted) override
{
if (auto* canvas = legacy_canvas())
canvas->m_layers[index]->m_hightlight = highlighted;
}
void mark_unsaved() override
{
if (auto* canvas = legacy_canvas())
canvas->m_unsaved = true;
}
void reload_animation_layers() override
{
if (app_.animation)
app_.animation->load_layers();
}
void update_title() override
{
app_.title_update();
}
private:
[[nodiscard]] Canvas* legacy_canvas() const noexcept
{
if (app_.canvas && app_.canvas->m_canvas)
return app_.canvas->m_canvas.get();
return Canvas::I;
}
App& app_;
std::shared_ptr<class Layer> pending_layer_;
};
void execute_main_toolbar_plan(App& app, const pp::app::MainToolbarPlan& plan)
{
LegacyMainToolbarServices services(app);
const auto status = pp::app::execute_main_toolbar_plan(plan, services);
if (!status.ok())
LOG("Main toolbar action failed: %s", status.message);
}
void execute_about_menu_plan(App& app, const pp::app::AboutMenuPlan& plan)
{
LegacyAboutMenuServices services(app);
const auto status = pp::app::execute_about_menu_plan(plan, services);
if (!status.ok())
LOG("About menu action failed: %s", status.message);
}
void execute_tools_menu_plan(App& app, const pp::app::ToolsMenuPlan& plan)
{
LegacyToolsMenuServices services(app);
const auto status = pp::app::execute_tools_menu_plan(plan, services);
if (!status.ok())
LOG("Tools menu action failed: %s", status.message);
}
void execute_document_layer_menu_plan(App& app, const pp::app::DocumentLayerMenuPlan& plan)
{
LegacyDocumentLayerMenuServices services(app);
const auto status = pp::app::execute_document_layer_menu_plan(plan, services);
if (!status.ok())
LOG("Layer menu action failed: %s", status.message);
}
void execute_document_layer_operation_plan(
App& app,
const pp::app::DocumentLayerOperationPlan& plan,
const std::shared_ptr<class Layer>& pending_layer = nullptr)
{
LegacyDocumentLayerOperationServices services(app, pending_layer);
const auto status = pp::app::execute_document_layer_operation_plan(plan, services);
if (!status.ok())
LOG("Layer operation failed: %s", status.message);
}
} // namespace
void App::title_update()
{
if (auto docname = layout[main_id]->find<NodeText>("txt-docname"))
{
const auto title = pp::app::make_document_title(
doc_name,
canvas->m_canvas->m_unsaved,
canvas->m_canvas->m_width);
docname->set_text(title.c_str());
}
if (auto node = layout[main_id]->find<NodeText>("txt-dpi"))
{
const auto label = pp::app::make_dpi_label(zoom);
node->set_text(label.c_str());
}
}
void App::init_toolbar_main()
{
if (auto* button = layout[main_id]->find<NodeButton>("btn-anim"))
{
button->on_click = [this, button](Node*) {
if (canvas)
{
//canvas->m_canvas->export_anim();
}
};
}
if (auto* button = layout[main_id]->find<NodeButton>("btn-open"))
{
button->on_click = [this, button](Node*) {
const auto plan = pp::app::plan_main_toolbar_command(
pp::app::MainToolbarCommand::open_document);
if (plan)
execute_main_toolbar_plan(*this, plan.value());
};
}
if (auto* button = layout[main_id]->find<NodeButton>("btn-save"))
{
button->on_click = [this, button](Node*) {
const auto plan = pp::app::plan_main_toolbar_command(
pp::app::MainToolbarCommand::save_document);
if (plan)
execute_main_toolbar_plan(*this, plan.value());
};
}
if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-undo"))
{
button->on_click = [this, button](Node*) {
const auto plan = pp::app::plan_main_toolbar_command(
pp::app::MainToolbarCommand::undo,
static_cast<int>(ActionManager::I.m_actions.size()));
if (plan)
execute_main_toolbar_plan(*this, plan.value());
};
}
if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-redo"))
{
button->on_click = [this, button](Node*) {
const auto plan = pp::app::plan_main_toolbar_command(
pp::app::MainToolbarCommand::redo,
0,
static_cast<int>(ActionManager::I.m_redos.size()));
if (plan)
execute_main_toolbar_plan(*this, plan.value());
};
}
if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-clean-memory"))
{
button->on_click = [this](Node*) {
const auto plan = pp::app::plan_main_toolbar_command(
pp::app::MainToolbarCommand::clear_history,
static_cast<int>(ActionManager::I.m_actions.size()),
static_cast<int>(ActionManager::I.m_redos.size()),
static_cast<int>(ActionManager::I.m_memory));
if (plan)
execute_main_toolbar_plan(*this, plan.value());
};
}
if (auto* button = layout[main_id]->find<NodeButton>("btn-clear"))
{
button->on_click = [this](Node*) {
//exit(0);
const auto plan = pp::app::plan_main_toolbar_command(
pp::app::MainToolbarCommand::clear_canvas,
0,
0,
0,
static_cast<bool>(canvas));
if (plan)
execute_main_toolbar_plan(*this, plan.value());
};
}
if (auto* button = layout[main_id]->find<NodeButton>("btn-popup"))
{
button->on_click = [this](Node*) {
const auto plan = pp::app::plan_main_toolbar_command(
pp::app::MainToolbarCommand::show_message_box);
if (plan)
execute_main_toolbar_plan(*this, plan.value());
};
}
if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-settings"))
{
button->on_click = [this](Node*) {
const auto plan = pp::app::plan_main_toolbar_command(
pp::app::MainToolbarCommand::show_settings);
if (plan)
execute_main_toolbar_plan(*this, plan.value());
};
}
}
template <class T> std::shared_ptr<T> create_panel(LayoutManager& manager)
{
std::shared_ptr<T> ret;
ret = std::make_shared<T>();
ret->set_manager(&manager);
ret->init();
ret->create();
ret->loaded();
return ret;
}
void App::init_sidebar()
{
sidebar = layout[main_id]->find<NodeBorder>("sidebar");
canvas = layout[main_id]->find<NodeCanvas>("paint-canvas");
quick = layout[main_id]->find<NodePanelQuick>("panel-quick");
floatings_container = layout[main_id]->find<Node>("floatings");
//brushes = layout[main_id]->find<NodePanelBrush>("panel-brush");
//layers = layout[main_id]->find<NodePanelLayer>("panel-layer");
//color = layout[main_id]->find<NodePanelColor>("panel-color");
//stroke = layout[main_id]->find<NodePanelStroke>("panel-stroke");
//brushes = find_or_create_panel<NodePanelBrush>(panels);
layers = create_panel<NodePanelLayer>(layout);
color = create_panel<NodePanelColor>(layout);
stroke = create_panel<NodePanelStroke>(layout);
grid = create_panel<NodePanelGrid>(layout);
presets = create_panel<NodePanelBrushPreset>(layout);
animation = create_panel<NodePanelAnimation>(layout);
//presets = find_or_create_panel<NodePanelBrushPreset>(panels);
canvas->m_canvas->on_mode_changed = [this](kCanvasMode prev, kCanvasMode mode) {
quick_mode_state[prev] = quick->get_state();
if (quick_mode_state.find(mode) != quick_mode_state.end())
quick->set_state(quick_mode_state[mode], true);
else
quick->reset_state(true);
brush_update(true, true);
};
color->on_color_changed = [this](Node* target, glm::vec4 color) {
apply_brush_color_plan(*this, color, true, false);
};
stroke->on_brush_changed = [this](Node* target, const std::string& path, const std::string& thumb) {
apply_brush_texture_plan(pp::app::BrushUiTextureSlot::tip, path, thumb);
};
stroke->on_pattern_changed = [this](Node*target, const std::string& path, const std::string& thumb) {
apply_brush_texture_plan(pp::app::BrushUiTextureSlot::pattern, path, thumb);
};
stroke->on_dual_changed = [this](Node*target, const std::string& path, const std::string& thumb) {
apply_brush_texture_plan(pp::app::BrushUiTextureSlot::dual, path, thumb);
};
stroke->on_stroke_change = [this](Node*) {
const auto plan = pp::app::plan_brush_ui_stroke_settings_changed();
LegacyBrushUiServices services(*this);
const auto status = pp::app::execute_brush_ui_plan(plan, services);
if (!status.ok())
LOG("Brush stroke settings action failed: %s", status.message);
};
quick->on_color_change = [this](Node*, glm::vec3 c) {
apply_brush_color_plan(*this, glm::vec4(c, 1.f), false, true);
};
quick->on_flow_change = [this](Node*, float value) {
stroke->set_flow(value, true, true);
};
quick->on_size_change = [this](Node*, float value) {
stroke->set_size(value, true, true);
};
quick->on_brush_change = [this](Node*, std::shared_ptr<Brush> b) {
apply_brush_preset_plan(*this, b);
};
layers->on_layer_add = [this](Node*, std::shared_ptr<class Layer> layer, int index) {
const auto plan = pp::app::plan_document_layer_add(
static_cast<int>(Canvas::I->m_layers.size()),
index,
layers->m_layers.back()->m_label_text);
if (!plan)
return;
execute_document_layer_operation_plan(*this, plan.value(), layer);
};
layers->on_layer_duplicate = [this](Node*, int source_index) {
const auto plan = pp::app::plan_document_layer_duplicate(
static_cast<int>(Canvas::I->m_layers.size()),
source_index);
if (!plan)
return;
execute_document_layer_operation_plan(*this, plan.value());
};
layers->on_layer_change = [this](Node*, int, int new_idx) {
const auto plan = pp::app::plan_document_layer_select(
static_cast<int>(canvas->m_canvas->m_layers.size()),
new_idx);
if (!plan)
return;
execute_document_layer_operation_plan(*this, plan.value());
};
layers->on_layer_order = [this](Node*, int old_idx, int new_idx) {
const auto plan = pp::app::plan_document_layer_reorder(
static_cast<int>(canvas->m_canvas->m_layers.size()),
old_idx,
new_idx);
if (!plan)
return;
execute_document_layer_operation_plan(*this, plan.value());
};
layers->on_layer_delete = [this](Node*, int idx) {
const auto plan = pp::app::plan_document_layer_remove(
static_cast<int>(canvas->m_canvas->m_layers.size()),
idx);
if (!plan)
return;
execute_document_layer_operation_plan(*this, plan.value());
};
layers->on_layer_opacity_changed = [this](Node*, int idx, float value) {
const auto plan = pp::app::plan_document_layer_opacity(
static_cast<int>(canvas->m_canvas->m_layers.size()),
idx,
value);
if (!plan)
return;
execute_document_layer_operation_plan(*this, plan.value());
};
layers->on_layer_visibility_changed = [this](Node*, int idx, bool visible) {
const auto plan = pp::app::plan_document_layer_visibility(
static_cast<int>(canvas->m_canvas->m_layers.size()),
idx,
visible);
if (!plan)
return;
execute_document_layer_operation_plan(*this, plan.value());
};
layers->on_layer_alpha_lock_changed = [this](Node*, int idx, bool locked) {
const auto plan = pp::app::plan_document_layer_alpha_lock(
static_cast<int>(canvas->m_canvas->m_layers.size()),
idx,
locked);
if (!plan)
return;
execute_document_layer_operation_plan(*this, plan.value());
};
layers->on_layer_blend_mode_changed = [this](Node*, int idx, int mode) {
const auto plan = pp::app::plan_document_layer_blend_mode(
static_cast<int>(canvas->m_canvas->m_layers.size()),
idx,
mode);
if (!plan)
return;
execute_document_layer_operation_plan(*this, plan.value());
};
layers->on_layer_highlight_changed = [this](Node*, int idx, bool highlight) {
const auto plan = pp::app::plan_document_layer_highlight(
static_cast<int>(canvas->m_canvas->m_layers.size()),
idx,
highlight);
if (!plan)
return;
execute_document_layer_operation_plan(*this, plan.value());
};
if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-stroke"))
{
button->on_click = [this, button](Node*) {
auto screen = layout[main_id]->m_size;
glm::vec2 pos = button->m_pos + glm::vec2(button->m_size.x * 0.5f, button->m_size.y);
if (stroke->m_parent)
{
if (auto fp = dynamic_cast<NodePanelFloating*>(stroke->m_parent->m_parent))
{
stroke->remove_from_parent();
fp->destroy();
}
}
layout[main_id]->add_child(stroke);
stroke->SetSize(350, glm::max(100.f, screen.y - pos.y - 50.f));
stroke->find("title")->SetVisibility(true);
auto tick = layout[main_id]->add_child<NodeImage>();
tick->SetPositioning(YGPositionTypeAbsolute);
tick->SetSize(32, 16);
tick->SetPosition(pos.x - 16, pos.y);
tick->set_image("data/ui/popup-tick-up.png");
layout[main_id]->update();
stroke->SetPosition(pos.x - stroke->m_size.x / 2.f, pos.y + 16);
stroke->SetPositioning(YGPositionTypeAbsolute);
stroke->m_capture_children = false;
stroke->m_mouse_ignore = false;
stroke->mouse_capture();
auto scroll = stroke->find<NodeScroll>("scroller");
//scroll->SetHeight(glm::max(100.f, screen.y - pos.y - 200.f));
stroke->on_popup_close = [this, tick](Node*) {
tick->destroy();
};
};
}
//if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-brush"))
//{
// button->on_click = [this, button](Node*) {
// panels->get_child_index(brushes.get()) == -1 ? panels->add_child(brushes) : panels->remove_child(brushes.get());
// panels->fix_scroll();
// button->set_color(panels->get_child_index(brushes.get()) == -1 ? color_button_normal : color_button_hlight);
// };
//}
//if (auto* button = layout[main_id]->find<NodeButton>("btn-brush-preset"))
//{
// button->on_click = [this, button](Node*) {
// panels->get_child_index(presets.get()) == -1 ? panels->add_child(presets) : panels->remove_child(presets.get());
// panels->fix_scroll();
// button->set_color(panels->get_child_index(presets.get()) == -1 ? color_button_normal : color_button_hlight);
// };
//}
if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-color"))
{
button->on_click = [this, button](Node*) {
auto screen = layout[main_id]->m_size;
glm::vec2 pos = button->m_pos + glm::vec2(button->m_size.x * 0.5f, button->m_size.y);
layout[main_id]->add_child(color);
color->find("title")->SetVisibility(true);
color->SetSize(350, 350);
auto tick = layout[main_id]->add_child<NodeImage>();
tick->SetPositioning(YGPositionTypeAbsolute);
tick->SetSize(32, 16);
tick->SetPosition(pos.x - 16, pos.y);
tick->set_image("data/ui/popup-tick-up.png");
layout[main_id]->update();
color->SetPosition(pos.x - color->m_size.x / 2.f, pos.y + 16);
color->SetPositioning(YGPositionTypeAbsolute);
color->m_capture_children = false;
color->m_mouse_ignore = false;
color->mouse_capture();
color->on_popup_close = [this, tick](Node*) {
tick->destroy();
};
};
}
if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-layer"))
{
button->on_click = [this, button](Node*) {
auto screen = layout[main_id]->m_size;
glm::vec2 pos = button->m_pos + glm::vec2(button->m_size.x * 0.5f, button->m_size.y);
layers->find("title")->SetVisibility(true);
layers->SetSize(350, YGUndefined);
if (layers->m_parent)
{
if (auto fp = dynamic_cast<NodePanelFloating*>(layers->m_parent->m_parent))
{
layers->remove_from_parent();
fp->destroy();
}
}
layout[main_id]->add_child(layers);
auto tick = layout[main_id]->add_child<NodeImage>();
tick->SetPositioning(YGPositionTypeAbsolute);
tick->SetSize(32, 16);
tick->SetPosition(pos.x - 16, pos.y);
tick->set_image("data/ui/popup-tick-up.png");
layout[main_id]->update();
layers->SetPosition(pos.x - layers->m_size.x / 2.f, pos.y + 16);
layers->SetPositioning(YGPositionTypeAbsolute);
layers->m_capture_children = false;
layers->m_mouse_ignore = false;
layers->mouse_capture();
auto scroll = layers->find<NodeScroll>("layers-container");
scroll->SetMaxHeight(glm::max(100.f, screen.y - pos.y - 200.f));
layers->on_popup_close = [this, tick](Node*) {
tick->destroy();
};
};
}
if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-grids-panel"))
{
button->on_click = [this, button](Node*) {
auto screen = layout[main_id]->m_size;
glm::vec2 pos = button->m_pos + glm::vec2(button->m_size.x * 0.5f, button->m_size.y);
grid->find("title")->SetVisibility(true);
grid->SetSize(350, YGUndefined);
if (grid->m_parent)
{
if (auto fp = dynamic_cast<NodePanelFloating*>(grid->m_parent->m_parent))
{
grid->remove_from_parent();
fp->destroy();
}
}
layout[main_id]->add_child(grid);
auto tick = layout[main_id]->add_child<NodeImage>();
tick->SetPositioning(YGPositionTypeAbsolute);
tick->SetSize(32, 16);
tick->SetPosition(pos.x - 16, pos.y);
tick->set_image("data/ui/popup-tick-up.png");
layout[main_id]->update();
grid->SetPosition(pos.x - grid->m_size.x / 2.f, pos.y + 16);
grid->SetPositioning(YGPositionTypeAbsolute);
grid->m_capture_children = false;
grid->m_mouse_ignore = false;
grid->mouse_capture();
auto scroll = grid->find<NodeScroll>("scroller");
scroll->SetMaxHeight(glm::max(100.f, screen.y - pos.y - 250.f));
grid->on_popup_close = [this, tick](Node*) {
tick->destroy();
};
};
}
}
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);
main->find<NodeButton>("btn-cam")->set_active(false);
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<NodeButtonCustom>("btn-mask-free")->set_active(false);
main->find<NodeButtonCustom>("btn-mask-line")->set_active(false);
main->find<NodeButtonCustom>("btn-bucket")->set_active(false);
set_canvas_tool_button_active(button, false);
}
kCanvasMode canvas_mode_from_tool(pp::app::CanvasToolMode mode)
{
switch (mode) {
case pp::app::CanvasToolMode::draw:
return kCanvasMode::Draw;
case pp::app::CanvasToolMode::erase:
return kCanvasMode::Erase;
case pp::app::CanvasToolMode::line:
return kCanvasMode::Line;
case pp::app::CanvasToolMode::camera:
return kCanvasMode::Camera;
case pp::app::CanvasToolMode::grid:
return kCanvasMode::Grid;
case pp::app::CanvasToolMode::copy:
return kCanvasMode::Copy;
case pp::app::CanvasToolMode::cut:
return kCanvasMode::Cut;
case pp::app::CanvasToolMode::fill:
return kCanvasMode::Fill;
case pp::app::CanvasToolMode::mask_free:
return kCanvasMode::MaskFree;
case pp::app::CanvasToolMode::mask_line:
return kCanvasMode::MaskLine;
case pp::app::CanvasToolMode::flood_fill:
return kCanvasMode::FloodFill;
}
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);
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()
{
if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-pen"))
{
button->on_click = [this, button](Node*) {
apply_canvas_tool_select(*this, button, pp::app::CanvasToolMode::draw);
};
//button->set_active(true);
const auto plan = pp::app::plan_canvas_tool_select(pp::app::CanvasToolMode::draw);
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*) {
const auto plan = pp::app::plan_canvas_tool_pick_toggle(
canvas->m_canvas->m_current_mode == kCanvasMode::Draw);
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();
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"))
{
button->on_click = [this, button](Node*) {
apply_canvas_tool_select(*this, button, pp::app::CanvasToolMode::erase);
};
}
if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-line"))
{
button->on_click = [this, button](Node*) {
apply_canvas_tool_select(*this, button, pp::app::CanvasToolMode::line);
};
}
if (auto* button = layout[main_id]->find<NodeButton>("btn-cam"))
{
button->on_click = [this, button](Node*) {
apply_canvas_tool_select(*this, button, pp::app::CanvasToolMode::camera);
};
}
if (auto* button = layout[main_id]->find<NodeButton>("btn-grid"))
{
button->on_click = [this, button](Node*) {
apply_canvas_tool_select(*this, button, pp::app::CanvasToolMode::grid);
};
}
if (auto* button = layout[main_id]->find<NodeButton>("btn-copy"))
{
button->on_click = [this, button](Node*) {
apply_canvas_tool_select(*this, button, pp::app::CanvasToolMode::copy);
};
}
if (auto* button = layout[main_id]->find<NodeButton>("btn-cut"))
{
button->on_click = [this, button](Node*) {
apply_canvas_tool_select(*this, button, pp::app::CanvasToolMode::cut);
};
}
if (auto* button = layout[main_id]->find<NodeButton>("btn-fill"))
{
// polygon fill
button->on_click = [this, button](Node*) {
apply_canvas_tool_select(*this, button, pp::app::CanvasToolMode::fill);
};
}
if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-mask-free"))
{
button->on_click = [this, button](Node*) {
apply_canvas_tool_select(*this, button, pp::app::CanvasToolMode::mask_free);
};
}
if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-mask-line"))
{
button->on_click = [this, button](Node*) {
apply_canvas_tool_select(*this, button, pp::app::CanvasToolMode::mask_line);
};
}
if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-bucket"))
{
button->on_click = [this, button](Node*) {
apply_canvas_tool_select(*this, button, pp::app::CanvasToolMode::flood_fill);
};
}
}
void App::init_menu_file()
{
if (auto* menu_file = layout[main_id]->find<NodeButtonCustom>("menu-file"))
{
menu_file->on_click = [=](Node*) {
glm::vec2 pos = menu_file->m_pos + glm::vec2(0, menu_file->m_size.y);
auto popup = layout[const_hash("file-menu")]->m_children[0]->clone<NodePopupMenu>();
popup->update();
if (YGNodeStyleGetDirection(layout[main_id]->y_node) == YGDirectionRTL)
pos.x = pos.x - popup->m_size.x + menu_file->m_size.x;
popup->SetPositioning(YGPositionTypeAbsolute);
popup->SetPosition(pos.x, pos.y);
layout[main_id]->add_child(popup);
if (auto b = popup->find<NodeButtonCustom>("file-newdoc"))
b->on_click = [this, popup](Node*) {
apply_file_menu_plan(*this, pp::app::FileMenuCommand::new_document);
popup->mouse_release();
popup->destroy();
};
if (auto b = popup->find<NodeButtonCustom>("file-import"))
b->on_click = [this, popup](Node*) {
apply_file_menu_plan(*this, pp::app::FileMenuCommand::import_image);
popup->mouse_release();
popup->destroy();
};
if (auto b = popup->find<NodeButtonCustom>("file-open"))
b->on_click = [this, popup](Node*) {
apply_file_menu_plan(*this, pp::app::FileMenuCommand::open_project);
popup->mouse_release();
popup->destroy();
};
if (auto b = popup->find<NodeButtonCustom>("file-browse"))
b->on_click = [this, popup](Node*) {
apply_file_menu_plan(*this, pp::app::FileMenuCommand::browse_cloud);
popup->mouse_release();
popup->destroy();
};
if (auto b = popup->find<NodeButtonCustom>("file-save"))
b->on_click = [this, popup](Node*) {
apply_file_menu_plan(*this, pp::app::FileMenuCommand::save);
popup->mouse_release();
popup->destroy();
};
if (auto b = popup->find<NodeButtonCustom>("file-save-as"))
b->on_click = [this, popup](Node*) {
apply_file_menu_plan(*this, pp::app::FileMenuCommand::save_as);
popup->mouse_release();
popup->destroy();
};
if (auto b = popup->find<NodeButtonCustom>("file-save-ver"))
b->on_click = [this, popup](Node*) {
apply_file_menu_plan(*this, pp::app::FileMenuCommand::save_version);
popup->mouse_release();
popup->destroy();
};
if (auto b = popup->find<NodeButtonCustom>("file-export"))
b->on_click = [this, popup](Node*) {
apply_file_menu_plan(*this, pp::app::FileMenuCommand::export_jpeg);
popup->mouse_release();
popup->destroy();
};
if (auto b = popup->find<NodeButtonCustom>("file-export-tick"))
b->on_click = [this, b, popup](Node*) {
glm::vec2 pos = b->m_pos + glm::vec2(b->m_size.x, 0);
auto subpopup = layout[const_hash("file-submenu-export")]->m_children[0]->clone<NodePopupMenu>();
subpopup->update();
if (YGNodeStyleGetDirection(layout[main_id]->y_node) == YGDirectionRTL)
pos.x = pos.x - subpopup->m_size.x + b->m_size.x;
subpopup->SetPositioning(YGPositionTypeAbsolute);
subpopup->SetPosition(pos.x, pos.y);
layout[main_id]->add_child(subpopup);
subpopup->find<NodeButtonCustom>("file-submenu-export-png")->on_click = [this, subpopup, popup](Node*) {
apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::png);
popup->mouse_release();
popup->destroy();
subpopup->mouse_release();
subpopup->destroy();
};
subpopup->find<NodeButtonCustom>("file-submenu-export-layers")->on_click = [this, subpopup, popup](Node*) {
apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::layers);
popup->mouse_release();
popup->destroy();
subpopup->mouse_release();
subpopup->destroy();
};
subpopup->find<NodeButtonCustom>("file-submenu-export-cube")->on_click = [this, subpopup, popup](Node*) {
apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::cube_faces);
popup->mouse_release();
popup->destroy();
subpopup->mouse_release();
subpopup->destroy();
};
subpopup->find<NodeButtonCustom>("file-submenu-export-depth")->on_click = [this, subpopup, popup](Node*) {
apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::depth);
popup->mouse_release();
popup->destroy();
subpopup->mouse_release();
subpopup->destroy();
};
subpopup->find<NodeButtonCustom>("file-submenu-export-anim")->on_click = [this, subpopup, popup](Node*) {
apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::animation_frames);
popup->mouse_release();
popup->destroy();
subpopup->mouse_release();
subpopup->destroy();
};
subpopup->find<NodeButtonCustom>("file-submenu-export-anim-mp4")->on_click = [this, subpopup, popup](Node*) {
apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::animation_mp4);
popup->mouse_release();
popup->destroy();
subpopup->mouse_release();
subpopup->destroy();
};
subpopup->find<NodeButtonCustom>("file-submenu-export-timelapse")->on_click = [this, subpopup, popup](Node*) {
apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::timelapse);
popup->mouse_release();
popup->destroy();
subpopup->mouse_release();
subpopup->destroy();
};
};
if (auto b = popup->find<NodeButtonCustom>("file-share"))
b->on_click = [this, popup](Node*) {
apply_file_menu_plan(*this, pp::app::FileMenuCommand::share);
popup->mouse_release();
popup->destroy();
};
if (auto b = popup->find<NodeButtonCustom>("file-resize"))
b->on_click = [this, popup](Node*) {
apply_file_menu_plan(*this, pp::app::FileMenuCommand::resize);
popup->mouse_release();
popup->destroy();
};
if (auto b = popup->find<NodeButtonCustom>("file-cloud-upload"))
b->on_click = [this, popup](Node*) {
apply_file_menu_plan(*this, pp::app::FileMenuCommand::cloud_upload);
popup->mouse_release();
popup->destroy();
};
if (auto b = popup->find<NodeButtonCustom>("file-cloud-browse"))
b->on_click = [this, popup](Node*) {
apply_file_menu_plan(*this, pp::app::FileMenuCommand::cloud_browse);
popup->mouse_release();
popup->destroy();
};
};
}
}
void App::init_menu_edit()
{
if (auto* menu_file = layout[main_id]->find<NodeButtonCustom>("menu-edit"))
{
menu_file->on_click = [=](Node*) {
glm::vec2 pos = menu_file->m_pos + glm::vec2(0, menu_file->m_size.y);
auto popup = layout[const_hash("edit-menu")]->m_children[0]->clone<NodePopupMenu>();
popup->update();
if (YGNodeStyleGetDirection(layout[main_id]->y_node) == YGDirectionRTL)
pos.x = pos.x - popup->m_size.x + menu_file->m_size.x;
popup->SetPositioning(YGPositionTypeAbsolute);
popup->SetPosition(pos.x, pos.y);
layout[main_id]->add_child(popup);
};
}
}
void App::init_menu_tools()
{
auto main = layout[main_id];
if (auto menu_exp = main->find<NodeButtonCustom>("menu-tools"))
{
menu_exp->on_click = [this, menu_exp, main](Node*) {
glm::vec2 pos = menu_exp->m_pos + glm::vec2(0, menu_exp->m_size.y);
auto popup_exp = layout[const_hash("tools-menu")]->m_children[0]->clone<NodePopupMenu>();
popup_exp->update();
if (YGNodeStyleGetDirection(layout[main_id]->y_node) == YGDirectionRTL)
pos.x = pos.x - popup_exp->m_size.x + menu_exp->m_size.x;
popup_exp->SetPositioning(YGPositionTypeAbsolute);
popup_exp->SetPosition(pos.x, pos.y);
layout[main_id]->add_child(popup_exp);
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();
if (YGNodeStyleGetDirection(layout[main_id]->y_node) == YGDirectionRTL)
pos.x = pos.x - popup_time->m_size.x + b->m_size.x;
popup_time->SetPositioning(YGPositionTypeAbsolute);
popup_time->SetPosition(pos.x, pos.y);
layout[main_id]->add_child(popup_time);
auto visible = [this](Node* panel) {
if (!panel)
return false;
for (auto& c : floatings_container->m_children)
{
if (auto fp = std::static_pointer_cast<NodePanelFloating>(c))
{
if (fp->m_container->is_child(panel))
return true;
}
}
return false;
};
popup_time->find<NodeButtonCustom>("panel-presets")->on_click = [this, popup_time, popup_exp, visible](Node*) {
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;
apply_tools_panel_chrome(*fpanel, plan);
if (!floating_presets)
{
floating_presets = fpanel->m_container->add_child_ref<NodePanelBrushPreset>();
floating_presets->SetHeightP(100);
//floating_presets->SetFlexGrow(1);
//floating_presets->find("toolbar")->destroy();
floating_presets->on_brush_changed = [this](Node* target, std::shared_ptr<Brush>& b) {
apply_brush_preset_plan(*this, b);
};
}
else
{
fpanel->m_container->add_child(floating_presets);
}
popup_exp->destroy();
popup_time->destroy();
};
popup_time->find<NodeButtonCustom>("panel-color")->on_click = [this, popup_time, popup_exp, visible](Node*) {
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;
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);
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);
};
}
else
{
fpanel->m_container->add_child(floating_color);
}
popup_exp->destroy();
popup_time->destroy();
};
popup_time->find<NodeButtonCustom>("panel-color-adv")->on_click = [this, popup_time, popup_exp, visible](Node*) {
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;
apply_tools_panel_chrome(*fpanel, plan);
if (!floating_picker)
{
floating_picker = fpanel->m_container->add_child_ref<NodeColorPicker>();
//floating_picker->find("title")->SetVisibility(false);
//floating_picker->SetHeightP(100);
//floating_picker->SetWidth(250);
floating_picker->m_autohide = false;
floating_picker->on_color_change = [this](Node* target, glm::vec3 color) {
apply_brush_color_plan(*this, glm::vec4(color, 1.f), false, false);
};
}
else
{
fpanel->m_container->add_child(floating_picker);
}
popup_exp->destroy();
popup_time->destroy();
};
popup_time->find<NodeButtonCustom>("panel-layers")->on_click = [this, popup_time, popup_exp, visible](Node*) {
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;
apply_tools_panel_chrome(*fpanel, plan);
fpanel->m_container->add_child(layers);
layers->SetPositioning(YGPositionTypeRelative);
layers->SetPosition(0, 0);
layers->SetWidthP(100);
layers->SetHeightP(100);
layers->SetFlexShrink(0);
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*) {
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);
apply_tools_panel_chrome(*fpanel, plan);
stroke->SetPositioning(YGPositionTypeRelative);
stroke->SetPosition(0, 0);
stroke->SetWidthP(100);
stroke->SetHeightP(100);
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*) {
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);
apply_tools_panel_chrome(*fpanel, plan);
grid->SetPositioning(YGPositionTypeRelative);
grid->SetPosition(0, 0);
grid->SetWidthP(100);
grid->SetHeightP(100);
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*) {
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);
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();
};
};
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();
if (YGNodeStyleGetDirection(layout[main_id]->y_node) == YGDirectionRTL)
pos.x = pos.x - popup_time->m_size.x + b->m_size.x;
popup_time->SetPositioning(YGPositionTypeAbsolute);
popup_time->SetPosition(pos.x, pos.y);
layout[main_id]->add_child(popup_time);
if (auto ui_scale = popup_time->find<NodeComboBox>("tools-ui-scale"))
{
std::vector<float> scale_options;
scale_options.reserve(ui_scale->m_data.size());
for (int i = 0; i < ui_scale->m_data.size(); i++)
scale_options.push_back(ui_scale->get_float(i));
const auto selection = pp::app::plan_scale_option_selection(App::I->zoom, scale_options);
if (selection.has_selection)
ui_scale->set_index(static_cast<int>(selection.index));
ui_scale->on_select = [ui_scale](Node* target, int index)
{
App::I->set_ui_scale(ui_scale->get_float(index));
};
}
if (auto vp_scale = popup_time->find<NodeComboBox>("tools-vp-scale"))
{
std::vector<float> scale_options;
scale_options.reserve(vp_scale->m_data.size());
for (int i = 0; i < vp_scale->m_data.size(); i++)
scale_options.push_back(vp_scale->get_float(i));
const auto selection = pp::app::plan_scale_option_selection(App::I->canvas->m_density, scale_options);
if (selection.has_selection)
vp_scale->set_index(static_cast<int>(selection.index));
vp_scale->on_select = [vp_scale](Node* target, int index)
{
const auto plan = pp::app::plan_viewport_scale(vp_scale->get_float(index));
App::I->canvas->set_density(plan.scale);
Settings::set("vp-scale", Serializer::Float(plan.scale));
Settings::save();
};
}
if (auto rtl_btn = popup_time->find<NodeButtonCustom>("tools-rtl"))
{
NodeCheckBox* cb = rtl_btn->find<NodeCheckBox>("tools-rtl-check");
cb->set_value(ui_rtl, false);
rtl_btn->on_click = [this, rtl_btn](Node* b)
{
NodeCheckBox* cb = rtl_btn->find<NodeCheckBox>("tools-rtl-check");
cb->set_value(!cb->checked, true);
};
rtl_btn->find<NodeCheckBox>("tools-rtl-check")->on_value_changed = [this, main](Node*, bool checked)
{
set_ui_rtl(checked);
};
}
if (auto vr_btn = popup_time->find<NodeButtonCustom>("tools-vr"))
{
NodeCheckBox* cb = vr_btn->find<NodeCheckBox>("tools-vr-check");
cb->set_value(has_vr);
vr_btn->on_click = [this, vr_btn](Node* b)
{
NodeCheckBox* cb = vr_btn->find<NodeCheckBox>("tools-vr-check");
cb->set_value(!cb->checked, true);
};
vr_btn->find<NodeCheckBox>("tools-vr-check")->on_value_changed = [this, main](Node* target, bool checked)
{
if (checked)
{
if (!vr_start())
{
auto cb = static_cast<NodeCheckBox*>(target);
cb->set_value(false);
message_box("VR Failed", "Couldn't start Virtual Reality mode");
}
}
else
{
vr_stop();
}
};
}
if (auto vr_btn = popup_time->find<NodeButtonCustom>("tools-vr-controllers"))
{
NodeCheckBox* cb = vr_btn->find<NodeCheckBox>("tools-vr-controllers-check");
cb->set_value(vr_controllers_enabled);
vr_btn->on_click = [this, vr_btn](Node* b)
{
NodeCheckBox* cb = vr_btn->find<NodeCheckBox>("tools-vr-controllers-check");
cb->set_value(!cb->checked, true);
};
vr_btn->find<NodeCheckBox>("tools-vr-controllers-check")->on_value_changed = [this, main](Node* target, bool checked)
{
const auto plan = pp::app::plan_vr_controllers_preference(checked);
vr_controllers_enabled = plan.value;
Settings::set("vr-controllers-enabled", Serializer::Boolean(plan.value));
Settings::save();
};
}
if (auto btn = popup_time->find<NodeButtonCustom>("tools-timelapse"))
{
NodeCheckBox* cb = btn->find<NodeCheckBox>("tools-timelapse-check");
cb->set_value(Settings::value_or<Serializer::Boolean>("auto-timelapse", true), false);
btn->on_click = [this, btn](Node* b)
{
NodeCheckBox* cb = btn->find<NodeCheckBox>("tools-timelapse-check");
cb->set_value(!cb->checked, true);
};
btn->find<NodeCheckBox>("tools-timelapse-check")->on_value_changed = [this, main](Node*, bool checked)
{
const auto plan = pp::app::plan_timelapse_preference(checked, App::I->rec_running);
if (plan.recording_action == pp::app::TimelapseRecordingAction::stop_recording)
App::I->rec_stop();
else if (plan.recording_action == pp::app::TimelapseRecordingAction::start_recording)
App::I->rec_start();
Settings::set("auto-timelapse", Serializer::Boolean(plan.enabled));
Settings::save();
};
}
if (auto mode = popup_time->find<NodeComboBox>("tools-show-cursor"))
{
mode->set_index(Settings::value_or<Serializer::Integer>("show-cursor", 0));
mode->on_select = [mode](Node* target, int index)
{
const auto plan = pp::app::plan_canvas_cursor_mode(index);
App::I->canvas->set_cursor_visibility((NodeCanvas::kCursorVisibility)plan.value);
Settings::set("show-cursor", Serializer::Integer(plan.value));
Settings::save();
};
}
};
popup_exp->find<NodeButtonCustom>("clear-grids")->on_click = [this, popup_exp](Node*) {
const auto plan = pp::app::plan_tools_menu_command(pp::app::ToolsMenuCommand::clear_grids);
execute_tools_menu_plan(*this, plan);
if (plan.closes_root_popup)
{
popup_exp->mouse_release();
popup_exp->destroy();
}
};
popup_exp->find<NodeButtonCustom>("camera-reset")->on_click = [this, popup_exp](Node*) {
const auto plan = pp::app::plan_tools_menu_command(pp::app::ToolsMenuCommand::reset_camera);
execute_tools_menu_plan(*this, plan);
if (plan.closes_root_popup)
{
popup_exp->mouse_release();
popup_exp->destroy();
}
};
popup_exp->find<NodeButtonCustom>("shortcuts")->on_click = [this, popup_exp](Node*) {
const auto plan = pp::app::plan_tools_menu_command(pp::app::ToolsMenuCommand::shortcuts);
execute_tools_menu_plan(*this, plan);
if (plan.closes_root_popup)
{
popup_exp->mouse_release();
popup_exp->destroy();
}
};
/*
popup_exp->find<NodeButtonCustom>("mp4test")->on_click = [this, popup_exp](Node*) {
dialog_export_mp4();
popup_exp->mouse_release();
popup_exp->destroy();
};
*/
#if __IOS__
popup_exp->find<NodeButtonCustom>("sonarpen")->on_click = [this, popup_exp](Node*) {
const auto plan = pp::app::plan_tools_menu_command(pp::app::ToolsMenuCommand::sonarpen, true);
execute_tools_menu_plan(*this, plan);
if (plan.closes_root_popup)
{
popup_exp->mouse_release();
popup_exp->destroy();
}
};
#endif
};
}
}
void App::init_menu_about()
{
if (auto* menu_file = layout[main_id]->find<NodeButtonCustom>("menu-about"))
{
menu_file->on_click = [=](Node*) {
glm::vec2 pos = menu_file->m_pos + glm::vec2(0, menu_file->m_size.y);
auto popup = layout[const_hash("about-menu")]->m_children[0]->clone<NodePopupMenu>();
popup->update();
if (YGNodeStyleGetDirection(layout[main_id]->y_node) == YGDirectionRTL)
pos.x = pos.x - popup->m_size.x + menu_file->m_size.x;
popup->SetPositioning(YGPositionTypeAbsolute);
popup->SetPosition(pos.x, pos.y);
layout[main_id]->add_child(popup);
popup->find<NodeButtonCustom>("about-app")->on_click = [this, popup](Node*) {
const auto plan = pp::app::plan_about_menu_command(
pp::app::AboutMenuCommand::about_app,
g_version_major,
g_version_minor,
g_version_fix);
execute_about_menu_plan(*this, plan);
if (plan.closes_root_popup)
{
popup->mouse_release();
popup->destroy();
}
};
popup->find<NodeButtonCustom>("about-doc")->on_click = [this, popup](Node*) {
// auto path = Asset::absolute("data/doc/test.pdf");
// display_file(path);
const auto plan = pp::app::plan_about_menu_command(
pp::app::AboutMenuCommand::help_guide,
g_version_major,
g_version_minor,
g_version_fix);
execute_about_menu_plan(*this, plan);
if (plan.closes_root_popup)
{
popup->mouse_release();
popup->destroy();
}
};
if (auto item = popup->find<NodeButtonCustom>("about-news"))
{
if (auto text = item->find<NodeText>("menu-label"))
{
const auto plan = pp::app::plan_about_menu_command(
pp::app::AboutMenuCommand::whats_new,
g_version_major,
g_version_minor,
g_version_fix);
text->set_text(plan.label.c_str());
}
item->on_click = [this, popup](Node*) {
const auto plan = pp::app::plan_about_menu_command(
pp::app::AboutMenuCommand::whats_new,
g_version_major,
g_version_minor,
g_version_fix);
execute_about_menu_plan(*this, plan);
if (plan.closes_root_popup)
{
popup->mouse_release();
popup->destroy();
}
};
}
if (auto b = popup->find<NodeButtonCustom>("about-crash"))
{
b->on_click = [this, popup](Node*) {
const auto plan = pp::app::plan_about_menu_command(
pp::app::AboutMenuCommand::induce_crash,
g_version_major,
g_version_minor,
g_version_fix);
execute_about_menu_plan(*this, plan);
if (plan.closes_root_popup)
{
popup->mouse_release();
popup->destroy();
}
};
}
if (auto b = popup->find<NodeButtonCustom>("about-perf"))
{
b->on_click = [this, popup](Node*) {
const auto plan = pp::app::plan_about_menu_command(
pp::app::AboutMenuCommand::performance_test,
g_version_major,
g_version_minor,
g_version_fix,
true,
Canvas::I != nullptr);
execute_about_menu_plan(*this, plan);
if (plan.closes_root_popup)
{
popup->mouse_release();
popup->destroy();
}
};
}
};
}
}
void App::brush_update(bool update_color, bool update_brush)
{
// brushes->select_brush(canvas->m_brush->id);
// stroke->set_params(canvas->m_brush);
render_task_async([this, update_color, update_brush]
{
if (update_brush)
{
stroke->update_controls();
quick->m_slider_flow->set_value(stroke->m_tip_flow->get_value());
quick->m_slider_size->set_value(stroke->m_tip_size->get_value());
*quick->m_button_brush_current_preview->m_brush = *Canvas::I->m_current_brush;
quick->m_button_brush_current_preview->draw_stroke();
}
if (update_color)
{
quick->m_button_color_current_inner->m_color = Canvas::I->m_current_brush->m_tip_color;
if (floating_picker)
floating_picker->set_color(Canvas::I->m_current_brush->m_tip_color);
if (floating_color)
floating_color->set_color(Canvas::I->m_current_brush->m_tip_color);
}
}, true);
}
void App::init_menu_layer()
{
if (auto* menu_file = layout[main_id]->find<NodeButtonCustom>("menu-layers"))
{
menu_file->on_click = [=](Node*) {
glm::vec2 pos = menu_file->m_pos + glm::vec2(0, menu_file->m_size.y);
auto popup = layout[const_hash("layers-menu")]->m_children[0]->clone<NodePopupMenu>();
popup->update();
if (YGNodeStyleGetDirection(layout[main_id]->y_node) == YGDirectionRTL)
pos.x = pos.x - popup->m_size.x + menu_file->m_size.x;
popup->SetPositioning(YGPositionTypeAbsolute);
popup->SetPosition(pos.x, pos.y);
layout[main_id]->add_child(popup);
popup->find<NodeButtonCustom>("layer-clear")->on_click = [this, popup](Node*) {
const auto plan = make_layer_menu_plan(pp::app::DocumentLayerMenuCommand::clear, *this);
execute_document_layer_menu_plan(*this, plan);
popup->mouse_release();
popup->destroy();
};
{
const auto plan = make_layer_menu_plan(pp::app::DocumentLayerMenuCommand::clear, *this);
popup->find<NodeButtonCustom>("layer-clear")->
find<NodeText>("menu-label")->
set_text(plan.label.c_str());
}
popup->find<NodeButtonCustom>("layer-rename")->on_click = [this, popup](Node*) {
const auto plan = make_layer_menu_plan(pp::app::DocumentLayerMenuCommand::rename, *this);
execute_document_layer_menu_plan(*this, plan);
popup->mouse_release();
popup->destroy();
};
{
const auto plan = make_layer_menu_plan(pp::app::DocumentLayerMenuCommand::rename, *this);
popup->find<NodeButtonCustom>("layer-rename")->
find<NodeText>("menu-label")->
set_text(plan.label.c_str());
}
popup->find<NodeButtonCustom>("layer-merge")->on_click = [this, popup](Node*) {
const auto plan = make_layer_menu_plan(pp::app::DocumentLayerMenuCommand::merge_down, *this);
execute_document_layer_menu_plan(*this, plan);
popup->mouse_release();
popup->destroy();
};
{
const auto plan = make_layer_menu_plan(pp::app::DocumentLayerMenuCommand::merge_down, *this);
popup->find<NodeButtonCustom>("layer-merge")->
find<NodeText>("menu-label")->
set_text(plan.label.c_str());
}
};
}
}
void App::initLayout()
{
LOG("initializing layout statics");
NodeBorder::static_init();
NodeImage::static_init();
NodeIcon::static_init();
NodeStrokePreview::static_init();
static std::vector<std::shared_ptr<Layer>> saved_layers;
layout.on_reloading = [&] {
saved_layers = std::move(Canvas::I->m_layers);
ui_save();
NodeStrokePreview::empty_queue();
};
layout.on_loaded = [&] (bool reloaded) {
LOG("initializing layout updating after load %d x %d zoom %f", (int)width, (int)height, zoom);
layout[main_id]->update(width, height, zoom);
LOG("initializing layout components");
init_sidebar();
if (reloaded)
{
for (const auto& l : saved_layers)
layers->add_layer(l->m_name.c_str(), false, true, l);
}
else
{
layers->add_layer("Default", false, true);
Canvas::I->m_unsaved = false;
}
init_toolbar_draw();
init_toolbar_main();
init_menu_file();
init_menu_edit();
init_menu_layer();
init_menu_tools();
init_menu_about();
// set version string
if (auto* version_label = layout[main_id]->find<NodeText>("version"))
{
version_label->set_text(g_version);
}
const auto renderer_features = ShaderManager::render_device_features();
const auto renderer_diagnostics = pp::app::plan_renderer_diagnostics({
.framebuffer_fetch = renderer_features.framebuffer_fetch,
.float32_render_targets = renderer_features.float32_render_targets,
.float32_linear_filtering = ShaderManager::ext_float32_linear,
.float16_render_targets = renderer_features.float16_render_targets,
});
if (auto x = layout[main_id]->find<NodeBorder>("ext-fbf"))
{
x->m_color = renderer_diagnostics.framebuffer_fetch.supported ?
glm::vec4(0, 1, 0, 1) :
glm::vec4(1, 0, 0, 1);
}
if (auto x = layout[main_id]->find<NodeBorder>("ext-flt"))
{
if (renderer_diagnostics.floating_point_targets.supported)
{
if (auto t = x->find<NodeText>("ext-flt-text"))
{
t->set_text(std::string(renderer_diagnostics.floating_point_targets.label));
}
x->m_color = glm::vec4(0, 1, 0, 1);
}
else
{
x->m_color = glm::vec4(1, 0, 0, 1);
}
}
dialog_whatsnew(false);
brush_update(true, true);
// hacky thing to make the toolbar buttons not steal events when moving cursor fast
if (auto* toolbar = layout[main_id]->find<Node>("toolbar"))
toolbar->m_flood_events = true;
NodeImage* n = new NodeImage;
n->m_path = "data/ui/p-black.png";
n->m_tex_id = const_hash("data/ui/p-black.png");
n->SetSize(30, 45);
n->create();
NodeButtonCustom* butt = new NodeButtonCustom;
butt->create();
butt->add_child(n);
butt->SetPositioning(YGPositionTypeAbsolute);
butt->set_color({ 0, 0, 0, 0 });
//n->SetPosition(100, 100);
YGNodeStyleSetPosition(butt->y_node, YGEdgeBottom, 8);
YGNodeStyleSetPosition(butt->y_node, YGEdgeLeft, 10);
//butt->SetSize(30, 45);
layout[main_id]->add_child(butt);
butt->on_click = [this](Node*){
toggle_ui();
};
ui_restore();
redraw = true;
};
LOG("initializing layout xml");
if (layout.m_loaded)
{
LOG("restore layout");
layout.restore_context();
}
else
layout.load("data/layout.xml");
LOG("initializing layout completed");
LOG("initializing layout designer xml");
layout_designer.on_loaded = [&](bool reloaded) {
layout_designer.create();
//layout_designer[main_id]->add_child(layout_designer.instantiate("shortcuts"));
auto p = layout_designer[main_id]->add_child<NodeShortcuts>();
//p->SetPosition(300, 300);
//p->SetSize(600, 400);
//p->m_container->add_child<NodePanelAnimation>();
};
//layout_designer.load("data/dialogs/shortcuts.xml");
}
void App::set_ui_scale(float scale)
{
const auto plan = pp::app::plan_ui_scale(scale, display_density);
zoom = plan.scale;
FontManager::change_scale(plan.font_scale);
Settings::set("ui-scale", Serializer::Float(plan.scale));
Settings::save();
App::I->title_update();
}
void App::set_ui_rtl(bool rtl)
{
const auto plan = pp::app::plan_interface_direction(rtl);
ui_rtl = plan.direction == pp::app::InterfaceDirection::right_to_left;
layout[main_id]->find("central-row")->SetRTL(
ui_rtl ? YGDirectionRTL : YGDirectionLTR);
}
bool App::get_ui_rtl() const
{
return ui_rtl;
}
void App::ui_save()
{
Serializer::Descriptor d;
d.class_id = "ui-state";
Serializer::List list_floatings;
for (auto const& c : layout[main_id]->find("floatings")->m_children)
{
if (auto const& f = std::dynamic_pointer_cast<NodePanelFloating>(c))
{
auto fd = list_floatings.add<Serializer::Descriptor>();
fd->class_id = "ui-flt";
fd->set("pos", Serializer::Vec2(f->GetPosition()));
fd->set("size", Serializer::Vec2(f->m_size));
fd->set("class", Serializer::Integer((int)f->m_class));
fd->set("title", Serializer::CString(f->m_title->m_text));
}
}
d.set("floatings", list_floatings);
Serializer::List list_drop_left;
for (auto const& c : layout[main_id]->find("drop-left")->m_children)
{
if (auto const& f = std::dynamic_pointer_cast<NodePanelFloating>(c))
{
auto fd = list_drop_left.add<Serializer::Descriptor>();
fd->class_id = "ui-dpl";
fd->set("size", Serializer::Vec2(f->m_size));
fd->set("class", Serializer::Integer((int)f->m_class));
fd->set("title", Serializer::CString(f->m_title->m_text));
}
}
d.set("drop-left", list_drop_left);
Serializer::List list_drop_right;
for (auto const& c : layout[main_id]->find("drop-right")->m_children)
{
if (auto const& f = std::dynamic_pointer_cast<NodePanelFloating>(c))
{
auto fd = list_drop_right.add<Serializer::Descriptor>();
fd->class_id = "ui-dpr";
fd->set("size", Serializer::Vec2(f->m_size));
fd->set("class", Serializer::Integer((int)f->m_class));
fd->set("title", Serializer::CString(f->m_title->m_text));
}
}
d.set("drop-right", list_drop_right);
Settings::set("ui", d);
Settings::set("ui-rtl", Serializer::Boolean(ui_rtl));
#if _WIN32
extern void win32_save_window_state();
win32_save_window_state();
#elif __OSX__
[osx_app save_ui_state];
#endif
Settings::save();
}
void App::ui_restore()
{
if (Settings::has("ui-rtl"))
set_ui_rtl(Settings::value<Serializer::Integer>("ui-rtl"));
if (!Settings::has("ui"))
return;
auto floatings = layout[main_id]->find_ref("floatings");
auto drop_left = layout[main_id]->find_ref("drop-left");
auto drop_right = layout[main_id]->find_ref("drop-right");
auto d = Settings::get<Serializer::Descriptor>("ui");
for (auto const& l : d->get<Serializer::List>("floatings")->items)
{
auto ld = std::static_pointer_cast<Serializer::Descriptor>(l);
auto pos = ld->value<Serializer::Vec2>("pos");
auto size = ld->value<Serializer::Vec2>("size");
auto cls = static_cast<NodePanelFloating::kClass>(ld->value<Serializer::Integer>("class"));
auto f = floatings->add_child<NodePanelFloating>();
std::string title = "Floating Panel";
ld->value<Serializer::CString>("title", title);
f->m_title->set_text(title.c_str());
switch (cls)
{
case NodePanelFloating::kClass::Presets:
{
floating_presets = f->m_container->add_child_ref<NodePanelBrushPreset>();
floating_presets->SetHeightP(100);
//floating_presets->find("toolbar")->destroy();
floating_presets->on_brush_changed = [this](Node* target, std::shared_ptr<Brush>& b) {
apply_brush_preset_plan(*this, b);
};
break;
}
case NodePanelFloating::kClass::Color:
{
floating_color = f->m_container->add_child_ref<NodePanelColor>();
floating_color->SetHeightP(100);
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);
};
break;
}
case NodePanelFloating::kClass::ColorAdv:
{
floating_picker = f->m_container->add_child_ref<NodeColorPicker>();
floating_picker->m_autohide = false;
floating_picker->on_color_change = [this](Node* target, glm::vec3 color) {
apply_brush_color_plan(*this, glm::vec4(color, 1.f), false, false);
};
break;
}
case NodePanelFloating::kClass::Layers:
f->m_container->add_child(layers);
f->SetMinHeight(100);
f->SetHeight(300);
layers->find("title")->SetVisibility(false);
layers->SetPositioning(YGPositionTypeRelative);
layers->SetPosition(0, 0);
layers->SetWidthP(100);
layers->SetHeightP(100);
layers->SetFlexShrink(0);
break;
case NodePanelFloating::kClass::Brush:
f->m_container->add_child(stroke);
stroke->find("title")->SetVisibility(false);
stroke->SetPositioning(YGPositionTypeRelative);
stroke->SetPosition(0, 0);
stroke->SetWidthP(100);
stroke->SetHeightP(100);
break;
case NodePanelFloating::kClass::Grids:
f->m_container->add_child(grid);
grid->find("title")->SetVisibility(false);
grid->SetPositioning(YGPositionTypeRelative);
grid->SetPosition(0, 0);
grid->SetWidthP(100);
grid->SetHeightP(100);
break;
case NodePanelFloating::kClass::Animation:
f->m_container->add_child(animation);
f->m_droppable = false;
//grid->find("title")->SetVisibility(false);
animation->SetPositioning(YGPositionTypeRelative);
animation->SetPosition(0, 0);
animation->SetWidthP(100);
animation->SetHeightP(100);
break;
case NodePanelFloating::kClass::Generic:
default:
f->m_container->add_child<Node>();
break;
}
f->m_class = cls;
f->SetSize(size);
f->SetPosition(pos);
f->SetPositioning(YGPositionTypeAbsolute);
}
for (auto const& l : d->get<Serializer::List>("drop-left")->items)
{
auto ld = std::static_pointer_cast<Serializer::Descriptor>(l);
auto size = ld->value<Serializer::Vec2>("size");
auto cls = static_cast<NodePanelFloating::kClass>(ld->value<Serializer::Integer>("class"));
auto f = drop_left->add_child<NodePanelFloating>();
std::string title = "Floating Panel";
ld->value<Serializer::CString>("title", title);
f->m_title->set_text(title.c_str());
switch (cls)
{
case NodePanelFloating::kClass::Presets:
{
auto floating_presets = f->m_container->add_child<NodePanelBrushPreset>();
floating_presets->SetHeightP(100);
//floating_presets->find("toolbar")->destroy();
floating_presets->on_brush_changed = [this](Node* target, std::shared_ptr<Brush>& b) {
apply_brush_preset_plan(*this, b);
};
break;
}
case NodePanelFloating::kClass::Color:
{
auto floating_color = f->m_container->add_child<NodePanelColor>();
floating_color->SetHeightP(100);
floating_color->find("title")->destroy();
floating_color->on_color_changed = [this](Node* target, glm::vec4 color) {
apply_brush_color_plan(*this, color, false, false);
};
break;
}
case NodePanelFloating::kClass::ColorAdv:
{
floating_picker = f->m_container->add_child_ref<NodeColorPicker>();
floating_picker->m_autohide = false;
floating_picker->on_color_change = [this](Node* target, glm::vec3 color) {
apply_brush_color_plan(*this, glm::vec4(color, 1.f), false, false);
};
break;
}
case NodePanelFloating::kClass::Layers:
f->m_container->add_child(layers);
layers->SetPositioning(YGPositionTypeRelative);
layers->SetPosition(0, 0);
layers->SetWidthP(100);
layers->SetHeightP(100);
layers->SetFlexShrink(0);
break;
case NodePanelFloating::kClass::Brush:
f->m_container->add_child(stroke);
stroke->SetPositioning(YGPositionTypeRelative);
stroke->SetPosition(0, 0);
stroke->SetWidthP(100);
stroke->SetHeightP(100);
break;
case NodePanelFloating::kClass::Grids:
f->m_container->add_child(grid);
grid->SetPositioning(YGPositionTypeRelative);
grid->SetPosition(0, 0);
grid->SetWidthP(100);
grid->SetHeightP(100);
break;
case NodePanelFloating::kClass::Generic:
default:
f->m_container->add_child<Node>();
break;
}
f->m_class = cls;
f->m_dock = drop_left;
f->SetPositioning(YGPositionTypeRelative);
f->SetPosition(0, 0);
f->SetSize(size);
}
for (auto const& l : d->get<Serializer::List>("drop-right")->items)
{
auto ld = std::static_pointer_cast<Serializer::Descriptor>(l);
auto size = ld->value<Serializer::Vec2>("size");
auto cls = static_cast<NodePanelFloating::kClass>(ld->value<Serializer::Integer>("class"));
auto f = drop_right->add_child<NodePanelFloating>();
std::string title = "Floating Panel";
ld->value<Serializer::CString>("title", title);
f->m_title->set_text(title.c_str());
switch (cls)
{
case NodePanelFloating::kClass::Presets:
{
auto floating_presets = f->m_container->add_child<NodePanelBrushPreset>();
floating_presets->SetHeightP(100);
//floating_presets->find("toolbar")->destroy();
floating_presets->on_brush_changed = [this](Node* target, std::shared_ptr<Brush>& b) {
apply_brush_preset_plan(*this, b);
};
break;
}
case NodePanelFloating::kClass::Color:
{
auto floating_color = f->m_container->add_child<NodePanelColor>();
floating_color->SetHeightP(100);
floating_color->find("title")->destroy();
floating_color->on_color_changed = [this](Node* target, glm::vec4 color) {
apply_brush_color_plan(*this, color, false, false);
};
break;
}
case NodePanelFloating::kClass::ColorAdv:
{
floating_picker = f->m_container->add_child_ref<NodeColorPicker>();
floating_picker->m_autohide = false;
floating_picker->on_color_change = [this](Node* target, glm::vec3 color) {
apply_brush_color_plan(*this, glm::vec4(color, 1.f), false, false);
};
break;
}
case NodePanelFloating::kClass::Layers:
f->m_container->add_child(layers);
layers->SetPositioning(YGPositionTypeRelative);
layers->SetPosition(0, 0);
layers->SetWidthP(100);
layers->SetHeightP(100);
layers->SetFlexShrink(0);
break;
case NodePanelFloating::kClass::Brush:
f->m_container->add_child(stroke);
stroke->SetPositioning(YGPositionTypeRelative);
stroke->SetPosition(0, 0);
stroke->SetWidthP(100);
stroke->SetHeightP(100);
break;
case NodePanelFloating::kClass::Grids:
f->m_container->add_child(grid);
grid->SetPositioning(YGPositionTypeRelative);
grid->SetPosition(0, 0);
grid->SetWidthP(100);
grid->SetHeightP(100);
break;
case NodePanelFloating::kClass::Generic:
default:
f->m_container->add_child<Node>();
break;
}
f->m_class = cls;
f->m_dock = drop_right;
f->SetPositioning(YGPositionTypeRelative);
f->SetPosition(0, 0);
f->SetSize(size);
}
}