#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 namespace { 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; Canvas::I->m_current_brush->m_tip_color = glm::vec4( plan.value().r, plan.value().g, plan.value().b, plan.value().a); if (update_quick) app.quick->set_color(Canvas::I->m_current_brush->m_tip_color); if (update_color_panel) app.color->set_color(Canvas::I->m_current_brush->m_tip_color); app.brush_update(plan.value().update_color_ui, plan.value().update_brush_ui); return true; } 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; switch (plan.value().texture_slot) { case pp::app::BrushUiTextureSlot::tip: Canvas::I->m_current_brush->load_tip(plan.value().path, plan.value().thumbnail_path); break; case pp::app::BrushUiTextureSlot::pattern: Canvas::I->m_current_brush->load_pattern(plan.value().path, plan.value().thumbnail_path); break; case pp::app::BrushUiTextureSlot::dual: Canvas::I->m_current_brush->load_dual(plan.value().path, plan.value().thumbnail_path); break; } App::I->brush_update(plan.value().update_color_ui, plan.value().update_brush_ui); return true; } bool apply_brush_preset_plan(App& app, const std::shared_ptr& brush) { const auto plan = pp::app::plan_brush_ui_preset_replace(static_cast(brush)); if (!plan) return false; auto color = Canvas::I->m_current_brush->m_tip_color; *Canvas::I->m_current_brush = *brush; if (plan.value().preserves_existing_color) Canvas::I->m_current_brush->m_tip_color = color; if (plan.value().loads_brush_resources) Canvas::I->m_current_brush->load(); app.brush_update(plan.value().update_color_ui, plan.value().update_brush_ui); return true; } 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( 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(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(plan.width), static_cast(plan.height)); } else { if (plan.width > 0) panel.SetWidth(static_cast(plan.width)); if (plan.height > 0) panel.SetHeight(static_cast(plan.height)); } if (plan.min_width > 0) panel.SetMinWidth(static_cast(plan.min_width)); if (plan.min_height > 0) panel.SetMinHeight(static_cast(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() override { ActionManager::undo(); } void invoke_redo() override { ActionManager::redo(); } void clear_history() override { ActionManager::clear(); } void clear_canvas(const pp::app::DocumentCanvasClearPlan& plan) override { 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_; }; 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); } 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: 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(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(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 { if (app_.canvas && app_.canvas->m_canvas) app_.canvas->m_canvas->clear(); } 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_; }; 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); } } // namespace void App::title_update() { if (auto docname = layout[main_id]->find("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("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("btn-anim")) { button->on_click = [this, button](Node*) { if (canvas) { //canvas->m_canvas->export_anim(); } }; } if (auto* button = layout[main_id]->find("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("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("btn-undo")) { button->on_click = [this, button](Node*) { const auto plan = pp::app::plan_main_toolbar_command( pp::app::MainToolbarCommand::undo, static_cast(ActionManager::I.m_actions.size())); if (plan) execute_main_toolbar_plan(*this, plan.value()); }; } if (auto* button = layout[main_id]->find("btn-redo")) { button->on_click = [this, button](Node*) { const auto plan = pp::app::plan_main_toolbar_command( pp::app::MainToolbarCommand::redo, 0, static_cast(ActionManager::I.m_redos.size())); if (plan) execute_main_toolbar_plan(*this, plan.value()); }; } if (auto* button = layout[main_id]->find("btn-clean-memory")) { button->on_click = [this](Node*) { const auto plan = pp::app::plan_main_toolbar_command( pp::app::MainToolbarCommand::clear_history, static_cast(ActionManager::I.m_actions.size()), static_cast(ActionManager::I.m_redos.size()), static_cast(ActionManager::I.m_memory)); if (plan) execute_main_toolbar_plan(*this, plan.value()); }; } if (auto* button = layout[main_id]->find("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(canvas)); if (plan) execute_main_toolbar_plan(*this, plan.value()); }; } if (auto* button = layout[main_id]->find("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("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 std::shared_ptr create_panel(LayoutManager& manager) { std::shared_ptr ret; ret = std::make_shared(); ret->set_manager(&manager); ret->init(); ret->create(); ret->loaded(); return ret; } void App::init_sidebar() { sidebar = layout[main_id]->find("sidebar"); canvas = layout[main_id]->find("paint-canvas"); quick = layout[main_id]->find("panel-quick"); floatings_container = layout[main_id]->find("floatings"); //brushes = layout[main_id]->find("panel-brush"); //layers = layout[main_id]->find("panel-layer"); //color = layout[main_id]->find("panel-color"); //stroke = layout[main_id]->find("panel-stroke"); //brushes = find_or_create_panel(panels); layers = create_panel(layout); color = create_panel(layout); stroke = create_panel(layout); grid = create_panel(layout); presets = create_panel(layout); animation = create_panel(layout); //presets = find_or_create_panel(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(); brush_update(plan.update_color_ui, plan.update_brush_ui); }; 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 b) { apply_brush_preset_plan(*this, b); }; layers->on_layer_add = [this](Node*, std::shared_ptr layer, int index) { const auto plan = pp::app::plan_document_layer_add( static_cast(Canvas::I->m_layers.size()), index, layers->m_layers.back()->m_label_text); if (!plan) return; Canvas::I->layer_add(plan.value().name, layer, plan.value().insert_index); Canvas::I->m_unsaved = plan.value().marks_unsaved; Canvas::I->anim_update(); if (plan.value().reloads_animation_layers) animation->load_layers(); if (plan.value().updates_title) title_update(); }; layers->on_layer_duplicate = [this](Node*, int source_index) { const auto plan = pp::app::plan_document_layer_duplicate( static_cast(Canvas::I->m_layers.size()), source_index); if (!plan) return; Canvas::I->layer_add(layers->m_layers.back()->m_label_text.c_str(), nullptr, plan.value().insert_index); auto& dst = Canvas::I->m_layers[plan.value().insert_index]; auto& src = Canvas::I->m_layers[plan.value().source_index]; for (int i = 1; i < src->frames_count(); i++) dst->add_frame(); Canvas::I->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; Canvas::I->m_unsaved = plan.value().marks_unsaved; if (plan.value().reloads_animation_layers) animation->load_layers(); if (plan.value().updates_title) title_update(); }; layers->on_layer_change = [this](Node*, int old_idx, int new_idx) { const auto plan = pp::app::plan_document_layer_select( static_cast(canvas->m_canvas->m_layers.size()), new_idx); if (!plan) return; canvas->m_canvas->m_current_layer_idx = plan.value().index; if (plan.value().reloads_animation_layers) animation->load_layers(); }; layers->on_layer_order = [this](Node*, int old_idx, int new_idx) { const auto plan = pp::app::plan_document_layer_reorder( static_cast(canvas->m_canvas->m_layers.size()), old_idx, new_idx); if (!plan || !plan.value().mutates_document) return; canvas->m_canvas->layer_order(plan.value().from_index, plan.value().to_index); canvas->m_canvas->m_unsaved = plan.value().marks_unsaved; if (plan.value().reloads_animation_layers) animation->load_layers(); if (plan.value().updates_title) title_update(); }; layers->on_layer_delete = [this](Node*, int idx) { const auto plan = pp::app::plan_document_layer_remove( static_cast(canvas->m_canvas->m_layers.size()), idx); if (!plan) return; canvas->m_canvas->layer_remove(plan.value().index); canvas->m_canvas->m_unsaved = plan.value().marks_unsaved; if (plan.value().reloads_animation_layers) animation->load_layers(); if (plan.value().updates_title) title_update(); }; layers->on_layer_opacity_changed = [this](Node*, int idx, float value) { const auto plan = pp::app::plan_document_layer_opacity( static_cast(canvas->m_canvas->m_layers.size()), idx, value); if (!plan) return; canvas->m_canvas->m_layers[plan.value().index]->m_opacity = plan.value().opacity; canvas->m_canvas->m_unsaved = plan.value().marks_unsaved; if (plan.value().updates_title) title_update(); }; layers->on_layer_visibility_changed = [this](Node*, int idx, bool visible) { const auto plan = pp::app::plan_document_layer_visibility( static_cast(canvas->m_canvas->m_layers.size()), idx, visible); if (!plan) return; canvas->m_canvas->m_layers[plan.value().index]->m_visible = plan.value().flag; canvas->m_canvas->m_unsaved = plan.value().marks_unsaved; if (plan.value().reloads_animation_layers) animation->load_layers(); if (plan.value().updates_title) title_update(); }; layers->on_layer_alpha_lock_changed = [this](Node*, int idx, bool locked) { const auto plan = pp::app::plan_document_layer_alpha_lock( static_cast(canvas->m_canvas->m_layers.size()), idx, locked); if (!plan) return; canvas->m_canvas->m_layers[plan.value().index]->m_alpha_locked = plan.value().flag; canvas->m_canvas->m_unsaved = plan.value().marks_unsaved; if (plan.value().updates_title) title_update(); }; layers->on_layer_blend_mode_changed = [this](Node*, int idx, int mode) { const auto plan = pp::app::plan_document_layer_blend_mode( static_cast(canvas->m_canvas->m_layers.size()), idx, mode); if (!plan) return; canvas->m_canvas->m_layers[plan.value().index]->m_blend_mode = plan.value().blend_mode; canvas->m_canvas->m_unsaved = plan.value().marks_unsaved; if (plan.value().updates_title) title_update(); }; layers->on_layer_highlight_changed = [this](Node*, int idx, bool highlight) { const auto plan = pp::app::plan_document_layer_highlight( static_cast(canvas->m_canvas->m_layers.size()), idx, highlight); if (!plan) return; canvas->m_canvas->m_layers[plan.value().index]->m_hightlight = plan.value().flag; }; if (auto* button = layout[main_id]->find("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(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(); 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("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("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("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("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(); 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("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(layers->m_parent->m_parent)) { layers->remove_from_parent(); fp->destroy(); } } layout[main_id]->add_child(layers); auto tick = layout[main_id]->add_child(); 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("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("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(grid->m_parent->m_parent)) { grid->remove_from_parent(); fp->destroy(); } } layout[main_id]->add_child(grid); auto tick = layout[main_id]->add_child(); 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("scroller"); scroll->SetMaxHeight(glm::max(100.f, screen.y - pos.y - 250.f)); grid->on_popup_close = [this, tick](Node*) { tick->destroy(); }; }; } } template void select_button(Node* main, T* button) { main->find("btn-pen")->set_active(false); main->find("btn-erase")->set_active(false); main->find("btn-line")->set_active(false); main->find("btn-cam")->set_active(false); main->find("btn-grid")->set_active(false); main->find("btn-copy")->set_active(false); main->find("btn-cut")->set_active(false); //main->find("btn-fill")->set_color(color_button_normal); main->find("btn-mask-free")->set_active(false); main->find("btn-mask-line")->set_active(false); main->find("btn-bucket")->set_active(false); button->set_active(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; } template void apply_canvas_tool_select(App& app, T* button, pp::app::CanvasToolMode mode) { const auto plan = pp::app::plan_canvas_tool_select(mode); if (plan.selects_toolbar_button) select_button(app.layout[app.main_id], button); if (plan.transform_action == pp::app::CanvasToolTransformAction::copy) { auto* transform = static_cast( app.canvas->m_canvas->modes[(int)kCanvasMode::Copy][0]); transform->m_action = CanvasModeTransform::ActionType::Copy; } else if (plan.transform_action == pp::app::CanvasToolTransformAction::cut) { auto* transform = static_cast( app.canvas->m_canvas->modes[(int)kCanvasMode::Cut][0]); transform->m_action = CanvasModeTransform::ActionType::Cut; } if (plan.updates_canvas_mode) Canvas::set_mode(canvas_mode_from_tool(plan.mode)); } void App::init_toolbar_draw() { if (auto* button = layout[main_id]->find("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); if (plan.updates_canvas_mode) Canvas::set_mode(canvas_mode_from_tool(plan.mode)); } if (auto* button = layout[main_id]->find("btn-pick")) { button->on_click = [this](Node*) { CanvasModePen* mode = (CanvasModePen*)canvas->m_canvas->modes[(int)kCanvasMode::Draw][0]; const auto plan = pp::app::plan_canvas_tool_pick_toggle( canvas->m_canvas->m_current_mode == kCanvasMode::Draw); if (mode && plan.toggles_picking) { mode->m_picking = !mode->m_picking; } }; } if (auto* button = layout[main_id]->find("btn-touchlock")) { button->on_click = [this](Node*) { const auto plan = pp::app::plan_canvas_tool_touch_lock_toggle(); if (plan.toggles_touch_lock) canvas->m_canvas->m_touch_lock = !canvas->m_canvas->m_touch_lock; }; } if (auto* button = layout[main_id]->find("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("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("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("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("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("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("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("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("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("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("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(); 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("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("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("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("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("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("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("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("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("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(); 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("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("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("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("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("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("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("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("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("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("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("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("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(); 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("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(); 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("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(); 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(c)) { if (fp->m_container->is_child(panel)) return true; } } return false; }; popup_time->find("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(); fpanel->m_class = NodePanelFloating::kClass::Presets; apply_tools_panel_chrome(*fpanel, plan); if (!floating_presets) { floating_presets = fpanel->m_container->add_child_ref(); floating_presets->SetHeightP(100); //floating_presets->SetFlexGrow(1); //floating_presets->find("toolbar")->destroy(); floating_presets->on_brush_changed = [this](Node* target, std::shared_ptr& b) { apply_brush_preset_plan(*this, b); }; } else { fpanel->m_container->add_child(floating_presets); } popup_exp->destroy(); popup_time->destroy(); }; popup_time->find("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(); fpanel->m_class = NodePanelFloating::kClass::Color; apply_tools_panel_chrome(*fpanel, plan); if (!floating_color) { floating_color = fpanel->m_container->add_child_ref(); 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("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(); fpanel->m_class = NodePanelFloating::kClass::ColorAdv; apply_tools_panel_chrome(*fpanel, plan); if (!floating_picker) { floating_picker = fpanel->m_container->add_child_ref(); //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("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(); 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("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(); 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("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(); 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("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(); 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("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(); 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("tools-ui-scale")) { std::vector 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(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("tools-vp-scale")) { std::vector 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(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("tools-rtl")) { NodeCheckBox* cb = rtl_btn->find("tools-rtl-check"); cb->set_value(ui_rtl, false); rtl_btn->on_click = [this, rtl_btn](Node* b) { NodeCheckBox* cb = rtl_btn->find("tools-rtl-check"); cb->set_value(!cb->checked, true); }; rtl_btn->find("tools-rtl-check")->on_value_changed = [this, main](Node*, bool checked) { set_ui_rtl(checked); }; } if (auto vr_btn = popup_time->find("tools-vr")) { NodeCheckBox* cb = vr_btn->find("tools-vr-check"); cb->set_value(has_vr); vr_btn->on_click = [this, vr_btn](Node* b) { NodeCheckBox* cb = vr_btn->find("tools-vr-check"); cb->set_value(!cb->checked, true); }; vr_btn->find("tools-vr-check")->on_value_changed = [this, main](Node* target, bool checked) { if (checked) { if (!vr_start()) { auto cb = static_cast(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("tools-vr-controllers")) { NodeCheckBox* cb = vr_btn->find("tools-vr-controllers-check"); cb->set_value(vr_controllers_enabled); vr_btn->on_click = [this, vr_btn](Node* b) { NodeCheckBox* cb = vr_btn->find("tools-vr-controllers-check"); cb->set_value(!cb->checked, true); }; vr_btn->find("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("tools-timelapse")) { NodeCheckBox* cb = btn->find("tools-timelapse-check"); cb->set_value(Settings::value_or("auto-timelapse", true), false); btn->on_click = [this, btn](Node* b) { NodeCheckBox* cb = btn->find("tools-timelapse-check"); cb->set_value(!cb->checked, true); }; btn->find("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("tools-show-cursor")) { mode->set_index(Settings::value_or("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("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("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("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("mp4test")->on_click = [this, popup_exp](Node*) { dialog_export_mp4(); popup_exp->mouse_release(); popup_exp->destroy(); }; */ #if __IOS__ popup_exp->find("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("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(); 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("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("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("about-news")) { if (auto text = item->find("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("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("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("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(); 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("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("layer-clear")-> find("menu-label")-> set_text(plan.label.c_str()); } popup->find("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("layer-rename")-> find("menu-label")-> set_text(plan.label.c_str()); } popup->find("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("layer-merge")-> find("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> 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("version")) { version_label->set_text(g_version); } if (auto x = layout[main_id]->find("ext-fbf")) { x->m_color = ShaderManager::ext_framebuffer_fetch ? glm::vec4(0, 1, 0, 1) : glm::vec4(1, 0, 0, 1); } if (auto x = layout[main_id]->find("ext-flt")) { if (ShaderManager::ext_float32 || ShaderManager::ext_float16) { if (auto t = x->find("ext-flt-text")) { if (ShaderManager::ext_float32_linear) t->set_text("F32L"); else if (ShaderManager::ext_float32) t->set_text("F32"); else if (ShaderManager::ext_float16) t->set_text("F16"); } 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("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(); //p->SetPosition(300, 300); //p->SetSize(600, 400); //p->m_container->add_child(); }; //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(c)) { auto fd = list_floatings.add(); 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(c)) { auto fd = list_drop_left.add(); 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(c)) { auto fd = list_drop_right.add(); 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("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("ui"); for (auto const& l : d->get("floatings")->items) { auto ld = std::static_pointer_cast(l); auto pos = ld->value("pos"); auto size = ld->value("size"); auto cls = static_cast(ld->value("class")); auto f = floatings->add_child(); std::string title = "Floating Panel"; ld->value("title", title); f->m_title->set_text(title.c_str()); switch (cls) { case NodePanelFloating::kClass::Presets: { floating_presets = f->m_container->add_child_ref(); floating_presets->SetHeightP(100); //floating_presets->find("toolbar")->destroy(); floating_presets->on_brush_changed = [this](Node* target, std::shared_ptr& b) { apply_brush_preset_plan(*this, b); }; break; } case NodePanelFloating::kClass::Color: { floating_color = f->m_container->add_child_ref(); 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(); 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(); break; } f->m_class = cls; f->SetSize(size); f->SetPosition(pos); f->SetPositioning(YGPositionTypeAbsolute); } for (auto const& l : d->get("drop-left")->items) { auto ld = std::static_pointer_cast(l); auto size = ld->value("size"); auto cls = static_cast(ld->value("class")); auto f = drop_left->add_child(); std::string title = "Floating Panel"; ld->value("title", title); f->m_title->set_text(title.c_str()); switch (cls) { case NodePanelFloating::kClass::Presets: { auto floating_presets = f->m_container->add_child(); floating_presets->SetHeightP(100); //floating_presets->find("toolbar")->destroy(); floating_presets->on_brush_changed = [this](Node* target, std::shared_ptr& b) { apply_brush_preset_plan(*this, b); }; break; } case NodePanelFloating::kClass::Color: { auto floating_color = f->m_container->add_child(); 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(); 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(); 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("drop-right")->items) { auto ld = std::static_pointer_cast(l); auto size = ld->value("size"); auto cls = static_cast(ld->value("class")); auto f = drop_right->add_child(); std::string title = "Floating Panel"; ld->value("title", title); f->m_title->set_text(title.c_str()); switch (cls) { case NodePanelFloating::kClass::Presets: { auto floating_presets = f->m_container->add_child(); floating_presets->SetHeightP(100); //floating_presets->find("toolbar")->destroy(); floating_presets->on_brush_changed = [this](Node* target, std::shared_ptr& b) { apply_brush_preset_plan(*this, b); }; break; } case NodePanelFloating::kClass::Color: { auto floating_color = f->m_container->add_child(); 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(); 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(); break; } f->m_class = cls; f->m_dock = drop_right; f->SetPositioning(YGPositionTypeRelative); f->SetPosition(0, 0); f->SetSize(size); } }