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 }