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 # Ensure diffs have LF endings
*.diff text eol=lf *.diff text eol=lf
*.bat text eol=crlf *.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="day-blue.tdesktop-theme">../../day-blue.tdesktop-theme</file>
<file alias="night.tdesktop-theme">../../night.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="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/hands.lottie">../../icons/calls/hands.lottie</file>
<file alias="icons/calls/hand_muted_active.json">../../icons/calls/hand_muted_active.json</file> <file alias="icons/calls/voice.lottie">../../icons/calls/voice.lottie</file>
<file alias="icons/calls/raised_hand.json">../../icons/calls/raised_hand.json</file>
</qresource> </qresource>
<qresource prefix="/qt-project.org"> <qresource prefix="/qt-project.org">
<file>../qmime/freedesktop.org.xml</file> <file>../qmime/freedesktop.org.xml</file>

View File

@ -393,8 +393,8 @@ callErrorToast: Toast(defaultToast) {
groupCallWidth: 380px; groupCallWidth: 380px;
groupCallHeight: 580px; groupCallHeight: 580px;
groupCallMuteButtonIconSize: size(55px, 55px); groupCallMuteButtonIconSize: size(69px, 69px);
groupCallMuteButtonIconTop: 42px; groupCallMuteButtonIconTop: 35px;
groupCallRipple: RippleAnimation(defaultRippleAnimation) { groupCallRipple: RippleAnimation(defaultRippleAnimation) {
color: groupCallMembersBgRipple; color: groupCallMembersBgRipple;
} }

View File

@ -357,6 +357,13 @@ bool GroupCall::showChooseJoinAs() const {
&& !_possibleJoinAs.front()->isSelf()); && !_possibleJoinAs.front()->isSelf());
} }
bool GroupCall::scheduleStartSubscribed() const {
if (const auto real = lookupReal()) {
return real->scheduleStartSubscribed();
}
return false;
}
Data::GroupCall *GroupCall::lookupReal() const { Data::GroupCall *GroupCall::lookupReal() const {
const auto real = _peer->groupCall(); const auto real = _peer->groupCall();
return (real && real->id() == _id) ? real : nullptr; return (real && real->id() == _id) ? real : nullptr;

View File

@ -113,6 +113,7 @@ public:
[[nodiscard]] TimeId scheduleDate() const { [[nodiscard]] TimeId scheduleDate() const {
return _scheduleDate; return _scheduleDate;
} }
[[nodiscard]] bool scheduleStartSubscribed() const;
[[nodiscard]] Data::GroupCall *lookupReal() const; [[nodiscard]] Data::GroupCall *lookupReal() const;
[[nodiscard]] rpl::producer<not_null<Data::GroupCall*>> real() 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() .text = (_call->scheduleDate()
? tr::lng_group_call_start_now(tr::now) ? tr::lng_group_call_start_now(tr::now)
: tr::lng_group_call_connecting(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::ScheduledCanStart
: Ui::CallMuteButtonType::Connecting), : _call->scheduleStartSubscribed()
? Ui::CallMuteButtonType::ScheduledNotify
: Ui::CallMuteButtonType::ScheduledSilent),
})) }))
, _hangup(widget(), st::groupCallHangup) { , _hangup(widget(), st::groupCallHangup) {
_layerBg->setStyleOverrides(&st::groupCallBox, &st::groupCallLayerBox); _layerBg->setStyleOverrides(&st::groupCallBox, &st::groupCallLayerBox);

View File

@ -70,6 +70,14 @@ constexpr auto kOverlapProgressRadialHide = 1.2;
constexpr auto kRadialFinishArcShift = 1200; constexpr auto kRadialFinishArcShift = 1200;
[[nodiscard]] CallMuteButtonType TypeForIcon(CallMuteButtonType type) {
return (type == CallMuteButtonType::Connecting)
? CallMuteButtonType::Muted
: (type == CallMuteButtonType::RaisedHand)
? CallMuteButtonType::ForceMuted
: type;
};
auto MuteBlobs() { auto MuteBlobs() {
return std::vector<Paint::Blobs::BlobData>{ return std::vector<Paint::Blobs::BlobData>{
{ {
@ -546,160 +554,101 @@ CallMuteButton::CallMuteButton(
} }
CallMuteButton::IconState CallMuteButton::initialState() { CallMuteButton::IconState CallMuteButton::initialState() {
const auto result = iconStateFrom(_state.current().type);
_icons[0].emplace(Lottie::IconDescriptor{ _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, .color = st::groupCallIconFg,
.sizeOverride = st::groupCallMuteButtonIconSize, .sizeOverride = st::groupCallMuteButtonIconSize,
.frame = 22, .frame = result.frameTo,
}); });
_icons[1].emplace(Lottie::IconDescriptor{ _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, .color = st::groupCallIconFg,
.sizeOverride = st::groupCallMuteButtonIconSize, .sizeOverride = st::groupCallMuteButtonIconSize,
.frame = 0, .frame = 0,
}); });
_icons[2].emplace(Lottie::IconDescriptor{ return result;
.path = u":/gui/icons/calls/raised_hand.json"_q, }
.color = st::groupCallIconFg,
.sizeOverride = st::groupCallMuteButtonIconSize, auto CallMuteButton::iconStateAnimated(CallMuteButtonType previous)
.frame = 0, -> IconState {
}); using Type = CallMuteButtonType;
return iconStateFrom(_state.current().type); 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( CallMuteButton::IconState CallMuteButton::iconStateFrom(
CallMuteButtonType previous) { CallMuteButtonType previous) {
const auto current = _state.current().type; if (const auto animated = iconStateAnimated(previous)) {
switch (previous) { return animated;
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;
} }
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() { CallMuteButton::IconState CallMuteButton::randomWavingState() {
switch (openssl::RandomValue<uint32>() % 5) { struct Animation {
case 0: return { int from = 0;
.icon = &*_icons[2], int to = 0;
.frameFrom = 0, };
.frameTo = 120 }; static const auto kAnimations = std::vector<Animation>{
case 1: return { { 0, 120 },
.icon = &*_icons[2], { 120, 240 },
.frameFrom = 120, { 240, 420 },
.frameTo = 240 }; { 420, 540 },
case 2: return { };
.icon = &*_icons[2], const auto index = openssl::RandomValue<uint32>() % kAnimations.size();
.frameFrom = 240, return { 1, kAnimations[index].from, kAnimations[index].to };
.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.");
} }
void CallMuteButton::init() { void CallMuteButton::init() {
@ -884,7 +833,7 @@ void CallMuteButton::init() {
) | rpl::start_with_next([=](QRect clip) { ) | rpl::start_with_next([=](QRect clip) {
Painter p(_content); 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()) { if (_radialInfo.state.has_value() && _switchAnimation.animating()) {
const auto radialProgress = _radialInfo.realShowProgress; const auto radialProgress = _radialInfo.realShowProgress;
@ -929,7 +878,7 @@ void CallMuteButton::init() {
void CallMuteButton::scheduleIconState(const IconState &state) { void CallMuteButton::scheduleIconState(const IconState &state) {
if (_iconState != state) { if (_iconState != state) {
if (_iconState.icon->animating()) { if (_icons[_iconState.index]->animating()) {
_scheduledState = state; _scheduledState = state;
} else { } else {
startIconState(state); startIconState(state);
@ -942,22 +891,15 @@ void CallMuteButton::scheduleIconState(const IconState &state) {
void CallMuteButton::startIconState(const IconState &state) { void CallMuteButton::startIconState(const IconState &state) {
_iconState = state; _iconState = state;
_scheduledState = std::nullopt; _scheduledState = std::nullopt;
_iconState.icon->animate( _icons[_iconState.index]->animate(
[=] { iconAnimationCallback(); }, [=] { iconAnimationCallback(); },
_iconState.frameFrom, _iconState.frameFrom,
_iconState.frameTo); _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() { void CallMuteButton::iconAnimationCallback() {
_content->update(_muteIconRect); _content->update(_muteIconRect);
if (!_iconState.icon->animating() && _scheduledState) { if (!_icons[_iconState.index]->animating() && _scheduledState) {
startIconState(*_scheduledState); startIconState(*_scheduledState);
} }
} }

View File

@ -87,20 +87,25 @@ private:
const style::InfiniteRadialAnimation &st = st::callConnectingRadial; const style::InfiniteRadialAnimation &st = st::callConnectingRadial;
}; };
struct IconState { struct IconState {
not_null<Lottie::Icon*> icon; int index = -1;
int frameFrom = 0; int frameFrom = 0;
int frameTo = 0; int frameTo = 0;
std::optional<int> otherJumpToFrame;
inline bool operator==(const IconState &other) const { inline bool operator==(const IconState &other) const {
return (icon == other.icon) return (index == other.index)
&& (frameFrom == other.frameFrom) && (frameFrom == other.frameFrom)
&& (frameTo == other.frameTo) && (frameTo == other.frameTo);
&& (otherJumpToFrame == other.otherJumpToFrame);
} }
inline bool operator!=(const IconState &other) const { inline bool operator!=(const IconState &other) const {
return !(*this == other); return !(*this == other);
} }
bool valid() const {
return (index >= 0);
}
explicit operator bool() const {
return valid();
}
}; };
void init(); void init();
@ -118,6 +123,7 @@ private:
[[nodiscard]] IconState initialState(); [[nodiscard]] IconState initialState();
[[nodiscard]] IconState iconStateFrom(CallMuteButtonType previous); [[nodiscard]] IconState iconStateFrom(CallMuteButtonType previous);
[[nodiscard]] IconState randomWavingState(); [[nodiscard]] IconState randomWavingState();
[[nodiscard]] IconState iconStateAnimated(CallMuteButtonType previous);
void scheduleIconState(const IconState &state); void scheduleIconState(const IconState &state);
void startIconState(const IconState &state); void startIconState(const IconState &state);
void iconAnimationCallback(); void iconAnimationCallback();
@ -143,7 +149,7 @@ private:
std::unique_ptr<InfiniteRadialAnimation> _radial; std::unique_ptr<InfiniteRadialAnimation> _radial;
const base::flat_map<CallMuteButtonType, anim::gradient_colors> _colors; 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; IconState _iconState;
std::optional<IconState> _scheduledState; std::optional<IconState> _scheduledState;