Improve emoji animation.

This commit is contained in:
John Preston 2025-04-15 20:05:20 +04:00
parent 29d87f692a
commit 70378bfac8
6 changed files with 158 additions and 32 deletions

View file

@ -4969,6 +4969,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_confcall_sure_remove" = "Remove {user} from the call?";
"lng_confcall_e2e_badge" = "End-to-End Encrypted";
"lng_confcall_e2e_badge_small" = "E2E Encrypted";
"lng_confcall_e2e_about" = "These four emoji represent the call's encryption key. They must match for all participants and change when someone joins or leaves.";
"lng_no_mic_permission" = "Telegram needs microphone access so that you can make calls and record voice messages.";

View file

@ -1680,3 +1680,7 @@ confcallFingerprintText: FlatLabel(defaultFlatLabel) {
}
}
confcallFingerprintSkip: 2px;
confcallFingerprintTooltipLabel: defaultImportantTooltipLabel;
confcallFingerprintTooltip: defaultImportantTooltip;
confcallFingerprintTooltipSkip: 12px;
confcallFingerprintTooltipMaxWidth: 220px;

View file

@ -12,20 +12,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/calls_signal_bars.h"
#include "lang/lang_keys.h"
#include "data/data_user.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/tooltip.h"
#include "ui/abstract_button.h"
#include "ui/emoji_config.h"
#include "ui/painter.h"
#include "ui/rp_widget.h"
#include "ui/ui_utility.h"
#include "styles/style_calls.h"
namespace Calls {
namespace {
constexpr auto kTooltipShowTimeoutMs = crl::time(1000);
constexpr auto kCarouselOneDuration = crl::time(1000);// crl::time(100);
constexpr auto kStartTimeShift = crl::time(500);// crl::time(50);
constexpr auto kCarouselOneDuration = crl::time(100);
constexpr auto kStartTimeShift = crl::time(50);
constexpr auto kEmojiInFingerprint = 4;
constexpr auto kEmojiInCarousel = 10;
@ -317,6 +319,7 @@ FingerprintBadge SetupFingerprintBadge(
};
const auto state = on.make_state<State>();
state->data.speed = 1. / kCarouselOneDuration;
state->update = [=](crl::time now) {
// speed-up-duration = 2 * one / speed.
const auto one = 1.;
@ -546,13 +549,112 @@ FingerprintBadge SetupFingerprintBadge(
return { .state = &state->data, .repaints = state->repaints.events() };
}
void SetupFingerprintTooltip(not_null<Ui::RpWidget*> widget) {
struct State {
std::unique_ptr<Ui::ImportantTooltip> tooltip;
Fn<void()> updateGeometry;
Fn<void(bool)> toggleTooltip;
};
const auto state = widget->lifetime().make_state<State>();
state->updateGeometry = [=] {
if (!state->tooltip.get()) {
return;
}
const auto geometry = Ui::MapFrom(
widget->window(),
widget,
widget->rect());
if (geometry.isEmpty()) {
state->toggleTooltip(false);
return;
}
const auto weak = QPointer<QWidget>(state->tooltip.get());
const auto countPosition = [=](QSize size) {
const auto result = geometry.bottomLeft()
+ QPoint(
geometry.width() / 2,
st::confcallFingerprintTooltipSkip)
- QPoint(size.width() / 2, 0);
return result;
};
state->tooltip.get()->pointAt(
geometry,
RectPart::Bottom,
countPosition);
};
state->toggleTooltip = [=](bool show) {
if (const auto was = state->tooltip.release()) {
was->toggleAnimated(false);
}
if (!show) {
return;
}
const auto text = tr::lng_confcall_e2e_about(
tr::now,
Ui::Text::WithEntities);
if (text.empty()) {
return;
}
state->tooltip = std::make_unique<Ui::ImportantTooltip>(
widget->window(),
Ui::MakeNiceTooltipLabel(
widget,
rpl::single(text),
st::confcallFingerprintTooltipMaxWidth,
st::confcallFingerprintTooltipLabel),
st::confcallFingerprintTooltip);
const auto raw = state->tooltip.get();
const auto weak = QPointer<QWidget>(raw);
const auto destroy = [=] {
delete weak.data();
};
raw->setAttribute(Qt::WA_TransparentForMouseEvents);
raw->setHiddenCallback(destroy);
state->updateGeometry();
raw->toggleAnimated(true);
};
widget->events() | rpl::start_with_next([=](not_null<QEvent*> e) {
const auto type = e->type();
if (type == QEvent::Enter) {
state->toggleTooltip(true);
} else if (type == QEvent::Leave) {
state->toggleTooltip(false);
}
}, widget->lifetime());
}
QImage MakeVerticalShadow(int height) {
const auto ratio = style::DevicePixelRatio();
auto result = QImage(
QSize(1, height) * ratio,
QImage::Format_ARGB32_Premultiplied);
result.setDevicePixelRatio(ratio);
auto p = QPainter(&result);
auto g = QLinearGradient(0, 0, 0, height);
auto color = st::groupCallMembersBg->c;
auto trans = color;
trans.setAlpha(0);
g.setStops({
{ 0.0, color },
{ 0.4, trans },
{ 0.6, trans },
{ 1.0, color },
});
p.setCompositionMode(QPainter::CompositionMode_Source);
p.fillRect(0, 0, 1, height, g);
p.end();
return result;
}
void SetupFingerprintBadgeWidget(
not_null<Ui::RpWidget*> widget,
not_null<const FingerprintBadgeState*> state,
rpl::producer<> repaints) {
auto &lifetime = widget->lifetime();
const auto button = Ui::CreateChild<Ui::AbstractButton>(widget);
const auto button = Ui::CreateChild<Ui::RpWidget>(widget);
button->show();
const auto label = Ui::CreateChild<Ui::FlatLabel>(
@ -633,12 +735,19 @@ void SetupFingerprintBadgeWidget(
st::confcallFingerprintTextMargins);
const auto count = int(state->entries.size());
cache->entries.resize(count);
cache->shadow = MakeVerticalShadow(outer.height());
for (auto i = 0; i != count; ++i) {
PaintFingerprintEntry(
p,
state->entries[i],
cache->entries[i],
esize);
const auto &entry = state->entries[i];
auto &cached = cache->entries[i];
const auto shadowed = entry.speed / state->speed;
PaintFingerprintEntry(p, entry, cached, esize);
if (shadowed > 0.) {
p.setOpacity(shadowed);
p.drawImage(
QRect(0, -st::confcallFingerprintMargins.top(), size, outer.height()),
cache->shadow);
p.setOpacity(1.);
}
if (i + 1 == count / 2) {
p.translate(size + textOuter.width(), 0);
} else {
@ -650,6 +759,8 @@ void SetupFingerprintBadgeWidget(
std::move(repaints) | rpl::start_with_next([=] {
button->update();
}, lifetime);
SetupFingerprintTooltip(button);
}
void PaintFingerprintEntry(

View file

@ -37,6 +37,7 @@ struct FingerprintBadgeState {
int added = 0;
};
std::vector<Entry> entries;
float64 speed = 1.;
};
struct FingerprintBadge {
not_null<const FingerprintBadgeState*> state;
@ -60,6 +61,7 @@ struct FingerprintBadgeCache {
std::vector<Emoji> emoji;
};
std::vector<Entry> entries;
QImage shadow;
};
void PaintFingerprintEntry(
QPainter &p,

View file

@ -115,6 +115,7 @@ private:
struct PrioritizedSelector {
object_ptr<Ui::RpWidget> content = { nullptr };
Fn<void()> init;
Fn<bool(int, int, int)> overrideKey;
Fn<void(PeerListRowId)> deselect;
Fn<void()> activate;
@ -320,7 +321,7 @@ void ConfInviteRow::elementsPaint(
[[nodiscard]] PrioritizedSelector PrioritizedInviteSelector(
const ConfInviteStyles &st,
std::vector<not_null<UserData*>> users,
Fn<bool(not_null<PeerListRow*>, bool)> toggleGetChecked,
Fn<bool(not_null<PeerListRow*>, bool, anim::type)> toggleGetChecked,
Fn<bool()> lastSelectWithVideo,
Fn<void(bool)> setLastSelectWithVideo) {
class PrioritizedController final : public PeerListController {
@ -328,7 +329,10 @@ void ConfInviteRow::elementsPaint(
PrioritizedController(
const ConfInviteStyles &st,
std::vector<not_null<UserData*>> users,
Fn<bool(not_null<PeerListRow*>, bool)> toggleGetChecked,
Fn<bool(
not_null<PeerListRow*>,
bool,
anim::type)> toggleGetChecked,
Fn<bool()> lastSelectWithVideo,
Fn<void(bool)> setLastSelectWithVideo)
: _st(st)
@ -368,7 +372,7 @@ void ConfInviteRow::elementsPaint(
void toggleRowSelected(not_null<PeerListRow*> row, bool video) {
delegate()->peerListSetRowChecked(
row,
_toggleGetChecked(row, video));
_toggleGetChecked(row, video, anim::type::normal));
}
Main::Session &session() const override {
@ -382,7 +386,7 @@ void ConfInviteRow::elementsPaint(
private:
const ConfInviteStyles &_st;
std::vector<not_null<UserData*>> _users;
Fn<bool(not_null<PeerListRow*>, bool)> _toggleGetChecked;
Fn<bool(not_null<PeerListRow*>, bool, anim::type)> _toggleGetChecked;
Fn<bool()> _lastSelectWithVideo;
Fn<void(bool)> _setLastSelectWithVideo;
@ -448,8 +452,19 @@ void ConfInviteRow::elementsPaint(
}
};
const auto init = [=] {
for (const auto &user : users) {
if (const auto row = delegate->peerListFindRow(user->id.value)) {
delegate->peerListSetRowChecked(
row,
toggleGetChecked(row, false, anim::type::instant));
}
}
};
return {
.content = std::move(result),
.init = init,
.overrideKey = overrideKey,
.deselect = deselect,
.activate = activate,
@ -608,12 +623,15 @@ void ConfInviteController::prepareViewHook() {
}
void ConfInviteController::addPriorityInvites() {
const auto toggleGetChecked = [=](not_null<PeerListRow*> row, bool video) {
const auto toggleGetChecked = [=](
not_null<PeerListRow*> row,
bool video,
anim::type animated) {
const auto result = toggleRowGetChecked(row, video);
delegate()->peerListSetForeignRowChecked(
row,
result,
anim::type::normal);
animated);
_hasSelected = (delegate()->peerListSelectedRowsCount() > 0);
@ -630,6 +648,13 @@ void ConfInviteController::addPriorityInvites() {
scrollTo
) | rpl::start_to_stream(_prioritizeScrollRequests, lifetime());
}
if (const auto onstack = _prioritizeRows.init) {
onstack();
// Force finishing in instant adding checked rows bunch.
delegate()->peerListAddSelectedPeers(
std::vector<not_null<PeerData*>>());
}
delegate()->peerListSetAboveWidget(std::move(_prioritizeRows.content));
}

View file

@ -77,23 +77,6 @@ constexpr auto kControlsBackgroundOpacity = 0.8;
constexpr auto kOverrideActiveColorBgAlpha = 172;
constexpr auto kHideControlsTimeout = 5 * crl::time(1000);
[[nodiscard]] QString ComposeTitle(const QByteArray &hash) {
auto result = tr::lng_confcall_join_title(tr::now);
if (hash.size() >= 32) {
const auto fp = bytes::make_span(hash).subspan(0, 32);
const auto emoji = Calls::ComputeEmojiFingerprint(fp);
result += QString::fromUtf8(" \xc2\xb7 ");
const auto base = result.size();
for (const auto &single : emoji) {
result += single->text();
}
MTP_LOG(0, ("Got Emoji: %1.").arg(result.mid(base)));
} else {
MTP_LOG(0, ("Cleared Emoji."));
}
return result;
}
#ifdef Q_OS_WIN
void UnpinMaximized(not_null<QWidget*> widget) {
SetWindowPos(
@ -2417,7 +2400,7 @@ void Panel::updateMembersGeometry() {
rpl::producer<QString> Panel::titleText() {
if (_call->conference()) {
return _call->emojiHashValue() | rpl::map(ComposeTitle);
return tr::lng_confcall_join_title();
}
return rpl::combine(
Info::Profile::NameValue(_peer),