[img-editor] Added styled text items.

This commit is contained in:
23rd
2026-04-12 13:46:10 +03:00
parent 04aec11daa
commit 0b70be9a9b
12 changed files with 818 additions and 2 deletions

View File

@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "editor/scene/scene_item_canvas.h"
#include "editor/scene/scene_item_image.h"
#include "editor/scene/scene_item_sticker.h"
#include "editor/scene/scene_item_text.h"
#include "editor/scene/scene.h"
#include "lang/lang_keys.h"
#include "lottie/lottie_single_player.h"
@@ -65,6 +66,14 @@ Paint::Paint(
_scene->setBlurSource(std::move(blurSource));
{
const auto shortSide = std::min(imageSize.width(), imageSize.height());
_scene->setTextDefaults(
QColor(255, 255, 255),
shortSide / 15.f,
int(TextStyle::Plain));
}
keepResult();
_view->show();
@@ -234,6 +243,10 @@ void Paint::applyBrush(const Brush &brush) {
brush.tool);
}
void Paint::createTextItem() {
_scene->createTextAtCenter();
}
void Paint::handleMimeData(const QMimeData *data) {
const auto add = [&](QImage image) {
if (image.isNull()) {

View File

@@ -41,6 +41,8 @@ public:
void keepResult();
void updateUndoState();
void createTextItem();
void handleMimeData(const QMimeData *data);
void paintImage(QPainter &p, const QPixmap &image) const;
void resetView();

View File

@@ -302,6 +302,11 @@ PhotoEditor::PhotoEditor(
};
}, lifetime());
_controls->textRequests(
) | rpl::on_next([=] {
_content->createTextItem();
}, lifetime());
_controls->doneRequests(
) | rpl::on_next([=] {
const auto mode = _mode.current().mode;

View File

@@ -162,6 +162,10 @@ void PhotoEditorContent::applyBrush(const Brush &brush) {
_paint->applyBrush(brush);
}
void PhotoEditorContent::createTextItem() {
_paint->createTextItem();
}
bool PhotoEditorContent::handleKeyPress(not_null<QKeyEvent*> e) const {
return false;
}

View File

@@ -31,6 +31,7 @@ public:
void applyModifications(PhotoModifications modifications);
void applyMode(const PhotoEditorMode &mode);
void applyBrush(const Brush &brush);
void createTextItem();
void applyAspectRatio(float64 ratio);
void save(PhotoModifications &modifications);

View File

@@ -202,6 +202,37 @@ ButtonBar::ButtonBar(
}, lifetime());
}
class TextToolButton final : public Ui::AbstractButton {
public:
TextToolButton(not_null<QWidget*> parent)
: AbstractButton(parent) {
resize(
st::photoEditorStickersButton.width,
st::photoEditorStickersButton.height);
events(
) | rpl::on_next([=](not_null<QEvent*> event) {
if (event->type() == QEvent::Enter
|| event->type() == QEvent::Leave) {
update();
}
}, lifetime());
}
private:
void paintEvent(QPaintEvent *) override {
auto p = QPainter(this);
auto hq = PainterHighQualityEnabler(p);
auto font = st::semiboldFont->f;
font.setPixelSize(QWidget::rect().height() / 2);
p.setFont(font);
p.setPen(isOver()
? st::photoEditorButtonIconFgOver
: st::photoEditorButtonIconFg);
p.translate(0, st::lineWidth * 3);
p.drawText(QWidget::rect(), style::al_center, u"A"_q);
}
};
PhotoEditorControls::PhotoEditorControls(
not_null<Ui::RpWidget*> parent,
std::shared_ptr<Controllers> controllers,
@@ -272,6 +303,7 @@ PhotoEditorControls::PhotoEditorControls(
_paintBottomButtons,
st::photoEditorStickersButton)
: nullptr)
, _textButton(base::make_unique_q<TextToolButton>(_paintBottomButtons))
, _paintDone(base::make_unique_q<EdgeButton>(
_paintBottomButtons,
tr::lng_box_done(tr::now),
@@ -499,6 +531,10 @@ rpl::producer<> PhotoEditorControls::paintModeRequests() const {
return _paintModeButton->clicks() | rpl::to_empty;
}
rpl::producer<> PhotoEditorControls::textRequests() const {
return _textButton->clicks() | rpl::to_empty;
}
rpl::producer<> PhotoEditorControls::doneRequests() const {
return rpl::merge(
_transformDone->clicks() | rpl::to_empty,

View File

@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "editor/photo_editor_inner_common.h"
namespace Ui {
class AbstractButton;
class IconButton;
class FlatLabel;
class PopupMenu;
@@ -41,6 +42,7 @@ public:
[[nodiscard]] rpl::producer<int> rotateRequests() const;
[[nodiscard]] rpl::producer<> flipRequests() const;
[[nodiscard]] rpl::producer<> paintModeRequests() const;
[[nodiscard]] rpl::producer<> textRequests() const;
[[nodiscard]] rpl::producer<> doneRequests() const;
[[nodiscard]] rpl::producer<> cancelRequests() const;
[[nodiscard]] rpl::producer<QPoint> colorLinePositionValue() const;
@@ -82,6 +84,7 @@ private:
const base::unique_qptr<Ui::IconButton> _redoButton;
const base::unique_qptr<Ui::IconButton> _paintModeButtonActive;
const base::unique_qptr<Ui::IconButton> _stickersButton;
const base::unique_qptr<Ui::AbstractButton> _textButton;
const base::unique_qptr<EdgeButton> _paintDone;
base::unique_qptr<Ui::PopupMenu> _ratioMenu;

View File

@@ -10,11 +10,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "editor/scene/scene_item_canvas.h"
#include "editor/scene/scene_item_line.h"
#include "editor/scene/scene_item_sticker.h"
#include "editor/scene/scene_item_text.h"
#include "ui/image/image_prepare.h"
#include "ui/rp_widget.h"
#include "styles/style_editor.h"
#include <QGraphicsSceneContextMenuEvent>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsTextItem>
#include <QTextCursor>
#include <QTextDocument>
#include <QTimer>
namespace Editor {
namespace {
@@ -100,6 +106,43 @@ bool SkipMouseEvent(not_null<QGraphicsSceneMouseEvent*> event) {
return event->isAccepted() || (event->button() == Qt::RightButton);
}
class TextEditProxy final : public QGraphicsTextItem {
public:
using QGraphicsTextItem::QGraphicsTextItem;
Fn<void()> onFinish;
Fn<void()> onCancel;
protected:
void keyPressEvent(QKeyEvent *event) override {
if (event->key() == Qt::Key_Escape) {
fire(onCancel);
return;
}
QGraphicsTextItem::keyPressEvent(event);
}
void focusOutEvent(QFocusEvent *event) override {
QGraphicsTextItem::focusOutEvent(event);
fire(onFinish);
}
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override {
event->accept();
}
private:
void fire(Fn<void()> &callback) {
if (!callback) {
return;
}
const auto cb = std::exchange(callback, nullptr);
onFinish = nullptr;
onCancel = nullptr;
QTimer::singleShot(0, cb);
}
};
} // namespace
Scene::Scene(const QRectF &rect)
@@ -240,6 +283,9 @@ Scene::Scene(const QRectF &rect)
}
void Scene::cancelDrawing() {
if (_textEdit.proxy) {
finishTextEditing(false);
}
_canvas->cancelDrawing();
}
@@ -271,6 +317,16 @@ void Scene::removeItem(const ItemPtr &item) {
}
void Scene::mousePressEvent(QGraphicsSceneMouseEvent *event) {
if (_textEdit.proxy) {
const auto clickOnProxy = _textEdit.proxy->contains(
_textEdit.proxy->mapFromScene(event->scenePos()));
if (!clickOnProxy) {
finishTextEditing(true);
QGraphicsScene::mousePressEvent(event);
return;
}
}
QGraphicsScene::mousePressEvent(event);
if (SkipMouseEvent(event) || !sceneRect().contains(event->scenePos())) {
return;
@@ -280,7 +336,7 @@ void Scene::mousePressEvent(QGraphicsSceneMouseEvent *event) {
void Scene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
QGraphicsScene::mouseReleaseEvent(event);
if (SkipMouseEvent(event)) {
if (SkipMouseEvent(event) || _textEdit.proxy) {
return;
}
_canvas->handleMouseReleaseEvent(event);
@@ -288,7 +344,7 @@ void Scene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
void Scene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
QGraphicsScene::mouseMoveEvent(event);
if (SkipMouseEvent(event)) {
if (SkipMouseEvent(event) || _textEdit.proxy) {
return;
}
_canvas->handleMouseMoveEvent(event);
@@ -296,6 +352,17 @@ void Scene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
void Scene::applyBrush(const QColor &color, float size, Brush::Tool tool) {
_canvas->applyBrush(color, size, tool);
for (auto *item : selectedItems()) {
if (item->type() == ItemText::Type) {
static_cast<ItemText*>(item)->setColor(color);
}
}
}
void Scene::setTextDefaults(const QColor &color, float fontSize, int style) {
_textColor = color;
_textFontSize = fontSize;
_textStyle = style;
}
void Scene::setBlurSource(Fn<QImage(QRect)> source) {
@@ -328,6 +395,7 @@ std::shared_ptr<float64> Scene::lastZ() const {
}
void Scene::updateZoom(float64 zoom) {
_currentZoom = zoom;
_canvas->updateZoom(zoom);
for (const auto &item : items()) {
if (item->type() >= ItemBase::Type) {
@@ -396,6 +464,10 @@ void Scene::clearRedoList() {
}
void Scene::save(SaveState state) {
if (_textEdit.proxy) {
finishTextEditing(true);
}
removeIf([](const ItemPtr &item) {
return item->isRemovedStatus()
&& !item->hasState(SaveState::Keep)
@@ -421,7 +493,183 @@ void Scene::restore(SaveState state) {
cancelDrawing();
}
void Scene::createTextAtCenter() {
if (_textEdit.proxy) {
return;
}
clearSelection();
cancelDrawing();
auto *proxy = new TextEditProxy();
proxy->setTextInteractionFlags(Qt::TextEditorInteraction);
proxy->setDefaultTextColor(_textColor);
auto font = QFont();
font.setPixelSize(int(_textFontSize));
font.setWeight(QFont::DemiBold);
proxy->setFont(font);
{
auto option = proxy->document()->defaultTextOption();
option.setAlignment(Qt::AlignCenter);
proxy->document()->setDefaultTextOption(option);
}
const auto shortSide = std::min(
sceneRect().width(),
sceneRect().height());
const auto padding = int(_textFontSize * 0.4);
const auto textWidth = int(shortSide * 0.8) - 2 * padding;
proxy->setTextWidth(textWidth);
const auto center = sceneRect().center();
proxy->setPos(center.x() - textWidth / 2., center.y());
QGraphicsScene::addItem(proxy);
proxy->setZValue((*_lastZ)++);
proxy->setFocus();
proxy->onFinish = crl::guard(this, [=] {
finishTextEditing(true);
});
proxy->onCancel = crl::guard(this, [=] {
finishTextEditing(false);
});
_textEdit = { .item = nullptr, .proxy = proxy };
}
void Scene::startTextEditing(ItemText *item) {
if (_textEdit.proxy) {
finishTextEditing(true);
}
if (!item) {
return;
}
cancelDrawing();
auto *proxy = new TextEditProxy();
proxy->setTextInteractionFlags(Qt::TextEditorInteraction);
proxy->setDefaultTextColor(item->color());
auto font = QFont();
font.setPixelSize(int(item->fontSize()));
font.setWeight(QFont::DemiBold);
proxy->setFont(font);
{
auto option = proxy->document()->defaultTextOption();
option.setAlignment(Qt::AlignCenter);
proxy->document()->setDefaultTextOption(option);
}
proxy->setPlainText(item->text());
const auto shortSide = std::min(
sceneRect().width(),
sceneRect().height());
const auto padding = int(item->fontSize() * 0.4);
const auto textWidth = int(shortSide * 0.8) - 2 * padding;
proxy->setTextWidth(textWidth);
const auto scale = item->editScale();
const auto proxyRect = proxy->boundingRect();
const auto center = proxyRect.center();
proxy->setTransformOriginPoint(center);
proxy->setPos(item->scenePos() - center);
proxy->setRotation(item->rotation());
if (std::abs(scale - 1.) > 0.01) {
proxy->setScale(scale);
}
QGraphicsScene::addItem(proxy);
proxy->setZValue((*_lastZ)++);
proxy->setFocus();
auto cursor = proxy->textCursor();
cursor.select(QTextCursor::Document);
proxy->setTextCursor(cursor);
item->setVisible(false);
proxy->onFinish = crl::guard(this, [=] {
finishTextEditing(true);
});
proxy->onCancel = crl::guard(this, [=] {
finishTextEditing(false);
});
_textEdit = { .item = item, .proxy = proxy };
}
void Scene::finishTextEditing(bool save) {
if (!_textEdit.proxy) {
return;
}
const auto text = save
? _textEdit.proxy->toPlainText().trimmed()
: QString();
const auto proxyRect = _textEdit.proxy->boundingRect();
const auto proxyCenter = _textEdit.proxy->pos()
+ QPointF(proxyRect.width() / 2., proxyRect.height() / 2.);
auto *existingItem = _textEdit.item;
const auto proxy = static_cast<TextEditProxy*>(_textEdit.proxy);
proxy->onFinish = nullptr;
proxy->onCancel = nullptr;
QGraphicsScene::removeItem(proxy);
delete proxy;
_textEdit.proxy = nullptr;
_textEdit.item = nullptr;
if (!text.isEmpty()) {
if (existingItem) {
existingItem->setText(text);
existingItem->setVisible(true);
} else {
const auto imageSize = sceneRect().size().toSize();
const auto contentSize = ItemText::computeContentSize(
text,
_textFontSize,
imageSize);
const auto size = std::max(contentSize.width(), 1);
auto data = ItemBase::Data{
.initialZoom = zoom,
.zPtr = _lastZ,
.size = size,
.x = int(proxyCenter.x()),
.y = int(proxyCenter.y()),
.imageSize = imageSize,
};
auto item = std::make_shared<ItemText>(
text,
_textColor,
_textFontSize,
TextStyle::Plain,
imageSize,
std::move(data));
addItem(item);
}
} else if (existingItem) {
if (save) {
removeItem(existingItem);
} else {
existingItem->setVisible(true);
}
}
}
Scene::~Scene() {
if (_textEdit.proxy) {
const auto proxy = static_cast<TextEditProxy*>(_textEdit.proxy);
proxy->onFinish = nullptr;
proxy->onCancel = nullptr;
QGraphicsScene::removeItem(proxy);
delete proxy;
_textEdit.proxy = nullptr;
}
// Prevent destroying by scene of all items.
QGraphicsScene::removeItem(_canvas.get());
for (const auto &item : items()) {

View File

@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QGraphicsScene>
class QGraphicsSceneMouseEvent;
class QGraphicsTextItem;
namespace Ui {
class RpWidget;
@@ -21,6 +22,7 @@ class RpWidget;
namespace Editor {
class ItemCanvas;
class ItemText;
class NumberedItem;
class Scene final : public QGraphicsScene {
@@ -31,6 +33,7 @@ public:
~Scene();
void applyBrush(const QColor &color, float size, Brush::Tool tool);
void setBlurSource(Fn<QImage(QRect)> source);
void setTextDefaults(const QColor &color, float fontSize, int style);
[[nodiscard]] std::vector<ItemPtr> items(
Qt::SortOrder order = Qt::DescendingOrder) const;
@@ -46,6 +49,9 @@ public:
void cancelDrawing();
void startTextEditing(ItemText *item);
void createTextAtCenter();
[[nodiscard]] bool hasUndo() const;
[[nodiscard]] bool hasRedo() const;
@@ -62,6 +68,8 @@ protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
private:
void removeIf(Fn<bool(const ItemPtr &)> proj);
void finishTextEditing(bool save);
const std::shared_ptr<ItemCanvas> _canvas;
const std::shared_ptr<float64> _lastZ;
Fn<QImage(QRect)> _blurSource;
@@ -70,8 +78,18 @@ private:
std::unordered_map<QGraphicsItem*, ItemPtr> _itemsByPointer;
float64 _lastLineZ = 0.;
float64 _currentZoom = 1.;
int _itemNumber = 0;
QColor _textColor;
float _textFontSize = 0.f;
int _textStyle = 3;
struct {
ItemText *item = nullptr;
QGraphicsTextItem *proxy = nullptr;
} _textEdit;
rpl::event_stream<> _addsItem, _removesItem;
rpl::lifetime _lifetime;

View File

@@ -0,0 +1,391 @@
/*
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_text.h"
#include "editor/scene/scene.h"
#include "lang/lang_keys.h"
#include "ui/painter.h"
#include "ui/widgets/popup_menu.h"
#include "styles/style_editor.h"
#include "styles/style_menu_icons.h"
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsSceneContextMenuEvent>
#include <QTextLayout>
#include <QTextOption>
namespace Editor {
namespace {
constexpr auto kPaddingFactor = 0.4f;
constexpr auto kMaxWidthFactor = 0.8f;
constexpr auto kMinContentWidth = 20;
struct LayoutMetrics {
int contentWidth = 0;
int contentHeight = 0;
int padding = 0;
int textMaxWidth = 0;
};
QFont TextFont(float fontSize) {
auto font = QFont();
font.setPixelSize(std::max(int(fontSize), 1));
font.setWeight(QFont::DemiBold);
return font;
}
float ComputeBrightness(const QColor &color) {
return (color.red() * 0.2126f
+ color.green() * 0.7152f
+ color.blue() * 0.0722f) / 255.f;
}
LayoutMetrics ComputeMetrics(
const QString &text,
float fontSize,
const QSize &imageSize) {
const auto padding = int(fontSize * kPaddingFactor);
const auto shortSide = std::min(imageSize.width(), imageSize.height());
const auto textMaxWidth = int(shortSide * kMaxWidthFactor) - 2 * padding;
const auto font = TextFont(fontSize);
auto processedText = text;
processedText.replace('\n', QChar::LineSeparator);
auto option = QTextOption();
option.setWrapMode(QTextOption::WordWrap);
auto layout = QTextLayout(processedText, font);
layout.setTextOption(option);
layout.beginLayout();
auto totalHeight = 0.;
auto maxWidth = 0.;
while (true) {
auto line = layout.createLine();
if (!line.isValid()) {
break;
}
line.setLineWidth(textMaxWidth);
line.setPosition(QPointF(0, totalHeight));
totalHeight += line.height();
maxWidth = std::max(maxWidth, double(line.naturalTextWidth()));
}
layout.endLayout();
return {
.contentWidth = std::max(int(std::ceil(maxWidth)), kMinContentWidth),
.contentHeight = int(std::ceil(totalHeight)),
.padding = padding,
.textMaxWidth = textMaxWidth,
};
}
} // namespace
ItemText::ItemText(
const QString &text,
const QColor &color,
float fontSize,
TextStyle style,
const QSize &imageSize,
ItemBase::Data data)
: ItemBase(std::move(data))
, _text(text)
, _color(color)
, _fontSize(fontSize)
, _textStyle(style)
, _imageSize(imageSize) {
renderContent();
}
void ItemText::renderContent() {
if (_text.isEmpty()) {
_pixmap = QPixmap();
setAspectRatio(1.);
return;
}
const auto m = ComputeMetrics(_text, _fontSize, _imageSize);
const auto pixWidth = m.contentWidth + 2 * m.padding;
const auto pixHeight = m.contentHeight + 2 * m.padding;
const auto font = TextFont(_fontSize);
auto processedText = _text;
processedText.replace('\n', QChar::LineSeparator);
auto option = QTextOption();
option.setWrapMode(QTextOption::WordWrap);
auto layout = QTextLayout(processedText, font);
layout.setTextOption(option);
layout.beginLayout();
auto y = 0.;
while (true) {
auto line = layout.createLine();
if (!line.isValid()) {
break;
}
line.setLineWidth(m.textMaxWidth);
line.setPosition(QPointF(0, y));
y += line.height();
}
layout.endLayout();
auto textColor = _color;
auto bgColor = QColor(Qt::transparent);
const auto brightness = ComputeBrightness(_color);
const auto cornerRadius = _fontSize / 3.f;
const auto hasPerLineBackground =
(_textStyle == TextStyle::Framed)
|| (_textStyle == TextStyle::SemiTransparent);
switch (_textStyle) {
case TextStyle::Framed:
bgColor = _color;
textColor = (brightness >= 0.721f)
? QColor(0, 0, 0)
: QColor(255, 255, 255);
break;
case TextStyle::SemiTransparent:
bgColor = (brightness >= 0.25f)
? QColor(0, 0, 0, 0x99)
: QColor(255, 255, 255, 0x99);
break;
case TextStyle::Plain:
break;
}
const auto dpr = style::DevicePixelRatio();
auto pixmap = QPixmap(QSize(pixWidth, pixHeight) * dpr);
pixmap.setDevicePixelRatio(dpr);
pixmap.fill(Qt::transparent);
{
auto p = QPainter(&pixmap);
auto hq = PainterHighQualityEnabler(p);
if (hasPerLineBackground) {
const auto linePadH = _fontSize / 3.f;
const auto linePadV = _fontSize / 8.f;
p.setPen(Qt::NoPen);
p.setBrush(bgColor);
for (auto i = 0; i < layout.lineCount(); ++i) {
const auto line = layout.lineAt(i);
const auto natWidth = line.naturalTextWidth();
const auto xOffset =
(m.contentWidth - natWidth) / 2.;
const auto rect = QRectF(
m.padding + xOffset - linePadH,
m.padding + line.y() - linePadV,
natWidth + 2. * linePadH,
line.height() + 2. * linePadV);
p.drawRoundedRect(rect, cornerRadius, cornerRadius);
}
}
p.setPen(textColor);
for (auto i = 0; i < layout.lineCount(); ++i) {
const auto line = layout.lineAt(i);
const auto xOffset =
(m.contentWidth - line.naturalTextWidth()) / 2.;
line.draw(&p, QPointF(m.padding + xOffset, m.padding));
}
}
_pixmap = std::move(pixmap);
const auto handleMargin = std::max(
innerRect().width() - contentRect().width(),
0.);
setAspectRatio(
(pixHeight + handleMargin) / float64(pixWidth + handleMargin));
}
QSize ItemText::computeContentSize(
const QString &text,
float fontSize,
const QSize &imageSize) {
if (text.isEmpty()) {
return {};
}
auto processedText = text;
processedText.replace('\n', QChar::LineSeparator);
const auto m = ComputeMetrics(processedText, fontSize, imageSize);
return QSize(
m.contentWidth + 2 * m.padding,
m.contentHeight + 2 * m.padding);
}
void ItemText::paint(
QPainter *p,
const QStyleOptionGraphicsItem *option,
QWidget *w) {
if (!_pixmap.isNull()) {
const auto rect = contentRect();
const auto pixmapSize = QSizeF(
_pixmap.size() / style::DevicePixelRatio()
).scaled(rect.size(), Qt::KeepAspectRatio);
const auto resultRect = QRectF(
rect.topLeft(),
pixmapSize
).translated(
(rect.width() - pixmapSize.width()) / 2.,
(rect.height() - pixmapSize.height()) / 2.);
if (flipped()) {
p->save();
const auto center = resultRect.center();
p->translate(center);
p->scale(-1, 1);
p->translate(-center);
p->drawPixmap(resultRect.toRect(), _pixmap);
p->restore();
} else {
p->drawPixmap(resultRect.toRect(), _pixmap);
}
}
ItemBase::paint(p, option, w);
}
int ItemText::type() const {
return Type;
}
const QString &ItemText::text() const {
return _text;
}
void ItemText::setText(const QString &text) {
_text = text;
renderContent();
update();
}
const QColor &ItemText::color() const {
return _color;
}
void ItemText::setColor(const QColor &color) {
_color = color;
renderContent();
update();
}
float ItemText::fontSize() const {
return _fontSize;
}
float64 ItemText::editScale() const {
const auto natural = computeContentSize(_text, _fontSize, _imageSize);
if (natural.width() <= 0) {
return 1.;
}
return size() / natural.width();
}
TextStyle ItemText::textStyle() const {
return _textStyle;
}
void ItemText::setTextStyle(TextStyle style) {
_textStyle = style;
renderContent();
update();
}
void ItemText::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) {
if (const auto s = static_cast<Scene*>(scene())) {
s->startTextEditing(this);
}
}
void ItemText::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) {
if (scene()) {
scene()->clearSelection();
setSelected(true);
}
_contextMenu = base::make_unique_q<Ui::PopupMenu>(
nullptr,
st::popupMenuWithIcons);
const auto add = [&](const QString &text, TextStyle style) {
const auto checked = (_textStyle == style);
auto action = _contextMenu->addAction(text, [=] {
setTextStyle(style);
});
if (checked) {
action->setChecked(true);
}
};
add(u"Plain"_q, TextStyle::Plain);
add(u"Framed"_q, TextStyle::Framed);
add(u"Semi-Transparent"_q, TextStyle::SemiTransparent);
_contextMenu->addSeparator();
_contextMenu->addAction(
tr::lng_photo_editor_menu_delete(tr::now),
[=] { actionDelete(); },
&st::menuIconDelete);
_contextMenu->addAction(
tr::lng_photo_editor_menu_duplicate(tr::now),
[=] { actionDuplicate(); },
&st::menuIconCopy);
_contextMenu->popup(event->screenPos());
}
void ItemText::performFlip() {
update();
}
std::shared_ptr<ItemBase> ItemText::duplicate(ItemBase::Data data) const {
return std::make_shared<ItemText>(
_text,
_color,
_fontSize,
_textStyle,
_imageSize,
std::move(data));
}
void ItemText::save(SaveState state) {
ItemBase::save(state);
auto &saved = (state == SaveState::Keep) ? _keepedState : _savedState;
saved = {
.saved = true,
.status = status(),
.text = _text,
.color = _color,
.fontSize = _fontSize,
.textStyle = _textStyle,
};
}
void ItemText::restore(SaveState state) {
if (!hasState(state)) {
return;
}
const auto &saved = (state == SaveState::Keep) ? _keepedState : _savedState;
_text = saved.text;
_color = saved.color;
_fontSize = saved.fontSize;
_textStyle = saved.textStyle;
renderContent();
ItemBase::restore(state);
}
bool ItemText::hasState(SaveState state) const {
return ItemBase::hasState(state);
}
} // namespace Editor

View File

@@ -0,0 +1,93 @@
/*
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 "base/unique_qptr.h"
#include "editor/scene/scene_item_base.h"
namespace Ui {
class PopupMenu;
} // namespace Ui
namespace Editor {
enum class TextStyle : uchar {
Framed,
SemiTransparent,
Plain,
};
class ItemText : public ItemBase {
public:
enum { Type = ItemBase::Type + 2 };
ItemText(
const QString &text,
const QColor &color,
float fontSize,
TextStyle style,
const QSize &imageSize,
ItemBase::Data data);
void paint(
QPainter *p,
const QStyleOptionGraphicsItem *option,
QWidget *widget) override;
int type() const override;
[[nodiscard]] const QString &text() const;
void setText(const QString &text);
[[nodiscard]] const QColor &color() const;
void setColor(const QColor &color);
[[nodiscard]] float fontSize() const;
[[nodiscard]] TextStyle textStyle() const;
void setTextStyle(TextStyle style);
[[nodiscard]] float64 editScale() const;
[[nodiscard]] static QSize computeContentSize(
const QString &text,
float fontSize,
const QSize &imageSize);
void save(SaveState state) override;
void restore(SaveState state) override;
bool hasState(SaveState state) const override;
protected:
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;
void performFlip() override;
std::shared_ptr<ItemBase> duplicate(ItemBase::Data data) const override;
private:
void renderContent();
QString _text;
QColor _color;
float _fontSize;
TextStyle _textStyle = TextStyle::Plain;
QSize _imageSize;
QPixmap _pixmap;
base::unique_qptr<Ui::PopupMenu> _contextMenu;
struct SavedText {
bool saved = false;
NumberedItem::Status status = NumberedItem::Status::Normal;
QString text;
QColor color;
float fontSize = 0;
TextStyle textStyle = TextStyle::Plain;
};
SavedText _savedState, _keepedState;
};
} // namespace Editor

View File

@@ -147,6 +147,8 @@ PRIVATE
editor/scene/scene_item_image.h
editor/scene/scene_item_line.cpp
editor/scene/scene_item_line.h
editor/scene/scene_item_text.cpp
editor/scene/scene_item_text.h
ui/boxes/about_cocoon_box.h
ui/boxes/about_cocoon_box.cpp