mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Add recent members userpics to group call bar.
This commit is contained in:
parent
058199aa0d
commit
e3a73378e7
10 changed files with 348 additions and 43 deletions
|
@ -1840,6 +1840,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_group_call_invite_done_user" = "You invited {user} to the voice chat.";
|
"lng_group_call_invite_done_user" = "You invited {user} to the voice chat.";
|
||||||
"lng_group_call_invite_done_many#one" = "You invited **{count} member** to the voice chat.";
|
"lng_group_call_invite_done_many#one" = "You invited **{count} member** to the voice chat.";
|
||||||
"lng_group_call_invite_done_many#other" = "You invited **{count} members** to the voice chat.";
|
"lng_group_call_invite_done_many#other" = "You invited **{count} members** to the voice chat.";
|
||||||
|
"lng_group_call_no_members" = "No members";
|
||||||
|
"lng_group_call_members#one" = "{count} member";
|
||||||
|
"lng_group_call_members#other" = "{count} members";
|
||||||
|
|
||||||
"lng_no_mic_permission" = "Telegram needs access to your microphone so that you can make calls and record voice messages.";
|
"lng_no_mic_permission" = "Telegram needs access to your microphone so that you can make calls and record voice messages.";
|
||||||
|
|
||||||
|
|
|
@ -496,3 +496,7 @@ groupCallSettings: CallButton(callMicrophoneMute) {
|
||||||
groupCallButtonSkip: 43px;
|
groupCallButtonSkip: 43px;
|
||||||
groupCallButtonBottomSkip: 134px;
|
groupCallButtonBottomSkip: 134px;
|
||||||
groupCallMuteBottomSkip: 149px;
|
groupCallMuteBottomSkip: 149px;
|
||||||
|
|
||||||
|
groupCallTopBarUserpicSize: 28px;
|
||||||
|
groupCallTopBarUserpicShift: 8px;
|
||||||
|
groupCallTopBarUserpicStroke: 2px;
|
||||||
|
|
|
@ -8,20 +8,275 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/view/history_view_group_call_tracker.h"
|
#include "history/view/history_view_group_call_tracker.h"
|
||||||
|
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
|
#include "data/data_user.h"
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
#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/painter.h"
|
||||||
#include "calls/calls_group_call.h"
|
#include "calls/calls_group_call.h"
|
||||||
#include "calls/calls_instance.h"
|
#include "calls/calls_instance.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
|
#include "styles/style_chat.h"
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
|
||||||
|
void GenerateUserpicsInRow(
|
||||||
|
QImage &result,
|
||||||
|
const std::vector<UserpicInRow> &list,
|
||||||
|
const UserpicsInRowStyle &st,
|
||||||
|
int maxElements) {
|
||||||
|
const auto count = int(list.size());
|
||||||
|
if (!count) {
|
||||||
|
result = QImage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto limit = std::max(count, maxElements);
|
||||||
|
const auto single = st.size;
|
||||||
|
const auto shift = st.shift;
|
||||||
|
const auto width = single + (limit - 1) * (single - shift);
|
||||||
|
if (result.width() != width * cIntRetinaFactor()) {
|
||||||
|
result = QImage(
|
||||||
|
QSize(width, single) * cIntRetinaFactor(),
|
||||||
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
|
}
|
||||||
|
result.fill(Qt::transparent);
|
||||||
|
result.setDevicePixelRatio(cRetinaFactor());
|
||||||
|
|
||||||
|
auto q = Painter(&result);
|
||||||
|
auto hq = PainterHighQualityEnabler(q);
|
||||||
|
auto pen = QPen(Qt::transparent);
|
||||||
|
pen.setWidth(st.stroke);
|
||||||
|
auto x = (count - 1) * (single - shift);
|
||||||
|
for (auto i = count; i != 0;) {
|
||||||
|
auto &entry = list[--i];
|
||||||
|
q.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||||
|
entry.peer->paintUserpic(q, entry.view, x, 0, single);
|
||||||
|
entry.uniqueKey = entry.peer->userpicUniqueKey(entry.view);
|
||||||
|
q.setCompositionMode(QPainter::CompositionMode_Source);
|
||||||
|
q.setBrush(Qt::NoBrush);
|
||||||
|
q.setPen(pen);
|
||||||
|
q.drawEllipse(x, 0, single, single);
|
||||||
|
x -= single - shift;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
GroupCallTracker::GroupCallTracker(not_null<ChannelData*> channel)
|
GroupCallTracker::GroupCallTracker(not_null<ChannelData*> channel)
|
||||||
: _channel(channel) {
|
: _channel(channel) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
||||||
|
not_null<Data::GroupCall*> call,
|
||||||
|
const UserpicsInRowStyle &st) {
|
||||||
|
struct State {
|
||||||
|
std::vector<UserpicInRow> userpics;
|
||||||
|
Ui::GroupCallBarContent current;
|
||||||
|
base::has_weak_ptr guard;
|
||||||
|
bool someUserpicsNotLoaded = false;
|
||||||
|
bool scheduled = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// speaking DESC, std::max(date, lastActive) DESC
|
||||||
|
static const auto SortKey = [](const Data::GroupCall::Participant &p) {
|
||||||
|
const auto result = (p.speaking ? uint64(0x100000000ULL) : uint64(0))
|
||||||
|
| uint64(std::max(p.lastActive, p.date));
|
||||||
|
return (~uint64(0)) - result; // sorting with less(), so invert.
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr auto kLimit = 3;
|
||||||
|
static const auto FillMissingUserpics = [](
|
||||||
|
not_null<State*> state,
|
||||||
|
not_null<Data::GroupCall*> call) {
|
||||||
|
const auto already = int(state->userpics.size());
|
||||||
|
const auto &participants = call->participants();
|
||||||
|
if (already >= kLimit || participants.size() <= already) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::array<const Data::GroupCall::Participant*, kLimit> adding{
|
||||||
|
{ nullptr }
|
||||||
|
};
|
||||||
|
for (const auto &participant : call->participants()) {
|
||||||
|
const auto alreadyInList = ranges::contains(
|
||||||
|
state->userpics,
|
||||||
|
participant.user,
|
||||||
|
&UserpicInRow::peer);
|
||||||
|
if (alreadyInList) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (auto i = 0; i != kLimit; ++i) {
|
||||||
|
if (!adding[i]) {
|
||||||
|
adding[i] = &participant;
|
||||||
|
break;
|
||||||
|
} else if (SortKey(participant) < SortKey(*adding[i])) {
|
||||||
|
for (auto j = kLimit - 1; j != i; --j) {
|
||||||
|
adding[j] = adding[j - 1];
|
||||||
|
}
|
||||||
|
adding[i] = &participant;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto i = 0; i != kLimit - already; ++i) {
|
||||||
|
if (adding[i]) {
|
||||||
|
state->userpics.push_back(UserpicInRow{ adding[i]->user });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const auto RegenerateUserpics = [](
|
||||||
|
not_null<State*> state,
|
||||||
|
not_null<Data::GroupCall*> call,
|
||||||
|
const UserpicsInRowStyle &st,
|
||||||
|
bool force = false) {
|
||||||
|
const auto result = FillMissingUserpics(state, call) || force;
|
||||||
|
if (!result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
GenerateUserpicsInRow(
|
||||||
|
state->current.userpics,
|
||||||
|
state->userpics,
|
||||||
|
st);
|
||||||
|
state->someUserpicsNotLoaded = false;
|
||||||
|
for (const auto &userpic : state->userpics) {
|
||||||
|
if (userpic.peer->hasUserpic()
|
||||||
|
&& userpic.peer->useEmptyUserpic(userpic.view)) {
|
||||||
|
state->someUserpicsNotLoaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const auto RemoveUserpic = [](
|
||||||
|
not_null<State*> state,
|
||||||
|
not_null<Data::GroupCall*> call,
|
||||||
|
not_null<UserData*> user,
|
||||||
|
const UserpicsInRowStyle &st) {
|
||||||
|
const auto i = ranges::find(
|
||||||
|
state->userpics,
|
||||||
|
user,
|
||||||
|
&UserpicInRow::peer);
|
||||||
|
if (i == state->userpics.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
state->userpics.erase(i);
|
||||||
|
RegenerateUserpics(state, call, st, true);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const auto CheckPushToFront = [](
|
||||||
|
not_null<State*> state,
|
||||||
|
not_null<Data::GroupCall*> call,
|
||||||
|
not_null<UserData*> user,
|
||||||
|
const UserpicsInRowStyle &st) {
|
||||||
|
Expects(state->userpics.size() <= kLimit);
|
||||||
|
|
||||||
|
const auto &participants = call->participants();
|
||||||
|
auto i = state->userpics.begin();
|
||||||
|
|
||||||
|
// Find where to put a new speaking userpic.
|
||||||
|
for (; i != state->userpics.end(); ++i) {
|
||||||
|
if (i->peer == user) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto j = ranges::find(
|
||||||
|
participants,
|
||||||
|
not_null{ static_cast<UserData*>(i->peer.get()) },
|
||||||
|
&Data::GroupCall::Participant::user);
|
||||||
|
if (j == end(participants) || !j->speaking) {
|
||||||
|
// Found a non-speaking one, put the new speaking one here.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i - state->userpics.begin() >= kLimit) {
|
||||||
|
// Full kLimit of speaking userpics already.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto added = state->userpics.insert(i, UserpicInRow{ user });
|
||||||
|
if (state->userpics.size() > kLimit) {
|
||||||
|
// Find last non-speaking userpic to remove. It must be there.
|
||||||
|
for (auto i = state->userpics.end() - 1; i != added; --i) {
|
||||||
|
const auto j = ranges::find(
|
||||||
|
participants,
|
||||||
|
not_null{ static_cast<UserData*>(i->peer.get()) },
|
||||||
|
&Data::GroupCall::Participant::user);
|
||||||
|
if (j == end(participants) || !j->speaking) {
|
||||||
|
// Found a non-speaking one, remove.
|
||||||
|
state->userpics.erase(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assert(state->userpics.size() <= kLimit);
|
||||||
|
}
|
||||||
|
RegenerateUserpics(state, call, st, true);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
return [=](auto consumer) {
|
||||||
|
auto lifetime = rpl::lifetime();
|
||||||
|
auto state = lifetime.make_state<State>();
|
||||||
|
state->current.shown = true;
|
||||||
|
|
||||||
|
const auto pushNext = [=] {
|
||||||
|
if (state->scheduled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state->scheduled = true;
|
||||||
|
crl::on_main(&state->guard, [=] {
|
||||||
|
state->scheduled = false;
|
||||||
|
consumer.put_next_copy(state->current);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
using ParticipantUpdate = Data::GroupCall::ParticipantUpdate;
|
||||||
|
call->participantUpdated(
|
||||||
|
) | rpl::start_with_next([=](const ParticipantUpdate &update) {
|
||||||
|
const auto user = update.now ? update.now->user : update.was->user;
|
||||||
|
if (!update.now) {
|
||||||
|
if (RemoveUserpic(state, call, user, st)) {
|
||||||
|
pushNext();
|
||||||
|
}
|
||||||
|
} else if (update.now->speaking
|
||||||
|
&& (!update.was || !update.was->speaking)) {
|
||||||
|
if (CheckPushToFront(state, call, user, st)) {
|
||||||
|
pushNext();
|
||||||
|
}
|
||||||
|
} else if (RegenerateUserpics(state, call, st)) {
|
||||||
|
pushNext();
|
||||||
|
}
|
||||||
|
}, lifetime);
|
||||||
|
|
||||||
|
call->participantsSliceAdded(
|
||||||
|
) | rpl::filter([=] {
|
||||||
|
return RegenerateUserpics(state, call, st);
|
||||||
|
}) | rpl::start_with_next(pushNext, lifetime);
|
||||||
|
|
||||||
|
call->channel()->session().downloaderTaskFinished(
|
||||||
|
) | rpl::filter([=] {
|
||||||
|
return state->someUserpicsNotLoaded;
|
||||||
|
}) | rpl::start_with_next([=] {
|
||||||
|
for (const auto &userpic : state->userpics) {
|
||||||
|
if (userpic.peer->userpicUniqueKey(userpic.view)
|
||||||
|
!= userpic.uniqueKey) {
|
||||||
|
RegenerateUserpics(state, call, st, true);
|
||||||
|
pushNext();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, lifetime);
|
||||||
|
|
||||||
|
RegenerateUserpics(state, call, st);
|
||||||
|
|
||||||
|
call->fullCountValue(
|
||||||
|
) | rpl::start_with_next([=](int count) {
|
||||||
|
state->current.count = count;
|
||||||
|
consumer.put_next_copy(state->current);
|
||||||
|
}, lifetime);
|
||||||
|
|
||||||
|
return lifetime;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::content() const {
|
rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::content() const {
|
||||||
const auto channel = _channel;
|
const auto channel = _channel;
|
||||||
return rpl::combine(
|
return rpl::combine(
|
||||||
|
@ -29,16 +284,26 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::content() const {
|
||||||
channel,
|
channel,
|
||||||
Data::PeerUpdate::Flag::GroupCall),
|
Data::PeerUpdate::Flag::GroupCall),
|
||||||
Core::App().calls().currentGroupCallValue()
|
Core::App().calls().currentGroupCallValue()
|
||||||
) | rpl::map([=](const auto&, Calls::GroupCall *current)
|
) | rpl::map([=](const auto&, Calls::GroupCall *current) {
|
||||||
-> Ui::GroupCallBarContent {
|
|
||||||
const auto call = channel->call();
|
const auto call = channel->call();
|
||||||
if (!call || (current && current->channel() == channel)) {
|
return (call && (!current || current->channel() != channel))
|
||||||
return { .shown = false };
|
? call
|
||||||
|
: nullptr;
|
||||||
|
}) | rpl::distinct_until_changed(
|
||||||
|
) | rpl::map([](Data::GroupCall *call)
|
||||||
|
-> rpl::producer<Ui::GroupCallBarContent> {
|
||||||
|
if (!call) {
|
||||||
|
return rpl::single(Ui::GroupCallBarContent{ .shown = false });
|
||||||
} else if (!call->fullCount() && !call->participantsLoaded()) {
|
} else if (!call->fullCount() && !call->participantsLoaded()) {
|
||||||
call->reload();
|
call->reload();
|
||||||
}
|
}
|
||||||
return { .count = call->fullCount(), .shown = true };
|
const auto st = UserpicsInRowStyle{
|
||||||
});
|
.size = st::historyGroupCallUserpicSize,
|
||||||
|
.shift = st::historyGroupCallUserpicShift,
|
||||||
|
.stroke = st::historyGroupCallUserpicStroke,
|
||||||
|
};
|
||||||
|
return ContentByCall(call, st);
|
||||||
|
}) | rpl::flatten_latest();
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<> GroupCallTracker::joinClicks() const {
|
rpl::producer<> GroupCallTracker::joinClicks() const {
|
||||||
|
|
|
@ -13,8 +13,31 @@ namespace Ui {
|
||||||
struct GroupCallBarContent;
|
struct GroupCallBarContent;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
class GroupCall;
|
||||||
|
class CloudImageView;
|
||||||
|
} // namespace Data
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
|
||||||
|
struct UserpicInRow {
|
||||||
|
not_null<PeerData*> peer;
|
||||||
|
mutable std::shared_ptr<Data::CloudImageView> view;
|
||||||
|
mutable InMemoryKey uniqueKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UserpicsInRowStyle {
|
||||||
|
int size = 0;
|
||||||
|
int shift = 0;
|
||||||
|
int stroke = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
void GenerateUserpicsInRow(
|
||||||
|
QImage &result,
|
||||||
|
const std::vector<UserpicInRow> &list,
|
||||||
|
const UserpicsInRowStyle &st,
|
||||||
|
int maxElements = 0);
|
||||||
|
|
||||||
class GroupCallTracker final {
|
class GroupCallTracker final {
|
||||||
public:
|
public:
|
||||||
GroupCallTracker(not_null<ChannelData*> channel);
|
GroupCallTracker(not_null<ChannelData*> channel);
|
||||||
|
@ -22,6 +45,10 @@ public:
|
||||||
[[nodiscard]] rpl::producer<Ui::GroupCallBarContent> content() const;
|
[[nodiscard]] rpl::producer<Ui::GroupCallBarContent> content() const;
|
||||||
[[nodiscard]] rpl::producer<> joinClicks() const;
|
[[nodiscard]] rpl::producer<> joinClicks() const;
|
||||||
|
|
||||||
|
[[nodiscard]] static rpl::producer<Ui::GroupCallBarContent> ContentByCall(
|
||||||
|
not_null<Data::GroupCall*> call,
|
||||||
|
const UserpicsInRowStyle &st);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
not_null<ChannelData*> _channel;
|
not_null<ChannelData*> _channel;
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history_message.h"
|
#include "history/history_message.h"
|
||||||
#include "history/view/media/history_view_media.h"
|
#include "history/view/media/history_view_media.h"
|
||||||
#include "history/view/media/history_view_web_page.h"
|
#include "history/view/media/history_view_web_page.h"
|
||||||
|
#include "history/view/history_view_group_call_tracker.h" // UserpicInRow.
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "ui/effects/ripple_animation.h"
|
#include "ui/effects/ripple_animation.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
|
@ -266,13 +267,8 @@ style::color FromNameFg(PeerId peerId, bool selected) {
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
struct Message::CommentsButton {
|
struct Message::CommentsButton {
|
||||||
struct Userpic {
|
|
||||||
not_null<PeerData*> peer;
|
|
||||||
std::shared_ptr<Data::CloudImageView> view;
|
|
||||||
InMemoryKey uniqueKey;
|
|
||||||
};
|
|
||||||
std::unique_ptr<Ui::RippleAnimation> ripple;
|
std::unique_ptr<Ui::RippleAnimation> ripple;
|
||||||
std::vector<Userpic> userpics;
|
std::vector<UserpicInRow> userpics;
|
||||||
QImage cachedUserpics;
|
QImage cachedUserpics;
|
||||||
ClickHandlerPtr link;
|
ClickHandlerPtr link;
|
||||||
QPoint lastPoint;
|
QPoint lastPoint;
|
||||||
|
@ -822,7 +818,7 @@ void Message::paintCommentsButton(
|
||||||
for (auto i = 0; i != count; ++i) {
|
for (auto i = 0; i != count; ++i) {
|
||||||
const auto peerId = views->recentRepliers[i];
|
const auto peerId = views->recentRepliers[i];
|
||||||
if (i == list.size()) {
|
if (i == list.size()) {
|
||||||
list.push_back(CommentsButton::Userpic{
|
list.push_back(UserpicInRow{
|
||||||
history()->owner().peer(peerId)
|
history()->owner().peer(peerId)
|
||||||
});
|
});
|
||||||
} else if (list[i].peer->id != peerId) {
|
} else if (list[i].peer->id != peerId) {
|
||||||
|
@ -832,31 +828,12 @@ void Message::paintCommentsButton(
|
||||||
while (list.size() > count) {
|
while (list.size() > count) {
|
||||||
list.pop_back();
|
list.pop_back();
|
||||||
}
|
}
|
||||||
const auto width = single + (limit - 1) * (single - shift);
|
const auto st = UserpicsInRowStyle{
|
||||||
if (_comments->cachedUserpics.isNull()) {
|
.size = single,
|
||||||
_comments->cachedUserpics = QImage(
|
.shift = shift,
|
||||||
QSize(width, single) * cIntRetinaFactor(),
|
.stroke = st::historyCommentsUserpicStroke,
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
};
|
||||||
}
|
GenerateUserpicsInRow(_comments->cachedUserpics, list, st, limit);
|
||||||
_comments->cachedUserpics.fill(Qt::transparent);
|
|
||||||
_comments->cachedUserpics.setDevicePixelRatio(cRetinaFactor());
|
|
||||||
|
|
||||||
auto q = Painter(&_comments->cachedUserpics);
|
|
||||||
auto hq = PainterHighQualityEnabler(q);
|
|
||||||
auto pen = QPen(Qt::transparent);
|
|
||||||
pen.setWidth(st::historyCommentsUserpicStroke);
|
|
||||||
auto x = (count - 1) * (single - shift);
|
|
||||||
for (auto i = count; i != 0;) {
|
|
||||||
auto &entry = list[--i];
|
|
||||||
q.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
|
||||||
entry.peer->paintUserpic(q, entry.view, x, 0, single);
|
|
||||||
entry.uniqueKey = entry.peer->userpicUniqueKey(entry.view);
|
|
||||||
q.setCompositionMode(QPainter::CompositionMode_Source);
|
|
||||||
q.setBrush(Qt::NoBrush);
|
|
||||||
q.setPen(pen);
|
|
||||||
q.drawEllipse(x, 0, single, single);
|
|
||||||
x -= single - shift;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
p.drawImage(
|
p.drawImage(
|
||||||
left,
|
left,
|
||||||
|
|
|
@ -978,7 +978,10 @@ void MainWidget::setCurrentGroupCall(Calls::GroupCall *call) {
|
||||||
_currentGroupCall->stateValue(
|
_currentGroupCall->stateValue(
|
||||||
) | rpl::start_with_next([=](Calls::GroupCall::State state) {
|
) | rpl::start_with_next([=](Calls::GroupCall::State state) {
|
||||||
using State = Calls::GroupCall::State;
|
using State = Calls::GroupCall::State;
|
||||||
if (state != State::Joined && state != State::Connecting) {
|
if (state != State::Creating
|
||||||
|
&& state != State::Joining
|
||||||
|
&& state != State::Joined
|
||||||
|
&& state != State::Connecting) {
|
||||||
destroyCallTopBar();
|
destroyCallTopBar();
|
||||||
} else if (!_callTopBar) {
|
} else if (!_callTopBar) {
|
||||||
createCallTopBar();
|
createCallTopBar();
|
||||||
|
|
|
@ -797,6 +797,10 @@ historyCommentsOpenOutSelected: icon {{ "history_comments_open", msgFileThumbLin
|
||||||
|
|
||||||
historySlowmodeCounterMargins: margins(0px, 0px, 10px, 0px);
|
historySlowmodeCounterMargins: margins(0px, 0px, 10px, 0px);
|
||||||
|
|
||||||
|
historyGroupCallUserpicSize: 36px;
|
||||||
|
historyGroupCallUserpicShift: 12px;
|
||||||
|
historyGroupCallUserpicStroke: 2px;
|
||||||
|
|
||||||
largeEmojiSize: 36px;
|
largeEmojiSize: 36px;
|
||||||
largeEmojiOutline: 1px;
|
largeEmojiOutline: 1px;
|
||||||
largeEmojiPadding: margins(0px, 0px, 0px, 0px);
|
largeEmojiPadding: margins(0px, 0px, 0px, 0px);
|
||||||
|
|
|
@ -101,9 +101,32 @@ void GroupCallBar::setupInner() {
|
||||||
|
|
||||||
void GroupCallBar::paint(Painter &p) {
|
void GroupCallBar::paint(Painter &p) {
|
||||||
p.fillRect(_inner->rect(), st::historyComposeAreaBg);
|
p.fillRect(_inner->rect(), st::historyComposeAreaBg);
|
||||||
|
|
||||||
|
auto left = st::msgReplyBarSkip;
|
||||||
|
if (!_content.userpics.isNull()) {
|
||||||
|
const auto imageSize = _content.userpics.size()
|
||||||
|
/ _content.userpics.devicePixelRatio();
|
||||||
|
p.drawImage(
|
||||||
|
left,
|
||||||
|
(_inner->height() - imageSize.height()) / 2,
|
||||||
|
_content.userpics);
|
||||||
|
left += imageSize.width() + st::msgReplyBarSkip;
|
||||||
|
}
|
||||||
|
const auto titleTop = st::msgReplyPadding.top();
|
||||||
|
const auto textTop = titleTop + st::msgServiceNameFont->height;
|
||||||
|
const auto width = _inner->width();
|
||||||
p.setPen(st::defaultMessageBar.textFg);
|
p.setPen(st::defaultMessageBar.textFg);
|
||||||
|
p.setFont(st::defaultMessageBar.title.font);
|
||||||
|
p.drawTextLeft(left, titleTop, width, tr::lng_group_call_title(tr::now));
|
||||||
|
p.setPen(st::historyComposeAreaFgService);
|
||||||
p.setFont(st::defaultMessageBar.text.font);
|
p.setFont(st::defaultMessageBar.text.font);
|
||||||
p.drawText(_inner->rect(), tr::lng_group_call_title(tr::now), style::al_center);
|
p.drawTextLeft(
|
||||||
|
left,
|
||||||
|
textTop,
|
||||||
|
width,
|
||||||
|
(_content.count > 0
|
||||||
|
? tr::lng_group_call_members(tr::now, lt_count, _content.count)
|
||||||
|
: tr::lng_group_call_no_members(tr::now)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void GroupCallBar::updateControlsGeometry(QRect wrapGeometry) {
|
void GroupCallBar::updateControlsGeometry(QRect wrapGeometry) {
|
||||||
|
|
|
@ -19,8 +19,7 @@ class PlainShadow;
|
||||||
struct GroupCallBarContent {
|
struct GroupCallBarContent {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
bool shown = false;
|
bool shown = false;
|
||||||
bool joined = false;
|
QImage userpics;
|
||||||
// #TODO calls userpics
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class GroupCallBar final {
|
class GroupCallBar final {
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 707bdc84918eddfd8c08e776d328dab03dc04b25
|
Subproject commit 3562a43685fdac00c277292ce2c83d92132cc319
|
Loading…
Add table
Reference in a new issue