Support floating topic bars in forums.

This commit is contained in:
John Preston 2025-08-19 20:09:39 +04:00
parent 10fe5cdd5d
commit 03770c52fe
14 changed files with 325 additions and 105 deletions

View file

@ -77,7 +77,9 @@ DefaultIconEmoji::DefaultIconEmoji(
std::move(value) | rpl::start_with_next([=](DefaultIcon value) {
_icon = value;
_image = QImage();
repaint();
if (repaint) {
repaint();
}
}, _lifetime);
}

View file

@ -235,7 +235,8 @@ void ChannelData::setFlags(ChannelDataFlags which) {
| Flag::CallNotEmpty
| Flag::SimilarExpanded
| Flag::Signatures
| Flag::SignatureProfiles)) {
| Flag::SignatureProfiles
| Flag::ForumTabs)) {
if (const auto history = this->owner().historyLoaded(this)) {
if (diff & Flag::CallNotEmpty) {
history->updateChatListEntry();
@ -262,6 +263,9 @@ void ChannelData::setFlags(ChannelDataFlags which) {
if (diff & (Flag::Signatures | Flag::SignatureProfiles)) {
session().changes().peerUpdated(this, UpdateFlag::Rights);
}
if (diff & Flag::ForumTabs) {
history->forumTabsChanged(which & Flag::ForumTabs);
}
}
}
if (const auto raw = takenForum.get()) {

View file

@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/painter.h"
#include "ui/color_int_conversion.h"
#include "ui/text/text_custom_emoji.h"
#include "ui/text/text_utilities.h"
#include "styles/style_dialogs.h"
#include "styles/style_chat_helpers.h"
@ -756,6 +757,16 @@ TextWithEntities ForumTopic::titleWithIcon() const {
return ForumTopicIconWithTitle(_rootId, _iconId, _title);
}
TextWithEntities ForumTopic::titleWithIconOrLogo() const {
if (_iconId || isGeneral()) {
return titleWithIcon();
}
return Ui::Text::SingleCustomEmoji(Data::TopicIconEmojiEntity({
.title = _title,
.colorId = _colorId,
})).append(' ').append(_title);
}
int ForumTopic::titleVersion() const {
return _titleVersion;
}

View file

@ -148,6 +148,7 @@ public:
[[nodiscard]] QString title() const;
[[nodiscard]] TextWithEntities titleWithIcon() const;
[[nodiscard]] TextWithEntities titleWithIconOrLogo() const;
[[nodiscard]] int titleVersion() const;
void applyTitle(const QString &title);
[[nodiscard]] DocumentId iconId() const;

View file

@ -3464,6 +3464,26 @@ bool History::suggestDraftAllowed() const {
return peer->isMonoforum() && !peer->amMonoforumAdmin();
}
bool History::hasForumThreadBars() const {
if (amMonoforumAdmin()) {
return true;
} else if (const auto channel = peer->asChannel()) {
return channel->forum() && channel->useSubsectionTabs();
}
return false;
}
void History::forumTabsChanged(bool forumTabs) {
for (auto &block : blocks) {
for (auto &view : block->messages) {
view->setPendingResize();
if (forumTabs || view->Has<HistoryView::ForumThreadBar>()) {
view->previousInBlocksChanged();
}
}
}
}
not_null<History*> History::migrateToOrMe() const {
if (const auto to = peer->migrateTo()) {
return owner().history(to);

View file

@ -80,6 +80,8 @@ public:
void monoforumChanged(Data::SavedMessages *old);
[[nodiscard]] bool amMonoforumAdmin() const;
[[nodiscard]] bool suggestDraftAllowed() const;
[[nodiscard]] bool hasForumThreadBars() const;
void forumTabsChanged(bool forumTabs);
[[nodiscard]] not_null<History*> migrateToOrMe() const;
[[nodiscard]] History *migrateFrom() const;

View file

@ -928,8 +928,8 @@ void HistoryInner::enumerateDates(Method method) {
}
template <typename Method>
void HistoryInner::enumerateMonoforumSenders(Method method) {
if (!_history->amMonoforumAdmin()) {
void HistoryInner::enumerateForumThreadBars(Method method) {
if (!_history->hasForumThreadBars()) {
return;
}
@ -946,28 +946,28 @@ void HistoryInner::enumerateMonoforumSenders(Method method) {
// -1 means we didn't find a same-day with previous message yet.
auto lowestInOneBunchItemBottom = -1;
auto senderCallback = [&](not_null<Element*> view, int itemtop, int itembottom) {
auto barCallback = [&](not_null<Element*> view, int itemtop, int itembottom) {
const auto item = view->data();
if (lowestInOneBunchItemBottom < 0 && view->isInOneBunchWithPrevious()) {
lowestInOneBunchItemBottom = itembottom - view->marginBottom();
}
// Call method on a sender for all messages that have it and for those who are not showing it
// Call method on a bar for all messages that have it and for those who are not showing it
// because they are in a one day together with the previous message if they are top-most visible.
if (view->displayMonoforumSender() || (!item->isEmpty() && itemtop <= _visibleAreaTop)) {
if (view->displayForumThreadBar() || (!item->isEmpty() && itemtop <= _visibleAreaTop)) {
if (lowestInOneBunchItemBottom < 0) {
lowestInOneBunchItemBottom = itembottom - view->marginBottom();
}
// Attach sender to the top of the visible area with the same margin as it has in service message.
int senderTop = qMax(itemtop + view->displayedDateHeight(), _visibleAreaTop + skip) + st::msgServiceMargin.top();
// Attach bar to the top of the visible area with the same margin as it has in service message.
int barTop = qMax(itemtop + view->displayedDateHeight(), _visibleAreaTop + skip) + st::msgServiceMargin.top();
// Do not let the sender go below the single-sender messages pack bottom line.
int senderHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top();
senderTop = qMin(senderTop, lowestInOneBunchItemBottom - senderHeight);
// Do not let the bar go below the single-bar messages pack bottom line.
int barHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top();
barTop = qMin(barTop, lowestInOneBunchItemBottom - barHeight);
// Call the template callback function that was passed
// and return if it finished everything it needed.
if (!method(view, itemtop, senderTop)) {
if (!method(view, itemtop, barTop)) {
return false;
}
}
@ -980,7 +980,7 @@ void HistoryInner::enumerateMonoforumSenders(Method method) {
return true;
};
enumerateItems<EnumItemsDirection::BottomToTop>(senderCallback);
enumerateItems<EnumItemsDirection::BottomToTop>(barCallback);
}
TextSelection HistoryInner::computeRenderSelection(
@ -1395,31 +1395,31 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
});
p.setOpacity(1.);
enumerateMonoforumSenders([&](not_null<Element*> view, int itemtop, int senderTop) {
// stop the enumeration if the sender is above the painted rect
if (senderTop + dateHeight <= clip.top()) {
enumerateForumThreadBars([&](not_null<Element*> view, int itemtop, int barTop) {
// stop the enumeration if the bar is above the painted rect
if (barTop + dateHeight <= clip.top()) {
return false;
}
const auto displaySender = view->displayMonoforumSender();
auto senderInPlace = displaySender;
if (senderInPlace) {
const auto correctSenderTop = itemtop + view->displayedDateHeight() + st::msgServiceMargin.top();
senderInPlace = (senderTop < correctSenderTop + st::msgServiceMargin.top());
const auto displayBar = view->displayForumThreadBar();
auto barInPlace = displayBar;
if (barInPlace) {
const auto correctBarTop = itemtop + view->displayedDateHeight() + st::msgServiceMargin.top();
barInPlace = (barTop < correctBarTop + st::msgServiceMargin.top());
}
// paint the sender if it intersects the painted rect
if (senderTop < clip.top() + clip.height()) {
const auto senderY = senderTop - st::msgServiceMargin.top();
if (const auto sender = view->Get<HistoryView::MonoforumSenderBar>()) {
sender->paint(p, context.st, senderY, _contentWidth, _isChatWide, !senderInPlace);
// paint the bar if it intersects the painted rect
if (barTop < clip.top() + clip.height()) {
const auto barY = barTop - st::msgServiceMargin.top();
if (const auto bar = view->Get<HistoryView::ForumThreadBar>()) {
bar->paint(p, context.st, barY, _contentWidth, _isChatWide, !barInPlace);
} else {
HistoryView::MonoforumSenderBar::PaintFor(
_forumThreadBarWidth = HistoryView::ForumThreadBar::PaintForGetWidth(
p,
context.st,
view,
_monoforumSenderUserpicView,
senderY,
_forumThreadBarUserpicView,
barY,
_contentWidth,
_isChatWide);
}
@ -3698,7 +3698,7 @@ void HistoryInner::toggleScrollDateShown() {
void HistoryInner::repaintScrollDateCallback() {
int updateTop = _visibleAreaTop;
int updateHeight = st::msgServiceMargin.top() + st::msgServicePadding.top() + st::msgServiceFont->height + st::msgServicePadding.bottom();
if (_history->amMonoforumAdmin()) {
if (_history->hasForumThreadBars()) {
updateHeight *= 2;
}
update(0, updateTop, width(), updateHeight);
@ -4261,6 +4261,54 @@ void HistoryInner::mouseActionUpdate() {
}
return true;
});
if (!dragState.link) {
enumerateForumThreadBars([&](not_null<Element*> view, int itemtop, int barTop) {
// stop the enumeration if the bar is above our point
if (barTop + dateHeight <= point.y()) {
return false;
}
const auto displayBar = view->displayForumThreadBar();
auto barInPlace = displayBar;
if (barInPlace) {
const auto correctBarTop = itemtop + view->displayedDateHeight() + st::msgServiceMargin.top();
barInPlace = (barTop < correctBarTop + st::msgServiceMargin.top());
}
// stop enumeration if we've found a bar under the cursor
if (barTop <= point.y()) {
const auto item = view->data();
auto barWidth = 0;
if (const auto bar = view->Get<HistoryView::ForumThreadBar>()) {
barWidth = bar->width;
} else {
barWidth = _forumThreadBarWidth;
}
auto barLeft = st::msgServiceMargin.left();
auto maxwidth = _contentWidth;
if (_isChatWide) {
maxwidth = qMin(maxwidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()));
}
auto widthForBar = maxwidth - st::msgServiceMargin.left() - st::msgServiceMargin.left();
barLeft += (widthForBar - barWidth) / 2;
if (point.x() >= barLeft && point.x() < barLeft + barWidth) {
if (!_forumThreadBarLink) {
_forumThreadBarLink = std::make_shared<Window::ForumThreadClickHandler>(item);
} else {
static_cast<Window::ForumThreadClickHandler*>(_forumThreadBarLink.get())->update(item);
}
dragState = TextState(
nullptr,
_forumThreadBarLink);
_dragStateItem = session().data().message(dragState.itemId);
lnkhost = view;
}
}
return true;
});
}
if (!dragState.link) {
StateRequest request;
if (_mouseAction == MouseAction::Selecting) {

View file

@ -315,13 +315,13 @@ private:
template <typename Method>
void enumerateDates(Method method);
// This function finds all monoforum sender elements that are displayed and calls template method
// This function finds all forum thread bar elements that are displayed and calls template method
// for each found date element (from the bottom to the top) using enumerateItems() method.
//
// Method has "bool (*Method)(not_null<Element*> view, int itemtop, int dateTop)" signature
// if it returns false the enumeration stops immediately.
template <typename Method>
void enumerateMonoforumSenders(Method method);
void enumerateForumThreadBars(Method method);
void scrollDateCheck();
void scrollDateHideByTimer();
@ -470,7 +470,8 @@ private:
int _contentWidth = 0;
int _historyPaddingTop = 0;
int _revealHeight = 0;
Ui::PeerUserpicView _monoforumSenderUserpicView;
int _forumThreadBarWidth = 0;
Ui::PeerUserpicView _forumThreadBarUserpicView;
// Save visible area coords for painting / pressing userpics.
int _visibleAreaTop = 0;
@ -572,6 +573,7 @@ private:
Element *_scrollDateLastItem = nullptr;
int _scrollDateLastItemTop = 0;
ClickHandlerPtr _scrollDateLink;
ClickHandlerPtr _forumThreadBarLink;
};

View file

@ -427,20 +427,37 @@ void DateBadge::paint(
ServiceMessagePainter::PaintDate(p, st, text, width, y, w, chatWide);
}
void MonoforumSenderBar::init(
void ForumThreadBar::init(
not_null<PeerData*> parentChat,
not_null<PeerData*> peer) {
sender = peer;
text.setText(st::semiboldTextStyle, peer->name());
not_null<Data::Thread*> thread) {
this->thread = thread;
const auto sublist = thread->asSublist();
if (sublist) {
text.setText(st::semiboldTextStyle, sublist->sublistPeer()->name());
} else if (const auto topic = thread->asTopic()) {
text.setMarkedText(
st::semiboldTextStyle,
topic->titleWithIconOrLogo(),
kMarkupTextOptions,
Core::TextContext({ .session = &topic->session() }));
}
const auto skip = st::monoforumBarUserpicSkip;
const auto userpic = st::msgServicePadding.top()
+ st::msgServiceFont->height
+ st::msgServicePadding.bottom()
- 2 * skip;
width = skip + userpic + skip * 2 + text.maxWidth() + st::msgServicePadding.right();
const auto userpic = sublist
? (st::msgServicePadding.top()
+ st::msgServiceFont->height
+ st::msgServicePadding.bottom()
- 2 * skip)
: (st::msgServicePadding.left() - 3 * skip);
width = skip
+ userpic
+ skip * 2
+ text.maxWidth()
+ st::topicButtonArrowSkip
+ st::msgServicePadding.right();
}
int MonoforumSenderBar::height() const {
int ForumThreadBar::height() const {
return st::msgServiceMargin.top()
+ st::msgServicePadding.top()
+ st::msgServiceFont->height
@ -448,17 +465,29 @@ int MonoforumSenderBar::height() const {
+ st::msgServiceMargin.bottom();
}
void MonoforumSenderBar::paint(
void ForumThreadBar::paint(
Painter &p,
not_null<const Ui::ChatStyle*> st,
int y,
int w,
bool chatWide,
bool skipPatternLine) const {
Paint(p, st, sender, text, width, view, y, w, chatWide, skipPatternLine);
if (const auto strong = thread.get()) {
Paint(
p,
st,
strong,
text,
width,
view,
y,
w,
chatWide,
skipPatternLine);
}
}
void MonoforumSenderBar::PaintFor(
int ForumThreadBar::PaintForGetWidth(
Painter &p,
not_null<const Ui::ChatStyle*> st,
not_null<Element*> itemView,
@ -466,31 +495,49 @@ void MonoforumSenderBar::PaintFor(
int y,
int w,
bool chatWide) {
const auto sublist = itemView->data()->savedSublist();
const auto sender = (sublist && sublist->parentChat())
? sublist->sublistPeer().get()
const auto item = itemView->data();
const auto topic = item->topic();
const auto sublist = item->savedSublist();
const auto sender = topic
? (Data::Thread*)topic
: (sublist && sublist->parentChat())
? (Data::Thread*)sublist
: nullptr;
if (!sender || sender->isMonoforum()) {
return;
auto text = Ui::Text::String();
if (!sender
|| !topic
|| (sublist && sublist->sublistPeer()->isMonoforum())) {
return 0;
} else if (topic) {
text.setMarkedText(
st::semiboldTextStyle,
topic->titleWithIconOrLogo(),
kMarkupTextOptions,
Core::TextContext({ .session = &topic->session() }));
} else {
text.setText(st::semiboldTextStyle, sublist->sublistPeer()->name());
}
auto text = Ui::Text::String(st::semiboldTextStyle, sender->name());
const auto skip = st::monoforumBarUserpicSkip;
const auto userpic = st::msgServicePadding.top()
+ st::msgServiceFont->height
+ st::msgServicePadding.bottom()
- 2 * skip;
const auto userpic = sublist
? (st::msgServicePadding.top()
+ st::msgServiceFont->height
+ st::msgServicePadding.bottom()
- 2 * skip)
: (st::msgServicePadding.left() - 3 * skip);
const auto width = skip
+ userpic
+ skip * 2
+ text.maxWidth()
+ st::topicButtonArrowSkip
+ st::msgServicePadding.right();
Paint(p, st, sender, text, width, userpicView, y, w, chatWide, true);
return width;
}
void MonoforumSenderBar::Paint(
void ForumThreadBar::Paint(
Painter &p,
not_null<const Ui::ChatStyle*> st,
not_null<PeerData*> sender,
not_null<Data::Thread*> thread,
const Ui::Text::String &text,
int width,
Ui::PeerUserpicView &view,
@ -498,8 +545,6 @@ void MonoforumSenderBar::Paint(
int w,
bool chatWide,
bool skipPatternLine) {
Expects(sender != nullptr);
int left = st::msgServiceMargin.left();
const auto maxwidth = chatWide
? std::min(w, WideChatWidth())
@ -528,24 +573,47 @@ void MonoforumSenderBar::Paint(
p.drawLine(left + use, top, 2 * w, top);
}
const auto userpic = st::msgServicePadding.top()
+ st::msgServiceFont->height
+ st::msgServicePadding.bottom()
- 2 * skip;
const auto available = use - (skip + userpic + skip * 2 + st::msgServicePadding.right());
const auto sublist = thread->asSublist();
const auto userpic = sublist
? (st::msgServicePadding.top()
+ st::msgServiceFont->height
+ st::msgServicePadding.bottom()
- 2 * skip)
: (st::msgServicePadding.left() - 3 * skip);
const auto available = use
- (skip
+ userpic
+ skip * 2
+ st::topicButtonArrowSkip
+ st::msgServicePadding.right());
sender->paintUserpic(p, view, left + skip, y + st::msgServiceMargin.top() + skip, userpic);
if (sublist) {
sublist->sublistPeer()->paintUserpic(
p,
view,
left + skip,
y + st::msgServiceMargin.top() + skip,
userpic);
}
const auto textLeft = left + skip + userpic + skip * 2;
const auto textTop = y
+ st::msgServiceMargin.top()
+ st::msgServicePadding.top();
p.setFont(st::msgServiceFont);
p.setPen(st->msgServiceFg());
text.draw(p, {
.position = {
left + skip + userpic + skip * 2,
y + st::msgServiceMargin.top() + st::msgServicePadding.top(),
},
.position = { textLeft, textTop },
.availableWidth = available,
.elisionLines = 1,
});
st::topicButtonArrow.paint(
p,
textLeft + available + st::topicButtonArrowPosition.x(),
textTop + st::topicButtonArrowPosition.y(),
w,
st->msgServiceFg()->c);
}
void ServicePreMessage::init(
@ -1367,7 +1435,7 @@ void Element::validateTextSkipBlock(bool has, int width, int height) {
}
void Element::previousInBlocksChanged() {
recountMonoforumSenderBarInBlocks();
recountThreadBarInBlocks();
recountDisplayDateInBlocks();
recountAttachToPreviousInBlocks();
}
@ -1404,7 +1472,7 @@ bool Element::computeIsAttachToPrevious(not_null<Element*> previous) {
if (!Has<DateBadge>()
&& !Has<UnreadBar>()
&& !Has<ServicePreMessage>()
&& !Has<MonoforumSenderBar>()) {
&& !Has<ForumThreadBar>()) {
const auto prev = previous->data();
const auto previousMarkup = prev->inlineReplyMarkup();
const auto possible = (std::abs(prev->date() - item->date())
@ -1516,12 +1584,12 @@ bool Element::isInOneDayWithPrevious() const {
return !data()->isEmpty() && !displayDate();
}
bool Element::displayMonoforumSender() const {
return Has<MonoforumSenderBar>();
bool Element::displayForumThreadBar() const {
return Has<ForumThreadBar>();
}
bool Element::isInOneBunchWithPrevious() const {
return !data()->isEmpty() && !displayMonoforumSender();
return !data()->isEmpty() && !displayForumThreadBar();
}
void Element::recountAttachToPreviousInBlocks() {
@ -1542,34 +1610,49 @@ void Element::recountAttachToPreviousInBlocks() {
setAttachToPrevious(attachToPrevious, previous);
}
void Element::recountMonoforumSenderBarInBlocks() {
void Element::recountThreadBarInBlocks() {
const auto item = data();
const auto topic = item->topic();
const auto sublist = item->savedSublist();
const auto parentChat = sublist ? sublist->parentChat() : nullptr;
const auto barPeer = [&]() -> PeerData* {
const auto parentChat = (topic && topic->channel()->useSubsectionTabs())
? topic->channel().get()
: sublist
? sublist->parentChat()
: nullptr;
const auto barThread = [&]() -> Data::Thread* {
if (!parentChat
|| isHidden()
|| item->isEmpty()
|| item->isSponsored()) {
return nullptr;
}
const auto sublistPeer = sublist->sublistPeer();
if (const auto previous = previousDisplayedInBlocks()) {
const auto prev = previous->data();
if (const auto prevSublist = prev->savedSublist()) {
if (const auto prevTopic = prev->topic()) {
Assert(prevTopic->channel() == parentChat);
const auto topicRootId = topic->rootId();
if (prevTopic->rootId() == topicRootId) {
return nullptr;
}
} else if (const auto prevSublist = prev->savedSublist()) {
Assert(prevSublist->parentChat() == parentChat);
const auto sublistPeer = sublist->sublistPeer();
if (prevSublist->sublistPeer() == sublistPeer) {
return nullptr;
}
}
}
return (sublistPeer == parentChat) ? nullptr : sublistPeer.get();
return topic
? (Data::Thread*)topic
: (sublist && sublist->sublistPeer() != parentChat)
? (Data::Thread*)sublist
: nullptr;
}();
if (barPeer && !Has<MonoforumSenderBar>()) {
AddComponents(MonoforumSenderBar::Bit());
Get<MonoforumSenderBar>()->init(parentChat, barPeer);
} else if (!barPeer && Has<MonoforumSenderBar>()) {
RemoveComponents(MonoforumSenderBar::Bit());
if (barThread && !Has<ForumThreadBar>()) {
AddComponents(ForumThreadBar::Bit());
Get<ForumThreadBar>()->init(parentChat, barThread);
} else if (!barThread && Has<ForumThreadBar>()) {
RemoveComponents(ForumThreadBar::Bit());
}
}

View file

@ -20,6 +20,7 @@ struct HistoryMessageReply;
struct PreparedServiceText;
namespace Data {
class Thread;
struct Reaction;
struct ReactionId;
} // namespace Data
@ -265,8 +266,10 @@ struct DateBadge : RuntimeComponent<DateBadge, Element> {
};
struct MonoforumSenderBar : RuntimeComponent<MonoforumSenderBar, Element> {
void init(not_null<PeerData*> parentChat, not_null<PeerData*> peer);
struct ForumThreadBar : RuntimeComponent<ForumThreadBar, Element> {
void init(
not_null<PeerData*> parentChat,
not_null<Data::Thread*> thread);
int height() const;
void paint(
@ -276,7 +279,7 @@ struct MonoforumSenderBar : RuntimeComponent<MonoforumSenderBar, Element> {
int w,
bool chatWide,
bool skipPatternLine) const;
static void PaintFor(
static int PaintForGetWidth(
Painter &p,
not_null<const Ui::ChatStyle*> st,
not_null<Element*> itemView,
@ -285,9 +288,8 @@ struct MonoforumSenderBar : RuntimeComponent<MonoforumSenderBar, Element> {
int w,
bool chatWide);
PeerData *sender = nullptr;
base::weak_ptr<Data::Thread> thread;
Ui::Text::String text;
ClickHandlerPtr link;
mutable Ui::PeerUserpicView view;
int width = 0;
@ -295,7 +297,7 @@ private:
static void Paint(
Painter &p,
not_null<const Ui::ChatStyle*> st,
not_null<PeerData*> sender,
not_null<Data::Thread*> thread,
const Ui::Text::String &text,
int width,
Ui::PeerUserpicView &view,
@ -475,7 +477,7 @@ public:
[[nodiscard]] bool displayDate() const;
[[nodiscard]] bool isInOneDayWithPrevious() const;
[[nodiscard]] bool displayMonoforumSender() const;
[[nodiscard]] bool displayForumThreadBar() const;
[[nodiscard]] bool isInOneBunchWithPrevious() const;
virtual void draw(Painter &p, const PaintContext &context) const = 0;
@ -688,7 +690,7 @@ protected:
std::unique_ptr<Reactions::InlineList> _reactions;
private:
void recountMonoforumSenderBarInBlocks();
void recountThreadBarInBlocks();
// This should be called only from previousInBlocksChanged()
// to add required bits to the Composer mask

View file

@ -1092,10 +1092,13 @@ QSize Message::performCountOptimalSize() {
void Message::refreshTopicButton() {
const auto item = data();
if (isAttachedToPrevious()
|| delegate()->elementHideTopicButton(this)) {
if (isAttachedToPrevious() || delegate()->elementHideTopicButton(this)) {
_topicButton = nullptr;
} else if (const auto topic = item->topic()) {
if (topic->channel()->useSubsectionTabs()) {
_topicButton = nullptr;
return;
}
if (!_topicButton) {
_topicButton = std::make_unique<TopicButton>();
}
@ -1132,8 +1135,8 @@ int Message::marginTop() const {
if (const auto bar = Get<UnreadBar>()) {
result += bar->height();
}
if (const auto monoforumBar = Get<MonoforumSenderBar>()) {
result += monoforumBar->height();
if (const auto bar = Get<ForumThreadBar>()) {
result += bar->height();
}
if (const auto service = Get<ServicePreMessage>()) {
result += service->height;
@ -1181,8 +1184,8 @@ void Message::draw(Painter &p, const PaintContext &context) const {
if (const auto date = Get<DateBadge>()) {
aboveh += date->height();
}
if (const auto sender = Get<MonoforumSenderBar>()) {
aboveh += sender->height();
if (const auto bar = Get<ForumThreadBar>()) {
aboveh += bar->height();
}
if (context.clip.intersects(QRect(0, aboveh, width(), unreadbarh))) {
p.translate(0, aboveh);

View file

@ -527,8 +527,8 @@ int Service::marginTop() const {
if (const auto bar = Get<UnreadBar>()) {
result += bar->height();
}
if (const auto monoforumBar = Get<MonoforumSenderBar>()) {
result += monoforumBar->height();
if (const auto bar = Get<ForumThreadBar>()) {
result += bar->height();
}
if (const auto service = Get<ServicePreMessage>()) {
result += service->height;
@ -553,8 +553,8 @@ void Service::draw(Painter &p, const PaintContext &context) const {
if (const auto date = Get<DateBadge>()) {
aboveh += date->height();
}
if (const auto sender = Get<MonoforumSenderBar>()) {
aboveh += sender->height();
if (const auto bar = Get<ForumThreadBar>()) {
aboveh += bar->height();
}
if (context.clip.intersects(QRect(0, aboveh, width(), unreadbarh))) {
p.translate(0, aboveh);

View file

@ -348,6 +348,33 @@ void DateClickHandler::onClick(ClickContext context) const {
}
}
ForumThreadClickHandler::ForumThreadClickHandler(not_null<HistoryItem*> item)
: _thread(resolveThread(item)) {
}
void ForumThreadClickHandler::update(not_null<HistoryItem*> item) {
_thread = resolveThread(item);
}
void ForumThreadClickHandler::onClick(ClickContext context) const {
const auto my = context.other.value<ClickHandlerContext>();
if (const auto window = my.sessionWindow.get()) {
if (const auto strong = _thread.get()) {
window->showThread(strong, 0, SectionShow::Way::ClearStack);
}
}
}
base::weak_ptr<Data::Thread> ForumThreadClickHandler::resolveThread(
not_null<HistoryItem*> item) const {
if (const auto sublist = item->savedSublist()) {
return sublist;
} else if (const auto topic = item->topic()) {
return topic;
}
return nullptr;
}
MessageHighlightId SearchHighlightId(const QString &query) {
auto result = MessageHighlightId{ .quote = { query } };
if (!result.quote.empty()) {

View file

@ -126,6 +126,21 @@ private:
};
class ForumThreadClickHandler : public ClickHandler {
public:
explicit ForumThreadClickHandler(not_null<HistoryItem*> item);
void update(not_null<HistoryItem*> item);
void onClick(ClickContext context) const override;
private:
[[nodiscard]] base::weak_ptr<Data::Thread> resolveThread(
not_null<HistoryItem*> item) const;
base::weak_ptr<Data::Thread> _thread;
};
struct SectionShow {
enum class Way {
Forward,