Warning, file /graphics/glaxnimate/src/gui/tools/select_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 "base.hpp" 0008 0009 #include <variant> 0010 0011 #include "model/shapes/path.hpp" 0012 #include "command/structure_commands.hpp" 0013 #include "command/animation_commands.hpp" 0014 #include "math/geom.hpp" 0015 0016 #ifndef Q_OS_ANDROID 0017 #include "widgets/dialogs/glaxnimate_window.hpp" 0018 #include "widgets/menus/node_menu.hpp" 0019 #include "handle_menu.hpp" 0020 #endif 0021 0022 namespace glaxnimate::gui::tools { 0023 0024 class SelectTool : public Tool 0025 { 0026 public: 0027 QString id() const override { return "select"; } 0028 QIcon icon() const override { return QIcon::fromTheme("edit-select"); } 0029 QString name() const override { return i18n("Select"); } 0030 QKeySequence key_sequence() const override { return QKeySequence(i18n("F1"), QKeySequence::PortableText); } 0031 static int static_group() noexcept { return Registry::Core; } 0032 int group() const noexcept override { return static_group(); } 0033 0034 private: 0035 enum DragMode 0036 { 0037 None, 0038 Click, 0039 RubberBand, 0040 ForwardEvents, 0041 DrawSelect, 0042 DragObject, 0043 }; 0044 0045 struct DragObjectData 0046 { 0047 template<class Prop> 0048 struct PropData 0049 { 0050 Prop* property; 0051 typename Prop::value_type start_value; 0052 }; 0053 using Variant = std::variant< 0054 PropData<model::AnimatedProperty<QPointF>>, 0055 PropData<model::AnimatedProperty<math::bezier::Bezier>> 0056 >; 0057 0058 template<class T> 0059 DragObjectData(model::VisualNode* node, T* property, const QPointF& scene_pos) 0060 : transform(node->docnode_fuzzy_parent()->transform_matrix(node->time()).inverted()), 0061 data(PropData<T>{property, property->get()}), 0062 start_point(transform.map(scene_pos)) 0063 {} 0064 0065 static void push(model::VisualNode* node, const QPointF& scene_pos, std::vector<DragObjectData>& out) 0066 { 0067 if ( auto prop = node->get_property("position") ) 0068 out.push_back(DragObjectData(node, static_cast<model::AnimatedProperty<QPointF>*>(prop), scene_pos)); 0069 if ( auto prop = node->get_property("transform") ) 0070 out.push_back(DragObjectData( 0071 node, 0072 &static_cast<model::SubObjectProperty<model::Transform>*>(prop)->get()->position, 0073 scene_pos) 0074 ); 0075 else if ( auto shape = qobject_cast<model::Path*>(node) ) 0076 out.push_back(DragObjectData(node, &shape->shape, scene_pos)); 0077 } 0078 0079 void drag(const QPointF& dragged_to, command::SetMultipleAnimated* cmd) const 0080 { 0081 QPointF delta = transform.map(dragged_to) - start_point; 0082 0083 0084 if ( data.index() == 0 ) 0085 { 0086 cmd->push_property(std::get<0>(data).property, std::get<0>(data).start_value + delta); 0087 return; 0088 } 0089 0090 math::bezier::Bezier new_bezier = std::get<1>(data).start_value; 0091 for ( auto& point : new_bezier ) 0092 point.translate(delta); 0093 0094 cmd->push_property(std::get<1>(data).property, QVariant::fromValue(new_bezier)); 0095 } 0096 0097 model::Document* doc() const 0098 { 0099 if ( data.index() == 0 ) 0100 return std::get<0>(data).property->object()->document(); 0101 return std::get<1>(data).property->object()->document(); 0102 } 0103 0104 QTransform transform; 0105 Variant data; 0106 QPointF start_point; 0107 }; 0108 0109 void do_drag(QPointF scene_pos, Qt::KeyboardModifiers mods, bool commit) 0110 { 0111 if ( drag_data.empty() ) 0112 return; 0113 0114 rubber_p2 = scene_pos; 0115 if ( mods & Qt::ControlModifier ) 0116 { 0117 std::array<QPointF, 2> directions = {QPointF(1, 0), QPointF(0, 1)}; 0118 QPointF best; 0119 qreal min_dist = std::numeric_limits<qreal>::max(); 0120 for ( const auto& dir : directions ) 0121 { 0122 auto p = math::line_closest_point(rubber_p1, rubber_p1 + dir, scene_pos); 0123 qreal dist = math::length_squared(p - scene_pos); 0124 if ( dist < min_dist ) 0125 { 0126 min_dist = dist; 0127 best = p; 0128 } 0129 } 0130 scene_pos = best; 0131 } 0132 0133 auto cmd = new command::SetMultipleAnimated(i18n("Drag"), commit); 0134 model::Document* doc = drag_data[0].doc(); 0135 0136 for ( const auto& dragger : drag_data ) 0137 dragger.drag(scene_pos, cmd); 0138 0139 doc->push_command(cmd); 0140 } 0141 0142 void mouse_press(const MouseEvent& event) override 0143 { 0144 if ( event.press_button == Qt::LeftButton ) 0145 { 0146 drag_data.clear(); 0147 0148 if ( event.modifiers() & Qt::AltModifier ) 0149 { 0150 drag_mode = DrawSelect; 0151 draw_path.moveTo(event.scene_pos); 0152 return; 0153 } 0154 0155 drag_mode = Click; 0156 rubber_p1 = event.pos(); 0157 0158 auto selection_mode = event.modifiers() & Qt::ControlModifier ? SelectionMode::Shape : SelectionMode::Group; 0159 auto clicked_on = under_mouse(event, true, selection_mode); 0160 if ( clicked_on.handle ) 0161 { 0162 drag_mode = ForwardEvents; 0163 event.forward_to_scene(); 0164 return; 0165 } 0166 else if ( !clicked_on.nodes.empty() ) 0167 { 0168 rubber_p1 = event.scene_pos; 0169 replace_selection = nullptr; 0170 0171 bool drag_selection = false; 0172 0173 for ( auto node : clicked_on.nodes ) 0174 { 0175 if ( event.scene->is_descendant_of_selection(node->node()) ) 0176 { 0177 drag_selection = true; 0178 break; 0179 } 0180 } 0181 0182 if ( drag_selection ) 0183 { 0184 for ( auto node : event.scene->cleaned_selection() ) 0185 DragObjectData::push(node, event.scene_pos, drag_data); 0186 } 0187 else 0188 { 0189 replace_selection = clicked_on.nodes[0]->node(); 0190 DragObjectData::push(clicked_on.nodes[0]->node(), event.scene_pos, drag_data); 0191 } 0192 } 0193 0194 } 0195 } 0196 0197 void mouse_move(const MouseEvent& event) override 0198 { 0199 if ( event.press_button == Qt::LeftButton ) 0200 { 0201 switch ( drag_mode ) 0202 { 0203 case None: 0204 break; 0205 case ForwardEvents: 0206 event.forward_to_scene(); 0207 break; 0208 case DrawSelect: 0209 draw_path.lineTo(event.scene_pos); 0210 break; 0211 case Click: 0212 if ( !drag_data.empty() ) 0213 { 0214 if ( replace_selection ) 0215 { 0216 event.scene->user_select({replace_selection}, graphics::DocumentScene::Replace); 0217 replace_selection = nullptr; 0218 } 0219 drag_mode = DragObject; 0220 } 0221 else 0222 { 0223 drag_mode = RubberBand; 0224 } 0225 mouse_move(event); 0226 break; 0227 case RubberBand: 0228 rubber_p2 = event.pos(); 0229 break; 0230 case DragObject: 0231 do_drag(event.scene_pos, event.modifiers(), false); 0232 break; 0233 } 0234 } 0235 } 0236 0237 void complex_select(const MouseEvent& event, const std::vector<graphics::DocumentNodeGraphicsItem*>& items) 0238 { 0239 0240 auto mode = graphics::DocumentScene::Replace; 0241 if ( event.modifiers() & Qt::ShiftModifier ) 0242 mode = graphics::DocumentScene::Append; 0243 0244 auto selection_mode = event.modifiers() & Qt::ControlModifier ? SelectionMode::Shape : SelectionMode::Group; 0245 0246 std::vector<model::VisualNode*> selection; 0247 for ( auto item : items ) 0248 { 0249 if ( item->node()->docnode_selectable() && item->selection_mode() >= selection_mode ) 0250 selection.push_back(item->node()); 0251 } 0252 0253 event.scene->user_select(selection, mode); 0254 } 0255 0256 void mouse_release(const MouseEvent& event) override 0257 { 0258 if ( event.button() == Qt::LeftButton ) 0259 { 0260 switch ( drag_mode ) 0261 { 0262 case None: 0263 break; 0264 case ForwardEvents: 0265 event.forward_to_scene(); 0266 break; 0267 case DrawSelect: 0268 draw_path.lineTo(event.scene_pos); 0269 complex_select(event, event.scene->nodes(draw_path, event.view->viewportTransform())); 0270 draw_path = {}; 0271 event.view->viewport()->update(); 0272 break; 0273 case RubberBand: 0274 rubber_p2 = event.pos(); 0275 complex_select(event, event.scene->nodes( 0276 event.view->mapToScene(QRect(rubber_p1.toPoint(), rubber_p2.toPoint()).normalized()), 0277 event.view->viewportTransform(), 0278 Qt::ContainsItemShape 0279 )); 0280 drag_mode = None; 0281 event.view->viewport()->update(); 0282 break; 0283 case Click: 0284 { 0285 replace_selection = nullptr; 0286 0287 std::vector<model::VisualNode*> selection; 0288 0289 auto selection_mode = event.modifiers() & Qt::ControlModifier ? SelectionMode::Shape : SelectionMode::Group; 0290 auto nodes = under_mouse(event, true, selection_mode).nodes; 0291 if ( !nodes.empty() ) 0292 selection.push_back(nodes[0]->node()); 0293 0294 auto mode = graphics::DocumentScene::Replace; 0295 if ( event.modifiers() & Qt::ShiftModifier ) 0296 mode = graphics::DocumentScene::Toggle; 0297 0298 event.scene->user_select(selection, mode); 0299 } 0300 break; 0301 case DragObject: 0302 do_drag(event.scene_pos, event.modifiers(), true); 0303 break; 0304 } 0305 0306 replace_selection = nullptr; 0307 drag_data.clear(); 0308 drag_mode = None; 0309 } 0310 else if ( event.press_button == Qt::RightButton ) 0311 { 0312 context_menu(event); 0313 } 0314 } 0315 0316 void mouse_double_click(const MouseEvent& event) override 0317 { 0318 edit_clicked(event); 0319 } 0320 0321 void key_press(const KeyEvent& event) override 0322 { 0323 if ( drag_mode == DragObject && event.key() == Qt::Key_Control ) 0324 { 0325 do_drag(rubber_p2, event.modifiers(), false); 0326 } 0327 } 0328 0329 0330 void key_release(const KeyEvent& event) override 0331 { 0332 if ( drag_mode == None && (event.key() == Qt::Key_Backspace) ) 0333 { 0334 event.window->delete_selected(); 0335 event.accept(); 0336 } 0337 else if ( drag_mode == DragObject && event.key() == Qt::Key_Control ) 0338 { 0339 do_drag(rubber_p2, event.modifiers(), false); 0340 } 0341 } 0342 0343 void paint(const PaintEvent& event) override 0344 { 0345 if ( !selected_shapes.empty() ) 0346 { 0347 QColor select_color = event.view->palette().color(QPalette::Highlight); 0348 QPen pen(select_color, 1); 0349 event.painter->setPen(pen); 0350 event.painter->setBrush(Qt::NoBrush); 0351 for ( auto shape : selected_shapes ) 0352 { 0353 QPainterPath p; 0354 shape->to_bezier(shape->time()).add_to_painter_path(p); 0355 QTransform trans = shape->transform_matrix(shape->time()) * event.view->viewportTransform(); 0356 event.painter->drawPath(trans.map(p)); 0357 } 0358 } 0359 0360 if ( drag_mode == DrawSelect ) 0361 { 0362 event.painter->setTransform(event.view->viewportTransform()); 0363 event.painter->setBrush(Qt::transparent); 0364 QPen pen(event.view->palette().color(QPalette::Highlight), 2); 0365 pen.setCosmetic(true); 0366 event.painter->setPen(pen); 0367 event.painter->drawPath(draw_path); 0368 } 0369 else if ( drag_mode == RubberBand ) 0370 { 0371 event.painter->setBrush(Qt::transparent); 0372 QColor select_color = event.view->palette().color(QPalette::Highlight); 0373 QPen pen(select_color, 1); 0374 pen.setCosmetic(true); 0375 event.painter->setPen(pen); 0376 select_color.setAlpha(128); 0377 event.painter->setBrush(select_color); 0378 event.painter->drawRect(QRectF(rubber_p1, rubber_p2)); 0379 } 0380 } 0381 0382 void enable_event(const Event&) override { selected_shapes.clear(); } 0383 void disable_event(const Event&) override { selected_shapes.clear(); } 0384 0385 QCursor cursor() override { return Qt::ArrowCursor; } 0386 0387 void context_menu(const MouseEvent& event) 0388 { 0389 #ifndef MOBILE_UI 0390 auto window = static_cast<GlaxnimateWindow*>(event.window); 0391 0392 auto targets = under_mouse(event, true, graphics::DocumentNodeGraphicsItem::None); 0393 0394 QMenu menu; 0395 auto undo_stack = &window->undo_group(); 0396 menu.addAction( 0397 QIcon::fromTheme("edit-undo"), 0398 i18n("Undo %1", undo_stack->undoText()), 0399 undo_stack, &QUndoGroup::undo 0400 )->setEnabled(undo_stack->canUndo()); 0401 menu.addAction( 0402 QIcon::fromTheme("edit-redo"), 0403 i18n("Redo %1", undo_stack->redoText()), 0404 undo_stack, &QUndoGroup::redo 0405 )->setEnabled(undo_stack->canRedo()); 0406 0407 menu.addSeparator(); 0408 menu.addAction(QIcon::fromTheme("edit-cut"), i18n("Cut"), 0409 window, &GlaxnimateWindow::cut); 0410 menu.addAction(QIcon::fromTheme("edit-copy"), i18n("Copy"), 0411 window, &GlaxnimateWindow::copy); 0412 menu.addAction(QIcon::fromTheme("edit-paste"), i18n("Paste"), 0413 window, &GlaxnimateWindow::paste); 0414 0415 menu.addSeparator(); 0416 menu.addAction(QIcon::fromTheme("edit-delete-remove"), i18n("Delete"), 0417 window, &GlaxnimateWindow::delete_selected); 0418 0419 0420 menu.addSeparator(); 0421 menu.addAction(QIcon::fromTheme("object-group"), i18n("Group Shapes"), 0422 window, &GlaxnimateWindow::group_shapes); 0423 0424 menu.addAction(QIcon::fromTheme("object-ungroup"), i18n("Ungroup Shapes"), 0425 window, &GlaxnimateWindow::ungroup_shapes); 0426 0427 menu.addSeparator(); 0428 menu.addAction(QIcon::fromTheme("selection-move-to-layer-above"), i18n("Move to..."), 0429 window, &GlaxnimateWindow::move_to); 0430 0431 0432 menu.addSeparator(); 0433 0434 model::DocumentNode* preferred = window->current_shape(); 0435 0436 for ( auto item : targets.nodes ) 0437 { 0438 auto obj_menu = new NodeMenu(item->node(), window, &menu); 0439 if ( item->node() == preferred ) 0440 preferred = nullptr; 0441 if ( obj_menu->actions().size() > 1 ) 0442 menu.addAction(obj_menu->menuAction()); 0443 else 0444 delete obj_menu; 0445 } 0446 0447 if ( preferred ) 0448 menu.addAction((new NodeMenu(preferred, window, &menu))->menuAction()); 0449 0450 if ( targets.handle ) 0451 add_property_menu_actions(&menu, targets.handle, event.window); 0452 0453 0454 menu.exec(QCursor::pos()); 0455 #else 0456 Q_UNUSED(event); 0457 #endif 0458 } 0459 0460 QWidget* on_create_widget() override 0461 { 0462 return new QWidget(); 0463 } 0464 0465 void on_selected(graphics::DocumentScene * scene, model::VisualNode * node) override 0466 { 0467 if ( node->has("transform") || node->has("position") || node->is_instance<model::Composition>() ) 0468 { 0469 scene->show_editors(node); 0470 } 0471 else if ( auto shape = node->cast<model::Shape>() ) 0472 { 0473 selected_shapes.push_back(shape); 0474 scene->invalidate( 0475 shape->transform_matrix(shape->time()) 0476 .map(shape->local_bounding_rect(shape->time())) 0477 .boundingRect() 0478 ); 0479 } 0480 } 0481 0482 void on_deselected(graphics::DocumentScene * scene, model::VisualNode * node) override 0483 { 0484 Tool::on_deselected(scene, node); 0485 0486 if ( auto shape = node->cast<model::Shape>() ) 0487 { 0488 selected_shapes.erase(std::remove(selected_shapes.begin(), selected_shapes.end(), shape), selected_shapes.end()); 0489 scene->invalidate( 0490 shape->transform_matrix(shape->time()) 0491 .map(shape->local_bounding_rect(shape->time())) 0492 .boundingRect() 0493 ); 0494 } 0495 } 0496 0497 void close_document_event(const glaxnimate::gui::tools::Event &) override 0498 { 0499 drag_data.clear(); 0500 replace_selection = nullptr; 0501 selected_shapes.clear(); 0502 } 0503 0504 DragMode drag_mode; 0505 QPainterPath draw_path; 0506 QPointF rubber_p1; 0507 QPointF rubber_p2; 0508 std::vector<DragObjectData> drag_data; 0509 model::VisualNode* replace_selection = nullptr; 0510 std::vector<model::Shape*> selected_shapes; 0511 0512 static Autoreg<SelectTool> autoreg; 0513 }; 0514 0515 0516 tools::Autoreg<tools::SelectTool> tools::SelectTool::autoreg{max_priority}; 0517 0518 } // namespace glaxnimate::gui::tools