mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-15 21:57:10 +02:00
Handle clicks on topic jump area.
This commit is contained in:
parent
ede34578da
commit
561e3f4809
11 changed files with 486 additions and 141 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue