diff --git a/Telegram/SourceFiles/editor/editor_crop.cpp b/Telegram/SourceFiles/editor/editor_crop.cpp index fd0d0de107..4c1932542e 100644 --- a/Telegram/SourceFiles/editor/editor_crop.cpp +++ b/Telegram/SourceFiles/editor/editor_crop.cpp @@ -19,6 +19,10 @@ constexpr auto kETL = Qt::TopEdge | Qt::LeftEdge; constexpr auto kETR = Qt::TopEdge | Qt::RightEdge; constexpr auto kEBL = Qt::BottomEdge | Qt::LeftEdge; constexpr auto kEBR = Qt::BottomEdge | Qt::RightEdge; +constexpr auto kEL = Qt::Edges(Qt::LeftEdge); +constexpr auto kER = Qt::Edges(Qt::RightEdge); +constexpr auto kET = Qt::Edges(Qt::TopEdge); +constexpr auto kEB = Qt::Edges(Qt::BottomEdge); constexpr auto kEAll = Qt::TopEdge | Qt::LeftEdge | Qt::BottomEdge @@ -34,6 +38,10 @@ QPoint PointOfEdge(Qt::Edges e, const QRectF &r) { case kETR: return QPoint(r.x() + r.width(), r.y()); case kEBL: return QPoint(r.x(), r.y() + r.height()); case kEBR: return QPoint(r.x() + r.width(), r.y() + r.height()); + case kEL: return QPoint(r.x(), r.y() + (r.height() / 2.)); + case kER: return QPoint(r.x() + r.width(), r.y() + (r.height() / 2.)); + case kET: return QPoint(r.x() + (r.width() / 2.), r.y()); + case kEB: return QPoint(r.x() + (r.width() / 2.), r.y() + r.height()); default: return QPoint(); } } @@ -83,7 +91,12 @@ Crop::Crop( auto p = QPainter(this); p.fillPath(_painterPath, st::photoCropFadeBg); - paintPoints(p); + paintFrame(p); + const auto gridOpacity = _gridOpacityAnimation.value( + _gridVisible ? 1. : 0.); + if (gridOpacity > 0.) { + paintGrid(p, gridOpacity); + } }, lifetime()); } @@ -140,12 +153,103 @@ void Crop::applyTransform( } } -void Crop::paintPoints(QPainter &p) { +QPainterPath Crop::cropPath() const { + auto result = QPainterPath(); + if (_data.cropType == EditorData::CropType::Ellipse) { + result.addEllipse(_cropPaint); + } else if (_data.cropType == EditorData::CropType::RoundedRect) { + const auto radius = std::min(_cropPaint.width(), _cropPaint.height()) + * Ui::ForumUserpicRadiusMultiplier(); + result.addRoundedRect(_cropPaint, radius, radius); + } else { + result.addRect(_cropPaint); + } + return result; +} + +void Crop::paintFrame(QPainter &p) { + const auto framePath = cropPath(); + auto frameStroker = QPainterPathStroker(); + frameStroker.setWidth(st::lineWidth * 2); + frameStroker.setJoinStyle(Qt::MiterJoin); + frameStroker.setCapStyle(Qt::SquareCap); + auto frameShape = frameStroker.createStroke(framePath); p.save(); - p.setPen(Qt::NoPen); - p.setBrush(st::photoCropPointFg); - for (const auto &r : ranges::views::values(_edges)) { - p.drawRect(r); + 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.); + if (cornerLength > 0.) { + auto cornerLines = QPainterPath(); + cornerLines.moveTo(_cropPaint.left(), _cropPaint.top()); + cornerLines.lineTo( + _cropPaint.left() + cornerLength, + _cropPaint.top()); + cornerLines.moveTo(_cropPaint.left(), _cropPaint.top()); + cornerLines.lineTo( + _cropPaint.left(), + _cropPaint.top() + cornerLength); + cornerLines.moveTo(_cropPaint.right(), _cropPaint.top()); + cornerLines.lineTo( + _cropPaint.right() - cornerLength, + _cropPaint.top()); + cornerLines.moveTo(_cropPaint.right(), _cropPaint.top()); + cornerLines.lineTo( + _cropPaint.right(), + _cropPaint.top() + cornerLength); + cornerLines.moveTo(_cropPaint.left(), _cropPaint.bottom()); + cornerLines.lineTo( + _cropPaint.left() + cornerLength, + _cropPaint.bottom()); + cornerLines.moveTo(_cropPaint.left(), _cropPaint.bottom()); + cornerLines.lineTo( + _cropPaint.left(), + _cropPaint.bottom() - cornerLength); + cornerLines.moveTo(_cropPaint.right(), _cropPaint.bottom()); + cornerLines.lineTo( + _cropPaint.right() - cornerLength, + _cropPaint.bottom()); + cornerLines.moveTo(_cropPaint.right(), _cropPaint.bottom()); + cornerLines.lineTo( + _cropPaint.right(), + _cropPaint.bottom() - cornerLength); + + auto cornerStroker = QPainterPathStroker(); + cornerStroker.setWidth(st::lineWidth * 4); + cornerStroker.setJoinStyle(Qt::MiterJoin); + cornerStroker.setCapStyle(Qt::SquareCap); + auto cornerColor = st::photoCropPointFg->c; + cornerColor.setAlpha(255); + p.fillPath(cornerStroker.createStroke(cornerLines), cornerColor); + } + } + p.restore(); +} + +void Crop::paintGrid(QPainter &p, float64 opacity) { + if ((_cropPaint.width() <= 0.) || (_cropPaint.height() <= 0.)) { + return; + } + p.save(); + p.setOpacity(opacity); + p.setRenderHint(QPainter::Antialiasing, true); + p.setClipPath(cropPath()); + auto pen = QPen(st::photoCropPointFg, st::lineWidth); + pen.setCapStyle(Qt::FlatCap); + p.setPen(pen); + const auto horizontalStep = _cropPaint.width() / 3.; + const auto verticalStep = _cropPaint.height() / 3.; + for (auto i = 1; i < 3; ++i) { + const auto x = _cropPaint.left() + (horizontalStep * i); + const auto y = _cropPaint.top() + (verticalStep * i); + p.drawLine( + QPointF(x, _cropPaint.top()), + QPointF(x, _cropPaint.bottom())); + p.drawLine( + QPointF(_cropPaint.left(), y), + QPointF(_cropPaint.right(), y)); } p.restore(); } @@ -157,15 +261,7 @@ void Crop::setCropPaint(QRectF &&rect) { _painterPath.clear(); _painterPath.addRect(_innerRect); - if (_data.cropType == EditorData::CropType::Ellipse) { - _painterPath.addEllipse(_cropPaint); - } else if (_data.cropType == EditorData::CropType::RoundedRect) { - const auto radius = std::min(_cropPaint.width(), _cropPaint.height()) - * Ui::ForumUserpicRadiusMultiplier(); - _painterPath.addRoundedRect(_cropPaint, radius, radius); - } else { - _painterPath.addRect(_cropPaint); - } + _painterPath.addPath(cropPath()); } void Crop::convertCropPaintToOriginal() { @@ -193,15 +289,46 @@ void Crop::updateEdges() { const auto &s = _pointSize; const auto &m = _edgePointMargins; const auto &r = _cropPaint; + const auto sideWidth = std::max(0., r.width() - s); + const auto sideHeight = std::max(0., r.height() - s); for (const auto &e : { kETL, kETR, kEBL, kEBR }) { _edges[e] = QRectF(PointOfEdge(e, r), QSize(s, s)) + m; } + if (!_keepAspectRatio) { + _edges[kEL] = QRectF( + r.left() - _pointSizeH, + r.top() + _pointSizeH, + s, + sideHeight); + _edges[kER] = QRectF( + r.right() - _pointSizeH, + r.top() + _pointSizeH, + s, + sideHeight); + _edges[kET] = QRectF( + r.left() + _pointSizeH, + r.top() - _pointSizeH, + sideWidth, + s); + _edges[kEB] = QRectF( + r.left() + _pointSizeH, + r.bottom() - _pointSizeH, + sideWidth, + s); + } else { + _edges.erase(kEL); + _edges.erase(kER); + _edges.erase(kET); + _edges.erase(kEB); + } } Qt::Edges Crop::mouseState(const QPoint &p) { - for (const auto &[e, r] : _edges) { - if (r.contains(p)) { - return e; + for (const auto &e : { kETL, kETR, kEBL, kEBR, kEL, kER, kET, kEB }) { + if (const auto i = _edges.find(e); i != end(_edges)) { + if (i->second.contains(p)) { + return e; + } } } if (_cropPaint.contains(p)) { @@ -212,9 +339,16 @@ Qt::Edges Crop::mouseState(const QPoint &p) { void Crop::mousePressEvent(QMouseEvent *e) { computeDownState(e->pos()); + if (_down.edge) { + setGridVisible(true, false); + } } void Crop::mouseReleaseEvent(QMouseEvent *e) { + const auto hadEdge = bool(_down.edge); + if (hadEdge) { + setGridVisible(false, true); + } clearDownState(); convertCropPaintToOriginal(); } @@ -263,6 +397,24 @@ void Crop::clearDownState() { _down = InfoAtDown(); } +void Crop::setGridVisible(bool visible, bool animated) { + if ((_gridVisible == visible) && !_gridOpacityAnimation.animating()) { + return; + } + const auto from = _gridOpacityAnimation.value(_gridVisible ? 1. : 0.); + const auto to = visible ? 1. : 0.; + _gridVisible = visible; + _gridOpacityAnimation.stop(); + if (animated) { + _gridOpacityAnimation.start( + [=] { update(); }, + from, + to, + st::photoEditorBarAnimationDuration * std::abs(to - from)); + } + update(); +} + void Crop::performCrop(const QPoint &pos) { const auto &crop = _down.rect; const auto &pressedEdge = _down.edge; @@ -338,6 +490,10 @@ void Crop::mouseMoveEvent(QMouseEvent *e) { ? style::cur_sizefdiag : ((edge == kETR) || (edge == kEBL)) ? style::cur_sizebdiag + : ((edge == kEL) || (edge == kER)) + ? style::cur_sizehor + : ((edge == kET) || (edge == kEB)) + ? style::cur_sizever : (edge == kEAll) ? style::cur_sizeall : style::cur_default; diff --git a/Telegram/SourceFiles/editor/editor_crop.h b/Telegram/SourceFiles/editor/editor_crop.h index 002bfcd4ec..8f73d0d6f4 100644 --- a/Telegram/SourceFiles/editor/editor_crop.h +++ b/Telegram/SourceFiles/editor/editor_crop.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "ui/rp_widget.h" +#include "ui/effects/animations.h" #include "base/flat_map.h" #include "editor/photo_editor_common.h" @@ -51,7 +52,10 @@ private: } borders; }; - void paintPoints(QPainter &p); + void paintFrame(QPainter &p); + void paintGrid(QPainter &p, float64 opacity); + void setGridVisible(bool visible, bool animated); + [[nodiscard]] QPainterPath cropPath() const; void updateEdges(); [[nodiscard]] QPoint pointOfEdge(Qt::Edges e) const; @@ -86,9 +90,11 @@ private: QPainterPath _painterPath; InfoAtDown _down; + Ui::Animations::Simple _gridOpacityAnimation; int _angle = 0; bool _flipped = false; + bool _gridVisible = false; bool _keepAspectRatio = false;