Add initial support for custom emoji.
This commit is contained in:
parent
b90d7ee27a
commit
87cd0b6127
7 changed files with 212 additions and 83 deletions
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
#include "ui/gl/gl_detection.h"
|
#include "ui/gl/gl_detection.h"
|
||||||
#include "ui/text/text_entity.h"
|
#include "ui/text/text_entity.h"
|
||||||
|
#include "ui/text/text_block.h"
|
||||||
#include "ui/basic_click_handlers.h"
|
#include "ui/basic_click_handlers.h"
|
||||||
#include "base/platform/base_platform_info.h"
|
#include "base/platform/base_platform_info.h"
|
||||||
|
|
||||||
|
|
@ -69,6 +70,12 @@ std::shared_ptr<ClickHandler> Integration::createLinkHandler(
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Text::CustomEmoji> Integration::createCustomEmoji(
|
||||||
|
const QString &data,
|
||||||
|
const std::any &context) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
bool Integration::handleUrlClick(
|
bool Integration::handleUrlClick(
|
||||||
const QString &url,
|
const QString &url,
|
||||||
const QVariant &context) {
|
const QVariant &context) {
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,10 @@ namespace Emoji {
|
||||||
class One;
|
class One;
|
||||||
} // namespace Emoji
|
} // namespace Emoji
|
||||||
|
|
||||||
|
namespace Text {
|
||||||
|
class CustomEmoji;
|
||||||
|
} // namespace Text
|
||||||
|
|
||||||
class Integration {
|
class Integration {
|
||||||
public:
|
public:
|
||||||
static void Set(not_null<Integration*> instance);
|
static void Set(not_null<Integration*> instance);
|
||||||
|
|
@ -56,6 +60,9 @@ public:
|
||||||
[[nodiscard]] virtual QString convertTagToMimeTag(const QString &tagId);
|
[[nodiscard]] virtual QString convertTagToMimeTag(const QString &tagId);
|
||||||
[[nodiscard]] virtual const Emoji::One *defaultEmojiVariant(
|
[[nodiscard]] virtual const Emoji::One *defaultEmojiVariant(
|
||||||
const Emoji::One *emoji);
|
const Emoji::One *emoji);
|
||||||
|
[[nodiscard]] virtual auto createCustomEmoji(
|
||||||
|
const QString &data,
|
||||||
|
const std::any &context) -> std::unique_ptr<Text::CustomEmoji>;
|
||||||
|
|
||||||
[[nodiscard]] virtual rpl::producer<> forcePopupMenuHideRequests();
|
[[nodiscard]] virtual rpl::producer<> forcePopupMenuHideRequests();
|
||||||
|
|
||||||
|
|
|
||||||
126
ui/text/text.cpp
126
ui/text/text.cpp
|
|
@ -272,6 +272,7 @@ private:
|
||||||
const QChar *_ptr = nullptr;
|
const QChar *_ptr = nullptr;
|
||||||
const EntitiesInText::const_iterator _entitiesEnd;
|
const EntitiesInText::const_iterator _entitiesEnd;
|
||||||
EntitiesInText::const_iterator _waitingEntity;
|
EntitiesInText::const_iterator _waitingEntity;
|
||||||
|
QString _customEmojiData;
|
||||||
const bool _multiline = false;
|
const bool _multiline = false;
|
||||||
|
|
||||||
const QFixed _stopAfterWidth; // summary width of all added words
|
const QFixed _stopAfterWidth; // summary width of all added words
|
||||||
|
|
@ -406,9 +407,16 @@ void Parser::createBlock(int32 skipBack) {
|
||||||
}
|
}
|
||||||
_lastSkipped = false;
|
_lastSkipped = false;
|
||||||
const auto lnkIndex = _monoIndex ? _monoIndex : _lnkIndex;
|
const auto lnkIndex = _monoIndex ? _monoIndex : _lnkIndex;
|
||||||
if (_emoji) {
|
auto custom = _customEmojiData.isEmpty()
|
||||||
|
? nullptr
|
||||||
|
: Integration::Instance().createCustomEmoji(
|
||||||
|
_customEmojiData,
|
||||||
|
_context);
|
||||||
|
if (custom) {
|
||||||
|
_t->_blocks.push_back(Block::CustomEmoji(_t->_st->font, _t->_text, _blockStart, len, _flags, lnkIndex, _spoilerIndex, std::move(custom)));
|
||||||
|
_lastSkipped = true;
|
||||||
|
} else if (_emoji) {
|
||||||
_t->_blocks.push_back(Block::Emoji(_t->_st->font, _t->_text, _blockStart, len, _flags, lnkIndex, _spoilerIndex, _emoji));
|
_t->_blocks.push_back(Block::Emoji(_t->_st->font, _t->_text, _blockStart, len, _flags, lnkIndex, _spoilerIndex, _emoji));
|
||||||
_emoji = nullptr;
|
|
||||||
_lastSkipped = true;
|
_lastSkipped = true;
|
||||||
} else if (newline) {
|
} else if (newline) {
|
||||||
_t->_blocks.push_back(Block::Newline(_t->_st->font, _t->_text, _blockStart, len, _flags, lnkIndex, _spoilerIndex));
|
_t->_blocks.push_back(Block::Newline(_t->_st->font, _t->_text, _blockStart, len, _flags, lnkIndex, _spoilerIndex));
|
||||||
|
|
@ -416,6 +424,8 @@ void Parser::createBlock(int32 skipBack) {
|
||||||
_t->_blocks.push_back(Block::Text(_t->_st->font, _t->_text, _t->_minResizeWidth, _blockStart, len, _flags, lnkIndex, _spoilerIndex));
|
_t->_blocks.push_back(Block::Text(_t->_st->font, _t->_text, _t->_minResizeWidth, _blockStart, len, _flags, lnkIndex, _spoilerIndex));
|
||||||
}
|
}
|
||||||
_blockStart += len;
|
_blockStart += len;
|
||||||
|
_customEmojiData = QByteArray();
|
||||||
|
_emoji = nullptr;
|
||||||
blockCreated();
|
blockCreated();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -499,7 +509,10 @@ bool Parser::checkEntities() {
|
||||||
link.data = _waitingEntity->data();
|
link.data = _waitingEntity->data();
|
||||||
link.text = QString(entityBegin, entityLength);
|
link.text = QString(entityBegin, entityLength);
|
||||||
};
|
};
|
||||||
if (entityType == EntityType::Bold) {
|
if (entityType == EntityType::CustomEmoji) {
|
||||||
|
createBlock();
|
||||||
|
_customEmojiData = _waitingEntity->data();
|
||||||
|
} else if (entityType == EntityType::Bold) {
|
||||||
flags = TextBlockFBold;
|
flags = TextBlockFBold;
|
||||||
} else if (entityType == EntityType::Semibold) {
|
} else if (entityType == EntityType::Semibold) {
|
||||||
flags = TextBlockFSemibold;
|
flags = TextBlockFSemibold;
|
||||||
|
|
@ -519,7 +532,8 @@ bool Parser::checkEntities() {
|
||||||
flags = TextBlockFPre;
|
flags = TextBlockFPre;
|
||||||
createBlock();
|
createBlock();
|
||||||
if (!_t->_blocks.empty()
|
if (!_t->_blocks.empty()
|
||||||
&& _t->_blocks.back()->type() != TextBlockTNewline) {
|
&& _t->_blocks.back()->type() != TextBlockTNewline
|
||||||
|
&& _customEmojiData.isEmpty()) {
|
||||||
createNewlineBlock();
|
createNewlineBlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -632,10 +646,11 @@ void Parser::skipBadEntities() {
|
||||||
void Parser::parseCurrentChar() {
|
void Parser::parseCurrentChar() {
|
||||||
_ch = ((_ptr < _end) ? *_ptr : 0);
|
_ch = ((_ptr < _end) ? *_ptr : 0);
|
||||||
_emojiLookback = 0;
|
_emojiLookback = 0;
|
||||||
const auto isNewLine = _multiline && IsNewline(_ch);
|
const auto inCustomEmoji = !_customEmojiData.isEmpty();
|
||||||
|
const auto isNewLine = !inCustomEmoji && _multiline && IsNewline(_ch);
|
||||||
const auto isSpace = IsSpace(_ch);
|
const auto isSpace = IsSpace(_ch);
|
||||||
const auto isDiac = IsDiac(_ch);
|
const auto isDiac = IsDiac(_ch);
|
||||||
const auto isTilde = _checkTilde && (_ch == '~');
|
const auto isTilde = !inCustomEmoji && _checkTilde && (_ch == '~');
|
||||||
const auto skip = [&] {
|
const auto skip = [&] {
|
||||||
if (IsBad(_ch) || _ch.isLowSurrogate()) {
|
if (IsBad(_ch) || _ch.isLowSurrogate()) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -711,6 +726,9 @@ void Parser::parseCurrentChar() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Parser::parseEmojiFromCurrent() {
|
void Parser::parseEmojiFromCurrent() {
|
||||||
|
if (!_customEmojiData.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
int len = 0;
|
int len = 0;
|
||||||
auto e = Emoji::Find(_ptr - _emojiLookback, _end, &len);
|
auto e = Emoji::Find(_ptr - _emojiLookback, _end, &len);
|
||||||
if (!e) return;
|
if (!e) return;
|
||||||
|
|
@ -878,7 +896,7 @@ void Parser::finalize(const TextParseOptions &options) {
|
||||||
}
|
}
|
||||||
_t->_links.squeeze();
|
_t->_links.squeeze();
|
||||||
_t->_spoilers.squeeze();
|
_t->_spoilers.squeeze();
|
||||||
_t->_blocks.squeeze();
|
_t->_blocks.shrink_to_fit();
|
||||||
_t->_text.squeeze();
|
_t->_text.squeeze();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1301,7 +1319,10 @@ private:
|
||||||
i = n;
|
i = n;
|
||||||
++n;
|
++n;
|
||||||
}
|
}
|
||||||
if ((*i)->type() != TextBlockTEmoji && *curr >= 0x590) {
|
const auto type = (*i)->type();
|
||||||
|
if (type != TextBlockTEmoji
|
||||||
|
&& type != TextBlockTCustomEmoji
|
||||||
|
&& *curr >= 0x590) {
|
||||||
ignore = false;
|
ignore = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -1490,8 +1511,13 @@ private:
|
||||||
levels[i] = si.analysis.bidiLevel;
|
levels[i] = si.analysis.bidiLevel;
|
||||||
}
|
}
|
||||||
if (si.analysis.flags == QScriptAnalysis::Object) {
|
if (si.analysis.flags == QScriptAnalysis::Object) {
|
||||||
if (_type == TextBlockTEmoji || _type == TextBlockTSkip) {
|
if (_type == TextBlockTEmoji
|
||||||
si.width = currentBlock->f_width() + (nextBlock == _endBlock && (!nextBlock || nextBlock->from() >= trimmedLineEnd) ? 0 : currentBlock->f_rpadding());
|
|| _type == TextBlockTCustomEmoji
|
||||||
|
|| _type == TextBlockTSkip) {
|
||||||
|
si.width = currentBlock->f_width()
|
||||||
|
+ (nextBlock == _endBlock && (!nextBlock || nextBlock->from() >= trimmedLineEnd)
|
||||||
|
? 0
|
||||||
|
: currentBlock->f_rpadding());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1586,7 +1612,7 @@ private:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} else if (_p && _type == TextBlockTEmoji) {
|
} else if (_p && (_type == TextBlockTEmoji || _type == TextBlockTCustomEmoji)) {
|
||||||
auto glyphX = x;
|
auto glyphX = x;
|
||||||
auto spacesWidth = (si.width - currentBlock->f_width());
|
auto spacesWidth = (si.width - currentBlock->f_width());
|
||||||
if (rtl) {
|
if (rtl) {
|
||||||
|
|
@ -1636,12 +1662,18 @@ private:
|
||||||
if (hasSpoiler) {
|
if (hasSpoiler) {
|
||||||
_p->setOpacity(opacity * (1. - spoilerOpacity));
|
_p->setOpacity(opacity * (1. - spoilerOpacity));
|
||||||
}
|
}
|
||||||
Emoji::Draw(
|
const auto x = (glyphX + st::emojiPadding).toInt();
|
||||||
*_p,
|
const auto y = _y + _yDelta + emojiY;
|
||||||
static_cast<const EmojiBlock*>(currentBlock)->_emoji,
|
if (_type == TextBlockTEmoji) {
|
||||||
Emoji::GetSizeNormal(),
|
Emoji::Draw(
|
||||||
(glyphX + st::emojiPadding).toInt(),
|
*_p,
|
||||||
_y + _yDelta + emojiY);
|
static_cast<const EmojiBlock*>(currentBlock)->_emoji,
|
||||||
|
Emoji::GetSizeNormal(),
|
||||||
|
x,
|
||||||
|
y);
|
||||||
|
} else if (const auto custom = static_cast<const CustomEmojiBlock*>(currentBlock)->_custom.get()) {
|
||||||
|
custom->paint(*_p, x, y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (hasSpoiler) {
|
if (hasSpoiler) {
|
||||||
_p->setOpacity(opacity * spoilerOpacity);
|
_p->setOpacity(opacity * spoilerOpacity);
|
||||||
|
|
@ -2014,11 +2046,16 @@ private:
|
||||||
}
|
}
|
||||||
TextBlockType _type = currentBlock->type();
|
TextBlockType _type = currentBlock->type();
|
||||||
if (si.analysis.flags == QScriptAnalysis::Object) {
|
if (si.analysis.flags == QScriptAnalysis::Object) {
|
||||||
if (_type == TextBlockTEmoji || _type == TextBlockTSkip) {
|
if (_type == TextBlockTEmoji
|
||||||
|
|| _type == TextBlockTCustomEmoji
|
||||||
|
|| _type == TextBlockTSkip) {
|
||||||
si.width = currentBlock->f_width() + currentBlock->f_rpadding();
|
si.width = currentBlock->f_width() + currentBlock->f_rpadding();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_type == TextBlockTEmoji || _type == TextBlockTSkip || _type == TextBlockTNewline) {
|
if (_type == TextBlockTEmoji
|
||||||
|
|| _type == TextBlockTCustomEmoji
|
||||||
|
|| _type == TextBlockTSkip
|
||||||
|
|| _type == TextBlockTNewline) {
|
||||||
if (_wLeft < si.width) {
|
if (_wLeft < si.width) {
|
||||||
lineText = lineText.mid(0, currentBlock->from() - _localFrom) + kQEllipsis;
|
lineText = lineText.mid(0, currentBlock->from() - _localFrom) + kQEllipsis;
|
||||||
lineLength = currentBlock->from() + kQEllipsis.size() - _lineStart;
|
lineLength = currentBlock->from() + kQEllipsis.size() - _lineStart;
|
||||||
|
|
@ -2214,7 +2251,9 @@ private:
|
||||||
nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr;
|
nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr;
|
||||||
}
|
}
|
||||||
auto _type = currentBlock->type();
|
auto _type = currentBlock->type();
|
||||||
if (_type == TextBlockTEmoji || _type == TextBlockTSkip) {
|
if (_type == TextBlockTEmoji
|
||||||
|
|| _type == TextBlockTCustomEmoji
|
||||||
|
|| _type == TextBlockTSkip) {
|
||||||
analysis->script = QChar::Script_Common;
|
analysis->script = QChar::Script_Common;
|
||||||
analysis->flags = QScriptAnalysis::Object;
|
analysis->flags = QScriptAnalysis::Object;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -2294,7 +2333,8 @@ private:
|
||||||
TextBlockType _itype = (*i)->type();
|
TextBlockType _itype = (*i)->type();
|
||||||
if (eor == _parLength)
|
if (eor == _parLength)
|
||||||
dir = control.basicDirection();
|
dir = control.basicDirection();
|
||||||
else if (_itype == TextBlockTEmoji)
|
else if (_itype == TextBlockTEmoji
|
||||||
|
|| _itype == TextBlockTCustomEmoji)
|
||||||
dir = QChar::DirCS;
|
dir = QChar::DirCS;
|
||||||
else if (_itype == TextBlockTSkip)
|
else if (_itype == TextBlockTSkip)
|
||||||
dir = QChar::DirCS;
|
dir = QChar::DirCS;
|
||||||
|
|
@ -2329,7 +2369,7 @@ private:
|
||||||
|
|
||||||
QChar::Direction sdir;
|
QChar::Direction sdir;
|
||||||
TextBlockType _stype = (*_parStartBlock)->type();
|
TextBlockType _stype = (*_parStartBlock)->type();
|
||||||
if (_stype == TextBlockTEmoji)
|
if (_stype == TextBlockTEmoji || _stype == TextBlockTCustomEmoji)
|
||||||
sdir = QChar::DirCS;
|
sdir = QChar::DirCS;
|
||||||
else if (_stype == TextBlockTSkip)
|
else if (_stype == TextBlockTSkip)
|
||||||
sdir = QChar::DirCS;
|
sdir = QChar::DirCS;
|
||||||
|
|
@ -2355,7 +2395,8 @@ private:
|
||||||
TextBlockType _itype = (*i)->type();
|
TextBlockType _itype = (*i)->type();
|
||||||
if (current == (int)_parLength)
|
if (current == (int)_parLength)
|
||||||
dirCurrent = control.basicDirection();
|
dirCurrent = control.basicDirection();
|
||||||
else if (_itype == TextBlockTEmoji)
|
else if (_itype == TextBlockTEmoji
|
||||||
|
|| _itype == TextBlockTCustomEmoji)
|
||||||
dirCurrent = QChar::DirCS;
|
dirCurrent = QChar::DirCS;
|
||||||
else if (_itype == TextBlockTSkip)
|
else if (_itype == TextBlockTSkip)
|
||||||
dirCurrent = QChar::DirCS;
|
dirCurrent = QChar::DirCS;
|
||||||
|
|
@ -3356,8 +3397,17 @@ uint16 String::countBlockLength(const String::TextBlocks::const_iterator &i, con
|
||||||
return countBlockEnd(i, e) - (*i)->from();
|
return countBlockEnd(i, e) - (*i)->from();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename AppendPartCallback, typename ClickHandlerStartCallback, typename ClickHandlerFinishCallback, typename FlagsChangeCallback>
|
template <
|
||||||
void String::enumerateText(TextSelection selection, AppendPartCallback appendPartCallback, ClickHandlerStartCallback clickHandlerStartCallback, ClickHandlerFinishCallback clickHandlerFinishCallback, FlagsChangeCallback flagsChangeCallback) const {
|
typename AppendPartCallback,
|
||||||
|
typename ClickHandlerStartCallback,
|
||||||
|
typename ClickHandlerFinishCallback,
|
||||||
|
typename FlagsChangeCallback>
|
||||||
|
void String::enumerateText(
|
||||||
|
TextSelection selection,
|
||||||
|
AppendPartCallback appendPartCallback,
|
||||||
|
ClickHandlerStartCallback clickHandlerStartCallback,
|
||||||
|
ClickHandlerFinishCallback clickHandlerFinishCallback,
|
||||||
|
FlagsChangeCallback flagsChangeCallback) const {
|
||||||
if (isEmpty() || selection.empty()) {
|
if (isEmpty() || selection.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -3437,12 +3487,20 @@ void String::enumerateText(TextSelection selection, AppendPartCallback appendPar
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((*i)->type() == TextBlockTSkip) continue;
|
const auto blockType = (*i)->type();
|
||||||
|
if (blockType == TextBlockTSkip) continue;
|
||||||
|
|
||||||
auto rangeFrom = qMax(selection.from, blockFrom);
|
auto rangeFrom = qMax(selection.from, blockFrom);
|
||||||
auto rangeTo = qMin(selection.to, uint16(blockFrom + countBlockLength(i, e)));
|
auto rangeTo = qMin(
|
||||||
|
selection.to,
|
||||||
|
uint16(blockFrom + countBlockLength(i, e)));
|
||||||
if (rangeTo > rangeFrom) {
|
if (rangeTo > rangeFrom) {
|
||||||
appendPartCallback(base::StringViewMid(_text, rangeFrom, rangeTo - rangeFrom));
|
const auto customEmojiData = (blockType == TextBlockTCustomEmoji)
|
||||||
|
? static_cast<const CustomEmojiBlock*>(i->get())->_custom->entityData()
|
||||||
|
: QString();
|
||||||
|
appendPartCallback(
|
||||||
|
base::StringViewMid(_text, rangeFrom, rangeTo - rangeFrom),
|
||||||
|
customEmojiData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3562,11 +3620,21 @@ TextForMimeData String::toText(
|
||||||
plainUrl ? QString() : entity.data });
|
plainUrl ? QString() : entity.data });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const auto appendPartCallback = [&](QStringView part) {
|
const auto appendPartCallback = [&](
|
||||||
|
QStringView part,
|
||||||
|
const QString &customEmojiData) {
|
||||||
result.rich.text += part;
|
result.rich.text += part;
|
||||||
if (composeExpanded) {
|
if (composeExpanded) {
|
||||||
result.expanded += part;
|
result.expanded += part;
|
||||||
}
|
}
|
||||||
|
if (composeEntities && !customEmojiData.isEmpty()) {
|
||||||
|
insertEntity({
|
||||||
|
EntityType::CustomEmoji,
|
||||||
|
result.rich.text.size() - part.size(),
|
||||||
|
part.size(),
|
||||||
|
customEmojiData,
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
enumerateText(
|
enumerateText(
|
||||||
|
|
|
||||||
|
|
@ -106,9 +106,7 @@ public:
|
||||||
const QString &text,
|
const QString &text,
|
||||||
const TextParseOptions &options = kDefaultTextOptions,
|
const TextParseOptions &options = kDefaultTextOptions,
|
||||||
int32 minResizeWidth = QFIXED_MAX);
|
int32 minResizeWidth = QFIXED_MAX);
|
||||||
String(const String &other) = default;
|
|
||||||
String(String &&other) = default;
|
String(String &&other) = default;
|
||||||
String &operator=(const String &other) = default;
|
|
||||||
String &operator=(String &&other) = default;
|
String &operator=(String &&other) = default;
|
||||||
~String() = default;
|
~String() = default;
|
||||||
|
|
||||||
|
|
@ -177,7 +175,7 @@ public:
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using TextBlocks = QVector<Block>;
|
using TextBlocks = std::vector<Block>;
|
||||||
using TextLinks = QVector<ClickHandlerPtr>;
|
using TextLinks = QVector<ClickHandlerPtr>;
|
||||||
|
|
||||||
uint16 countBlockEnd(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const;
|
uint16 countBlockEnd(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const;
|
||||||
|
|
|
||||||
|
|
@ -348,8 +348,8 @@ AbstractBlock::AbstractBlock(
|
||||||
uint16 flags,
|
uint16 flags,
|
||||||
uint16 lnkIndex,
|
uint16 lnkIndex,
|
||||||
uint16 spoilerIndex)
|
uint16 spoilerIndex)
|
||||||
: _from(from)
|
: _flags((flags & 0b1111111111) | ((lnkIndex & 0xFFFF) << 14))
|
||||||
, _flags((flags & 0b1111111111) | ((lnkIndex & 0xFFFF) << 14))
|
, _from(from)
|
||||||
, _spoilerIndex(spoilerIndex) {
|
, _spoilerIndex(spoilerIndex) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -474,6 +474,30 @@ EmojiBlock::EmojiBlock(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CustomEmojiBlock::CustomEmojiBlock(
|
||||||
|
const style::font &font,
|
||||||
|
const QString &str,
|
||||||
|
uint16 from,
|
||||||
|
uint16 length,
|
||||||
|
uint16 flags,
|
||||||
|
uint16 lnkIndex,
|
||||||
|
uint16 spoilerIndex,
|
||||||
|
std::unique_ptr<CustomEmoji> custom)
|
||||||
|
: AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex)
|
||||||
|
, _custom(std::move(custom)) {
|
||||||
|
_flags |= ((TextBlockTCustomEmoji & 0x0F) << 10);
|
||||||
|
_width = int(st::emojiSize + 2 * st::emojiPadding);
|
||||||
|
_rpadding = 0;
|
||||||
|
for (auto i = length; i != 0;) {
|
||||||
|
auto ch = str[_from + (--i)];
|
||||||
|
if (ch.unicode() == QChar::Space) {
|
||||||
|
_rpadding += font->spacew;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NewlineBlock::NewlineBlock(
|
NewlineBlock::NewlineBlock(
|
||||||
const style::font &font,
|
const style::font &font,
|
||||||
const QString &str,
|
const QString &str,
|
||||||
|
|
@ -546,25 +570,6 @@ Block::Block() {
|
||||||
Unexpected("Should not be called.");
|
Unexpected("Should not be called.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Block::Block(const Block &other) {
|
|
||||||
switch (other->type()) {
|
|
||||||
case TextBlockTNewline:
|
|
||||||
emplace<NewlineBlock>(other.unsafe<NewlineBlock>());
|
|
||||||
break;
|
|
||||||
case TextBlockTText:
|
|
||||||
emplace<TextBlock>(other.unsafe<TextBlock>());
|
|
||||||
break;
|
|
||||||
case TextBlockTEmoji:
|
|
||||||
emplace<EmojiBlock>(other.unsafe<EmojiBlock>());
|
|
||||||
break;
|
|
||||||
case TextBlockTSkip:
|
|
||||||
emplace<SkipBlock>(other.unsafe<SkipBlock>());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Unexpected("Bad text block type in Block(const Block&).");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Block::Block(Block &&other) {
|
Block::Block(Block &&other) {
|
||||||
switch (other->type()) {
|
switch (other->type()) {
|
||||||
case TextBlockTNewline:
|
case TextBlockTNewline:
|
||||||
|
|
@ -576,6 +581,9 @@ Block::Block(Block &&other) {
|
||||||
case TextBlockTEmoji:
|
case TextBlockTEmoji:
|
||||||
emplace<EmojiBlock>(std::move(other.unsafe<EmojiBlock>()));
|
emplace<EmojiBlock>(std::move(other.unsafe<EmojiBlock>()));
|
||||||
break;
|
break;
|
||||||
|
case TextBlockTCustomEmoji:
|
||||||
|
emplace<CustomEmojiBlock>(std::move(other.unsafe<CustomEmojiBlock>()));
|
||||||
|
break;
|
||||||
case TextBlockTSkip:
|
case TextBlockTSkip:
|
||||||
emplace<SkipBlock>(std::move(other.unsafe<SkipBlock>()));
|
emplace<SkipBlock>(std::move(other.unsafe<SkipBlock>()));
|
||||||
break;
|
break;
|
||||||
|
|
@ -584,30 +592,6 @@ Block::Block(Block &&other) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Block &Block::operator=(const Block &other) {
|
|
||||||
if (&other == this) {
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
destroy();
|
|
||||||
switch (other->type()) {
|
|
||||||
case TextBlockTNewline:
|
|
||||||
emplace<NewlineBlock>(other.unsafe<NewlineBlock>());
|
|
||||||
break;
|
|
||||||
case TextBlockTText:
|
|
||||||
emplace<TextBlock>(other.unsafe<TextBlock>());
|
|
||||||
break;
|
|
||||||
case TextBlockTEmoji:
|
|
||||||
emplace<EmojiBlock>(other.unsafe<EmojiBlock>());
|
|
||||||
break;
|
|
||||||
case TextBlockTSkip:
|
|
||||||
emplace<SkipBlock>(other.unsafe<SkipBlock>());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Unexpected("Bad text block type in operator=(const Block&).");
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
Block &Block::operator=(Block &&other) {
|
Block &Block::operator=(Block &&other) {
|
||||||
if (&other == this) {
|
if (&other == this) {
|
||||||
return *this;
|
return *this;
|
||||||
|
|
@ -623,6 +607,9 @@ Block &Block::operator=(Block &&other) {
|
||||||
case TextBlockTEmoji:
|
case TextBlockTEmoji:
|
||||||
emplace<EmojiBlock>(std::move(other.unsafe<EmojiBlock>()));
|
emplace<EmojiBlock>(std::move(other.unsafe<EmojiBlock>()));
|
||||||
break;
|
break;
|
||||||
|
case TextBlockTCustomEmoji:
|
||||||
|
emplace<CustomEmojiBlock>(std::move(other.unsafe<CustomEmojiBlock>()));
|
||||||
|
break;
|
||||||
case TextBlockTSkip:
|
case TextBlockTSkip:
|
||||||
emplace<SkipBlock>(std::move(other.unsafe<SkipBlock>()));
|
emplace<SkipBlock>(std::move(other.unsafe<SkipBlock>()));
|
||||||
break;
|
break;
|
||||||
|
|
@ -694,6 +681,26 @@ Block Block::Emoji(
|
||||||
emoji);
|
emoji);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Block Block::CustomEmoji(
|
||||||
|
const style::font &font,
|
||||||
|
const QString &str,
|
||||||
|
uint16 from,
|
||||||
|
uint16 length,
|
||||||
|
uint16 flags,
|
||||||
|
uint16 lnkIndex,
|
||||||
|
uint16 spoilerIndex,
|
||||||
|
std::unique_ptr<Text::CustomEmoji> custom) {
|
||||||
|
return New<CustomEmojiBlock>(
|
||||||
|
font,
|
||||||
|
str,
|
||||||
|
from,
|
||||||
|
length,
|
||||||
|
flags,
|
||||||
|
lnkIndex,
|
||||||
|
spoilerIndex,
|
||||||
|
std::move(custom));
|
||||||
|
}
|
||||||
|
|
||||||
Block Block::Skip(
|
Block Block::Skip(
|
||||||
const style::font &font,
|
const style::font &font,
|
||||||
const QString &str,
|
const QString &str,
|
||||||
|
|
@ -740,6 +747,9 @@ void Block::destroy() {
|
||||||
case TextBlockTEmoji:
|
case TextBlockTEmoji:
|
||||||
unsafe<EmojiBlock>().~EmojiBlock();
|
unsafe<EmojiBlock>().~EmojiBlock();
|
||||||
break;
|
break;
|
||||||
|
case TextBlockTCustomEmoji:
|
||||||
|
unsafe<CustomEmojiBlock>().~CustomEmojiBlock();
|
||||||
|
break;
|
||||||
case TextBlockTSkip:
|
case TextBlockTSkip:
|
||||||
unsafe<SkipBlock>().~SkipBlock();
|
unsafe<SkipBlock>().~SkipBlock();
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ enum TextBlockType {
|
||||||
TextBlockTNewline = 0x01,
|
TextBlockTNewline = 0x01,
|
||||||
TextBlockTText = 0x02,
|
TextBlockTText = 0x02,
|
||||||
TextBlockTEmoji = 0x03,
|
TextBlockTEmoji = 0x03,
|
||||||
TextBlockTSkip = 0x04,
|
TextBlockTCustomEmoji = 0x04,
|
||||||
|
TextBlockTSkip = 0x05,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum TextBlockFlags {
|
enum TextBlockFlags {
|
||||||
|
|
@ -63,10 +64,8 @@ protected:
|
||||||
uint16 lnkIndex,
|
uint16 lnkIndex,
|
||||||
uint16 spoilerIndex);
|
uint16 spoilerIndex);
|
||||||
|
|
||||||
uint16 _from = 0;
|
|
||||||
|
|
||||||
uint32 _flags = 0; // 2 bits empty, 16 bits lnkIndex, 4 bits type, 10 bits flags
|
uint32 _flags = 0; // 2 bits empty, 16 bits lnkIndex, 4 bits type, 10 bits flags
|
||||||
|
uint16 _from = 0;
|
||||||
uint16 _spoilerIndex = 0;
|
uint16 _spoilerIndex = 0;
|
||||||
|
|
||||||
QFixed _width = 0;
|
QFixed _width = 0;
|
||||||
|
|
@ -165,6 +164,35 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class CustomEmoji {
|
||||||
|
public:
|
||||||
|
virtual ~CustomEmoji() = default;
|
||||||
|
[[nodiscard]] virtual QString entityData() = 0;
|
||||||
|
virtual void paint(QPainter &p, int x, int y) = 0;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class CustomEmojiBlock final : public AbstractBlock {
|
||||||
|
public:
|
||||||
|
CustomEmojiBlock(
|
||||||
|
const style::font &font,
|
||||||
|
const QString &str,
|
||||||
|
uint16 from,
|
||||||
|
uint16 length,
|
||||||
|
uint16 flags,
|
||||||
|
uint16 lnkIndex,
|
||||||
|
uint16 spoilerIndex,
|
||||||
|
std::unique_ptr<CustomEmoji> custom);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<CustomEmoji> _custom;
|
||||||
|
|
||||||
|
friend class String;
|
||||||
|
friend class Parser;
|
||||||
|
friend class Renderer;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
class SkipBlock final : public AbstractBlock {
|
class SkipBlock final : public AbstractBlock {
|
||||||
public:
|
public:
|
||||||
SkipBlock(
|
SkipBlock(
|
||||||
|
|
@ -190,9 +218,7 @@ private:
|
||||||
class Block final {
|
class Block final {
|
||||||
public:
|
public:
|
||||||
Block();
|
Block();
|
||||||
Block(const Block &other);
|
|
||||||
Block(Block &&other);
|
Block(Block &&other);
|
||||||
Block &operator=(const Block &other);
|
|
||||||
Block &operator=(Block &&other);
|
Block &operator=(Block &&other);
|
||||||
~Block();
|
~Block();
|
||||||
|
|
||||||
|
|
@ -225,6 +251,16 @@ public:
|
||||||
uint16 spoilerIndex,
|
uint16 spoilerIndex,
|
||||||
EmojiPtr emoji);
|
EmojiPtr emoji);
|
||||||
|
|
||||||
|
[[nodiscard]] static Block CustomEmoji(
|
||||||
|
const style::font &font,
|
||||||
|
const QString &str,
|
||||||
|
uint16 from,
|
||||||
|
uint16 length,
|
||||||
|
uint16 flags,
|
||||||
|
uint16 lnkIndex,
|
||||||
|
uint16 spoilerIndex,
|
||||||
|
std::unique_ptr<CustomEmoji> custom);
|
||||||
|
|
||||||
[[nodiscard]] static Block Skip(
|
[[nodiscard]] static Block Skip(
|
||||||
const style::font &font,
|
const style::font &font,
|
||||||
const QString &str,
|
const QString &str,
|
||||||
|
|
@ -278,6 +314,8 @@ private:
|
||||||
static_assert(alignof(NewlineBlock) <= alignof(void*));
|
static_assert(alignof(NewlineBlock) <= alignof(void*));
|
||||||
static_assert(sizeof(EmojiBlock) <= sizeof(TextBlock));
|
static_assert(sizeof(EmojiBlock) <= sizeof(TextBlock));
|
||||||
static_assert(alignof(EmojiBlock) <= alignof(void*));
|
static_assert(alignof(EmojiBlock) <= alignof(void*));
|
||||||
|
static_assert(sizeof(CustomEmojiBlock) <= sizeof(TextBlock));
|
||||||
|
static_assert(alignof(CustomEmojiBlock) <= alignof(void*));
|
||||||
static_assert(sizeof(SkipBlock) <= sizeof(TextBlock));
|
static_assert(sizeof(SkipBlock) <= sizeof(TextBlock));
|
||||||
static_assert(alignof(SkipBlock) <= alignof(void*));
|
static_assert(alignof(SkipBlock) <= alignof(void*));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ enum class EntityType : uchar {
|
||||||
Cashtag,
|
Cashtag,
|
||||||
Mention,
|
Mention,
|
||||||
MentionName,
|
MentionName,
|
||||||
|
CustomEmoji,
|
||||||
BotCommand,
|
BotCommand,
|
||||||
MediaTimestamp,
|
MediaTimestamp,
|
||||||
PlainLink, // Senders in chat list, attachements in chat list, etc.
|
PlainLink, // Senders in chat list, attachements in chat list, etc.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue