mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-06 15:13:57 +02:00
Reuse the code for userpics in Calls::TopBar.
This commit is contained in:
parent
aede42b0b6
commit
bcd2560e8f
12 changed files with 612 additions and 509 deletions
|
@ -9,6 +9,7 @@ using "ui/basic.style";
|
||||||
|
|
||||||
using "ui/widgets/widgets.style";
|
using "ui/widgets/widgets.style";
|
||||||
using "ui/layers/layers.style";
|
using "ui/layers/layers.style";
|
||||||
|
using "ui/chat/chat.style"; // GroupCallUserpics
|
||||||
using "window/window.style";
|
using "window/window.style";
|
||||||
|
|
||||||
CallSignalBars {
|
CallSignalBars {
|
||||||
|
@ -605,9 +606,12 @@ groupCallButtonSkip: 43px;
|
||||||
groupCallButtonBottomSkip: 145px;
|
groupCallButtonBottomSkip: 145px;
|
||||||
groupCallMuteBottomSkip: 160px;
|
groupCallMuteBottomSkip: 160px;
|
||||||
|
|
||||||
groupCallTopBarUserpicSize: 28px;
|
groupCallTopBarUserpics: GroupCallUserpics {
|
||||||
groupCallTopBarUserpicShift: 8px;
|
size: 28px;
|
||||||
groupCallTopBarUserpicStroke: 2px;
|
shift: 8px;
|
||||||
|
stroke: 2px;
|
||||||
|
align: align(left);
|
||||||
|
}
|
||||||
groupCallTopBarJoin: RoundButton(defaultActiveButton) {
|
groupCallTopBarJoin: RoundButton(defaultActiveButton) {
|
||||||
width: -26px;
|
width: -26px;
|
||||||
height: 26px;
|
height: 26px;
|
||||||
|
|
|
@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/paint/blobs_linear.h"
|
#include "ui/paint/blobs_linear.h"
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
#include "ui/widgets/labels.h"
|
#include "ui/widgets/labels.h"
|
||||||
|
#include "ui/chat/group_call_userpics.h" // Ui::GroupCallUser.
|
||||||
#include "ui/chat/group_call_bar.h" // Ui::GroupCallBarContent.
|
#include "ui/chat/group_call_bar.h" // Ui::GroupCallBarContent.
|
||||||
#include "ui/layers/generic_box.h"
|
#include "ui/layers/generic_box.h"
|
||||||
#include "ui/wrap/padding_wrap.h"
|
#include "ui/wrap/padding_wrap.h"
|
||||||
|
@ -32,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "base/timer.h"
|
#include "base/timer.h"
|
||||||
#include "app.h"
|
#include "app.h"
|
||||||
#include "styles/style_calls.h"
|
#include "styles/style_calls.h"
|
||||||
|
#include "styles/style_chat.h" // style::GroupCallUserpics
|
||||||
#include "styles/style_layers.h"
|
#include "styles/style_layers.h"
|
||||||
|
|
||||||
namespace Calls {
|
namespace Calls {
|
||||||
|
@ -165,7 +167,7 @@ void DebugInfoBox::updateText() {
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
struct TopBar::User {
|
struct TopBar::User {
|
||||||
Ui::GroupCallBarContent::User data;
|
Ui::GroupCallUser data;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Mute final : public Ui::IconButton {
|
class Mute final : public Ui::IconButton {
|
||||||
|
@ -238,6 +240,12 @@ TopBar::TopBar(
|
||||||
: RpWidget(parent)
|
: RpWidget(parent)
|
||||||
, _call(call)
|
, _call(call)
|
||||||
, _groupCall(groupCall)
|
, _groupCall(groupCall)
|
||||||
|
, _userpics(call
|
||||||
|
? nullptr
|
||||||
|
: std::make_unique<Ui::GroupCallUserpics>(
|
||||||
|
st::groupCallTopBarUserpics,
|
||||||
|
rpl::single(true),
|
||||||
|
[=] { updateUserpics(); }))
|
||||||
, _durationLabel(_call
|
, _durationLabel(_call
|
||||||
? object_ptr<Ui::LabelSimple>(this, st::callBarLabel)
|
? object_ptr<Ui::LabelSimple>(this, st::callBarLabel)
|
||||||
: object_ptr<Ui::LabelSimple>(nullptr))
|
: object_ptr<Ui::LabelSimple>(nullptr))
|
||||||
|
@ -551,35 +559,31 @@ void TopBar::subscribeToMembersChanges(not_null<GroupCall*> call) {
|
||||||
) | rpl::map([=](not_null<Data::GroupCall*> real) {
|
) | rpl::map([=](not_null<Data::GroupCall*> real) {
|
||||||
return HistoryView::GroupCallTracker::ContentByCall(
|
return HistoryView::GroupCallTracker::ContentByCall(
|
||||||
real,
|
real,
|
||||||
HistoryView::UserpicsInRowStyle{
|
st::groupCallTopBarUserpics.size);
|
||||||
.size = st::groupCallTopBarUserpicSize,
|
|
||||||
.shift = st::groupCallTopBarUserpicShift,
|
|
||||||
.stroke = st::groupCallTopBarUserpicStroke,
|
|
||||||
});
|
|
||||||
}) | rpl::flatten_latest(
|
}) | rpl::flatten_latest(
|
||||||
) | rpl::filter([=](const Ui::GroupCallBarContent &content) {
|
) | rpl::filter([=](const Ui::GroupCallBarContent &content) {
|
||||||
if (_users.size() != content.users.size()) {
|
if (_users.size() != content.users.size()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
for (auto i = 0, count = int(_users.size()); i != count; ++i) {
|
for (auto i = 0, count = int(_users.size()); i != count; ++i) {
|
||||||
if (_users[i].data.userpicKey != content.users[i].userpicKey
|
if (_users[i].userpicKey != content.users[i].userpicKey
|
||||||
|| _users[i].data.id != content.users[i].id) {
|
|| _users[i].id != content.users[i].id) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}) | rpl::start_with_next([=](const Ui::GroupCallBarContent &content) {
|
}) | rpl::start_with_next([=](const Ui::GroupCallBarContent &content) {
|
||||||
const auto sizeChanged = (_users.size() != content.users.size());
|
_users = content.users;
|
||||||
_users = ranges::view::all(
|
for (auto &user : _users) {
|
||||||
content.users
|
user.speaking = false;
|
||||||
) | ranges::view::transform([](const auto &user) {
|
|
||||||
return User{ user };
|
|
||||||
}) | ranges::to_vector;
|
|
||||||
generateUserpicsInRow();
|
|
||||||
if (sizeChanged) {
|
|
||||||
updateControlsGeometry();
|
|
||||||
}
|
}
|
||||||
update();
|
_userpics->update(_users, !isHidden());
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
_userpics->widthValue(
|
||||||
|
) | rpl::start_with_next([=](int width) {
|
||||||
|
_userpicsWidth = width;
|
||||||
|
updateControlsGeometry();
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
call->peer()->session().changes().peerUpdates(
|
call->peer()->session().changes().peerUpdates(
|
||||||
|
@ -591,41 +595,10 @@ void TopBar::subscribeToMembersChanges(not_null<GroupCall*> call) {
|
||||||
}) | rpl::start_with_next([=] {
|
}) | rpl::start_with_next([=] {
|
||||||
updateInfoLabels();
|
updateInfoLabels();
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TopBar::generateUserpicsInRow() {
|
void TopBar::updateUserpics() {
|
||||||
const auto count = int(_users.size());
|
update(_mute->width(), 0, _userpics->maxWidth(), height());
|
||||||
if (!count) {
|
|
||||||
_userpics = QImage();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto limit = std::min(count, kMaxUsersInBar);
|
|
||||||
const auto single = st::groupCallTopBarUserpicSize;
|
|
||||||
const auto shift = st::groupCallTopBarUserpicShift;
|
|
||||||
const auto width = single + (limit - 1) * (single - shift);
|
|
||||||
if (_userpics.width() != width * cIntRetinaFactor()) {
|
|
||||||
_userpics = QImage(
|
|
||||||
QSize(width, single) * cIntRetinaFactor(),
|
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
|
||||||
}
|
|
||||||
_userpics.fill(Qt::transparent);
|
|
||||||
_userpics.setDevicePixelRatio(cRetinaFactor());
|
|
||||||
|
|
||||||
auto q = Painter(&_userpics);
|
|
||||||
auto hq = PainterHighQualityEnabler(q);
|
|
||||||
auto pen = QPen(Qt::transparent);
|
|
||||||
pen.setWidth(st::groupCallTopBarUserpicStroke);
|
|
||||||
auto x = (count - 1) * (single - shift);
|
|
||||||
for (auto i = count; i != 0;) {
|
|
||||||
q.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
|
||||||
q.drawImage(x, 0, _users[--i].data.userpic);
|
|
||||||
q.setCompositionMode(QPainter::CompositionMode_Source);
|
|
||||||
q.setBrush(Qt::NoBrush);
|
|
||||||
q.setPen(pen);
|
|
||||||
q.drawEllipse(x, 0, single, single);
|
|
||||||
x -= single - shift;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TopBar::updateInfoLabels() {
|
void TopBar::updateInfoLabels() {
|
||||||
|
@ -688,9 +661,13 @@ void TopBar::updateControlsGeometry() {
|
||||||
_durationLabel->moveToLeft(left, st::callBarLabelTop);
|
_durationLabel->moveToLeft(left, st::callBarLabelTop);
|
||||||
left += _durationLabel->width() + st::callBarSkip;
|
left += _durationLabel->width() + st::callBarSkip;
|
||||||
}
|
}
|
||||||
if (!_userpics.isNull()) {
|
if (_userpicsWidth) {
|
||||||
left += (_userpics.width() / _userpics.devicePixelRatio())
|
const auto single = st::groupCallTopBarUserpics.size;
|
||||||
+ st::callBarSkip;
|
const auto skip = anim::interpolate(
|
||||||
|
0,
|
||||||
|
st::callBarSkip,
|
||||||
|
std::min(_userpicsWidth, single) / float64(single));
|
||||||
|
left += _userpicsWidth + skip;
|
||||||
}
|
}
|
||||||
if (_signalBars) {
|
if (_signalBars) {
|
||||||
_signalBars->moveToLeft(left, (height() - _signalBars->height()) / 2);
|
_signalBars->moveToLeft(left, (height() - _signalBars->height()) / 2);
|
||||||
|
@ -742,11 +719,10 @@ void TopBar::paintEvent(QPaintEvent *e) {
|
||||||
: (_muted ? st::callBarBgMuted : st::callBarBg);
|
: (_muted ? st::callBarBgMuted : st::callBarBg);
|
||||||
p.fillRect(e->rect(), std::move(brush));
|
p.fillRect(e->rect(), std::move(brush));
|
||||||
|
|
||||||
if (!_userpics.isNull()) {
|
if (_userpicsWidth) {
|
||||||
const auto imageSize = _userpics.size()
|
const auto size = st::groupCallTopBarUserpics.size;
|
||||||
/ _userpics.devicePixelRatio();
|
const auto top = (height() - size) / 2;
|
||||||
const auto top = (height() - imageSize.height()) / 2;
|
_userpics->paint(p, _mute->width(), top, size);
|
||||||
p.drawImage(_mute->width(), top, _userpics);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,8 @@ class IconButton;
|
||||||
class AbstractButton;
|
class AbstractButton;
|
||||||
class LabelSimple;
|
class LabelSimple;
|
||||||
class FlatLabel;
|
class FlatLabel;
|
||||||
|
struct GroupCallUser;
|
||||||
|
class GroupCallUserpics;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
namespace Main {
|
namespace Main {
|
||||||
|
@ -66,14 +68,15 @@ private:
|
||||||
void setMuted(bool mute);
|
void setMuted(bool mute);
|
||||||
|
|
||||||
void subscribeToMembersChanges(not_null<GroupCall*> call);
|
void subscribeToMembersChanges(not_null<GroupCall*> call);
|
||||||
void generateUserpicsInRow();
|
void updateUserpics();
|
||||||
|
|
||||||
const base::weak_ptr<Call> _call;
|
const base::weak_ptr<Call> _call;
|
||||||
const base::weak_ptr<GroupCall> _groupCall;
|
const base::weak_ptr<GroupCall> _groupCall;
|
||||||
|
|
||||||
bool _muted = false;
|
bool _muted = false;
|
||||||
std::vector<User> _users;
|
std::vector<Ui::GroupCallUser> _users;
|
||||||
QImage _userpics;
|
std::unique_ptr<Ui::GroupCallUserpics> _userpics;
|
||||||
|
int _userpicsWidth = 0;
|
||||||
object_ptr<Ui::LabelSimple> _durationLabel;
|
object_ptr<Ui::LabelSimple> _durationLabel;
|
||||||
object_ptr<SignalBars> _signalBars;
|
object_ptr<SignalBars> _signalBars;
|
||||||
object_ptr<Ui::FlatLabel> _fullInfoLabel;
|
object_ptr<Ui::FlatLabel> _fullInfoLabel;
|
||||||
|
|
|
@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_group_call.h"
|
#include "data/data_group_call.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "ui/chat/group_call_bar.h"
|
#include "ui/chat/group_call_bar.h"
|
||||||
|
#include "ui/chat/group_call_userpics.h"
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
#include "calls/calls_group_call.h"
|
#include "calls/calls_group_call.h"
|
||||||
#include "calls/calls_instance.h"
|
#include "calls/calls_instance.h"
|
||||||
|
@ -24,7 +25,7 @@ namespace HistoryView {
|
||||||
void GenerateUserpicsInRow(
|
void GenerateUserpicsInRow(
|
||||||
QImage &result,
|
QImage &result,
|
||||||
const std::vector<UserpicInRow> &list,
|
const std::vector<UserpicInRow> &list,
|
||||||
const UserpicsInRowStyle &st,
|
const style::GroupCallUserpics &st,
|
||||||
int maxElements) {
|
int maxElements) {
|
||||||
const auto count = int(list.size());
|
const auto count = int(list.size());
|
||||||
if (!count) {
|
if (!count) {
|
||||||
|
@ -67,7 +68,7 @@ GroupCallTracker::GroupCallTracker(not_null<PeerData*> peer)
|
||||||
|
|
||||||
rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
||||||
not_null<Data::GroupCall*> call,
|
not_null<Data::GroupCall*> call,
|
||||||
const UserpicsInRowStyle &st) {
|
int userpicSize) {
|
||||||
struct State {
|
struct State {
|
||||||
std::vector<UserpicInRow> userpics;
|
std::vector<UserpicInRow> userpics;
|
||||||
Ui::GroupCallBarContent current;
|
Ui::GroupCallBarContent current;
|
||||||
|
@ -130,7 +131,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
||||||
static const auto RegenerateUserpics = [](
|
static const auto RegenerateUserpics = [](
|
||||||
not_null<State*> state,
|
not_null<State*> state,
|
||||||
not_null<Data::GroupCall*> call,
|
not_null<Data::GroupCall*> call,
|
||||||
const UserpicsInRowStyle &st,
|
int userpicSize,
|
||||||
bool force = false) {
|
bool force = false) {
|
||||||
const auto result = FillMissingUserpics(state, call) || force;
|
const auto result = FillMissingUserpics(state, call) || force;
|
||||||
if (!result) {
|
if (!result) {
|
||||||
|
@ -141,7 +142,9 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
||||||
state->someUserpicsNotLoaded = false;
|
state->someUserpicsNotLoaded = false;
|
||||||
for (auto &userpic : state->userpics) {
|
for (auto &userpic : state->userpics) {
|
||||||
userpic.peer->loadUserpic();
|
userpic.peer->loadUserpic();
|
||||||
const auto pic = userpic.peer->genUserpic(userpic.view, st.size);
|
const auto pic = userpic.peer->genUserpic(
|
||||||
|
userpic.view,
|
||||||
|
userpicSize);
|
||||||
userpic.uniqueKey = userpic.peer->userpicUniqueKey(userpic.view);
|
userpic.uniqueKey = userpic.peer->userpicUniqueKey(userpic.view);
|
||||||
state->current.users.push_back({
|
state->current.users.push_back({
|
||||||
.userpic = pic.toImage(),
|
.userpic = pic.toImage(),
|
||||||
|
@ -161,7 +164,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
||||||
not_null<State*> state,
|
not_null<State*> state,
|
||||||
not_null<Data::GroupCall*> call,
|
not_null<Data::GroupCall*> call,
|
||||||
not_null<UserData*> user,
|
not_null<UserData*> user,
|
||||||
const UserpicsInRowStyle &st) {
|
int userpicSize) {
|
||||||
const auto i = ranges::find(
|
const auto i = ranges::find(
|
||||||
state->userpics,
|
state->userpics,
|
||||||
user,
|
user,
|
||||||
|
@ -170,7 +173,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
state->userpics.erase(i);
|
state->userpics.erase(i);
|
||||||
RegenerateUserpics(state, call, st, true);
|
RegenerateUserpics(state, call, userpicSize, true);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -178,7 +181,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
||||||
not_null<State*> state,
|
not_null<State*> state,
|
||||||
not_null<Data::GroupCall*> call,
|
not_null<Data::GroupCall*> call,
|
||||||
not_null<UserData*> user,
|
not_null<UserData*> user,
|
||||||
const UserpicsInRowStyle &st) {
|
int userpicSize) {
|
||||||
Expects(state->userpics.size() <= kLimit);
|
Expects(state->userpics.size() <= kLimit);
|
||||||
|
|
||||||
const auto &participants = call->participants();
|
const auto &participants = call->participants();
|
||||||
|
@ -237,7 +240,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
||||||
}
|
}
|
||||||
Assert(state->userpics.size() <= kLimit);
|
Assert(state->userpics.size() <= kLimit);
|
||||||
}
|
}
|
||||||
RegenerateUserpics(state, call, st, true);
|
RegenerateUserpics(state, call, userpicSize, true);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -262,12 +265,12 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
||||||
) | rpl::start_with_next([=](const ParticipantUpdate &update) {
|
) | rpl::start_with_next([=](const ParticipantUpdate &update) {
|
||||||
const auto user = update.now ? update.now->user : update.was->user;
|
const auto user = update.now ? update.now->user : update.was->user;
|
||||||
if (!update.now) {
|
if (!update.now) {
|
||||||
if (RemoveUserpic(state, call, user, st)) {
|
if (RemoveUserpic(state, call, user, userpicSize)) {
|
||||||
pushNext();
|
pushNext();
|
||||||
}
|
}
|
||||||
} else if (update.now->speaking
|
} else if (update.now->speaking
|
||||||
&& (!update.was || !update.was->speaking)) {
|
&& (!update.was || !update.was->speaking)) {
|
||||||
if (CheckPushToFront(state, call, user, st)) {
|
if (CheckPushToFront(state, call, user, userpicSize)) {
|
||||||
pushNext();
|
pushNext();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -287,7 +290,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
||||||
updateSpeakingState = false;
|
updateSpeakingState = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (RegenerateUserpics(state, call, st)
|
if (RegenerateUserpics(state, call, userpicSize)
|
||||||
|| updateSpeakingState) {
|
|| updateSpeakingState) {
|
||||||
pushNext();
|
pushNext();
|
||||||
}
|
}
|
||||||
|
@ -296,7 +299,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
||||||
|
|
||||||
call->participantsSliceAdded(
|
call->participantsSliceAdded(
|
||||||
) | rpl::filter([=] {
|
) | rpl::filter([=] {
|
||||||
return RegenerateUserpics(state, call, st);
|
return RegenerateUserpics(state, call, userpicSize);
|
||||||
}) | rpl::start_with_next(pushNext, lifetime);
|
}) | rpl::start_with_next(pushNext, lifetime);
|
||||||
|
|
||||||
call->peer()->session().downloaderTaskFinished(
|
call->peer()->session().downloaderTaskFinished(
|
||||||
|
@ -306,14 +309,14 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
||||||
for (const auto &userpic : state->userpics) {
|
for (const auto &userpic : state->userpics) {
|
||||||
if (userpic.peer->userpicUniqueKey(userpic.view)
|
if (userpic.peer->userpicUniqueKey(userpic.view)
|
||||||
!= userpic.uniqueKey) {
|
!= userpic.uniqueKey) {
|
||||||
RegenerateUserpics(state, call, st, true);
|
RegenerateUserpics(state, call, userpicSize, true);
|
||||||
pushNext();
|
pushNext();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, lifetime);
|
}, lifetime);
|
||||||
|
|
||||||
RegenerateUserpics(state, call, st);
|
RegenerateUserpics(state, call, userpicSize);
|
||||||
|
|
||||||
call->fullCountValue(
|
call->fullCountValue(
|
||||||
) | rpl::start_with_next([=](int count) {
|
) | rpl::start_with_next([=](int count) {
|
||||||
|
@ -345,12 +348,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::content() const {
|
||||||
} else if (!call->fullCount() && !call->participantsLoaded()) {
|
} else if (!call->fullCount() && !call->participantsLoaded()) {
|
||||||
call->reload();
|
call->reload();
|
||||||
}
|
}
|
||||||
const auto st = UserpicsInRowStyle{
|
return ContentByCall(call, st::historyGroupCallUserpics.size);
|
||||||
.size = st::historyGroupCallUserpicSize,
|
|
||||||
.shift = st::historyGroupCallUserpicShift,
|
|
||||||
.stroke = st::historyGroupCallUserpicStroke,
|
|
||||||
};
|
|
||||||
return ContentByCall(call, st);
|
|
||||||
}) | rpl::flatten_latest();
|
}) | rpl::flatten_latest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,10 @@ class GroupCall;
|
||||||
class CloudImageView;
|
class CloudImageView;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
|
namespace style {
|
||||||
|
struct GroupCallUserpics;
|
||||||
|
} // namespace style
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
|
||||||
struct UserpicInRow {
|
struct UserpicInRow {
|
||||||
|
@ -27,16 +31,10 @@ struct UserpicInRow {
|
||||||
mutable InMemoryKey uniqueKey;
|
mutable InMemoryKey uniqueKey;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct UserpicsInRowStyle {
|
|
||||||
int size = 0;
|
|
||||||
int shift = 0;
|
|
||||||
int stroke = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
void GenerateUserpicsInRow(
|
void GenerateUserpicsInRow(
|
||||||
QImage &result,
|
QImage &result,
|
||||||
const std::vector<UserpicInRow> &list,
|
const std::vector<UserpicInRow> &list,
|
||||||
const UserpicsInRowStyle &st,
|
const style::GroupCallUserpics &st,
|
||||||
int maxElements = 0);
|
int maxElements = 0);
|
||||||
|
|
||||||
class GroupCallTracker final {
|
class GroupCallTracker final {
|
||||||
|
@ -48,7 +46,7 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] static rpl::producer<Ui::GroupCallBarContent> ContentByCall(
|
[[nodiscard]] static rpl::producer<Ui::GroupCallBarContent> ContentByCall(
|
||||||
not_null<Data::GroupCall*> call,
|
not_null<Data::GroupCall*> call,
|
||||||
const UserpicsInRowStyle &st);
|
int userpicSize);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const not_null<PeerData*> _peer;
|
const not_null<PeerData*> _peer;
|
||||||
|
|
|
@ -795,8 +795,8 @@ void Message::paintCommentsButton(
|
||||||
auto &list = _comments->userpics;
|
auto &list = _comments->userpics;
|
||||||
const auto limit = HistoryMessageViews::kMaxRecentRepliers;
|
const auto limit = HistoryMessageViews::kMaxRecentRepliers;
|
||||||
const auto count = std::min(int(views->recentRepliers.size()), limit);
|
const auto count = std::min(int(views->recentRepliers.size()), limit);
|
||||||
const auto single = st::historyCommentsUserpicSize;
|
const auto single = st::historyCommentsUserpics.size;
|
||||||
const auto shift = st::historyCommentsUserpicOverlap;
|
const auto shift = st::historyCommentsUserpics.shift;
|
||||||
const auto regenerate = [&] {
|
const auto regenerate = [&] {
|
||||||
if (list.size() != count) {
|
if (list.size() != count) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -828,12 +828,11 @@ void Message::paintCommentsButton(
|
||||||
while (list.size() > count) {
|
while (list.size() > count) {
|
||||||
list.pop_back();
|
list.pop_back();
|
||||||
}
|
}
|
||||||
const auto st = UserpicsInRowStyle{
|
GenerateUserpicsInRow(
|
||||||
.size = single,
|
_comments->cachedUserpics,
|
||||||
.shift = shift,
|
list,
|
||||||
.stroke = st::historyCommentsUserpicStroke,
|
st::historyCommentsUserpics,
|
||||||
};
|
limit);
|
||||||
GenerateUserpicsInRow(_comments->cachedUserpics, list, st, limit);
|
|
||||||
}
|
}
|
||||||
p.drawImage(
|
p.drawImage(
|
||||||
left,
|
left,
|
||||||
|
@ -2135,8 +2134,8 @@ int Message::minWidthForMedia() const {
|
||||||
const auto views = data()->Get<HistoryMessageViews>();
|
const auto views = data()->Get<HistoryMessageViews>();
|
||||||
if (data()->repliesAreComments() && !views->replies.text.isEmpty()) {
|
if (data()->repliesAreComments() && !views->replies.text.isEmpty()) {
|
||||||
const auto limit = HistoryMessageViews::kMaxRecentRepliers;
|
const auto limit = HistoryMessageViews::kMaxRecentRepliers;
|
||||||
const auto single = st::historyCommentsUserpicSize;
|
const auto single = st::historyCommentsUserpics.size;
|
||||||
const auto shift = st::historyCommentsUserpicOverlap;
|
const auto shift = st::historyCommentsUserpics.shift;
|
||||||
const auto added = single
|
const auto added = single
|
||||||
+ (limit - 1) * (single - shift)
|
+ (limit - 1) * (single - shift)
|
||||||
+ st::historyCommentsSkipLeft
|
+ st::historyCommentsSkipLeft
|
||||||
|
|
|
@ -18,6 +18,13 @@ MessageBar {
|
||||||
duration: int;
|
duration: int;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GroupCallUserpics {
|
||||||
|
size: pixels;
|
||||||
|
shift: pixels;
|
||||||
|
stroke: pixels;
|
||||||
|
align: align;
|
||||||
|
}
|
||||||
|
|
||||||
defaultMessageBar: MessageBar {
|
defaultMessageBar: MessageBar {
|
||||||
title: semiboldTextStyle;
|
title: semiboldTextStyle;
|
||||||
titleFg: windowActiveTextFg;
|
titleFg: windowActiveTextFg;
|
||||||
|
@ -739,10 +746,13 @@ historyPollInChosenSelected: icon {{ "poll_select_check", historyFileInIconFgSel
|
||||||
historyCommentsButtonHeight: 40px;
|
historyCommentsButtonHeight: 40px;
|
||||||
historyCommentsSkipLeft: 9px;
|
historyCommentsSkipLeft: 9px;
|
||||||
historyCommentsSkipText: 10px;
|
historyCommentsSkipText: 10px;
|
||||||
historyCommentsUserpicSize: 25px;
|
|
||||||
historyCommentsUserpicStroke: 2px;
|
|
||||||
historyCommentsUserpicOverlap: 6px;
|
|
||||||
historyCommentsSkipRight: 8px;
|
historyCommentsSkipRight: 8px;
|
||||||
|
historyCommentsUserpics: GroupCallUserpics {
|
||||||
|
size: 25px;
|
||||||
|
shift: 6px;
|
||||||
|
stroke: 2px;
|
||||||
|
align: align(left);
|
||||||
|
}
|
||||||
|
|
||||||
boxAttachEmoji: IconButton(historyAttachEmoji) {
|
boxAttachEmoji: IconButton(historyAttachEmoji) {
|
||||||
width: 30px;
|
width: 30px;
|
||||||
|
@ -798,9 +808,12 @@ historyCommentsOpenOutSelected: icon {{ "history_comments_open", msgFileThumbLin
|
||||||
|
|
||||||
historySlowmodeCounterMargins: margins(0px, 0px, 10px, 0px);
|
historySlowmodeCounterMargins: margins(0px, 0px, 10px, 0px);
|
||||||
|
|
||||||
historyGroupCallUserpicSize: 32px;
|
historyGroupCallUserpics: GroupCallUserpics {
|
||||||
historyGroupCallUserpicShift: 12px;
|
size: 32px;
|
||||||
historyGroupCallUserpicStroke: 4px;
|
shift: 12px;
|
||||||
|
stroke: 4px;
|
||||||
|
align: align(top);
|
||||||
|
}
|
||||||
historyGroupCallBlobMinRadius: 23px;
|
historyGroupCallBlobMinRadius: 23px;
|
||||||
historyGroupCallBlobMaxRadius: 25px;
|
historyGroupCallBlobMaxRadius: 25px;
|
||||||
|
|
||||||
|
|
|
@ -7,12 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "ui/chat/group_call_bar.h"
|
#include "ui/chat/group_call_bar.h"
|
||||||
|
|
||||||
#include "ui/chat/message_bar.h"
|
#include "ui/chat/group_call_userpics.h"
|
||||||
#include "ui/widgets/shadow.h"
|
#include "ui/widgets/shadow.h"
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
#include "ui/paint/blobs.h"
|
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "base/openssl_help.h"
|
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
#include "styles/style_calls.h"
|
#include "styles/style_calls.h"
|
||||||
#include "styles/style_info.h" // st::topBarArrowPadding, like TopBarWidget.
|
#include "styles/style_info.h" // st::topBarArrowPadding, like TopBarWidget.
|
||||||
|
@ -21,71 +19,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include <QtGui/QtEvents>
|
#include <QtGui/QtEvents>
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
namespace {
|
|
||||||
|
|
||||||
constexpr auto kDuration = 160;
|
|
||||||
constexpr auto kMaxUserpics = 4;
|
|
||||||
constexpr auto kWideScale = 5;
|
|
||||||
|
|
||||||
constexpr auto kBlobsEnterDuration = crl::time(250);
|
|
||||||
constexpr auto kLevelDuration = 100. + 500. * 0.23;
|
|
||||||
constexpr auto kBlobScale = 0.605;
|
|
||||||
constexpr auto kMinorBlobFactor = 0.9f;
|
|
||||||
constexpr auto kUserpicMinScale = 0.8;
|
|
||||||
constexpr auto kMaxLevel = 1.;
|
|
||||||
constexpr auto kSendRandomLevelInterval = crl::time(100);
|
|
||||||
|
|
||||||
auto Blobs()->std::array<Ui::Paint::Blobs::BlobData, 2> {
|
|
||||||
return { {
|
|
||||||
{
|
|
||||||
.segmentsCount = 6,
|
|
||||||
.minScale = kBlobScale * kMinorBlobFactor,
|
|
||||||
.minRadius = st::historyGroupCallBlobMinRadius * kMinorBlobFactor,
|
|
||||||
.maxRadius = st::historyGroupCallBlobMaxRadius * kMinorBlobFactor,
|
|
||||||
.speedScale = 1.,
|
|
||||||
.alpha = .5,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.segmentsCount = 8,
|
|
||||||
.minScale = kBlobScale,
|
|
||||||
.minRadius = (float)st::historyGroupCallBlobMinRadius,
|
|
||||||
.maxRadius = (float)st::historyGroupCallBlobMaxRadius,
|
|
||||||
.speedScale = 1.,
|
|
||||||
.alpha = .2,
|
|
||||||
},
|
|
||||||
} };
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
struct GroupCallBar::BlobsAnimation {
|
|
||||||
BlobsAnimation(
|
|
||||||
std::vector<Ui::Paint::Blobs::BlobData> blobDatas,
|
|
||||||
float levelDuration,
|
|
||||||
float maxLevel)
|
|
||||||
: blobs(std::move(blobDatas), levelDuration, maxLevel) {
|
|
||||||
}
|
|
||||||
|
|
||||||
Ui::Paint::Blobs blobs;
|
|
||||||
crl::time lastTime = 0;
|
|
||||||
crl::time lastSpeakingUpdateTime = 0;
|
|
||||||
float64 enter = 0.;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GroupCallBar::Userpic {
|
|
||||||
User data;
|
|
||||||
std::pair<uint64, uint64> cacheKey;
|
|
||||||
crl::time speakingStarted = 0;
|
|
||||||
QImage cache;
|
|
||||||
Animations::Simple leftAnimation;
|
|
||||||
Animations::Simple shownAnimation;
|
|
||||||
std::unique_ptr<BlobsAnimation> blobsAnimation;
|
|
||||||
int left = 0;
|
|
||||||
bool positionInited = false;
|
|
||||||
bool topMost = false;
|
|
||||||
bool hiding = false;
|
|
||||||
bool cacheMasked = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
GroupCallBar::GroupCallBar(
|
GroupCallBar::GroupCallBar(
|
||||||
not_null<QWidget*> parent,
|
not_null<QWidget*> parent,
|
||||||
|
@ -98,16 +31,13 @@ GroupCallBar::GroupCallBar(
|
||||||
tr::lng_group_call_join(),
|
tr::lng_group_call_join(),
|
||||||
st::groupCallTopBarJoin))
|
st::groupCallTopBarJoin))
|
||||||
, _shadow(std::make_unique<PlainShadow>(_wrap.parentWidget()))
|
, _shadow(std::make_unique<PlainShadow>(_wrap.parentWidget()))
|
||||||
, _randomSpeakingTimer([=] { sendRandomLevels(); }) {
|
, _userpics(std::make_unique<GroupCallUserpics>(
|
||||||
|
st::historyGroupCallUserpics,
|
||||||
|
std::move(hideBlobs),
|
||||||
|
[=] { updateUserpics(); })) {
|
||||||
_wrap.hide(anim::type::instant);
|
_wrap.hide(anim::type::instant);
|
||||||
_shadow->hide();
|
_shadow->hide();
|
||||||
|
|
||||||
const auto limit = kMaxUserpics;
|
|
||||||
const auto single = st::historyGroupCallUserpicSize;
|
|
||||||
const auto shift = st::historyGroupCallUserpicShift;
|
|
||||||
// + 1 * single for the blobs.
|
|
||||||
_maxUserpicsWidth = 2 * single + (limit - 1) * (single - shift);
|
|
||||||
|
|
||||||
_wrap.entity()->paintRequest(
|
_wrap.entity()->paintRequest(
|
||||||
) | rpl::start_with_next([=](QRect clip) {
|
) | rpl::start_with_next([=](QRect clip) {
|
||||||
QPainter(_wrap.entity()).fillRect(clip, st::historyPinnedBg);
|
QPainter(_wrap.entity()).fillRect(clip, st::historyPinnedBg);
|
||||||
|
@ -122,7 +52,7 @@ GroupCallBar::GroupCallBar(
|
||||||
copy
|
copy
|
||||||
) | rpl::start_with_next([=](GroupCallBarContent &&content) {
|
) | rpl::start_with_next([=](GroupCallBarContent &&content) {
|
||||||
_content = content;
|
_content = content;
|
||||||
updateUserpicsFromContent();
|
_userpics->update(_content.users, !_wrap.isHidden());
|
||||||
_inner->update();
|
_inner->update();
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
|
@ -140,53 +70,10 @@ GroupCallBar::GroupCallBar(
|
||||||
_wrap.toggle(false, anim::type::normal);
|
_wrap.toggle(false, anim::type::normal);
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
style::PaletteChanged(
|
|
||||||
) | rpl::start_with_next([=] {
|
|
||||||
for (auto &userpic : _userpics) {
|
|
||||||
userpic.cache = QImage();
|
|
||||||
}
|
|
||||||
}, lifetime());
|
|
||||||
|
|
||||||
_speakingAnimation.init([=](crl::time now) {
|
|
||||||
if (const auto &last = _speakingAnimationHideLastTime; (last > 0)
|
|
||||||
&& (now - last >= kBlobsEnterDuration)) {
|
|
||||||
_speakingAnimation.stop();
|
|
||||||
}
|
|
||||||
for (auto &userpic : _userpics) {
|
|
||||||
if (const auto blobs = userpic.blobsAnimation.get()) {
|
|
||||||
blobs->blobs.updateLevel(now - blobs->lastTime);
|
|
||||||
blobs->lastTime = now;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateUserpics();
|
|
||||||
});
|
|
||||||
|
|
||||||
rpl::combine(
|
|
||||||
rpl::single(anim::Disabled()) | rpl::then(anim::Disables()),
|
|
||||||
std::move(hideBlobs)
|
|
||||||
) | rpl::start_with_next([=](bool animDisabled, bool deactivated) {
|
|
||||||
const auto hide = animDisabled || deactivated;
|
|
||||||
|
|
||||||
if (!(hide && _speakingAnimationHideLastTime)) {
|
|
||||||
_speakingAnimationHideLastTime = hide ? crl::now() : 0;
|
|
||||||
}
|
|
||||||
_skipLevelUpdate = hide;
|
|
||||||
for (auto &userpic : _userpics) {
|
|
||||||
if (const auto blobs = userpic.blobsAnimation.get()) {
|
|
||||||
blobs->blobs.setLevel(0.);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!hide && !_speakingAnimation.animating()) {
|
|
||||||
_speakingAnimation.start();
|
|
||||||
}
|
|
||||||
_skipLevelUpdate = hide;
|
|
||||||
}, lifetime());
|
|
||||||
|
|
||||||
setupInner();
|
setupInner();
|
||||||
}
|
}
|
||||||
|
|
||||||
GroupCallBar::~GroupCallBar() {
|
GroupCallBar::~GroupCallBar() = default;
|
||||||
}
|
|
||||||
|
|
||||||
void GroupCallBar::setupInner() {
|
void GroupCallBar::setupInner() {
|
||||||
_inner->resize(0, st::historyReplyHeight);
|
_inner->resize(0, st::historyReplyHeight);
|
||||||
|
@ -252,142 +139,11 @@ void GroupCallBar::paint(Painter &p) {
|
||||||
? tr::lng_group_call_members(tr::now, lt_count, _content.count)
|
? tr::lng_group_call_members(tr::now, lt_count, _content.count)
|
||||||
: tr::lng_group_call_no_members(tr::now)));
|
: tr::lng_group_call_no_members(tr::now)));
|
||||||
|
|
||||||
|
const auto size = st::historyGroupCallUserpics.size;
|
||||||
// Skip shadow of the bar above.
|
// Skip shadow of the bar above.
|
||||||
paintUserpics(p);
|
const auto top = (st::historyReplyHeight - st::lineWidth - size) / 2
|
||||||
}
|
+ st::lineWidth;
|
||||||
|
_userpics->paint(p, _inner->width() / 2, top, size);
|
||||||
void GroupCallBar::paintUserpics(Painter &p) {
|
|
||||||
const auto top = (st::historyReplyHeight
|
|
||||||
- st::lineWidth
|
|
||||||
- st::historyGroupCallUserpicSize) / 2 + st::lineWidth;
|
|
||||||
const auto middle = _inner->width() / 2;
|
|
||||||
const auto size = st::historyGroupCallUserpicSize;
|
|
||||||
const auto factor = style::DevicePixelRatio();
|
|
||||||
const auto &minScale = kUserpicMinScale;
|
|
||||||
for (auto &userpic : ranges::view::reverse(_userpics)) {
|
|
||||||
const auto shown = userpic.shownAnimation.value(
|
|
||||||
userpic.hiding ? 0. : 1.);
|
|
||||||
if (shown == 0.) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
validateUserpicCache(userpic);
|
|
||||||
p.setOpacity(shown);
|
|
||||||
const auto left = middle + userpic.leftAnimation.value(userpic.left);
|
|
||||||
const auto blobs = userpic.blobsAnimation.get();
|
|
||||||
const auto shownScale = 0.5 + shown / 2.;
|
|
||||||
const auto scale = shownScale * (!blobs
|
|
||||||
? 1.
|
|
||||||
: (minScale
|
|
||||||
+ (1. - minScale) * (_speakingAnimationHideLastTime
|
|
||||||
? (1. - blobs->blobs.currentLevel())
|
|
||||||
: blobs->blobs.currentLevel())));
|
|
||||||
if (blobs) {
|
|
||||||
auto hq = PainterHighQualityEnabler(p);
|
|
||||||
|
|
||||||
const auto shift = QPointF(left + size / 2., top + size / 2.);
|
|
||||||
p.translate(shift);
|
|
||||||
blobs->blobs.paint(p, st::windowActiveTextFg);
|
|
||||||
p.translate(-shift);
|
|
||||||
p.setOpacity(1.);
|
|
||||||
}
|
|
||||||
if (std::abs(scale - 1.) < 0.001) {
|
|
||||||
const auto skip = ((kWideScale - 1) / 2) * size * factor;
|
|
||||||
p.drawImage(
|
|
||||||
QRect(left, top, size, size),
|
|
||||||
userpic.cache,
|
|
||||||
QRect(skip, skip, size * factor, size * factor));
|
|
||||||
} else {
|
|
||||||
auto hq = PainterHighQualityEnabler(p);
|
|
||||||
|
|
||||||
auto target = QRect(
|
|
||||||
left + (1 - kWideScale) / 2 * size,
|
|
||||||
top + (1 - kWideScale) / 2 * size,
|
|
||||||
kWideScale * size,
|
|
||||||
kWideScale * size);
|
|
||||||
auto shrink = anim::interpolate(
|
|
||||||
(1 - kWideScale) / 2 * size,
|
|
||||||
0,
|
|
||||||
scale);
|
|
||||||
auto margins = QMargins(shrink, shrink, shrink, shrink);
|
|
||||||
p.drawImage(target.marginsAdded(margins), userpic.cache);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.setOpacity(1.);
|
|
||||||
|
|
||||||
const auto hidden = [](const Userpic &userpic) {
|
|
||||||
return userpic.hiding && !userpic.shownAnimation.animating();
|
|
||||||
};
|
|
||||||
_userpics.erase(ranges::remove_if(_userpics, hidden), end(_userpics));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GroupCallBar::needUserpicCacheRefresh(Userpic &userpic) {
|
|
||||||
if (userpic.cache.isNull()) {
|
|
||||||
return true;
|
|
||||||
} else if (userpic.hiding) {
|
|
||||||
return false;
|
|
||||||
} else if (userpic.cacheKey != userpic.data.userpicKey) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const auto shouldBeMasked = !userpic.topMost;
|
|
||||||
if (userpic.cacheMasked == shouldBeMasked || !shouldBeMasked) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return !userpic.leftAnimation.animating();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GroupCallBar::ensureBlobsAnimation(Userpic &userpic) {
|
|
||||||
if (userpic.blobsAnimation) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
userpic.blobsAnimation = std::make_unique<BlobsAnimation>(
|
|
||||||
Blobs() | ranges::to_vector,
|
|
||||||
kLevelDuration,
|
|
||||||
kMaxLevel);
|
|
||||||
userpic.blobsAnimation->lastTime = crl::now();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GroupCallBar::sendRandomLevels() {
|
|
||||||
if (_skipLevelUpdate) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (auto &userpic : _userpics) {
|
|
||||||
if (const auto blobs = userpic.blobsAnimation.get()) {
|
|
||||||
const auto value = 30 + (openssl::RandomValue<uint32>() % 70);
|
|
||||||
userpic.blobsAnimation->blobs.setLevel(float64(value) / 100.);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GroupCallBar::validateUserpicCache(Userpic &userpic) {
|
|
||||||
if (!needUserpicCacheRefresh(userpic)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto factor = style::DevicePixelRatio();
|
|
||||||
const auto size = st::historyGroupCallUserpicSize;
|
|
||||||
const auto shift = st::historyGroupCallUserpicShift;
|
|
||||||
const auto full = QSize(size, size) * kWideScale * factor;
|
|
||||||
if (userpic.cache.isNull()) {
|
|
||||||
userpic.cache = QImage(full, QImage::Format_ARGB32_Premultiplied);
|
|
||||||
userpic.cache.setDevicePixelRatio(factor);
|
|
||||||
}
|
|
||||||
userpic.cacheKey = userpic.data.userpicKey;
|
|
||||||
userpic.cacheMasked = !userpic.topMost;
|
|
||||||
userpic.cache.fill(Qt::transparent);
|
|
||||||
{
|
|
||||||
Painter p(&userpic.cache);
|
|
||||||
const auto skip = (kWideScale - 1) / 2 * size;
|
|
||||||
p.drawImage(skip, skip, userpic.data.userpic);
|
|
||||||
|
|
||||||
if (userpic.cacheMasked) {
|
|
||||||
auto hq = PainterHighQualityEnabler(p);
|
|
||||||
auto pen = QPen(Qt::transparent);
|
|
||||||
pen.setWidth(st::historyGroupCallUserpicStroke);
|
|
||||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
|
||||||
p.setBrush(Qt::transparent);
|
|
||||||
p.setPen(pen);
|
|
||||||
p.drawEllipse(skip - size + shift, skip, size, size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GroupCallBar::updateControlsGeometry(QRect wrapGeometry) {
|
void GroupCallBar::updateControlsGeometry(QRect wrapGeometry) {
|
||||||
|
@ -413,127 +169,14 @@ void GroupCallBar::updateShadowGeometry(QRect wrapGeometry) {
|
||||||
: regular);
|
: regular);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GroupCallBar::updateUserpicsFromContent() {
|
|
||||||
const auto idFromUserpic = [](const Userpic &userpic) {
|
|
||||||
return userpic.data.id;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Use "topMost" as "willBeHidden" flag.
|
|
||||||
for (auto &userpic : _userpics) {
|
|
||||||
userpic.topMost = true;
|
|
||||||
}
|
|
||||||
for (const auto &user : _content.users) {
|
|
||||||
const auto i = ranges::find(_userpics, user.id, idFromUserpic);
|
|
||||||
if (i == end(_userpics)) {
|
|
||||||
_userpics.push_back(Userpic{ user });
|
|
||||||
toggleUserpic(_userpics.back(), true);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
i->topMost = false;
|
|
||||||
|
|
||||||
if (i->hiding) {
|
|
||||||
toggleUserpic(*i, true);
|
|
||||||
}
|
|
||||||
i->data = user;
|
|
||||||
|
|
||||||
// Put this one after the last we are not hiding.
|
|
||||||
for (auto j = end(_userpics) - 1; j != i; --j) {
|
|
||||||
if (!j->topMost) {
|
|
||||||
ranges::rotate(i, i + 1, j + 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide the ones that "willBeHidden" (currently having "topMost" flag).
|
|
||||||
// Set correct real values of "topMost" flag.
|
|
||||||
const auto userpicsBegin = begin(_userpics);
|
|
||||||
const auto userpicsEnd = end(_userpics);
|
|
||||||
auto markedTopMost = userpicsEnd;
|
|
||||||
auto hasBlobs = false;
|
|
||||||
for (auto i = userpicsBegin; i != userpicsEnd; ++i) {
|
|
||||||
auto &userpic = *i;
|
|
||||||
if (userpic.data.speaking) {
|
|
||||||
ensureBlobsAnimation(userpic);
|
|
||||||
hasBlobs = true;
|
|
||||||
} else {
|
|
||||||
userpic.blobsAnimation = nullptr;
|
|
||||||
}
|
|
||||||
if (userpic.topMost) {
|
|
||||||
toggleUserpic(userpic, false);
|
|
||||||
userpic.topMost = false;
|
|
||||||
} else if (markedTopMost == userpicsEnd) {
|
|
||||||
userpic.topMost = true;
|
|
||||||
markedTopMost = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (markedTopMost != userpicsEnd && markedTopMost != userpicsBegin) {
|
|
||||||
// Bring the topMost userpic to the very beginning, above all hiding.
|
|
||||||
std::rotate(userpicsBegin, markedTopMost, markedTopMost + 1);
|
|
||||||
}
|
|
||||||
updateUserpicsPositions();
|
|
||||||
|
|
||||||
if (!hasBlobs) {
|
|
||||||
_randomSpeakingTimer.cancel();
|
|
||||||
_speakingAnimation.stop();
|
|
||||||
} else if (!_randomSpeakingTimer.isActive()) {
|
|
||||||
_randomSpeakingTimer.callEach(kSendRandomLevelInterval);
|
|
||||||
_speakingAnimation.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_wrap.isHidden()) {
|
|
||||||
for (auto &userpic : _userpics) {
|
|
||||||
userpic.shownAnimation.stop();
|
|
||||||
userpic.leftAnimation.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GroupCallBar::toggleUserpic(Userpic &userpic, bool shown) {
|
|
||||||
userpic.hiding = !shown;
|
|
||||||
userpic.shownAnimation.start(
|
|
||||||
[=] { updateUserpics(); },
|
|
||||||
shown ? 0. : 1.,
|
|
||||||
shown ? 1. : 0.,
|
|
||||||
kDuration);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GroupCallBar::updateUserpicsPositions() {
|
|
||||||
const auto shownCount = ranges::count(_userpics, false, &Userpic::hiding);
|
|
||||||
if (!shownCount) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto single = st::historyGroupCallUserpicSize;
|
|
||||||
const auto shift = st::historyGroupCallUserpicShift;
|
|
||||||
// + 1 * single for the blobs.
|
|
||||||
const auto fullWidth = single + (shownCount - 1) * (single - shift);
|
|
||||||
auto left = (-fullWidth / 2);
|
|
||||||
for (auto &userpic : _userpics) {
|
|
||||||
if (userpic.hiding) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!userpic.positionInited) {
|
|
||||||
userpic.positionInited = true;
|
|
||||||
userpic.left = left;
|
|
||||||
} else if (userpic.left != left) {
|
|
||||||
userpic.leftAnimation.start(
|
|
||||||
[=] { updateUserpics(); },
|
|
||||||
userpic.left,
|
|
||||||
left,
|
|
||||||
kDuration);
|
|
||||||
userpic.left = left;
|
|
||||||
}
|
|
||||||
left += (single - shift);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GroupCallBar::updateUserpics() {
|
void GroupCallBar::updateUserpics() {
|
||||||
const auto widget = _wrap.entity();
|
const auto widget = _wrap.entity();
|
||||||
const auto middle = widget->width() / 2;
|
const auto middle = widget->width() / 2;
|
||||||
_wrap.entity()->update(
|
const auto width = _userpics->maxWidth();
|
||||||
(middle - _maxUserpicsWidth / 2),
|
widget->update(
|
||||||
|
(middle - width / 2),
|
||||||
0,
|
0,
|
||||||
_maxUserpicsWidth,
|
width,
|
||||||
widget->height());
|
widget->height());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/wrap/slide_wrap.h"
|
#include "ui/wrap/slide_wrap.h"
|
||||||
#include "ui/effects/animations.h"
|
#include "ui/effects/animations.h"
|
||||||
#include "base/object_ptr.h"
|
#include "base/object_ptr.h"
|
||||||
#include "base/timer.h"
|
|
||||||
|
|
||||||
class Painter;
|
class Painter;
|
||||||
|
|
||||||
|
@ -18,17 +17,13 @@ namespace Ui {
|
||||||
|
|
||||||
class PlainShadow;
|
class PlainShadow;
|
||||||
class RoundButton;
|
class RoundButton;
|
||||||
|
struct GroupCallUser;
|
||||||
|
class GroupCallUserpics;
|
||||||
|
|
||||||
struct GroupCallBarContent {
|
struct GroupCallBarContent {
|
||||||
struct User {
|
|
||||||
QImage userpic;
|
|
||||||
std::pair<uint64, uint64> userpicKey = {};
|
|
||||||
int32 id = 0;
|
|
||||||
bool speaking = false;
|
|
||||||
};
|
|
||||||
int count = 0;
|
int count = 0;
|
||||||
bool shown = false;
|
bool shown = false;
|
||||||
std::vector<User> users;
|
std::vector<GroupCallUser> users;
|
||||||
};
|
};
|
||||||
|
|
||||||
class GroupCallBar final {
|
class GroupCallBar final {
|
||||||
|
@ -58,24 +53,13 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using User = GroupCallBarContent::User;
|
using User = GroupCallUser;
|
||||||
struct BlobsAnimation;
|
|
||||||
struct Userpic;
|
|
||||||
|
|
||||||
void updateShadowGeometry(QRect wrapGeometry);
|
void updateShadowGeometry(QRect wrapGeometry);
|
||||||
void updateControlsGeometry(QRect wrapGeometry);
|
void updateControlsGeometry(QRect wrapGeometry);
|
||||||
void updateUserpicsFromContent();
|
void updateUserpics();
|
||||||
void setupInner();
|
void setupInner();
|
||||||
void paint(Painter &p);
|
void paint(Painter &p);
|
||||||
void paintUserpics(Painter &p);
|
|
||||||
|
|
||||||
void toggleUserpic(Userpic &userpic, bool shown);
|
|
||||||
void updateUserpics();
|
|
||||||
void updateUserpicsPositions();
|
|
||||||
void validateUserpicCache(Userpic &userpic);
|
|
||||||
[[nodiscard]] bool needUserpicCacheRefresh(Userpic &userpic);
|
|
||||||
void ensureBlobsAnimation(Userpic &userpic);
|
|
||||||
void sendRandomLevels();
|
|
||||||
|
|
||||||
SlideWrap<> _wrap;
|
SlideWrap<> _wrap;
|
||||||
not_null<RpWidget*> _inner;
|
not_null<RpWidget*> _inner;
|
||||||
|
@ -83,17 +67,11 @@ private:
|
||||||
std::unique_ptr<PlainShadow> _shadow;
|
std::unique_ptr<PlainShadow> _shadow;
|
||||||
rpl::event_stream<> _barClicks;
|
rpl::event_stream<> _barClicks;
|
||||||
Fn<QRect(QRect)> _shadowGeometryPostprocess;
|
Fn<QRect(QRect)> _shadowGeometryPostprocess;
|
||||||
std::vector<Userpic> _userpics;
|
|
||||||
base::Timer _randomSpeakingTimer;
|
|
||||||
Ui::Animations::Basic _speakingAnimation;
|
|
||||||
int _maxUserpicsWidth = 0;
|
|
||||||
bool _shouldBeShown = false;
|
bool _shouldBeShown = false;
|
||||||
bool _forceHidden = false;
|
bool _forceHidden = false;
|
||||||
|
|
||||||
bool _skipLevelUpdate = false;
|
|
||||||
crl::time _speakingAnimationHideLastTime = 0;
|
|
||||||
|
|
||||||
GroupCallBarContent _content;
|
GroupCallBarContent _content;
|
||||||
|
std::unique_ptr<GroupCallUserpics> _userpics;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
416
Telegram/SourceFiles/ui/chat/group_call_userpics.cpp
Normal file
416
Telegram/SourceFiles/ui/chat/group_call_userpics.cpp
Normal file
|
@ -0,0 +1,416 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#include "ui/chat/group_call_userpics.h"
|
||||||
|
|
||||||
|
#include "ui/paint/blobs.h"
|
||||||
|
#include "base/openssl_help.h"
|
||||||
|
#include "styles/style_chat.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kDuration = 160;
|
||||||
|
constexpr auto kMaxUserpics = 4;
|
||||||
|
constexpr auto kWideScale = 5;
|
||||||
|
|
||||||
|
constexpr auto kBlobsEnterDuration = crl::time(250);
|
||||||
|
constexpr auto kLevelDuration = 100. + 500. * 0.23;
|
||||||
|
constexpr auto kBlobScale = 0.605;
|
||||||
|
constexpr auto kMinorBlobFactor = 0.9f;
|
||||||
|
constexpr auto kUserpicMinScale = 0.8;
|
||||||
|
constexpr auto kMaxLevel = 1.;
|
||||||
|
constexpr auto kSendRandomLevelInterval = crl::time(100);
|
||||||
|
|
||||||
|
auto Blobs()->std::array<Ui::Paint::Blobs::BlobData, 2> {
|
||||||
|
return { {
|
||||||
|
{
|
||||||
|
.segmentsCount = 6,
|
||||||
|
.minScale = kBlobScale * kMinorBlobFactor,
|
||||||
|
.minRadius = st::historyGroupCallBlobMinRadius * kMinorBlobFactor,
|
||||||
|
.maxRadius = st::historyGroupCallBlobMaxRadius * kMinorBlobFactor,
|
||||||
|
.speedScale = 1.,
|
||||||
|
.alpha = .5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.segmentsCount = 8,
|
||||||
|
.minScale = kBlobScale,
|
||||||
|
.minRadius = (float)st::historyGroupCallBlobMinRadius,
|
||||||
|
.maxRadius = (float)st::historyGroupCallBlobMaxRadius,
|
||||||
|
.speedScale = 1.,
|
||||||
|
.alpha = .2,
|
||||||
|
},
|
||||||
|
} };
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
struct GroupCallUserpics::BlobsAnimation {
|
||||||
|
BlobsAnimation(
|
||||||
|
std::vector<Ui::Paint::Blobs::BlobData> blobDatas,
|
||||||
|
float levelDuration,
|
||||||
|
float maxLevel)
|
||||||
|
: blobs(std::move(blobDatas), levelDuration, maxLevel) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Ui::Paint::Blobs blobs;
|
||||||
|
crl::time lastTime = 0;
|
||||||
|
crl::time lastSpeakingUpdateTime = 0;
|
||||||
|
float64 enter = 0.;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GroupCallUserpics::Userpic {
|
||||||
|
User data;
|
||||||
|
std::pair<uint64, uint64> cacheKey;
|
||||||
|
crl::time speakingStarted = 0;
|
||||||
|
QImage cache;
|
||||||
|
Animations::Simple leftAnimation;
|
||||||
|
Animations::Simple shownAnimation;
|
||||||
|
std::unique_ptr<BlobsAnimation> blobsAnimation;
|
||||||
|
int left = 0;
|
||||||
|
bool positionInited = false;
|
||||||
|
bool topMost = false;
|
||||||
|
bool hiding = false;
|
||||||
|
bool cacheMasked = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
GroupCallUserpics::GroupCallUserpics(
|
||||||
|
const style::GroupCallUserpics &st,
|
||||||
|
rpl::producer<bool> &&hideBlobs,
|
||||||
|
Fn<void()> repaint)
|
||||||
|
: _st(st)
|
||||||
|
, _randomSpeakingTimer([=] { sendRandomLevels(); })
|
||||||
|
, _repaint(std::move(repaint)) {
|
||||||
|
const auto limit = kMaxUserpics;
|
||||||
|
const auto single = _st.size;
|
||||||
|
const auto shift = _st.shift;
|
||||||
|
// + 1 * single for the blobs.
|
||||||
|
_maxWidth = 2 * single + (limit - 1) * (single - shift);
|
||||||
|
|
||||||
|
style::PaletteChanged(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
for (auto &userpic : _list) {
|
||||||
|
userpic.cache = QImage();
|
||||||
|
}
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
_speakingAnimation.init([=](crl::time now) {
|
||||||
|
if (const auto &last = _speakingAnimationHideLastTime; (last > 0)
|
||||||
|
&& (now - last >= kBlobsEnterDuration)) {
|
||||||
|
_speakingAnimation.stop();
|
||||||
|
}
|
||||||
|
for (auto &userpic : _list) {
|
||||||
|
if (const auto blobs = userpic.blobsAnimation.get()) {
|
||||||
|
blobs->blobs.updateLevel(now - blobs->lastTime);
|
||||||
|
blobs->lastTime = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (const auto onstack = _repaint) {
|
||||||
|
onstack();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
rpl::combine(
|
||||||
|
rpl::single(anim::Disabled()) | rpl::then(anim::Disables()),
|
||||||
|
std::move(hideBlobs)
|
||||||
|
) | rpl::start_with_next([=](bool animDisabled, bool deactivated) {
|
||||||
|
const auto hide = animDisabled || deactivated;
|
||||||
|
|
||||||
|
if (!(hide && _speakingAnimationHideLastTime)) {
|
||||||
|
_speakingAnimationHideLastTime = hide ? crl::now() : 0;
|
||||||
|
}
|
||||||
|
_skipLevelUpdate = hide;
|
||||||
|
for (auto &userpic : _list) {
|
||||||
|
if (const auto blobs = userpic.blobsAnimation.get()) {
|
||||||
|
blobs->blobs.setLevel(0.);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hide && !_speakingAnimation.animating()) {
|
||||||
|
_speakingAnimation.start();
|
||||||
|
}
|
||||||
|
_skipLevelUpdate = hide;
|
||||||
|
}, lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupCallUserpics::~GroupCallUserpics() = default;
|
||||||
|
|
||||||
|
void GroupCallUserpics::paint(Painter &p, int x, int y, int size) {
|
||||||
|
const auto factor = style::DevicePixelRatio();
|
||||||
|
const auto &minScale = kUserpicMinScale;
|
||||||
|
for (auto &userpic : ranges::view::reverse(_list)) {
|
||||||
|
const auto shown = userpic.shownAnimation.value(
|
||||||
|
userpic.hiding ? 0. : 1.);
|
||||||
|
if (shown == 0.) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
validateCache(userpic);
|
||||||
|
p.setOpacity(shown);
|
||||||
|
const auto left = x + userpic.leftAnimation.value(userpic.left);
|
||||||
|
const auto blobs = userpic.blobsAnimation.get();
|
||||||
|
const auto shownScale = 0.5 + shown / 2.;
|
||||||
|
const auto scale = shownScale * (!blobs
|
||||||
|
? 1.
|
||||||
|
: (minScale
|
||||||
|
+ (1. - minScale) * (_speakingAnimationHideLastTime
|
||||||
|
? (1. - blobs->blobs.currentLevel())
|
||||||
|
: blobs->blobs.currentLevel())));
|
||||||
|
if (blobs) {
|
||||||
|
auto hq = PainterHighQualityEnabler(p);
|
||||||
|
|
||||||
|
const auto shift = QPointF(left + size / 2., y + size / 2.);
|
||||||
|
p.translate(shift);
|
||||||
|
blobs->blobs.paint(p, st::windowActiveTextFg);
|
||||||
|
p.translate(-shift);
|
||||||
|
p.setOpacity(1.);
|
||||||
|
}
|
||||||
|
if (std::abs(scale - 1.) < 0.001) {
|
||||||
|
const auto skip = ((kWideScale - 1) / 2) * size * factor;
|
||||||
|
p.drawImage(
|
||||||
|
QRect(left, y, size, size),
|
||||||
|
userpic.cache,
|
||||||
|
QRect(skip, skip, size * factor, size * factor));
|
||||||
|
} else {
|
||||||
|
auto hq = PainterHighQualityEnabler(p);
|
||||||
|
|
||||||
|
auto target = QRect(
|
||||||
|
left + (1 - kWideScale) / 2 * size,
|
||||||
|
y + (1 - kWideScale) / 2 * size,
|
||||||
|
kWideScale * size,
|
||||||
|
kWideScale * size);
|
||||||
|
auto shrink = anim::interpolate(
|
||||||
|
(1 - kWideScale) / 2 * size,
|
||||||
|
0,
|
||||||
|
scale);
|
||||||
|
auto margins = QMargins(shrink, shrink, shrink, shrink);
|
||||||
|
p.drawImage(target.marginsAdded(margins), userpic.cache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.setOpacity(1.);
|
||||||
|
|
||||||
|
const auto hidden = [](const Userpic &userpic) {
|
||||||
|
return userpic.hiding && !userpic.shownAnimation.animating();
|
||||||
|
};
|
||||||
|
_list.erase(ranges::remove_if(_list, hidden), end(_list));
|
||||||
|
}
|
||||||
|
|
||||||
|
int GroupCallUserpics::maxWidth() const {
|
||||||
|
return _maxWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<int> GroupCallUserpics::widthValue() const {
|
||||||
|
return _width.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GroupCallUserpics::needCacheRefresh(Userpic &userpic) {
|
||||||
|
if (userpic.cache.isNull()) {
|
||||||
|
return true;
|
||||||
|
} else if (userpic.hiding) {
|
||||||
|
return false;
|
||||||
|
} else if (userpic.cacheKey != userpic.data.userpicKey) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const auto shouldBeMasked = !userpic.topMost;
|
||||||
|
if (userpic.cacheMasked == shouldBeMasked || !shouldBeMasked) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return !userpic.leftAnimation.animating();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupCallUserpics::ensureBlobsAnimation(Userpic &userpic) {
|
||||||
|
if (userpic.blobsAnimation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
userpic.blobsAnimation = std::make_unique<BlobsAnimation>(
|
||||||
|
Blobs() | ranges::to_vector,
|
||||||
|
kLevelDuration,
|
||||||
|
kMaxLevel);
|
||||||
|
userpic.blobsAnimation->lastTime = crl::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupCallUserpics::sendRandomLevels() {
|
||||||
|
if (_skipLevelUpdate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (auto &userpic : _list) {
|
||||||
|
if (const auto blobs = userpic.blobsAnimation.get()) {
|
||||||
|
const auto value = 30 + (openssl::RandomValue<uint32>() % 70);
|
||||||
|
userpic.blobsAnimation->blobs.setLevel(float64(value) / 100.);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupCallUserpics::validateCache(Userpic &userpic) {
|
||||||
|
if (!needCacheRefresh(userpic)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto factor = style::DevicePixelRatio();
|
||||||
|
const auto size = _st.size;
|
||||||
|
const auto shift = _st.shift;
|
||||||
|
const auto full = QSize(size, size) * kWideScale * factor;
|
||||||
|
if (userpic.cache.isNull()) {
|
||||||
|
userpic.cache = QImage(full, QImage::Format_ARGB32_Premultiplied);
|
||||||
|
userpic.cache.setDevicePixelRatio(factor);
|
||||||
|
}
|
||||||
|
userpic.cacheKey = userpic.data.userpicKey;
|
||||||
|
userpic.cacheMasked = !userpic.topMost;
|
||||||
|
userpic.cache.fill(Qt::transparent);
|
||||||
|
{
|
||||||
|
Painter p(&userpic.cache);
|
||||||
|
const auto skip = (kWideScale - 1) / 2 * size;
|
||||||
|
p.drawImage(skip, skip, userpic.data.userpic);
|
||||||
|
|
||||||
|
if (userpic.cacheMasked) {
|
||||||
|
auto hq = PainterHighQualityEnabler(p);
|
||||||
|
auto pen = QPen(Qt::transparent);
|
||||||
|
pen.setWidth(_st.stroke);
|
||||||
|
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||||
|
p.setBrush(Qt::transparent);
|
||||||
|
p.setPen(pen);
|
||||||
|
p.drawEllipse(skip - size + shift, skip, size, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupCallUserpics::update(
|
||||||
|
const std::vector<GroupCallUser> &users,
|
||||||
|
bool visible) {
|
||||||
|
const auto idFromUserpic = [](const Userpic &userpic) {
|
||||||
|
return userpic.data.id;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use "topMost" as "willBeHidden" flag.
|
||||||
|
for (auto &userpic : _list) {
|
||||||
|
userpic.topMost = true;
|
||||||
|
}
|
||||||
|
for (const auto &user : users) {
|
||||||
|
const auto i = ranges::find(_list, user.id, idFromUserpic);
|
||||||
|
if (i == end(_list)) {
|
||||||
|
_list.push_back(Userpic{ user });
|
||||||
|
toggle(_list.back(), true);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
i->topMost = false;
|
||||||
|
|
||||||
|
if (i->hiding) {
|
||||||
|
toggle(*i, true);
|
||||||
|
}
|
||||||
|
i->data = user;
|
||||||
|
|
||||||
|
// Put this one after the last we are not hiding.
|
||||||
|
for (auto j = end(_list) - 1; j != i; --j) {
|
||||||
|
if (!j->topMost) {
|
||||||
|
ranges::rotate(i, i + 1, j + 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide the ones that "willBeHidden" (currently having "topMost" flag).
|
||||||
|
// Set correct real values of "topMost" flag.
|
||||||
|
const auto userpicsBegin = begin(_list);
|
||||||
|
const auto userpicsEnd = end(_list);
|
||||||
|
auto markedTopMost = userpicsEnd;
|
||||||
|
auto hasBlobs = false;
|
||||||
|
for (auto i = userpicsBegin; i != userpicsEnd; ++i) {
|
||||||
|
auto &userpic = *i;
|
||||||
|
if (userpic.data.speaking) {
|
||||||
|
ensureBlobsAnimation(userpic);
|
||||||
|
hasBlobs = true;
|
||||||
|
} else {
|
||||||
|
userpic.blobsAnimation = nullptr;
|
||||||
|
}
|
||||||
|
if (userpic.topMost) {
|
||||||
|
toggle(userpic, false);
|
||||||
|
userpic.topMost = false;
|
||||||
|
} else if (markedTopMost == userpicsEnd) {
|
||||||
|
userpic.topMost = true;
|
||||||
|
markedTopMost = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (markedTopMost != userpicsEnd && markedTopMost != userpicsBegin) {
|
||||||
|
// Bring the topMost userpic to the very beginning, above all hiding.
|
||||||
|
std::rotate(userpicsBegin, markedTopMost, markedTopMost + 1);
|
||||||
|
}
|
||||||
|
updatePositions();
|
||||||
|
|
||||||
|
if (!hasBlobs) {
|
||||||
|
_randomSpeakingTimer.cancel();
|
||||||
|
_speakingAnimation.stop();
|
||||||
|
} else if (!_randomSpeakingTimer.isActive()) {
|
||||||
|
_randomSpeakingTimer.callEach(kSendRandomLevelInterval);
|
||||||
|
_speakingAnimation.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!visible) {
|
||||||
|
for (auto &userpic : _list) {
|
||||||
|
userpic.shownAnimation.stop();
|
||||||
|
userpic.leftAnimation.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recountAndRepaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupCallUserpics::toggle(Userpic &userpic, bool shown) {
|
||||||
|
userpic.hiding = !shown;
|
||||||
|
userpic.shownAnimation.start(
|
||||||
|
[=] { recountAndRepaint(); },
|
||||||
|
shown ? 0. : 1.,
|
||||||
|
shown ? 1. : 0.,
|
||||||
|
kDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupCallUserpics::updatePositions() {
|
||||||
|
const auto shownCount = ranges::count(_list, false, &Userpic::hiding);
|
||||||
|
if (!shownCount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto single = _st.size;
|
||||||
|
const auto shift = _st.shift;
|
||||||
|
// + 1 * single for the blobs.
|
||||||
|
const auto fullWidth = single + (shownCount - 1) * (single - shift);
|
||||||
|
auto left = (_st.align & Qt::AlignLeft)
|
||||||
|
? 0
|
||||||
|
: (_st.align & Qt::AlignHCenter)
|
||||||
|
? (-fullWidth / 2)
|
||||||
|
: -fullWidth;
|
||||||
|
for (auto &userpic : _list) {
|
||||||
|
if (userpic.hiding) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!userpic.positionInited) {
|
||||||
|
userpic.positionInited = true;
|
||||||
|
userpic.left = left;
|
||||||
|
} else if (userpic.left != left) {
|
||||||
|
userpic.leftAnimation.start(
|
||||||
|
_repaint,
|
||||||
|
userpic.left,
|
||||||
|
left,
|
||||||
|
kDuration);
|
||||||
|
userpic.left = left;
|
||||||
|
}
|
||||||
|
left += (single - shift);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupCallUserpics::recountAndRepaint() {
|
||||||
|
auto width = 0;
|
||||||
|
auto maxShown = 0.;
|
||||||
|
for (const auto &userpic : _list) {
|
||||||
|
const auto shown = userpic.shownAnimation.value(
|
||||||
|
userpic.hiding ? 0. : 1.);
|
||||||
|
if (shown > maxShown) {
|
||||||
|
maxShown = shown;
|
||||||
|
}
|
||||||
|
width += anim::interpolate(0, _st.size - _st.shift, shown);
|
||||||
|
}
|
||||||
|
_width = width + anim::interpolate(0, _st.shift, maxShown);
|
||||||
|
if (_repaint) {
|
||||||
|
_repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Ui
|
73
Telegram/SourceFiles/ui/chat/group_call_userpics.h
Normal file
73
Telegram/SourceFiles/ui/chat/group_call_userpics.h
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "base/timer.h"
|
||||||
|
|
||||||
|
namespace style {
|
||||||
|
struct GroupCallUserpics;
|
||||||
|
} // namespace style
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
|
||||||
|
struct GroupCallUser {
|
||||||
|
QImage userpic;
|
||||||
|
std::pair<uint64, uint64> userpicKey = {};
|
||||||
|
int32 id = 0;
|
||||||
|
bool speaking = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GroupCallUserpics final {
|
||||||
|
public:
|
||||||
|
GroupCallUserpics(
|
||||||
|
const style::GroupCallUserpics &st,
|
||||||
|
rpl::producer<bool> &&hideBlobs,
|
||||||
|
Fn<void()> repaint);
|
||||||
|
~GroupCallUserpics();
|
||||||
|
|
||||||
|
void update(
|
||||||
|
const std::vector<GroupCallUser> &users,
|
||||||
|
bool visible);
|
||||||
|
void paint(Painter &p, int x, int y, int size);
|
||||||
|
|
||||||
|
[[nodiscard]] int maxWidth() const;
|
||||||
|
[[nodiscard]] rpl::producer<int> widthValue() const;
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||||
|
return _lifetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
using User = GroupCallUser;
|
||||||
|
struct BlobsAnimation;
|
||||||
|
struct Userpic;
|
||||||
|
|
||||||
|
void toggle(Userpic &userpic, bool shown);
|
||||||
|
void updatePositions();
|
||||||
|
void validateCache(Userpic &userpic);
|
||||||
|
[[nodiscard]] bool needCacheRefresh(Userpic &userpic);
|
||||||
|
void ensureBlobsAnimation(Userpic &userpic);
|
||||||
|
void sendRandomLevels();
|
||||||
|
void recountAndRepaint();
|
||||||
|
|
||||||
|
const style::GroupCallUserpics &_st;
|
||||||
|
std::vector<Userpic> _list;
|
||||||
|
base::Timer _randomSpeakingTimer;
|
||||||
|
Fn<void()> _repaint;
|
||||||
|
Ui::Animations::Basic _speakingAnimation;
|
||||||
|
int _maxWidth = 0;
|
||||||
|
bool _skipLevelUpdate = false;
|
||||||
|
crl::time _speakingAnimationHideLastTime = 0;
|
||||||
|
|
||||||
|
rpl::variable<int> _width;
|
||||||
|
|
||||||
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Ui
|
|
@ -80,6 +80,8 @@ PRIVATE
|
||||||
ui/chat/attach/attach_single_media_preview.h
|
ui/chat/attach/attach_single_media_preview.h
|
||||||
ui/chat/group_call_bar.cpp
|
ui/chat/group_call_bar.cpp
|
||||||
ui/chat/group_call_bar.h
|
ui/chat/group_call_bar.h
|
||||||
|
ui/chat/group_call_userpics.cpp
|
||||||
|
ui/chat/group_call_userpics.h
|
||||||
ui/chat/message_bar.cpp
|
ui/chat/message_bar.cpp
|
||||||
ui/chat/message_bar.h
|
ui/chat/message_bar.h
|
||||||
ui/chat/pinned_bar.cpp
|
ui/chat/pinned_bar.cpp
|
||||||
|
|
Loading…
Add table
Reference in a new issue