init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
456
Telegram/SourceFiles/editor/scene/scene_item_base.cpp
Normal file
456
Telegram/SourceFiles/editor/scene/scene_item_base.cpp
Normal file
@@ -0,0 +1,456 @@
|
||||
/*
|
||||
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_base.h"
|
||||
|
||||
#include "editor/scene/scene.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/painter.h"
|
||||
#include "styles/style_editor.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
#include <QGraphicsScene>
|
||||
#include <QGraphicsSceneHoverEvent>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QStyleOptionGraphicsItem>
|
||||
#include <QtMath>
|
||||
|
||||
namespace Editor {
|
||||
namespace {
|
||||
|
||||
constexpr auto kSnapAngle = 45.;
|
||||
|
||||
const auto kDuplicateSequence = QKeySequence("ctrl+d");
|
||||
const auto kFlipSequence = QKeySequence("ctrl+s");
|
||||
const auto kDeleteSequence = QKeySequence("delete");
|
||||
|
||||
constexpr auto kMinSizeRatio = 0.05;
|
||||
constexpr auto kMaxSizeRatio = 1.00;
|
||||
|
||||
auto Normalized(float64 angle) {
|
||||
return angle
|
||||
+ ((std::abs(angle) < 360) ? 0 : (-360 * (angle < 0 ? -1 : 1)));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int NumberedItem::type() const {
|
||||
return NumberedItem::Type;
|
||||
}
|
||||
|
||||
int NumberedItem::number() const {
|
||||
return _number;
|
||||
}
|
||||
|
||||
void NumberedItem::setNumber(int number) {
|
||||
_number = number;
|
||||
}
|
||||
|
||||
NumberedItem::Status NumberedItem::status() const {
|
||||
return _status;
|
||||
}
|
||||
|
||||
bool NumberedItem::isNormalStatus() const {
|
||||
return _status == Status::Normal;
|
||||
}
|
||||
|
||||
bool NumberedItem::isUndidStatus() const {
|
||||
return _status == Status::Undid;
|
||||
}
|
||||
|
||||
bool NumberedItem::isRemovedStatus() const {
|
||||
return _status == Status::Removed;
|
||||
}
|
||||
|
||||
void NumberedItem::save(SaveState state) {
|
||||
}
|
||||
|
||||
void NumberedItem::restore(SaveState state) {
|
||||
}
|
||||
|
||||
bool NumberedItem::hasState(SaveState state) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void NumberedItem::setStatus(Status status) {
|
||||
if (status != _status) {
|
||||
_status = status;
|
||||
setVisible(status == Status::Normal);
|
||||
}
|
||||
}
|
||||
|
||||
ItemBase::ItemBase(Data data)
|
||||
: _lastZ(data.zPtr)
|
||||
, _imageSize(data.imageSize)
|
||||
, _horizontalSize(data.size) {
|
||||
setFlags(QGraphicsItem::ItemIsMovable
|
||||
| QGraphicsItem::ItemIsSelectable
|
||||
| QGraphicsItem::ItemIsFocusable);
|
||||
setAcceptHoverEvents(true);
|
||||
applyData(data);
|
||||
}
|
||||
|
||||
QRectF ItemBase::boundingRect() const {
|
||||
return innerRect() + _scaledInnerMargins;
|
||||
}
|
||||
|
||||
QRectF ItemBase::contentRect() const {
|
||||
return innerRect() - _scaledInnerMargins;
|
||||
}
|
||||
|
||||
QRectF ItemBase::innerRect() const {
|
||||
const auto &hSize = _horizontalSize;
|
||||
const auto &vSize = _verticalSize;
|
||||
return QRectF(-hSize / 2, -vSize / 2, hSize, vSize);
|
||||
}
|
||||
|
||||
void ItemBase::paint(
|
||||
QPainter *p,
|
||||
const QStyleOptionGraphicsItem *option,
|
||||
QWidget *) {
|
||||
if (!(option->state & QStyle::State_Selected)) {
|
||||
return;
|
||||
}
|
||||
PainterHighQualityEnabler hq(*p);
|
||||
const auto hasFocus = (option->state & QStyle::State_HasFocus);
|
||||
p->setPen(hasFocus ? _pens.select : _pens.selectInactive);
|
||||
p->drawRect(innerRect());
|
||||
|
||||
p->setPen(hasFocus ? _pens.handle : _pens.handleInactive);
|
||||
p->setBrush(st::photoEditorItemBaseHandleFg);
|
||||
p->drawEllipse(rightHandleRect());
|
||||
p->drawEllipse(leftHandleRect());
|
||||
}
|
||||
|
||||
void ItemBase::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
|
||||
if (isHandling()) {
|
||||
const auto mousePos = event->pos();
|
||||
const auto shift = event->modifiers().testFlag(Qt::ShiftModifier);
|
||||
const auto isLeft = (_handle == HandleType::Left);
|
||||
if (!shift) {
|
||||
// Resize.
|
||||
const auto p = isLeft ? (mousePos * -1) : mousePos;
|
||||
const auto dx = int(2.0 * p.x());
|
||||
const auto dy = int(2.0 * p.y());
|
||||
prepareGeometryChange();
|
||||
_horizontalSize = std::clamp(
|
||||
(dx > dy ? dx : dy),
|
||||
_sizeLimits.min,
|
||||
_sizeLimits.max);
|
||||
updateVerticalSize();
|
||||
}
|
||||
|
||||
// Rotate.
|
||||
const auto origin = mapToScene(boundingRect().center());
|
||||
const auto pos = mapToScene(mousePos);
|
||||
|
||||
const auto diff = pos - origin;
|
||||
const auto angle = Normalized((isLeft ? 180 : 0)
|
||||
+ (std::atan2(diff.y(), diff.x()) * 180 / M_PI));
|
||||
setRotation(shift
|
||||
? (base::SafeRound(angle / kSnapAngle) * kSnapAngle)
|
||||
: angle);
|
||||
} else {
|
||||
QGraphicsItem::mouseMoveEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void ItemBase::hoverMoveEvent(QGraphicsSceneHoverEvent *event) {
|
||||
setCursor(isHandling()
|
||||
? Qt::ClosedHandCursor
|
||||
: (handleType(event->pos()) != HandleType::None) && isSelected()
|
||||
? Qt::OpenHandCursor
|
||||
: Qt::ArrowCursor);
|
||||
QGraphicsItem::hoverMoveEvent(event);
|
||||
}
|
||||
|
||||
void ItemBase::mousePressEvent(QGraphicsSceneMouseEvent *event) {
|
||||
setZValue((*_lastZ)++);
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
_handle = handleType(event->pos());
|
||||
}
|
||||
if (isHandling()) {
|
||||
setCursor(Qt::ClosedHandCursor);
|
||||
} else {
|
||||
QGraphicsItem::mousePressEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void ItemBase::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
|
||||
if ((event->button() == Qt::LeftButton) && isHandling()) {
|
||||
_handle = HandleType::None;
|
||||
} else {
|
||||
QGraphicsItem::mouseReleaseEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void ItemBase::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) {
|
||||
if (scene()) {
|
||||
scene()->clearSelection();
|
||||
setSelected(true);
|
||||
}
|
||||
|
||||
const auto add = [&](
|
||||
auto base,
|
||||
const QKeySequence &sequence,
|
||||
Fn<void()> callback,
|
||||
const style::icon *icon) {
|
||||
// TODO: refactor.
|
||||
const auto sequenceText = QChar('\t')
|
||||
+ sequence.toString(QKeySequence::NativeText);
|
||||
_menu->addAction(
|
||||
base(tr::now) + sequenceText,
|
||||
std::move(callback),
|
||||
icon);
|
||||
};
|
||||
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
nullptr,
|
||||
st::popupMenuWithIcons);
|
||||
add(
|
||||
tr::lng_photo_editor_menu_delete,
|
||||
kDeleteSequence,
|
||||
[=] { actionDelete(); },
|
||||
&st::menuIconDelete);
|
||||
add(
|
||||
tr::lng_photo_editor_menu_flip,
|
||||
kFlipSequence,
|
||||
[=] { actionFlip(); },
|
||||
&st::menuIconFlip);
|
||||
add(
|
||||
tr::lng_photo_editor_menu_duplicate,
|
||||
kDuplicateSequence,
|
||||
[=] { actionDuplicate(); },
|
||||
&st::menuIconCopy);
|
||||
|
||||
_menu->popup(event->screenPos());
|
||||
}
|
||||
|
||||
void ItemBase::performForSelectedItems(Action action) {
|
||||
if (const auto s = scene()) {
|
||||
for (const auto item : s->selectedItems()) {
|
||||
if (const auto base = static_cast<ItemBase*>(item)) {
|
||||
(base->*action)();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ItemBase::actionFlip() {
|
||||
setFlip(!flipped());
|
||||
}
|
||||
|
||||
void ItemBase::actionDelete() {
|
||||
if (const auto s = static_cast<Scene*>(scene())) {
|
||||
s->removeItem(this);
|
||||
}
|
||||
}
|
||||
|
||||
void ItemBase::actionDuplicate() {
|
||||
if (const auto s = static_cast<Scene*>(scene())) {
|
||||
auto data = generateData();
|
||||
data.x += int(_horizontalSize / 3);
|
||||
data.y += int(_verticalSize / 3);
|
||||
const auto newItem = duplicate(std::move(data));
|
||||
if (hasFocus()) {
|
||||
newItem->setFocus();
|
||||
}
|
||||
const auto selected = isSelected();
|
||||
newItem->setSelected(selected);
|
||||
setSelected(false);
|
||||
s->addItem(newItem);
|
||||
}
|
||||
}
|
||||
|
||||
void ItemBase::keyPressEvent(QKeyEvent *e) {
|
||||
if (e->key() == Qt::Key_Escape) {
|
||||
if (const auto s = scene()) {
|
||||
s->clearSelection();
|
||||
s->clearFocus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
handleActionKey(e);
|
||||
}
|
||||
|
||||
void ItemBase::handleActionKey(not_null<QKeyEvent*> e) {
|
||||
const auto matches = [&](const QKeySequence &sequence) {
|
||||
const auto searchKey = (e->modifiers() | e->key())
|
||||
& ~(Qt::KeypadModifier | Qt::GroupSwitchModifier);
|
||||
const auto events = QKeySequence(searchKey);
|
||||
return sequence.matches(events) == QKeySequence::ExactMatch;
|
||||
};
|
||||
if (matches(kDuplicateSequence)) {
|
||||
performForSelectedItems(&ItemBase::actionDuplicate);
|
||||
} else if (matches(kDeleteSequence)) {
|
||||
performForSelectedItems(&ItemBase::actionDelete);
|
||||
} else if (matches(kFlipSequence)) {
|
||||
performForSelectedItems(&ItemBase::actionFlip);
|
||||
}
|
||||
}
|
||||
|
||||
QRectF ItemBase::rightHandleRect() const {
|
||||
return QRectF(
|
||||
(_horizontalSize / 2) - (_scaledHandleSize / 2),
|
||||
0 - (_scaledHandleSize / 2),
|
||||
_scaledHandleSize,
|
||||
_scaledHandleSize);
|
||||
}
|
||||
|
||||
QRectF ItemBase::leftHandleRect() const {
|
||||
return QRectF(
|
||||
(-_horizontalSize / 2) - (_scaledHandleSize / 2),
|
||||
0 - (_scaledHandleSize / 2),
|
||||
_scaledHandleSize,
|
||||
_scaledHandleSize);
|
||||
}
|
||||
|
||||
bool ItemBase::isHandling() const {
|
||||
return _handle != HandleType::None;
|
||||
}
|
||||
|
||||
float64 ItemBase::size() const {
|
||||
return _horizontalSize;
|
||||
}
|
||||
|
||||
void ItemBase::updateVerticalSize() {
|
||||
const auto verticalSize = _horizontalSize * _aspectRatio;
|
||||
_verticalSize = std::max(
|
||||
verticalSize,
|
||||
float64(_sizeLimits.min));
|
||||
if (verticalSize < _sizeLimits.min) {
|
||||
_horizontalSize = _verticalSize / _aspectRatio;
|
||||
}
|
||||
}
|
||||
|
||||
void ItemBase::setAspectRatio(float64 aspectRatio) {
|
||||
_aspectRatio = aspectRatio;
|
||||
updateVerticalSize();
|
||||
}
|
||||
|
||||
ItemBase::HandleType ItemBase::handleType(const QPointF &pos) const {
|
||||
return rightHandleRect().contains(pos)
|
||||
? HandleType::Right
|
||||
: leftHandleRect().contains(pos)
|
||||
? HandleType::Left
|
||||
: HandleType::None;
|
||||
}
|
||||
|
||||
bool ItemBase::flipped() const {
|
||||
return _flipped;
|
||||
}
|
||||
|
||||
void ItemBase::setFlip(bool value) {
|
||||
if (_flipped != value) {
|
||||
performFlip();
|
||||
_flipped = value;
|
||||
}
|
||||
}
|
||||
|
||||
int ItemBase::type() const {
|
||||
return ItemBase::Type;
|
||||
}
|
||||
|
||||
void ItemBase::updateZoom(float64 zoom) {
|
||||
_scaledHandleSize = st::photoEditorItemHandleSize / zoom;
|
||||
_scaledInnerMargins = QMarginsF(
|
||||
_scaledHandleSize,
|
||||
_scaledHandleSize,
|
||||
_scaledHandleSize,
|
||||
_scaledHandleSize) * 0.5;
|
||||
|
||||
const auto maxSide = std::max(
|
||||
_imageSize.width(),
|
||||
_imageSize.height());
|
||||
_sizeLimits = {
|
||||
.min = int(maxSide * kMinSizeRatio),
|
||||
.max = int(maxSide * kMaxSizeRatio),
|
||||
};
|
||||
_horizontalSize = std::clamp(
|
||||
_horizontalSize,
|
||||
float64(_sizeLimits.min),
|
||||
float64(_sizeLimits.max));
|
||||
updateVerticalSize();
|
||||
|
||||
updatePens(QPen(
|
||||
QBrush(),
|
||||
1 / zoom,
|
||||
Qt::DashLine,
|
||||
Qt::SquareCap,
|
||||
Qt::RoundJoin));
|
||||
}
|
||||
|
||||
void ItemBase::performFlip() {
|
||||
}
|
||||
|
||||
void ItemBase::updatePens(QPen pen) {
|
||||
_pens = {
|
||||
.select = pen,
|
||||
.selectInactive = pen,
|
||||
.handle = pen,
|
||||
.handleInactive = pen,
|
||||
};
|
||||
_pens.select.setColor(Qt::white);
|
||||
_pens.selectInactive.setColor(Qt::gray);
|
||||
_pens.handle.setColor(Qt::white);
|
||||
_pens.handleInactive.setColor(Qt::gray);
|
||||
_pens.handle.setStyle(Qt::SolidLine);
|
||||
_pens.handleInactive.setStyle(Qt::SolidLine);
|
||||
}
|
||||
|
||||
ItemBase::Data ItemBase::generateData() const {
|
||||
return {
|
||||
.initialZoom = (st::photoEditorItemHandleSize / _scaledHandleSize),
|
||||
.zPtr = _lastZ,
|
||||
.size = int(_horizontalSize),
|
||||
.x = int(scenePos().x()),
|
||||
.y = int(scenePos().y()),
|
||||
.flipped = flipped(),
|
||||
.rotation = int(rotation()),
|
||||
.imageSize = _imageSize,
|
||||
};
|
||||
}
|
||||
|
||||
void ItemBase::applyData(const Data &data) {
|
||||
// _lastZ is const.
|
||||
// _imageSize is const.
|
||||
_horizontalSize = data.size;
|
||||
setPos(data.x, data.y);
|
||||
setZValue((*_lastZ)++);
|
||||
setFlip(data.flipped);
|
||||
setRotation(data.rotation);
|
||||
updateZoom(data.initialZoom);
|
||||
update();
|
||||
}
|
||||
|
||||
void ItemBase::save(SaveState state) {
|
||||
const auto z = zValue();
|
||||
auto &saved = (state == SaveState::Keep) ? _keeped : _saved;
|
||||
saved = {
|
||||
.data = generateData(),
|
||||
.zValue = z,
|
||||
.status = status(),
|
||||
};
|
||||
}
|
||||
|
||||
void ItemBase::restore(SaveState state) {
|
||||
if (!hasState(state)) {
|
||||
return;
|
||||
}
|
||||
const auto &saved = (state == SaveState::Keep) ? _keeped : _saved;
|
||||
applyData(saved.data);
|
||||
setZValue(saved.zValue);
|
||||
setStatus(saved.status);
|
||||
}
|
||||
|
||||
bool ItemBase::hasState(SaveState state) const {
|
||||
const auto &saved = (state == SaveState::Keep) ? _keeped : _saved;
|
||||
return saved.zValue;
|
||||
}
|
||||
|
||||
} // namespace Editor
|
||||
Reference in New Issue
Block a user