Show full reply names with icons.

This commit is contained in:
John Preston 2023-11-02 13:33:55 +04:00
parent 12e164c4df
commit 3b40bc6297
15 changed files with 226 additions and 95 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 890 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 B

View File

@ -89,6 +89,10 @@ private:
: FrameSizeFromTag(tag);
}
[[nodiscard]] QString InternalPrefix() {
return u"internal:"_q;
}
} // namespace
class CustomEmojiLoader final
@ -514,6 +518,9 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
Fn<void()> update,
SizeTag tag,
int sizeOverride) {
if (data.startsWith(InternalPrefix())) {
return internal(data);
}
const auto parsed = ParseCustomEmojiData(data);
return parsed
? create(parsed, std::move(update), tag, sizeOverride)
@ -540,6 +547,18 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
});
}
std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::internal(
QStringView data) {
const auto index = data.mid(InternalPrefix().size()).toInt();
Assert(index >= 0 && index < _internalEmoji.size());
auto &info = _internalEmoji[index];
return std::make_unique<Ui::CustomEmoji::Internal>(
data.toString(),
info.image,
info.textColor);
}
void CustomEmojiManager::resolve(
QStringView data,
not_null<Listener*> listener) {
@ -885,6 +904,34 @@ uint64 CustomEmojiManager::coloredSetId() const {
return _coloredSetId;
}
QString CustomEmojiManager::registerInternalEmoji(
QImage emoji,
bool textColor) {
_internalEmoji.push_back({ std::move(emoji), textColor });
return InternalPrefix() + QString::number(_internalEmoji.size() - 1);
}
QString CustomEmojiManager::registerInternalEmoji(
const style::icon &icon,
bool textColor) {
const auto i = _iconEmoji.find(&icon);
if (i != end(_iconEmoji)) {
return i->second;
}
auto image = QImage(
icon.size() * style::DevicePixelRatio(),
QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::transparent);
image.setDevicePixelRatio(style::DevicePixelRatio());
auto p = QPainter(&image);
icon.paint(p, 0, 0, icon.width());
p.end();
const auto result = registerInternalEmoji(std::move(image), textColor);
_iconEmoji.emplace(&icon, result);
return result;
}
int FrameSizeFromTag(SizeTag tag) {
const auto emoji = EmojiSizeFromTag(tag);
const auto factor = style::DevicePixelRatio();

View File

@ -83,11 +83,22 @@ public:
[[nodiscard]] Main::Session &session() const;
[[nodiscard]] Session &owner() const;
[[nodiscard]] QString registerInternalEmoji(
QImage emoji,
bool textColor = true);
[[nodiscard]] QString registerInternalEmoji(
const style::icon &icon,
bool textColor = true);
[[nodiscard]] uint64 coloredSetId() const;
private:
static constexpr auto kSizeCount = int(SizeTag::kCount);
struct InternalEmojiData {
QImage image;
bool textColor = true;
};
struct RepaintBunch {
crl::time when = 0;
std::vector<base::weak_ptr<Ui::CustomEmoji::Instance>> instances;
@ -131,6 +142,8 @@ private:
SizeTag tag,
int sizeOverride,
LoaderFactory factory);
[[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> internal(
QStringView data);
[[nodiscard]] static int SizeIndex(SizeTag tag);
const not_null<Session*> _owner;
@ -163,6 +176,9 @@ private:
bool _repaintTimerScheduled = false;
bool _requestSetsScheduled = false;
std::vector<InternalEmojiData> _internalEmoji;
base::flat_map<not_null<const style::icon*>, QString> _iconEmoji;
#if 0 // inject-to-on_main
crl::time _repaintsLastAdded = 0;
rpl::lifetime _repaintsLifetime;

View File

@ -477,7 +477,6 @@ HistoryMessageReply &HistoryMessageReply::operator=(
HistoryMessageReply::~HistoryMessageReply() {
// clearData() should be called by holder.
Expects(resolvedMessage.empty());
Expects(originalVia == nullptr);
}
bool HistoryMessageReply::updateData(
@ -523,44 +522,42 @@ bool HistoryMessageReply::updateData(
}
}
const auto repaint = [=] { holder->customEmojiRepaint(); };
const auto context = Core::MarkedTextContext{
.session = &holder->history()->session(),
.customEmojiRepaint = repaint,
};
const auto external = this->external();
if (resolvedMessage
_multiline = 0;
// #TODO !_fields.storyId && (external || !_fields.quote.empty());
const auto displaying = resolvedMessage
|| resolvedStory
|| (external && (!_fields.messageId || force))) {
const auto repaint = [=] { holder->customEmojiRepaint(); };
const auto context = Core::MarkedTextContext{
.session = &holder->history()->session(),
.customEmojiRepaint = repaint,
};
const auto text = !_fields.quote.empty()
? _fields.quote
: resolvedMessage
? resolvedMessage->inReplyText()
: resolvedStory
? resolvedStory->inReplyText()
: TextWithEntities{ u"..."_q };
_text.setMarkedText(
st::defaultTextStyle,
text,
Ui::DialogTextOptions(),
context);
|| (external && (!_fields.messageId || force));
_displaying = displaying ? 1 : 0;
updateName(holder);
const auto unavailable = !resolvedMessage
&& !resolvedStory
&& ((!_fields.storyId && !_fields.messageId) || force);
_unavailable = unavailable ? 1 : 0;
const auto text = !_fields.quote.empty()
? _fields.quote
: resolvedMessage
? resolvedMessage->inReplyText()
: resolvedStory
? resolvedStory->inReplyText()
: TextWithEntities{ u"..."_q };
_text.setMarkedText(
st::defaultTextStyle,
text,
Ui::DialogTextOptions(),
context);
updateName(holder);
if (_displaying) {
setLinkFrom(holder);
if (resolvedMessage
&& !resolvedMessage->Has<HistoryMessageForwarded>()) {
if (const auto bot = resolvedMessage->viaBot()) {
originalVia = std::make_unique<HistoryMessageVia>();
originalVia->create(
&holder->history()->owner(),
peerToUser(bot->id));
}
}
if (!resolvedMessage && !resolvedStory) {
_unavailable = 1;
}
const auto media = resolvedMessage
? resolvedMessage->media()
: nullptr;
@ -642,7 +639,6 @@ void HistoryMessageReply::setTopMessageId(MsgId topMessageId) {
}
void HistoryMessageReply::clearData(not_null<HistoryItem*> holder) {
originalVia = nullptr;
if (resolvedMessage) {
holder->history()->owner().unregisterDependentMessage(
holder,
@ -658,6 +654,12 @@ void HistoryMessageReply::clearData(not_null<HistoryItem*> holder) {
_name.clear();
_text.clear();
_unavailable = 1;
_displaying = 0;
_expandable = 0;
if (_multiline) {
holder->history()->owner().requestItemResize(holder);
_multiline = 0;
}
refreshReplyToMedia();
}
@ -691,9 +693,10 @@ PeerData *HistoryMessageReply::sender(not_null<HistoryItem*> holder) const {
}
QString HistoryMessageReply::senderName(
not_null<HistoryItem*> holder) const {
not_null<HistoryItem*> holder,
bool shorten) const {
if (const auto peer = sender(holder)) {
return senderName(peer);
return senderName(peer, shorten);
} else if (!resolvedMessage) {
return _fields.externalSenderName;
} else if (holder->Has<HistoryMessageForwarded>()) {
@ -708,11 +711,11 @@ QString HistoryMessageReply::senderName(
return QString();
}
QString HistoryMessageReply::senderName(not_null<PeerData*> peer) const {
if (const auto user = originalVia ? peer->asUser() : nullptr) {
return user->firstName;
}
return peer->name();
QString HistoryMessageReply::senderName(
not_null<PeerData*> peer,
bool shorten) const {
const auto user = shorten ? peer->asUser() : nullptr;
return user ? user->firstName : peer->name();
}
bool HistoryMessageReply::isNameUpdated(
@ -729,47 +732,101 @@ bool HistoryMessageReply::isNameUpdated(
void HistoryMessageReply::updateName(
not_null<HistoryItem*> holder,
std::optional<PeerData*> resolvedSender) const {
const auto peer = resolvedSender.value_or(sender(holder));
const auto name = peer ? senderName(peer) : senderName(holder);
auto viaBotUsername = QString();
if (resolvedMessage
&& !resolvedMessage->Has<HistoryMessageForwarded>()) {
if (const auto bot = resolvedMessage->viaBot()) {
viaBotUsername = bot->username();
}
}
const auto sender = resolvedSender.value_or(this->sender(holder));
const auto externalPeer = _fields.externalPeerId
? holder->history()->owner().peer(_fields.externalPeerId).get()
: nullptr;
const auto groupNameAdded = (externalPeer && externalPeer != sender);
const auto shorten = !viaBotUsername.isEmpty() || groupNameAdded;
const auto name = sender
? senderName(sender, shorten)
: senderName(holder, shorten);
const auto hasPreview = (resolvedStory
&& resolvedStory->hasReplyPreview())
|| (resolvedMessage
&& resolvedMessage->media()
&& resolvedMessage->media()->hasReplyPreview());
const auto textLeft = hasPreview
? (st::messageQuoteStyle.outline
+ st::historyReplyPreviewMargin.left()
+ st::historyReplyPreview
+ st::historyReplyPreviewMargin.right())
: st::historyReplyPadding.left();
if (!name.isEmpty()) {
_name.setText(st::fwdTextStyle, name, Ui::NameTextOptions());
if (peer) {
_nameVersion = peer->nameVersion();
}
const auto w = _name.maxWidth()
+ (originalVia
? (st::msgServiceFont->spacew + originalVia->maxWidth)
: 0)
+ (_fields.quote.empty()
? 0
: st::messageTextStyle.blockquote.icon.width());
_maxWidth = std::max(
w,
std::min(_text.maxWidth(), st::maxSignatureSize))
+ (_fields.storyId
? (st::dialogsMiniReplyStory.skipText
+ st::dialogsMiniReplyStory.icon.icon.width())
: 0);
} else {
_maxWidth = st::msgDateFont->width(statePhrase());
const auto previewSkip = st::messageQuoteStyle.outline
+ st::historyReplyPreviewMargin.left()
+ st::historyReplyPreview
+ st::historyReplyPreviewMargin.right()
- st::historyReplyPadding.left();
const auto peerIcon = [](PeerData *peer) {
return !peer
? &st::historyReplyUser
: peer->isBroadcast()
? &st::historyReplyChannel
: (peer->isChannel() || peer->isChat())
? &st::historyReplyGroup
: &st::historyReplyUser;
};
const auto peerEmoji = [&](PeerData *peer) {
const auto owner = &holder->history()->owner();
return Ui::Text::SingleCustomEmoji(
owner->customEmojiManager().registerInternalEmoji(
*peerIcon(peer)));
};
auto nameFull = TextWithEntities();
if (!groupNameAdded && !_fields.storyId) {
nameFull.append(peerEmoji(sender));
}
_maxWidth = textLeft
nameFull.append(name);
if (groupNameAdded) {
nameFull.append(peerEmoji(externalPeer));
nameFull.append(externalPeer->name());
}
if (!viaBotUsername.isEmpty()) {
nameFull.append(u" @"_q).append(viaBotUsername);
}
const auto context = Core::MarkedTextContext{
.session = &holder->history()->session(),
.customEmojiRepaint = [] {},
.customEmojiLoopLimit = 1,
};
_name.setMarkedText(
st::fwdTextStyle,
nameFull,
Ui::NameTextOptions(),
context);
if (sender) {
_nameVersion = sender->nameVersion();
}
const auto nameMaxWidth = previewSkip
+ _name.maxWidth()
+ (hasQuoteIcon()
? st::messageTextStyle.blockquote.icon.width()
: 0);
const auto storySkip = _fields.storyId
? (st::dialogsMiniReplyStory.skipText
+ st::dialogsMiniReplyStory.icon.icon.width())
: 0;
const auto optimalTextSize = _multiline
? countMultilineOptimalSize(previewSkip)
: QSize(
(previewSkip
+ storySkip
+ std::min(_text.maxWidth(), st::maxSignatureSize)),
st::normalFont->height);
_maxWidth = std::max(nameMaxWidth, optimalTextSize.width());
if (!_displaying) {
const auto phraseWidth = st::msgDateFont->width(statePhrase());
_maxWidth = _unavailable
? phraseWidth
: std::max(_maxWidth, phraseWidth);
}
_maxWidth = st::historyReplyPadding.left()
+ _maxWidth
+ st::historyReplyPadding.right();
_minHeight = st::historyReplyPadding.top()
+ st::msgServiceNameFont->height
+ st::normalFont->height
+ optimalTextSize.height()
+ st::historyReplyPadding.bottom();
}
@ -785,17 +842,11 @@ int HistoryMessageReply::resizeToWidth(int width) const {
+ st::historyReplyPreview
+ st::historyReplyPreviewMargin.right())
: st::historyReplyPadding.left();
if (originalVia) {
originalVia->resize(width
- textLeft
- st::historyReplyPadding.right()
- _name.maxWidth()
- st::msgServiceFont->spacew);
}
if (width >= _maxWidth) {
if (width >= _maxWidth || !_multiline) {
_height = _minHeight;
return height();
}
// #TODO
_height = _minHeight;
return height();
}
@ -826,6 +877,15 @@ void HistoryMessageReply::storyRemoved(
}
}
bool HistoryMessageReply::hasQuoteIcon() const {
return _fields.manualQuote && !_fields.quote.empty();
}
QSize HistoryMessageReply::countMultilineOptimalSize(
int firstLineSkip) const {
return QSize(); // #TODO
}
void HistoryMessageReply::paint(
Painter &p,
not_null<const HistoryView::Element*> holder,
@ -839,7 +899,7 @@ void HistoryMessageReply::paint(
y += st::historyReplyTop;
const auto rect = QRect(x, y, w, _height);
const auto hasQuote = _fields.manualQuote && !_fields.quote.empty();
const auto hasQuote = hasQuoteIcon();
const auto selected = context.selected();
const auto colorPeer = resolvedMessage
? resolvedMessage->displayFrom()
@ -969,10 +1029,6 @@ void HistoryMessageReply::paint(
? FromNameFg(context, colorIndexPlusOne - 1)
: stm->msgServiceFg->c);
_name.drawLeftElided(p, x + textLeft, y + st::historyReplyPadding.top(), w, w + 2 * x + 2 * textLeft);
if (originalVia && w > _name.maxWidth() + st::msgServiceFont->spacew) {
p.setFont(st::msgServiceFont);
p.drawText(x + textLeft + _name.maxWidth() + st::msgServiceFont->spacew, y + st::historyReplyPadding.top() + st::msgServiceFont->ascent, originalVia->text);
}
p.setPen(inBubble
? stm->historyTextFg

View File

@ -276,8 +276,12 @@ struct HistoryMessageReply
[[nodiscard]] bool external() const;
[[nodiscard]] PeerData *sender(not_null<HistoryItem*> holder) const;
[[nodiscard]] QString senderName(not_null<HistoryItem*> holder) const;
[[nodiscard]] QString senderName(not_null<PeerData*> peer) const;
[[nodiscard]] QString senderName(
not_null<HistoryItem*> holder,
bool shorten) const;
[[nodiscard]] QString senderName(
not_null<PeerData*> peer,
bool shorten) const;
[[nodiscard]] bool isNameUpdated(not_null<HistoryItem*> holder) const;
void updateName(
not_null<HistoryItem*> holder,
@ -340,7 +344,6 @@ struct HistoryMessageReply
WebPageId replyToWebPageId = 0;
ReplyToMessagePointer resolvedMessage;
ReplyToStoryPointer resolvedStory;
std::unique_ptr<HistoryMessageVia> originalVia;
std::unique_ptr<Ui::SpoilerAnimation> spoiler;
struct {
@ -349,6 +352,10 @@ struct HistoryMessageReply
} ripple;
private:
[[nodiscard]] bool hasQuoteIcon() const;
[[nodiscard]] QSize countMultilineOptimalSize(
int firstLineSkip) const;
ReplyFields _fields;
ClickHandlerPtr _link;
mutable Ui::Text::String _name;
@ -359,6 +366,9 @@ private:
mutable int _height = 0;
mutable int _nameVersion = 0;
uint8 _unavailable : 1 = 0;
uint8 _displaying : 1 = 0;
uint8 _multiline : 1 = 0;
uint8 _expandable : 1 = 0;
};

View File

@ -771,13 +771,9 @@ QSize Message::performCountOptimalSize() {
accumulate_max(maxWidth, namew);
}
if (reply) {
auto replyw = st::msgPadding.left()
const auto replyw = st::msgPadding.left()
+ reply->maxWidth()
+ st::msgPadding.right();
if (reply->originalVia) {
replyw += st::msgServiceFont->spacew
+ reply->originalVia->maxWidth;
}
accumulate_max(maxWidth, replyw);
}
if (entry) {

View File

@ -32,6 +32,12 @@ historyReplyBottom: 2px;
historyReplyPreview: 32px;
historyReplyPreviewMargin: margins(7px, 4px, 4px, 4px);
historyReplyPadding: margins(11px, 2px, 6px, 2px);
historyReplyUser: icon {{ "chat/reply_type_user", windowFg }};
historyReplyUserPadding: margins(0px, 4px, 4px, 0px);
historyReplyGroup: icon {{ "chat/reply_type_group", windowFg }};
historyReplyGroupPadding: margins(0px, 4px, 4px, 0px);
historyReplyChannel: icon {{ "chat/reply_type_channel", windowFg }};
historyReplyChannelPadding: margins(0px, 5px, 4px, 0px);
msgReplyPadding: margins(6px, 6px, 11px, 6px);
msgReplyBarPos: point(1px, 0px);