Add jump-to-topic panel in View as Messages.

This commit is contained in:
John Preston 2022-12-02 18:19:56 +04:00
parent 8281990bb8
commit a4e4681835
25 changed files with 470 additions and 150 deletions

View File

@ -2213,7 +2213,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
history->requestChatListMessage();
if (!history->folderKnown()
|| (!history->unreadCountKnown()
&& !history->peer->isForum())) {
&& !history->isForum())) {
history->owner().histories().requestDialogEntry(history);
}
if (!channel->amCreator()) {

View File

@ -86,8 +86,7 @@ forumDialogRow: DialogRow(defaultDialogRow) {
forumDialogJumpArrow: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFg }};
forumDialogJumpArrowOver: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFgOver }};
forumDialogJumpArrowSkip: 8px;
forumDialogJumpArrowLeft: 3px;
forumDialogJumpArrowTop: 3px;
forumDialogJumpArrowPosition: point(3px, 3px);
forumDialogJumpPadding: margins(8px, 3px, 8px, 3px);
forumDialogJumpRadius: 11px;

View File

@ -571,8 +571,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
bool mayBeActive) {
const auto key = row->key();
const auto active = mayBeActive && (activeEntry.key == key);
const auto forum = key.history()
&& key.history()->peer->isForum();
const auto forum = key.history() && key.history()->isForum();
if (forum && !_topicJumpCache) {
_topicJumpCache = std::make_unique<Ui::TopicJumpCache>();
}

View File

@ -264,7 +264,7 @@ Row::Row(Key key, int index, int top) : _id(key), _top(top), _index(index) {
void Row::recountHeight(float64 narrowRatio) {
if (const auto history = _id.history()) {
_height = history->peer->isForum()
_height = history->isForum()
? anim::interpolate(
st::forumDialogRow.height,
st::defaultDialogRow.height,

View File

@ -455,7 +455,7 @@ void Widget::chosenRow(const ChosenRow &row) {
topic,
row.message.fullId.msg,
Window::SectionShow::Way::ClearStack);
} else if (history && history->peer->isForum() && !row.message.fullId) {
} else if (history && history->isForum() && !row.message.fullId) {
const auto forum = history->peer->forum();
if (controller()->shownForum().current() == forum) {
controller()->closeForum();
@ -1916,7 +1916,7 @@ void Widget::dropEvent(QDropEvent *e) {
controller()->content()->filesOrForwardDrop(
thread,
e->mimeData());
if (!thread->owningHistory()->peer->isForum()) {
if (!thread->owningHistory()->isForum()) {
hideChildList();
}
controller()->widget()->raise();

View File

@ -56,7 +56,7 @@ const auto kPsaBadgePrefix = "cloud_lng_badge_psa_";
} else if (const auto user = history->peer->asUser()) {
return (user->onlineTill > 0);
}
return !history->peer->isForum();
return !history->isForum();
}
void PaintRowTopRight(

View File

@ -326,14 +326,11 @@ void MessageView::paint(
rect.setLeft(rect.x() + _textCache.maxWidth());
}
if (jump1) {
const auto x = (rect.width() > 0)
? rect.x()
: finalRight;
const auto add = st::forumDialogJumpArrowLeft;
const auto y = rect.y() + st::forumDialogJumpArrowTop;
const auto position = st::forumDialogJumpArrowPosition
+ QPoint((rect.width() > 0) ? rect.x() : finalRight, rect.y());
(context.selected
? st::forumDialogJumpArrowOver
: st::forumDialogJumpArrow).paint(p, x + add, y, context.width);
: st::forumDialogJumpArrow).paint(p, position, context.width);
}
}

View File

@ -1271,7 +1271,7 @@ void History::newItemAdded(not_null<HistoryItem*> item) {
if (item->unread(this)) {
if (unreadCountKnown()) {
setUnreadCount(unreadCount() + 1);
} else if (!peer->isForum()) {
} else if (!isForum()) {
owner().histories().requestDialogEntry(this);
}
} else {
@ -1800,7 +1800,7 @@ void History::setUnreadCount(int newUnreadCount) {
if (_unreadCount == newUnreadCount) {
return;
}
const auto notifier = unreadStateChangeNotifier(!peer->isForum());
const auto notifier = unreadStateChangeNotifier(!isForum());
_unreadCount = newUnreadCount;
const auto lastOutgoing = [&] {
@ -1839,7 +1839,7 @@ void History::setUnreadMark(bool unread) {
return;
}
const auto notifier = unreadStateChangeNotifier(
!unreadCount() && !peer->isForum());
!unreadCount() && !isForum());
Thread::setUnreadMarkFlag(unread);
}
@ -1871,7 +1871,7 @@ void History::setMuted(bool muted) {
if (this->muted() == muted) {
return;
} else {
const auto state = peer->isForum()
const auto state = isForum()
? Dialogs::BadgesState()
: computeBadgesState();
const auto notify = (state.unread || state.reaction);
@ -1984,7 +1984,7 @@ int History::chatListNameVersion() const {
}
void History::hasUnreadMentionChanged(bool has) {
if (peer->isForum()) {
if (isForum()) {
return;
}
auto was = chatListUnreadState();
@ -1997,7 +1997,7 @@ void History::hasUnreadMentionChanged(bool has) {
}
void History::hasUnreadReactionChanged(bool has) {
if (peer->isForum()) {
if (isForum()) {
return;
}
auto was = chatListUnreadState();
@ -2966,18 +2966,22 @@ HistoryItem *History::lastEditableMessage() const {
}
void History::resizeToWidth(int newWidth) {
const auto resizeAllItems = (_width != newWidth);
if (!resizeAllItems && !hasPendingResizedItems()) {
using Request = HistoryBlock::ResizeRequest;
const auto request = (_flags & Flag::PendingAllItemsResize)
? Request::ReinitAll
: (_width != newWidth)
? Request::ResizeAll
: Request::ResizePending;
if (request == Request::ResizePending && !hasPendingResizedItems()) {
return;
}
_flags &= ~(Flag::HasPendingResizedItems);
_flags &= ~(Flag::HasPendingResizedItems | Flag::PendingAllItemsResize);
_width = newWidth;
int y = 0;
for (const auto &block : blocks) {
block->setY(y);
y += block->resizeGetHeight(newWidth, resizeAllItems);
y += block->resizeGetHeight(newWidth, request);
}
_height = y;
}
@ -3024,6 +3028,11 @@ void History::forumChanged(Data::Forum *old) {
if (cloudDraft(MsgId(0))) {
updateChatListSortPosition();
}
_flags |= Flag::PendingAllItemsResize;
}
bool History::isForum() const {
return (_flags & Flag::IsForum);
}
not_null<History*> History::migrateToOrMe() const {
@ -3441,14 +3450,25 @@ HistoryBlock::HistoryBlock(not_null<History*> history)
: _history(history) {
}
int HistoryBlock::resizeGetHeight(int newWidth, bool resizeAllItems) {
int HistoryBlock::resizeGetHeight(int newWidth, ResizeRequest request) {
auto y = 0;
for (const auto &message : messages) {
message->setY(y);
if (resizeAllItems || message->pendingResize()) {
if (request == ResizeRequest::ReinitAll) {
for (const auto &message : messages) {
message->setY(y);
message->initDimensions();
y += message->resizeGetHeight(newWidth);
} else {
y += message->height();
}
} else if (request == ResizeRequest::ResizeAll) {
for (const auto &message : messages) {
message->setY(y);
y += message->resizeGetHeight(newWidth);
}
} else {
for (const auto &message : messages) {
message->setY(y);
y += message->pendingResize()
? message->resizeGetHeight(newWidth)
: message->height();
}
}
_height = y;

View File

@ -94,6 +94,7 @@ public:
}
void forumChanged(Data::Forum *old);
[[nodiscard]] bool isForum() const;
not_null<History*> migrateToOrMe() const;
History *migrateFrom() const;
@ -462,10 +463,11 @@ private:
enum class Flag : uchar {
HasPendingResizedItems = (1 << 0),
IsTopPromoted = (1 << 1),
IsForum = (1 << 2),
FakeUnreadWhileOpened = (1 << 3),
HasPinnedMessages = (1 << 4),
PendingAllItemsResize = (1 << 1),
IsTopPromoted = (1 << 2),
IsForum = (1 << 3),
FakeUnreadWhileOpened = (1 << 4),
HasPinnedMessages = (1 << 5),
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) {
@ -636,12 +638,18 @@ private:
HistoryView::SendActionPainter _sendActionPainter;
};
};
class HistoryBlock {
public:
using Element = HistoryView::Element;
enum class ResizeRequest {
ReinitAll = 0,
ResizeAll = 1,
ResizePending = 2,
};
HistoryBlock(not_null<History*> history);
HistoryBlock(const HistoryBlock &) = delete;
HistoryBlock &operator=(const HistoryBlock &) = delete;
@ -652,7 +660,7 @@ public:
void remove(not_null<Element*> view);
void refreshView(not_null<Element*> view);
int resizeGetHeight(int newWidth, bool resizeAllItems);
int resizeGetHeight(int newWidth, ResizeRequest request);
int y() const {
return _y;
}

View File

@ -227,7 +227,7 @@ public:
return _widget ? _widget->elementAnimationsPaused() : false;
}
bool elementHideReply(not_null<const Element*> view) override {
return false;
return view->isTopicRootReply();
}
bool elementShownUnread(not_null<const Element*> view) override {
return view->data()->unread(view->data()->history());
@ -2096,7 +2096,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}
const auto repliesCount = item->repliesCount();
const auto withReplies = (repliesCount > 0);
const auto topicRootId = item->history()->peer->isForum()
const auto topicRootId = item->history()->isForum()
? item->topicRootId()
: 0;
if (topicRootId

View File

@ -775,7 +775,7 @@ void HistoryItem::setRealId(MsgId newId) {
}
_history->owner().notifyItemDataChange(this);
_history->owner().requestItemRepaint(this);
_history->owner().requestItemResize(this);
}
bool HistoryItem::canPin() const {

View File

@ -2971,7 +2971,7 @@ void HistoryWidget::unreadCountUpdated() {
}
});
} else {
_cornerButtons.updateJumpDownVisibility(_history->peer->isForum()
_cornerButtons.updateJumpDownVisibility(_history->isForum()
? 0
: _history->chatListBadgesState().unreadCounter);
}
@ -5991,7 +5991,7 @@ void HistoryWidget::handlePeerMigration() {
}
bool HistoryWidget::replyToPreviousMessage() {
if (!_history || _editMsgId || _history->peer->isForum()) {
if (!_history || _editMsgId || _history->isForum()) {
return false;
}
const auto fullId = FullMsgId(_history->peer->id, _replyToId);
@ -6014,7 +6014,7 @@ bool HistoryWidget::replyToPreviousMessage() {
}
bool HistoryWidget::replyToNextMessage() {
if (!_history || _editMsgId || _history->peer->isForum()) {
if (!_history || _editMsgId || _history->isForum()) {
return false;
}
const auto fullId = FullMsgId(_history->peer->id, _replyToId);

View File

@ -617,7 +617,7 @@ bool AddViewRepliesAction(
|| (context != Context::History && context != Context::Pinned)) {
return false;
}
const auto topicRootId = item->history()->peer->isForum()
const auto topicRootId = item->history()->isForum()
? item->topicRootId()
: 0;
const auto repliesCount = item->repliesCount();

View File

@ -566,6 +566,10 @@ bool Element::isBubbleAttachedToNext() const {
return _flags & Flag::BubbleAttachedToNext;
}
bool Element::isTopicRootReply() const {
return _flags & Flag::TopicRootReply;
}
int Element::skipBlockWidth() const {
return st::msgDateSpace + infoWidth() - st::msgDateDelta.x();
}
@ -905,7 +909,8 @@ bool Element::computeIsAttachToPrevious(not_null<Element*> previous) {
< kAttachMessageToPreviousSecondsDelta)
&& mayBeAttached(this)
&& mayBeAttached(previous)
&& (!previousMarkup || previousMarkup->hiddenBy(prev->media()));
&& (!previousMarkup || previousMarkup->hiddenBy(prev->media()))
&& (item->topicRootId() == prev->topicRootId());
if (possible) {
const auto forwarded = item->Get<HistoryMessageForwarded>();
const auto prevForwarded = prev->Get<HistoryMessageForwarded>();
@ -1068,17 +1073,35 @@ void Element::recountDisplayDateInBlocks() {
}
QSize Element::countOptimalSize() {
_flags &= ~Flag::NeedsResize;
return performCountOptimalSize();
}
QSize Element::countCurrentSize(int newWidth) {
if (_flags & Flag::NeedsResize) {
_flags &= ~Flag::NeedsResize;
initDimensions();
}
return performCountCurrentSize(newWidth);
}
void Element::refreshIsTopicRootReply() {
const auto topicRootReply = countIsTopicRootReply();
if (topicRootReply) {
_flags |= Flag::TopicRootReply;
} else {
_flags &= ~Flag::TopicRootReply;
}
}
bool Element::countIsTopicRootReply() const {
const auto item = data();
if (!item->history()->isForum()) {
return false;
}
const auto replyTo = item->replyToId();
return !replyTo || (item->topicRootId() == replyTo);
}
void Element::setDisplayDate(bool displayDate) {
const auto item = data();
if (displayDate && !Has<DateBadge>()) {
@ -1156,6 +1179,10 @@ bool Element::displayFromName() const {
return false;
}
bool Element::displayTopicButton() const {
return false;
}
bool Element::displayForwardedFrom() const {
return false;
}

View File

@ -254,6 +254,7 @@ public:
SpecialOnlyEmoji = 0x0080,
CustomEmojiRepainting = 0x0100,
ScheduledUntilOnline = 0x0200,
TopicRootReply = 0x0400,
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; }
@ -290,6 +291,8 @@ public:
[[nodiscard]] bool isBubbleAttachedToPrevious() const;
[[nodiscard]] bool isBubbleAttachedToNext() const;
[[nodiscard]] bool isTopicRootReply() const;
[[nodiscard]] int skipBlockWidth() const;
[[nodiscard]] int skipBlockHeight() const;
[[nodiscard]] virtual int infoWidth() const;
@ -373,6 +376,7 @@ public:
[[nodiscard]] virtual bool displayFromPhoto() const;
[[nodiscard]] virtual bool hasFromName() const;
[[nodiscard]] virtual bool displayFromName() const;
[[nodiscard]] virtual bool displayTopicButton() const;
[[nodiscard]] virtual bool displayForwardedFrom() const;
[[nodiscard]] virtual bool hasOutLayout() const;
[[nodiscard]] virtual bool drawBubble() const;
@ -497,6 +501,7 @@ protected:
void clearSpecialOnlyEmoji();
void checkSpecialOnlyEmoji();
void refreshIsTopicRootReply();
private:
// This should be called only from previousInBlocksChanged()
@ -510,6 +515,8 @@ private:
// HistoryView::Element::Flag::AttachedToPrevious.
void recountAttachToPreviousInBlocks();
[[nodiscard]] bool countIsTopicRootReply() const;
QSize countOptimalSize() final override;
QSize countCurrentSize(int newWidth) final override;

View File

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_message.h"
#include "core/click_handler_types.h" // ClickHandlerContext
#include "core/ui_integration.h"
#include "history/view/history_view_cursor_state.h"
#include "history/history_item_components.h"
#include "history/history_message.h"
@ -225,6 +226,23 @@ QString FastReplyText() {
return tr::lng_fast_reply(tr::now);
}
[[nodiscard]] ClickHandlerPtr MakeTopicButtonLink(
not_null<Data::ForumTopic*> topic,
MsgId messageId) {
const auto weak = base::make_weak(topic);
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
if (const auto controller = my.sessionWindow.get()) {
if (const auto strong = weak.get()) {
controller->showTopic(
strong,
messageId,
Window::SectionShow::Way::Forward);
}
}
});
}
} // namespace
style::color FromNameFg(
@ -260,11 +278,19 @@ style::color FromNameFg(
struct Message::CommentsButton {
std::unique_ptr<Ui::RippleAnimation> ripple;
int rippleShift = 0;
std::vector<UserpicInRow> userpics;
QImage cachedUserpics;
ClickHandlerPtr link;
QPoint lastPoint;
int rippleShift = 0;
};
struct Message::TopicButton {
std::unique_ptr<Ui::RippleAnimation> ripple;
ClickHandlerPtr link;
Ui::Text::String name;
QPoint lastPoint;
int nameVersion = 0;
};
struct Message::FromNameStatus {
@ -488,9 +514,11 @@ auto Message::takeReactionAnimations()
QSize Message::performCountOptimalSize() {
const auto item = message();
const auto markup = item->inlineReplyMarkup();
refreshIsTopicRootReply();
validateText();
validateInlineKeyboard(markup);
updateViewButtonExistence();
refreshTopicButton();
updateMediaInBubbleState();
refreshRightBadge();
refreshInfoSkipBlock();
@ -614,6 +642,15 @@ QSize Message::performCountOptimalSize() {
} else if (via && !displayForwardedFrom()) {
accumulate_max(maxWidth, st::msgPadding.left() + via->maxWidth + st::msgPadding.right());
}
if (displayTopicButton()) {
const auto padding = st::msgPadding + st::topicButtonPadding;
accumulate_max(
maxWidth,
(padding.left()
+ _topicButton->name.maxWidth()
+ st::topicButtonArrowSkip
+ padding.right()));
}
if (displayForwardedFrom()) {
const auto skip1 = forwarded->psaType.isEmpty()
? 0
@ -653,6 +690,34 @@ QSize Message::performCountOptimalSize() {
return QSize(maxWidth, minHeight);
}
void Message::refreshTopicButton() {
const auto item = message();
if (isAttachedToPrevious() || context() != Context::History) {
_topicButton = nullptr;
} else if (const auto topic = item->topic()) {
if (!_topicButton) {
_topicButton = std::make_unique<TopicButton>();
}
const auto jumpToId = IsServerMsgId(item->id) ? item->id : MsgId();
_topicButton->link = MakeTopicButtonLink(topic, jumpToId);
if (_topicButton->nameVersion != topic->titleVersion()) {
_topicButton->nameVersion = topic->titleVersion();
const auto context = Core::MarkedTextContext{
.session = &history()->session(),
.customEmojiRepaint = [=] { customEmojiRepaint(); },
.customEmojiLoopLimit = 1,
};
_topicButton->name.setMarkedText(
st::fwdTextStyle,
topic->titleWithIcon(),
kMarkupTextOptions,
context);
}
} else {
_topicButton = nullptr;
}
}
int Message::marginTop() const {
auto result = 0;
if (!isHidden()) {
@ -869,6 +934,7 @@ void Message::draw(Painter &p, const PaintContext &context) const {
trect.setY(trect.y() - st::msgPadding.top());
} else {
paintFromName(p, trect, context);
paintTopicButton(p, trect, context);
paintForwardedInfo(p, trect, context);
paintReplyInfo(p, trect, context);
paintViaBotIdInfo(p, trect, context);
@ -1233,6 +1299,71 @@ void Message::paintFromName(
trect.setY(trect.y() + st::msgNameFont->height);
}
void Message::paintTopicButton(
Painter &p,
QRect &trect,
const PaintContext &context) const {
if (!displayTopicButton()) {
return;
}
trect.setTop(trect.top() + st::topicButtonSkip);
const auto padding = st::topicButtonPadding;
const auto availableWidth = trect.width();
const auto height = padding.top()
+ st::msgNameFont->height
+ padding.bottom();
const auto width = std::max(
std::min(
availableWidth,
(padding.left()
+ _topicButton->name.maxWidth()
+ st::topicButtonArrowSkip
+ padding.right())),
height);
const auto rect = QRect(trect.x(), trect.y(), width, height);
const auto st = context.st;
const auto stm = context.messageStyle();
const auto skip = padding.right() + st::topicButtonArrowSkip;
auto color = stm->msgServiceFg->c;
color.setAlpha(color.alpha() / 8);
p.setPen(Qt::NoPen);
p.setBrush(color);
{
auto hq = PainterHighQualityEnabler(p);
p.drawRoundedRect(rect, height / 2, height / 2);
}
if (_topicButton->ripple) {
_topicButton->ripple->paint(
p,
rect.x(),
rect.y(),
this->width(),
&color);
if (_topicButton->ripple->empty()) {
_topicButton->ripple.reset();
}
}
clearCustomEmojiRepaint();
p.setPen(stm->msgServiceFg);
p.setTextPalette(stm->fwdTextPalette);
_topicButton->name.drawElided(
p,
trect.x() + padding.left(),
trect.y() + padding.top(),
width - padding.left() - skip);
const auto &icon = st::topicButtonArrow;
icon.paint(
p,
rect.x() + rect.width() - skip + st::topicButtonArrowPosition.x(),
rect.y() + padding.top() + st::topicButtonArrowPosition.y(),
this->width(),
stm->msgServiceFg->c);
trect.setY(trect.y() + height + st::topicButtonSkip);
}
void Message::paintForwardedInfo(
Painter &p,
QRect &trect,
@ -1388,6 +1519,7 @@ PointState Message::pointState(QPoint point) const {
// trect.setY(trect.y() - st::msgPadding.top());
//} else {
// if (getStateFromName(point, trect, &result)) return result;
// if (getStateTopicButton(point, trect, &result)) return result;
// if (getStateForwardedInfo(point, trect, &result, request)) return result;
// if (getStateReplyInfo(point, trect, &result)) return result;
// if (getStateViaBotIdInfo(point, trect, &result)) return result;
@ -1432,6 +1564,8 @@ void Message::clickHandlerPressedChanged(
return;
} else if (_comments && (handler == _comments->link)) {
toggleCommentsButtonRipple(pressed);
} else if (_topicButton && (handler == _topicButton->link)) {
toggleTopicButtonRipple(pressed);
} else if (_viewButton) {
_viewButton->checkLink(handler, pressed);
}
@ -1444,7 +1578,7 @@ void Message::toggleCommentsButtonRipple(bool pressed) {
return;
} else if (pressed) {
if (!_comments->ripple) {
createCommentsRipple();
createCommentsButtonRipple();
}
_comments->ripple->add(_comments->lastPoint
+ QPoint(_comments->rippleShift, 0));
@ -1522,7 +1656,7 @@ BottomRippleMask Message::bottomRippleMask(int buttonHeight) const {
};
}
void Message::createCommentsRipple() {
void Message::createCommentsButtonRipple() {
auto mask = bottomRippleMask(st::historyCommentsButtonHeight);
_comments->ripple = std::make_unique<Ui::RippleAnimation>(
st::defaultRippleAnimation,
@ -1531,6 +1665,45 @@ void Message::createCommentsRipple() {
_comments->rippleShift = mask.shift;
}
void Message::toggleTopicButtonRipple(bool pressed) {
Expects(_topicButton != nullptr);
if (!drawBubble()) {
return;
} else if (pressed) {
if (!_topicButton->ripple) {
createTopicButtonRipple();
}
_topicButton->ripple->add(_topicButton->lastPoint);
} else if (_topicButton->ripple) {
_topicButton->ripple->lastStop();
}
}
void Message::createTopicButtonRipple() {
const auto geometry = countGeometry().marginsRemoved(st::msgPadding);
const auto availableWidth = geometry.width();
const auto padding = st::topicButtonPadding;
const auto height = padding.top()
+ st::msgNameFont->height
+ padding.bottom();
const auto width = std::max(
std::min(
availableWidth,
(padding.left()
+ _topicButton->name.maxWidth()
+ st::topicButtonArrowSkip
+ padding.right())),
height);
auto mask = Ui::RippleAnimation::RoundRectMask(
{ width, height },
height / 2);
_topicButton->ripple = std::make_unique<Ui::RippleAnimation>(
st::defaultRippleAnimation,
std::move(mask),
[=] { repaint(); });
}
bool Message::hasHeavyPart() const {
return _comments
|| (_fromNameStatus && _fromNameStatus->custom)
@ -1692,6 +1865,9 @@ TextState Message::textState(
if (getStateFromName(point, trect, &result)) {
return result;
}
if (getStateTopicButton(point, trect, &result)) {
return result;
}
if (getStateForwardedInfo(point, trect, &result, request)) {
return result;
}
@ -1847,57 +2023,89 @@ bool Message::getStateFromName(
QPoint point,
QRect &trect,
not_null<TextState*> outResult) const {
const auto item = message();
if (displayFromName()) {
const auto replyWidth = [&] {
if (isUnderCursor() && displayFastReply()) {
return st::msgFont->width(FastReplyText());
if (!displayFromName()) {
return false;
}
const auto replyWidth = [&] {
if (isUnderCursor() && displayFastReply()) {
return st::msgFont->width(FastReplyText());
}
return 0;
}();
if (replyWidth
&& point.x() >= trect.left() + trect.width() - replyWidth
&& point.x() < trect.left() + trect.width() + st::msgPadding.right()
&& point.y() >= trect.top() - st::msgPadding.top()
&& point.y() < trect.top() + st::msgServiceFont->height) {
outResult->link = fastReplyLink();
return true;
}
if (point.y() >= trect.top() && point.y() < trect.top() + st::msgNameFont->height) {
auto availableLeft = trect.left();
auto availableWidth = trect.width();
if (replyWidth) {
availableWidth -= st::msgPadding.right() + replyWidth;
}
const auto item = message();
const auto from = item->displayFrom();
const auto nameText = [&]() -> const Ui::Text::String * {
if (from) {
validateFromNameText(from);
return &_fromName;
} else if (const auto info = item->hiddenSenderInfo()) {
return &info->nameText();
} else {
Unexpected("Corrupt forwarded information in message.");
}
return 0;
}();
if (replyWidth
&& point.x() >= trect.left() + trect.width() - replyWidth
&& point.x() < trect.left() + trect.width() + st::msgPadding.right()
&& point.y() >= trect.top() - st::msgPadding.top()
&& point.y() < trect.top() + st::msgServiceFont->height) {
outResult->link = fastReplyLink();
if (point.x() >= availableLeft
&& point.x() < availableLeft + availableWidth
&& point.x() < availableLeft + nameText->maxWidth()) {
outResult->link = fromLink();
return true;
}
if (point.y() >= trect.top() && point.y() < trect.top() + st::msgNameFont->height) {
auto availableLeft = trect.left();
auto availableWidth = trect.width();
if (replyWidth) {
availableWidth -= st::msgPadding.right() + replyWidth;
}
const auto from = item->displayFrom();
const auto nameText = [&]() -> const Ui::Text::String * {
if (from) {
validateFromNameText(from);
return &_fromName;
} else if (const auto info = item->hiddenSenderInfo()) {
return &info->nameText();
} else {
Unexpected("Corrupt forwarded information in message.");
}
}();
if (point.x() >= availableLeft
&& point.x() < availableLeft + availableWidth
&& point.x() < availableLeft + nameText->maxWidth()) {
outResult->link = fromLink();
return true;
}
auto via = item->Get<HistoryMessageVia>();
if (via
&& !displayForwardedFrom()
&& point.x() >= availableLeft + nameText->maxWidth() + st::msgServiceFont->spacew
&& point.x() < availableLeft + availableWidth
&& point.x() < availableLeft + nameText->maxWidth() + st::msgServiceFont->spacew + via->width) {
outResult->link = via->link;
return true;
}
auto via = item->Get<HistoryMessageVia>();
if (via
&& !displayForwardedFrom()
&& point.x() >= availableLeft + nameText->maxWidth() + st::msgServiceFont->spacew
&& point.x() < availableLeft + availableWidth
&& point.x() < availableLeft + nameText->maxWidth() + st::msgServiceFont->spacew + via->width) {
outResult->link = via->link;
return true;
}
trect.setTop(trect.top() + st::msgNameFont->height);
}
trect.setTop(trect.top() + st::msgNameFont->height);
return false;
}
bool Message::getStateTopicButton(
QPoint point,
QRect &trect,
not_null<TextState*> outResult) const {
if (!displayTopicButton()) {
return false;
}
trect.setTop(trect.top() + st::topicButtonSkip);
const auto padding = st::topicButtonPadding;
const auto availableWidth = trect.width();
const auto height = padding.top()
+ st::msgNameFont->height
+ padding.bottom();
const auto width = std::max(
std::min(
availableWidth,
(padding.left()
+ _topicButton->name.maxWidth()
+ st::topicButtonArrowSkip
+ padding.right())),
height);
const auto rect = QRect(trect.x(), trect.y(), width, height);
if (rect.contains(point)) {
outResult->link = _topicButton->link;
_topicButton->lastPoint = point - rect.topLeft();
return true;
}
trect.setY(trect.y() + height + st::topicButtonSkip);
return false;
}
@ -1906,56 +2114,57 @@ bool Message::getStateForwardedInfo(
QRect &trect,
not_null<TextState*> outResult,
StateRequest request) const {
if (displayForwardedFrom()) {
const auto item = message();
const auto forwarded = item->Get<HistoryMessageForwarded>();
const auto skip1 = forwarded->psaType.isEmpty()
? 0
: st::historyPsaIconSkip1;
const auto skip2 = forwarded->psaType.isEmpty()
? 0
: st::historyPsaIconSkip2;
const auto fits = (forwarded->text.maxWidth() <= (trect.width() - skip1));
const auto fwdheight = (fits ? 1 : 2) * st::semiboldFont->height;
if (point.y() >= trect.top() && point.y() < trect.top() + fwdheight) {
if (skip1) {
const auto &icon = st::historyPsaIconIn;
const auto position = fits
? st::historyPsaIconPosition1
: st::historyPsaIconPosition2;
const auto iconRect = QRect(
trect.x() + trect.width() - position.x() - icon.width(),
trect.y() + position.y(),
icon.width(),
icon.height());
if (iconRect.contains(point)) {
if (const auto link = psaTooltipLink()) {
outResult->link = link;
return true;
}
if (!displayForwardedFrom()) {
return false;
}
const auto item = message();
const auto forwarded = item->Get<HistoryMessageForwarded>();
const auto skip1 = forwarded->psaType.isEmpty()
? 0
: st::historyPsaIconSkip1;
const auto skip2 = forwarded->psaType.isEmpty()
? 0
: st::historyPsaIconSkip2;
const auto fits = (forwarded->text.maxWidth() <= (trect.width() - skip1));
const auto fwdheight = (fits ? 1 : 2) * st::semiboldFont->height;
if (point.y() >= trect.top() && point.y() < trect.top() + fwdheight) {
if (skip1) {
const auto &icon = st::historyPsaIconIn;
const auto position = fits
? st::historyPsaIconPosition1
: st::historyPsaIconPosition2;
const auto iconRect = QRect(
trect.x() + trect.width() - position.x() - icon.width(),
trect.y() + position.y(),
icon.width(),
icon.height());
if (iconRect.contains(point)) {
if (const auto link = psaTooltipLink()) {
outResult->link = link;
return true;
}
}
const auto useWidth = trect.width() - (fits ? skip1 : skip2);
const auto breakEverywhere = (forwarded->text.countHeight(useWidth) > 2 * st::semiboldFont->height);
auto textRequest = request.forText();
if (breakEverywhere) {
textRequest.flags |= Ui::Text::StateRequest::Flag::BreakEverywhere;
}
*outResult = TextState(item, forwarded->text.getState(
point - trect.topLeft(),
useWidth,
textRequest));
outResult->symbol = 0;
outResult->afterSymbol = false;
if (breakEverywhere) {
outResult->cursor = CursorState::Forwarded;
} else {
outResult->cursor = CursorState::None;
}
return true;
}
trect.setTop(trect.top() + fwdheight);
const auto useWidth = trect.width() - (fits ? skip1 : skip2);
const auto breakEverywhere = (forwarded->text.countHeight(useWidth) > 2 * st::semiboldFont->height);
auto textRequest = request.forText();
if (breakEverywhere) {
textRequest.flags |= Ui::Text::StateRequest::Flag::BreakEverywhere;
}
*outResult = TextState(item, forwarded->text.getState(
point - trect.topLeft(),
useWidth,
textRequest));
outResult->symbol = 0;
outResult->afterSymbol = false;
if (breakEverywhere) {
outResult->cursor = CursorState::Forwarded;
} else {
outResult->cursor = CursorState::None;
}
return true;
}
trect.setTop(trect.top() + fwdheight);
return false;
}
@ -2076,6 +2285,14 @@ void Message::updatePressed(QPoint point) {
if (displayFromName()) {
trect.setTop(trect.top() + st::msgNameFont->height);
}
if (displayTopicButton()) {
trect.setTop(trect.top()
+ st::topicButtonSkip
+ st::topicButtonPadding.top()
+ st::msgNameFont->height
+ st::topicButtonPadding.bottom()
+ st::topicButtonSkip);
}
if (displayForwardedFrom()) {
auto forwarded = item->Get<HistoryMessageForwarded>();
auto fwdheight = ((forwarded->text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height;
@ -2652,6 +2869,10 @@ bool Message::hasBubble() const {
return drawBubble();
}
bool Message::displayTopicButton() const {
return _topicButton != nullptr;
}
bool Message::unwrapped() const {
const auto item = message();
if (isHidden()) {
@ -2946,6 +3167,7 @@ void Message::updateMediaInBubbleState() {
auto mediaHasSomethingAbove = false;
auto getMediaHasSomethingAbove = [&] {
return displayFromName()
|| displayTopicButton()
|| displayForwardedFrom()
|| displayedReply()
|| item->Has<HistoryMessageVia>();
@ -3064,6 +3286,13 @@ QRect Message::innerGeometry() const {
// See paintFromName().
result.translate(0, st::msgNameFont->height);
}
if (displayTopicButton()) {
result.translate(0, st::topicButtonSkip
+ st::topicButtonPadding.top()
+ st::msgNameFont->height
+ st::topicButtonPadding.bottom()
+ st::topicButtonSkip);
}
// Skip displayForwardedFrom() until there are no animations for it.
if (displayedReply()) {
// See paintReplyInfo().
@ -3284,6 +3513,14 @@ int Message::resizeContentGetHeight(int newWidth) {
newHeight += st::msgNameFont->height;
}
if (displayTopicButton()) {
newHeight += st::topicButtonSkip
+ st::topicButtonPadding.top()
+ st::msgNameFont->height
+ st::topicButtonPadding.bottom()
+ st::topicButtonSkip;
}
if (displayForwardedFrom()) {
const auto forwarded = item->Get<HistoryMessageForwarded>();
const auto skip1 = forwarded->psaType.isEmpty()

View File

@ -123,6 +123,7 @@ public:
bool hasOutLayout() const override;
bool drawBubble() const override;
bool hasBubble() const override;
bool displayTopicButton() const override;
bool unwrapped() const override;
int minWidthForMedia() const override;
bool hasFastReply() const override;
@ -167,6 +168,7 @@ protected:
private:
struct CommentsButton;
struct FromNameStatus;
struct TopicButton;
void initLogEntryOriginal();
void initPsa();
@ -180,7 +182,10 @@ private:
TextSelection selection) const;
void toggleCommentsButtonRipple(bool pressed);
void createCommentsRipple();
void createCommentsButtonRipple();
void toggleTopicButtonRipple(bool pressed);
void createTopicButtonRipple();
void paintCommentsButton(
Painter &p,
@ -190,6 +195,10 @@ private:
Painter &p,
QRect &trect,
const PaintContext &context) const;
void paintTopicButton(
Painter &p,
QRect &trect,
const PaintContext &context) const;
void paintForwardedInfo(
Painter &p,
QRect &trect,
@ -217,6 +226,10 @@ private:
QPoint point,
QRect &trect,
not_null<TextState*> outResult) const;
bool getStateTopicButton(
QPoint point,
QRect &trect,
not_null<TextState*> outResult) const;
bool getStateForwardedInfo(
QPoint point,
QRect &trect,
@ -257,6 +270,7 @@ private:
[[nodiscard]] bool displayGoToOriginal() const;
[[nodiscard]] ClickHandlerPtr fastReplyLink() const;
void refreshTopicButton();
void refreshInfoSkipBlock();
[[nodiscard]] int plainMaxWidth() const;
[[nodiscard]] int monospaceMaxWidth() const;
@ -279,6 +293,7 @@ private:
mutable ClickHandlerPtr _fastReplyLink;
mutable std::unique_ptr<ViewButton> _viewButton;
std::unique_ptr<Reactions::InlineList> _reactions;
std::unique_ptr<TopicButton> _topicButton;
mutable std::unique_ptr<CommentsButton> _comments;
mutable Ui::Text::String _fromName;

View File

@ -703,7 +703,7 @@ void TopBarWidget::backClicked() {
_controller->closeFolder();
} else if (_activeChat.section == Section::ChatsList
&& _activeChat.key.history()
&& _activeChat.key.history()->peer->isForum()) {
&& _activeChat.key.history()->isForum()) {
_controller->closeForum();
} else {
_controller->showBackFromStack();

View File

@ -430,7 +430,8 @@ bool ExtendedPreview::needsBubble() const {
|| item->viaBot()
|| _parent->displayedReply()
|| _parent->displayForwardedFrom()
|| _parent->displayFromName());
|| _parent->displayFromName()
|| _parent->displayTopicButton());
}
QPoint ExtendedPreview::resolveCustomInfoRightBottom() const {

View File

@ -1308,7 +1308,8 @@ bool Gif::needsBubble() const {
|| item->viaBot()
|| _parent->displayedReply()
|| _parent->displayForwardedFrom()
|| _parent->displayFromName();
|| _parent->displayFromName()
|| _parent->displayTopicButton();
return false;
}

View File

@ -377,7 +377,8 @@ bool Location::needsBubble() const {
|| item->viaBot()
|| _parent->displayedReply()
|| _parent->displayForwardedFrom()
|| _parent->displayFromName();
|| _parent->displayFromName()
|| _parent->displayTopicButton();
}
QPoint Location::resolveCustomInfoRightBottom() const {

View File

@ -775,6 +775,7 @@ bool GroupedMedia::computeNeedBubble() const {
|| _parent->displayedReply()
|| _parent->displayForwardedFrom()
|| _parent->displayFromName()
|| _parent->displayTopicButton()
) {
return true;
}

View File

@ -412,7 +412,7 @@ void Photo::paintUserpicFrame(
auto request = ::Media::Streaming::FrameRequest();
request.outer = size * cIntRetinaFactor();
request.resize = size * cIntRetinaFactor();
const auto forum = _parent->data()->history()->peer->isForum();
const auto forum = _parent->data()->history()->isForum();
if (forum) {
request.rounding = Images::CornersMaskRef(
Images::CornersMask(ImageRoundRadius::Large));
@ -439,7 +439,7 @@ void Photo::paintUserpicFrame(
return;
}
const auto pix = [&] {
const auto forum = _parent->data()->history()->peer->isForum();
const auto forum = _parent->data()->history()->isForum();
const auto args = Images::PrepareArgs{
.options = (forum
? Images::Option::RoundLarge
@ -885,7 +885,8 @@ bool Photo::needsBubble() const {
|| item->viaBot()
|| _parent->displayedReply()
|| _parent->displayForwardedFrom()
|| _parent->displayFromName());
|| _parent->displayFromName()
|| _parent->displayTopicButton());
}
QPoint Photo::resolveCustomInfoRightBottom() const {

View File

@ -261,7 +261,7 @@ void Uploader::sendProgressUpdate(
if (history->peer->isMegagroup()) {
manager.update(history, replyTo, type, progress);
}
} else if (history->peer->isForum()) {
} else if (history->isForum()) {
manager.update(history, item->topicRootId(), type, progress);
}
_api->session().data().requestItemRepaint(item);

View File

@ -649,6 +649,12 @@ historyPinnedBotButton: RoundButton(defaultActiveButton) {
}
historyPinnedBotButtonMaxWidth: 150px;
topicButtonSkip: 3px;
topicButtonPadding: margins(6px, 3px, 8px, 3px);
topicButtonArrowSkip: 8px;
topicButtonArrowPosition: point(3px, 3px);
topicButtonArrow: icon{{ "dialogs/dialogs_topic_arrow", historyReplyIconFg }};
msgBotKbDuration: 200;
msgBotKbFont: semiboldFont;
msgBotKbIconPadding: 4px;