#include "pch.h" #include "app.h" #include "node_panel_grid.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/file_menu.h" #include "app_core/app_status.h" #include "app_core/main_toolbar.h" #include "app_core/tools_menu.h" #include "legacy_app_preference_services.h" #include "legacy_app_shell_services.h" #include "legacy_brush_ui_services.h" #include "legacy_canvas_tool_services.h" #include "legacy_document_layer_services.h" #include "legacy_history_services.h" #include "legacy_ui_overlay_services.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) { return pp::panopainter::apply_legacy_brush_color_plan(app, color, update_quick, update_color_panel); } bool apply_brush_texture_plan(App& app, pp::app::BrushUiTextureSlot slot, const std::string& path, const std::string& thumb) { return pp::panopainter::apply_legacy_brush_texture_plan(app, slot, path, thumb); } bool apply_brush_preset_plan(App& app, const std::shared_ptr& brush) { return pp::panopainter::apply_legacy_brush_preset_plan(app, brush); } bool apply_document_export_menu_plan(App& app, pp::app::DocumentExportMenuKind kind) { return pp::panopainter::apply_legacy_document_export_menu_plan(app, kind); } void apply_file_menu_plan(App& app, pp::app::FileMenuCommand command) { pp::panopainter::apply_legacy_file_menu_command(app, command); } std::shared_ptr add_menu_popup( App& app, const char* template_id, glm::vec2 position, float rtl_anchor_width) { const auto popup = pp::panopainter::add_legacy_popup_menu( app, template_id, position.x, position.y, rtl_anchor_width); if (!popup) { LOG("Popup menu '%s' failed: %s", template_id ? template_id : "", popup.status().message); return nullptr; } return popup.value(); } 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; } void execute_main_toolbar_plan(App& app, const pp::app::MainToolbarPlan& plan) { pp::panopainter::execute_legacy_main_toolbar_plan(app, plan); } void execute_about_menu_plan(App& app, const pp::app::AboutMenuPlan& plan) { pp::panopainter::execute_legacy_about_menu_plan(app, plan); } void execute_tools_menu_plan(App& app, const pp::app::ToolsMenuPlan& plan) { pp::panopainter::execute_legacy_tools_menu_plan(app, plan); } void execute_document_layer_menu_plan(App& app, const pp::app::DocumentLayerMenuPlan& plan) { const auto status = pp::panopainter::execute_legacy_document_layer_menu_plan(app, plan); if (!status.ok()) LOG("Layer menu action failed: %s", status.message); } void execute_document_layer_merge_plan(App& app, const pp::app::DocumentLayerMergePlan& plan) { const auto status = pp::panopainter::execute_legacy_document_layer_merge_plan(app, plan); if (!status.ok()) LOG("Layer merge failed: %s", status.message); } void execute_document_layer_operation_plan( App& app, const pp::app::DocumentLayerOperationPlan& plan, const std::shared_ptr& pending_layer = nullptr) { const auto status = pp::panopainter::execute_legacy_document_layer_operation_plan(app, plan, pending_layer); if (!status.ok()) LOG("Layer operation 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 history = pp::panopainter::legacy_history_snapshot(); const auto plan = pp::app::plan_main_toolbar_command( pp::app::MainToolbarCommand::undo, history.undo_count); 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 history = pp::panopainter::legacy_history_snapshot(); const auto plan = pp::app::plan_main_toolbar_command( pp::app::MainToolbarCommand::redo, 0, history.redo_count); 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 history = pp::panopainter::legacy_history_snapshot(); const auto plan = pp::app::plan_main_toolbar_command( pp::app::MainToolbarCommand::clear_history, history.undo_count, history.redo_count, history.memory_bytes); 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(*this, 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(*this, 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(*this, pp::app::BrushUiTextureSlot::dual, path, thumb); }; stroke->on_stroke_change = [this](Node*) { const auto status = pp::panopainter::execute_legacy_brush_stroke_changed_plan(*this); 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 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; 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(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(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(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(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(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(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(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(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(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("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(); }; }; } } [[nodiscard]] bool current_canvas_mode_is_draw(App& app) noexcept { return app.canvas && app.canvas->m_canvas && app.canvas->m_canvas->m_current_mode == kCanvasMode::Draw; } template void execute_canvas_tool_toolbar_binding( App& app, const pp::app::CanvasToolToolbarBinding& binding, T* button) { const auto plan = pp::app::plan_canvas_tool_toolbar_binding_action( binding, current_canvas_mode_is_draw(app)); const auto status = binding.action == pp::app::CanvasToolToolbarAction::select_mode ? pp::panopainter::execute_legacy_canvas_tool_plan(app, plan, button) : pp::panopainter::execute_legacy_canvas_tool_plan(app, plan); if (!status.ok()) LOG("Canvas toolbar action failed: %s", status.message); } template void bind_canvas_tool_toolbar_button( App& app, const pp::app::CanvasToolToolbarBinding& binding, T* button) { button->on_click = [&app, binding, button](Node*) { execute_canvas_tool_toolbar_binding(app, binding, button); }; } void App::init_toolbar_draw() { const auto toolbar = pp::app::plan_canvas_tool_toolbar(); bool apply_default_tool = false; for (const auto& binding : toolbar.bindings) { if (binding.custom_button) { if (auto* button = layout[main_id]->find(binding.button_id.data())) { bind_canvas_tool_toolbar_button(*this, binding, button); apply_default_tool = apply_default_tool || binding.applies_default_on_init; } } else { if (auto* button = layout[main_id]->find(binding.button_id.data())) { bind_canvas_tool_toolbar_button(*this, binding, button); apply_default_tool = apply_default_tool || binding.applies_default_on_init; } } } if (apply_default_tool) { const auto default_plan = pp::app::plan_canvas_tool_select(toolbar.default_mode); const auto status = pp::panopainter::execute_legacy_canvas_tool_plan(*this, default_plan); if (!status.ok()) LOG("Canvas default tool action failed: %s", status.message); } } 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 = add_menu_popup(*this, "file-menu", pos, menu_file->m_size.x); if (!popup) return; 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 = add_menu_popup(*this, "file-submenu-export", pos, b->m_size.x); if (!subpopup) return; 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 = add_menu_popup(*this, "edit-menu", pos, menu_file->m_size.x); if (!popup) return; }; } } 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 = add_menu_popup(*this, "tools-menu", pos, menu_exp->m_size.x); if (!popup_exp) return; 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 = add_menu_popup(*this, "panels-menu", pos, b->m_size.x); if (!popup_time) return; 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 = add_menu_popup(*this, "options-menu", pos, b->m_size.x); if (!popup_time) return; 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) { const auto status = pp::panopainter::execute_legacy_ui_scale_preference( *App::I, ui_scale->get_float(index)); if (!status.ok()) LOG("UI scale preference failed: %s", status.message); }; } 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 status = pp::panopainter::execute_legacy_viewport_scale_preference( *App::I, vp_scale->get_float(index)); if (!status.ok()) LOG("Viewport scale preference failed: %s", status.message); }; } 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) { const auto status = pp::panopainter::execute_legacy_interface_direction_preference( *this, checked); if (!status.ok()) LOG("Interface direction preference failed: %s", status.message); }; } 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) { const auto status = pp::panopainter::execute_legacy_vr_mode_preference( *this, checked); if (!status.ok()) { auto cb = static_cast(target); cb->set_value(false); message_box("VR Failed", "Couldn't start Virtual Reality mode"); } }; } 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 status = pp::panopainter::execute_legacy_vr_controllers_preference( *this, checked); if (!status.ok()) LOG("VR controllers preference failed: %s", status.message); }; } 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 status = pp::panopainter::execute_legacy_timelapse_preference( *this, checked); if (!status.ok()) LOG("Timelapse preference failed: %s", status.message); }; } 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 status = pp::panopainter::execute_legacy_canvas_cursor_mode_preference( *App::I, index); if (!status.ok()) LOG("Cursor mode preference failed: %s", status.message); }; } }; 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 (platform_supports_sonarpen()) { popup_exp->find("sonarpen")->on_click = [this, popup_exp](Node*) { const auto plan = pp::app::plan_tools_menu_command( pp::app::ToolsMenuCommand::sonarpen, platform_supports_sonarpen()); execute_tools_menu_plan(*this, plan); if (plan.closes_root_popup) { popup_exp->mouse_release(); popup_exp->destroy(); } }; } }; } } 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 = add_menu_popup(*this, "about-menu", pos, menu_file->m_size.x); if (!popup) return; 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] { pp::app::BrushUiRefreshInput input; input.update_color = update_color; input.update_brush = update_brush; auto current_brush = Canvas::I ? Canvas::I->m_current_brush : nullptr; input.has_current_brush = current_brush != nullptr; input.has_floating_picker = floating_picker != nullptr; input.has_floating_color_panel = floating_color != nullptr; if (input.has_current_brush) { input.tip_flow = current_brush->m_tip_flow; input.tip_size = current_brush->m_tip_size; input.r = current_brush->m_tip_color.r; input.g = current_brush->m_tip_color.g; input.b = current_brush->m_tip_color.b; input.a = current_brush->m_tip_color.a; } const auto view = pp::app::plan_brush_ui_refresh(input); if (!view) { LOG("Brush UI refresh failed: %s", view.status().message); return; } if (view.value().updates_stroke_controls) { stroke->update_controls(); } if (view.value().updates_quick_flow) { quick->m_slider_flow->set_value(stroke->m_tip_flow->get_value()); } if (view.value().updates_quick_size) { quick->m_slider_size->set_value(stroke->m_tip_size->get_value()); } if (view.value().updates_quick_brush_preview && current_brush) { *quick->m_button_brush_current_preview->m_brush = *current_brush; quick->m_button_brush_current_preview->draw_stroke(); } if (view.value().updates_quick_color) { const glm::vec4 color(view.value().r, view.value().g, view.value().b, view.value().a); quick->m_button_color_current_inner->m_color = color; if (view.value().updates_floating_picker) floating_picker->set_color(color); if (view.value().updates_floating_color_panel) floating_color->set_color(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 = add_menu_popup(*this, "layers-menu", pos, menu_file->m_size.x); if (!popup) return; 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); } 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 = renderer_features.float32_linear_filtering, .float16_render_targets = renderer_features.float16_render_targets, }); if (auto x = layout[main_id]->find("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("ext-flt")) { if (renderer_diagnostics.floating_point_targets.supported) { if (auto t = x->find("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("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)); save_platform_ui_state(); 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); } }