diff --git a/Telegram/Resources/icons/photo_editor/ratio.svg b/Telegram/Resources/icons/photo_editor/ratio.svg new file mode 100644 index 0000000000..357b6dd6ef --- /dev/null +++ b/Telegram/Resources/icons/photo_editor/ratio.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 96d6db3340..c4f3ed3368 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -7151,6 +7151,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_photo_editor_menu_flip" = "Flip"; "lng_photo_editor_menu_duplicate" = "Duplicate"; +"lng_photo_editor_crop_original" = "Original"; +"lng_photo_editor_crop_square" = "Square"; +"lng_photo_editor_crop_free" = "Free"; + "lng_voice_speed_slow" = "Slow"; "lng_voice_speed_normal" = "Normal"; "lng_voice_speed_medium" = "Medium"; diff --git a/Telegram/SourceFiles/editor/editor.style b/Telegram/SourceFiles/editor/editor.style index dfc4c77247..a8c9157c5f 100644 --- a/Telegram/SourceFiles/editor/editor.style +++ b/Telegram/SourceFiles/editor/editor.style @@ -89,6 +89,18 @@ photoEditorStickersButton: IconButton(photoEditorRotateButton) { } photoEditorStickersIconActive: icon {{ "settings/settings_stickers", photoEditorButtonIconFgActive }}; +photoEditorCropRatioButton: IconButton(photoEditorRotateButton) { + icon: icon {{ "photo_editor/ratio-23x23", photoEditorButtonIconFg, point(0px, 1px) }}; + iconOver: icon {{ "photo_editor/ratio-23x23", photoEditorButtonIconFgOver, point(0px, 1px) }}; +} +photoEditorCropRatioIconActive: icon {{ "photo_editor/ratio-23x23", photoEditorButtonIconFgActive, point(0px, 1px) }}; + +photoEditorCropRatioMenu: PopupMenu(popupMenuWithIcons) { + menu: Menu(menuWithIcons) { + widthMin: 108px; + } +} + photoEditorUndoButtonInactive: icon {{ "photo_editor/undo", photoEditorButtonIconFgInactive }}; photoEditorRedoButtonInactive: icon {{ "photo_editor/undo-flip_horizontal", photoEditorButtonIconFgInactive }}; diff --git a/Telegram/SourceFiles/editor/editor_crop.cpp b/Telegram/SourceFiles/editor/editor_crop.cpp index 4c1932542e..0fd553768f 100644 --- a/Telegram/SourceFiles/editor/editor_crop.cpp +++ b/Telegram/SourceFiles/editor/editor_crop.cpp @@ -177,7 +177,7 @@ void Crop::paintFrame(QPainter &p) { p.save(); p.setRenderHint(QPainter::Antialiasing, true); p.fillPath(frameShape, st::photoCropPointFg); - if (_data.cropType == EditorData::CropType::Rect) { + { const auto cornerLength = std::min( float64(st::photoEditorCropPointSize * 2), std::min(_cropPaint.width(), _cropPaint.height()) / 2.); @@ -439,12 +439,14 @@ void Crop::performCrop(const QPoint &pos) { } const auto &minSize = st::photoEditorCropMinSize; - const auto xMin = xFactor * int(crop.width() - minSize); - // const auto xMin = int(xFactor * crop.width() - // - xFactor * minSize * ((cropRatio > 1.) ? cropRatio : 1.)); - const auto yMin = yFactor * int(crop.height() - minSize); - // const auto yMin = int(yFactor * crop.height() - // - yFactor * minSize * ((cropRatio < 1.) ? (1. / cropRatio) : 1.)); + const auto minW = (_keepAspectRatio && cropRatio > 1.) + ? (minSize * cropRatio) + : float64(minSize); + const auto minH = (_keepAspectRatio && cropRatio < 1.) + ? (minSize / cropRatio) + : float64(minSize); + const auto xMin = xFactor * int(crop.width() - minW); + const auto yMin = yFactor * int(crop.height() - minH); const auto x = std::clamp( diff.x(), @@ -504,6 +506,48 @@ style::margins Crop::cropMargins() const { return _innerMargins; } +void Crop::setAspectRatio(float64 ratio) { + const auto free = (ratio <= 0.); + _keepAspectRatio = !free; + + if (!free) { + const auto maxW = _innerRect.width(); + const auto maxH = _innerRect.height(); + auto newW = maxW; + auto newH = maxW / ratio; + if (newH > maxH) { + newH = maxH; + newW = maxH * ratio; + } + + const auto center = _cropPaint.center(); + auto adjusted = QRectF( + center.x() - newW / 2., + center.y() - newH / 2., + newW, + newH); + + if (adjusted.left() < _innerRect.left()) { + adjusted.moveLeft(_innerRect.left()); + } + if (adjusted.top() < _innerRect.top()) { + adjusted.moveTop(_innerRect.top()); + } + if (adjusted.right() > _innerRect.right()) { + adjusted.moveRight(_innerRect.right()); + } + if (adjusted.bottom() > _innerRect.bottom()) { + adjusted.moveBottom(_innerRect.bottom()); + } + + setCropPaint(std::move(adjusted)); + convertCropPaintToOriginal(); + } else { + updateEdges(); + } + update(); +} + QRect Crop::saveCropRect() { const auto savedCrop = _cropOriginal.toRect(); return (!savedCrop.topLeft().isNull() || (savedCrop.size() != _imageSize)) diff --git a/Telegram/SourceFiles/editor/editor_crop.h b/Telegram/SourceFiles/editor/editor_crop.h index 8f73d0d6f4..b9d7eec655 100644 --- a/Telegram/SourceFiles/editor/editor_crop.h +++ b/Telegram/SourceFiles/editor/editor_crop.h @@ -31,6 +31,7 @@ public: const QSizeF &scaledImageSize); [[nodiscard]] QRect saveCropRect(); [[nodiscard]] style::margins cropMargins() const; + void setAspectRatio(float64 ratio); protected: void mousePressEvent(QMouseEvent *e) override; diff --git a/Telegram/SourceFiles/editor/photo_editor.cpp b/Telegram/SourceFiles/editor/photo_editor.cpp index 70cfeed815..7d722a93c5 100644 --- a/Telegram/SourceFiles/editor/photo_editor.cpp +++ b/Telegram/SourceFiles/editor/photo_editor.cpp @@ -224,7 +224,8 @@ PhotoEditor::PhotoEditor( this, _controllers, _modifications, - data)) + data, + photo->size())) , _brushes(Deserialize(Core::App().settings().photoEditorBrush()).brushes) , _brushTool(Deserialize(Core::App().settings().photoEditorBrush()).tool) , _colorPicker(std::make_unique( @@ -285,6 +286,10 @@ PhotoEditor::PhotoEditor( _content->applyModifications(_modifications); }, lifetime()); + _controls->aspectRatioChanges() | rpl::on_next([=](float64 ratio) { + _content->applyAspectRatio(ratio); + }, lifetime()); + _controls->paintModeRequests( ) | rpl::on_next([=] { _mode = PhotoEditorMode{ diff --git a/Telegram/SourceFiles/editor/photo_editor_content.cpp b/Telegram/SourceFiles/editor/photo_editor_content.cpp index 0e1b1c8063..8b1697d613 100644 --- a/Telegram/SourceFiles/editor/photo_editor_content.cpp +++ b/Telegram/SourceFiles/editor/photo_editor_content.cpp @@ -142,6 +142,10 @@ void PhotoEditorContent::applyMode(const PhotoEditorMode &mode) { update(); } +void PhotoEditorContent::applyAspectRatio(float64 ratio) { + _crop->setAspectRatio(ratio); +} + void PhotoEditorContent::applyBrush(const Brush &brush) { _paint->applyBrush(brush); } diff --git a/Telegram/SourceFiles/editor/photo_editor_content.h b/Telegram/SourceFiles/editor/photo_editor_content.h index 027e5574a6..cf1cc3226f 100644 --- a/Telegram/SourceFiles/editor/photo_editor_content.h +++ b/Telegram/SourceFiles/editor/photo_editor_content.h @@ -31,6 +31,7 @@ public: void applyModifications(PhotoModifications modifications); void applyMode(const PhotoEditorMode &mode); void applyBrush(const Brush &brush); + void applyAspectRatio(float64 ratio); void save(PhotoModifications &modifications); bool handleKeyPress(not_null e) const; diff --git a/Telegram/SourceFiles/editor/photo_editor_controls.cpp b/Telegram/SourceFiles/editor/photo_editor_controls.cpp index b4c8621ac6..6a824f7a9c 100644 --- a/Telegram/SourceFiles/editor/photo_editor_controls.cpp +++ b/Telegram/SourceFiles/editor/photo_editor_controls.cpp @@ -12,10 +12,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/image/image_prepare.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" +#include "ui/widgets/popup_menu.h" #include "ui/wrap/fade_wrap.h" #include "ui/painter.h" #include "ui/rect.h" #include "styles/style_editor.h" +#include "styles/style_media_player.h" // mediaPlayerMenuCheck #include @@ -204,8 +206,10 @@ PhotoEditorControls::PhotoEditorControls( not_null parent, std::shared_ptr controllers, const PhotoModifications modifications, - const EditorData &data) + const EditorData &data, + const QSize &imageSize) : RpWidget(parent) +, _imageSize(imageSize) , _bg(st::roundedBg) , _buttonHeight(st::photoEditorButtonBarHeight) , _transformButtons(base::make_unique_q(this, _bg)) @@ -235,6 +239,11 @@ PhotoEditorControls::PhotoEditorControls( , _paintModeButton(base::make_unique_q( _transformButtons, st::photoEditorPaintModeButton)) +, _cropRatioButton(data.keepAspectRatio + ? nullptr + : base::make_unique_q( + _transformButtons, + st::photoEditorCropRatioButton)) , _transformDone(base::make_unique_q( _transformButtons, (data.confirm.isEmpty() ? tr::lng_box_done(tr::now) : data.confirm), @@ -430,6 +439,50 @@ PhotoEditorControls::PhotoEditorControls( _flipButton->setIconOverride(icon, icon); }, _flipButton->lifetime()); + if (_cropRatioButton) { + const auto imageRatio = float64( + _imageSize.width()) / _imageSize.height(); + const auto ratiosMatch = [](float64 a, float64 b) { + return std::abs(a - b) < 0.01; + }; + _cropRatioButton->setClickedCallback([=] { + _ratioMenu = base::make_unique_q( + _cropRatioButton.get(), + st::photoEditorCropRatioMenu); + _ratioMenu->setForcedOrigin( + Ui::PanelAnimation::Origin::BottomRight); + const auto check = &st::mediaPlayerMenuCheck; + const auto add = [&](const QString &text, float64 ratio) { + const auto selected = ratiosMatch(_currentRatio, ratio); + _ratioMenu->addAction( + text, + [=] { + if (ratiosMatch(_currentRatio, ratio)) { + return; + } + _currentRatio = ratio; + _aspectRatioChanges.fire_copy(ratio); + const auto locked = (ratio > 0.); + const auto icon = locked + ? &st::photoEditorCropRatioIconActive + : nullptr; + _cropRatioButton->setIconOverride(icon, icon); + }, + selected ? check : nullptr); + }; + add(tr::lng_photo_editor_crop_original(tr::now), imageRatio); + add(tr::lng_photo_editor_crop_square(tr::now), 1.); + add(u"3:2"_q, 3. / 2.); + add(u"16:9"_q, 16. / 9.); + add(u"9:16"_q, 9. / 16.); + add(tr::lng_photo_editor_crop_free(tr::now), 0.); + const auto button = _cropRatioButton.get(); + const auto bottomRight = button->mapToGlobal( + QPoint(button->width(), 0)); + _ratioMenu->popup(bottomRight); + }); + } + updateInputMask(); } @@ -470,6 +523,10 @@ rpl::producer<> PhotoEditorControls::cancelRequests() const { }) | rpl::to_empty); } +rpl::producer PhotoEditorControls::aspectRatioChanges() const { + return _aspectRatioChanges.events(); +} + int PhotoEditorControls::bottomButtonsTop() const { return height() - st::photoEditorControlsBottomSkip diff --git a/Telegram/SourceFiles/editor/photo_editor_controls.h b/Telegram/SourceFiles/editor/photo_editor_controls.h index 920902f4a5..24d95fa799 100644 --- a/Telegram/SourceFiles/editor/photo_editor_controls.h +++ b/Telegram/SourceFiles/editor/photo_editor_controls.h @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/rp_widget.h" +#include "base/unique_qptr.h" #include "ui/effects/animations.h" #include "editor/photo_editor_common.h" #include "editor/photo_editor_inner_common.h" @@ -16,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Ui { class IconButton; class FlatLabel; +class PopupMenu; template class FadeWrap; } // namespace Ui @@ -33,7 +35,8 @@ public: not_null parent, std::shared_ptr controllers, const PhotoModifications modifications, - const EditorData &data); + const EditorData &data, + const QSize &imageSize); [[nodiscard]] rpl::producer rotateRequests() const; [[nodiscard]] rpl::producer<> flipRequests() const; @@ -42,6 +45,7 @@ public: [[nodiscard]] rpl::producer<> cancelRequests() const; [[nodiscard]] rpl::producer colorLinePositionValue() const; [[nodiscard]] rpl::producer colorLineShownValue() const; + [[nodiscard]] rpl::producer aspectRatioChanges() const; [[nodiscard]] bool animating() const; @@ -57,6 +61,7 @@ private: int bottomButtonsTop() const; + const QSize _imageSize; const style::color &_bg; const int _buttonHeight; const base::unique_qptr _transformButtons; @@ -69,6 +74,7 @@ private: const base::unique_qptr _flipButton; const base::unique_qptr _rotateButton; const base::unique_qptr _paintModeButton; + const base::unique_qptr _cropRatioButton; const base::unique_qptr _transformDone; const base::unique_qptr _paintCancel; @@ -78,12 +84,16 @@ private: const base::unique_qptr _stickersButton; const base::unique_qptr _paintDone; + base::unique_qptr _ratioMenu; + float64 _currentRatio = 0.; + bool _flipped = false; Ui::Animations::Simple _toggledBarAnimation; rpl::variable _mode; rpl::event_stream> _keyPresses; + rpl::event_stream _aspectRatioChanges; };