Added ability to drag and drop images in photo editor.

This commit is contained in:
23rd 2021-05-07 00:47:49 +03:00
parent a93ec9c2c2
commit 96b40f43e9
14 changed files with 246 additions and 18 deletions

View File

@ -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

View File

@ -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> stickersPanelController,
std::unique_ptr<UndoController> undoController)
std::unique_ptr<UndoController> undoController,
Fn<void(object_ptr<Ui::BoxContent>)> showBox)
: stickersPanelController(std::move(stickersPanelController))
, undoController(std::move(undoController)) {
, undoController(std::move(undoController))
, showBox(std::move(showBox)) {
}
~Controllers() {
};
const std::unique_ptr<StickersPanelController> stickersPanelController;
const std::unique_ptr<UndoController> undoController;
const Fn<void(object_ptr<Ui::BoxContent>)> showBox;
};
} // namespace Editor

View File

@ -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 <QGraphicsView>
#include <QtCore/QMimeData>
namespace Editor {
namespace {
@ -46,6 +52,7 @@ Paint::Paint(
const QSize &imageSize,
std::shared_ptr<Controllers> controllers)
: RpWidget(parent)
, _controllers(controllers)
, _lastZ(std::make_shared<float64>(9000.))
, _scene(EnsureScene(modifications, imageSize))
, _view(base::make_unique_q<QGraphicsView>(_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<InformBox>(tr::lng_edit_media_invalid_file(tr::now)));
return;
}
const auto item = std::make_shared<ItemImage>(
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<QImage>(data->imageData()));
}
}
} // namespace Editor

View File

@ -37,6 +37,8 @@ public:
void keepResult();
void updateUndoState();
void handleMimeData(const QMimeData *data);
private:
struct SavedItem {
std::shared_ptr<QGraphicsItem> item;
@ -50,6 +52,7 @@ private:
bool isItemToRemove(const std::shared_ptr<QGraphicsItem> &item) const;
bool isItemHidden(const std::shared_ptr<QGraphicsItem> &item) const;
const std::shared_ptr<Controllers> _controllers;
const std::shared_ptr<float64> _lastZ;
const std::shared_ptr<Scene> _scene;
const base::unique_qptr<QGraphicsView> _view;

View File

@ -58,7 +58,8 @@ PhotoEditor::PhotoEditor(
this,
controller->sessionController())
: nullptr,
std::make_unique<UndoController>()))
std::make_unique<UndoController>(),
[=] (object_ptr<Ui::BoxContent> c) { controller->show(std::move(c)); }))
, _content(base::make_unique_q<PhotoEditorContent>(
this,
photo,

View File

@ -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<QKeyEvent*> 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

View File

@ -34,6 +34,8 @@ public:
bool handleKeyPress(not_null<QKeyEvent*> e) const;
void setupDragArea();
private:
const QSize _photoSize;

View File

@ -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) {

View File

@ -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<float64> zoomValue,
std::shared_ptr<float64> 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<ItemBase> ItemImage::duplicate(
rpl::producer<float64> zoomValue,
std::shared_ptr<float64> zPtr,
int size,
int x,
int y) const {
auto pixmap = _pixmap;
return std::make_shared<ItemImage>(
std::move(pixmap),
std::move(zoomValue),
std::move(zPtr),
size,
x,
y);
}
} // namespace Editor

View File

@ -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<float64> zoomValue,
std::shared_ptr<float64> zPtr,
int size,
int x,
int y);
void paint(
QPainter *p,
const QStyleOptionGraphicsItem *option,
QWidget *widget) override;
protected:
void performFlip() override;
std::shared_ptr<ItemBase> duplicate(
rpl::producer<float64> zoomValue,
std::shared_ptr<float64> zPtr,
int size,
int x,
int y) const override;
private:
QPixmap _pixmap;
};
} // namespace Editor

View File

@ -51,7 +51,8 @@ DragArea::Areas DragArea::SetupDragAreaToContainer(
Fn<bool(not_null<const QMimeData*>)> &&dragEnterFilter,
Fn<void(bool)> &&setAcceptDropsField,
Fn<void()> &&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;

View File

@ -32,7 +32,8 @@ public:
Fn<bool(not_null<const QMimeData*>)> &&dragEnterFilter = nullptr,
Fn<void(bool)> &&setAcceptDropsField = nullptr,
Fn<void()> &&updateControlsGeometry = nullptr,
CallbackComputeState &&computeState = nullptr);
CallbackComputeState &&computeState = nullptr,
bool hideSubtext = false);
void setText(const QString &text, const QString &subtext);

View File

@ -85,6 +85,27 @@ void PrepareDetailsInParallel(PreparedList &result, int previewWidth) {
} // namespace
bool ValidatePhotoEditorMediaDragData(not_null<const QMimeData*> 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<const QMimeData*> data,
Ui::AlbumType albumType) {
@ -173,7 +194,6 @@ PreparedList PrepareMediaList(const QList<QUrl> &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();

View File

@ -26,24 +26,30 @@ enum class MimeDataState {
Image,
};
std::optional<Ui::PreparedList> PreparedFileFromFilesDialog(
[[nodiscard]] std::optional<Ui::PreparedList> PreparedFileFromFilesDialog(
FileDialog::OpenResult &&result,
Fn<bool(const Ui::PreparedList&)> checkResult,
Fn<void(tr::phrase<>)> errorCallback,
int previewWidth);
MimeDataState ComputeMimeDataState(const QMimeData *data);
bool ValidateEditMediaDragData(
[[nodiscard]] MimeDataState ComputeMimeDataState(const QMimeData *data);
[[nodiscard]] bool ValidatePhotoEditorMediaDragData(
not_null<const QMimeData*> data);
[[nodiscard]] bool ValidateEditMediaDragData(
not_null<const QMimeData*> data,
Ui::AlbumType albumType);
Ui::PreparedList PrepareMediaList(const QList<QUrl> &files, int previewWidth);
Ui::PreparedList PrepareMediaList(const QStringList &files, int previewWidth);
Ui::PreparedList PrepareMediaFromImage(
[[nodiscard]] Ui::PreparedList PrepareMediaList(
const QList<QUrl> &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