mirror of
https://github.com/telegramdesktop/tdesktop
synced 2024-12-30 18:42:13 +00:00
Added animated online dots to panel of pinned dialogs in touchbar.
This commit is contained in:
parent
e5732cba97
commit
25ab88d87a
@ -25,6 +25,7 @@
|
|||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_cloud_file.h"
|
#include "data/data_cloud_file.h"
|
||||||
|
#include "data/data_user.h"
|
||||||
#include "data/stickers/data_stickers.h"
|
#include "data/stickers/data_stickers.h"
|
||||||
#include "dialogs/dialogs_layout.h"
|
#include "dialogs/dialogs_layout.h"
|
||||||
#include "ui/emoji_config.h"
|
#include "ui/emoji_config.h"
|
||||||
@ -34,6 +35,8 @@
|
|||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "base/call_delayed.h"
|
#include "base/call_delayed.h"
|
||||||
#include "base/platform/mac/base_utilities_mac.h"
|
#include "base/platform/mac/base_utilities_mac.h"
|
||||||
|
#include "base/timer.h"
|
||||||
|
#include "base/unixtime.h"
|
||||||
#include "styles/style_basic.h"
|
#include "styles/style_basic.h"
|
||||||
#include "styles/style_dialogs.h"
|
#include "styles/style_dialogs.h"
|
||||||
#include "styles/style_media_player.h"
|
#include "styles/style_media_player.h"
|
||||||
@ -72,6 +75,9 @@ constexpr auto kCommandLink = 0x016;
|
|||||||
constexpr auto kCommandScrubberStickers = 0x020;
|
constexpr auto kCommandScrubberStickers = 0x020;
|
||||||
constexpr auto kCommandScrubberEmoji = 0x021;
|
constexpr auto kCommandScrubberEmoji = 0x021;
|
||||||
|
|
||||||
|
constexpr auto kOnlineCircleSize = 8;
|
||||||
|
constexpr auto kOnlineCircleStrokeWidth = 1.5;
|
||||||
|
|
||||||
constexpr auto kMs = 1000;
|
constexpr auto kMs = 1000;
|
||||||
|
|
||||||
constexpr auto kSongType = AudioMsgId::Type::Song;
|
constexpr auto kSongType = AudioMsgId::Type::Song;
|
||||||
@ -207,6 +213,27 @@ NSRect PeerRectByIndex(int index) {
|
|||||||
kCircleDiameter);
|
kCircleDiameter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TimeId CalculateOnlineTill(not_null<PeerData*> peer) {
|
||||||
|
if (peer->isSelf()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (const auto user = peer->asUser()) {
|
||||||
|
if (!user->isServiceUser() && !user->isBot()) {
|
||||||
|
const auto onlineTill = user->onlineTill;
|
||||||
|
return (onlineTill <= -5)
|
||||||
|
? -onlineTill
|
||||||
|
: (onlineTill <= 0)
|
||||||
|
? 0
|
||||||
|
: onlineTill;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline auto RplToEmpty() {
|
||||||
|
return rpl::map([=] { return rpl::empty_value(); });
|
||||||
|
}
|
||||||
|
|
||||||
int WidthFromString(NSString *s) {
|
int WidthFromString(NSString *s) {
|
||||||
return (int)ceil(
|
return (int)ceil(
|
||||||
[[NSTextField labelWithString:s] frame].size.width) * 1.2;
|
[[NSTextField labelWithString:s] frame].size.width) * 1.2;
|
||||||
@ -548,6 +575,10 @@ void AppendEmojiPacks(
|
|||||||
int x = 0;
|
int x = 0;
|
||||||
int horizontalShift = 0;
|
int horizontalShift = 0;
|
||||||
bool onTop = false;
|
bool onTop = false;
|
||||||
|
|
||||||
|
Ui::Animations::Simple onlineAnimation;
|
||||||
|
TimeId onlineTill = 0;
|
||||||
|
bool hasUnread = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
rpl::lifetime _lifetime;
|
rpl::lifetime _lifetime;
|
||||||
@ -561,6 +592,8 @@ void AppendEmojiPacks(
|
|||||||
bool _selfUnpinned;
|
bool _selfUnpinned;
|
||||||
|
|
||||||
rpl::event_stream<not_null<NSEvent*>> _touches;
|
rpl::event_stream<not_null<NSEvent*>> _touches;
|
||||||
|
|
||||||
|
double _r, _g, _b, _a; // The online circle color.
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)processHorizontalReorder {
|
- (void)processHorizontalReorder {
|
||||||
@ -834,6 +867,8 @@ void AppendEmojiPacks(
|
|||||||
_hasArchive = _selfUnpinned = false;
|
_hasArchive = _selfUnpinned = false;
|
||||||
_savedMessages = SavedMessagesUserpic();
|
_savedMessages = SavedMessagesUserpic();
|
||||||
|
|
||||||
|
using UpdateFlag = Data::PeerUpdate::Flag;
|
||||||
|
|
||||||
const auto downloadLifetime = _lifetime.make_state<rpl::lifetime>();
|
const auto downloadLifetime = _lifetime.make_state<rpl::lifetime>();
|
||||||
const auto peerChangedLifetime = _lifetime.make_state<rpl::lifetime>();
|
const auto peerChangedLifetime = _lifetime.make_state<rpl::lifetime>();
|
||||||
const auto lastDialogsCount = _lifetime.make_state<rpl::variable<int>>(0);
|
const auto lastDialogsCount = _lifetime.make_state<rpl::variable<int>>(0);
|
||||||
@ -841,25 +876,6 @@ void AppendEmojiPacks(
|
|||||||
_pins
|
_pins
|
||||||
) | ranges::views::transform(&Pin::peer);
|
) | ranges::views::transform(&Pin::peer);
|
||||||
|
|
||||||
const auto updateBadge = [=](Pin &pin) {
|
|
||||||
const auto peer = pin.peer;
|
|
||||||
if (IsSelfPeer(peer)
|
|
||||||
|| !peer->owner().history(peer->id)->unreadCountForBadge()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto pixmap = App::pixmapFromImageInPlace(
|
|
||||||
base::take(pin.userpic));
|
|
||||||
if (pixmap.isNull()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Painter p(&pixmap);
|
|
||||||
PaintUnreadBadge(p, peer);
|
|
||||||
pin.userpic = pixmap.toImage();
|
|
||||||
|
|
||||||
const auto userpicIndex = pin.index + [self shift];
|
|
||||||
[self setNeedsDisplayInRect:PeerRectByIndex(userpicIndex)];
|
|
||||||
};
|
|
||||||
const auto updatePanelSize = [=] {
|
const auto updatePanelSize = [=] {
|
||||||
const auto size = lastDialogsCount->current();
|
const auto size = lastDialogsCount->current();
|
||||||
self.image = [[NSImage alloc] initWithSize:NSMakeSize(
|
self.image = [[NSImage alloc] initWithSize:NSMakeSize(
|
||||||
@ -889,6 +905,31 @@ void AppendEmojiPacks(
|
|||||||
*lastDialogsCount = [self shift] + std::ssize(_pins);
|
*lastDialogsCount = [self shift] + std::ssize(_pins);
|
||||||
[self display];
|
[self display];
|
||||||
};
|
};
|
||||||
|
const auto updateBadge = [=](Pin &pin) {
|
||||||
|
const auto peer = pin.peer;
|
||||||
|
if (IsSelfPeer(peer)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto guard = gsl::finally([&] {
|
||||||
|
const auto userpicIndex = pin.index + [self shift];
|
||||||
|
pin.hasUnread = (UnreadCount(peer) != 0);
|
||||||
|
|
||||||
|
[self setNeedsDisplayInRect:PeerRectByIndex(userpicIndex)];
|
||||||
|
});
|
||||||
|
if (!peer->owner().history(peer->id)->unreadCountForBadge()) {
|
||||||
|
singleUserpic(pin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto pixmap = App::pixmapFromImageInPlace(
|
||||||
|
base::take(pin.userpic));
|
||||||
|
if (pixmap.isNull()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Painter p(&pixmap);
|
||||||
|
PaintUnreadBadge(p, peer);
|
||||||
|
pin.userpic = pixmap.toImage();
|
||||||
|
};
|
||||||
const auto listenToDownloaderFinished = [=] {
|
const auto listenToDownloaderFinished = [=] {
|
||||||
base::ObservableViewer(
|
base::ObservableViewer(
|
||||||
_session->downloaderTaskFinished()
|
_session->downloaderTaskFinished()
|
||||||
@ -903,6 +944,80 @@ void AppendEmojiPacks(
|
|||||||
updateUserpics();
|
updateUserpics();
|
||||||
}, *downloadLifetime);
|
}, *downloadLifetime);
|
||||||
};
|
};
|
||||||
|
const auto processOnline = [=](int index) {
|
||||||
|
auto &pin = _pins[index];
|
||||||
|
const auto peer = pin.peer;
|
||||||
|
const auto redrawOnline = [=] {
|
||||||
|
const auto s = kOnlineCircleSize + kOnlineCircleStrokeWidth;
|
||||||
|
[self setNeedsDisplayInRect:NSMakeRect(
|
||||||
|
_pins[index].x + kCircleDiameter - s,
|
||||||
|
0,
|
||||||
|
s,
|
||||||
|
s)];
|
||||||
|
};
|
||||||
|
// TODO: this should be replaced
|
||||||
|
// with the global application timer for online statuses.
|
||||||
|
const auto onlineChanges =
|
||||||
|
peerChangedLifetime->make_state<rpl::event_stream<PeerData*>>();
|
||||||
|
const auto onlineTimer =
|
||||||
|
peerChangedLifetime->make_state<base::Timer>([=] {
|
||||||
|
onlineChanges->fire_copy({ peer });
|
||||||
|
});
|
||||||
|
|
||||||
|
const auto callTimer = [=](auto &pin) {
|
||||||
|
onlineTimer->cancel();
|
||||||
|
if (pin.onlineTill) {
|
||||||
|
const auto time = pin.onlineTill - base::unixtime::now();
|
||||||
|
if (time > 0) {
|
||||||
|
onlineTimer->callOnce(time * crl::time(1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
callTimer(pin);
|
||||||
|
|
||||||
|
using PeerUpdate = Data::PeerUpdate;
|
||||||
|
auto to_peer = rpl::map([=](const PeerUpdate &update) -> PeerData* {
|
||||||
|
return update.peer;
|
||||||
|
});
|
||||||
|
rpl::merge(
|
||||||
|
_session->changes().peerUpdates(
|
||||||
|
peer,
|
||||||
|
UpdateFlag::OnlineStatus) | to_peer,
|
||||||
|
onlineChanges->events()
|
||||||
|
) | rpl::start_with_next([=](PeerData *peer) {
|
||||||
|
const auto it = ranges::find(_pins, peer, &Pin::peer);
|
||||||
|
if (it == end(_pins)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto &pin = *it;
|
||||||
|
const auto index = pin.index;
|
||||||
|
pin.onlineTill = CalculateOnlineTill(pin.peer);
|
||||||
|
|
||||||
|
callTimer(pin);
|
||||||
|
|
||||||
|
if (![NSApplication sharedApplication].active) {
|
||||||
|
pin.onlineAnimation.stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto online = Data::OnlineTextActive(
|
||||||
|
pin.onlineTill,
|
||||||
|
base::unixtime::now());
|
||||||
|
if (pin.onlineAnimation.animating()) {
|
||||||
|
pin.onlineAnimation.change(
|
||||||
|
online ? 1. : 0.,
|
||||||
|
st::dialogsOnlineBadgeDuration);
|
||||||
|
} else {
|
||||||
|
Core::Sandbox::Instance().customEnterFromEventLoop([=] {
|
||||||
|
_pins[index].onlineAnimation.start(
|
||||||
|
redrawOnline,
|
||||||
|
online ? 0. : 1.,
|
||||||
|
online ? 1. : 0.,
|
||||||
|
st::dialogsOnlineBadgeDuration);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, *peerChangedLifetime);
|
||||||
|
};
|
||||||
|
|
||||||
const auto updatePinnedChats = [=] {
|
const auto updatePinnedChats = [=] {
|
||||||
_pins = ranges::view::zip(
|
_pins = ranges::view::zip(
|
||||||
_session->data().pinnedChatsOrder(nullptr, FilterId()),
|
_session->data().pinnedChatsOrder(nullptr, FilterId()),
|
||||||
@ -911,39 +1026,49 @@ void AppendEmojiPacks(
|
|||||||
const auto index = pair.second;
|
const auto index = pair.second;
|
||||||
auto peer = pair.first.history()->peer;
|
auto peer = pair.first.history()->peer;
|
||||||
auto view = peer->createUserpicView();
|
auto view = peer->createUserpicView();
|
||||||
return { std::move(peer), std::move(view), index, QImage() };
|
const auto onlineTill = CalculateOnlineTill(peer);
|
||||||
|
return {
|
||||||
|
.peer = std::move(peer),
|
||||||
|
.userpicView = std::move(view),
|
||||||
|
.index = index,
|
||||||
|
.onlineTill = onlineTill };
|
||||||
});
|
});
|
||||||
_selfUnpinned = ranges::none_of(peers, &PeerData::isSelf);
|
_selfUnpinned = ranges::none_of(peers, &PeerData::isSelf);
|
||||||
|
|
||||||
peerChangedLifetime->destroy();
|
peerChangedLifetime->destroy();
|
||||||
for (const auto &pin : _pins) {
|
for (const auto &pin : _pins) {
|
||||||
_session->changes().peerUpdates(
|
const auto peer = pin.peer;
|
||||||
pin.peer,
|
|
||||||
Data::PeerUpdate::Flag::Photo
|
|
||||||
) | rpl::start_with_next(
|
|
||||||
listenToDownloaderFinished,
|
|
||||||
*peerChangedLifetime);
|
|
||||||
|
|
||||||
using UpdateFlag = Data::PeerUpdate::Flag;
|
|
||||||
auto to_empty = rpl::map([=] { return rpl::empty_value(); });
|
|
||||||
|
|
||||||
const auto index = pin.index;
|
const auto index = pin.index;
|
||||||
|
_session->changes().peerUpdates(
|
||||||
|
peer,
|
||||||
|
UpdateFlag::Photo
|
||||||
|
) | rpl::start_with_next(
|
||||||
|
listenToDownloaderFinished,
|
||||||
|
*peerChangedLifetime);
|
||||||
|
|
||||||
|
if (const auto user = peer->asUser()) {
|
||||||
|
if (!user->isServiceUser()
|
||||||
|
&& !user->isBot()
|
||||||
|
&& !peer->isSelf()) {
|
||||||
|
processOnline(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rpl::merge(
|
rpl::merge(
|
||||||
_session->changes().historyUpdates(
|
_session->changes().historyUpdates(
|
||||||
_session->data().history(pin.peer),
|
_session->data().history(peer),
|
||||||
Data::HistoryUpdate::Flag::UnreadView
|
Data::HistoryUpdate::Flag::UnreadView
|
||||||
) | to_empty,
|
) | RplToEmpty(),
|
||||||
_session->changes().peerFlagsValue(
|
_session->changes().peerFlagsValue(
|
||||||
pin.peer,
|
peer,
|
||||||
UpdateFlag::Notifications
|
UpdateFlag::Notifications
|
||||||
) | to_empty
|
) | RplToEmpty()
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
updateBadge(_pins[index]);
|
updateBadge(_pins[index]);
|
||||||
}, *peerChangedLifetime);
|
}, *peerChangedLifetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateUserpics();
|
updateUserpics();
|
||||||
// ranges::for_each(peers, updateBadge);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
rpl::single(
|
rpl::single(
|
||||||
@ -969,12 +1094,18 @@ void AppendEmojiPacks(
|
|||||||
updateUserpics();
|
updateUserpics();
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
|
|
||||||
|
const auto updateOnlineColor = [=] {
|
||||||
|
st::dialogsOnlineBadgeFg->c.getRgbF(&_r, &_g, &_b, &_a);
|
||||||
|
};
|
||||||
|
updateOnlineColor();
|
||||||
|
|
||||||
base::ObservableViewer(
|
base::ObservableViewer(
|
||||||
*Window::Theme::Background()
|
*Window::Theme::Background()
|
||||||
) | rpl::filter([](const Window::Theme::BackgroundUpdate &update) {
|
) | rpl::filter([](const Window::Theme::BackgroundUpdate &update) {
|
||||||
return update.paletteChanged();
|
return update.paletteChanged();
|
||||||
}) | rpl::start_with_next([=] {
|
}) | rpl::start_with_next([=] {
|
||||||
crl::on_main(&_guard, [=] {
|
crl::on_main(&_guard, [=] {
|
||||||
|
updateOnlineColor();
|
||||||
if (const auto f = _session->data().folderLoaded(ArchiveId)) {
|
if (const auto f = _session->data().folderLoaded(ArchiveId)) {
|
||||||
_archive = ArchiveUserpic(f);
|
_archive = ArchiveUserpic(f);
|
||||||
}
|
}
|
||||||
@ -1070,6 +1201,35 @@ void AppendEmojiPacks(
|
|||||||
CGImageRef image = ([self imageToDraw:i]).toCGImage();
|
CGImageRef image = ([self imageToDraw:i]).toCGImage();
|
||||||
CGContextDrawImage(context, rect, image);
|
CGContextDrawImage(context, rect, image);
|
||||||
CGImageRelease(image);
|
CGImageRelease(image);
|
||||||
|
|
||||||
|
if (i >= 0) {
|
||||||
|
const auto &pin = _pins[i];
|
||||||
|
if (pin.hasUnread) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto online = Data::OnlineTextActive(
|
||||||
|
pin.onlineTill,
|
||||||
|
base::unixtime::now());
|
||||||
|
const auto value = pin.onlineAnimation.value(online ? 1. : 0.);
|
||||||
|
if (value < 0.05) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto lineWidth = kOnlineCircleStrokeWidth;
|
||||||
|
const auto circleSize = kOnlineCircleSize;
|
||||||
|
const auto progress = value * circleSize;
|
||||||
|
const auto diff = (circleSize - progress) / 2;
|
||||||
|
const auto borderRect = CGRectMake(
|
||||||
|
NSMaxX(rect) - circleSize + diff - lineWidth / 2,
|
||||||
|
diff,
|
||||||
|
progress,
|
||||||
|
progress);
|
||||||
|
|
||||||
|
CGContextSetRGBStrokeColor(context, 0, 0, 0, 1.0);
|
||||||
|
CGContextSetRGBFillColor(context, _r, _g, _b, _a);
|
||||||
|
CGContextSetLineWidth(context, lineWidth);
|
||||||
|
CGContextFillEllipseInRect(context, borderRect);
|
||||||
|
CGContextStrokeEllipseInRect(context, borderRect);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)drawRect:(NSRect)dirtyRect {
|
- (void)drawRect:(NSRect)dirtyRect {
|
||||||
|
Loading…
Reference in New Issue
Block a user