2698 lines
102 KiB
C++
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);
|
|
}
|
|
}
|