Optimize online dots + add animations.

This commit is contained in:
John Preston 2019-06-17 16:37:29 +02:00
parent 61c66994a2
commit d0b86e1229
12 changed files with 306 additions and 130 deletions

View File

@ -366,4 +366,11 @@ bool OnlineTextActive(not_null<UserData*> user, TimeId now) {
return OnlineTextActive(user->onlineTill, now);
}
bool IsPeerAnOnlineUser(not_null<PeerData*> peer) {
if (const auto user = peer->asUser()) {
return OnlineTextActive(user, unixtime());
}
return false;
}
} // namespace Data

View File

@ -110,13 +110,14 @@ rpl::producer<bool> CanWriteValue(ChatData *chat);
rpl::producer<bool> CanWriteValue(ChannelData *channel);
rpl::producer<bool> CanWriteValue(not_null<PeerData*> peer);
TimeId SortByOnlineValue(not_null<UserData*> user, TimeId now);
crl::time OnlineChangeTimeout(TimeId online, TimeId now);
crl::time OnlineChangeTimeout(not_null<UserData*> user, TimeId now);
QString OnlineText(TimeId online, TimeId now);
QString OnlineText(not_null<UserData*> user, TimeId now);
QString OnlineTextFull(not_null<UserData*> user, TimeId now);
bool OnlineTextActive(TimeId online, TimeId now);
bool OnlineTextActive(not_null<UserData*> user, TimeId now);
[[nodiscard]] TimeId SortByOnlineValue(not_null<UserData*> user, TimeId now);
[[nodiscard]] crl::time OnlineChangeTimeout(TimeId online, TimeId now);
[[nodiscard]] crl::time OnlineChangeTimeout(not_null<UserData*> user, TimeId now);
[[nodiscard]] QString OnlineText(TimeId online, TimeId now);
[[nodiscard]] QString OnlineText(not_null<UserData*> user, TimeId now);
[[nodiscard]] QString OnlineTextFull(not_null<UserData*> user, TimeId now);
[[nodiscard]] bool OnlineTextActive(TimeId online, TimeId now);
[[nodiscard]] bool OnlineTextActive(not_null<UserData*> user, TimeId now);
[[nodiscard]] bool IsPeerAnOnlineUser(not_null<PeerData*> peer);
} // namespace Data

View File

@ -37,6 +37,7 @@ dialogsPadding: point(10px, 8px);
dialogsOnlineBadgeStroke: 2px;
dialogsOnlineBadgeSize: 10px;
dialogsOnlineBadgeSkip: point(10px, 12px);
dialogsOnlineBadgeDuration: 150;
dialogsImportantBarHeight: 37px;

View File

@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_user.h"
#include "data/data_peer_values.h"
#include "lang/lang_keys.h"
#include "mainwindow.h"
#include "mainwidget.h"
@ -79,21 +80,21 @@ struct InnerWidget::CollapsedRow {
}
Data::Folder *folder = nullptr;
RippleRow row;
BasicRow row;
};
struct InnerWidget::HashtagResult {
HashtagResult(const QString &tag) : tag(tag) {
}
QString tag;
RippleRow row;
BasicRow row;
};
struct InnerWidget::PeerSearchResult {
PeerSearchResult(not_null<PeerData*> peer) : peer(peer) {
}
not_null<PeerData*> peer;
RippleRow row;
BasicRow row;
};
InnerWidget::InnerWidget(
@ -177,39 +178,7 @@ InnerWidget::InnerWidget(
UpdateRowSection::Default | UpdateRowSection::Filtered);
}, lifetime());
const auto handleUserOnline = [=](const Notify::PeerUpdate &peerUpdate) {
if (peerUpdate.peer->isSelf()) {
return;
}
const auto history = session().data().historyLoaded(peerUpdate.peer);
if (!history) {
return;
}
const auto size = st::dialogsOnlineBadgeSize;
const auto stroke = st::dialogsOnlineBadgeStroke;
const auto skip = st::dialogsOnlineBadgeSkip;
const auto edge = st::dialogsPadding.x() + st::dialogsPhotoSize;
const auto updateRect = QRect(
edge - skip.x() - size,
edge - skip.y() - size,
size,
size
).marginsAdded(
{ stroke, stroke, stroke, stroke }
).translated(
st::dialogsPadding
);
updateDialogRow(
RowDescriptor(
history,
FullMsgId()),
updateRect,
UpdateRowSection::Default | UpdateRowSection::Filtered);
};
subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(
Notify::PeerUpdate::Flag::UserOnlineChanged,
handleUserOnline));
setupOnlineStatusCheck();
session().data().chatsListChanges(
) | rpl::filter([=](Data::Folder *folder) {
@ -1507,6 +1476,15 @@ void InnerWidget::repaintCollapsedFolderRow(not_null<Data::Folder*> folder) {
}
}
int InnerWidget::defaultRowTop(not_null<Row*> row) const {
const auto position = row->pos();
auto top = dialogsOffset();
if (base::in_range(position, 0, _pinnedRows.size())) {
top += qRound(_pinnedRows[position].yadd.current());
}
return top + position * st::dialogsRowHeight;
}
void InnerWidget::repaintDialogRow(
Mode list,
not_null<Row*> row) {
@ -1515,12 +1493,7 @@ void InnerWidget::repaintDialogRow(
if (const auto folder = row->folder()) {
repaintCollapsedFolderRow(folder);
}
auto position = row->pos();
auto top = dialogsOffset();
if (base::in_range(position, 0, _pinnedRows.size())) {
top += qRound(_pinnedRows[position].yadd.current());
}
update(0, top + position * st::dialogsRowHeight, width(), st::dialogsRowHeight);
update(0, defaultRowTop(row), width(), st::dialogsRowHeight);
}
} else if (_state == WidgetState::Filtered) {
if (list == Mode::All) {
@ -2816,6 +2789,71 @@ MsgId InnerWidget::lastSearchMigratedId() const {
return _lastSearchMigratedId;
}
void InnerWidget::setupOnlineStatusCheck() {
using namespace Notify;
subscribe(PeerUpdated(), PeerUpdatedHandler(
PeerUpdate::Flag::UserOnlineChanged,
[=](const PeerUpdate &update) { userOnlineUpdated(update); }));
}
void InnerWidget::userOnlineUpdated(const Notify::PeerUpdate &update) {
const auto user = update.peer->isSelf()
? nullptr
: update.peer->asUser();
if (!user) {
return;
}
const auto history = session().data().historyLoaded(user);
if (!history) {
return;
}
const auto size = st::dialogsOnlineBadgeSize;
const auto stroke = st::dialogsOnlineBadgeStroke;
const auto skip = st::dialogsOnlineBadgeSkip;
const auto edge = st::dialogsPadding.x() + st::dialogsPhotoSize;
const auto updateRect = QRect(
edge - skip.x() - size,
edge - skip.y() - size,
size,
size
).marginsAdded(
{ stroke, stroke, stroke, stroke }
).translated(
st::dialogsPadding
);
const auto repaint = [=] {
updateDialogRow(
RowDescriptor(
history,
FullMsgId()),
updateRect,
UpdateRowSection::Default | UpdateRowSection::Filtered);
};
repaint();
const auto findRow = [&](not_null<History*> history)
-> std::pair<Row*, int> {
if (state() == WidgetState::Default) {
const auto row = shownDialogs()->getRow({ history });
return { row, row ? defaultRowTop(row) : 0 };
}
const auto i = ranges::find(
_filterResults,
history.get(),
[](not_null<Row*> row) { return row->history(); });
const auto index = (i - begin(_filterResults));
const auto row = (i == end(_filterResults)) ? nullptr : i->get();
return { row, filteredOffset() + index * st::dialogsRowHeight };
};
if (const auto &[row, top] = findRow(history); row != nullptr) {
const auto visible = (top < _visibleBottom)
&& (top + st::dialogsRowHeight > _visibleTop);
row->setOnline(
Data::OnlineTextActive(user, unixtime()),
visible ? Fn<void()>(crl::guard(this, repaint)) : nullptr);
}
}
void InnerWidget::setupShortcuts() {
Shortcuts::Requests(
) | rpl::filter([=] {

View File

@ -25,6 +25,10 @@ namespace Window {
class SessionController;
} // namespace Window
namespace Notify {
struct PeerUpdate;
} // namespace Notify
namespace Dialogs {
class Row;
@ -204,6 +208,10 @@ private:
bool uniqueSearchResults() const;
bool hasHistoryInResults(not_null<History*> history) const;
int defaultRowTop(not_null<Row*> row) const;
void setupOnlineStatusCheck();
void userOnlineUpdated(const Notify::PeerUpdate &update);
void setupShortcuts();
RowDescriptor computeJump(
const RowDescriptor &to,

View File

@ -204,7 +204,7 @@ enum class Flag {
Selected = 0x02,
SearchResult = 0x04,
SavedMessages = 0x08,
UserOnline = 0x10,
AllowUserOnline = 0x10,
//FeedSearchResult = 0x10, // #feed
};
inline constexpr bool is_flag_type(Flag) { return true; }
@ -212,7 +212,7 @@ inline constexpr bool is_flag_type(Flag) { return true; }
template <typename PaintItemCallback, typename PaintCounterCallback>
void paintRow(
Painter &p,
not_null<const RippleRow*> row,
not_null<const BasicRow*> row,
not_null<Entry*> entry,
Dialogs::Key chat,
PeerData *from,
@ -252,51 +252,12 @@ void paintRow(
fullWidth,
st::dialogsPhotoSize);
} else if (from) {
if (flags & Flag::UserOnline) {
auto frame = QImage(
st::dialogsPhotoSize * cRetinaFactor(),
st::dialogsPhotoSize * cRetinaFactor(),
QImage::Format_ARGB32_Premultiplied);
frame.setDevicePixelRatio(cRetinaFactor());
frame.fill(Qt::transparent);
{
Painter q(&frame);
from->paintUserpicLeft(
q,
0,
0,
fullWidth,
st::dialogsPhotoSize);
PainterHighQualityEnabler hq(q);
q.setCompositionMode(QPainter::CompositionMode_Source);
const auto size = st::dialogsOnlineBadgeSize;
const auto stroke = st::dialogsOnlineBadgeStroke;
const auto skip = st::dialogsOnlineBadgeSkip;
const auto edge = st::dialogsPadding.x() + st::dialogsPhotoSize;
auto pen = QPen(Qt::transparent);
pen.setWidth(stroke);
q.setPen(pen);
q.setBrush(active
? st::dialogsOnlineBadgeFgActive
: st::dialogsOnlineBadgeFg);
q.drawEllipse(
edge - skip.x() - size,
edge - skip.y() - size,
size,
size);
}
p.drawImage(st::dialogsPadding, frame);
} else {
from->paintUserpicLeft(
p,
st::dialogsPadding.x(),
st::dialogsPadding.y(),
fullWidth,
st::dialogsPhotoSize);
}
row->paintUserpic(
p,
from,
(flags & Flag::AllowUserOnline),
active,
fullWidth);
} else if (hiddenSenderInfo) {
hiddenSenderInfo->userpic.paint(
p,
@ -690,14 +651,11 @@ void RowPainter::paint(
? history->peer->migrateTo()
: history->peer.get())
: nullptr;
const auto showUserOnline = peer
&& peer->isUser()
&& Data::OnlineTextActive(peer->asUser(), unixtime())
&& !(fullWidth < st::columnMinimalWidthLeft
&& (displayUnreadCounter || displayUnreadMark));
const auto allowUserOnline = (fullWidth >= st::columnMinimalWidthLeft)
|| (!displayUnreadCounter && !displayUnreadMark);
const auto flags = (active ? Flag::Active : Flag(0))
| (selected ? Flag::Selected : Flag(0))
| (showUserOnline ? Flag::UserOnline : Flag(0))
| (allowUserOnline ? Flag::AllowUserOnline : Flag(0))
| (peer && peer->isSelf() ? Flag::SavedMessages : Flag(0));
const auto paintItemCallback = [&](int nameleft, int namewidth) {
const auto texttop = st::dialogsPadding.y()
@ -908,7 +866,7 @@ QRect RowPainter::sendActionAnimationRect(int animationWidth, int animationHeigh
void PaintCollapsedRow(
Painter &p,
const RippleRow &row,
const BasicRow &row,
Data::Folder *folder,
const QString &text,
int unread,

View File

@ -11,7 +11,7 @@ namespace Dialogs {
class Row;
class FakeRow;
class RippleRow;
class BasicRow;
namespace Layout {
@ -51,7 +51,7 @@ public:
void PaintCollapsedRow(
Painter &p,
const RippleRow &row,
const BasicRow &row,
Data::Folder *folder,
const QString &text,
int unread,

View File

@ -31,7 +31,7 @@ not_null<Row*> List::addToEnd(Key key) {
key,
std::make_unique<Row>(key, _rows.size())
).first->second.get();
_rows.push_back(result);
_rows.emplace_back(result);
if (_sortMode == SortMode::Date) {
adjustByDate(result);
}

View File

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text_options.h"
#include "dialogs/dialogs_entry.h"
#include "data/data_folder.h"
#include "data/data_peer_values.h"
#include "history/history.h"
#include "lang/lang_keys.h"
#include "mainwidget.h"
@ -66,24 +67,59 @@ QString ComposeFolderListEntryText(not_null<Data::Folder*> folder) {
} // namespace
RippleRow::RippleRow() = default;
RippleRow::~RippleRow() = default;
BasicRow::BasicRow() = default;
BasicRow::~BasicRow() = default;
void RippleRow::addRipple(QPoint origin, QSize size, Fn<void()> updateCallback) {
void BasicRow::setOnline(bool online, Fn<void()> updateCallback) const {
if (_online == online) {
return;
}
_online = online;
if (_onlineUserpic && _onlineUserpic->animation.animating()) {
_onlineUserpic->animation.change(
_online ? 1. : 0.,
st::dialogsOnlineBadgeDuration);
} else if (updateCallback) {
ensureOnlineUserpic();
_onlineUserpic->animation.start(
std::move(updateCallback),
_online ? 0. : 1.,
_online ? 1. : 0.,
st::dialogsOnlineBadgeDuration);
}
if (!_online
&& _onlineUserpic
&& !_onlineUserpic->animation.animating()) {
_onlineUserpic = nullptr;
}
}
void BasicRow::addRipple(
QPoint origin,
QSize size,
Fn<void()> updateCallback) {
if (!_ripple) {
auto mask = Ui::RippleAnimation::rectMask(size);
_ripple = std::make_unique<Ui::RippleAnimation>(st::dialogsRipple, std::move(mask), std::move(updateCallback));
_ripple = std::make_unique<Ui::RippleAnimation>(
st::dialogsRipple,
std::move(mask),
std::move(updateCallback));
}
_ripple->add(origin);
}
void RippleRow::stopLastRipple() {
void BasicRow::stopLastRipple() {
if (_ripple) {
_ripple->lastStop();
}
}
void RippleRow::paintRipple(Painter &p, int x, int y, int outerWidth, const QColor *colorOverride) const {
void BasicRow::paintRipple(
Painter &p,
int x,
int y,
int outerWidth,
const QColor *colorOverride) const {
if (_ripple) {
_ripple->paint(p, x, y, outerWidth, colorOverride);
if (_ripple->empty()) {
@ -92,6 +128,97 @@ void RippleRow::paintRipple(Painter &p, int x, int y, int outerWidth, const QCol
}
}
void BasicRow::ensureOnlineUserpic() const {
if (_onlineUserpic) {
return;
}
_onlineUserpic = std::make_unique<OnlineUserpic>();
}
void BasicRow::PaintOnlineFrame(
not_null<OnlineUserpic*> data,
not_null<PeerData*> peer) {
data->frame.fill(Qt::transparent);
Painter q(&data->frame);
peer->paintUserpic(
q,
0,
0,
st::dialogsPhotoSize);
PainterHighQualityEnabler hq(q);
q.setCompositionMode(QPainter::CompositionMode_Source);
const auto size = st::dialogsOnlineBadgeSize;
const auto stroke = st::dialogsOnlineBadgeStroke;
const auto skip = st::dialogsOnlineBadgeSkip;
const auto edge = st::dialogsPadding.x() + st::dialogsPhotoSize;
const auto shrink = (size / 2) * (1. - data->online);
auto pen = QPen(Qt::transparent);
pen.setWidthF(stroke * data->online);
q.setPen(pen);
q.setBrush(data->active
? st::dialogsOnlineBadgeFgActive
: st::dialogsOnlineBadgeFg);
q.drawEllipse(QRectF(
edge - skip.x() - size,
edge - skip.y() - size,
size,
size
).marginsRemoved({ shrink, shrink, shrink, shrink }));
}
void BasicRow::paintUserpic(
Painter &p,
not_null<PeerData*> peer,
bool allowOnline,
bool active,
int fullWidth) const {
setOnline(Data::IsPeerAnOnlineUser(peer));
const auto online = _onlineUserpic
? _onlineUserpic->animation.value(_online ? 1. : 0.)
: (_online ? 1. : 0.);
if (!allowOnline || online == 0.) {
peer->paintUserpicLeft(
p,
st::dialogsPadding.x(),
st::dialogsPadding.y(),
fullWidth,
st::dialogsPhotoSize);
if (!allowOnline || !_online) {
_onlineUserpic = nullptr;
}
return;
}
ensureOnlineUserpic();
if (_onlineUserpic->frame.isNull()) {
_onlineUserpic->frame = QImage(
st::dialogsPhotoSize * cRetinaFactor(),
st::dialogsPhotoSize * cRetinaFactor(),
QImage::Format_ARGB32_Premultiplied);
_onlineUserpic->frame.setDevicePixelRatio(cRetinaFactor());
}
const auto key = peer->userpicUniqueKey();
if (_onlineUserpic->online != online
|| _onlineUserpic->key != key
|| _onlineUserpic->active != active) {
_onlineUserpic->online = online;
_onlineUserpic->key = key;
_onlineUserpic->active = active;
PaintOnlineFrame(_onlineUserpic.get(), peer);
}
p.drawImage(st::dialogsPadding, _onlineUserpic->frame);
}
Row::Row(Key key, int pos) : _id(key), _pos(pos) {
if (const auto history = key.history()) {
setOnline(Data::IsPeerAnOnlineUser(history->peer));
}
}
uint64 Row::sortKey() const {
return _id.entry()->sortKeyInChatList();
}

View File

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "ui/text/text.h"
#include "ui/effects/animations.h"
#include "dialogs/dialogs_key.h"
class History;
@ -22,28 +23,55 @@ namespace Layout {
class RowPainter;
} // namespace Layout
class RippleRow {
class BasicRow {
public:
RippleRow();
~RippleRow();
BasicRow();
~BasicRow();
void setOnline(bool online, Fn<void()> updateCallback = nullptr) const;
void paintUserpic(
Painter &p,
not_null<PeerData*> peer,
bool allowOnline,
bool active,
int fullWidth) const;
void addRipple(QPoint origin, QSize size, Fn<void()> updateCallback);
void stopLastRipple();
void paintRipple(Painter &p, int x, int y, int outerWidth, const QColor *colorOverride = nullptr) const;
void paintRipple(
Painter &p,
int x,
int y,
int outerWidth,
const QColor *colorOverride = nullptr) const;
private:
struct OnlineUserpic {
InMemoryKey key;
float64 online = 0.;
bool active = false;
QImage frame;
Ui::Animations::Simple animation;
};
void ensureOnlineUserpic() const;
static void PaintOnlineFrame(
not_null<OnlineUserpic*> data,
not_null<PeerData*> peer);
mutable std::unique_ptr<Ui::RippleAnimation> _ripple;
mutable std::unique_ptr<OnlineUserpic> _onlineUserpic;
mutable bool _online = false;
};
class List;
class Row : public RippleRow {
class Row : public BasicRow {
public:
explicit Row(std::nullptr_t) {
}
Row(Key key, int pos) : _id(key), _pos(pos) {
}
Row(Key key, int pos);
Key key() const {
return _id;
@ -80,7 +108,7 @@ private:
};
class FakeRow : public RippleRow {
class FakeRow : public BasicRow {
public:
FakeRow(Key searchInChat, not_null<HistoryItem*> item);

View File

@ -96,13 +96,7 @@ inline bool UseEmptyUserpic(PeerData *peer) {
}
inline bool IsSelfPeer(PeerData *peer) {
return (peer && peer->id == Auth().userPeerId());
}
inline bool IsUserOnline(PeerData *peer) {
return peer
&& peer->isUser()
&& Data::OnlineTextActive(peer->asUser(), unixtime());
return (peer && peer->isSelf());
}
inline int UnreadCount(PeerData *peer) {
@ -266,7 +260,7 @@ void SendKeyEvent(int command) {
themeChanged
) | rpl::filter([=](const Update &update) {
return update.type == Update::Type::ApplyingTheme
&& (UnreadCount(_peer) || IsUserOnline(_peer));
&& (UnreadCount(_peer) || Data::IsPeerAnOnlineUser(_peer));
}) | rpl::start_with_next([=] {
[self updateBadge];
}, _lifetime);
@ -373,7 +367,7 @@ void SendKeyEvent(int command) {
// Draw unread or online badge.
auto pixmap = App::pixmapFromImageInPlace(_userpic.toImage());
Painter p(&pixmap);
if (!PaintUnreadBadge(p, _peer) && IsUserOnline(_peer)) {
if (!PaintUnreadBadge(p, _peer) && Data::IsPeerAnOnlineUser(_peer)) {
PaintOnlineCircle(p);
}
[self updateImage:pixmap];

View File

@ -58,6 +58,10 @@ public:
float64 to,
crl::time duration,
anim::transition transition = anim::linear);
void change(
float64 to,
crl::time duration,
anim::transition transition = anim::linear);
void stop();
[[nodiscard]] bool animating() const;
[[nodiscard]] float64 value(float64 final) const;
@ -328,6 +332,16 @@ inline void Simple::start(
startPrepared(to, duration, transition);
}
inline void Simple::change(
float64 to,
crl::time duration,
anim::transition transition) {
Expects(_data != nullptr);
prepare(0. /* ignored */, duration);
startPrepared(to, duration, transition);
}
inline void Simple::prepare(float64 from, crl::time duration) {
const auto isLong = (duration > kLongAnimationDuration);
if (!_data) {