mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-07-25 06:54:43 +00:00
[img-editor] Improved color handling in text items.
This commit is contained in:
@@ -7182,6 +7182,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
"lng_photo_editor_menu_flip" = "Flip";
|
"lng_photo_editor_menu_flip" = "Flip";
|
||||||
"lng_photo_editor_menu_duplicate" = "Duplicate";
|
"lng_photo_editor_menu_duplicate" = "Duplicate";
|
||||||
|
|
||||||
|
"lng_photo_editor_text_style_plain" = "Plain";
|
||||||
|
"lng_photo_editor_text_style_framed" = "Framed";
|
||||||
|
"lng_photo_editor_text_style_semi_transparent" = "Semi-Transparent";
|
||||||
|
|
||||||
"lng_photo_editor_crop_original" = "Original";
|
"lng_photo_editor_crop_original" = "Original";
|
||||||
"lng_photo_editor_crop_square" = "Square";
|
"lng_photo_editor_crop_square" = "Square";
|
||||||
"lng_photo_editor_crop_free" = "Free";
|
"lng_photo_editor_crop_free" = "Free";
|
||||||
|
|||||||
@@ -570,6 +570,17 @@ void ColorPicker::storeCurrentBrush() {
|
|||||||
_toolBrushes[ToolIndex(_brush.tool)] = _brush;
|
_toolBrushes[ToolIndex(_brush.tool)] = _brush;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ColorPicker::setColor(const QColor &color) {
|
||||||
|
_brush.color = color;
|
||||||
|
storeCurrentBrush();
|
||||||
|
updateColorButtonColor(color, true);
|
||||||
|
if (_paletteVisible) {
|
||||||
|
rebuildPalette();
|
||||||
|
} else {
|
||||||
|
_colorButton->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ColorPicker::updateColorButtonColor(const QColor &color, bool animated) {
|
void ColorPicker::updateColorButtonColor(const QColor &color, bool animated) {
|
||||||
const auto hasValid = _colorButtonFrom.isValid() && _colorButtonTo.isValid();
|
const auto hasValid = _colorButtonFrom.isValid() && _colorButtonTo.isValid();
|
||||||
const auto from = hasValid ? colorButtonColor() : color;
|
const auto from = hasValid ? colorButtonColor() : color;
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ public:
|
|||||||
void moveLine(const QPoint &position);
|
void moveLine(const QPoint &position);
|
||||||
void setCanvasRect(const QRect &rect);
|
void setCanvasRect(const QRect &rect);
|
||||||
void setVisible(bool visible);
|
void setVisible(bool visible);
|
||||||
|
void setColor(const QColor &color);
|
||||||
bool preventHandleKeyPress() const;
|
bool preventHandleKeyPress() const;
|
||||||
|
|
||||||
rpl::producer<Brush> saveBrushRequests() const;
|
rpl::producer<Brush> saveBrushRequests() const;
|
||||||
|
|||||||
@@ -67,10 +67,13 @@ Paint::Paint(
|
|||||||
_scene->setBlurSource(std::move(blurSource));
|
_scene->setBlurSource(std::move(blurSource));
|
||||||
|
|
||||||
{
|
{
|
||||||
const auto shortSide = std::min(imageSize.width(), imageSize.height());
|
constexpr auto kDefaultFontSizeDivisor = 15.;
|
||||||
|
const auto shortSide = std::min(
|
||||||
|
imageSize.width(),
|
||||||
|
imageSize.height());
|
||||||
_scene->setTextDefaults(
|
_scene->setTextDefaults(
|
||||||
QColor(255, 255, 255),
|
QColor(255, 255, 255),
|
||||||
shortSide / 15.f,
|
shortSide / kDefaultFontSizeDivisor,
|
||||||
int(TextStyle::Plain));
|
int(TextStyle::Plain));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,6 +250,14 @@ void Paint::createTextItem() {
|
|||||||
_scene->createTextAtCenter();
|
_scene->createTextAtCenter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Paint::setTextColor(const QColor &color) {
|
||||||
|
_scene->setTextColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<QColor> Paint::textColorRequests() const {
|
||||||
|
return _scene->textColorRequests();
|
||||||
|
}
|
||||||
|
|
||||||
void Paint::handleMimeData(const QMimeData *data) {
|
void Paint::handleMimeData(const QMimeData *data) {
|
||||||
const auto add = [&](QImage image) {
|
const auto add = [&](QImage image) {
|
||||||
if (image.isNull()) {
|
if (image.isNull()) {
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ public:
|
|||||||
void updateUndoState();
|
void updateUndoState();
|
||||||
|
|
||||||
void createTextItem();
|
void createTextItem();
|
||||||
|
void setTextColor(const QColor &color);
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<QColor> textColorRequests() const;
|
||||||
|
|
||||||
void handleMimeData(const QMimeData *data);
|
void handleMimeData(const QMimeData *data);
|
||||||
void paintImage(QPainter &p, const QPixmap &image) const;
|
void paintImage(QPainter &p, const QPixmap &image) const;
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ struct BrushState {
|
|||||||
const auto tool = ToolFromSerialized(entryTool);
|
const auto tool = ToolFromSerialized(entryTool);
|
||||||
const auto index = ToolIndex(tool);
|
const auto index = ToolIndex(tool);
|
||||||
if (version == kBrushesVersion && size > 0) {
|
if (version == kBrushesVersion && size > 0) {
|
||||||
result.brushes[index].sizeRatio = size / float(kPrecision);
|
result.brushes[index].sizeRatio = size / float64(kPrecision);
|
||||||
}
|
}
|
||||||
if (color.isValid()) {
|
if (color.isValid()) {
|
||||||
result.brushes[index].color = color;
|
result.brushes[index].color = color;
|
||||||
@@ -344,6 +344,7 @@ PhotoEditor::PhotoEditor(
|
|||||||
_colorPicker->saveBrushRequests(
|
_colorPicker->saveBrushRequests(
|
||||||
) | rpl::on_next([=](const Brush &brush) {
|
) | rpl::on_next([=](const Brush &brush) {
|
||||||
_content->applyBrush(brush);
|
_content->applyBrush(brush);
|
||||||
|
_content->setTextColor(brush.color);
|
||||||
|
|
||||||
_brushTool = brush.tool;
|
_brushTool = brush.tool;
|
||||||
_brushes[ToolIndex(brush.tool)] = brush;
|
_brushes[ToolIndex(brush.tool)] = brush;
|
||||||
@@ -353,6 +354,11 @@ PhotoEditor::PhotoEditor(
|
|||||||
Core::App().saveSettingsDelayed();
|
Core::App().saveSettingsDelayed();
|
||||||
}
|
}
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
|
_content->textColorRequests(
|
||||||
|
) | rpl::on_next([=](const QColor &color) {
|
||||||
|
_colorPicker->setColor(color);
|
||||||
|
}, lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
void PhotoEditor::keyPressEvent(QKeyEvent *e) {
|
void PhotoEditor::keyPressEvent(QKeyEvent *e) {
|
||||||
|
|||||||
@@ -166,6 +166,14 @@ void PhotoEditorContent::createTextItem() {
|
|||||||
_paint->createTextItem();
|
_paint->createTextItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PhotoEditorContent::setTextColor(const QColor &color) {
|
||||||
|
_paint->setTextColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<QColor> PhotoEditorContent::textColorRequests() const {
|
||||||
|
return _paint->textColorRequests();
|
||||||
|
}
|
||||||
|
|
||||||
bool PhotoEditorContent::handleKeyPress(not_null<QKeyEvent*> e) const {
|
bool PhotoEditorContent::handleKeyPress(not_null<QKeyEvent*> e) const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ public:
|
|||||||
void applyMode(const PhotoEditorMode &mode);
|
void applyMode(const PhotoEditorMode &mode);
|
||||||
void applyBrush(const Brush &brush);
|
void applyBrush(const Brush &brush);
|
||||||
void createTextItem();
|
void createTextItem();
|
||||||
|
void setTextColor(const QColor &color);
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<QColor> textColorRequests() const;
|
||||||
void applyAspectRatio(float64 ratio);
|
void applyAspectRatio(float64 ratio);
|
||||||
void save(PhotoModifications &modifications);
|
void save(PhotoModifications &modifications);
|
||||||
|
|
||||||
|
|||||||
@@ -206,9 +206,10 @@ class TextToolButton final : public Ui::AbstractButton {
|
|||||||
public:
|
public:
|
||||||
TextToolButton(not_null<QWidget*> parent)
|
TextToolButton(not_null<QWidget*> parent)
|
||||||
: AbstractButton(parent) {
|
: AbstractButton(parent) {
|
||||||
|
constexpr auto kSizeShrink = 6;
|
||||||
resize(
|
resize(
|
||||||
st::photoEditorStickersButton.width,
|
st::photoEditorStickersButton.width - kSizeShrink,
|
||||||
st::photoEditorStickersButton.height);
|
st::photoEditorStickersButton.height - kSizeShrink);
|
||||||
events(
|
events(
|
||||||
) | rpl::on_next([=](not_null<QEvent*> event) {
|
) | rpl::on_next([=](not_null<QEvent*> event) {
|
||||||
if (event->type() == QEvent::Enter
|
if (event->type() == QEvent::Enter
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "editor/scene/scene_item_line.h"
|
#include "editor/scene/scene_item_line.h"
|
||||||
#include "editor/scene/scene_item_sticker.h"
|
#include "editor/scene/scene_item_sticker.h"
|
||||||
#include "editor/scene/scene_item_text.h"
|
#include "editor/scene/scene_item_text.h"
|
||||||
#include "ui/emoji_config.h"
|
#include "editor/scene/scene_emoji_document.h"
|
||||||
#include "ui/image/image_prepare.h"
|
#include "ui/image/image_prepare.h"
|
||||||
#include "ui/rp_widget.h"
|
#include "ui/rp_widget.h"
|
||||||
#include "styles/style_editor.h"
|
#include "styles/style_editor.h"
|
||||||
@@ -22,7 +22,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include <QGraphicsView>
|
#include <QGraphicsView>
|
||||||
#include <QTextCursor>
|
#include <QTextCursor>
|
||||||
#include <QTextDocument>
|
#include <QTextDocument>
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
namespace Editor {
|
namespace Editor {
|
||||||
namespace {
|
namespace {
|
||||||
@@ -108,6 +107,13 @@ bool SkipMouseEvent(not_null<QGraphicsSceneMouseEvent*> event) {
|
|||||||
return event->isAccepted() || (event->button() == Qt::RightButton);
|
return event->isAccepted() || (event->button() == Qt::RightButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr auto kPaddingFactor = 0.4;
|
||||||
|
constexpr auto kMaxWidthFactor = 0.8;
|
||||||
|
constexpr auto kMinWidthFactor = 0.16;
|
||||||
|
constexpr auto kIdealWidthExtra = 2;
|
||||||
|
constexpr auto kDefaultFontSizeDivisor = 15.;
|
||||||
|
constexpr auto kScaleThreshold = 0.01;
|
||||||
|
|
||||||
class TextEditProxy final : public QGraphicsTextItem {
|
class TextEditProxy final : public QGraphicsTextItem {
|
||||||
public:
|
public:
|
||||||
using QGraphicsTextItem::QGraphicsTextItem;
|
using QGraphicsTextItem::QGraphicsTextItem;
|
||||||
@@ -141,7 +147,7 @@ private:
|
|||||||
const auto cb = std::exchange(callback, nullptr);
|
const auto cb = std::exchange(callback, nullptr);
|
||||||
onFinish = nullptr;
|
onFinish = nullptr;
|
||||||
onCancel = nullptr;
|
onCancel = nullptr;
|
||||||
QTimer::singleShot(0, cb);
|
crl::on_main(cb);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -352,21 +358,30 @@ void Scene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
|
|||||||
_canvas->handleMouseMoveEvent(event);
|
_canvas->handleMouseMoveEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scene::applyBrush(const QColor &color, float size, Brush::Tool tool) {
|
void Scene::applyBrush(const QColor &color, float64 size, Brush::Tool tool) {
|
||||||
_canvas->applyBrush(color, size, 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) {
|
void Scene::setTextDefaults(
|
||||||
|
const QColor &color,
|
||||||
|
float64 fontSize,
|
||||||
|
int style) {
|
||||||
_textColor = color;
|
_textColor = color;
|
||||||
_textFontSize = fontSize;
|
_textFontSize = fontSize;
|
||||||
_textStyle = style;
|
_textStyle = style;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Scene::setTextColor(const QColor &color) {
|
||||||
|
_textColor = color;
|
||||||
|
if (_textEdit.proxy) {
|
||||||
|
_textEdit.proxy->setDefaultTextColor(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<QColor> Scene::textColorRequests() const {
|
||||||
|
return _textColorRequests.events();
|
||||||
|
}
|
||||||
|
|
||||||
void Scene::setBlurSource(Fn<QImage(QRect)> source) {
|
void Scene::setBlurSource(Fn<QImage(QRect)> source) {
|
||||||
_blurSource = std::move(source);
|
_blurSource = std::move(source);
|
||||||
}
|
}
|
||||||
@@ -495,24 +510,19 @@ void Scene::restore(SaveState state) {
|
|||||||
cancelDrawing();
|
cancelDrawing();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scene::createTextAtCenter() {
|
void Scene::setupTextProxy(
|
||||||
if (_textEdit.proxy) {
|
QGraphicsTextItem *proxy,
|
||||||
return;
|
const QColor &color,
|
||||||
}
|
float64 fontSize) {
|
||||||
|
|
||||||
clearSelection();
|
|
||||||
cancelDrawing();
|
|
||||||
|
|
||||||
auto *proxy = new TextEditProxy();
|
|
||||||
proxy->setTextInteractionFlags(Qt::TextEditorInteraction);
|
proxy->setTextInteractionFlags(Qt::TextEditorInteraction);
|
||||||
proxy->setDefaultTextColor(_textColor);
|
proxy->setDefaultTextColor(color);
|
||||||
|
|
||||||
auto *emojiDoc = new EmojiDocument(proxy);
|
auto *emojiDoc = new EmojiDocument(proxy);
|
||||||
emojiDoc->setDocumentMargin(0);
|
emojiDoc->setDocumentMargin(0);
|
||||||
proxy->setDocument(emojiDoc);
|
proxy->setDocument(emojiDoc);
|
||||||
|
|
||||||
auto font = QFont();
|
auto font = QFont();
|
||||||
font.setPixelSize(int(_textFontSize));
|
font.setPixelSize(int(fontSize));
|
||||||
font.setWeight(QFont::DemiBold);
|
font.setWeight(QFont::DemiBold);
|
||||||
proxy->setFont(font);
|
proxy->setFont(font);
|
||||||
|
|
||||||
@@ -521,19 +531,33 @@ void Scene::createTextAtCenter() {
|
|||||||
option.setAlignment(Qt::AlignCenter);
|
option.setAlignment(Qt::AlignCenter);
|
||||||
emojiDoc->setDefaultTextOption(option);
|
emojiDoc->setDefaultTextOption(option);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scene::createTextAtCenter() {
|
||||||
|
if (_textEdit.proxy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearSelection();
|
||||||
|
cancelDrawing();
|
||||||
|
|
||||||
|
_textEdit.proxy.reset(new TextEditProxy());
|
||||||
|
const auto proxy = _textEdit.proxy.get();
|
||||||
|
setupTextProxy(proxy, _textColor, _textFontSize);
|
||||||
|
|
||||||
|
const auto emojiDoc = proxy->document();
|
||||||
const auto shortSide = std::min(
|
const auto shortSide = std::min(
|
||||||
sceneRect().width(),
|
sceneRect().width(),
|
||||||
sceneRect().height());
|
sceneRect().height());
|
||||||
const auto padding = int(_textFontSize * 0.4);
|
const auto padding = int(_textFontSize * kPaddingFactor);
|
||||||
const auto maxTextWidth = int(shortSide * 0.8) - 2 * padding;
|
const auto maxTextWidth = int(shortSide * kMaxWidthFactor) - 2 * padding;
|
||||||
const auto minTextWidth = int(shortSide * 0.16) - 2 * padding;
|
const auto minTextWidth = int(shortSide * kMinWidthFactor) - 2 * padding;
|
||||||
const auto sceneCenter = sceneRect().center();
|
const auto sceneCenter = sceneRect().center();
|
||||||
const auto adjustWidth = [=] {
|
const auto adjustWidth = [=] {
|
||||||
emojiDoc->setTextWidth(maxTextWidth);
|
emojiDoc->setTextWidth(maxTextWidth);
|
||||||
const auto ideal = int(std::ceil(emojiDoc->idealWidth()));
|
const auto ideal = int(std::ceil(emojiDoc->idealWidth()));
|
||||||
const auto width = std::clamp(
|
const auto width = std::clamp(
|
||||||
ideal + 2,
|
ideal + kIdealWidthExtra,
|
||||||
minTextWidth,
|
minTextWidth,
|
||||||
maxTextWidth);
|
maxTextWidth);
|
||||||
proxy->setTextWidth(width);
|
proxy->setTextWidth(width);
|
||||||
@@ -553,14 +577,16 @@ void Scene::createTextAtCenter() {
|
|||||||
views().first()->setFocus();
|
views().first()->setFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy->onFinish = crl::guard(this, [=] {
|
const auto raw = static_cast<TextEditProxy*>(proxy);
|
||||||
|
raw->onFinish = crl::guard(this, [=] {
|
||||||
finishTextEditing(true);
|
finishTextEditing(true);
|
||||||
});
|
});
|
||||||
proxy->onCancel = crl::guard(this, [=] {
|
raw->onCancel = crl::guard(this, [=] {
|
||||||
finishTextEditing(false);
|
finishTextEditing(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
_textEdit = { .item = nullptr, .proxy = proxy };
|
_textEdit.item.reset();
|
||||||
|
_textColorRequests.fire_copy(_textColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scene::startTextEditing(ItemText *item) {
|
void Scene::startTextEditing(ItemText *item) {
|
||||||
@@ -573,40 +599,26 @@ void Scene::startTextEditing(ItemText *item) {
|
|||||||
|
|
||||||
cancelDrawing();
|
cancelDrawing();
|
||||||
|
|
||||||
auto *proxy = new TextEditProxy();
|
_textEdit.proxy.reset(new TextEditProxy());
|
||||||
proxy->setTextInteractionFlags(Qt::TextEditorInteraction);
|
const auto proxy = _textEdit.proxy.get();
|
||||||
proxy->setDefaultTextColor(item->color());
|
setupTextProxy(proxy, item->color(), item->fontSize());
|
||||||
|
|
||||||
auto *emojiDoc = new EmojiDocument(proxy);
|
|
||||||
emojiDoc->setDocumentMargin(0);
|
|
||||||
proxy->setDocument(emojiDoc);
|
|
||||||
|
|
||||||
auto font = QFont();
|
|
||||||
font.setPixelSize(int(item->fontSize()));
|
|
||||||
font.setWeight(QFont::DemiBold);
|
|
||||||
proxy->setFont(font);
|
|
||||||
|
|
||||||
{
|
|
||||||
auto option = emojiDoc->defaultTextOption();
|
|
||||||
option.setAlignment(Qt::AlignCenter);
|
|
||||||
emojiDoc->setDefaultTextOption(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy->setPlainText(item->text());
|
proxy->setPlainText(item->text());
|
||||||
ReplaceEmoji(emojiDoc);
|
ReplaceEmoji(proxy->document());
|
||||||
|
|
||||||
|
const auto emojiDoc = proxy->document();
|
||||||
const auto shortSide = std::min(
|
const auto shortSide = std::min(
|
||||||
sceneRect().width(),
|
sceneRect().width(),
|
||||||
sceneRect().height());
|
sceneRect().height());
|
||||||
const auto padding = int(item->fontSize() * 0.4);
|
const auto padding = int(item->fontSize() * kPaddingFactor);
|
||||||
const auto maxTextWidth = int(shortSide * 0.8) - 2 * padding;
|
const auto maxTextWidth = int(shortSide * kMaxWidthFactor) - 2 * padding;
|
||||||
const auto minTextWidth = int(shortSide * 0.16) - 2 * padding;
|
const auto minTextWidth = int(shortSide * kMinWidthFactor) - 2 * padding;
|
||||||
const auto anchor = item->scenePos();
|
const auto anchor = item->scenePos();
|
||||||
const auto adjustWidth = [=] {
|
const auto adjustWidth = [=] {
|
||||||
emojiDoc->setTextWidth(maxTextWidth);
|
emojiDoc->setTextWidth(maxTextWidth);
|
||||||
const auto ideal = int(std::ceil(emojiDoc->idealWidth()));
|
const auto ideal = int(std::ceil(emojiDoc->idealWidth()));
|
||||||
const auto width = std::clamp(
|
const auto width = std::clamp(
|
||||||
ideal + 2,
|
ideal + kIdealWidthExtra,
|
||||||
minTextWidth,
|
minTextWidth,
|
||||||
maxTextWidth);
|
maxTextWidth);
|
||||||
proxy->setTextWidth(width);
|
proxy->setTextWidth(width);
|
||||||
@@ -623,7 +635,7 @@ void Scene::startTextEditing(ItemText *item) {
|
|||||||
|
|
||||||
const auto scale = item->editScale();
|
const auto scale = item->editScale();
|
||||||
proxy->setRotation(item->rotation());
|
proxy->setRotation(item->rotation());
|
||||||
if (std::abs(scale - 1.) > 0.01) {
|
if (std::abs(scale - 1.) > kScaleThreshold) {
|
||||||
proxy->setScale(scale);
|
proxy->setScale(scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -637,14 +649,19 @@ void Scene::startTextEditing(ItemText *item) {
|
|||||||
|
|
||||||
item->setVisible(false);
|
item->setVisible(false);
|
||||||
|
|
||||||
proxy->onFinish = crl::guard(this, [=] {
|
const auto raw = static_cast<TextEditProxy*>(proxy);
|
||||||
|
raw->onFinish = crl::guard(this, [=] {
|
||||||
finishTextEditing(true);
|
finishTextEditing(true);
|
||||||
});
|
});
|
||||||
proxy->onCancel = crl::guard(this, [=] {
|
raw->onCancel = crl::guard(this, [=] {
|
||||||
finishTextEditing(false);
|
finishTextEditing(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
_textEdit = { .item = item, .proxy = proxy };
|
const auto it = _itemsByPointer.find(item);
|
||||||
|
_textEdit.item = (it != end(_itemsByPointer))
|
||||||
|
? it->second
|
||||||
|
: std::weak_ptr<NumberedItem>();
|
||||||
|
_textColorRequests.fire_copy(item->color());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scene::finishTextEditing(bool save) {
|
void Scene::finishTextEditing(bool save) {
|
||||||
@@ -658,15 +675,19 @@ void Scene::finishTextEditing(bool save) {
|
|||||||
const auto proxyRect = _textEdit.proxy->boundingRect();
|
const auto proxyRect = _textEdit.proxy->boundingRect();
|
||||||
const auto proxyCenter = _textEdit.proxy->pos()
|
const auto proxyCenter = _textEdit.proxy->pos()
|
||||||
+ QPointF(proxyRect.width() / 2., proxyRect.height() / 2.);
|
+ QPointF(proxyRect.width() / 2., proxyRect.height() / 2.);
|
||||||
auto *existingItem = _textEdit.item;
|
const auto lockedItem = _textEdit.item.lock();
|
||||||
|
auto *existingItem = lockedItem
|
||||||
|
? static_cast<ItemText*>(lockedItem.get())
|
||||||
|
: (ItemText*)(nullptr);
|
||||||
|
|
||||||
const auto proxy = static_cast<TextEditProxy*>(_textEdit.proxy);
|
const auto raw = static_cast<TextEditProxy*>(_textEdit.proxy.get());
|
||||||
proxy->onFinish = nullptr;
|
raw->onFinish = nullptr;
|
||||||
proxy->onCancel = nullptr;
|
raw->onCancel = nullptr;
|
||||||
QGraphicsScene::removeItem(proxy);
|
QGraphicsScene::removeItem(_textEdit.proxy.get());
|
||||||
delete proxy;
|
|
||||||
_textEdit.proxy = nullptr;
|
_textEdit.proxy = nullptr;
|
||||||
_textEdit.item = nullptr;
|
_textEdit.item.reset();
|
||||||
|
|
||||||
|
const auto defaultStyle = static_cast<TextStyle>(_textStyle);
|
||||||
|
|
||||||
if (!text.isEmpty()) {
|
if (!text.isEmpty()) {
|
||||||
if (existingItem) {
|
if (existingItem) {
|
||||||
@@ -678,7 +699,7 @@ void Scene::finishTextEditing(bool save) {
|
|||||||
text,
|
text,
|
||||||
_textFontSize,
|
_textFontSize,
|
||||||
imageSize,
|
imageSize,
|
||||||
TextStyle::Plain);
|
defaultStyle);
|
||||||
const auto zoom = (_currentZoom > 0.) ? _currentZoom : 1.;
|
const auto zoom = (_currentZoom > 0.) ? _currentZoom : 1.;
|
||||||
const auto handleInflate = int(
|
const auto handleInflate = int(
|
||||||
std::ceil(st::photoEditorItemHandleSize / zoom));
|
std::ceil(st::photoEditorItemHandleSize / zoom));
|
||||||
@@ -697,7 +718,7 @@ void Scene::finishTextEditing(bool save) {
|
|||||||
text,
|
text,
|
||||||
_textColor,
|
_textColor,
|
||||||
_textFontSize,
|
_textFontSize,
|
||||||
TextStyle::Plain,
|
defaultStyle,
|
||||||
imageSize,
|
imageSize,
|
||||||
std::move(data));
|
std::move(data));
|
||||||
addItem(item);
|
addItem(item);
|
||||||
@@ -713,17 +734,15 @@ void Scene::finishTextEditing(bool save) {
|
|||||||
|
|
||||||
Scene::~Scene() {
|
Scene::~Scene() {
|
||||||
if (_textEdit.proxy) {
|
if (_textEdit.proxy) {
|
||||||
const auto proxy = static_cast<TextEditProxy*>(_textEdit.proxy);
|
const auto raw = static_cast<TextEditProxy*>(
|
||||||
proxy->onFinish = nullptr;
|
_textEdit.proxy.get());
|
||||||
proxy->onCancel = nullptr;
|
raw->onFinish = nullptr;
|
||||||
QGraphicsScene::removeItem(proxy);
|
raw->onCancel = nullptr;
|
||||||
delete proxy;
|
QGraphicsScene::removeItem(_textEdit.proxy.get());
|
||||||
_textEdit.proxy = nullptr;
|
_textEdit.proxy = nullptr;
|
||||||
}
|
}
|
||||||
// Prevent destroying by scene of all items.
|
|
||||||
QGraphicsScene::removeItem(_canvas.get());
|
QGraphicsScene::removeItem(_canvas.get());
|
||||||
for (const auto &item : items()) {
|
for (const auto &item : items()) {
|
||||||
// Scene loses ownership of an item.
|
|
||||||
QGraphicsScene::removeItem(item.get());
|
QGraphicsScene::removeItem(item.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ public:
|
|||||||
|
|
||||||
Scene(const QRectF &rect);
|
Scene(const QRectF &rect);
|
||||||
~Scene();
|
~Scene();
|
||||||
void applyBrush(const QColor &color, float size, Brush::Tool tool);
|
void applyBrush(const QColor &color, float64 size, Brush::Tool tool);
|
||||||
void setBlurSource(Fn<QImage(QRect)> source);
|
void setBlurSource(Fn<QImage(QRect)> source);
|
||||||
void setTextDefaults(const QColor &color, float fontSize, int style);
|
void setTextDefaults(const QColor &color, float64 fontSize, int style);
|
||||||
|
|
||||||
[[nodiscard]] std::vector<ItemPtr> items(
|
[[nodiscard]] std::vector<ItemPtr> items(
|
||||||
Qt::SortOrder order = Qt::DescendingOrder) const;
|
Qt::SortOrder order = Qt::DescendingOrder) const;
|
||||||
@@ -51,6 +51,9 @@ public:
|
|||||||
|
|
||||||
void startTextEditing(ItemText *item);
|
void startTextEditing(ItemText *item);
|
||||||
void createTextAtCenter();
|
void createTextAtCenter();
|
||||||
|
void setTextColor(const QColor &color);
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<QColor> textColorRequests() const;
|
||||||
|
|
||||||
[[nodiscard]] bool hasUndo() const;
|
[[nodiscard]] bool hasUndo() const;
|
||||||
[[nodiscard]] bool hasRedo() const;
|
[[nodiscard]] bool hasRedo() const;
|
||||||
@@ -69,6 +72,10 @@ protected:
|
|||||||
private:
|
private:
|
||||||
void removeIf(Fn<bool(const ItemPtr &)> proj);
|
void removeIf(Fn<bool(const ItemPtr &)> proj);
|
||||||
void finishTextEditing(bool save);
|
void finishTextEditing(bool save);
|
||||||
|
void setupTextProxy(
|
||||||
|
QGraphicsTextItem *proxy,
|
||||||
|
const QColor &color,
|
||||||
|
float64 fontSize);
|
||||||
|
|
||||||
const std::shared_ptr<ItemCanvas> _canvas;
|
const std::shared_ptr<ItemCanvas> _canvas;
|
||||||
const std::shared_ptr<float64> _lastZ;
|
const std::shared_ptr<float64> _lastZ;
|
||||||
@@ -82,15 +89,16 @@ private:
|
|||||||
int _itemNumber = 0;
|
int _itemNumber = 0;
|
||||||
|
|
||||||
QColor _textColor;
|
QColor _textColor;
|
||||||
float _textFontSize = 0.f;
|
float64 _textFontSize = 0.;
|
||||||
int _textStyle = 3;
|
int _textStyle = 0;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
ItemText *item = nullptr;
|
std::weak_ptr<NumberedItem> item;
|
||||||
QGraphicsTextItem *proxy = nullptr;
|
base::unique_qptr<QGraphicsTextItem> proxy;
|
||||||
} _textEdit;
|
} _textEdit;
|
||||||
|
|
||||||
rpl::event_stream<> _addsItem, _removesItem;
|
rpl::event_stream<> _addsItem, _removesItem;
|
||||||
|
rpl::event_stream<QColor> _textColorRequests;
|
||||||
rpl::lifetime _lifetime;
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
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_emoji_document.h"
|
||||||
|
|
||||||
|
#include "ui/emoji_config.h"
|
||||||
|
#include "ui/painter.h"
|
||||||
|
|
||||||
|
#include <QTextBlock>
|
||||||
|
#include <QTextCursor>
|
||||||
|
|
||||||
|
namespace Editor {
|
||||||
|
|
||||||
|
EmojiDocument::EmojiDocument(QObject *parent)
|
||||||
|
: QTextDocument(parent) {
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant EmojiDocument::loadResource(int type, const QUrl &name) {
|
||||||
|
if (type != QTextDocument::ImageResource
|
||||||
|
|| name.scheme() != u"emoji"_q) {
|
||||||
|
return QTextDocument::loadResource(type, name);
|
||||||
|
}
|
||||||
|
const auto i = _cache.find(name);
|
||||||
|
if (i != _cache.end()) {
|
||||||
|
return i->second;
|
||||||
|
}
|
||||||
|
auto result = QVariant();
|
||||||
|
if (const auto emoji = Ui::Emoji::FromUrl(name.toDisplayString())) {
|
||||||
|
const auto factor = style::DevicePixelRatio();
|
||||||
|
const auto logical = QFontMetrics(defaultFont()).height();
|
||||||
|
const auto source = Ui::Emoji::GetSizeLarge();
|
||||||
|
auto image = QImage(
|
||||||
|
QSize(logical, logical) * factor,
|
||||||
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
|
image.setDevicePixelRatio(factor);
|
||||||
|
image.fill(Qt::transparent);
|
||||||
|
{
|
||||||
|
auto p = QPainter(&image);
|
||||||
|
auto hq = PainterHighQualityEnabler(p);
|
||||||
|
const auto sourceLogical = source / float64(factor);
|
||||||
|
const auto scale = logical / sourceLogical;
|
||||||
|
p.scale(scale, scale);
|
||||||
|
Ui::Emoji::Draw(p, emoji, source, 0, 0);
|
||||||
|
}
|
||||||
|
result = QVariant(QPixmap::fromImage(std::move(image)));
|
||||||
|
}
|
||||||
|
_cache.emplace(name, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReplaceEmoji(QTextDocument *doc) {
|
||||||
|
QSignalBlocker blocker(doc);
|
||||||
|
const auto fontHeight = QFontMetrics(doc->defaultFont()).height();
|
||||||
|
auto cursor = QTextCursor(doc);
|
||||||
|
auto block = doc->begin();
|
||||||
|
while (block.isValid()) {
|
||||||
|
auto text = block.text();
|
||||||
|
auto start = text.constData();
|
||||||
|
auto end = start + text.size();
|
||||||
|
auto ch = start;
|
||||||
|
while (ch < end) {
|
||||||
|
auto emojiLength = 0;
|
||||||
|
const auto emoji = Ui::Emoji::Find(ch, end, &emojiLength);
|
||||||
|
if (!emoji || emojiLength <= 0) {
|
||||||
|
++ch;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto pos = block.position() + int(ch - start);
|
||||||
|
cursor.setPosition(pos);
|
||||||
|
cursor.setPosition(
|
||||||
|
pos + emojiLength,
|
||||||
|
QTextCursor::KeepAnchor);
|
||||||
|
|
||||||
|
auto format = QTextImageFormat();
|
||||||
|
format.setName(emoji->toUrl());
|
||||||
|
format.setWidth(fontHeight);
|
||||||
|
format.setHeight(fontHeight);
|
||||||
|
format.setVerticalAlignment(
|
||||||
|
QTextCharFormat::AlignBaseline);
|
||||||
|
cursor.insertImage(format);
|
||||||
|
|
||||||
|
block = doc->findBlock(pos);
|
||||||
|
text = block.text();
|
||||||
|
start = text.constData();
|
||||||
|
end = start + text.size();
|
||||||
|
ch = start + (pos - block.position()) + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
block = block.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString RecoverTextFromDocument(QTextDocument *doc) {
|
||||||
|
auto result = QString();
|
||||||
|
auto block = doc->begin();
|
||||||
|
while (block.isValid()) {
|
||||||
|
if (block != doc->begin()) {
|
||||||
|
result += '\n';
|
||||||
|
}
|
||||||
|
auto it = block.begin();
|
||||||
|
while (!it.atEnd()) {
|
||||||
|
const auto fragment = it.fragment();
|
||||||
|
if (!fragment.isValid()) {
|
||||||
|
++it;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto text = fragment.text();
|
||||||
|
const auto format = fragment.charFormat();
|
||||||
|
for (const auto &ch : text) {
|
||||||
|
if (ch == QChar::ObjectReplacementCharacter) {
|
||||||
|
if (format.isImageFormat()) {
|
||||||
|
const auto name = format.toImageFormat().name();
|
||||||
|
if (const auto emoji = Ui::Emoji::FromUrl(name)) {
|
||||||
|
result += emoji->text();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result += ch;
|
||||||
|
}
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
block = block.next();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Editor
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
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 <QTextDocument>
|
||||||
|
|
||||||
|
namespace Editor {
|
||||||
|
|
||||||
|
class EmojiDocument final : public QTextDocument {
|
||||||
|
public:
|
||||||
|
explicit EmojiDocument(QObject *parent = nullptr);
|
||||||
|
QVariant loadResource(int type, const QUrl &name) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<QUrl, QVariant> _cache;
|
||||||
|
};
|
||||||
|
|
||||||
|
void ReplaceEmoji(QTextDocument *doc);
|
||||||
|
[[nodiscard]] QString RecoverTextFromDocument(QTextDocument *doc);
|
||||||
|
|
||||||
|
} // namespace Editor
|
||||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "editor/scene/scene_item_text.h"
|
#include "editor/scene/scene_item_text.h"
|
||||||
|
|
||||||
#include "editor/scene/scene.h"
|
#include "editor/scene/scene.h"
|
||||||
|
#include "editor/scene/scene_emoji_document.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "ui/emoji_config.h"
|
#include "ui/emoji_config.h"
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
@@ -26,9 +27,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
namespace Editor {
|
namespace Editor {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kPaddingFactor = 0.4f;
|
constexpr auto kPaddingFactor = 0.4;
|
||||||
constexpr auto kMaxWidthFactor = 0.8f;
|
constexpr auto kMaxWidthFactor = 0.8;
|
||||||
constexpr auto kMinContentWidth = 20;
|
constexpr auto kMinContentWidth = 20;
|
||||||
|
constexpr auto kBrightnessFramedThreshold = 0.721;
|
||||||
|
constexpr auto kBrightnessSemiTransparentThreshold = 0.25;
|
||||||
|
constexpr auto kSemiTransparentAlpha = 0x99;
|
||||||
|
constexpr auto kCornerRadiusFactor = 1. / 3.;
|
||||||
|
constexpr auto kLinePadHFactor = 1. / 3.;
|
||||||
|
constexpr auto kLinePadVFactor = 1. / 8.;
|
||||||
|
constexpr auto kMergeRadiusFactor = 1.5;
|
||||||
|
constexpr auto kLineShiftFactor = 1. / 7.;
|
||||||
|
|
||||||
struct LayoutMetrics {
|
struct LayoutMetrics {
|
||||||
int contentWidth = 0;
|
int contentWidth = 0;
|
||||||
@@ -37,22 +46,22 @@ struct LayoutMetrics {
|
|||||||
int textMaxWidth = 0;
|
int textMaxWidth = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
QFont TextFont(float fontSize) {
|
QFont TextFont(float64 fontSize) {
|
||||||
auto font = QFont();
|
auto font = QFont();
|
||||||
font.setPixelSize(std::max(int(fontSize), 1));
|
font.setPixelSize(std::max(int(fontSize), 1));
|
||||||
font.setWeight(QFont::DemiBold);
|
font.setWeight(QFont::DemiBold);
|
||||||
return font;
|
return font;
|
||||||
}
|
}
|
||||||
|
|
||||||
float ComputeBrightness(const QColor &color) {
|
float64 ComputeBrightness(const QColor &color) {
|
||||||
return (color.red() * 0.2126f
|
return (color.red() * 0.2126
|
||||||
+ color.green() * 0.7152f
|
+ color.green() * 0.7152
|
||||||
+ color.blue() * 0.0722f) / 255.f;
|
+ color.blue() * 0.0722) / 255.;
|
||||||
}
|
}
|
||||||
|
|
||||||
LayoutMetrics ComputeMetrics(
|
LayoutMetrics ComputeMetrics(
|
||||||
const QString &text,
|
const QString &text,
|
||||||
float fontSize,
|
float64 fontSize,
|
||||||
const QSize &imageSize,
|
const QSize &imageSize,
|
||||||
TextStyle style) {
|
TextStyle style) {
|
||||||
const auto hasBackground = (style == TextStyle::Framed)
|
const auto hasBackground = (style == TextStyle::Framed)
|
||||||
@@ -96,33 +105,33 @@ LayoutMetrics ComputeMetrics(
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct LineRect {
|
struct LineRect {
|
||||||
float left = 0;
|
float64 left = 0;
|
||||||
float top = 0;
|
float64 top = 0;
|
||||||
float right = 0;
|
float64 right = 0;
|
||||||
float bottom = 0;
|
float64 bottom = 0;
|
||||||
[[nodiscard]] float width() const { return right - left; }
|
[[nodiscard]] float64 width() const { return right - left; }
|
||||||
};
|
};
|
||||||
|
|
||||||
QPainterPath BuildConnectedBackground(
|
QPainterPath BuildConnectedBackground(
|
||||||
const QTextLayout &layout,
|
const QTextLayout &layout,
|
||||||
int contentWidth,
|
int contentWidth,
|
||||||
int padding,
|
int padding,
|
||||||
float fontSize) {
|
float64 fontSize) {
|
||||||
const auto linePadH = fontSize / 3.f;
|
const auto linePadH = fontSize * kLinePadHFactor;
|
||||||
const auto linePadV = fontSize / 8.f;
|
const auto linePadV = fontSize * kLinePadVFactor;
|
||||||
const auto cornerRadius = fontSize / 3.f;
|
const auto cornerRadius = fontSize * kCornerRadiusFactor;
|
||||||
const auto mergeRadius = cornerRadius * 1.5f;
|
const auto mergeRadius = cornerRadius * kMergeRadiusFactor;
|
||||||
const auto centerX = padding + contentWidth / 2.f;
|
const auto centerX = padding + contentWidth / 2.;
|
||||||
|
|
||||||
auto rects = std::vector<LineRect>();
|
auto rects = std::vector<LineRect>();
|
||||||
for (auto i = 0; i < layout.lineCount(); ++i) {
|
for (auto i = 0; i < layout.lineCount(); ++i) {
|
||||||
const auto line = layout.lineAt(i);
|
const auto line = layout.lineAt(i);
|
||||||
const auto hw = float(line.naturalTextWidth()) / 2.f + linePadH;
|
const auto hw = float64(line.naturalTextWidth()) / 2. + linePadH;
|
||||||
rects.push_back({
|
rects.push_back({
|
||||||
.left = centerX - hw,
|
.left = centerX - hw,
|
||||||
.top = padding + float(line.y()) - linePadV,
|
.top = padding + float64(line.y()) - linePadV,
|
||||||
.right = centerX + hw,
|
.right = centerX + hw,
|
||||||
.bottom = padding + float(line.y() + line.height()) + linePadV,
|
.bottom = padding + float64(line.y() + line.height()) + linePadV,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +166,8 @@ QPainterPath BuildConnectedBackground(
|
|||||||
}
|
}
|
||||||
if (traceback) {
|
if (traceback) {
|
||||||
for (auto j = i; j >= 1; --j) {
|
for (auto j = i; j >= 1; --j) {
|
||||||
if (std::abs(rects[j - 1].left - rects[j].left) < mergeRadius) {
|
if (std::abs(rects[j - 1].left - rects[j].left)
|
||||||
|
< mergeRadius) {
|
||||||
const auto v = std::min(
|
const auto v = std::min(
|
||||||
rects[j - 1].left,
|
rects[j - 1].left,
|
||||||
rects[j].left);
|
rects[j].left);
|
||||||
@@ -174,14 +184,14 @@ QPainterPath BuildConnectedBackground(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct V { float x, y; };
|
struct V { float64 x, y; };
|
||||||
auto verts = std::vector<V>();
|
auto verts = std::vector<V>();
|
||||||
|
|
||||||
verts.push_back({ rects[0].left, rects[0].top });
|
verts.push_back({ rects[0].left, rects[0].top });
|
||||||
verts.push_back({ rects[0].right, rects[0].top });
|
verts.push_back({ rects[0].right, rects[0].top });
|
||||||
|
|
||||||
for (auto i = 1; i < int(rects.size()); ++i) {
|
for (auto i = 1; i < int(rects.size()); ++i) {
|
||||||
if (std::abs(rects[i].right - rects[i - 1].right) > 0.5f) {
|
if (std::abs(rects[i].right - rects[i - 1].right) > 0.5) {
|
||||||
verts.push_back({ rects[i - 1].right, rects[i].top });
|
verts.push_back({ rects[i - 1].right, rects[i].top });
|
||||||
verts.push_back({ rects[i].right, rects[i].top });
|
verts.push_back({ rects[i].right, rects[i].top });
|
||||||
}
|
}
|
||||||
@@ -192,7 +202,7 @@ QPainterPath BuildConnectedBackground(
|
|||||||
verts.push_back({ rects[last].left, rects[last].bottom });
|
verts.push_back({ rects[last].left, rects[last].bottom });
|
||||||
|
|
||||||
for (auto i = last - 1; i >= 0; --i) {
|
for (auto i = last - 1; i >= 0; --i) {
|
||||||
if (std::abs(rects[i].left - rects[i + 1].left) > 0.5f) {
|
if (std::abs(rects[i].left - rects[i + 1].left) > 0.5) {
|
||||||
verts.push_back({ rects[i + 1].left, rects[i + 1].top });
|
verts.push_back({ rects[i + 1].left, rects[i + 1].top });
|
||||||
verts.push_back({ rects[i].left, rects[i + 1].top });
|
verts.push_back({ rects[i].left, rects[i + 1].top });
|
||||||
}
|
}
|
||||||
@@ -213,7 +223,7 @@ QPainterPath BuildConnectedBackground(
|
|||||||
const auto dy2 = next.y - curr.y;
|
const auto dy2 = next.y - curr.y;
|
||||||
const auto len2 = std::sqrt(dx2 * dx2 + dy2 * dy2);
|
const auto len2 = std::sqrt(dx2 * dx2 + dy2 * dy2);
|
||||||
|
|
||||||
if (len1 < 0.1f || len2 < 0.1f) {
|
if (len1 < 0.1 || len2 < 0.1) {
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
path.moveTo(curr.x, curr.y);
|
path.moveTo(curr.x, curr.y);
|
||||||
} else {
|
} else {
|
||||||
@@ -224,8 +234,8 @@ QPainterPath BuildConnectedBackground(
|
|||||||
|
|
||||||
const auto r = std::min({
|
const auto r = std::min({
|
||||||
cornerRadius,
|
cornerRadius,
|
||||||
len1 / 2.f,
|
len1 / 2.,
|
||||||
len2 / 2.f,
|
len2 / 2.,
|
||||||
});
|
});
|
||||||
const auto bx = curr.x - dx1 / len1 * r;
|
const auto bx = curr.x - dx1 / len1 * r;
|
||||||
const auto by = curr.y - dy1 / len1 * r;
|
const auto by = curr.y - dy1 / len1 * r;
|
||||||
@@ -245,126 +255,10 @@ QPainterPath BuildConnectedBackground(
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
EmojiDocument::EmojiDocument(QObject *parent)
|
|
||||||
: QTextDocument(parent) {
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant EmojiDocument::loadResource(int type, const QUrl &name) {
|
|
||||||
if (type != QTextDocument::ImageResource
|
|
||||||
|| name.scheme() != u"emoji"_q) {
|
|
||||||
return QTextDocument::loadResource(type, name);
|
|
||||||
}
|
|
||||||
const auto i = _cache.find(name);
|
|
||||||
if (i != _cache.end()) {
|
|
||||||
return i->second;
|
|
||||||
}
|
|
||||||
auto result = QVariant();
|
|
||||||
if (const auto emoji = Ui::Emoji::FromUrl(name.toDisplayString())) {
|
|
||||||
const auto factor = style::DevicePixelRatio();
|
|
||||||
const auto logical = QFontMetrics(defaultFont()).height();
|
|
||||||
const auto source = Ui::Emoji::GetSizeLarge();
|
|
||||||
auto image = QImage(
|
|
||||||
QSize(logical, logical) * factor,
|
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
|
||||||
image.setDevicePixelRatio(factor);
|
|
||||||
image.fill(Qt::transparent);
|
|
||||||
{
|
|
||||||
auto p = QPainter(&image);
|
|
||||||
auto hq = PainterHighQualityEnabler(p);
|
|
||||||
const auto enlarged = logical * 1.0;
|
|
||||||
const auto sourceLogical = source / float64(factor);
|
|
||||||
const auto scale = enlarged / sourceLogical;
|
|
||||||
const auto offset = (logical - enlarged) / 2.;
|
|
||||||
p.translate(offset, offset);
|
|
||||||
p.scale(scale, scale);
|
|
||||||
Ui::Emoji::Draw(p, emoji, source, 0, 0);
|
|
||||||
}
|
|
||||||
result = QVariant(QPixmap::fromImage(std::move(image)));
|
|
||||||
}
|
|
||||||
_cache.emplace(name, result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReplaceEmoji(QTextDocument *doc) {
|
|
||||||
const auto fontHeight = QFontMetrics(doc->defaultFont()).height();
|
|
||||||
auto cursor = QTextCursor(doc);
|
|
||||||
auto block = doc->begin();
|
|
||||||
while (block.isValid()) {
|
|
||||||
auto text = block.text();
|
|
||||||
auto start = text.constData();
|
|
||||||
auto end = start + text.size();
|
|
||||||
auto ch = start;
|
|
||||||
while (ch < end) {
|
|
||||||
auto emojiLength = 0;
|
|
||||||
const auto emoji = Ui::Emoji::Find(ch, end, &emojiLength);
|
|
||||||
if (!emoji) {
|
|
||||||
++ch;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const auto pos = block.position() + int(ch - start);
|
|
||||||
cursor.setPosition(pos);
|
|
||||||
cursor.setPosition(
|
|
||||||
pos + emojiLength,
|
|
||||||
QTextCursor::KeepAnchor);
|
|
||||||
|
|
||||||
auto format = QTextImageFormat();
|
|
||||||
format.setName(emoji->toUrl());
|
|
||||||
format.setWidth(fontHeight);
|
|
||||||
format.setHeight(fontHeight);
|
|
||||||
format.setVerticalAlignment(
|
|
||||||
QTextCharFormat::AlignBaseline);
|
|
||||||
cursor.insertImage(format);
|
|
||||||
|
|
||||||
block = doc->findBlock(pos);
|
|
||||||
text = block.text();
|
|
||||||
start = text.constData();
|
|
||||||
end = start + text.size();
|
|
||||||
ch = start + (pos - block.position()) + 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
block = block.next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QString RecoverTextFromDocument(QTextDocument *doc) {
|
|
||||||
auto result = QString();
|
|
||||||
auto block = doc->begin();
|
|
||||||
while (block.isValid()) {
|
|
||||||
if (block != doc->begin()) {
|
|
||||||
result += '\n';
|
|
||||||
}
|
|
||||||
auto it = block.begin();
|
|
||||||
while (!it.atEnd()) {
|
|
||||||
const auto fragment = it.fragment();
|
|
||||||
if (!fragment.isValid()) {
|
|
||||||
++it;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const auto text = fragment.text();
|
|
||||||
const auto format = fragment.charFormat();
|
|
||||||
for (const auto &ch : text) {
|
|
||||||
if (ch == QChar::ObjectReplacementCharacter) {
|
|
||||||
if (format.isImageFormat()) {
|
|
||||||
const auto name = format.toImageFormat().name();
|
|
||||||
if (const auto emoji = Ui::Emoji::FromUrl(name)) {
|
|
||||||
result += emoji->text();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result += ch;
|
|
||||||
}
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
block = block.next();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemText::ItemText(
|
ItemText::ItemText(
|
||||||
const QString &text,
|
const QString &text,
|
||||||
const QColor &color,
|
const QColor &color,
|
||||||
float fontSize,
|
float64 fontSize,
|
||||||
TextStyle style,
|
TextStyle style,
|
||||||
const QSize &imageSize,
|
const QSize &imageSize,
|
||||||
ItemBase::Data data)
|
ItemBase::Data data)
|
||||||
@@ -445,14 +339,14 @@ void ItemText::renderContent() {
|
|||||||
switch (_textStyle) {
|
switch (_textStyle) {
|
||||||
case TextStyle::Framed:
|
case TextStyle::Framed:
|
||||||
bgColor = _color;
|
bgColor = _color;
|
||||||
textColor = (brightness >= 0.721f)
|
textColor = (brightness >= kBrightnessFramedThreshold)
|
||||||
? QColor(0, 0, 0)
|
? QColor(0, 0, 0)
|
||||||
: QColor(255, 255, 255);
|
: QColor(255, 255, 255);
|
||||||
break;
|
break;
|
||||||
case TextStyle::SemiTransparent:
|
case TextStyle::SemiTransparent:
|
||||||
bgColor = (brightness >= 0.25f)
|
bgColor = (brightness >= kBrightnessSemiTransparentThreshold)
|
||||||
? QColor(0, 0, 0, 0x99)
|
? QColor(0, 0, 0, kSemiTransparentAlpha)
|
||||||
: QColor(255, 255, 255, 0x99);
|
: QColor(255, 255, 255, kSemiTransparentAlpha);
|
||||||
break;
|
break;
|
||||||
case TextStyle::Plain:
|
case TextStyle::Plain:
|
||||||
break;
|
break;
|
||||||
@@ -496,14 +390,14 @@ void ItemText::renderContent() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto lineShift = _fontSize / 7.f;
|
const auto lineShift = _fontSize * kLineShiftFactor;
|
||||||
const auto lineCount = layout.lineCount();
|
const auto lineCount = layout.lineCount();
|
||||||
p.setPen(textColor);
|
p.setPen(textColor);
|
||||||
for (auto i = 0; i < lineCount; ++i) {
|
for (auto i = 0; i < lineCount; ++i) {
|
||||||
const auto line = layout.lineAt(i);
|
const auto line = layout.lineAt(i);
|
||||||
const auto xOffset =
|
const auto xOffset =
|
||||||
(m.contentWidth - line.naturalTextWidth()) / 2.;
|
(m.contentWidth - line.naturalTextWidth()) / 2.;
|
||||||
const auto yShift = (i < lineCount - 1) ? -lineShift : 0.f;
|
const auto yShift = (i < lineCount - 1) ? -lineShift : 0.;
|
||||||
line.draw(
|
line.draw(
|
||||||
&p,
|
&p,
|
||||||
QPointF(m.padding + xOffset, m.padding + yShift));
|
QPointF(m.padding + xOffset, m.padding + yShift));
|
||||||
@@ -519,7 +413,7 @@ void ItemText::renderContent() {
|
|||||||
const auto line = layout.lineAt(i);
|
const auto line = layout.lineAt(i);
|
||||||
const auto xOffset =
|
const auto xOffset =
|
||||||
(m.contentWidth - line.naturalTextWidth()) / 2.;
|
(m.contentWidth - line.naturalTextWidth()) / 2.;
|
||||||
const auto yShift = (i < lineCount - 1) ? -lineShift : 0.f;
|
const auto yShift = (i < lineCount - 1) ? -lineShift : 0.;
|
||||||
const auto lineStart = line.textStart();
|
const auto lineStart = line.textStart();
|
||||||
const auto lineText = processedText.mid(
|
const auto lineText = processedText.mid(
|
||||||
lineStart,
|
lineStart,
|
||||||
@@ -567,7 +461,7 @@ void ItemText::renderContent() {
|
|||||||
|
|
||||||
QSize ItemText::computeContentSize(
|
QSize ItemText::computeContentSize(
|
||||||
const QString &text,
|
const QString &text,
|
||||||
float fontSize,
|
float64 fontSize,
|
||||||
const QSize &imageSize,
|
const QSize &imageSize,
|
||||||
TextStyle style) {
|
TextStyle style) {
|
||||||
if (text.isEmpty()) {
|
if (text.isEmpty()) {
|
||||||
@@ -635,7 +529,7 @@ void ItemText::setColor(const QColor &color) {
|
|||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
float ItemText::fontSize() const {
|
float64 ItemText::fontSize() const {
|
||||||
return _fontSize;
|
return _fontSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -685,9 +579,13 @@ void ItemText::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) {
|
|||||||
action->setChecked(true);
|
action->setChecked(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
add(u"Plain"_q, TextStyle::Plain);
|
add(tr::lng_photo_editor_text_style_plain(tr::now), TextStyle::Plain);
|
||||||
add(u"Framed"_q, TextStyle::Framed);
|
add(
|
||||||
add(u"Semi-Transparent"_q, TextStyle::SemiTransparent);
|
tr::lng_photo_editor_text_style_framed(tr::now),
|
||||||
|
TextStyle::Framed);
|
||||||
|
add(
|
||||||
|
tr::lng_photo_editor_text_style_semi_transparent(tr::now),
|
||||||
|
TextStyle::SemiTransparent);
|
||||||
|
|
||||||
_contextMenu->addSeparator();
|
_contextMenu->addSeparator();
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "base/unique_qptr.h"
|
#include "base/unique_qptr.h"
|
||||||
#include "editor/scene/scene_item_base.h"
|
#include "editor/scene/scene_item_base.h"
|
||||||
|
|
||||||
#include <QTextDocument>
|
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class PopupMenu;
|
class PopupMenu;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
@@ -24,18 +22,6 @@ enum class TextStyle : uchar {
|
|||||||
Plain,
|
Plain,
|
||||||
};
|
};
|
||||||
|
|
||||||
class EmojiDocument final : public QTextDocument {
|
|
||||||
public:
|
|
||||||
explicit EmojiDocument(QObject *parent = nullptr);
|
|
||||||
QVariant loadResource(int type, const QUrl &name) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::map<QUrl, QVariant> _cache;
|
|
||||||
};
|
|
||||||
|
|
||||||
void ReplaceEmoji(QTextDocument *doc);
|
|
||||||
[[nodiscard]] QString RecoverTextFromDocument(QTextDocument *doc);
|
|
||||||
|
|
||||||
class ItemText : public ItemBase {
|
class ItemText : public ItemBase {
|
||||||
public:
|
public:
|
||||||
enum { Type = ItemBase::Type + 2 };
|
enum { Type = ItemBase::Type + 2 };
|
||||||
@@ -43,7 +29,7 @@ public:
|
|||||||
ItemText(
|
ItemText(
|
||||||
const QString &text,
|
const QString &text,
|
||||||
const QColor &color,
|
const QColor &color,
|
||||||
float fontSize,
|
float64 fontSize,
|
||||||
TextStyle style,
|
TextStyle style,
|
||||||
const QSize &imageSize,
|
const QSize &imageSize,
|
||||||
ItemBase::Data data);
|
ItemBase::Data data);
|
||||||
@@ -60,7 +46,7 @@ public:
|
|||||||
[[nodiscard]] const QColor &color() const;
|
[[nodiscard]] const QColor &color() const;
|
||||||
void setColor(const QColor &color);
|
void setColor(const QColor &color);
|
||||||
|
|
||||||
[[nodiscard]] float fontSize() const;
|
[[nodiscard]] float64 fontSize() const;
|
||||||
|
|
||||||
[[nodiscard]] TextStyle textStyle() const;
|
[[nodiscard]] TextStyle textStyle() const;
|
||||||
void setTextStyle(TextStyle style);
|
void setTextStyle(TextStyle style);
|
||||||
@@ -69,7 +55,7 @@ public:
|
|||||||
|
|
||||||
[[nodiscard]] static QSize computeContentSize(
|
[[nodiscard]] static QSize computeContentSize(
|
||||||
const QString &text,
|
const QString &text,
|
||||||
float fontSize,
|
float64 fontSize,
|
||||||
const QSize &imageSize,
|
const QSize &imageSize,
|
||||||
TextStyle style);
|
TextStyle style);
|
||||||
|
|
||||||
@@ -88,7 +74,7 @@ private:
|
|||||||
|
|
||||||
QString _text;
|
QString _text;
|
||||||
QColor _color;
|
QColor _color;
|
||||||
float _fontSize;
|
float64 _fontSize;
|
||||||
TextStyle _textStyle = TextStyle::Plain;
|
TextStyle _textStyle = TextStyle::Plain;
|
||||||
QSize _imageSize;
|
QSize _imageSize;
|
||||||
QPixmap _pixmap;
|
QPixmap _pixmap;
|
||||||
@@ -99,7 +85,7 @@ private:
|
|||||||
NumberedItem::Status status = NumberedItem::Status::Normal;
|
NumberedItem::Status status = NumberedItem::Status::Normal;
|
||||||
QString text;
|
QString text;
|
||||||
QColor color;
|
QColor color;
|
||||||
float fontSize = 0;
|
float64 fontSize = 0.;
|
||||||
TextStyle textStyle = TextStyle::Plain;
|
TextStyle textStyle = TextStyle::Plain;
|
||||||
};
|
};
|
||||||
SavedText _savedState, _keepedState;
|
SavedText _savedState, _keepedState;
|
||||||
|
|||||||
@@ -149,6 +149,8 @@ PRIVATE
|
|||||||
editor/scene/scene_item_line.h
|
editor/scene/scene_item_line.h
|
||||||
editor/scene/scene_item_text.cpp
|
editor/scene/scene_item_text.cpp
|
||||||
editor/scene/scene_item_text.h
|
editor/scene/scene_item_text.h
|
||||||
|
editor/scene/scene_emoji_document.cpp
|
||||||
|
editor/scene/scene_emoji_document.h
|
||||||
|
|
||||||
ui/boxes/about_cocoon_box.h
|
ui/boxes/about_cocoon_box.h
|
||||||
ui/boxes/about_cocoon_box.cpp
|
ui/boxes/about_cocoon_box.cpp
|
||||||
|
|||||||
Reference in New Issue
Block a user