mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-04-01 23:00:58 +00:00
Update lottie icons in voice chats.
This commit is contained in:
parent
dc2192d5ca
commit
4d91ab7079
3
.gitattributes
vendored
3
.gitattributes
vendored
@ -4,3 +4,6 @@
|
||||
# Ensure diffs have LF endings
|
||||
*.diff text eol=lf
|
||||
*.bat text eol=crlf
|
||||
|
||||
# Ensure lottie animations are treated as binary files
|
||||
*.lottie binary
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
Telegram/Resources/icons/calls/hands.lottie
Normal file
1
Telegram/Resources/icons/calls/hands.lottie
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
Telegram/Resources/icons/calls/voice.lottie
Normal file
1
Telegram/Resources/icons/calls/voice.lottie
Normal file
File diff suppressed because one or more lines are too long
@ -59,9 +59,8 @@
|
||||
<file alias="day-blue.tdesktop-theme">../../day-blue.tdesktop-theme</file>
|
||||
<file alias="night.tdesktop-theme">../../night.tdesktop-theme</file>
|
||||
<file alias="night-green.tdesktop-theme">../../night-green.tdesktop-theme</file>
|
||||
<file alias="icons/calls/active_hand.json">../../icons/calls/active_hand.json</file>
|
||||
<file alias="icons/calls/hand_muted_active.json">../../icons/calls/hand_muted_active.json</file>
|
||||
<file alias="icons/calls/raised_hand.json">../../icons/calls/raised_hand.json</file>
|
||||
<file alias="icons/calls/hands.lottie">../../icons/calls/hands.lottie</file>
|
||||
<file alias="icons/calls/voice.lottie">../../icons/calls/voice.lottie</file>
|
||||
</qresource>
|
||||
<qresource prefix="/qt-project.org">
|
||||
<file>../qmime/freedesktop.org.xml</file>
|
||||
|
@ -393,8 +393,8 @@ callErrorToast: Toast(defaultToast) {
|
||||
groupCallWidth: 380px;
|
||||
groupCallHeight: 580px;
|
||||
|
||||
groupCallMuteButtonIconSize: size(55px, 55px);
|
||||
groupCallMuteButtonIconTop: 42px;
|
||||
groupCallMuteButtonIconSize: size(69px, 69px);
|
||||
groupCallMuteButtonIconTop: 35px;
|
||||
groupCallRipple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: groupCallMembersBgRipple;
|
||||
}
|
||||
|
@ -357,6 +357,13 @@ bool GroupCall::showChooseJoinAs() const {
|
||||
&& !_possibleJoinAs.front()->isSelf());
|
||||
}
|
||||
|
||||
bool GroupCall::scheduleStartSubscribed() const {
|
||||
if (const auto real = lookupReal()) {
|
||||
return real->scheduleStartSubscribed();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Data::GroupCall *GroupCall::lookupReal() const {
|
||||
const auto real = _peer->groupCall();
|
||||
return (real && real->id() == _id) ? real : nullptr;
|
||||
|
@ -113,6 +113,7 @@ public:
|
||||
[[nodiscard]] TimeId scheduleDate() const {
|
||||
return _scheduleDate;
|
||||
}
|
||||
[[nodiscard]] bool scheduleStartSubscribed() const;
|
||||
|
||||
[[nodiscard]] Data::GroupCall *lookupReal() const;
|
||||
[[nodiscard]] rpl::producer<not_null<Data::GroupCall*>> real() const;
|
||||
|
@ -388,9 +388,13 @@ Panel::Panel(not_null<GroupCall*> call)
|
||||
.text = (_call->scheduleDate()
|
||||
? tr::lng_group_call_start_now(tr::now)
|
||||
: tr::lng_group_call_connecting(tr::now)),
|
||||
.type = (_call->scheduleDate()
|
||||
.type = (!_call->scheduleDate()
|
||||
? Ui::CallMuteButtonType::Connecting
|
||||
: _peer->canManageGroupCall()
|
||||
? Ui::CallMuteButtonType::ScheduledCanStart
|
||||
: Ui::CallMuteButtonType::Connecting),
|
||||
: _call->scheduleStartSubscribed()
|
||||
? Ui::CallMuteButtonType::ScheduledNotify
|
||||
: Ui::CallMuteButtonType::ScheduledSilent),
|
||||
}))
|
||||
, _hangup(widget(), st::groupCallHangup) {
|
||||
_layerBg->setStyleOverrides(&st::groupCallBox, &st::groupCallLayerBox);
|
||||
|
@ -70,6 +70,14 @@ constexpr auto kOverlapProgressRadialHide = 1.2;
|
||||
|
||||
constexpr auto kRadialFinishArcShift = 1200;
|
||||
|
||||
[[nodiscard]] CallMuteButtonType TypeForIcon(CallMuteButtonType type) {
|
||||
return (type == CallMuteButtonType::Connecting)
|
||||
? CallMuteButtonType::Muted
|
||||
: (type == CallMuteButtonType::RaisedHand)
|
||||
? CallMuteButtonType::ForceMuted
|
||||
: type;
|
||||
};
|
||||
|
||||
auto MuteBlobs() {
|
||||
return std::vector<Paint::Blobs::BlobData>{
|
||||
{
|
||||
@ -546,160 +554,101 @@ CallMuteButton::CallMuteButton(
|
||||
}
|
||||
|
||||
CallMuteButton::IconState CallMuteButton::initialState() {
|
||||
const auto result = iconStateFrom(_state.current().type);
|
||||
_icons[0].emplace(Lottie::IconDescriptor{
|
||||
.path = u":/gui/icons/calls/hand_muted_active.json"_q,
|
||||
.path = u":/gui/icons/calls/voice.lottie"_q,
|
||||
.color = st::groupCallIconFg,
|
||||
.sizeOverride = st::groupCallMuteButtonIconSize,
|
||||
.frame = 22,
|
||||
.frame = result.frameTo,
|
||||
});
|
||||
_icons[1].emplace(Lottie::IconDescriptor{
|
||||
.path = u":/gui/icons/calls/active_hand.json"_q,
|
||||
.path = u":/gui/icons/calls/hands.lottie"_q,
|
||||
.color = st::groupCallIconFg,
|
||||
.sizeOverride = st::groupCallMuteButtonIconSize,
|
||||
.frame = 0,
|
||||
});
|
||||
_icons[2].emplace(Lottie::IconDescriptor{
|
||||
.path = u":/gui/icons/calls/raised_hand.json"_q,
|
||||
.color = st::groupCallIconFg,
|
||||
.sizeOverride = st::groupCallMuteButtonIconSize,
|
||||
.frame = 0,
|
||||
});
|
||||
return iconStateFrom(_state.current().type);
|
||||
return result;
|
||||
}
|
||||
|
||||
auto CallMuteButton::iconStateAnimated(CallMuteButtonType previous)
|
||||
-> IconState {
|
||||
using Type = CallMuteButtonType;
|
||||
using Key = std::pair<Type, Type>;
|
||||
struct Animation {
|
||||
int from = 0;
|
||||
int to = 0;
|
||||
};
|
||||
static const auto kAnimations = std::vector<std::pair<Key, Animation>>{
|
||||
{ { Type::ForceMuted, Type::Muted }, { 0, 35 } },
|
||||
{ { Type::Muted, Type::Active }, { 36, 68 } },
|
||||
{ { Type::Active, Type::Muted }, { 69, 98 } },
|
||||
{ { Type::Muted, Type::ForceMuted }, { 99, 135 } },
|
||||
{ { Type::Active, Type::ForceMuted }, { 136, 172 } },
|
||||
{ { Type::ScheduledSilent, Type::ScheduledNotify }, { 173, 201 } },
|
||||
{ { Type::ScheduledSilent, Type::Muted }, { 202, 236 } },
|
||||
{ { Type::ScheduledSilent, Type::ForceMuted }, { 237, 273 } },
|
||||
{ { Type::ScheduledNotify, Type::ForceMuted }, { 274, 310 } },
|
||||
{ { Type::ScheduledNotify, Type::ScheduledSilent }, { 311, 343 } },
|
||||
{ { Type::ScheduledNotify, Type::Muted }, { 344, 375 } },
|
||||
{ { Type::ScheduledCanStart, Type::Muted }, { 376, 403 } },
|
||||
};
|
||||
static const auto kMap = [] {
|
||||
// flat_multi_map_pair_type lacks some required constructors :(
|
||||
auto &&list = kAnimations | ranges::views::transform([](auto &&pair) {
|
||||
return base::flat_multi_map_pair_type<Key, Animation>(
|
||||
pair.first,
|
||||
pair.second);
|
||||
});
|
||||
return base::flat_map<Key, Animation>(begin(list), end(list));
|
||||
}();
|
||||
const auto was = TypeForIcon(previous);
|
||||
const auto now = TypeForIcon(_state.current().type);
|
||||
if (was == now) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (const auto i = kMap.find(Key{ was, now }); i != end(kMap)) {
|
||||
return { 0, i->second.from, i->second.to };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
CallMuteButton::IconState CallMuteButton::iconStateFrom(
|
||||
CallMuteButtonType previous) {
|
||||
const auto current = _state.current().type;
|
||||
switch (previous) {
|
||||
case CallMuteButtonType::Active: {
|
||||
switch (current) {
|
||||
case CallMuteButtonType::Active: {
|
||||
return { // Active
|
||||
.icon = &*_icons[0],
|
||||
.frameFrom = 41,
|
||||
.frameTo = 41,
|
||||
.otherJumpToFrame = 0,
|
||||
};
|
||||
} break;
|
||||
case CallMuteButtonType::Connecting:
|
||||
case CallMuteButtonType::Muted: {
|
||||
return { // Active -> Muted
|
||||
.icon = &*_icons[0],
|
||||
.frameFrom = 42,
|
||||
.frameTo = 62,
|
||||
};
|
||||
} break;
|
||||
case CallMuteButtonType::ScheduledCanStart: // #TODO voice chats
|
||||
case CallMuteButtonType::ScheduledNotify:
|
||||
case CallMuteButtonType::ScheduledSilent:
|
||||
case CallMuteButtonType::ForceMuted:
|
||||
case CallMuteButtonType::RaisedHand: {
|
||||
return { // Active -> Hand
|
||||
.icon = &*_icons[1],
|
||||
.frameFrom = 0,
|
||||
.frameTo = 22,
|
||||
.otherJumpToFrame = 0,
|
||||
};
|
||||
} break;
|
||||
}
|
||||
} break;
|
||||
case CallMuteButtonType::Connecting:
|
||||
case CallMuteButtonType::Muted: {
|
||||
switch (current) {
|
||||
case CallMuteButtonType::Active: {
|
||||
return { // Muted -> Active
|
||||
.icon = &*_icons[0],
|
||||
.frameFrom = 21,
|
||||
.frameTo = 41,
|
||||
.otherJumpToFrame = 0,
|
||||
};
|
||||
} break;
|
||||
case CallMuteButtonType::Connecting:
|
||||
case CallMuteButtonType::Muted: {
|
||||
return { // Muted
|
||||
.icon = &*_icons[0],
|
||||
.frameFrom = 22,
|
||||
.frameTo = 22,
|
||||
};
|
||||
} break;
|
||||
case CallMuteButtonType::ScheduledCanStart: // #TODO voice chats
|
||||
case CallMuteButtonType::ScheduledNotify:
|
||||
case CallMuteButtonType::ScheduledSilent:
|
||||
case CallMuteButtonType::ForceMuted:
|
||||
case CallMuteButtonType::RaisedHand: {
|
||||
return { // Muted -> Hand
|
||||
.icon = &*_icons[0],
|
||||
.frameFrom = 63,
|
||||
.frameTo = 83,
|
||||
.otherJumpToFrame = 22,
|
||||
};
|
||||
} break;
|
||||
}
|
||||
} break;
|
||||
case CallMuteButtonType::ScheduledCanStart: // #TODO voice chats
|
||||
case CallMuteButtonType::ScheduledNotify:
|
||||
case CallMuteButtonType::ScheduledSilent:
|
||||
case CallMuteButtonType::ForceMuted:
|
||||
case CallMuteButtonType::RaisedHand: {
|
||||
switch (current) {
|
||||
case CallMuteButtonType::Active: {
|
||||
return { // Hand -> Active
|
||||
.icon = &*_icons[1],
|
||||
.frameFrom = 22,
|
||||
.frameTo = 0,
|
||||
.otherJumpToFrame = 42,
|
||||
};
|
||||
} break;
|
||||
case CallMuteButtonType::Connecting:
|
||||
case CallMuteButtonType::Muted: {
|
||||
return { // Hand -> Muted
|
||||
.icon = &*_icons[0],
|
||||
.frameFrom = 0,
|
||||
.frameTo = 20,
|
||||
};
|
||||
} break;
|
||||
case CallMuteButtonType::ScheduledCanStart: // #TODO voice chats
|
||||
case CallMuteButtonType::ScheduledNotify:
|
||||
case CallMuteButtonType::ScheduledSilent:
|
||||
case CallMuteButtonType::ForceMuted:
|
||||
case CallMuteButtonType::RaisedHand: {
|
||||
return { // Hand
|
||||
.icon = &*_icons[0],
|
||||
.frameFrom = 0,
|
||||
.frameTo = 0,
|
||||
.otherJumpToFrame = 22,
|
||||
};
|
||||
} break;
|
||||
}
|
||||
} break;
|
||||
if (const auto animated = iconStateAnimated(previous)) {
|
||||
return animated;
|
||||
}
|
||||
Unexpected("State in CallMuteButton::iconStateFrom.");
|
||||
|
||||
using Type = CallMuteButtonType;
|
||||
static const auto kFinal = base::flat_map<Type, int>{
|
||||
{ Type::ForceMuted, 0 },
|
||||
{ Type::Muted, 36 },
|
||||
{ Type::Active, 69 },
|
||||
{ Type::ScheduledSilent, 173 },
|
||||
{ Type::ScheduledNotify, 274 },
|
||||
{ Type::ScheduledCanStart, 376 },
|
||||
};
|
||||
|
||||
const auto now = TypeForIcon(_state.current().type);
|
||||
const auto i = kFinal.find(now);
|
||||
|
||||
Ensures(i != end(kFinal));
|
||||
return { 0, i->second, i->second };
|
||||
}
|
||||
|
||||
CallMuteButton::IconState CallMuteButton::randomWavingState() {
|
||||
switch (openssl::RandomValue<uint32>() % 5) {
|
||||
case 0: return {
|
||||
.icon = &*_icons[2],
|
||||
.frameFrom = 0,
|
||||
.frameTo = 120 };
|
||||
case 1: return {
|
||||
.icon = &*_icons[2],
|
||||
.frameFrom = 120,
|
||||
.frameTo = 240 };
|
||||
case 2: return {
|
||||
.icon = &*_icons[2],
|
||||
.frameFrom = 240,
|
||||
.frameTo = 420 };
|
||||
case 3: return {
|
||||
.icon = &*_icons[2],
|
||||
.frameFrom = 420,
|
||||
.frameTo = 540 };
|
||||
case 4: return {
|
||||
.icon = &*_icons[2],
|
||||
.frameFrom = 540,
|
||||
.frameTo = 720 };
|
||||
}
|
||||
Unexpected("Value in CallMuteButton::randomWavingState.");
|
||||
struct Animation {
|
||||
int from = 0;
|
||||
int to = 0;
|
||||
};
|
||||
static const auto kAnimations = std::vector<Animation>{
|
||||
{ 0, 120 },
|
||||
{ 120, 240 },
|
||||
{ 240, 420 },
|
||||
{ 420, 540 },
|
||||
};
|
||||
const auto index = openssl::RandomValue<uint32>() % kAnimations.size();
|
||||
return { 1, kAnimations[index].from, kAnimations[index].to };
|
||||
}
|
||||
|
||||
void CallMuteButton::init() {
|
||||
@ -884,7 +833,7 @@ void CallMuteButton::init() {
|
||||
) | rpl::start_with_next([=](QRect clip) {
|
||||
Painter p(_content);
|
||||
|
||||
_iconState.icon->paint(p, _muteIconRect.x(), _muteIconRect.y());
|
||||
_icons[_iconState.index]->paint(p, _muteIconRect.x(), _muteIconRect.y());
|
||||
|
||||
if (_radialInfo.state.has_value() && _switchAnimation.animating()) {
|
||||
const auto radialProgress = _radialInfo.realShowProgress;
|
||||
@ -929,7 +878,7 @@ void CallMuteButton::init() {
|
||||
|
||||
void CallMuteButton::scheduleIconState(const IconState &state) {
|
||||
if (_iconState != state) {
|
||||
if (_iconState.icon->animating()) {
|
||||
if (_icons[_iconState.index]->animating()) {
|
||||
_scheduledState = state;
|
||||
} else {
|
||||
startIconState(state);
|
||||
@ -942,22 +891,15 @@ void CallMuteButton::scheduleIconState(const IconState &state) {
|
||||
void CallMuteButton::startIconState(const IconState &state) {
|
||||
_iconState = state;
|
||||
_scheduledState = std::nullopt;
|
||||
_iconState.icon->animate(
|
||||
_icons[_iconState.index]->animate(
|
||||
[=] { iconAnimationCallback(); },
|
||||
_iconState.frameFrom,
|
||||
_iconState.frameTo);
|
||||
if (const auto other = state.otherJumpToFrame) {
|
||||
if (_iconState.icon == &*_icons[0]) {
|
||||
_icons[1]->jumpTo(*other, nullptr);
|
||||
} else {
|
||||
_icons[0]->jumpTo(*other, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CallMuteButton::iconAnimationCallback() {
|
||||
_content->update(_muteIconRect);
|
||||
if (!_iconState.icon->animating() && _scheduledState) {
|
||||
if (!_icons[_iconState.index]->animating() && _scheduledState) {
|
||||
startIconState(*_scheduledState);
|
||||
}
|
||||
}
|
||||
|
@ -87,20 +87,25 @@ private:
|
||||
const style::InfiniteRadialAnimation &st = st::callConnectingRadial;
|
||||
};
|
||||
struct IconState {
|
||||
not_null<Lottie::Icon*> icon;
|
||||
int index = -1;
|
||||
int frameFrom = 0;
|
||||
int frameTo = 0;
|
||||
std::optional<int> otherJumpToFrame;
|
||||
|
||||
inline bool operator==(const IconState &other) const {
|
||||
return (icon == other.icon)
|
||||
return (index == other.index)
|
||||
&& (frameFrom == other.frameFrom)
|
||||
&& (frameTo == other.frameTo)
|
||||
&& (otherJumpToFrame == other.otherJumpToFrame);
|
||||
&& (frameTo == other.frameTo);
|
||||
}
|
||||
inline bool operator!=(const IconState &other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
bool valid() const {
|
||||
return (index >= 0);
|
||||
}
|
||||
explicit operator bool() const {
|
||||
return valid();
|
||||
}
|
||||
};
|
||||
|
||||
void init();
|
||||
@ -118,6 +123,7 @@ private:
|
||||
[[nodiscard]] IconState initialState();
|
||||
[[nodiscard]] IconState iconStateFrom(CallMuteButtonType previous);
|
||||
[[nodiscard]] IconState randomWavingState();
|
||||
[[nodiscard]] IconState iconStateAnimated(CallMuteButtonType previous);
|
||||
void scheduleIconState(const IconState &state);
|
||||
void startIconState(const IconState &state);
|
||||
void iconAnimationCallback();
|
||||
@ -143,7 +149,7 @@ private:
|
||||
std::unique_ptr<InfiniteRadialAnimation> _radial;
|
||||
const base::flat_map<CallMuteButtonType, anim::gradient_colors> _colors;
|
||||
|
||||
std::array<std::optional<Lottie::Icon>, 3> _icons;
|
||||
std::array<std::optional<Lottie::Icon>, 2> _icons;
|
||||
IconState _iconState;
|
||||
std::optional<IconState> _scheduledState;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user