Handle clicks on topic jump area.

This commit is contained in:
John Preston 2022-11-14 11:24:31 +04:00
parent ede34578da
commit 561e3f4809
11 changed files with 486 additions and 141 deletions

View file

@ -156,6 +156,11 @@ InnerWidget::InnerWidget(
_cancelSearchInChat->hide();
_cancelSearchFromUser->hide();
style::PaletteChanged(
) | rpl::start_with_next([=] {
_topicJumpCache = nullptr;
}, lifetime());
session().downloaderTaskFinished(
) | rpl::start_with_next([=] {
update();
@ -336,7 +341,7 @@ void InnerWidget::refreshWithCollapsedRows(bool toTop) {
_selected = nullptr;
}
if (_pressed && _pressed->folder() == archive) {
setPressed(nullptr);
clearPressed();
}
_skipTopDialog = true;
if (!inMainMenu && !_filterId) {
@ -538,6 +543,9 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
.selected = (_menuRow.key
? (row->key() == _menuRow.key)
: selected),
.topicJumpSelected = (selected
&& _selectedTopicJump
&& (!_pressed || _pressedTopicJump)),
.paused = videoPaused,
.narrow = (fullWidth < st::columnMinimalWidthLeft),
});
@ -1142,7 +1150,7 @@ void InnerWidget::clearIrrelevantState() {
_collapsedSelected = -1;
setCollapsedPressed(-1);
_selected = nullptr;
setPressed(nullptr);
clearPressed();
}
}
@ -1168,9 +1176,16 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
: (mouseY >= offset)
? _shownList->rowAtY(mouseY - offset)
: nullptr;
if (_selected != selected || _collapsedSelected != collapsedSelected) {
const auto selectedTopicJump = selected
&& selected->lookupIsInTopicJump(
local.x(),
mouseY - offset - selected->top());
if (_collapsedSelected != collapsedSelected
|| _selected != selected
|| _selectedTopicJump != selectedTopicJump) {
updateSelectedRow();
_selected = selected;
_selectedTopicJump = selectedTopicJump;
_collapsedSelected = collapsedSelected;
updateSelectedRow();
setCursor((_selected || _collapsedSelected)
@ -1203,9 +1218,15 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
if (filteredSelected < 0 || filteredSelected >= _filterResults.size()) {
filteredSelected = -1;
}
if (_filteredSelected != filteredSelected) {
const auto selectedTopicJump = (filteredSelected >= 0)
&& _filterResults[filteredSelected].row->lookupIsInTopicJump(
local.x(),
mouseY - skip - _filterResults[filteredSelected].top);
if (_filteredSelected != filteredSelected
|| _selectedTopicJump != selectedTopicJump) {
updateSelectedRow();
_filteredSelected = filteredSelected;
_selectedTopicJump = selectedTopicJump;
updateSelectedRow();
}
}
@ -1243,7 +1264,7 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
selectByMouse(e->globalPos());
_pressButton = e->button();
setPressed(_selected);
setPressed(_selected, _selectedTopicJump);
setCollapsedPressed(_collapsedSelected);
setHashtagPressed(_hashtagSelected);
_hashtagDeletePressed = _hashtagDeleteSelected;
@ -1257,14 +1278,25 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
});
} else if (_pressed) {
auto row = _pressed;
row->addRipple(
e->pos() - QPoint(0, dialogsOffset() + _pressed->top()),
QSize(width(), _pressed->height()),
[this, row] {
if (!_pinnedShiftAnimation.animating()) {
row->entry()->updateChatListEntry();
}
});
const auto updateCallback = [this, row] {
if (!_pinnedShiftAnimation.animating()) {
row->entry()->updateChatListEntry();
}
};
const auto origin = e->pos()
- QPoint(0, dialogsOffset() + _pressed->top());
if (_pressedTopicJump) {
row->addTopicJumpRipple(
origin,
_topicJumpCache.get(),
updateCallback);
} else {
row->clearTopicJumpRipple();
row->addRipple(
origin,
QSize(width(), _pressed->height()),
updateCallback);
}
_dragStart = e->pos();
} else if (base::in_range(_hashtagPressed, 0, _hashtagResults.size()) && !_hashtagDeletePressed) {
auto row = &_hashtagResults[_hashtagPressed]->row;
@ -1543,8 +1575,10 @@ void InnerWidget::mousePressReleased(
auto collapsedPressed = _collapsedPressed;
setCollapsedPressed(-1);
const auto pressedTopicRootId = _pressedTopicJumpRootId;
const auto pressedTopicJump = _pressedTopicJump;
auto pressed = _pressed;
setPressed(nullptr);
clearPressed();
auto hashtagPressed = _hashtagPressed;
setHashtagPressed(-1);
auto hashtagDeletePressed = _hashtagDeletePressed;
@ -1561,7 +1595,9 @@ void InnerWidget::mousePressReleased(
updateSelectedRow();
if (!wasDragging && button == Qt::LeftButton) {
if ((collapsedPressed >= 0 && collapsedPressed == _collapsedSelected)
|| (pressed && pressed == _selected)
|| (pressed
&& pressed == _selected
&& pressedTopicJump == _selectedTopicJump)
|| (hashtagPressed >= 0
&& hashtagPressed == _hashtagSelected
&& hashtagDeletePressed == _hashtagDeleteSelected)
@ -1570,7 +1606,7 @@ void InnerWidget::mousePressReleased(
&& peerSearchPressed == _peerSearchSelected)
|| (searchedPressed >= 0
&& searchedPressed == _searchedSelected)) {
chooseRow(modifiers);
chooseRow(modifiers, pressedTopicRootId);
}
}
}
@ -1584,15 +1620,23 @@ void InnerWidget::setCollapsedPressed(int pressed) {
}
}
void InnerWidget::setPressed(Row *pressed) {
if (_pressed != pressed) {
void InnerWidget::setPressed(Row *pressed, bool pressedTopicJump) {
if (_pressed != pressed || _pressedTopicJump != pressedTopicJump) {
if (_pressed) {
_pressed->stopLastRipple();
}
_pressed = pressed;
_pressedTopicJump = pressedTopicJump;
const auto history = pressedTopicJump ? pressed->history() : nullptr;
const auto item = history ? history->chatListMessage() : nullptr;
_pressedTopicJumpRootId = item ? item->topicRootId() : MsgId();
}
}
void InnerWidget::clearPressed() {
setPressed(nullptr, false);
}
void InnerWidget::setHashtagPressed(int pressed) {
if (base::in_range(_hashtagPressed, 0, _hashtagResults.size())) {
_hashtagResults[_hashtagPressed]->row.stopLastRipple();
@ -1656,7 +1700,7 @@ void InnerWidget::dialogRowReplaced(
_selected = newRow;
}
if (_pressed == oldRow) {
setPressed(newRow);
setPressed(newRow, _pressedTopicJump);
}
if (_dragging == oldRow) {
if (newRow) {
@ -1704,7 +1748,7 @@ void InnerWidget::handleChatListEntryRefreshes() {
_selected = nullptr;
}
if (_pressed && _pressed->key() == key) {
setPressed(nullptr);
clearPressed();
}
const auto i = ranges::find(
_filterResults,
@ -3147,7 +3191,9 @@ ChosenRow InnerWidget::computeChosenRow() const {
return ChosenRow();
}
bool InnerWidget::chooseRow(Qt::KeyboardModifiers modifiers) {
bool InnerWidget::chooseRow(
Qt::KeyboardModifiers modifiers,
MsgId pressedTopicRootId) {
if (chooseCollapsedRow()) {
return true;
} else if (chooseHashtag()) {
@ -3161,11 +3207,20 @@ bool InnerWidget::chooseRow(Qt::KeyboardModifiers modifiers) {
}
return row;
};
const auto chosen = modifyChosenRow(computeChosenRow(), modifiers);
auto chosen = modifyChosenRow(computeChosenRow(), modifiers);
if (chosen.key) {
if (IsServerMsgId(chosen.message.fullId.msg)) {
session().local().saveRecentSearchHashtags(_filter);
}
if (pressedTopicRootId && !chosen.message.fullId) {
const auto history = chosen.key.history();
if (history->peer->isForum()) {
chosen.message.fullId = {
history->peer->id,
pressedTopicRootId,
};
}
}
_chosenRow.fire_copy(chosen);
return true;
}

View file

@ -113,7 +113,9 @@ public:
void refreshEmptyLabel();
void resizeEmptyLabel();
bool chooseRow(Qt::KeyboardModifiers modifiers = {});
bool chooseRow(
Qt::KeyboardModifiers modifiers = {},
MsgId pressedTopicRootId = {});
void scrollToEntry(const RowDescriptor &entry);
@ -240,7 +242,8 @@ private:
void scrollToItem(int top, int height);
void scrollToDefaultSelected();
void setCollapsedPressed(int pressed);
void setPressed(Row *pressed);
void setPressed(Row *pressed, bool pressedTopicJump);
void clearPressed();
void setHashtagPressed(int pressed);
void setFilteredPressed(int pressed);
void setPeerSearchPressed(int pressed);
@ -404,6 +407,9 @@ private:
bool _skipTopDialog = false;
Row *_selected = nullptr;
Row *_pressed = nullptr;
MsgId _pressedTopicJumpRootId;
bool _selectedTopicJump = false;
bool _pressedTopicJump = false;
Row *_dragging = nullptr;
int _draggingIndex = -1;

View file

@ -35,15 +35,30 @@ void BasicRow::addRipple(
QSize size,
Fn<void()> updateCallback) {
if (!_ripple) {
auto mask = Ui::RippleAnimation::RectMask(size);
_ripple = std::make_unique<Ui::RippleAnimation>(
st::dialogsRipple,
std::move(mask),
addRippleWithMask(
origin,
Ui::RippleAnimation::RectMask(size),
std::move(updateCallback));
} else {
_ripple->add(origin);
}
}
void BasicRow::addRippleWithMask(
QPoint origin,
QImage mask,
Fn<void()> updateCallback) {
_ripple = std::make_unique<Ui::RippleAnimation>(
st::dialogsRipple,
std::move(mask),
std::move(updateCallback));
_ripple->add(origin);
}
void BasicRow::clearRipple() {
_ripple = nullptr;
}
void BasicRow::stopLastRipple() {
if (_ripple) {
_ripple->lastStop();
@ -102,10 +117,11 @@ uint64 Row::sortKey(FilterId filterId) const {
void Row::setCornerBadgeShown(
bool shown,
Fn<void()> updateCallback) const {
if (_cornerBadgeShown == shown) {
const auto value = (shown ? 1 : 0);
if (_cornerBadgeShown == value) {
return;
}
const_cast<Row*>(this)->_cornerBadgeShown = shown;
const_cast<Row*>(this)->_cornerBadgeShown = value;
if (_cornerBadgeUserpic && _cornerBadgeUserpic->animation.animating()) {
_cornerBadgeUserpic->animation.change(
_cornerBadgeShown ? 1. : 0.,
@ -279,6 +295,46 @@ void Row::paintUserpic(
p.setOpacity(1.);
}
bool Row::lookupIsInTopicJump(int x, int y) const {
const auto history = this->history();
return history && history->lastItemDialogsView().isInTopicJump(x, y);
}
void Row::stopLastRipple() {
BasicRow::stopLastRipple();
const auto history = this->history();
const auto view = history ? &history->lastItemDialogsView() : nullptr;
if (view) {
view->stopLastRipple();
}
}
void Row::addTopicJumpRipple(
QPoint origin,
not_null<Ui::TopicJumpCache*> topicJumpCache,
Fn<void()> updateCallback) {
const auto history = this->history();
const auto view = history ? &history->lastItemDialogsView() : nullptr;
if (view) {
view->addTopicJumpRipple(
origin,
topicJumpCache,
std::move(updateCallback));
_topicJumpRipple = 1;
}
}
void Row::clearTopicJumpRipple() {
if (_topicJumpRipple) {
clearRipple();
_topicJumpRipple = 0;
}
}
bool Row::topicJumpRipple() const {
return _topicJumpRipple != 0;
}
FakeRow::FakeRow(
Key searchInChat,
not_null<HistoryItem*> item,

View file

@ -33,6 +33,7 @@ using namespace ::Ui;
class RowPainter;
class VideoUserpic;
struct PaintContext;
struct TopicJumpCache;
} // namespace Dialogs::Ui
namespace Dialogs {
@ -52,7 +53,12 @@ public:
const Ui::PaintContext &context) const;
void addRipple(QPoint origin, QSize size, Fn<void()> updateCallback);
void stopLastRipple();
virtual void stopLastRipple();
void addRippleWithMask(
QPoint origin,
QImage mask,
Fn<void()> updateCallback);
void clearRipple();
void paintRipple(
QPainter &p,
@ -96,6 +102,15 @@ public:
History *historyForCornerBadge,
const Ui::PaintContext &context) const final override;
[[nodiscard]] bool lookupIsInTopicJump(int x, int y) const;
void stopLastRipple() override;
void addTopicJumpRipple(
QPoint origin,
not_null<Ui::TopicJumpCache*> topicJumpCache,
Fn<void()> updateCallback);
void clearTopicJumpRipple();
[[nodiscard]] bool topicJumpRipple() const;
[[nodiscard]] Key key() const {
return _id;
}
@ -149,8 +164,9 @@ private:
mutable std::unique_ptr<CornerBadgeUserpic> _cornerBadgeUserpic;
int _top = 0;
int _height = 0;
int _index = 0;
bool _cornerBadgeShown = false;
int _index : 30 = 0;
int _cornerBadgeShown : 1 = 0;
int _topicJumpRipple : 1 = 0;
};

View file

@ -415,8 +415,19 @@ void Widget::chosenRow(const ChosenRow &row) {
const auto openSearchResult = !controller()->selectingPeer()
&& row.filteredRow;
const auto history = row.key.history();
if (const auto topic = row.key.topic()) {
controller()->content()->chooseThread(topic, row.message.fullId.msg);
const auto topicJump = history
? history->peer->forumTopicFor(row.message.fullId.msg)
: nullptr;
if (topicJump) {
controller()->openForum(history->peer->asChannel());
controller()->content()->chooseThread(
topicJump,
ShowAtUnreadMsgId);
return;
} else if (const auto topic = row.key.topic()) {
controller()->content()->chooseThread(
topic,
row.message.fullId.msg);
} else if (history && history->peer->isForum() && !row.message.fullId) {
controller()->openForum(history->peer->asChannel());
return;

View file

@ -232,6 +232,7 @@ enum class Flag {
SavedMessages = 0x08,
RepliesMessages = 0x10,
AllowUserOnline = 0x20,
TopicJumpRipple = 0x40,
};
inline constexpr bool is_flag_type(Flag) { return true; }
@ -264,11 +265,13 @@ void PaintRow(
: context.selected
? st::dialogsBgOver
: st::dialogsBg;
auto ripple = context.active
? st::dialogsRippleBgActive
: st::dialogsRippleBg;
p.fillRect(geometry, bg);
row->paintRipple(p, 0, 0, context.width, &ripple->c);
if (!(flags & Flag::TopicJumpRipple)) {
auto ripple = context.active
? st::dialogsRippleBgActive
: st::dialogsRippleBg;
row->paintRipple(p, 0, 0, context.width, &ripple->c);
}
const auto history = entry->asHistory();
const auto thread = entry->asThread();
@ -883,7 +886,8 @@ void RowPainter::Paint(
const auto allowUserOnline = !context.narrow || badgesState.empty();
const auto flags = (allowUserOnline ? Flag::AllowUserOnline : Flag(0))
| (peer && peer->isSelf() ? Flag::SavedMessages : Flag(0))
| (peer && peer->isRepliesChat() ? Flag::RepliesMessages : Flag(0));
| (peer && peer->isRepliesChat() ? Flag::RepliesMessages : Flag(0))
| (row->topicJumpRipple() ? Flag::TopicJumpRipple : Flag(0));
const auto paintItemCallback = [&](int nameleft, int namewidth) {
const auto texttop = context.st->textTop;
const auto availableWidth = PaintWideCounter(

View file

@ -48,6 +48,7 @@ struct TopicJumpCorners {
struct TopicJumpCache {
TopicJumpCorners corners;
TopicJumpCorners over;
TopicJumpCorners selected;
TopicJumpCorners rippleMask;
};
@ -61,6 +62,7 @@ struct PaintContext {
int width = 0;
bool active = false;
bool selected = false;
bool topicJumpSelected = false;
bool paused = false;
bool search = false;
bool narrow = false;

View file

@ -200,6 +200,28 @@ void MessageView::prepare(
}
}
bool MessageView::isInTopicJump(int x, int y) const {
return _topics && _topics->isInTopicJumpArea(x, y);
}
void MessageView::addTopicJumpRipple(
QPoint origin,
not_null<TopicJumpCache*> topicJumpCache,
Fn<void()> updateCallback) {
if (_topics) {
_topics->addTopicJumpRipple(
origin,
topicJumpCache,
std::move(updateCallback));
}
}
void MessageView::stopLastRipple() {
if (_topics) {
_topics->stopLastRipple();
}
}
int MessageView::countWidth() const {
auto result = 0;
if (!_senderCache.isEmpty()) {
@ -248,6 +270,8 @@ void MessageView::paint(
const auto jump1 = checkJump ? _topics->jumpToTopicWidth() : 0;
if (jump1) {
paintJumpToLast(p, rect, context, jump1);
} else if (_topics) {
_topics->clearTopicJumpGeometry();
}
if (withTopic) {
@ -320,9 +344,11 @@ void MessageView::paintJumpToLast(
const PaintContext &context,
int width1) const {
if (!context.topicJumpCache) {
_topics->clearTopicJumpGeometry();
return;
}
FillJumpToLastBg(p, {
const auto width2 = countWidth() + st::forumDialogJumpArrowSkip;
const auto geometry = FillJumpToLastBg(p, {
.st = context.st,
.corners = (context.selected
? &context.topicJumpCache->over
@ -332,97 +358,23 @@ void MessageView::paintJumpToLast(
? st::dialogsRippleBg
: st::dialogsBgOver),
.width1 = width1,
.width2 = countWidth() + st::forumDialogJumpArrowSkip,
.width2 = width2,
});
}
void FillJumpToLastBg(QPainter &p, JumpToLastBg context) {
const auto availableWidth = context.geometry.width();
const auto use1 = std::min(context.width1, availableWidth);
const auto use2 = std::min(context.width2, availableWidth);
const auto padding = st::forumDialogJumpPadding;
const auto radius = st::forumDialogJumpRadius;
const auto &bg = context.bg;
auto &normal = context.corners->normal;
auto &inverted = context.corners->inverted;
auto &small = context.corners->small;
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(bg);
const auto origin = context.geometry.topLeft();
const auto delta = std::abs(use1 - use2);
if (delta <= context.st->topicsSkip / 2) {
if (normal.p[0].isNull()) {
normal = Ui::PrepareCornerPixmaps(radius, bg);
}
const auto w = std::max(use1, use2);
const auto h = context.st->topicsHeight + st::normalFont->height;
const auto fill = QRect(origin, QSize(w, h));
Ui::FillRoundRect(p, fill.marginsAdded(padding), bg, normal);
} else {
const auto h1 = context.st->topicsHeight;
const auto h2 = st::normalFont->height;
const auto hmin = std::min(h1, h2);
const auto wantedInvertedRadius = hmin - radius;
const auto invertedr = std::min(wantedInvertedRadius, delta / 2);
const auto smallr = std::min(radius, delta - invertedr);
const auto smallkey = (use1 < use2) ? smallr : (-smallr);
if (normal.p[0].isNull()) {
normal = Ui::PrepareCornerPixmaps(radius, bg);
}
if (inverted.p[0].isNull()
|| context.corners->invertedRadius != invertedr) {
context.corners->invertedRadius = invertedr;
inverted = Ui::PrepareInvertedCornerPixmaps(invertedr, bg);
}
if (smallr != radius
&& (small.isNull() || context.corners->smallKey != smallkey)) {
context.corners->smallKey = smallr;
auto pixmaps = Ui::PrepareCornerPixmaps(smallr, bg);
small = pixmaps.p[(use1 < use2) ? 1 : 3];
}
const auto rect1 = QRect(origin, QSize(use1, h1));
auto no1 = normal;
no1.p[2] = QPixmap();
if (use1 < use2) {
no1.p[3] = QPixmap();
} else if (smallr != radius) {
no1.p[3] = small;
}
auto fill1 = rect1.marginsAdded({
padding.left(),
padding.top(),
padding.right(),
(use1 < use2 ? -padding.top() : padding.bottom()),
if (context.topicJumpSelected) {
p.setOpacity(0.1);
FillJumpToLastPrepared(p, {
.st = context.st,
.corners = &context.topicJumpCache->selected,
.bg = st::dialogsTextFg,
.prepared = geometry,
});
Ui::FillRoundRect(p, fill1, bg, no1);
if (use1 < use2) {
p.drawPixmap(
fill1.x() + fill1.width(),
fill1.y() + fill1.height() - invertedr,
inverted.p[3]);
}
const auto add = QPoint(0, h1);
const auto rect2 = QRect(origin + add, QSize(use2, h2));
const auto fill2 = rect2.marginsAdded({
padding.left(),
(use2 < use1 ? -padding.bottom() : padding.top()),
padding.right(),
padding.bottom(),
});
auto no2 = normal;
no2.p[0] = QPixmap();
if (use2 < use1) {
no2.p[1] = QPixmap();
} else if (smallr != radius) {
no2.p[1] = small;
}
Ui::FillRoundRect(p, fill2, bg, no2);
if (use2 < use1) {
p.drawPixmap(
fill2.x() + fill2.width(),
fill2.y(),
inverted.p[0]);
p.setOpacity(1.);
}
if (!_topics->changeTopicJumpGeometry(geometry)) {
auto color = st::dialogsTextFg->c;
color.setAlpha(color.alpha() / 10);
if (color.alpha() > 0) {
_topics->paintRipple(p, 0, 0, context.width, &color);
}
}
}

View file

@ -35,8 +35,8 @@ namespace Dialogs::Ui {
using namespace ::Ui;
struct PaintContext;
struct TopicJumpCache;
class TopicsView;
struct TopicJumpCorners;
[[nodiscard]] TextWithEntities DialogsPreviewText(TextWithEntities text);
@ -66,6 +66,13 @@ public:
const QRect &geometry,
const PaintContext &context) const;
[[nodiscard]] bool isInTopicJump(int x, int y) const;
void addTopicJumpRipple(
QPoint origin,
not_null<TopicJumpCache*> topicJumpCache,
Fn<void()> updateCallback);
void stopLastRipple();
private:
struct LoadingContext;
@ -90,14 +97,4 @@ private:
const QString &sender,
TextWithEntities topic);
struct JumpToLastBg {
not_null<const style::DialogRow*> st;
not_null<TopicJumpCorners*> corners;
QRect geometry;
const style::color &bg;
int width1 = 0;
int width2 = 0;
};
void FillJumpToLastBg(QPainter &p, JumpToLastBg context);
} // namespace Dialogs::Ui

View file

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/painter.h"
#include "ui/text/text_options.h"
#include "ui/text/text_utilities.h"
#include "ui/effects/ripple_animation.h"
#include "styles/style_dialogs.h"
namespace Dialogs::Ui {
@ -142,4 +143,198 @@ void TopicsView::paint(
}
}
bool TopicsView::changeTopicJumpGeometry(JumpToLastGeometry geometry) {
if (_lastTopicJumpGeometry != geometry) {
_lastTopicJumpGeometry = geometry;
return true;
}
return false;
}
void TopicsView::clearTopicJumpGeometry() {
changeTopicJumpGeometry({});
}
bool TopicsView::isInTopicJumpArea(int x, int y) const {
return _lastTopicJumpGeometry.area1.contains(x, y)
|| _lastTopicJumpGeometry.area2.contains(x, y);
}
void TopicsView::addTopicJumpRipple(
QPoint origin,
not_null<TopicJumpCache*> topicJumpCache,
Fn<void()> updateCallback) {
auto mask = topicJumpRippleMask(topicJumpCache);
if (mask.isNull()) {
return;
}
_ripple = std::make_unique<Ui::RippleAnimation>(
st::dialogsRipple,
std::move(mask),
std::move(updateCallback));
_ripple->add(origin);
}
void TopicsView::stopLastRipple() {
if (_ripple) {
_ripple->lastStop();
}
}
void TopicsView::paintRipple(
QPainter &p,
int x,
int y,
int outerWidth,
const QColor *colorOverride) const {
if (_ripple) {
_ripple->paint(p, x, y, outerWidth, colorOverride);
if (_ripple->empty()) {
_ripple.reset();
}
}
}
QImage TopicsView::topicJumpRippleMask(
not_null<TopicJumpCache*> topicJumpCache) const {
const auto &st = st::forumDialogRow;
const auto area1 = _lastTopicJumpGeometry.area1;
if (area1.isEmpty()) {
return QImage();
}
const auto area2 = _lastTopicJumpGeometry.area2;
const auto drawer = [&](QPainter &p) {
const auto white = style::complex_color([] { return Qt::white; });
// p.setOpacity(.1);
FillJumpToLastPrepared(p, {
.st = &st,
.corners = &topicJumpCache->rippleMask,
.bg = white.color(),
.prepared = _lastTopicJumpGeometry,
});
};
return Ui::RippleAnimation::MaskByDrawer(
QRect(0, 0, 1, 1).united(area1).united(area2).size(),
false,
drawer);
}
JumpToLastGeometry FillJumpToLastBg(QPainter &p, JumpToLastBg context) {
const auto availableWidth = context.geometry.width();
const auto use1 = std::min(context.width1, availableWidth);
const auto use2 = std::min(context.width2, availableWidth);
const auto padding = st::forumDialogJumpPadding;
const auto radius = st::forumDialogJumpRadius;
const auto origin = context.geometry.topLeft();
const auto delta = std::abs(use1 - use2);
if (delta <= context.st->topicsSkip / 2) {
const auto w = std::max(use1, use2);
const auto h = context.st->topicsHeight + st::normalFont->height;
const auto fill = QRect(origin, QSize(w, h));
const auto full = fill.marginsAdded(padding);
auto result = JumpToLastGeometry{ full };
FillJumpToLastPrepared(p, {
.st = context.st,
.corners = context.corners,
.bg = context.bg,
.prepared = result,
});
return result;
}
const auto h1 = context.st->topicsHeight;
const auto h2 = st::normalFont->height;
const auto rect1 = QRect(origin, QSize(use1, h1));
const auto fill1 = rect1.marginsAdded({
padding.left(),
padding.top(),
padding.right(),
(use1 < use2 ? -padding.top() : padding.bottom()),
});
const auto add = QPoint(0, h1);
const auto rect2 = QRect(origin + add, QSize(use2, h2));
const auto fill2 = rect2.marginsAdded({
padding.left(),
(use2 < use1 ? -padding.bottom() : padding.top()),
padding.right(),
padding.bottom(),
});
auto result = JumpToLastGeometry{ fill1, fill2 };
FillJumpToLastPrepared(p, {
.st = context.st,
.corners = context.corners,
.bg = context.bg,
.prepared = result,
});
return result;
}
void FillJumpToLastPrepared(QPainter &p, JumpToLastPrepared context) {
auto &normal = context.corners->normal;
auto &inverted = context.corners->inverted;
auto &small = context.corners->small;
const auto radius = st::forumDialogJumpRadius;
const auto &bg = context.bg;
const auto area1 = context.prepared.area1;
const auto area2 = context.prepared.area2;
if (area2.isNull()) {
if (normal.p[0].isNull()) {
normal = Ui::PrepareCornerPixmaps(radius, bg);
}
Ui::FillRoundRect(p, area1, bg, normal);
return;
}
const auto width1 = area1.width();
const auto width2 = area2.width();
const auto delta = std::abs(width1 - width2);
const auto h1 = context.st->topicsHeight;
const auto h2 = st::normalFont->height;
const auto hmin = std::min(h1, h2);
const auto wantedInvertedRadius = hmin - radius;
const auto invertedr = std::min(wantedInvertedRadius, delta / 2);
const auto smallr = std::min(radius, delta - invertedr);
const auto smallkey = (width1 < width2) ? smallr : (-smallr);
if (normal.p[0].isNull()) {
normal = Ui::PrepareCornerPixmaps(radius, bg);
}
if (inverted.p[0].isNull()
|| context.corners->invertedRadius != invertedr) {
context.corners->invertedRadius = invertedr;
inverted = Ui::PrepareInvertedCornerPixmaps(invertedr, bg);
}
if (smallr != radius
&& (small.isNull() || context.corners->smallKey != smallkey)) {
context.corners->smallKey = smallr;
auto pixmaps = Ui::PrepareCornerPixmaps(smallr, bg);
small = pixmaps.p[(width1 < width2) ? 1 : 3];
}
auto no1 = normal;
no1.p[2] = QPixmap();
if (width1 < width2) {
no1.p[3] = QPixmap();
} else if (smallr != radius) {
no1.p[3] = small;
}
Ui::FillRoundRect(p, area1, bg, no1);
if (width1 < width2) {
p.drawPixmap(
area1.x() + width1,
area1.y() + area1.height() - invertedr,
inverted.p[3]);
}
auto no2 = normal;
no2.p[0] = QPixmap();
if (width2 < width1) {
no2.p[1] = QPixmap();
} else if (smallr != radius) {
no2.p[1] = small;
}
Ui::FillRoundRect(p, area2, bg, no2);
if (width2 < width1) {
p.drawPixmap(
area2.x() + width2,
area2.y(),
inverted.p[0]);
}
}
} // namespace Dialogs::Ui

View file

@ -19,6 +19,7 @@ class ForumTopic;
} // namespace Data
namespace Ui {
class RippleAnimation;
} // namespace Ui
namespace Dialogs::Ui {
@ -26,6 +27,34 @@ namespace Dialogs::Ui {
using namespace ::Ui;
struct PaintContext;
struct TopicJumpCache;
struct TopicJumpCorners;
struct JumpToLastBg {
not_null<const style::DialogRow*> st;
not_null<TopicJumpCorners*> corners;
QRect geometry;
const style::color &bg;
int width1 = 0;
int width2 = 0;
};
struct JumpToLastGeometry {
QRect area1;
QRect area2;
friend inline auto operator<=>(
const JumpToLastGeometry&,
const JumpToLastGeometry&) = default;
};
JumpToLastGeometry FillJumpToLastBg(QPainter &p, JumpToLastBg context);
struct JumpToLastPrepared {
not_null<const style::DialogRow*> st;
not_null<TopicJumpCorners*> corners;
const style::color &bg;
const JumpToLastGeometry &prepared;
};
void FillJumpToLastPrepared(QPainter &p, JumpToLastPrepared context);
class TopicsView final {
public:
@ -46,6 +75,22 @@ public:
const QRect &geometry,
const PaintContext &context) const;
bool changeTopicJumpGeometry(JumpToLastGeometry geometry);
void clearTopicJumpGeometry();
[[nodiscard]] bool isInTopicJumpArea(int x, int y) const;
void addTopicJumpRipple(
QPoint origin,
not_null<TopicJumpCache*> topicJumpCache,
Fn<void()> updateCallback);
void paintRipple(
QPainter &p,
int x,
int y,
int outerWidth,
const QColor *colorOverride) const;
void clearRipple();
void stopLastRipple();
[[nodiscard]] rpl::lifetime &lifetime() {
return _lifetime;
}
@ -57,9 +102,15 @@ private:
int version = -1;
bool unread = false;
};
[[nodiscard]] QImage topicJumpRippleMask(
not_null<TopicJumpCache*> topicJumpCache) const;
const not_null<Data::Forum*> _forum;
mutable std::vector<Title> _titles;
mutable std::unique_ptr<RippleAnimation> _ripple;
JumpToLastGeometry _lastTopicJumpGeometry;
int _version = -1;
bool _jumpToTopic = false;