Warning, file /graphics/glaxnimate/src/gui/tools/edit_tool.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best> 0003 * 0004 * SPDX-License-Identifier: GPL-3.0-or-later 0005 */ 0006 0007 #include "edit_tool.hpp" 0008 0009 #include <algorithm> 0010 0011 #include <QMenu> 0012 #include <QPointer> 0013 #include <QActionGroup> 0014 0015 #include <QtColorWidgets/ColorDialog> 0016 0017 #include <KLocalizedString> 0018 0019 #include "app/application.hpp" 0020 0021 #include "math/bezier/operations.hpp" 0022 #include "math/bezier/cubic_struts.hpp" 0023 #include "model/shapes/shape.hpp" 0024 #include "model/shapes/text.hpp" 0025 #include "model/shapes/rect.hpp" 0026 #include "model/shapes/ellipse.hpp" 0027 #include "model/shapes/polystar.hpp" 0028 #include "model/shapes/precomp_layer.hpp" 0029 #include "model/shapes/image.hpp" 0030 #include "command/animation_commands.hpp" 0031 #include "command/object_list_commands.hpp" 0032 #include "command/undo_macro_guard.hpp" 0033 0034 #include "graphics/bezier_item.hpp" 0035 #include "graphics/item_data.hpp" 0036 #include "graphics/graphics_editor.hpp" 0037 #include "graphics/gradient_editor.hpp" 0038 #include "handle_menu.hpp" 0039 0040 using namespace glaxnimate::gui; 0041 using namespace glaxnimate; 0042 0043 tools::Autoreg<tools::EditTool> tools::EditTool::autoreg{max_priority + 1}; 0044 0045 class tools::EditTool::Private 0046 { 0047 public: 0048 enum DragMode 0049 { 0050 None, 0051 Click, 0052 RubberBand, 0053 ForwardEvents, 0054 VertexClick, 0055 VertexDrag, 0056 VertexAdd, 0057 MoldCurve 0058 }; 0059 0060 struct Selection 0061 { 0062 struct SelectedBezier 0063 { 0064 graphics::BezierItem* item; 0065 QTransform transform; 0066 QPointF start_point; 0067 0068 SelectedBezier(graphics::BezierItem* item) 0069 : item(item) 0070 {} 0071 0072 void start_drag(const QPointF& drag_start_point) 0073 { 0074 transform = item->target_object()->docnode_fuzzy_parent()->transform_matrix(item->target_object()->time()).inverted(); 0075 start_point = transform.map(drag_start_point); 0076 } 0077 0078 void drag( 0079 const QPointF& scene_pos, 0080 std::vector<model::AnimatableBase*>& props, 0081 QVariantList& before, 0082 QVariantList& after 0083 ) 0084 { 0085 props.push_back(item->target_property()); 0086 0087 math::bezier::Bezier bezier = item->bezier(); 0088 before.push_back(QVariant::fromValue(bezier)); 0089 0090 QPointF pos = transform.map(scene_pos); 0091 QPointF delta = pos - start_point; 0092 for ( int i : item->selected_indices() ) 0093 bezier[i].translate(delta); 0094 after.push_back(QVariant::fromValue(bezier)); 0095 start_point = pos; 0096 } 0097 }; 0098 0099 std::map<graphics::BezierItem*, SelectedBezier> selected; 0100 QPointer<graphics::PointItem> initial = nullptr; 0101 0102 QPointF drag_start; 0103 0104 bool empty() const 0105 { 0106 return selected.empty(); 0107 } 0108 0109 void clear() 0110 { 0111 initial = nullptr; 0112 for ( const auto& p : selected ) 0113 p.second.item->clear_selected_indices(); 0114 selected.clear(); 0115 } 0116 0117 void add_bezier_point_item(graphics::PointItem* item) 0118 { 0119 auto grandpa = item->parent_editor(); 0120 if ( selected.find(grandpa) == selected.end() ) 0121 add_bezier_item(grandpa); 0122 grandpa->select_index(item->index()); 0123 } 0124 0125 void toggle_handle(graphics::MoveHandle* item) 0126 { 0127 auto parent = static_cast<graphics::PointItem*>(item->parentItem()); 0128 auto grandpa = parent->parent_editor(); 0129 auto it = selected.find(grandpa); 0130 if ( it == selected.end() ) 0131 { 0132 add_bezier_item(grandpa); 0133 grandpa->select_index(parent->index()); 0134 } 0135 else 0136 { 0137 grandpa->toggle_index(parent->index()); 0138 if ( grandpa->selected_indices().empty() ) 0139 selected.erase(it); 0140 } 0141 } 0142 0143 void add_bezier_item(graphics::BezierItem* item) 0144 { 0145 selected.emplace(item, SelectedBezier(item)); 0146 QObject::connect(item, &QObject::destroyed, item, [this, item]{ 0147 selected.erase(item); 0148 }); 0149 } 0150 0151 void start_drag() 0152 { 0153 for ( auto& p : selected ) 0154 p.second.start_drag(drag_start); 0155 } 0156 0157 void drag(const MouseEvent& event, bool commit) 0158 { 0159 std::vector<model::AnimatableBase*> props; 0160 QVariantList before; 0161 QVariantList after; 0162 0163 for ( auto& p : selected ) 0164 { 0165 p.second.drag(event.scene_pos, props, before, after); 0166 } 0167 0168 event.window->document()->push_command(new command::SetMultipleAnimated( 0169 i18n("Drag nodes"), 0170 props, 0171 before, 0172 after, 0173 commit 0174 )); 0175 } 0176 }; 0177 0178 struct PathCache 0179 { 0180 QTransform forward_transform; 0181 QTransform inverse_transform; 0182 0183 PathCache() = default; 0184 0185 PathCache(graphics::BezierItem* item) 0186 : forward_transform(item->target_object()->transform_matrix(item->target_object()->time())), 0187 inverse_transform(forward_transform.inverted()) 0188 {} 0189 }; 0190 0191 void add_bezier_editor(graphics::BezierItem* editor) 0192 { 0193 active[editor] = PathCache(editor); 0194 0195 connect(editor, &QObject::destroyed, editor, [this, editor]{ 0196 active.erase(editor); 0197 }); 0198 connect(editor->target_object(), &model::VisualNode::transform_matrix_changed, editor, [this, editor]{ 0199 active[editor] = PathCache(editor); 0200 }); 0201 } 0202 0203 void extract_editor(graphics::DocumentScene * scene, model::VisualNode* node) 0204 { 0205 auto editor_parent = scene->get_editor(node); 0206 if ( !editor_parent ) 0207 return; 0208 0209 for ( auto editor_child : editor_parent->childItems() ) 0210 { 0211 if ( auto editor = qgraphicsitem_cast<graphics::BezierItem*>(editor_child) ) 0212 add_bezier_editor(editor); 0213 } 0214 } 0215 0216 void show_position_editor(graphics::DocumentScene * scene, model::VisualNode* node, model::Transform* transform) 0217 { 0218 if ( transform->position.keyframe_count() >= 2 ) 0219 { 0220 auto editor = std::make_unique<graphics::BezierItem>(&transform->position, node); 0221 auto bezit = editor.get(); 0222 0223 QObject::connect( 0224 node, &model::VisualNode::transform_matrix_changed, 0225 bezit, [bezit, node](const QTransform& t){ 0226 bezit->setTransform(node->local_transform_matrix(node->time()).inverted() * t); 0227 } 0228 ); 0229 bezit->setTransform(node->local_transform_matrix(node->time()).inverted() * node->transform_matrix(node->time())); 0230 0231 scene->show_custom_editor(node, std::move(editor)); 0232 0233 add_bezier_editor(bezit); 0234 } 0235 } 0236 0237 void impl_extract_selection_recursive_item(graphics::DocumentScene * scene, model::VisualNode* node) 0238 { 0239 auto meta = node->metaObject(); 0240 if ( meta->inherits(&model::Shape::staticMetaObject) || meta->inherits(&model::ShapeOperator::staticMetaObject) || meta->inherits(&model::TextShape::staticMetaObject) ) 0241 { 0242 scene->show_editors(node); 0243 0244 if ( 0245 meta->inherits(&model::Path::staticMetaObject) || 0246 meta->inherits(&model::Rect::staticMetaObject) || 0247 meta->inherits(&model::Ellipse::staticMetaObject) || 0248 meta->inherits(&model::PolyStar::staticMetaObject) 0249 ) 0250 extract_editor(scene, node); 0251 } 0252 else if ( meta->inherits(&model::Group::staticMetaObject) ) 0253 { 0254 show_position_editor(scene, node, static_cast<model::Group*>(node)->transform.get()); 0255 for ( const auto& sub : static_cast<model::Group*>(node)->shapes ) 0256 impl_extract_selection_recursive_item(scene, sub.get()); 0257 } 0258 else if ( meta->inherits(&model::PreCompLayer::staticMetaObject) ) 0259 { 0260 show_position_editor(scene, node, static_cast<model::PreCompLayer*>(node)->transform.get()); 0261 } 0262 else if ( meta->inherits(&model::Image::staticMetaObject) ) 0263 { 0264 show_position_editor(scene, node, static_cast<model::Image*>(node)->transform.get()); 0265 } 0266 } 0267 0268 static void node_type_action(QMenu* menu, QActionGroup* group, graphics::PointItem* item, math::bezier::PointType type) 0269 { 0270 QIcon icon; 0271 QString label; 0272 switch ( type ) 0273 { 0274 case math::bezier::PointType::Corner: 0275 icon = QIcon::fromTheme("node-type-cusp"); 0276 label = i18n("Cusp"); 0277 break; 0278 case math::bezier::PointType::Smooth: 0279 icon = QIcon::fromTheme("node-type-smooth"); 0280 label = i18n("Smooth"); 0281 break; 0282 case math::bezier::PointType::Symmetrical: 0283 icon = QIcon::fromTheme("node-type-auto-smooth"); 0284 label = i18n("Symmetrical"); 0285 break; 0286 } 0287 QAction* action = menu->addAction(icon, label, item, [item, type, label]{ 0288 auto point = item->point(); 0289 point.type = type; 0290 point.adjust_handles_from_type(); 0291 item->modify(point, i18n("Set %1 Node", label)); 0292 }); 0293 0294 action->setCheckable(true); 0295 action->setActionGroup(group); 0296 0297 if ( item->point().type == type ) 0298 action->setChecked(true); 0299 } 0300 0301 static void vertex_menu(QMenu& menu, graphics::PointItem* item, int role, graphics::MoveHandle* handle) 0302 { 0303 auto grp = new QActionGroup(&menu); 0304 0305 menu.addSection(i18n("Node")); 0306 0307 node_type_action(&menu, grp, item, math::bezier::PointType::Corner); 0308 node_type_action(&menu, grp, item, math::bezier::PointType::Smooth); 0309 node_type_action(&menu, grp, item, math::bezier::PointType::Symmetrical); 0310 0311 menu.addSeparator(); 0312 0313 if ( role == graphics::MoveHandle::Vertex ) 0314 { 0315 menu.addAction(QIcon::fromTheme("format-remove-node"), i18n("Remove Node"), item, [item]{ 0316 item->parent_editor()->remove_point(item->index()); 0317 }); 0318 0319 menu.addAction(QIcon::fromTheme("show-node-handles"), i18n("Show Tangents"), item, [item]{ 0320 item->show_tan_in(true); 0321 item->show_tan_out(true); 0322 }); 0323 0324 if ( item->index() > 0 && item->parent_editor()->bezier().closed() ) 0325 { 0326 menu.addAction(QIcon::fromTheme("edit-node"), i18n("Make First Node"), item, [item]{ 0327 item->parent_editor()->make_first(item->index()); 0328 }); 0329 } 0330 0331 } 0332 else 0333 { 0334 menu.addAction(QIcon::fromTheme("show-node-handles"), i18n("Remove Tangent"), item, [item, handle]{ 0335 item->remove_tangent(handle); 0336 }); 0337 } 0338 } 0339 0340 static void gradient_menu(QMenu& menu, graphics::MoveHandle* handle, QWidget* dialog_parent) 0341 { 0342 auto item = static_cast<graphics::GradientEditor*>(handle->parentItem()); 0343 int index = handle->data(graphics::GradientStopIndex).toInt(); 0344 0345 if ( !item->styler() || !item->gradient() ) 0346 return; 0347 0348 auto gradient = item->gradient(); 0349 auto gradient_colors = item->gradient()->colors.get(); 0350 auto doc = gradient->document(); 0351 0352 QMenu* use_menu = new QMenu(i18n("Gradient Colors"), &menu); 0353 use_menu->setIcon(QIcon::fromTheme("color-gradient")); 0354 0355 for ( const auto& colors : doc->assets()->gradient_colors->values ) 0356 { 0357 use_menu->addAction( 0358 colors->instance_icon(), 0359 colors->name.get() 0360 )->setData(QVariant::fromValue(colors.get())); 0361 } 0362 0363 connect(use_menu, &QMenu::triggered, gradient, [gradient](QAction* act){ 0364 gradient->colors.set_undoable(act->data()); 0365 }); 0366 0367 menu.addAction(use_menu->menuAction()); 0368 0369 menu.addAction(QIcon::fromTheme("color-management"), i18nc("@action:inmenu", "Stop Color..."), gradient_colors, [gradient_colors, index, dialog_parent]{ 0370 color_widgets::ColorDialog dialog(dialog_parent); 0371 auto colors = gradient_colors->colors.get(); 0372 dialog.setColor(colors[index].second); 0373 if ( dialog.exec() != QDialog::Accepted ) 0374 return; 0375 0376 colors[index].second = dialog.color(); 0377 gradient_colors->colors.set_undoable(QVariant::fromValue(colors)); 0378 }); 0379 0380 0381 menu.addAction(QIcon::fromTheme("list-add"), i18nc("@action:inmenu", "Add Stop"), gradient_colors, [gradient_colors, index]{ 0382 gradient_colors->split_segment(index); 0383 }); 0384 0385 menu.addAction(QIcon::fromTheme("list-remove"), i18nc("@action:inmenu", "Remove Stop"), gradient_colors, [gradient_colors, index]{ 0386 gradient_colors->remove_stop(index); 0387 }); 0388 0389 } 0390 0391 static void context_menu(EditTool* thus, const MouseEvent& event) 0392 { 0393 #ifndef Q_OS_ANDROID 0394 auto handle = thus->under_mouse(event, true, SelectionMode::Shape).handle; 0395 if ( !handle ) 0396 return; 0397 0398 QMenu menu; 0399 0400 auto role = handle->role(); 0401 if ( role == graphics::MoveHandle::Vertex || role == graphics::MoveHandle::Tangent ) 0402 vertex_menu(menu, static_cast<graphics::PointItem*>(handle->parentItem()), role, handle); 0403 else if ( role == graphics::MoveHandle::GradientStop ) 0404 gradient_menu(menu, handle, event.window->as_widget()); 0405 0406 add_property_menu_actions(&menu, handle, event.window); 0407 0408 if ( !menu.actions().empty() ) 0409 menu.exec(QCursor::pos()); 0410 #else 0411 Q_UNUSED(thus); 0412 Q_UNUSED(event); 0413 #endif 0414 } 0415 0416 void mold_bezier(const QPointF& scene_pos, bool commit) 0417 { 0418 using namespace glaxnimate::math::bezier; 0419 if ( !insert_item ) 0420 return; 0421 0422 // see https://pomax.github.io/bezierinfo/#molding 0423 QPointF B = active[insert_item].inverse_transform.map(scene_pos); 0424 BezierStruts struts_ideal = cubic_struts_idealized(mold_original, B); 0425 auto struts_proj = cubic_struts_projection(mold_original, B, insert_params); 0426 qreal falloff = 512; 0427 qreal dist = math::length(insert_params.point - B); 0428 qreal interp = math::min(falloff, dist) / falloff; 0429 0430 BezierStruts struts_interp = { 0431 B, 0432 math::lerp(struts_proj.t, struts_ideal.t, interp), 0433 math::lerp(struts_proj.e1, struts_ideal.e1, interp), 0434 math::lerp(struts_proj.e2, struts_ideal.e2, interp), 0435 }; 0436 0437 // adjust interpolated struts so the pass through B 0438 QPointF offset = B - math::lerp(struts_interp.e1, struts_interp.e2, struts_interp.t); 0439 struts_interp.e1 += offset; 0440 struts_interp.e2 += offset; 0441 0442 auto bez = insert_item->bezier(); 0443 bez.set_segment(insert_params.index, cubic_segment_from_struts(mold_original, struts_interp)); 0444 0445 insert_item->set_bezier(bez, commit); 0446 } 0447 0448 void delete_nodes_bezier(graphics::BezierItem* item, bool dissolve) 0449 { 0450 if ( item->selected_indices().empty() ) 0451 return; 0452 0453 if ( auto bez_prop = item->target_bezier_property() ) 0454 { 0455 int sz1 = item->selected_indices().size() + 1; 0456 if ( sz1 >= item->bezier().size() && std::all_of(bez_prop->begin(), bez_prop->end(), [sz1](const auto& kf){ return sz1 > kf.get().size(); }) ) 0457 { 0458 // At the moment it always is a Path, but it might change in the future (ie: masks) 0459 if ( auto path = item->target_object()->cast<model::Path>() ) 0460 { 0461 item->target_object()->push_command(new command::RemoveObject<model::ShapeElement>(path, path->owner())); 0462 return; 0463 } 0464 } 0465 bez_prop->remove_points(item->selected_indices()); 0466 } 0467 else if ( auto pos_prop = item->target_position_property() ) 0468 { 0469 int sz1 = item->selected_indices().size() + 1; 0470 if ( sz1 >= item->bezier().size() ) 0471 { 0472 QVariant value; 0473 for ( int i = 0; i < pos_prop->keyframe_count(); i++ ) 0474 { 0475 if ( !item->selected_indices().count(i) ) 0476 { 0477 value = pos_prop->keyframe(i)->value(); 0478 break; 0479 } 0480 } 0481 pos_prop->clear_keyframes_undoable(value); 0482 return; 0483 } 0484 pos_prop->remove_points(item->selected_indices()); 0485 } 0486 0487 auto bez = item->bezier(); 0488 0489 if ( dissolve && item->selected_indices().size() == 1 ) 0490 { 0491 math::bezier::Bezier new_bez = item->bezier(); 0492 0493 int index = *item->selected_indices().begin(); 0494 if ( bez.closed() || (index > 0 && index < bez.size() - 1) ) 0495 { 0496 auto old_point = bez[index]; 0497 old_point.set_point_type(math::bezier::Smooth); 0498 auto segment = new_bez.segment(index-1); 0499 qreal d1 = math::length(new_bez[index-1].pos - old_point.pos); 0500 qreal d2 = math::length(new_bez[index].pos - old_point.pos); 0501 qreal t = d1 / (d1 + d2); 0502 0503 auto approx = math::bezier::cubic_segment_from_struts( 0504 segment, 0505 { 0506 old_point.pos, 0507 t, 0508 old_point.tan_in, 0509 old_point.tan_out 0510 } 0511 ); 0512 0513 new_bez.set_segment(index-1, approx); 0514 } 0515 item->set_bezier(new_bez); 0516 } 0517 0518 item->clear_selected_indices(); 0519 } 0520 0521 void delete_nodes(bool dissolve) 0522 { 0523 if ( selection.empty() ) 0524 return; 0525 0526 auto doc = selection.selected.begin()->first->target_object()->document(); 0527 command::UndoMacroGuard macro(dissolve ? i18n("Dissolve Nodes") : i18n("Delete Nodes"), doc); 0528 0529 auto selected = std::move(selection.selected); 0530 selection.selected = {}; 0531 for ( const auto& p : selected ) 0532 delete_nodes_bezier(p.first, dissolve); 0533 0534 selection.initial = nullptr; 0535 } 0536 0537 bool mold_grab(const MouseEvent& event) 0538 { 0539 return insert_item && insert_params.distance <= drag_dist / event.view->get_zoom_factor() && insert_params.factor > 0.1 && insert_params.factor < 0.9; 0540 } 0541 0542 DragMode drag_mode; 0543 QPointF rubber_p1; 0544 QPointF rubber_p2; 0545 QPointer<model::Shape> highlight = nullptr; 0546 Selection selection; 0547 0548 std::map<graphics::BezierItem*, PathCache> active; 0549 math::bezier::ProjectResult insert_params; 0550 math::bezier::Point insert_preview{{}}; 0551 QPointer<graphics::BezierItem> insert_item = nullptr; 0552 math::bezier::BezierSegment mold_original; 0553 0554 Qt::CursorShape cursor = Qt::ArrowCursor; 0555 0556 graphics::DocumentScene * active_scene = nullptr; 0557 0558 static const int drag_dist = 36; // distance squared 0559 }; 0560 0561 tools::EditTool::EditTool() 0562 : d(std::make_unique<Private>()) 0563 {} 0564 0565 tools::EditTool::~EditTool() = default; 0566 0567 void tools::EditTool::mouse_press(const MouseEvent& event) 0568 { 0569 d->highlight = nullptr; 0570 0571 if ( event.press_button == Qt::LeftButton && d->drag_mode == Private::None ) 0572 { 0573 d->drag_mode = Private::Click; 0574 d->rubber_p1 = event.pos(); 0575 0576 auto clicked_on = under_mouse(event, true, SelectionMode::Shape); 0577 if ( clicked_on.handle ) 0578 { 0579 if ( clicked_on.handle->role() == graphics::MoveHandle::Vertex ) 0580 { 0581 d->drag_mode = Private::VertexClick; 0582 d->selection.drag_start = event.scene_pos; 0583 d->selection.initial = static_cast<graphics::PointItem*>(clicked_on.handle->parentItem()); 0584 } 0585 else 0586 { 0587 if ( clicked_on.handle->role() == graphics::MoveHandle::GradientStop ) 0588 { 0589 auto editor = static_cast<graphics::GradientEditor*>(clicked_on.handle->parentItem()); 0590 Q_EMIT gradient_stop_changed( 0591 editor->styler(), 0592 clicked_on.handle->data(graphics::GradientStopIndex).toInt() 0593 ); 0594 } 0595 0596 d->drag_mode = Private::ForwardEvents; 0597 event.forward_to_scene(); 0598 } 0599 } 0600 else if ( d->mold_grab(event) ) 0601 { 0602 d->drag_mode = Private::MoldCurve; 0603 d->insert_params = math::bezier::project(d->insert_item->bezier(), d->active[d->insert_item].inverse_transform.map(event.scene_pos)); 0604 d->mold_original = d->insert_item->bezier().segment(d->insert_params.index); 0605 set_cursor(Qt::ClosedHandCursor); 0606 } 0607 } 0608 } 0609 0610 void tools::EditTool::mouse_move(const MouseEvent& event) 0611 { 0612 if ( event.press_button == Qt::LeftButton ) 0613 { 0614 switch ( d->drag_mode ) 0615 { 0616 case Private::None: 0617 break; 0618 case Private::ForwardEvents: 0619 event.forward_to_scene(); 0620 break; 0621 case Private::Click: 0622 d->drag_mode = Private::RubberBand; 0623 [[fallthrough]]; 0624 case Private::RubberBand: 0625 d->rubber_p2 = event.pos(); 0626 break; 0627 case Private::VertexClick: 0628 d->drag_mode = Private::VertexDrag; 0629 if ( d->selection.initial && !d->selection.initial->parent_editor()->selected_indices().count(d->selection.initial->index()) ) 0630 { 0631 graphics::PointItem* initial = d->selection.initial; 0632 d->selection.clear(); 0633 d->selection.add_bezier_point_item(initial); 0634 } 0635 d->selection.start_drag(); 0636 [[fallthrough]]; 0637 case Private::VertexDrag: 0638 d->selection.drag(event, false); 0639 break; 0640 case Private::VertexAdd: 0641 break; 0642 case Private::MoldCurve: 0643 d->mold_bezier(event.scene_pos, false); 0644 break; 0645 } 0646 } 0647 else if ( event.buttons() == Qt::NoButton ) 0648 { 0649 // find closest point on a bezier 0650 d->insert_params = {}; 0651 d->insert_item = nullptr; 0652 for ( const auto& it : d->active ) 0653 { 0654 0655 auto closest = math::bezier::project(it.first->bezier(), it.second.inverse_transform.map(event.scene_pos)); 0656 if ( closest.distance < d->insert_params.distance ) 0657 { 0658 d->insert_params = closest; 0659 d->insert_item = it.first; 0660 } 0661 } 0662 // get tangents 0663 if ( d->insert_item && d->drag_mode == Private::VertexAdd ) 0664 { 0665 d->insert_preview = d->insert_item->bezier().split_segment_point( 0666 d->insert_params.index, d->insert_params.factor 0667 ); 0668 d->insert_preview.transform(d->active[d->insert_item].forward_transform); 0669 } 0670 0671 // update cursor 0672 auto um = under_mouse(event, true, SelectionMode::Shape); 0673 if ( d->drag_mode == Private::None && !um.handle && d->mold_grab(event) ) 0674 set_cursor(Qt::OpenHandCursor); 0675 else if ( d->drag_mode != Private::VertexAdd ) 0676 set_cursor(Qt::ArrowCursor); 0677 0678 // Find shape to highlight 0679 d->highlight = nullptr; 0680 for ( auto node : um.nodes ) 0681 { 0682 if ( auto path = node->node()->cast<model::Shape>() ) 0683 { 0684 if ( !event.scene->has_editors(path) ) 0685 d->highlight = path; 0686 break; 0687 } 0688 } 0689 } 0690 } 0691 0692 void tools::EditTool::mouse_release(const MouseEvent& event) 0693 { 0694 if ( event.button() == Qt::LeftButton ) 0695 { 0696 switch ( d->drag_mode ) 0697 { 0698 case Private::None: 0699 break; 0700 case Private::ForwardEvents: 0701 event.forward_to_scene(); 0702 break; 0703 case Private::RubberBand: 0704 { 0705 d->rubber_p2 = event.pos(); 0706 d->drag_mode = Private::None; 0707 if ( !(event.modifiers() & Qt::ShiftModifier) ) 0708 d->selection.clear(); 0709 auto items = event.scene->items( 0710 event.view->mapToScene( 0711 QRect(d->rubber_p1.toPoint(), d->rubber_p2.toPoint()).normalized().normalized() 0712 ), 0713 Qt::IntersectsItemShape, 0714 Qt::DescendingOrder, 0715 event.view->viewportTransform() 0716 ); 0717 for ( auto item : items ) 0718 if ( item->data(graphics::ItemData::HandleRole).toInt() == graphics::MoveHandle::Vertex ) 0719 d->selection.add_bezier_point_item(static_cast<graphics::PointItem*>(item->parentItem())); 0720 event.view->viewport()->update(); 0721 break; 0722 } 0723 case Private::Click: 0724 { 0725 std::vector<model::VisualNode*> selection; 0726 0727 auto nodes = under_mouse(event, true, SelectionMode::Group).nodes; 0728 0729 if ( !nodes.empty() ) 0730 selection.push_back(nodes[0]->node()); 0731 0732 auto mode = graphics::DocumentScene::Replace; 0733 if ( event.modifiers() & (Qt::ShiftModifier|Qt::ControlModifier) ) 0734 mode = graphics::DocumentScene::Toggle; 0735 0736 event.scene->user_select(selection, mode); 0737 break; 0738 } 0739 case Private::VertexClick: 0740 if ( event.modifiers() & (Qt::ControlModifier|Qt::AltModifier) ) 0741 { 0742 if ( d->selection.initial->point().type == math::bezier::PointType::Corner ) 0743 d->selection.initial->set_point_type(math::bezier::PointType::Smooth); 0744 else 0745 d->selection.initial->set_point_type(math::bezier::PointType::Corner); 0746 } 0747 else 0748 { 0749 if ( !(event.modifiers() & Qt::ShiftModifier) ) 0750 d->selection.clear(); 0751 d->selection.toggle_handle(handle_under_mouse(event)); 0752 } 0753 d->selection.initial = nullptr; 0754 break; 0755 case Private::VertexDrag: 0756 d->selection.drag(event, true); 0757 d->selection.initial = nullptr; 0758 break; 0759 case Private::VertexAdd: 0760 if ( d->insert_item ) 0761 { 0762 d->insert_item->split_segment(d->insert_params.index, d->insert_params.factor); 0763 exit_add_point_mode(); 0764 } 0765 break; 0766 case Private::MoldCurve: 0767 d->mold_bezier(event.scene_pos, true); 0768 set_cursor(Qt::OpenHandCursor); 0769 break; 0770 } 0771 0772 d->drag_mode = Private::None; 0773 } 0774 else if ( event.button() == Qt::RightButton ) 0775 { 0776 Private::context_menu(this, event); 0777 } 0778 0779 } 0780 0781 void tools::EditTool::mouse_double_click(const MouseEvent& event) 0782 { 0783 edit_clicked(event); 0784 } 0785 0786 0787 void tools::EditTool::paint(const PaintEvent& event) 0788 { 0789 if ( d->drag_mode == Private::RubberBand ) 0790 { 0791 event.painter->setBrush(Qt::transparent); 0792 QColor select_color = event.view->palette().color(QPalette::Highlight); 0793 QPen pen(select_color, 1); 0794 pen.setCosmetic(true); 0795 event.painter->setPen(pen); 0796 select_color.setAlpha(128); 0797 event.painter->setBrush(select_color); 0798 event.painter->drawRect(QRectF(d->rubber_p1, d->rubber_p2)); 0799 } 0800 else if ( d->drag_mode == Private::VertexAdd ) 0801 { 0802 if ( d->insert_item ) 0803 { 0804 QColor select_color = event.view->palette().color(QPalette::Highlight); 0805 QPen pen(select_color, 1); 0806 pen.setCosmetic(true); 0807 event.painter->setPen(pen); 0808 event.painter->drawLine( 0809 event.view->mapFromScene(d->insert_preview.tan_in), 0810 event.view->mapFromScene(d->insert_preview.tan_out) 0811 ); 0812 0813 select_color.setAlpha(128); 0814 event.painter->setBrush(select_color); 0815 event.painter->drawEllipse(event.view->mapFromScene(d->insert_preview.pos), 6, 6); 0816 } 0817 } 0818 else if ( d->highlight ) 0819 { 0820 QColor select_color = event.view->palette().color(QPalette::Highlight); 0821 QPen pen(select_color, 1); 0822 QPainterPath p; 0823 d->highlight->to_bezier(d->highlight->time()).add_to_painter_path(p); 0824 QTransform trans = d->highlight->transform_matrix(d->highlight->time()) * event.view->viewportTransform(); 0825 p = trans.map(p); 0826 event.painter->setPen(pen); 0827 event.painter->setBrush(Qt::NoBrush); 0828 event.painter->drawPath(p); 0829 } 0830 } 0831 0832 void tools::EditTool::key_press(const KeyEvent& event) 0833 { 0834 Q_UNUSED(event); 0835 } 0836 0837 void tools::EditTool::key_release(const KeyEvent& event) 0838 { 0839 if ( event.key() == Qt::Key_Escape || event.key() == Qt::Key_Back ) 0840 { 0841 if ( d->drag_mode == Private::VertexAdd ) 0842 { 0843 exit_add_point_mode(); 0844 event.repaint(); 0845 } 0846 event.accept(); 0847 } 0848 } 0849 0850 QCursor tools::EditTool::cursor() 0851 { 0852 return d->cursor; 0853 } 0854 0855 void tools::EditTool::on_selected(graphics::DocumentScene * scene, model::VisualNode * node) 0856 { 0857 d->impl_extract_selection_recursive_item(scene, node); 0858 } 0859 0860 void tools::EditTool::enable_event(const Event& event) 0861 { 0862 d->highlight = nullptr; 0863 exit_add_point_mode(); 0864 d->active_scene = event.scene; 0865 } 0866 0867 void tools::EditTool::disable_event(const Event&) 0868 { 0869 d->highlight = nullptr; 0870 d->active.clear(); 0871 exit_add_point_mode(); 0872 d->active_scene = nullptr; 0873 } 0874 0875 QWidget* tools::EditTool::on_create_widget() 0876 { 0877 return new QWidget(); 0878 } 0879 0880 void tools::EditTool::selection_set_vertex_type(math::bezier::PointType t) 0881 { 0882 if ( d->selection.empty() ) 0883 return; 0884 0885 auto doc = d->selection.selected.begin()->first->target_object()->document(); 0886 command::UndoMacroGuard macro(i18n("Set node type"), doc); 0887 for ( const auto& p : d->selection.selected ) 0888 { 0889 auto bez = p.first->bezier(); 0890 for ( int index : p.first->selected_indices() ) 0891 bez[index] = bez.point_with_type(index, t); 0892 p.first->set_bezier(bez); 0893 } 0894 } 0895 0896 void tools::EditTool::selection_delete() 0897 { 0898 d->delete_nodes(false); 0899 } 0900 0901 void tools::EditTool::selection_dissolve() 0902 { 0903 d->delete_nodes(true); 0904 } 0905 0906 0907 void tools::EditTool::selection_straighten() 0908 { 0909 if ( d->selection.empty() ) 0910 return; 0911 0912 auto doc = d->selection.selected.begin()->first->target_object()->document(); 0913 0914 command::UndoMacroGuard macro(i18n("Straighten segments"), doc, false); 0915 0916 for ( const auto& p : d->selection.selected ) 0917 { 0918 auto bez = p.first->bezier(); 0919 bool modified = false; 0920 0921 for ( int index : p.first->selected_indices() ) 0922 { 0923 int prev_index = index-1; 0924 if ( index == 0 && bez.closed() ) 0925 prev_index = bez.size() - 1; 0926 int next_index = index+1; 0927 if ( index == bez.size()-1 && bez.closed() ) 0928 next_index = 0; 0929 0930 if ( p.first->selected_indices().count(prev_index) ) 0931 { 0932 bez[index].tan_in = bez[index].pos; 0933 bez[index].type = math::bezier::Corner; 0934 modified = true; 0935 } 0936 0937 if ( p.first->selected_indices().count(next_index) ) 0938 { 0939 bez[index].tan_out = bez[index].pos; 0940 bez[index].type = math::bezier::Corner; 0941 modified = true; 0942 } 0943 0944 } 0945 0946 if ( modified ) 0947 { 0948 if ( !macro.started() ) 0949 macro.start(); 0950 p.first->set_bezier(bez); 0951 } 0952 } 0953 } 0954 0955 void tools::EditTool::selection_curve() 0956 { 0957 if ( d->selection.empty() ) 0958 return; 0959 0960 auto doc = d->selection.selected.begin()->first->target_object()->document(); 0961 0962 command::UndoMacroGuard macro(i18n("Curve segments"), doc, false); 0963 0964 for ( const auto& p : d->selection.selected ) 0965 { 0966 auto bez = p.first->bezier(); 0967 bool modified = false; 0968 for ( int index : p.first->selected_indices() ) 0969 { 0970 bool mod_in = false; 0971 int prev_index = index-1; 0972 if ( index == 0 && bez.closed() ) 0973 prev_index = bez.size() - 1; 0974 0975 int next_index = index+1; 0976 if ( index == bez.size()-1 && bez.closed() ) 0977 next_index = 0; 0978 0979 if ( bez[index].tan_in == bez[index].pos && p.first->selected_indices().count(prev_index) ) 0980 { 0981 bez[index].tan_in = math::lerp(bez[index].pos, bez[prev_index].pos, 1/3.); 0982 modified = mod_in = true; 0983 } 0984 0985 if ( bez[index].tan_out == bez[index].pos && p.first->selected_indices().count(next_index) ) 0986 { 0987 bez[index].tan_out = math::lerp(bez[index].pos, bez[next_index].pos, 1/3.); 0988 modified = true; 0989 if ( mod_in ) 0990 { 0991 bez[index].set_point_type(math::bezier::Smooth); 0992 } 0993 } 0994 } 0995 0996 if ( modified ) 0997 { 0998 if ( !macro.started() ) 0999 macro.start(); 1000 p.first->set_bezier(bez); 1001 } 1002 } 1003 } 1004 1005 void tools::EditTool::add_point_mode() 1006 { 1007 d->drag_mode = Private::VertexAdd; 1008 set_cursor(Qt::DragCopyCursor); 1009 } 1010 1011 void tools::EditTool::exit_add_point_mode() 1012 { 1013 d->insert_item = nullptr; 1014 d->insert_params = {}; 1015 d->drag_mode = Private::None; 1016 set_cursor(Qt::ArrowCursor); 1017 } 1018 1019 void tools::EditTool::set_cursor(Qt::CursorShape shape) 1020 { 1021 if ( shape != d->cursor ) 1022 Q_EMIT cursor_changed(d->cursor = shape); 1023 }