Update lottie icons in voice chats.

This commit is contained in:
John Preston 2021-04-08 20:25:50 +04:00
parent dc2192d5ca
commit 4d91ab7079
13 changed files with 123 additions and 164 deletions

3
.gitattributes vendored
View File

@ -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

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -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>

View 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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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;