Warning, file /graphics/glaxnimate/src/gui/widgets/canvas.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 "canvas.hpp" 0008 0009 #include <cmath> 0010 0011 #include <QMouseEvent> 0012 #include <QtMath> 0013 #include <QTouchEvent> 0014 0015 #include "command/undo_macro_guard.hpp" 0016 #include "tools/base.hpp" 0017 #include "graphics/document_scene.hpp" 0018 0019 using namespace glaxnimate::gui; 0020 using namespace glaxnimate; 0021 0022 0023 class Canvas::Private 0024 { 0025 public: 0026 enum MouseMode 0027 { 0028 None, 0029 }; 0030 0031 enum MouseViewMode 0032 { 0033 NoDrag, 0034 Pan, 0035 Scale, 0036 Rotate 0037 }; 0038 0039 Private(Canvas* view) : view(view) {} 0040 0041 Canvas* view; 0042 0043 qreal zoom_factor = 1; 0044 qreal rotation = 0; 0045 tools::Tool* tool = nullptr; 0046 glaxnimate::gui::SelectionManager* tool_target = nullptr; 0047 // MouseMode mouse_mode = None; 0048 0049 MouseViewMode mouse_view_mode = NoDrag; 0050 QPoint move_last; 0051 QPoint move_last_screen; 0052 QPointF move_last_scene; 0053 Qt::MouseButton press_button; 0054 QPoint move_press_screen; 0055 QPointF move_press_scene; 0056 0057 QPoint transform_center; 0058 QPointF transform_center_scene; 0059 qreal scale_start_zoom = 1; 0060 bool resize_fit = true; 0061 QPainterPath clip; 0062 QPainterPath in_clip; 0063 qreal pinch_zoom = 1; 0064 0065 0066 QPointF map_to_scene(QPointF p) 0067 { 0068 return view->transform().inverted().map(p); 0069 } 0070 0071 void expand_scene_rect(float margin) 0072 { 0073 QRectF vp = QRectF( 0074 view->mapToScene(-margin,-margin), 0075 view->mapToScene(view->width()+margin, view->height()+margin) 0076 ).normalized(); 0077 0078 QRectF sr = view->sceneRect(); 0079 if ( ! sr.contains(vp) ) 0080 { 0081 view->setSceneRect(sr.united(vp)); 0082 } 0083 0084 // Q_EMIT scene_rect_changed(QRectF(mapToScene(0,0),mapToScene(width(),height()))); 0085 } 0086 0087 void update_mouse_cursor() 0088 { 0089 if ( tool ) 0090 view->setCursor(tool->cursor()); 0091 else 0092 view->unsetCursor(); 0093 } 0094 0095 QPointF anchor_scene() 0096 { 0097 QPoint anchor = view->mapFromGlobal(QCursor::pos()); 0098 QRect vp = view->rect(); 0099 if ( !vp.contains(anchor) ) 0100 anchor = vp.center(); 0101 return view->mapToScene(anchor); 0102 } 0103 tools::Event event() 0104 { 0105 return { 0106 view, 0107 scene(), 0108 tool_target 0109 }; 0110 } 0111 0112 tools::MouseEvent mouse_event(QMouseEvent* ev) 0113 { 0114 return { 0115 event(), 0116 ev, 0117 view->mapToScene(ev->pos()), 0118 press_button, 0119 move_press_scene, 0120 move_press_screen, 0121 move_last_scene, 0122 move_last_screen, 0123 }; 0124 } 0125 0126 tools::PaintEvent paint_event(QPainter* painter) 0127 { 0128 return { 0129 event(), 0130 painter, 0131 view->palette() 0132 }; 0133 } 0134 0135 tools::KeyEvent key_event(QKeyEvent* ev) 0136 { 0137 return { 0138 event(), 0139 ev 0140 }; 0141 } 0142 0143 graphics::DocumentScene* scene() 0144 { 0145 return static_cast<graphics::DocumentScene*>(view->scene()); 0146 } 0147 0148 model::Document* document() 0149 { 0150 return scene()->document(); 0151 } 0152 }; 0153 0154 0155 Canvas::Canvas(QWidget* parent) 0156 : QGraphicsView(parent), d(std::make_unique<Private>(this)) 0157 { 0158 setMouseTracking(true); 0159 setRenderHint(QPainter::Antialiasing); 0160 setTransformationAnchor(NoAnchor); 0161 setResizeAnchor(NoAnchor); 0162 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0163 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0164 setFrameStyle(QFrame::NoFrame); 0165 } 0166 0167 Canvas::~Canvas() = default; 0168 0169 void Canvas::mousePressEvent(QMouseEvent* event) 0170 { 0171 // QGraphicsView::mousePressEvent(event); 0172 0173 QPoint mpos = event->pos(); 0174 QPointF scene_pos = mapToScene(mpos); 0175 0176 d->press_button = event->button(); 0177 d->move_press_scene = scene_pos; 0178 #if QT_VERSION_MAJOR < 6 0179 d->move_press_screen = event->screenPos().toPoint(); 0180 d->move_last_screen = event->screenPos().toPoint(); 0181 #else 0182 d->move_press_screen = event->globalPosition().toPoint(); 0183 d->move_last_screen = event->globalPosition().toPoint(); 0184 #endif 0185 d->move_last = mpos; 0186 d->move_last_scene = scene_pos; 0187 0188 if ( event->button() == Qt::MiddleButton ) 0189 { 0190 if ( event->modifiers() & Qt::ControlModifier ) 0191 { 0192 d->mouse_view_mode = Private::Scale; 0193 setCursor(Qt::SizeAllCursor); 0194 d->transform_center = mpos; 0195 d->scale_start_zoom = d->zoom_factor; 0196 } 0197 else if ( event->modifiers() & Qt::ShiftModifier ) 0198 { 0199 d->mouse_view_mode = Private::Rotate; 0200 setCursor(Qt::SizeAllCursor); 0201 d->transform_center = mpos; 0202 d->transform_center_scene = scene_pos; 0203 d->expand_scene_rect(std::max(width(), height())*3); 0204 } 0205 else 0206 { 0207 d->mouse_view_mode = Private::Pan; 0208 setCursor(Qt::ClosedHandCursor); 0209 d->transform_center_scene = scene_pos; 0210 } 0211 } 0212 else if ( d->tool ) 0213 { 0214 d->tool->mouse_press(d->mouse_event(event)); 0215 } 0216 } 0217 0218 void Canvas::mouseMoveEvent(QMouseEvent* event) 0219 { 0220 // QGraphicsView::mouseMoveEvent(event); 0221 0222 QPoint mpos = event->pos(); 0223 QPointF scene_pos = mapToScene(mpos); 0224 Q_EMIT mouse_moved(scene_pos); 0225 0226 if ( event->buttons() & Qt::MiddleButton ) 0227 { 0228 0229 if ( d->mouse_view_mode == Private::Pan ) 0230 { 0231 QPointF delta = scene_pos - d->transform_center_scene; 0232 translate_view(delta); 0233 } 0234 else if ( d->mouse_view_mode == Private::Scale ) 0235 { 0236 QPointF delta = mpos - d->transform_center; 0237 qreal delta_x = delta.y(); 0238 qreal factor = std::pow(5, delta_x/256); 0239 set_zoom_anchor(factor * d->scale_start_zoom, d->transform_center_scene); 0240 } 0241 else if ( d->mouse_view_mode == Private::Rotate ) 0242 { 0243 QPointF delta = mpos - d->transform_center; 0244 qreal len = std::hypot(delta.x(), delta.y()); 0245 if ( len > 4 ) 0246 { 0247 qreal ang = std::atan2(delta.y(), delta.x()); 0248 QPointF deltap = d->move_last - d->transform_center; 0249 qreal angp = std::atan2(deltap.y(), deltap.x()); 0250 do_rotate(ang-angp, d->transform_center_scene); 0251 } 0252 } 0253 } 0254 else if ( d->tool ) 0255 { 0256 d->tool->mouse_move(d->mouse_event(event)); 0257 } 0258 0259 d->move_last = mpos; 0260 d->move_last_scene = scene_pos; 0261 d->move_last_screen = event->screenPos().toPoint(); 0262 // scene()->invalidate(); 0263 viewport()->update(); 0264 } 0265 0266 0267 0268 void Canvas::mouseReleaseEvent(QMouseEvent * event) 0269 { 0270 // QGraphicsView::mouseReleaseEvent(event); 0271 // QPoint mpos = event->pos(); 0272 // QPointF scene_pos = mapToScene(mpos); 0273 0274 0275 if ( event->button() == Qt::MiddleButton ) 0276 { 0277 d->mouse_view_mode = Private::NoDrag; 0278 } 0279 else if ( d->tool ) 0280 { 0281 d->tool->mouse_release(d->mouse_event(event)); 0282 } 0283 0284 d->press_button = Qt::NoButton; 0285 d->update_mouse_cursor(); 0286 } 0287 0288 void Canvas::mouseDoubleClickEvent(QMouseEvent* event) 0289 { 0290 if ( d->tool ) 0291 { 0292 d->tool->mouse_double_click(d->mouse_event(event)); 0293 } 0294 } 0295 0296 void Canvas::keyPressEvent(QKeyEvent* event) 0297 { 0298 if ( d->tool ) 0299 { 0300 d->tool->key_press(d->key_event(event)); 0301 } 0302 } 0303 0304 void Canvas::keyReleaseEvent(QKeyEvent* event) 0305 { 0306 if ( d->tool ) 0307 { 0308 d->tool->key_release(d->key_event(event)); 0309 } 0310 } 0311 0312 void Canvas::wheelEvent(QWheelEvent* event) 0313 { 0314 if ( event->angleDelta().y() < 0 ) 0315 zoom_out(); 0316 else 0317 zoom_in(); 0318 } 0319 0320 void Canvas::zoom_in() 0321 { 0322 zoom_view(1.25); 0323 } 0324 0325 void Canvas::zoom_out() 0326 { 0327 zoom_view(0.8); 0328 } 0329 0330 void Canvas::translate_view(const QPointF& delta) 0331 { 0332 translate(delta); 0333 d->expand_scene_rect(10); 0334 } 0335 0336 void Canvas::zoom_view(qreal factor) 0337 { 0338 d->expand_scene_rect(std::max(width(), height())); 0339 zoom_view_anchor(factor, d->anchor_scene()); 0340 } 0341 0342 void Canvas::zoom_view_anchor(qreal factor, const QPointF& scene_anchor) 0343 { 0344 if ( d->zoom_factor*factor < 0.01 ) 0345 return; 0346 0347 d->expand_scene_rect(10); 0348 translate(scene_anchor); 0349 scale(factor, factor); 0350 translate(-scene_anchor); 0351 d->expand_scene_rect(0); 0352 0353 d->zoom_factor *= factor; 0354 0355 d->resize_fit = false; 0356 Q_EMIT zoomed(d->zoom_factor); 0357 } 0358 0359 void Canvas::set_zoom(qreal factor) 0360 { 0361 set_zoom_anchor(factor, d->anchor_scene()); 0362 } 0363 0364 void Canvas::set_zoom_anchor(qreal factor, const QPointF& anchor) 0365 { 0366 if ( factor < 0.01 ) 0367 return; 0368 zoom_view_anchor(factor / d->zoom_factor, anchor); 0369 } 0370 0371 qreal Canvas::get_zoom_factor() const 0372 { 0373 return d->zoom_factor; 0374 } 0375 0376 void Canvas::flip_horizontal() 0377 { 0378 auto anchor = mapToScene(width()/2, height()/2); 0379 auto angle = math::rad2deg(d->rotation); 0380 translate(anchor); 0381 rotate(-angle); 0382 scale(-1, 1); 0383 rotate(angle); 0384 translate(-anchor); 0385 } 0386 0387 void Canvas::paintEvent(QPaintEvent *event) 0388 { 0389 QGraphicsView::paintEvent(event); 0390 0391 QPainter painter; 0392 painter.begin(viewport()); 0393 painter.setRenderHints(renderHints()); 0394 0395 if ( d->mouse_view_mode == Private::Rotate || d->mouse_view_mode == Private::Scale ) 0396 { 0397 QPoint p1 = d->move_last; 0398 QPoint p2 = d->transform_center; 0399 painter.setPen(QPen(QColor(150, 150, 150), 3)); 0400 painter.drawLine(p1, p2); 0401 painter.setPen(QPen(QColor(50, 50, 50), 1)); 0402 painter.drawLine(p1, p2); 0403 } 0404 else if ( d->tool ) 0405 { 0406 d->tool->paint(d->paint_event(&painter)); 0407 } 0408 0409 const QPalette& palette = this->palette(); 0410 painter.setPen(Qt::NoPen); 0411 painter.setBrush(palette.window()); 0412 painter.drawPath(d->clip); 0413 0414 painter.setBrush(Qt::NoBrush); 0415 0416 QPen pen(palette.mid(), 1); 0417 0418 if ( d->document() && d->document()->record_to_keyframe() ) 0419 { 0420 if ( hasFocus() ) 0421 pen.setColor(QColor(255, 0, 0)); 0422 else 0423 pen.setColor(math::lerp(pen.color(), QColor(255, 0, 0), 0.25)); 0424 } 0425 else 0426 { 0427 if ( hasFocus() ) 0428 pen.setBrush(palette.highlight()); 0429 } 0430 0431 painter.setPen(pen); 0432 painter.drawPath(d->in_clip); 0433 0434 painter.end(); 0435 0436 } 0437 0438 void Canvas::do_rotate(qreal radians, const QPointF& scene_anchor) 0439 { 0440 translate(scene_anchor); 0441 rotate(qRadiansToDegrees(radians)); 0442 translate(-scene_anchor); 0443 d->expand_scene_rect(10); 0444 d->rotation += radians; 0445 d->rotation = std::fmod(d->rotation, math::tau); 0446 if ( d->rotation < 0 ) 0447 d->rotation += math::tau; 0448 0449 0450 d->resize_fit = false; 0451 Q_EMIT rotated(d->rotation); 0452 } 0453 0454 void Canvas::set_rotation(qreal radians) 0455 { 0456 do_rotate(radians-d->rotation, d->anchor_scene()); 0457 } 0458 0459 void Canvas::view_fit() 0460 { 0461 setTransform(QTransform()); 0462 d->rotation = 0; 0463 d->zoom_factor = 1; 0464 Q_EMIT rotated(0); 0465 0466 QRect fit_target; 0467 0468 if ( d->tool_target->current_composition() ) 0469 fit_target = QRect( 0470 -32, 0471 -32, 0472 d->tool_target->current_composition()->width.get() + 64, 0473 d->tool_target->current_composition()->height.get() + 64 0474 ); 0475 0476 if ( fit_target.isValid() && width() > 0 && height() > 0 ) 0477 { 0478 d->expand_scene_rect(std::max(width(), height())*3); 0479 qreal factor = std::min(width() / qreal(fit_target.width()), height() / qreal(fit_target.height())); 0480 QPointF center(fit_target.center()); 0481 zoom_view_anchor(factor, QPointF(0, 0)); 0482 centerOn(center); 0483 } 0484 else 0485 { 0486 Q_EMIT zoomed(1); 0487 } 0488 0489 d->resize_fit = true; 0490 } 0491 0492 void Canvas::set_active_tool(tools::Tool* tool) 0493 { 0494 if ( d->tool ) 0495 { 0496 d->tool->disable_event(d->event()); 0497 disconnect(d->tool, nullptr, this, nullptr); 0498 } 0499 0500 d->tool = tool; 0501 0502 d->tool->enable_event(d->event()); 0503 0504 connect(d->tool, &tools::Tool::cursor_changed, this, &QWidget::setCursor); 0505 0506 if ( d->mouse_view_mode == Private::NoDrag ) 0507 setCursor(tool->cursor()); 0508 } 0509 0510 void Canvas::set_tool_target(glaxnimate::gui::SelectionManager* window) 0511 { 0512 d->tool_target = window; 0513 } 0514 0515 void Canvas::translate(const QPointF& p) 0516 { 0517 d->resize_fit = false; 0518 QGraphicsView::translate(p.x(), p.y()); 0519 } 0520 0521 void Canvas::resizeEvent(QResizeEvent* event) 0522 { 0523 QGraphicsView::resizeEvent(event); 0524 if ( d->resize_fit ) 0525 view_fit(); 0526 0527 d->clip = {}; 0528 d->clip.setFillRule(Qt::OddEvenFill); 0529 d->clip.addRect(QRectF(viewport()->rect())); 0530 QPainterPath pp; 0531 pp.addRoundedRect(QRectF(viewport()->rect()), 4, 4); 0532 d->in_clip = pp.toReversed(); 0533 d->clip.addPath(d->in_clip); 0534 } 0535 0536 void Canvas::changeEvent(QEvent* event) 0537 { 0538 QWidget::changeEvent ( event ); 0539 0540 if ( event->type() == QEvent::PaletteChange ) { 0541 scene()->setPalette(palette()); 0542 } 0543 } 0544 0545 void Canvas::dragEnterEvent(QDragEnterEvent* event) 0546 { 0547 if ( event->mimeData()->hasFormat("application/x.glaxnimate-asset-uuid") ) { 0548 event->setDropAction(Qt::LinkAction); 0549 event->acceptProposedAction(); 0550 } 0551 } 0552 0553 void Canvas::dragMoveEvent(QDragMoveEvent* event) 0554 { 0555 event->acceptProposedAction(); 0556 } 0557 0558 0559 void Canvas::dropEvent(QDropEvent* event) 0560 { 0561 event->acceptProposedAction(); 0562 Q_EMIT dropped(event->mimeData()); 0563 } 0564 0565 bool Canvas::event(QEvent* event) 0566 { 0567 if ( event->type() == QEvent::FocusIn || event->type() == QEvent::FocusOut ) 0568 viewport()->update(); 0569 0570 return QGraphicsView::event(event); 0571 } 0572 0573 bool Canvas::viewportEvent(QEvent *event) 0574 { 0575 switch ( event->type() ) 0576 { 0577 case QEvent::TouchBegin: 0578 case QEvent::TouchUpdate: 0579 case QEvent::TouchEnd: 0580 { 0581 QTouchEvent *touch_event = static_cast<QTouchEvent *>(event); 0582 0583 #if QT_VERSION_MAJOR < 6 0584 QList<QTouchEvent::TouchPoint> touch_points = touch_event->touchPoints(); 0585 #else 0586 QList<QTouchEvent::TouchPoint> touch_points = touch_event->points(); 0587 #endif 0588 0589 if (touch_points.count() == 2) 0590 { 0591 const QTouchEvent::TouchPoint &p0 = touch_points.first(); 0592 const QTouchEvent::TouchPoint &p1 = touch_points.last(); 0593 0594 #if QT_VERSION_MAJOR < 6 0595 qreal initial_distance = math::length(p0.startPos() - p1.startPos()); 0596 #else 0597 qreal initial_distance = math::length(p0.pressPosition() - p1.pressPosition()); 0598 #endif 0599 0600 if ( initial_distance > 0 ) 0601 { 0602 #if QT_VERSION_MAJOR < 6 0603 qreal distance = math::length(p0.pos() - p1.pos()); 0604 QPointF travel = (p0.pos() - p0.startPos() + p1.pos() - p1.startPos()) / 2; 0605 #else 0606 qreal distance = math::length(p0.position() - p1.position()); 0607 QPointF travel = (p0.position() - p0.pressPosition() + p1.position() - p1.pressPosition()) / 2; 0608 #endif 0609 qreal travel_distance = math::length(travel); 0610 0611 // pinch 0612 if ( math::abs(distance - initial_distance) > travel_distance ) 0613 { 0614 // scenePos() lies... 0615 QPointF center = (d->map_to_scene(p0.pos()) + d->map_to_scene(p1.pos())) / 2; 0616 qreal scale_by = distance / initial_distance; 0617 0618 if ( touch_event->touchPointStates() & Qt::TouchPointReleased ) 0619 { 0620 d->pinch_zoom *= scale_by; 0621 scale_by = 1; 0622 } 0623 0624 set_zoom_anchor(d->pinch_zoom * scale_by, center); 0625 } 0626 // pan 0627 else 0628 { 0629 #if QT_VERSION_MAJOR < 6 0630 QPointF scene_travel = (p0.scenePos() - p0.lastScenePos() + p1.scenePos() - p1.lastScenePos()) / 2 / d->zoom_factor; 0631 #else 0632 QPointF scene_travel = (p0.scenePosition() - p0.sceneLastPosition() + p1.scenePosition() - p1.sceneLastPosition()) / 2 / d->zoom_factor; 0633 #endif 0634 translate_view(scene_travel); 0635 } 0636 } 0637 0638 return true; 0639 } 0640 } 0641 default: 0642 break; 0643 } 0644 0645 return QGraphicsView::viewportEvent(event); 0646 }