mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 14:17:12 +02:00
Added ability to undo and to redo paint actions in photo editor.
This commit is contained in:
parent
8eca57f419
commit
4849376347
12 changed files with 266 additions and 30 deletions
|
@ -520,6 +520,8 @@ PRIVATE
|
|||
editor/photo_editor_content.h
|
||||
editor/photo_editor_controls.cpp
|
||||
editor/photo_editor_controls.h
|
||||
editor/undo_controller.cpp
|
||||
editor/undo_controller.h
|
||||
export/export_manager.cpp
|
||||
export/export_manager.h
|
||||
export/view/export_view_content.cpp
|
||||
|
|
|
@ -17,6 +17,7 @@ photoEditorButtonIconFg: historyComposeIconFg;
|
|||
photoEditorButtonIconFgOver: historyComposeIconFgOver;
|
||||
|
||||
photoEditorButtonIconFgActive: historyComposeIconFgOver;
|
||||
photoEditorButtonIconFgInactive: menuFgDisabled;
|
||||
|
||||
photoEditorRotateButton: IconButton(historyAttach) {
|
||||
icon: icon {{ "photo_editor/rotate", photoEditorButtonIconFg }};
|
||||
|
@ -39,4 +40,7 @@ photoEditorRedoButton: IconButton(historyAttach) {
|
|||
iconOver: icon {{ "photo_editor/undo-flip_horizontal", photoEditorButtonIconFgOver }};
|
||||
}
|
||||
|
||||
photoEditorUndoButtonInactive: icon {{ "photo_editor/undo", photoEditorButtonIconFgInactive }};
|
||||
photoEditorRedoButtonInactive: icon {{ "photo_editor/undo-flip_horizontal", photoEditorButtonIconFgInactive }};
|
||||
|
||||
photoEditorTextButtonPadding: margins(10px, 0px, 10px, 0px);
|
||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "editor/editor_paint.h"
|
||||
|
||||
#include "editor/undo_controller.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
|
@ -29,7 +30,7 @@ std::shared_ptr<QGraphicsScene> EnsureScene(PhotoModifications &mods) {
|
|||
return mods.paint;
|
||||
}
|
||||
|
||||
auto FilterItems(QGraphicsItem *i) {
|
||||
auto GroupsFilter(QGraphicsItem *i) {
|
||||
return i->type() == QGraphicsItemGroup::Type;
|
||||
}
|
||||
|
||||
|
@ -38,14 +39,16 @@ auto FilterItems(QGraphicsItem *i) {
|
|||
Paint::Paint(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
PhotoModifications &modifications,
|
||||
const QSize &imageSize)
|
||||
const QSize &imageSize,
|
||||
std::shared_ptr<UndoController> undoController)
|
||||
: RpWidget(parent)
|
||||
, _scene(EnsureScene(modifications))
|
||||
, _view(base::make_unique_q<QGraphicsView>(_scene.get(), this))
|
||||
, _imageSize(imageSize)
|
||||
, _startItemsCount(itemsCount()) {
|
||||
, _imageSize(imageSize) {
|
||||
Expects(modifications.paint != nullptr);
|
||||
|
||||
keepResult();
|
||||
|
||||
_view->show();
|
||||
_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
|
@ -54,6 +57,41 @@ Paint::Paint(
|
|||
_scene->setSceneRect(0, 0, imageSize.width(), imageSize.height());
|
||||
|
||||
initDrawing();
|
||||
|
||||
// Undo / Redo.
|
||||
undoController->performRequestChanges(
|
||||
) | rpl::start_with_next([=](const Undo &command) {
|
||||
const auto isUndo = (command == Undo::Undo);
|
||||
|
||||
const auto filtered = groups(isUndo
|
||||
? Qt::DescendingOrder
|
||||
: Qt::AscendingOrder);
|
||||
|
||||
auto proj = [&](QGraphicsItem *i) {
|
||||
return isUndo ? i->isVisible() : isItemHidden(i);
|
||||
};
|
||||
const auto it = ranges::find_if(filtered, std::move(proj));
|
||||
if (it != filtered.end()) {
|
||||
(*it)->setVisible(!isUndo);
|
||||
}
|
||||
|
||||
_hasUndo = hasUndo();
|
||||
_hasRedo = hasRedo();
|
||||
}, lifetime());
|
||||
|
||||
undoController->setCanPerformChanges(rpl::merge(
|
||||
_hasUndo.value() | rpl::map([](bool enable) {
|
||||
return UndoController::EnableRequest{
|
||||
.command = Undo::Undo,
|
||||
.enable = enable,
|
||||
};
|
||||
}),
|
||||
_hasRedo.value() | rpl::map([](bool enable) {
|
||||
return UndoController::EnableRequest{
|
||||
.command = Undo::Redo,
|
||||
.enable = enable,
|
||||
};
|
||||
})));
|
||||
}
|
||||
|
||||
void Paint::applyTransform(QRect geometry, int angle, bool flipped) {
|
||||
|
@ -95,6 +133,9 @@ void Paint::initDrawing() {
|
|||
const auto &color = _brushData.color;
|
||||
const auto mousePoint = e->scenePos();
|
||||
if (isPress) {
|
||||
_hasUndo = true;
|
||||
clearRedoList();
|
||||
|
||||
auto dot = _scene->addEllipse(
|
||||
mousePoint.x() - size / 2,
|
||||
mousePoint.y() - size / 2,
|
||||
|
@ -129,33 +170,83 @@ std::shared_ptr<QGraphicsScene> Paint::saveScene() const {
|
|||
}
|
||||
|
||||
void Paint::cancel() {
|
||||
const auto items = _scene->items(Qt::AscendingOrder);
|
||||
const auto filtered = ranges::views::all(
|
||||
items
|
||||
) | ranges::views::filter(FilterItems) | ranges::to_vector;
|
||||
|
||||
const auto filtered = groups(Qt::AscendingOrder);
|
||||
if (filtered.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto i = 0; i < filtered.size(); i++) {
|
||||
const auto &item = filtered[i];
|
||||
if (i < _startItemsCount) {
|
||||
if (!item->isVisible()) {
|
||||
item->show();
|
||||
}
|
||||
for (const auto &group : filtered) {
|
||||
const auto it = ranges::find(
|
||||
_previousItems,
|
||||
group,
|
||||
&SavedItem::item);
|
||||
if (it == end(_previousItems)) {
|
||||
_scene->removeItem(group);
|
||||
} else {
|
||||
_scene->removeItem(item);
|
||||
it->item->setVisible(!it->undid);
|
||||
}
|
||||
}
|
||||
|
||||
_itemsToRemove.clear();
|
||||
}
|
||||
|
||||
void Paint::keepResult() {
|
||||
_startItemsCount = itemsCount();
|
||||
for (const auto &item : _itemsToRemove) {
|
||||
_scene->removeItem(item);
|
||||
}
|
||||
|
||||
const auto items = _scene->items();
|
||||
_previousItems = ranges::views::all(
|
||||
items
|
||||
) | ranges::views::transform([=](QGraphicsItem *i) -> SavedItem {
|
||||
return { i, !i->isVisible() };
|
||||
}) | ranges::to_vector;
|
||||
}
|
||||
|
||||
int Paint::itemsCount() const {
|
||||
return ranges::count_if(_scene->items(), FilterItems);
|
||||
bool Paint::hasUndo() const {
|
||||
return ranges::any_of(groups(), &QGraphicsItem::isVisible);
|
||||
}
|
||||
|
||||
bool Paint::hasRedo() const {
|
||||
return ranges::any_of(
|
||||
groups(),
|
||||
[=](QGraphicsItem *i) { return isItemHidden(i); });
|
||||
}
|
||||
|
||||
void Paint::clearRedoList() {
|
||||
const auto items = groups(Qt::AscendingOrder);
|
||||
auto &&filtered = ranges::views::all(
|
||||
items
|
||||
) | ranges::views::filter(
|
||||
[=](QGraphicsItem *i) { return isItemHidden(i); }
|
||||
);
|
||||
|
||||
ranges::for_each(std::move(filtered), [&](QGraphicsItem *item) {
|
||||
item->hide();
|
||||
_itemsToRemove.push_back(item);
|
||||
});
|
||||
|
||||
_hasRedo = false;
|
||||
}
|
||||
|
||||
bool Paint::isItemHidden(not_null<QGraphicsItem*> item) const {
|
||||
return !item->isVisible() && !isItemToRemove(item);
|
||||
}
|
||||
|
||||
bool Paint::isItemToRemove(not_null<QGraphicsItem*> item) const {
|
||||
return ranges::contains(_itemsToRemove, item.get());
|
||||
}
|
||||
|
||||
void Paint::updateUndoState() {
|
||||
_hasUndo = hasUndo();
|
||||
_hasRedo = hasRedo();
|
||||
}
|
||||
|
||||
std::vector<QGraphicsItem*> Paint::groups(Qt::SortOrder order) const {
|
||||
const auto items = _scene->items(order);
|
||||
return ranges::views::all(
|
||||
items
|
||||
) | ranges::views::filter(GroupsFilter) | ranges::to_vector;
|
||||
}
|
||||
|
||||
} // namespace Editor
|
||||
|
|
|
@ -16,29 +16,47 @@ class QGraphicsView;
|
|||
|
||||
namespace Editor {
|
||||
|
||||
class UndoController;
|
||||
|
||||
// Paint control.
|
||||
class Paint final : public Ui::RpWidget {
|
||||
public:
|
||||
Paint(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
PhotoModifications &modifications,
|
||||
const QSize &imageSize);
|
||||
const QSize &imageSize,
|
||||
std::shared_ptr<UndoController> undoController);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<QGraphicsScene> saveScene() const;
|
||||
|
||||
void applyTransform(QRect geometry, int angle, bool flipped);
|
||||
void cancel();
|
||||
void keepResult();
|
||||
void updateUndoState();
|
||||
|
||||
private:
|
||||
struct SavedItem {
|
||||
QGraphicsItem *item;
|
||||
bool undid = false;
|
||||
};
|
||||
|
||||
void initDrawing();
|
||||
int itemsCount() const;
|
||||
bool hasUndo() const;
|
||||
bool hasRedo() const;
|
||||
void clearRedoList();
|
||||
|
||||
bool isItemToRemove(not_null<QGraphicsItem*> item) const;
|
||||
bool isItemHidden(not_null<QGraphicsItem*> item) const;
|
||||
|
||||
std::vector<QGraphicsItem*> groups(
|
||||
Qt::SortOrder order = Qt::DescendingOrder) const;
|
||||
|
||||
const std::shared_ptr<QGraphicsScene> _scene;
|
||||
const base::unique_qptr<QGraphicsView> _view;
|
||||
const QSize _imageSize;
|
||||
|
||||
int _startItemsCount = 0;
|
||||
std::vector<SavedItem> _previousItems;
|
||||
QList<QGraphicsItem*> _itemsToRemove;
|
||||
|
||||
struct {
|
||||
QPointF lastPoint;
|
||||
|
@ -48,6 +66,9 @@ private:
|
|||
QGraphicsItemGroup *group;
|
||||
} _brushData;
|
||||
|
||||
rpl::variable<bool> _hasUndo = true;
|
||||
rpl::variable<bool> _hasRedo = true;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Editor
|
||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "editor/photo_editor_content.h"
|
||||
#include "editor/photo_editor_controls.h"
|
||||
#include "editor/undo_controller.h"
|
||||
#include "styles/style_editor.h"
|
||||
|
||||
namespace Editor {
|
||||
|
@ -19,11 +20,13 @@ PhotoEditor::PhotoEditor(
|
|||
PhotoModifications modifications)
|
||||
: RpWidget(parent)
|
||||
, _modifications(std::move(modifications))
|
||||
, _undoController(std::make_shared<UndoController>())
|
||||
, _content(base::make_unique_q<PhotoEditorContent>(
|
||||
this,
|
||||
photo,
|
||||
_modifications))
|
||||
, _controls(base::make_unique_q<PhotoEditorControls>(this)) {
|
||||
_modifications,
|
||||
_undoController))
|
||||
, _controls(base::make_unique_q<PhotoEditorControls>(this, _undoController)) {
|
||||
sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
const auto geometry = QRect(QPoint(), size);
|
||||
|
|
|
@ -17,6 +17,7 @@ namespace Editor {
|
|||
|
||||
class PhotoEditorContent;
|
||||
class PhotoEditorControls;
|
||||
class UndoController;
|
||||
|
||||
class PhotoEditor final : public Ui::RpWidget {
|
||||
public:
|
||||
|
@ -32,6 +33,8 @@ private:
|
|||
|
||||
PhotoModifications _modifications;
|
||||
|
||||
const std::shared_ptr<UndoController> _undoController;
|
||||
|
||||
base::unique_qptr<PhotoEditorContent> _content;
|
||||
base::unique_qptr<PhotoEditorControls> _controls;
|
||||
|
||||
|
|
|
@ -19,9 +19,14 @@ using Media::View::RotatedRect;
|
|||
PhotoEditorContent::PhotoEditorContent(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
std::shared_ptr<QPixmap> photo,
|
||||
PhotoModifications modifications)
|
||||
PhotoModifications modifications,
|
||||
std::shared_ptr<UndoController> undoController)
|
||||
: RpWidget(parent)
|
||||
, _paint(base::make_unique_q<Paint>(this, modifications, photo->size()))
|
||||
, _paint(base::make_unique_q<Paint>(
|
||||
this,
|
||||
modifications,
|
||||
photo->size(),
|
||||
std::move(undoController)))
|
||||
, _crop(base::make_unique_q<Crop>(this, modifications, photo->size()))
|
||||
, _photo(photo)
|
||||
, _modifications(modifications) {
|
||||
|
@ -88,9 +93,7 @@ void PhotoEditorContent::applyModifications(
|
|||
|
||||
void PhotoEditorContent::save(PhotoModifications &modifications) {
|
||||
modifications.crop = _crop->saveCropRect(_imageRect, _photo->rect());
|
||||
if (!modifications.paint) {
|
||||
modifications.paint = _paint->saveScene();
|
||||
}
|
||||
_paint->keepResult();
|
||||
}
|
||||
|
||||
void PhotoEditorContent::applyMode(const PhotoEditorMode &mode) {
|
||||
|
@ -98,6 +101,9 @@ void PhotoEditorContent::applyMode(const PhotoEditorMode &mode) {
|
|||
_crop->setVisible(isTransform);
|
||||
|
||||
_paint->setAttribute(Qt::WA_TransparentForMouseEvents, isTransform);
|
||||
if (!isTransform) {
|
||||
_paint->updateUndoState();
|
||||
}
|
||||
|
||||
if (mode.action == PhotoEditorMode::Action::Discard) {
|
||||
_paint->cancel();
|
||||
|
|
|
@ -15,13 +15,15 @@ namespace Editor {
|
|||
|
||||
class Crop;
|
||||
class Paint;
|
||||
class UndoController;
|
||||
|
||||
class PhotoEditorContent final : public Ui::RpWidget {
|
||||
public:
|
||||
PhotoEditorContent(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
std::shared_ptr<QPixmap> photo,
|
||||
PhotoModifications modifications);
|
||||
PhotoModifications modifications,
|
||||
std::shared_ptr<UndoController> undoController);
|
||||
|
||||
void applyModifications(PhotoModifications modifications);
|
||||
void applyMode(const PhotoEditorMode &mode);
|
||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "editor/photo_editor_controls.h"
|
||||
|
||||
#include "editor/undo_controller.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/image/image_prepare.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
|
@ -133,6 +134,7 @@ void HorizontalContainer::updateChildrenPosition() {
|
|||
|
||||
PhotoEditorControls::PhotoEditorControls(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
std::shared_ptr<UndoController> undoController,
|
||||
bool doneControls)
|
||||
: RpWidget(parent)
|
||||
, _bg(st::mediaviewSaveMsgBg)
|
||||
|
@ -213,6 +215,26 @@ PhotoEditorControls::PhotoEditorControls(
|
|||
|
||||
}, lifetime());
|
||||
|
||||
undoController->setPerformRequestChanges(rpl::merge(
|
||||
_undoButton->clicks() | rpl::map_to(Undo::Undo),
|
||||
_redoButton->clicks() | rpl::map_to(Undo::Redo)));
|
||||
|
||||
undoController->canPerformChanges(
|
||||
) | rpl::start_with_next([=](const UndoController::EnableRequest &r) {
|
||||
const auto isUndo = (r.command == Undo::Undo);
|
||||
const auto &button = isUndo ? _undoButton : _redoButton;
|
||||
button->setAttribute(Qt::WA_TransparentForMouseEvents, !r.enable);
|
||||
if (!r.enable) {
|
||||
button->clearState();
|
||||
}
|
||||
|
||||
button->setIconOverride(r.enable
|
||||
? nullptr
|
||||
: isUndo
|
||||
? &st::photoEditorUndoButtonInactive
|
||||
: &st::photoEditorRedoButtonInactive);
|
||||
}, lifetime());
|
||||
|
||||
}
|
||||
|
||||
rpl::producer<int> PhotoEditorControls::rotateRequests() const {
|
||||
|
|
|
@ -19,11 +19,13 @@ namespace Editor {
|
|||
|
||||
class EdgeButton;
|
||||
class HorizontalContainer;
|
||||
class UndoController;
|
||||
|
||||
class PhotoEditorControls final : public Ui::RpWidget {
|
||||
public:
|
||||
PhotoEditorControls(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
std::shared_ptr<UndoController> undoController,
|
||||
bool doneControls = true);
|
||||
|
||||
[[nodiscard]] rpl::producer<int> rotateRequests() const;
|
||||
|
|
39
Telegram/SourceFiles/editor/undo_controller.cpp
Normal file
39
Telegram/SourceFiles/editor/undo_controller.cpp
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
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/undo_controller.h"
|
||||
|
||||
namespace Editor {
|
||||
namespace {
|
||||
using EnableRequest = UndoController::EnableRequest;
|
||||
} // namespace
|
||||
|
||||
UndoController::UndoController() {
|
||||
}
|
||||
|
||||
void UndoController::setCanPerformChanges(
|
||||
rpl::producer<EnableRequest> &&command) {
|
||||
std::move(
|
||||
command
|
||||
) | rpl::start_to_stream(_enable, _lifetime);
|
||||
}
|
||||
|
||||
void UndoController::setPerformRequestChanges(rpl::producer<Undo> &&command) {
|
||||
std::move(
|
||||
command
|
||||
) | rpl::start_to_stream(_perform, _lifetime);
|
||||
}
|
||||
|
||||
rpl::producer<EnableRequest> UndoController::canPerformChanges() const {
|
||||
return _enable.events();
|
||||
}
|
||||
|
||||
rpl::producer<Undo> UndoController::performRequestChanges() const {
|
||||
return _perform.events();
|
||||
}
|
||||
|
||||
} // namespace Editor
|
41
Telegram/SourceFiles/editor/undo_controller.h
Normal file
41
Telegram/SourceFiles/editor/undo_controller.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
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
|
||||
|
||||
namespace Editor {
|
||||
|
||||
enum class Undo {
|
||||
Undo,
|
||||
Redo,
|
||||
};
|
||||
|
||||
class UndoController final {
|
||||
public:
|
||||
struct EnableRequest {
|
||||
Undo command = Undo::Undo;
|
||||
bool enable = true;
|
||||
};
|
||||
|
||||
UndoController();
|
||||
|
||||
void setCanPerformChanges(rpl::producer<EnableRequest> &&command);
|
||||
void setPerformRequestChanges(rpl::producer<Undo> &&command);
|
||||
|
||||
[[nodiscard]] rpl::producer<EnableRequest> canPerformChanges() const;
|
||||
[[nodiscard]] rpl::producer<Undo> performRequestChanges() const;
|
||||
|
||||
private:
|
||||
|
||||
rpl::event_stream<Undo> _perform;
|
||||
rpl::event_stream<EnableRequest> _enable;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Editor
|
Loading…
Add table
Reference in a new issue