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