diff --git a/Telegram/SourceFiles/editor/editor_paint.cpp b/Telegram/SourceFiles/editor/editor_paint.cpp index d0cd23f2ab..5a28ee302f 100644 --- a/Telegram/SourceFiles/editor/editor_paint.cpp +++ b/Telegram/SourceFiles/editor/editor_paint.cpp @@ -36,6 +36,9 @@ constexpr auto kMinCanvasZoom = 1.; constexpr auto kMaxCanvasZoom = 8.; constexpr auto kCanvasZoomStep = 1.15; constexpr auto kZoomEpsilon = 0.0001; +constexpr auto kMinItemZoom = 0.1; +constexpr auto kMaxItemZoom = 10.; +constexpr auto kCanvasZoomStepFine = 1.015; std::shared_ptr EnsureScene( PhotoModifications &mods, @@ -55,13 +58,15 @@ Paint::Paint( PhotoModifications &modifications, const QSize &imageSize, std::shared_ptr controllers, - Fn blurSource) + Fn blurSource, + bool fixedCrop) : RpWidget(parent) , _controllers(controllers) , _scene(EnsureScene(modifications, imageSize)) , _view(base::make_unique_q(_scene.get(), this)) , _viewport(_view->viewport()) -, _imageSize(imageSize) { +, _imageSize(imageSize) +, _fixedCrop(fixedCrop) { Expects(modifications.paint != nullptr); _scene->setBlurSource(std::move(blurSource)); @@ -165,6 +170,51 @@ Paint::Paint( } +bool Paint::zoomSceneItems(float64 wheelDelta, bool fine) { + if (!wheelDelta) { + return false; + } + const auto step = wheelDelta + / float64(QWheelEvent::DefaultDeltasPerStep); + const auto base = fine ? kCanvasZoomStepFine : kCanvasZoomStep; + const auto factor = std::pow(base, step); + const auto center = rect::center(_scene->sceneRect()); + auto applied = false; + for (const auto &item : _scene->items()) { + const auto raw = item.get(); + const auto oldScale = raw->scale(); + const auto newScale = std::clamp( + oldScale * factor, + kMinItemZoom, + kMaxItemZoom); + if (std::abs(newScale - oldScale) < kZoomEpsilon) { + continue; + } + const auto ratio = newScale / oldScale; + raw->setScale(newScale); + const auto pos = raw->pos(); + raw->setPos(center + (pos - center) * ratio); + applied = true; + } + return applied; +} + +void Paint::panSceneItems(QPointF sceneDelta) { + if (sceneDelta.isNull()) { + return; + } + for (const auto &item : _scene->items()) { + item->setPos(item->pos() + sceneDelta); + } +} + +QPointF Paint::mapWidgetDeltaToScene(QPoint delta) const { + if (!_view) { + return QPointF(delta); + } + return _view->mapToScene(delta) - _view->mapToScene(QPoint()); +} + Paint::~Paint() { if (_viewport) { _viewport->removeEventFilter(this); @@ -393,6 +443,12 @@ bool Paint::eventFilter(QObject *obj, QEvent *e) { return true; } + if (_fixedCrop) { + zoomSceneItems( + delta, + wheel->modifiers().testFlag(Qt::ShiftModifier)); + return true; + } const auto step = delta / float64(QWheelEvent::DefaultDeltasPerStep); const auto factor = std::pow(kCanvasZoomStep, step); const auto newZoom = std::clamp( @@ -421,7 +477,8 @@ bool Paint::eventFilter(QObject *obj, QEvent *e) { const auto mouse = static_cast(e); if (mouse->button() == Qt::MiddleButton) { _pan = { - .active = (_transform.userZoom > kMinCanvasZoom), + .active = (_fixedCrop + || _transform.userZoom > kMinCanvasZoom), .point = mouse->pos(), }; if (_pan.active) { @@ -436,7 +493,9 @@ bool Paint::eventFilter(QObject *obj, QEvent *e) { const auto delta = point - _pan.point; _pan.point = point; - if (_transform.userZoom > kMinCanvasZoom) { + if (_fixedCrop) { + panSceneItems(mapWidgetDeltaToScene(delta)); + } else if (_transform.userZoom > kMinCanvasZoom) { view->horizontalScrollBar()->setValue( view->horizontalScrollBar()->value() - delta.x()); view->verticalScrollBar()->setValue( diff --git a/Telegram/SourceFiles/editor/editor_paint.h b/Telegram/SourceFiles/editor/editor_paint.h index a1dcf00c72..8d100ee4ad 100644 --- a/Telegram/SourceFiles/editor/editor_paint.h +++ b/Telegram/SourceFiles/editor/editor_paint.h @@ -29,7 +29,8 @@ public: PhotoModifications &modifications, const QSize &imageSize, std::shared_ptr controllers, - Fn blurSource); + Fn blurSource, + bool fixedCrop = false); ~Paint() override; [[nodiscard]] std::shared_ptr saveScene() const; @@ -55,6 +56,10 @@ public: void paintImage(QPainter &p, const QPixmap &image) const; void resetView(); + bool zoomSceneItems(float64 wheelDelta, bool fine = false); + void panSceneItems(QPointF sceneDelta); + [[nodiscard]] QPointF mapWidgetDeltaToScene(QPoint delta) const; + private: bool eventFilter(QObject *obj, QEvent *e) override; void updateViewGeometry(); @@ -74,6 +79,7 @@ private: const base::unique_qptr _view; QPointer _viewport; const QSize _imageSize; + const bool _fixedCrop = false; QRect _imageGeometry; QRect _outerGeometry; diff --git a/Telegram/SourceFiles/editor/photo_editor_content.cpp b/Telegram/SourceFiles/editor/photo_editor_content.cpp index 6b0be6f9fc..97a56a706b 100644 --- a/Telegram/SourceFiles/editor/photo_editor_content.cpp +++ b/Telegram/SourceFiles/editor/photo_editor_content.cpp @@ -13,6 +13,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/view/media_view_pip.h" #include "storage/storage_media_prepare.h" +#include +#include + namespace Editor { using Media::View::FlipSizeByRotation; @@ -26,6 +29,7 @@ PhotoEditorContent::PhotoEditorContent( EditorData data) : RpWidget(parent) , _photoSize(photo->size()) +, _fixedCrop(data.fixedCrop) , _paint(base::make_unique_q( this, modifications, @@ -42,7 +46,8 @@ PhotoEditorContent::PhotoEditorContent( auto result = img.copy(pixelRect.intersected(img.rect())); result.setDevicePixelRatio(dpr); return result; - })) + }, + data.fixedCrop)) , _crop(base::make_unique_q( this, modifications, @@ -110,6 +115,47 @@ PhotoEditorContent::PhotoEditorContent( }, lifetime()); setupDragArea(); + + if (_fixedCrop) { + const auto pan = _crop->lifetime().make_state< + std::optional + >(); + _crop->events( + ) | rpl::on_next([=](not_null e) { + const auto type = e->type(); + if (type == QEvent::Wheel) { + const auto wheel = static_cast(e.get()); + _paint->zoomSceneItems( + wheel->angleDelta().y(), + wheel->modifiers().testFlag(Qt::ShiftModifier)); + e->accept(); + } else if (type == QEvent::MouseButtonPress) { + const auto mouse = static_cast(e.get()); + if (mouse->button() == Qt::MiddleButton) { + *pan = mouse->pos(); + _crop->setCursor(Qt::ClosedHandCursor); + e->accept(); + } + } else if (type == QEvent::MouseMove) { + if (pan->has_value()) { + const auto mouse = static_cast(e.get()); + const auto point = mouse->pos(); + const auto delta = point - **pan; + *pan = point; + _paint->panSceneItems( + _paint->mapWidgetDeltaToScene(delta)); + e->accept(); + } + } else if (type == QEvent::MouseButtonRelease) { + const auto mouse = static_cast(e.get()); + if (mouse->button() == Qt::MiddleButton && pan->has_value()) { + pan->reset(); + _crop->unsetCursor(); + e->accept(); + } + } + }, _crop->lifetime()); + } } void PhotoEditorContent::applyModifications( diff --git a/Telegram/SourceFiles/editor/photo_editor_content.h b/Telegram/SourceFiles/editor/photo_editor_content.h index 085ace2344..50dbfff3ad 100644 --- a/Telegram/SourceFiles/editor/photo_editor_content.h +++ b/Telegram/SourceFiles/editor/photo_editor_content.h @@ -54,6 +54,7 @@ public: private: const QSize _photoSize; + const bool _fixedCrop = false; const base::unique_qptr _paint; const base::unique_qptr _crop; const std::shared_ptr _photo;