From 96b40f43e96842a6815b7a4f2c6cdbf488933943 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 7 May 2021 00:47:49 +0300 Subject: [PATCH] Added ability to drag and drop images in photo editor. --- Telegram/CMakeLists.txt | 2 + .../editor/controllers/controllers.h | 8 ++- Telegram/SourceFiles/editor/editor_paint.cpp | 48 +++++++++++++++ Telegram/SourceFiles/editor/editor_paint.h | 3 + Telegram/SourceFiles/editor/photo_editor.cpp | 3 +- .../editor/photo_editor_content.cpp | 24 ++++++++ .../SourceFiles/editor/photo_editor_content.h | 2 + .../editor/scene/scene_item_base.cpp | 12 +++- .../editor/scene/scene_item_image.cpp | 58 +++++++++++++++++++ .../editor/scene/scene_item_image.h | 40 +++++++++++++ .../SourceFiles/history/history_drag_area.cpp | 19 ++++-- .../SourceFiles/history/history_drag_area.h | 3 +- .../storage/storage_media_prepare.cpp | 22 ++++++- .../storage/storage_media_prepare.h | 20 ++++--- 14 files changed, 246 insertions(+), 18 deletions(-) create mode 100644 Telegram/SourceFiles/editor/scene/scene_item_image.cpp create mode 100644 Telegram/SourceFiles/editor/scene/scene_item_image.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 9e897bc5b..65ca3f23b 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -533,6 +533,8 @@ PRIVATE editor/scene/scene_item_base.h editor/scene/scene_item_canvas.cpp editor/scene/scene_item_canvas.h + editor/scene/scene_item_image.cpp + editor/scene/scene_item_image.h editor/scene/scene_item_line.cpp editor/scene/scene_item_line.h editor/scene/scene_item_sticker.cpp diff --git a/Telegram/SourceFiles/editor/controllers/controllers.h b/Telegram/SourceFiles/editor/controllers/controllers.h index 9bd40ef17..5ca6b49e7 100644 --- a/Telegram/SourceFiles/editor/controllers/controllers.h +++ b/Telegram/SourceFiles/editor/controllers/controllers.h @@ -9,21 +9,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "editor/controllers/stickers_panel_controller.h" #include "editor/controllers/undo_controller.h" +#include "ui/layers/box_content.h" namespace Editor { struct Controllers final { Controllers( std::unique_ptr stickersPanelController, - std::unique_ptr undoController) + std::unique_ptr undoController, + Fn)> showBox) : stickersPanelController(std::move(stickersPanelController)) - , undoController(std::move(undoController)) { + , undoController(std::move(undoController)) + , showBox(std::move(showBox)) { } ~Controllers() { }; const std::unique_ptr stickersPanelController; const std::unique_ptr undoController; + const Fn)> showBox; }; } // namespace Editor diff --git a/Telegram/SourceFiles/editor/editor_paint.cpp b/Telegram/SourceFiles/editor/editor_paint.cpp index 14eeeeb22..490275868 100644 --- a/Telegram/SourceFiles/editor/editor_paint.cpp +++ b/Telegram/SourceFiles/editor/editor_paint.cpp @@ -7,14 +7,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "editor/editor_paint.h" +#include "app.h" +#include "boxes/confirm_box.h" #include "editor/controllers/controllers.h" #include "editor/scene/scene.h" #include "editor/scene/scene_item_base.h" #include "editor/scene/scene_item_canvas.h" +#include "editor/scene/scene_item_image.h" #include "editor/scene/scene_item_sticker.h" #include "lottie/lottie_single_player.h" +#include "storage/storage_media_prepare.h" +#include "ui/chat/attach/attach_prepare.h" #include +#include namespace Editor { namespace { @@ -46,6 +52,7 @@ Paint::Paint( const QSize &imageSize, std::shared_ptr controllers) : RpWidget(parent) +, _controllers(controllers) , _lastZ(std::make_shared(9000.)) , _scene(EnsureScene(modifications, imageSize)) , _view(base::make_unique_q(_scene.get(), this)) @@ -256,4 +263,45 @@ void Paint::applyBrush(const Brush &brush) { (kMinBrush + float64(kMaxBrush - kMinBrush) * brush.sizeRatio)); } +void Paint::handleMimeData(const QMimeData *data) { + const auto add = [&](QImage image) { + if (image.isNull()) { + return; + } + const auto s = _scene->sceneRect().size(); + const auto size = std::min(s.width(), s.height()) / 2; + const auto x = s.width() / 2; + const auto y = s.height() / 2; + if (!Ui::ValidateThumbDimensions(image.width(), image.height())) { + _controllers->showBox( + Box(tr::lng_edit_media_invalid_file(tr::now))); + return; + } + + const auto item = std::make_shared( + App::pixmapFromImageInPlace(std::move(image)), + _transform.zoom.value(), + _lastZ, + size, + x, + y); + item->setFlip(_transform.flipped); + item->setRotation(-_transform.angle); + _scene->addItem(item); + _scene->clearSelection(); + }; + + using Error = Ui::PreparedList::Error; + auto result = data->hasUrls() + ? Storage::PrepareMediaList( + data->urls().mid(0, 1), + _imageSize.width() / 2) + : Ui::PreparedList(Error::EmptyFile, QString()); + if (result.error == Error::None) { + add(base::take(result.files.front().preview)); + } else if (data->hasImage()) { + add(qvariant_cast(data->imageData())); + } +} + } // namespace Editor diff --git a/Telegram/SourceFiles/editor/editor_paint.h b/Telegram/SourceFiles/editor/editor_paint.h index 721ef7d30..0a825b5d5 100644 --- a/Telegram/SourceFiles/editor/editor_paint.h +++ b/Telegram/SourceFiles/editor/editor_paint.h @@ -37,6 +37,8 @@ public: void keepResult(); void updateUndoState(); + void handleMimeData(const QMimeData *data); + private: struct SavedItem { std::shared_ptr item; @@ -50,6 +52,7 @@ private: bool isItemToRemove(const std::shared_ptr &item) const; bool isItemHidden(const std::shared_ptr &item) const; + const std::shared_ptr _controllers; const std::shared_ptr _lastZ; const std::shared_ptr _scene; const base::unique_qptr _view; diff --git a/Telegram/SourceFiles/editor/photo_editor.cpp b/Telegram/SourceFiles/editor/photo_editor.cpp index 420488d24..6a4852fcb 100644 --- a/Telegram/SourceFiles/editor/photo_editor.cpp +++ b/Telegram/SourceFiles/editor/photo_editor.cpp @@ -58,7 +58,8 @@ PhotoEditor::PhotoEditor( this, controller->sessionController()) : nullptr, - std::make_unique())) + std::make_unique(), + [=] (object_ptr c) { controller->show(std::move(c)); })) , _content(base::make_unique_q( this, photo, diff --git a/Telegram/SourceFiles/editor/photo_editor_content.cpp b/Telegram/SourceFiles/editor/photo_editor_content.cpp index 45da5b5d2..0eb6237ec 100644 --- a/Telegram/SourceFiles/editor/photo_editor_content.cpp +++ b/Telegram/SourceFiles/editor/photo_editor_content.cpp @@ -9,7 +9,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "editor/editor_crop.h" #include "editor/editor_paint.h" +#include "history/history_drag_area.h" #include "media/view/media_view_pip.h" +#include "storage/storage_media_prepare.h" namespace Editor { @@ -92,6 +94,8 @@ PhotoEditorContent::PhotoEditorContent( _imageRect, _photo->pix(_imageRect.width(), _imageRect.height())); }, lifetime()); + + setupDragArea(); } void PhotoEditorContent::applyModifications( @@ -133,4 +137,24 @@ bool PhotoEditorContent::handleKeyPress(not_null e) const { return false; } +void PhotoEditorContent::setupDragArea() { + auto dragEnterFilter = [=](const QMimeData *data) { + return (_mode.mode == PhotoEditorMode::Mode::Transform) + ? false + : Storage::ValidatePhotoEditorMediaDragData(data); + }; + + const auto areas = DragArea::SetupDragAreaToContainer( + this, + std::move(dragEnterFilter), + nullptr, + nullptr, + [](const QMimeData *d) { return Storage::MimeDataState::Image; }, + true); + + areas.photo->setDroppedCallback([=](const QMimeData *data) { + _paint->handleMimeData(data); + }); +} + } // namespace Editor diff --git a/Telegram/SourceFiles/editor/photo_editor_content.h b/Telegram/SourceFiles/editor/photo_editor_content.h index 46ca580e1..253d70e27 100644 --- a/Telegram/SourceFiles/editor/photo_editor_content.h +++ b/Telegram/SourceFiles/editor/photo_editor_content.h @@ -34,6 +34,8 @@ public: bool handleKeyPress(not_null e) const; + void setupDragArea(); + private: const QSize _photoSize; diff --git a/Telegram/SourceFiles/editor/scene/scene_item_base.cpp b/Telegram/SourceFiles/editor/scene/scene_item_base.cpp index f9f93015d..e94b88c5e 100644 --- a/Telegram/SourceFiles/editor/scene/scene_item_base.cpp +++ b/Telegram/SourceFiles/editor/scene/scene_item_base.cpp @@ -74,6 +74,10 @@ ItemBase::ItemBase( .min = int(st::photoEditorItemMinSize / zoom), .max = int(st::photoEditorItemMaxSize / zoom), }; + _horizontalSize = std::clamp( + _horizontalSize, + float64(_sizeLimits.min), + float64(_sizeLimits.max)); updatePens(QPen( QBrush(), @@ -303,7 +307,13 @@ float64 ItemBase::size() const { } void ItemBase::updateVerticalSize() { - _verticalSize = _horizontalSize * _aspectRatio; + const auto verticalSize = _horizontalSize * _aspectRatio; + _verticalSize = std::max( + verticalSize, + float64(st::photoEditorItemMinSize)); + if (verticalSize < st::photoEditorItemMinSize) { + _horizontalSize = _verticalSize / _aspectRatio; + } } void ItemBase::setAspectRatio(float64 aspectRatio) { diff --git a/Telegram/SourceFiles/editor/scene/scene_item_image.cpp b/Telegram/SourceFiles/editor/scene/scene_item_image.cpp new file mode 100644 index 000000000..48ed66a1a --- /dev/null +++ b/Telegram/SourceFiles/editor/scene/scene_item_image.cpp @@ -0,0 +1,58 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "editor/scene/scene_item_image.h" + +namespace Editor { +namespace { + +} // namespace + +ItemImage::ItemImage( + const QPixmap &&pixmap, + rpl::producer zoomValue, + std::shared_ptr zPtr, + int size, + int x, + int y) +: ItemBase(std::move(zoomValue), std::move(zPtr), size, x, y) +, _pixmap(std::move(pixmap)) { + setAspectRatio(_pixmap.isNull() + ? 1.0 + : (_pixmap.height() / float64(_pixmap.width()))); +} + +void ItemImage::paint( + QPainter *p, + const QStyleOptionGraphicsItem *option, + QWidget *w) { + p->drawPixmap(contentRect().toRect(), _pixmap); + ItemBase::paint(p, option, w); +} + +void ItemImage::performFlip() { + _pixmap = _pixmap.transformed(QTransform().scale(-1, 1)); + update(); +} + +std::shared_ptr ItemImage::duplicate( + rpl::producer zoomValue, + std::shared_ptr zPtr, + int size, + int x, + int y) const { + auto pixmap = _pixmap; + return std::make_shared( + std::move(pixmap), + std::move(zoomValue), + std::move(zPtr), + size, + x, + y); +} + +} // namespace Editor diff --git a/Telegram/SourceFiles/editor/scene/scene_item_image.h b/Telegram/SourceFiles/editor/scene/scene_item_image.h new file mode 100644 index 000000000..b47b7ecaa --- /dev/null +++ b/Telegram/SourceFiles/editor/scene/scene_item_image.h @@ -0,0 +1,40 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "editor/scene/scene_item_base.h" + +namespace Editor { + +class ItemImage : public ItemBase { +public: + ItemImage( + const QPixmap &&pixmap, + rpl::producer zoomValue, + std::shared_ptr zPtr, + int size, + int x, + int y); + void paint( + QPainter *p, + const QStyleOptionGraphicsItem *option, + QWidget *widget) override; +protected: + void performFlip() override; + std::shared_ptr duplicate( + rpl::producer zoomValue, + std::shared_ptr zPtr, + int size, + int x, + int y) const override; +private: + QPixmap _pixmap; + +}; + +} // namespace Editor diff --git a/Telegram/SourceFiles/history/history_drag_area.cpp b/Telegram/SourceFiles/history/history_drag_area.cpp index 92a3041a9..b08928f66 100644 --- a/Telegram/SourceFiles/history/history_drag_area.cpp +++ b/Telegram/SourceFiles/history/history_drag_area.cpp @@ -51,7 +51,8 @@ DragArea::Areas DragArea::SetupDragAreaToContainer( Fn)> &&dragEnterFilter, Fn &&setAcceptDropsField, Fn &&updateControlsGeometry, - DragArea::CallbackComputeState &&computeState) { + DragArea::CallbackComputeState &&computeState, + bool hideSubtext) { using DragState = Storage::MimeDataState; @@ -133,24 +134,32 @@ DragArea::Areas DragArea::SetupDragAreaToContainer( case DragState::Files: attachDragDocument->setText( tr::lng_drag_files_here(tr::now), - tr::lng_drag_to_send_files(tr::now)); + hideSubtext + ? QString() + : tr::lng_drag_to_send_files(tr::now)); attachDragDocument->otherEnter(); attachDragPhoto->hideFast(); break; case DragState::PhotoFiles: attachDragDocument->setText( tr::lng_drag_images_here(tr::now), - tr::lng_drag_to_send_no_compression(tr::now)); + hideSubtext + ? QString() + : tr::lng_drag_to_send_no_compression(tr::now)); attachDragPhoto->setText( tr::lng_drag_photos_here(tr::now), - tr::lng_drag_to_send_quick(tr::now)); + hideSubtext + ? QString() + : tr::lng_drag_to_send_quick(tr::now)); attachDragDocument->otherEnter(); attachDragPhoto->otherEnter(); break; case DragState::Image: attachDragPhoto->setText( tr::lng_drag_images_here(tr::now), - tr::lng_drag_to_send_quick(tr::now)); + hideSubtext + ? QString() + : tr::lng_drag_to_send_quick(tr::now)); attachDragDocument->hideFast(); attachDragPhoto->otherEnter(); break; diff --git a/Telegram/SourceFiles/history/history_drag_area.h b/Telegram/SourceFiles/history/history_drag_area.h index 1e5529bfd..ec802b107 100644 --- a/Telegram/SourceFiles/history/history_drag_area.h +++ b/Telegram/SourceFiles/history/history_drag_area.h @@ -32,7 +32,8 @@ public: Fn)> &&dragEnterFilter = nullptr, Fn &&setAcceptDropsField = nullptr, Fn &&updateControlsGeometry = nullptr, - CallbackComputeState &&computeState = nullptr); + CallbackComputeState &&computeState = nullptr, + bool hideSubtext = false); void setText(const QString &text, const QString &subtext); diff --git a/Telegram/SourceFiles/storage/storage_media_prepare.cpp b/Telegram/SourceFiles/storage/storage_media_prepare.cpp index 94dace24c..e33e84fc7 100644 --- a/Telegram/SourceFiles/storage/storage_media_prepare.cpp +++ b/Telegram/SourceFiles/storage/storage_media_prepare.cpp @@ -85,6 +85,27 @@ void PrepareDetailsInParallel(PreparedList &result, int previewWidth) { } // namespace +bool ValidatePhotoEditorMediaDragData(not_null data) { + if (data->urls().size() > 1) { + return false; + } else if (data->hasImage()) { + return true; + } + + if (data->hasUrls()) { + const auto url = data->urls().front(); + if (url.isLocalFile()) { + using namespace Core; + const auto info = QFileInfo(Platform::File::UrlToLocal(url)); + const auto filename = info.fileName(); + return FileIsImage(filename, MimeTypeForFile(info).name()) + && HasExtensionFrom(filename, Ui::ExtensionsForCompression()); + } + } + + return false; +} + bool ValidateEditMediaDragData( not_null data, Ui::AlbumType albumType) { @@ -173,7 +194,6 @@ PreparedList PrepareMediaList(const QList &files, int previewWidth) { PreparedList PrepareMediaList(const QStringList &files, int previewWidth) { auto result = PreparedList(); result.files.reserve(files.size()); - const auto extensionsToCompress = Ui::ExtensionsForCompression(); for (const auto &file : files) { const auto fileinfo = QFileInfo(file); const auto filesize = fileinfo.size(); diff --git a/Telegram/SourceFiles/storage/storage_media_prepare.h b/Telegram/SourceFiles/storage/storage_media_prepare.h index 67b150660..5cdf4cb8e 100644 --- a/Telegram/SourceFiles/storage/storage_media_prepare.h +++ b/Telegram/SourceFiles/storage/storage_media_prepare.h @@ -26,24 +26,30 @@ enum class MimeDataState { Image, }; -std::optional PreparedFileFromFilesDialog( +[[nodiscard]] std::optional PreparedFileFromFilesDialog( FileDialog::OpenResult &&result, Fn checkResult, Fn)> errorCallback, int previewWidth); -MimeDataState ComputeMimeDataState(const QMimeData *data); -bool ValidateEditMediaDragData( +[[nodiscard]] MimeDataState ComputeMimeDataState(const QMimeData *data); +[[nodiscard]] bool ValidatePhotoEditorMediaDragData( + not_null data); +[[nodiscard]] bool ValidateEditMediaDragData( not_null data, Ui::AlbumType albumType); -Ui::PreparedList PrepareMediaList(const QList &files, int previewWidth); -Ui::PreparedList PrepareMediaList(const QStringList &files, int previewWidth); -Ui::PreparedList PrepareMediaFromImage( +[[nodiscard]] Ui::PreparedList PrepareMediaList( + const QList &files, + int previewWidth); +[[nodiscard]] Ui::PreparedList PrepareMediaList( + const QStringList &files, + int previewWidth); +[[nodiscard]] Ui::PreparedList PrepareMediaFromImage( QImage &&image, QByteArray &&content, int previewWidth); void PrepareDetails(Ui::PreparedFile &file, int previewWidth); void UpdateImageDetails(Ui::PreparedFile &file, int previewWidth); -bool ApplyModifications(const Ui::PreparedList &list); +[[nodiscard]] bool ApplyModifications(const Ui::PreparedList &list); } // namespace Storage