Update lottie icons in voice chats.
This commit is contained in:
parent
dc2192d5ca
commit
4d91ab7079
|
@ -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.
|
@ -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 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 iconStateFrom(_state.current().type);
|
||||
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;
|
||||
if (const auto animated = iconStateAnimated(previous)) {
|
||||
return animated;
|
||||
}
|
||||
} break;
|
||||
case CallMuteButtonType::Connecting:
|
||||
case CallMuteButtonType::Muted: {
|
||||
switch (current) {
|
||||
case CallMuteButtonType::Active: {
|
||||
return { // Muted -> Active
|
||||
.icon = &*_icons[0],
|
||||
.frameFrom = 21,
|
||||
.frameTo = 41,
|
||||
.otherJumpToFrame = 0,
|
||||
|
||||
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 },
|
||||
};
|
||||
} 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.");
|
||||
|
||||
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