1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "calls/group/calls_group_panel.h"
9
10 #include "calls/group/calls_group_common.h"
11 #include "calls/group/calls_group_members.h"
12 #include "calls/group/calls_group_settings.h"
13 #include "calls/group/calls_group_menu.h"
14 #include "calls/group/calls_group_viewport.h"
15 #include "calls/group/calls_group_toasts.h"
16 #include "calls/group/calls_group_invite_controller.h"
17 #include "calls/group/ui/calls_group_scheduled_labels.h"
18 #include "calls/group/ui/desktop_capture_choose_source.h"
19 #include "ui/platform/ui_platform_window_title.h"
20 #include "ui/platform/ui_platform_utility.h"
21 #include "ui/controls/call_mute_button.h"
22 #include "ui/widgets/buttons.h"
23 #include "ui/widgets/call_button.h"
24 #include "ui/widgets/checkbox.h"
25 #include "ui/widgets/dropdown_menu.h"
26 #include "ui/widgets/input_fields.h"
27 #include "ui/widgets/tooltip.h"
28 #include "ui/widgets/rp_window.h"
29 #include "ui/chat/group_call_bar.h"
30 #include "ui/layers/layer_manager.h"
31 #include "ui/layers/generic_box.h"
32 #include "ui/text/text_utilities.h"
33 #include "ui/toast/toast.h"
34 #include "ui/toasts/common_toasts.h"
35 #include "ui/image/image_prepare.h"
36 #include "ui/round_rect.h"
37 #include "ui/special_buttons.h"
38 #include "info/profile/info_profile_values.h" // Info::Profile::Value.
39 #include "core/application.h"
40 #include "lang/lang_keys.h"
41 #include "data/data_channel.h"
42 #include "data/data_chat.h"
43 #include "data/data_user.h"
44 #include "data/data_group_call.h"
45 #include "data/data_session.h"
46 #include "data/data_changes.h"
47 #include "main/main_session.h"
48 #include "base/event_filter.h"
49 #include "base/unixtime.h"
50 #include "base/qt_signal_producer.h"
51 #include "base/timer_rpl.h"
52 #include "apiwrap.h" // api().kickParticipant.
53 #include "webrtc/webrtc_video_track.h"
54 #include "webrtc/webrtc_media_devices.h" // UniqueDesktopCaptureSource.
55 #include "webrtc/webrtc_audio_input_tester.h"
56 #include "styles/style_calls.h"
57 #include "styles/style_layers.h"
58
59 #include <QtWidgets/QApplication>
60 #include <QtGui/QWindow>
61 #include <QtGui/QScreen>
62
63 namespace Calls::Group {
64 namespace {
65
66 constexpr auto kSpacePushToTalkDelay = crl::time(250);
67 constexpr auto kRecordingAnimationDuration = crl::time(1200);
68 constexpr auto kRecordingOpacity = 0.6;
69 constexpr auto kStartNoConfirmation = TimeId(10);
70 constexpr auto kControlsBackgroundOpacity = 0.8;
71 constexpr auto kOverrideActiveColorBgAlpha = 172;
72
73 } // namespace
74
75 struct Panel::ControlsBackgroundNarrow {
ControlsBackgroundNarrowCalls::Group::Panel::ControlsBackgroundNarrow76 explicit ControlsBackgroundNarrow(not_null<QWidget*> parent)
77 : shadow(parent)
78 , blocker(parent) {
79 }
80
81 Ui::RpWidget shadow;
82 Ui::RpWidget blocker;
83 };
84
Panel(not_null<GroupCall * > call)85 Panel::Panel(not_null<GroupCall*> call)
86 : _call(call)
87 , _peer(call->peer())
88 , _layerBg(std::make_unique<Ui::LayerManager>(widget()))
89 #ifndef Q_OS_MAC
90 , _controls(std::make_unique<Ui::Platform::TitleControls>(
91 widget(),
92 st::groupCallTitle))
93 #endif // !Q_OS_MAC
94 , _viewport(
95 std::make_unique<Viewport>(widget(), PanelMode::Wide, _window.backend()))
96 , _mute(std::make_unique<Ui::CallMuteButton>(
97 widget(),
98 st::callMuteButton,
99 Core::App().appDeactivatedValue(),
100 Ui::CallMuteButtonState{
101 .text = (_call->scheduleDate()
102 ? tr::lng_group_call_start_now(tr::now)
103 : tr::lng_group_call_connecting(tr::now)),
104 .type = (!_call->scheduleDate()
105 ? Ui::CallMuteButtonType::Connecting
106 : _peer->canManageGroupCall()
107 ? Ui::CallMuteButtonType::ScheduledCanStart
108 : _call->scheduleStartSubscribed()
109 ? Ui::CallMuteButtonType::ScheduledNotify
110 : Ui::CallMuteButtonType::ScheduledSilent),
111 }))
112 , _hangup(widget(), st::groupCallHangup)
113 , _stickedTooltipsShown(Core::App().settings().hiddenGroupCallTooltips()
114 & ~StickedTooltip::Microphone) // Always show tooltip about mic.
115 , _toasts(std::make_unique<Toasts>(this)) {
116 _layerBg->setStyleOverrides(&st::groupCallBox, &st::groupCallLayerBox);
117 _layerBg->setHideByBackgroundClick(true);
118
119 _viewport->widget()->hide();
120 if (!_viewport->requireARGB32()) {
121 _call->setNotRequireARGB32();
122 }
123
124 SubscribeToMigration(
125 _peer,
126 lifetime(),
__anon220bdc520202(not_null<ChannelData*> channel) 127 [=](not_null<ChannelData*> channel) { migrate(channel); });
128 setupRealCallViewers();
129
130 initWindow();
131 initWidget();
132 initControls();
133 initLayout();
134 showAndActivate();
135 }
136
~Panel()137 Panel::~Panel() {
138 _menu.destroy();
139 _viewport = nullptr;
140 }
141
setupRealCallViewers()142 void Panel::setupRealCallViewers() {
143 _call->real(
144 ) | rpl::start_with_next([=](not_null<Data::GroupCall*> real) {
145 subscribeToChanges(real);
146 }, lifetime());
147 }
148
call() const149 not_null<GroupCall*> Panel::call() const {
150 return _call;
151 }
152
isActive() const153 bool Panel::isActive() const {
154 return window()->isActiveWindow()
155 && window()->isVisible()
156 && !(window()->windowState() & Qt::WindowMinimized);
157 }
158
showToast(TextWithEntities && text,crl::time duration)159 void Panel::showToast(TextWithEntities &&text, crl::time duration) {
160 if (const auto strong = _lastToast.get()) {
161 strong->hideAnimated();
162 }
163 _lastToast = Ui::ShowMultilineToast({
164 .parentOverride = widget(),
165 .text = std::move(text),
166 .duration = duration,
167 });
168 }
169
minimize()170 void Panel::minimize() {
171 window()->setWindowState(window()->windowState() | Qt::WindowMinimized);
172 }
173
close()174 void Panel::close() {
175 window()->close();
176 }
177
showAndActivate()178 void Panel::showAndActivate() {
179 if (window()->isHidden()) {
180 window()->show();
181 }
182 const auto state = window()->windowState();
183 if (state & Qt::WindowMinimized) {
184 window()->setWindowState(state & ~Qt::WindowMinimized);
185 }
186 window()->raise();
187 window()->activateWindow();
188 window()->setFocus();
189 }
190
migrate(not_null<ChannelData * > channel)191 void Panel::migrate(not_null<ChannelData*> channel) {
192 _peer = channel;
193 _peerLifetime.destroy();
194 subscribeToPeerChanges();
195 _title.destroy();
196 refreshTitle();
197 }
198
subscribeToPeerChanges()199 void Panel::subscribeToPeerChanges() {
200 Info::Profile::NameValue(
201 _peer
202 ) | rpl::start_with_next([=](const TextWithEntities &name) {
203 window()->setTitle(name.text);
204 }, _peerLifetime);
205 }
206
chooseSourceParent()207 QWidget *Panel::chooseSourceParent() {
208 return window().get();
209 }
210
chooseSourceActiveDeviceId()211 QString Panel::chooseSourceActiveDeviceId() {
212 return _call->screenSharingDeviceId();
213 }
214
chooseSourceActiveWithAudio()215 bool Panel::chooseSourceActiveWithAudio() {
216 return _call->screenSharingWithAudio();
217 }
218
chooseSourceWithAudioSupported()219 bool Panel::chooseSourceWithAudioSupported() {
220 #ifdef Q_OS_WIN
221 return true;
222 #else // Q_OS_WIN
223 return false;
224 #endif // Q_OS_WIN
225 }
226
chooseSourceInstanceLifetime()227 rpl::lifetime &Panel::chooseSourceInstanceLifetime() {
228 return lifetime();
229 }
230
chooseSourceAccepted(const QString & deviceId,bool withAudio)231 void Panel::chooseSourceAccepted(
232 const QString &deviceId,
233 bool withAudio) {
234 _call->toggleScreenSharing(deviceId, withAudio);
235 }
236
chooseSourceStop()237 void Panel::chooseSourceStop() {
238 _call->toggleScreenSharing(std::nullopt);
239 }
240
initWindow()241 void Panel::initWindow() {
242 window()->setAttribute(Qt::WA_OpaquePaintEvent);
243 window()->setAttribute(Qt::WA_NoSystemBackground);
244 window()->setTitleStyle(st::groupCallTitle);
245
246 subscribeToPeerChanges();
247
248 base::install_event_filter(window().get(), [=](not_null<QEvent*> e) {
249 if (e->type() == QEvent::Close && handleClose()) {
250 e->ignore();
251 return base::EventFilterResult::Cancel;
252 } else if (e->type() == QEvent::KeyPress
253 || e->type() == QEvent::KeyRelease) {
254 if (static_cast<QKeyEvent*>(e.get())->key() == Qt::Key_Space) {
255 _call->pushToTalk(
256 e->type() == QEvent::KeyPress,
257 kSpacePushToTalkDelay);
258 }
259 }
260 return base::EventFilterResult::Continue;
261 });
262
263 window()->setBodyTitleArea([=](QPoint widgetPoint) {
264 using Flag = Ui::WindowTitleHitTestFlag;
265 const auto titleRect = QRect(
266 0,
267 0,
268 widget()->width(),
269 st::groupCallMembersTop);
270 const auto moveable = (titleRect.contains(widgetPoint)
271 && (!_menuToggle || !_menuToggle->geometry().contains(widgetPoint))
272 && (!_menu || !_menu->geometry().contains(widgetPoint))
273 && (!_recordingMark || !_recordingMark->geometry().contains(widgetPoint))
274 && (!_joinAsToggle || !_joinAsToggle->geometry().contains(widgetPoint)));
275 if (!moveable) {
276 return (Flag::None | Flag(0));
277 }
278 const auto shown = _layerBg->topShownLayer();
279 return (!shown || !shown->geometry().contains(widgetPoint))
280 ? (Flag::Move | Flag::Maximize)
281 : Flag::None;
282 });
283
284 _call->hasVideoWithFramesValue(
285 ) | rpl::start_with_next([=] {
286 updateMode();
287 }, lifetime());
288 }
289
initWidget()290 void Panel::initWidget() {
291 widget()->setMouseTracking(true);
292
293 widget()->paintRequest(
294 ) | rpl::start_with_next([=](QRect clip) {
295 paint(clip);
296 }, lifetime());
297
298 widget()->sizeValue(
299 ) | rpl::skip(1) | rpl::start_with_next([=](QSize size) {
300 if (!updateMode()) {
301 updateControlsGeometry();
302 }
303
304 // title geometry depends on _controls->geometry,
305 // which is not updated here yet.
306 crl::on_main(widget(), [=] { refreshTitle(); });
307 }, lifetime());
308 }
309
endCall()310 void Panel::endCall() {
311 if (!_call->canManage()) {
312 _call->hangup();
313 return;
314 }
315 showBox(Box(
316 LeaveBox,
317 _call,
318 false,
319 BoxContext::GroupCallPanel));
320 }
321
startScheduledNow()322 void Panel::startScheduledNow() {
323 const auto date = _call->scheduleDate();
324 const auto now = base::unixtime::now();
325 if (!date) {
326 return;
327 } else if (now + kStartNoConfirmation >= date) {
328 _call->startScheduledNow();
329 } else {
330 const auto box = std::make_shared<QPointer<Ui::GenericBox>>();
331 const auto done = [=] {
332 if (*box) {
333 (*box)->closeBox();
334 }
335 _call->startScheduledNow();
336 };
337 auto owned = ConfirmBox({
338 .text = { (_call->peer()->isBroadcast()
339 ? tr::lng_group_call_start_now_sure_channel
340 : tr::lng_group_call_start_now_sure)(tr::now) },
341 .button = tr::lng_group_call_start_now(),
342 .callback = done,
343 });
344 *box = owned.data();
345 showBox(std::move(owned));
346 }
347 }
348
initControls()349 void Panel::initControls() {
350 _mute->clicks(
351 ) | rpl::filter([=](Qt::MouseButton button) {
352 return (button == Qt::LeftButton);
353 }) | rpl::start_with_next([=] {
354 if (_call->scheduleDate()) {
355 if (_call->canManage()) {
356 startScheduledNow();
357 } else if (const auto real = _call->lookupReal()) {
358 _call->toggleScheduleStartSubscribed(
359 !real->scheduleStartSubscribed());
360 }
361 return;
362 }
363 const auto oldState = _call->muted();
364 const auto newState = (oldState == MuteState::ForceMuted)
365 ? MuteState::RaisedHand
366 : (oldState == MuteState::RaisedHand)
367 ? MuteState::RaisedHand
368 : (oldState == MuteState::Muted)
369 ? MuteState::Active
370 : MuteState::Muted;
371 _call->setMutedAndUpdate(newState);
372 }, _mute->lifetime());
373
374 initShareAction();
375 refreshLeftButton();
376 refreshVideoButtons();
377
378 rpl::combine(
379 _mode.value(),
380 _call->canManageValue()
381 ) | rpl::start_with_next([=] {
382 refreshTopButton();
383 }, lifetime());
384
385 _hangup->setClickedCallback([=] { endCall(); });
386
387 const auto scheduleDate = _call->scheduleDate();
388 if (scheduleDate) {
389 auto changes = _call->real(
390 ) | rpl::map([=](not_null<Data::GroupCall*> real) {
391 return real->scheduleDateValue();
392 }) | rpl::flatten_latest();
393
394 setupScheduledLabels(rpl::single(
395 scheduleDate
396 ) | rpl::then(rpl::duplicate(changes)));
397
398 auto started = std::move(changes) | rpl::filter([](TimeId date) {
399 return (date == 0);
400 }) | rpl::take(1);
401
402 rpl::merge(
403 rpl::duplicate(started) | rpl::to_empty,
404 _peer->session().changes().peerFlagsValue(
405 _peer,
406 Data::PeerUpdate::Flag::Username
407 ) | rpl::skip(1) | rpl::to_empty
408 ) | rpl::start_with_next([=] {
409 refreshLeftButton();
410 updateControlsGeometry();
411 }, _callLifetime);
412
413 std::move(started) | rpl::start_with_next([=] {
414 refreshVideoButtons();
415 updateButtonsStyles();
416 setupMembers();
417 }, _callLifetime);
418 }
419
420 _call->stateValue(
421 ) | rpl::before_next([=] {
422 showStickedTooltip();
423 }) | rpl::filter([](State state) {
424 return (state == State::HangingUp)
425 || (state == State::Ended)
426 || (state == State::FailedHangingUp)
427 || (state == State::Failed);
428 }) | rpl::start_with_next([=] {
429 closeBeforeDestroy();
430 }, _callLifetime);
431
432 _call->levelUpdates(
433 ) | rpl::filter([=](const LevelUpdate &update) {
434 return update.me;
435 }) | rpl::start_with_next([=](const LevelUpdate &update) {
436 _mute->setLevel(update.value);
437 }, _callLifetime);
438
439 _call->real(
440 ) | rpl::start_with_next([=](not_null<Data::GroupCall*> real) {
441 setupRealMuteButtonState(real);
442 }, _callLifetime);
443
444 refreshControlsBackground();
445 }
446
refreshLeftButton()447 void Panel::refreshLeftButton() {
448 const auto share = _call->scheduleDate()
449 && _peer->isBroadcast()
450 && _peer->asChannel()->hasUsername();
451 if ((share && _callShare) || (!share && _settings)) {
452 return;
453 }
454 if (share) {
455 _settings.destroy();
456 _callShare.create(widget(), st::groupCallShare);
457 _callShare->setClickedCallback(_callShareLinkCallback);
458 } else {
459 _callShare.destroy();
460 _settings.create(widget(), st::groupCallSettings);
461 _settings->setClickedCallback([=] {
462 showBox(Box(SettingsBox, _call));
463 });
464 }
465 const auto raw = _callShare ? _callShare.data() : _settings.data();
466 raw->show();
467 raw->setColorOverrides(_mute->colorOverrides());
468 updateButtonsStyles();
469 }
470
refreshVideoButtons(std::optional<bool> overrideWideMode)471 void Panel::refreshVideoButtons(std::optional<bool> overrideWideMode) {
472 const auto create = overrideWideMode.value_or(mode() == PanelMode::Wide)
473 || (!_call->scheduleDate() && _call->videoIsWorking());
474 const auto created = _video && _screenShare;
475 if (created == create) {
476 return;
477 } else if (created) {
478 _video.destroy();
479 _screenShare.destroy();
480 if (!overrideWideMode) {
481 updateButtonsGeometry();
482 }
483 return;
484 }
485 auto toggleableOverrides = [&](rpl::producer<bool> active) {
486 return rpl::combine(
487 std::move(active),
488 _mute->colorOverrides()
489 ) | rpl::map([](bool active, Ui::CallButtonColors colors) {
490 if (active && colors.bg) {
491 colors.bg->setAlpha(kOverrideActiveColorBgAlpha);
492 }
493 return colors;
494 });
495 };
496 if (!_video) {
497 _video.create(
498 widget(),
499 st::groupCallVideoSmall,
500 &st::groupCallVideoActiveSmall);
501 _video->show();
502 _video->setClickedCallback([=] {
503 hideStickedTooltip(
504 StickedTooltip::Camera,
505 StickedTooltipHide::Activated);
506 _call->toggleVideo(!_call->isSharingCamera());
507 });
508 _video->setColorOverrides(
509 toggleableOverrides(_call->isSharingCameraValue()));
510 _call->isSharingCameraValue(
511 ) | rpl::start_with_next([=](bool sharing) {
512 if (sharing) {
513 hideStickedTooltip(
514 StickedTooltip::Camera,
515 StickedTooltipHide::Activated);
516 }
517 _video->setProgress(sharing ? 1. : 0.);
518 }, _video->lifetime());
519 }
520 if (!_screenShare) {
521 _screenShare.create(widget(), st::groupCallScreenShareSmall);
522 _screenShare->show();
523 _screenShare->setClickedCallback([=] {
524 chooseShareScreenSource();
525 });
526 _screenShare->setColorOverrides(
527 toggleableOverrides(_call->isSharingScreenValue()));
528 _call->isSharingScreenValue(
529 ) | rpl::start_with_next([=](bool sharing) {
530 _screenShare->setProgress(sharing ? 1. : 0.);
531 }, _screenShare->lifetime());
532 }
533 if (!_wideMenu) {
534 _wideMenu.create(widget(), st::groupCallMenuToggleSmall);
535 _wideMenu->show();
536 _wideMenu->setClickedCallback([=] { showMainMenu(); });
537 _wideMenu->setColorOverrides(
538 toggleableOverrides(_wideMenuShown.value()));
539 }
540 updateButtonsStyles();
541 updateButtonsGeometry();
542 raiseControls();
543 }
544
hideStickedTooltip(StickedTooltipHide hide)545 void Panel::hideStickedTooltip(StickedTooltipHide hide) {
546 if (!_stickedTooltipClose || !_niceTooltipControl) {
547 return;
548 }
549 if (_niceTooltipControl.data() == _video.data()) {
550 hideStickedTooltip(StickedTooltip::Camera, hide);
551 } else if (_niceTooltipControl.data() == _mute->outer().get()) {
552 hideStickedTooltip(StickedTooltip::Microphone, hide);
553 }
554 }
555
hideStickedTooltip(StickedTooltip type,StickedTooltipHide hide)556 void Panel::hideStickedTooltip(
557 StickedTooltip type,
558 StickedTooltipHide hide) {
559 if (hide != StickedTooltipHide::Unavailable) {
560 _stickedTooltipsShown |= type;
561 if (hide == StickedTooltipHide::Discarded) {
562 Core::App().settings().setHiddenGroupCallTooltip(type);
563 Core::App().saveSettingsDelayed();
564 }
565 }
566 const auto control = (type == StickedTooltip::Camera)
567 ? _video.data()
568 : (type == StickedTooltip::Microphone)
569 ? _mute->outer().get()
570 : nullptr;
571 if (_niceTooltipControl.data() == control) {
572 hideNiceTooltip();
573 }
574 }
575
hideNiceTooltip()576 void Panel::hideNiceTooltip() {
577 if (!_niceTooltip) {
578 return;
579 }
580 _stickedTooltipClose = nullptr;
581 _niceTooltip.release()->toggleAnimated(false);
582 _niceTooltipControl = nullptr;
583 }
584
initShareAction()585 void Panel::initShareAction() {
586 const auto showBoxCallback = [=](object_ptr<Ui::BoxContent> next) {
587 showBox(std::move(next));
588 };
589 const auto showToastCallback = [=](QString text) {
590 showToast({ text });
591 };
592 auto [shareLinkCallback, shareLinkLifetime] = ShareInviteLinkAction(
593 _peer,
594 showBoxCallback,
595 showToastCallback);
596 _callShareLinkCallback = [=, callback = std::move(shareLinkCallback)] {
597 if (_call->lookupReal()) {
598 callback();
599 }
600 };
601 lifetime().add(std::move(shareLinkLifetime));
602 }
603
setupRealMuteButtonState(not_null<Data::GroupCall * > real)604 void Panel::setupRealMuteButtonState(not_null<Data::GroupCall*> real) {
605 using namespace rpl::mappers;
606 rpl::combine(
607 _call->mutedValue() | MapPushToTalkToActive(),
608 _call->instanceStateValue(),
609 real->scheduleDateValue(),
610 real->scheduleStartSubscribedValue(),
611 _call->canManageValue(),
612 _mode.value()
613 ) | rpl::distinct_until_changed(
614 ) | rpl::filter(
615 _2 != GroupCall::InstanceState::TransitionToRtc
616 ) | rpl::start_with_next([=](
617 MuteState mute,
618 GroupCall::InstanceState state,
619 TimeId scheduleDate,
620 bool scheduleStartSubscribed,
621 bool canManage,
622 PanelMode mode) {
623 const auto wide = (mode == PanelMode::Wide);
624 using Type = Ui::CallMuteButtonType;
625 _mute->setState(Ui::CallMuteButtonState{
626 .text = (wide
627 ? QString()
628 : scheduleDate
629 ? (canManage
630 ? tr::lng_group_call_start_now(tr::now)
631 : scheduleStartSubscribed
632 ? tr::lng_group_call_cancel_reminder(tr::now)
633 : tr::lng_group_call_set_reminder(tr::now))
634 : state == GroupCall::InstanceState::Disconnected
635 ? tr::lng_group_call_connecting(tr::now)
636 : mute == MuteState::ForceMuted
637 ? tr::lng_group_call_force_muted(tr::now)
638 : mute == MuteState::RaisedHand
639 ? tr::lng_group_call_raised_hand(tr::now)
640 : mute == MuteState::Muted
641 ? tr::lng_group_call_unmute(tr::now)
642 : tr::lng_group_call_you_are_live(tr::now)),
643 .tooltip = ((!scheduleDate && mute == MuteState::Muted)
644 ? tr::lng_group_call_unmute_sub(tr::now)
645 : QString()),
646 .type = (scheduleDate
647 ? (canManage
648 ? Type::ScheduledCanStart
649 : scheduleStartSubscribed
650 ? Type::ScheduledNotify
651 : Type::ScheduledSilent)
652 : state == GroupCall::InstanceState::Disconnected
653 ? Type::Connecting
654 : mute == MuteState::ForceMuted
655 ? Type::ForceMuted
656 : mute == MuteState::RaisedHand
657 ? Type::RaisedHand
658 : mute == MuteState::Muted
659 ? Type::Muted
660 : Type::Active),
661 });
662 }, _callLifetime);
663 }
664
setupScheduledLabels(rpl::producer<TimeId> date)665 void Panel::setupScheduledLabels(rpl::producer<TimeId> date) {
666 using namespace rpl::mappers;
667 date = std::move(date) | rpl::take_while(_1 != 0);
668 _startsWhen.create(
669 widget(),
670 Ui::StartsWhenText(rpl::duplicate(date)),
671 st::groupCallStartsWhen);
672 auto countdownCreated = std::move(
673 date
674 ) | rpl::map([=](TimeId date) {
675 _countdownData = std::make_shared<Ui::GroupCallScheduledLeft>(date);
676 return rpl::empty_value();
677 }) | rpl::start_spawning(lifetime());
678
679 _countdown = Ui::CreateGradientLabel(widget(), rpl::duplicate(
680 countdownCreated
681 ) | rpl::map([=] {
682 return _countdownData->text(
683 Ui::GroupCallScheduledLeft::Negative::Ignore);
684 }) | rpl::flatten_latest());
685
686 _startsIn.create(
687 widget(),
688 rpl::conditional(
689 std::move(
690 countdownCreated
691 ) | rpl::map(
692 [=] { return _countdownData->late(); }
693 ) | rpl::flatten_latest(),
694 tr::lng_group_call_late_by(),
695 tr::lng_group_call_starts_in()),
696 st::groupCallStartsIn);
697
698 const auto top = [=] {
699 const auto muteTop = widget()->height() - st::groupCallMuteBottomSkip;
700 const auto membersTop = st::groupCallMembersTop;
701 const auto height = st::groupCallScheduledBodyHeight;
702 return (membersTop + (muteTop - membersTop - height) / 2);
703 };
704 rpl::combine(
705 widget()->sizeValue(),
706 _startsIn->widthValue()
707 ) | rpl::start_with_next([=](QSize size, int width) {
708 _startsIn->move(
709 (size.width() - width) / 2,
710 top() + st::groupCallStartsInTop);
711 }, _startsIn->lifetime());
712
713 rpl::combine(
714 widget()->sizeValue(),
715 _startsWhen->widthValue()
716 ) | rpl::start_with_next([=](QSize size, int width) {
717 _startsWhen->move(
718 (size.width() - width) / 2,
719 top() + st::groupCallStartsWhenTop);
720 }, _startsWhen->lifetime());
721
722 rpl::combine(
723 widget()->sizeValue(),
724 _countdown->widthValue()
725 ) | rpl::start_with_next([=](QSize size, int width) {
726 _countdown->move(
727 (size.width() - width) / 2,
728 top() + st::groupCallCountdownTop);
729 }, _startsWhen->lifetime());
730 }
731
mode() const732 PanelMode Panel::mode() const {
733 return _mode.current();
734 }
735
setupMembers()736 void Panel::setupMembers() {
737 if (_members) {
738 return;
739 }
740
741 _startsIn.destroy();
742 _countdown.destroy();
743 _startsWhen.destroy();
744
745 _members.create(widget(), _call, mode(), _window.backend());
746
747 setupVideo(_viewport.get());
748 setupVideo(_members->viewport());
749 _viewport->mouseInsideValue(
750 ) | rpl::start_with_next([=](bool inside) {
751 toggleWideControls(inside);
752 }, _viewport->lifetime());
753
754 _members->show();
755
756 refreshControlsBackground();
757 raiseControls();
758
759 _members->desiredHeightValue(
760 ) | rpl::start_with_next([=] {
761 updateMembersGeometry();
762 }, _members->lifetime());
763
764 _members->toggleMuteRequests(
765 ) | rpl::start_with_next([=](MuteRequest request) {
766 if (_call) {
767 _call->toggleMute(request);
768 }
769 }, _callLifetime);
770
771 _members->changeVolumeRequests(
772 ) | rpl::start_with_next([=](VolumeRequest request) {
773 if (_call) {
774 _call->changeVolume(request);
775 }
776 }, _callLifetime);
777
778 _members->kickParticipantRequests(
779 ) | rpl::start_with_next([=](not_null<PeerData*> participantPeer) {
780 kickParticipant(participantPeer);
781 }, _callLifetime);
782
783 _members->addMembersRequests(
784 ) | rpl::start_with_next([=] {
785 if (!_peer->isBroadcast()
786 && _peer->canWrite()
787 && _call->joinAs()->isSelf()) {
788 addMembers();
789 } else if (const auto channel = _peer->asChannel()) {
790 if (channel->hasUsername()) {
791 _callShareLinkCallback();
792 }
793 }
794 }, _callLifetime);
795
796 _call->videoEndpointLargeValue(
797 ) | rpl::start_with_next([=](const VideoEndpoint &large) {
798 if (large && mode() != PanelMode::Wide) {
799 enlargeVideo();
800 }
801 _viewport->showLarge(large);
802 }, _callLifetime);
803 }
804
enlargeVideo()805 void Panel::enlargeVideo() {
806 _lastSmallGeometry = window()->geometry();
807
808 const auto available = window()->screen()->availableGeometry();
809 const auto width = std::max(
810 window()->width(),
811 std::max(
812 std::min(available.width(), st::groupCallWideModeSize.width()),
813 st::groupCallWideModeWidthMin));
814 const auto height = std::max(
815 window()->height(),
816 std::min(available.height(), st::groupCallWideModeSize.height()));
817 auto geometry = QRect(window()->pos(), QSize(width, height));
818 if (geometry.x() < available.x()) {
819 geometry.moveLeft(std::min(available.x(), window()->x()));
820 }
821 if (geometry.x() + geometry.width()
822 > available.x() + available.width()) {
823 geometry.moveLeft(std::max(
824 available.x() + available.width(),
825 window()->x() + window()->width()) - geometry.width());
826 }
827 if (geometry.y() < available.y()) {
828 geometry.moveTop(std::min(available.y(), window()->y()));
829 }
830 if (geometry.y() + geometry.height() > available.y() + available.height()) {
831 geometry.moveTop(std::max(
832 available.y() + available.height(),
833 window()->y() + window()->height()) - geometry.height());
834 }
835 if (_lastLargeMaximized) {
836 window()->setWindowState(
837 window()->windowState() | Qt::WindowMaximized);
838 } else {
839 window()->setGeometry((_lastLargeGeometry
840 && available.intersects(*_lastLargeGeometry))
841 ? *_lastLargeGeometry
842 : geometry);
843 }
844 }
845
minimizeVideo()846 void Panel::minimizeVideo() {
847 if (window()->windowState() & Qt::WindowMaximized) {
848 _lastLargeMaximized = true;
849 window()->setWindowState(
850 window()->windowState() & ~Qt::WindowMaximized);
851 } else {
852 _lastLargeMaximized = false;
853 _lastLargeGeometry = window()->geometry();
854 }
855 const auto available = window()->screen()->availableGeometry();
856 const auto width = st::groupCallWidth;
857 const auto height = st::groupCallHeight;
858 auto geometry = QRect(
859 window()->x() + (window()->width() - width) / 2,
860 window()->y() + (window()->height() - height) / 2,
861 width,
862 height);
863 window()->setGeometry((_lastSmallGeometry
864 && available.intersects(*_lastSmallGeometry))
865 ? *_lastSmallGeometry
866 : geometry);
867 }
868
raiseControls()869 void Panel::raiseControls() {
870 if (_controlsBackgroundWide) {
871 _controlsBackgroundWide->raise();
872 }
873 if (_controlsBackgroundNarrow) {
874 _controlsBackgroundNarrow->shadow.raise();
875 _controlsBackgroundNarrow->blocker.raise();
876 }
877 const auto buttons = {
878 &_settings,
879 &_callShare,
880 &_screenShare,
881 &_wideMenu,
882 &_video,
883 &_hangup
884 };
885 for (const auto button : buttons) {
886 if (const auto raw = button->data()) {
887 raw->raise();
888 }
889 }
890 _mute->raise();
891 _layerBg->raise();
892 if (_niceTooltip) {
893 _niceTooltip->raise();
894 }
895 }
896
setupVideo(not_null<Viewport * > viewport)897 void Panel::setupVideo(not_null<Viewport*> viewport) {
898 const auto setupTile = [=](
899 const VideoEndpoint &endpoint,
900 const std::unique_ptr<GroupCall::VideoTrack> &track) {
901 using namespace rpl::mappers;
902 const auto row = _members->lookupRow(GroupCall::TrackPeer(track));
903 Assert(row != nullptr);
904 auto pinned = rpl::combine(
905 _call->videoEndpointLargeValue(),
906 _call->videoEndpointPinnedValue()
907 ) | rpl::map(_1 == endpoint && _2);
908 viewport->add(
909 endpoint,
910 VideoTileTrack{ GroupCall::TrackPointer(track), row },
911 GroupCall::TrackSizeValue(track),
912 std::move(pinned));
913 };
914 for (const auto &[endpoint, track] : _call->activeVideoTracks()) {
915 setupTile(endpoint, track);
916 }
917 _call->videoStreamActiveUpdates(
918 ) | rpl::start_with_next([=](const VideoStateToggle &update) {
919 if (update.value) {
920 // Add async (=> the participant row is definitely in Members).
921 const auto endpoint = update.endpoint;
922 crl::on_main(viewport->widget(), [=] {
923 const auto &tracks = _call->activeVideoTracks();
924 const auto i = tracks.find(endpoint);
925 if (i != end(tracks)) {
926 setupTile(endpoint, i->second);
927 }
928 });
929 } else {
930 // Remove sync.
931 viewport->remove(update.endpoint);
932 }
933 }, viewport->lifetime());
934
935 viewport->pinToggled(
936 ) | rpl::start_with_next([=](bool pinned) {
937 _call->pinVideoEndpoint(pinned
938 ? _call->videoEndpointLarge()
939 : VideoEndpoint{});
940 }, viewport->lifetime());
941
942 viewport->clicks(
943 ) | rpl::start_with_next([=](VideoEndpoint &&endpoint) {
944 if (_call->videoEndpointLarge() == endpoint) {
945 _call->showVideoEndpointLarge({});
946 } else if (_call->videoEndpointPinned()) {
947 _call->pinVideoEndpoint(std::move(endpoint));
948 } else {
949 _call->showVideoEndpointLarge(std::move(endpoint));
950 }
951 }, viewport->lifetime());
952
953 viewport->qualityRequests(
954 ) | rpl::start_with_next([=](const VideoQualityRequest &request) {
955 _call->requestVideoQuality(request.endpoint, request.quality);
956 }, viewport->lifetime());
957 }
958
toggleWideControls(bool shown)959 void Panel::toggleWideControls(bool shown) {
960 if (_showWideControls == shown) {
961 return;
962 }
963 _showWideControls = shown;
964 crl::on_main(widget(), [=] {
965 updateWideControlsVisibility();
966 });
967 }
968
updateWideControlsVisibility()969 void Panel::updateWideControlsVisibility() {
970 const auto shown = _showWideControls
971 || (_stickedTooltipClose != nullptr);
972 if (_wideControlsShown == shown) {
973 return;
974 }
975 _wideControlsShown = shown;
976 _wideControlsAnimation.start(
977 [=] { updateButtonsGeometry(); },
978 _wideControlsShown ? 0. : 1.,
979 _wideControlsShown ? 1. : 0.,
980 st::slideWrapDuration);
981 }
982
subscribeToChanges(not_null<Data::GroupCall * > real)983 void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
984 const auto livestream = real->peer()->isBroadcast();
985 const auto validateRecordingMark = [=](bool recording) {
986 if (!recording && _recordingMark) {
987 _recordingMark.destroy();
988 } else if (recording && !_recordingMark) {
989 struct State {
990 Ui::Animations::Simple animation;
991 base::Timer timer;
992 bool opaque = true;
993 };
994 _recordingMark.create(widget());
995 _recordingMark->show();
996 const auto state = _recordingMark->lifetime().make_state<State>();
997 const auto size = st::groupCallRecordingMark;
998 const auto skip = st::groupCallRecordingMarkSkip;
999 _recordingMark->resize(size + 2 * skip, size + 2 * skip);
1000 _recordingMark->setClickedCallback([=] {
1001 showToast({ (livestream
1002 ? tr::lng_group_call_is_recorded_channel
1003 : real->recordVideo()
1004 ? tr::lng_group_call_is_recorded_video
1005 : tr::lng_group_call_is_recorded)(tr::now) });
1006 });
1007 const auto animate = [=] {
1008 const auto opaque = state->opaque;
1009 state->opaque = !opaque;
1010 state->animation.start(
1011 [=] { _recordingMark->update(); },
1012 opaque ? 1. : kRecordingOpacity,
1013 opaque ? kRecordingOpacity : 1.,
1014 kRecordingAnimationDuration);
1015 };
1016 state->timer.setCallback(animate);
1017 state->timer.callEach(kRecordingAnimationDuration);
1018 animate();
1019
1020 _recordingMark->paintRequest(
1021 ) | rpl::start_with_next([=] {
1022 auto p = QPainter(_recordingMark.data());
1023 auto hq = PainterHighQualityEnabler(p);
1024 p.setPen(Qt::NoPen);
1025 p.setBrush(st::groupCallMemberMutedIcon);
1026 p.setOpacity(state->animation.value(
1027 state->opaque ? 1. : kRecordingOpacity));
1028 p.drawEllipse(skip, skip, size, size);
1029 }, _recordingMark->lifetime());
1030 }
1031 refreshTitleGeometry();
1032 };
1033
1034 using namespace rpl::mappers;
1035 const auto startedAsVideo = std::make_shared<bool>(real->recordVideo());
1036 real->recordStartDateChanges(
1037 ) | rpl::map(
1038 _1 != 0
1039 ) | rpl::distinct_until_changed(
1040 ) | rpl::start_with_next([=](bool recorded) {
1041 const auto livestream = _call->peer()->isBroadcast();
1042 const auto isVideo = real->recordVideo();
1043 if (recorded) {
1044 *startedAsVideo = isVideo;
1045 }
1046 validateRecordingMark(recorded);
1047 showToast((recorded
1048 ? (livestream
1049 ? tr::lng_group_call_recording_started_channel
1050 : isVideo
1051 ? tr::lng_group_call_recording_started_video
1052 : tr::lng_group_call_recording_started)
1053 : _call->recordingStoppedByMe()
1054 ? ((*startedAsVideo)
1055 ? tr::lng_group_call_recording_saved_video
1056 : tr::lng_group_call_recording_saved)
1057 : (livestream
1058 ? tr::lng_group_call_recording_stopped_channel
1059 : tr::lng_group_call_recording_stopped))(
1060 tr::now,
1061 Ui::Text::RichLangValue));
1062 }, lifetime());
1063 validateRecordingMark(real->recordStartDate() != 0);
1064
1065 rpl::combine(
1066 _call->videoIsWorkingValue(),
1067 _call->isSharingCameraValue()
1068 ) | rpl::start_with_next([=] {
1069 refreshVideoButtons();
1070 showStickedTooltip();
1071 }, lifetime());
1072
1073 rpl::combine(
1074 _call->videoIsWorkingValue(),
1075 _call->isSharingScreenValue()
1076 ) | rpl::start_with_next([=] {
1077 refreshTopButton();
1078 }, lifetime());
1079
1080 _call->mutedValue(
1081 ) | rpl::skip(1) | rpl::start_with_next([=](MuteState state) {
1082 updateButtonsGeometry();
1083 if (state == MuteState::Active
1084 || state == MuteState::PushToTalk) {
1085 hideStickedTooltip(
1086 StickedTooltip::Microphone,
1087 StickedTooltipHide::Activated);
1088 }
1089 showStickedTooltip();
1090 }, lifetime());
1091
1092 updateControlsGeometry();
1093 }
1094
refreshTopButton()1095 void Panel::refreshTopButton() {
1096 if (_mode.current() == PanelMode::Wide) {
1097 _menuToggle.destroy();
1098 _joinAsToggle.destroy();
1099 updateButtonsGeometry(); // _wideMenu <-> _settings
1100 return;
1101 }
1102 const auto hasJoinAs = _call->showChooseJoinAs();
1103 const auto showNarrowMenu = _call->canManage()
1104 || _call->videoIsWorking();
1105 const auto showNarrowUserpic = !showNarrowMenu && hasJoinAs;
1106 if (showNarrowMenu) {
1107 _joinAsToggle.destroy();
1108 if (!_menuToggle) {
1109 _menuToggle.create(widget(), st::groupCallMenuToggle);
1110 _menuToggle->show();
1111 _menuToggle->setClickedCallback([=] { showMainMenu(); });
1112 updateControlsGeometry();
1113 }
1114 } else if (showNarrowUserpic) {
1115 _menuToggle.destroy();
1116 rpl::single(
1117 _call->joinAs()
1118 ) | rpl::then(_call->rejoinEvents(
1119 ) | rpl::map([](const RejoinEvent &event) {
1120 return event.nowJoinAs;
1121 })) | rpl::start_with_next([=](not_null<PeerData*> joinAs) {
1122 auto joinAsToggle = object_ptr<Ui::UserpicButton>(
1123 widget(),
1124 joinAs,
1125 Ui::UserpicButton::Role::Custom,
1126 st::groupCallJoinAsToggle);
1127 _joinAsToggle.destroy();
1128 _joinAsToggle = std::move(joinAsToggle);
1129 _joinAsToggle->show();
1130 _joinAsToggle->setClickedCallback([=] {
1131 chooseJoinAs();
1132 });
1133 updateControlsGeometry();
1134 }, lifetime());
1135 } else {
1136 _menuToggle.destroy();
1137 _joinAsToggle.destroy();
1138 }
1139 }
1140
screenSharingPrivacyRequest()1141 void Panel::screenSharingPrivacyRequest() {
1142 if (auto box = ScreenSharingPrivacyRequestBox()) {
1143 showBox(std::move(box));
1144 }
1145 }
1146
chooseShareScreenSource()1147 void Panel::chooseShareScreenSource() {
1148 if (_call->emitShareScreenError()) {
1149 return;
1150 }
1151 const auto choose = [=] {
1152 if (!Webrtc::DesktopCaptureAllowed()) {
1153 screenSharingPrivacyRequest();
1154 } else if (const auto source = Webrtc::UniqueDesktopCaptureSource()) {
1155 if (_call->isSharingScreen()) {
1156 _call->toggleScreenSharing(std::nullopt);
1157 } else {
1158 chooseSourceAccepted(*source, false);
1159 }
1160 } else {
1161 Ui::DesktopCapture::ChooseSource(this);
1162 }
1163 };
1164 const auto screencastFromPeer = [&]() -> PeerData* {
1165 for (const auto &[endpoint, track] : _call->activeVideoTracks()) {
1166 if (endpoint.type == VideoEndpointType::Screen) {
1167 return endpoint.peer;
1168 }
1169 }
1170 return nullptr;
1171 }();
1172 if (!screencastFromPeer || _call->isSharingScreen()) {
1173 choose();
1174 return;
1175 }
1176 const auto text = tr::lng_group_call_sure_screencast(
1177 tr::now,
1178 lt_user,
1179 screencastFromPeer->shortName());
1180 const auto shared = std::make_shared<QPointer<Ui::GenericBox>>();
1181 const auto done = [=] {
1182 if (*shared) {
1183 base::take(*shared)->closeBox();
1184 }
1185 choose();
1186 };
1187 auto box = ConfirmBox({
1188 .text = { text },
1189 .button = tr::lng_continue(),
1190 .callback = done,
1191 });
1192 *shared = box.data();
1193 showBox(std::move(box));
1194 }
1195
chooseJoinAs()1196 void Panel::chooseJoinAs() {
1197 const auto context = ChooseJoinAsProcess::Context::Switch;
1198 const auto callback = [=](JoinInfo info) {
1199 _call->rejoinAs(info);
1200 };
1201 const auto showBoxCallback = [=](object_ptr<Ui::BoxContent> next) {
1202 showBox(std::move(next));
1203 };
1204 const auto showToastCallback = [=](QString text) {
1205 showToast({ text });
1206 };
1207 _joinAsProcess.start(
1208 _peer,
1209 context,
1210 showBoxCallback,
1211 showToastCallback,
1212 callback,
1213 _call->joinAs());
1214 }
1215
showMainMenu()1216 void Panel::showMainMenu() {
1217 if (_menu) {
1218 return;
1219 }
1220 const auto wide = (_mode.current() == PanelMode::Wide) && _wideMenu;
1221 if (!wide && !_menuToggle) {
1222 return;
1223 }
1224 _menu.create(widget(), st::groupCallDropdownMenu);
1225 FillMenu(
1226 _menu.data(),
1227 _peer,
1228 _call,
1229 wide,
1230 [=] { chooseJoinAs(); },
1231 [=] { chooseShareScreenSource(); },
1232 [=](auto box) { showBox(std::move(box)); });
1233 if (_menu->empty()) {
1234 _wideMenuShown = false;
1235 _menu.destroy();
1236 return;
1237 }
1238
1239 const auto raw = _menu.data();
1240 raw->setHiddenCallback([=] {
1241 raw->deleteLater();
1242 if (_menu == raw) {
1243 _menu = nullptr;
1244 _wideMenuShown = false;
1245 _trackControlsMenuLifetime.destroy();
1246 if (_menuToggle) {
1247 _menuToggle->setForceRippled(false);
1248 }
1249 }
1250 });
1251 raw->setShowStartCallback([=] {
1252 if (_menu == raw) {
1253 if (wide) {
1254 _wideMenuShown = true;
1255 } else if (_menuToggle) {
1256 _menuToggle->setForceRippled(true);
1257 }
1258 }
1259 });
1260 raw->setHideStartCallback([=] {
1261 if (_menu == raw) {
1262 _wideMenuShown = false;
1263 if (_menuToggle) {
1264 _menuToggle->setForceRippled(false);
1265 }
1266 }
1267 });
1268
1269 if (wide) {
1270 _wideMenu->installEventFilter(_menu);
1271 const auto x = st::groupCallWideMenuPosition.x();
1272 const auto y = st::groupCallWideMenuPosition.y();
1273 _menu->moveToLeft(
1274 _wideMenu->x() + x,
1275 _wideMenu->y() - _menu->height() + y);
1276 _menu->showAnimated(Ui::PanelAnimation::Origin::BottomLeft);
1277 trackControl(_menu, _trackControlsMenuLifetime);
1278 } else {
1279 _menuToggle->installEventFilter(_menu);
1280 const auto x = st::groupCallMenuPosition.x();
1281 const auto y = st::groupCallMenuPosition.y();
1282 if (_menuToggle->x() > widget()->width() / 2) {
1283 _menu->moveToRight(x, y);
1284 _menu->showAnimated(Ui::PanelAnimation::Origin::TopRight);
1285 } else {
1286 _menu->moveToLeft(x, y);
1287 _menu->showAnimated(Ui::PanelAnimation::Origin::TopLeft);
1288 }
1289 }
1290 }
1291
addMembers()1292 void Panel::addMembers() {
1293 const auto showToastCallback = [=](TextWithEntities &&text) {
1294 showToast(std::move(text));
1295 };
1296 if (auto box = PrepareInviteBox(_call, showToastCallback)) {
1297 showBox(std::move(box));
1298 }
1299 }
1300
kickParticipant(not_null<PeerData * > participantPeer)1301 void Panel::kickParticipant(not_null<PeerData*> participantPeer) {
1302 showBox(Box([=](not_null<Ui::GenericBox*> box) {
1303 box->addRow(
1304 object_ptr<Ui::FlatLabel>(
1305 box.get(),
1306 (!participantPeer->isUser()
1307 ? (_peer->isBroadcast()
1308 ? tr::lng_group_call_remove_channel_from_channel
1309 : tr::lng_group_call_remove_channel)(
1310 tr::now,
1311 lt_channel,
1312 participantPeer->name)
1313 : (_peer->isBroadcast()
1314 ? tr::lng_profile_sure_kick_channel
1315 : tr::lng_profile_sure_kick)(
1316 tr::now,
1317 lt_user,
1318 participantPeer->asUser()->firstName)),
1319 st::groupCallBoxLabel),
1320 style::margins(
1321 st::boxRowPadding.left(),
1322 st::boxPadding.top(),
1323 st::boxRowPadding.right(),
1324 st::boxPadding.bottom()));
1325 box->addButton(tr::lng_box_remove(), [=] {
1326 box->closeBox();
1327 kickParticipantSure(participantPeer);
1328 });
1329 box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
1330 }));
1331 }
1332
showBox(object_ptr<Ui::BoxContent> box)1333 void Panel::showBox(object_ptr<Ui::BoxContent> box) {
1334 showBox(std::move(box), Ui::LayerOption::KeepOther, anim::type::normal);
1335 }
1336
showBox(object_ptr<Ui::BoxContent> box,Ui::LayerOptions options,anim::type animated)1337 void Panel::showBox(
1338 object_ptr<Ui::BoxContent> box,
1339 Ui::LayerOptions options,
1340 anim::type animated) {
1341 hideStickedTooltip(StickedTooltipHide::Unavailable);
1342 _layerBg->showBox(std::move(box), options, animated);
1343 }
1344
kickParticipantSure(not_null<PeerData * > participantPeer)1345 void Panel::kickParticipantSure(not_null<PeerData*> participantPeer) {
1346 if (const auto chat = _peer->asChat()) {
1347 chat->session().api().kickParticipant(chat, participantPeer);
1348 } else if (const auto channel = _peer->asChannel()) {
1349 const auto currentRestrictedRights = [&] {
1350 const auto user = participantPeer->asUser();
1351 if (!channel->mgInfo || !user) {
1352 return ChatRestrictionsInfo();
1353 }
1354 const auto i = channel->mgInfo->lastRestricted.find(user);
1355 return (i != channel->mgInfo->lastRestricted.cend())
1356 ? i->second.rights
1357 : ChatRestrictionsInfo();
1358 }();
1359 channel->session().api().kickParticipant(
1360 channel,
1361 participantPeer,
1362 currentRestrictedRights);
1363 }
1364 }
1365
initLayout()1366 void Panel::initLayout() {
1367 initGeometry();
1368
1369 #ifndef Q_OS_MAC
1370 _controls->raise();
1371
1372 Ui::Platform::TitleControlsLayoutChanged(
1373 ) | rpl::start_with_next([=] {
1374 // _menuToggle geometry depends on _controls arrangement.
1375 crl::on_main(widget(), [=] { updateControlsGeometry(); });
1376 }, lifetime());
1377
1378 #endif // !Q_OS_MAC
1379 }
1380
showControls()1381 void Panel::showControls() {
1382 Expects(_call != nullptr);
1383
1384 widget()->showChildren();
1385 }
1386
closeBeforeDestroy()1387 void Panel::closeBeforeDestroy() {
1388 window()->close();
1389 _callLifetime.destroy();
1390 }
1391
lifetime()1392 rpl::lifetime &Panel::lifetime() {
1393 return window()->lifetime();
1394 }
1395
initGeometry()1396 void Panel::initGeometry() {
1397 const auto center = Core::App().getPointForCallPanelCenter();
1398 const auto rect = QRect(0, 0, st::groupCallWidth, st::groupCallHeight);
1399 window()->setGeometry(rect.translated(center - rect.center()));
1400 window()->setMinimumSize(rect.size());
1401 window()->show();
1402 }
1403
computeTitleRect() const1404 QRect Panel::computeTitleRect() const {
1405 const auto skip = st::groupCallTitleTop;
1406 const auto remove = skip + (_menuToggle
1407 ? (_menuToggle->width() + st::groupCallMenuTogglePosition.x())
1408 : 0) + (_joinAsToggle
1409 ? (_joinAsToggle->width() + st::groupCallMenuTogglePosition.x())
1410 : 0);
1411 const auto width = widget()->width();
1412 #ifdef Q_OS_MAC
1413 return QRect(70, 0, width - remove - 70, 28);
1414 #else // Q_OS_MAC
1415 const auto controls = _controls->geometry();
1416 const auto right = controls.x() + controls.width() + skip;
1417 return (controls.center().x() < width / 2)
1418 ? QRect(right, 0, width - right - remove, controls.height())
1419 : QRect(remove, 0, controls.x() - skip - remove, controls.height());
1420 #endif // !Q_OS_MAC
1421 }
1422
updateMode()1423 bool Panel::updateMode() {
1424 if (!_viewport) {
1425 return false;
1426 }
1427 const auto wide = _call->hasVideoWithFrames()
1428 && (widget()->width() >= st::groupCallWideModeWidthMin);
1429 const auto mode = wide ? PanelMode::Wide : PanelMode::Default;
1430 if (_mode.current() == mode) {
1431 return false;
1432 }
1433 if (!wide && _call->videoEndpointLarge()) {
1434 _call->showVideoEndpointLarge({});
1435 }
1436 refreshVideoButtons(wide);
1437 if (!_stickedTooltipClose
1438 || _niceTooltipControl.data() != _mute->outer().get()) {
1439 _niceTooltip.destroy();
1440 }
1441 _mode = mode;
1442 if (_title) {
1443 _title->setTextColorOverride(wide
1444 ? std::make_optional(st::groupCallMemberNotJoinedStatus->c)
1445 : std::nullopt);
1446 }
1447 if (wide && _subtitle) {
1448 _subtitle.destroy();
1449 } else if (!wide && !_subtitle) {
1450 refreshTitle();
1451 }
1452 _wideControlsShown = _showWideControls = true;
1453 _wideControlsAnimation.stop();
1454 _viewport->widget()->setVisible(wide);
1455 if (_members) {
1456 _members->setMode(mode);
1457 }
1458 updateButtonsStyles();
1459 refreshControlsBackground();
1460 updateControlsGeometry();
1461 showStickedTooltip();
1462 return true;
1463 }
1464
updateButtonsStyles()1465 void Panel::updateButtonsStyles() {
1466 const auto wide = (_mode.current() == PanelMode::Wide);
1467 _mute->setStyle(wide ? st::callMuteButtonSmall : st::callMuteButton);
1468 if (_video) {
1469 _video->setStyle(
1470 wide ? st::groupCallVideoSmall : st::groupCallVideo,
1471 (wide
1472 ? &st::groupCallVideoActiveSmall
1473 : &st::groupCallVideoActive));
1474 _video->setText(wide
1475 ? rpl::single(QString())
1476 : tr::lng_group_call_video());
1477 }
1478 if (_settings) {
1479 _settings->setText(wide
1480 ? rpl::single(QString())
1481 : tr::lng_group_call_settings());
1482 _settings->setStyle(wide
1483 ? st::groupCallSettingsSmall
1484 : st::groupCallSettings);
1485 }
1486 _hangup->setText(wide
1487 ? rpl::single(QString())
1488 : _call->scheduleDate()
1489 ? tr::lng_group_call_close()
1490 : tr::lng_group_call_leave());
1491 _hangup->setStyle(wide
1492 ? st::groupCallHangupSmall
1493 : st::groupCallHangup);
1494 }
1495
refreshControlsBackground()1496 void Panel::refreshControlsBackground() {
1497 if (!_members) {
1498 return;
1499 }
1500 if (mode() == PanelMode::Default) {
1501 trackControls(false);
1502 _controlsBackgroundWide.destroy();
1503 if (_controlsBackgroundNarrow) {
1504 return;
1505 }
1506 setupControlsBackgroundNarrow();
1507 } else {
1508 _controlsBackgroundNarrow = nullptr;
1509 if (_controlsBackgroundWide) {
1510 return;
1511 }
1512 setupControlsBackgroundWide();
1513 }
1514 raiseControls();
1515 updateButtonsGeometry();
1516 }
1517
setupControlsBackgroundNarrow()1518 void Panel::setupControlsBackgroundNarrow() {
1519 _controlsBackgroundNarrow = std::make_unique<ControlsBackgroundNarrow>(
1520 widget());
1521 _controlsBackgroundNarrow->shadow.show();
1522 _controlsBackgroundNarrow->blocker.show();
1523 auto &lifetime = _controlsBackgroundNarrow->shadow.lifetime();
1524
1525 const auto factor = cIntRetinaFactor();
1526 const auto height = std::max(
1527 st::groupCallMembersShadowHeight,
1528 st::groupCallMembersFadeSkip + st::groupCallMembersFadeHeight);
1529 const auto full = lifetime.make_state<QImage>(
1530 QSize(1, height * factor),
1531 QImage::Format_ARGB32_Premultiplied);
1532 rpl::single(
1533 rpl::empty_value()
1534 ) | rpl::then(
1535 style::PaletteChanged()
1536 ) | rpl::start_with_next([=] {
1537 full->fill(Qt::transparent);
1538
1539 auto p = QPainter(full);
1540 const auto bottom = (height - st::groupCallMembersFadeSkip) * factor;
1541 p.fillRect(
1542 0,
1543 bottom,
1544 full->width(),
1545 st::groupCallMembersFadeSkip * factor,
1546 st::groupCallMembersBg);
1547 p.drawImage(
1548 QRect(
1549 0,
1550 bottom - (st::groupCallMembersFadeHeight * factor),
1551 full->width(),
1552 st::groupCallMembersFadeHeight * factor),
1553 Images::GenerateShadow(
1554 st::groupCallMembersFadeHeight,
1555 0,
1556 255,
1557 st::groupCallMembersBg->c));
1558 p.drawImage(
1559 QRect(
1560 0,
1561 (height - st::groupCallMembersShadowHeight) * factor,
1562 full->width(),
1563 st::groupCallMembersShadowHeight * factor),
1564 Images::GenerateShadow(
1565 st::groupCallMembersShadowHeight,
1566 0,
1567 255,
1568 st::groupCallBg->c));
1569 }, lifetime);
1570
1571 _controlsBackgroundNarrow->shadow.resize(
1572 (widget()->width()
1573 - st::groupCallMembersMargin.left()
1574 - st::groupCallMembersMargin.right()),
1575 height);
1576 _controlsBackgroundNarrow->shadow.paintRequest(
1577 ) | rpl::start_with_next([=](QRect clip) {
1578 auto p = QPainter(&_controlsBackgroundNarrow->shadow);
1579 clip = clip.intersected(_controlsBackgroundNarrow->shadow.rect());
1580 const auto inner = _members->getInnerGeometry().translated(
1581 _members->x() - _controlsBackgroundNarrow->shadow.x(),
1582 _members->y() - _controlsBackgroundNarrow->shadow.y());
1583 const auto faded = clip.intersected(inner);
1584 if (!faded.isEmpty()) {
1585 const auto factor = cIntRetinaFactor();
1586 p.drawImage(
1587 faded,
1588 *full,
1589 QRect(
1590 0,
1591 faded.y() * factor,
1592 full->width(),
1593 faded.height() * factor));
1594 }
1595 const auto bottom = inner.y() + inner.height();
1596 const auto after = clip.intersected(QRect(
1597 0,
1598 bottom,
1599 inner.width(),
1600 _controlsBackgroundNarrow->shadow.height() - bottom));
1601 if (!after.isEmpty()) {
1602 p.fillRect(after, st::groupCallBg);
1603 }
1604 }, lifetime);
1605 _controlsBackgroundNarrow->shadow.setAttribute(
1606 Qt::WA_TransparentForMouseEvents);
1607 _controlsBackgroundNarrow->blocker.setUpdatesEnabled(false);
1608 }
1609
setupControlsBackgroundWide()1610 void Panel::setupControlsBackgroundWide() {
1611 _controlsBackgroundWide.create(widget());
1612 _controlsBackgroundWide->show();
1613 auto &lifetime = _controlsBackgroundWide->lifetime();
1614 const auto color = lifetime.make_state<style::complex_color>([] {
1615 auto result = st::groupCallBg->c;
1616 result.setAlphaF(kControlsBackgroundOpacity);
1617 return result;
1618 });
1619 const auto corners = lifetime.make_state<Ui::RoundRect>(
1620 st::groupCallControlsBackRadius,
1621 color->color());
1622 _controlsBackgroundWide->paintRequest(
1623 ) | rpl::start_with_next([=] {
1624 auto p = QPainter(_controlsBackgroundWide.data());
1625 corners->paint(p, _controlsBackgroundWide->rect());
1626 }, lifetime);
1627
1628 trackControls(true);
1629 }
1630
trackControl(Ui::RpWidget * widget,rpl::lifetime & lifetime)1631 void Panel::trackControl(Ui::RpWidget *widget, rpl::lifetime &lifetime) {
1632 if (!widget) {
1633 return;
1634 }
1635 widget->events(
1636 ) | rpl::start_with_next([=](not_null<QEvent*> e) {
1637 if (e->type() == QEvent::Enter) {
1638 trackControlOver(widget, true);
1639 } else if (e->type() == QEvent::Leave) {
1640 trackControlOver(widget, false);
1641 }
1642 }, lifetime);
1643 }
1644
trackControlOver(not_null<Ui::RpWidget * > control,bool over)1645 void Panel::trackControlOver(not_null<Ui::RpWidget*> control, bool over) {
1646 if (_stickedTooltipClose) {
1647 if (!over) {
1648 return;
1649 }
1650 } else {
1651 hideNiceTooltip();
1652 }
1653 if (over) {
1654 Ui::Integration::Instance().registerLeaveSubscription(control);
1655 showNiceTooltip(control);
1656 } else {
1657 Ui::Integration::Instance().unregisterLeaveSubscription(control);
1658 }
1659 toggleWideControls(over);
1660 }
1661
showStickedTooltip()1662 void Panel::showStickedTooltip() {
1663 static const auto kHasCamera = !Webrtc::GetVideoInputList().empty();
1664 const auto callReady = (_call->state() == State::Joined
1665 || _call->state() == State::Connecting);
1666 if (!(_stickedTooltipsShown & StickedTooltip::Camera)
1667 && callReady
1668 && (_mode.current() == PanelMode::Wide)
1669 && _video
1670 && _call->videoIsWorking()
1671 && !_call->mutedByAdmin()
1672 && kHasCamera) { // Don't recount this every time for now.
1673 showNiceTooltip(_video, NiceTooltipType::Sticked);
1674 return;
1675 }
1676 hideStickedTooltip(
1677 StickedTooltip::Camera,
1678 StickedTooltipHide::Unavailable);
1679
1680 if (!(_stickedTooltipsShown & StickedTooltip::Microphone)
1681 && callReady
1682 && _mute
1683 && !_call->mutedByAdmin()
1684 && !_layerBg->topShownLayer()) {
1685 if (_stickedTooltipClose) {
1686 // Showing already.
1687 return;
1688 } else if (!_micLevelTester) {
1689 // Check if there is incoming sound.
1690 _micLevelTester = std::make_unique<MicLevelTester>([=] {
1691 showStickedTooltip();
1692 });
1693 }
1694 if (_micLevelTester->showTooltip()) {
1695 _micLevelTester = nullptr;
1696 showNiceTooltip(_mute->outer(), NiceTooltipType::Sticked);
1697 }
1698 return;
1699 }
1700 _micLevelTester = nullptr;
1701 hideStickedTooltip(
1702 StickedTooltip::Microphone,
1703 StickedTooltipHide::Unavailable);
1704 }
1705
showNiceTooltip(not_null<Ui::RpWidget * > control,NiceTooltipType type)1706 void Panel::showNiceTooltip(
1707 not_null<Ui::RpWidget*> control,
1708 NiceTooltipType type) {
1709 auto text = [&]() -> rpl::producer<QString> {
1710 if (control == _screenShare.data()) {
1711 if (_call->mutedByAdmin()) {
1712 return nullptr;
1713 }
1714 return tr::lng_group_call_tooltip_screen();
1715 } else if (control == _video.data()) {
1716 if (_call->mutedByAdmin()) {
1717 return nullptr;
1718 }
1719 return _call->isSharingCameraValue(
1720 ) | rpl::map([=](bool sharing) {
1721 return sharing
1722 ? tr::lng_group_call_tooltip_camera_off()
1723 : tr::lng_group_call_tooltip_camera();
1724 }) | rpl::flatten_latest();
1725 } else if (control == _settings.data()) {
1726 return tr::lng_group_call_settings();
1727 } else if (control == _mute->outer()) {
1728 return MuteButtonTooltip(_call);
1729 } else if (control == _hangup.data()) {
1730 return tr::lng_group_call_leave();
1731 }
1732 return rpl::producer<QString>();
1733 }();
1734 if (!text || _stickedTooltipClose) {
1735 return;
1736 } else if (_wideControlsAnimation.animating() || !_wideControlsShown) {
1737 if (type == NiceTooltipType::Normal) {
1738 return;
1739 }
1740 }
1741 const auto inner = [&]() -> Ui::RpWidget* {
1742 const auto normal = (type == NiceTooltipType::Normal);
1743 auto container = normal
1744 ? nullptr
1745 : Ui::CreateChild<Ui::RpWidget>(widget().get());
1746 const auto label = Ui::CreateChild<Ui::FlatLabel>(
1747 (normal ? widget().get() : container),
1748 std::move(text),
1749 st::groupCallNiceTooltipLabel);
1750 if (normal) {
1751 return label;
1752 }
1753 const auto button = Ui::CreateChild<Ui::IconButton>(
1754 container,
1755 st::groupCallStickedTooltipClose);
1756 rpl::combine(
1757 label->sizeValue(),
1758 button->sizeValue()
1759 ) | rpl::start_with_next([=](QSize text, QSize close) {
1760 const auto height = std::max(text.height(), close.height());
1761 container->resize(text.width() + close.width(), height);
1762 label->move(0, (height - text.height()) / 2);
1763 button->move(text.width(), (height - close.height()) / 2);
1764 }, container->lifetime());
1765 button->setClickedCallback([=] {
1766 hideStickedTooltip(StickedTooltipHide::Discarded);
1767 });
1768 _stickedTooltipClose = button;
1769 updateWideControlsVisibility();
1770 return container;
1771 }();
1772 _niceTooltip.create(
1773 widget().get(),
1774 object_ptr<Ui::RpWidget>::fromRaw(inner),
1775 (type == NiceTooltipType::Sticked
1776 ? st::groupCallStickedTooltip
1777 : st::groupCallNiceTooltip));
1778 const auto tooltip = _niceTooltip.data();
1779 const auto weak = QPointer<QWidget>(tooltip);
1780 const auto destroy = [=] {
1781 delete weak.data();
1782 };
1783 if (type != NiceTooltipType::Sticked) {
1784 tooltip->setAttribute(Qt::WA_TransparentForMouseEvents);
1785 }
1786 tooltip->setHiddenCallback(destroy);
1787 base::qt_signal_producer(
1788 control.get(),
1789 &QObject::destroyed
1790 ) | rpl::start_with_next(destroy, tooltip->lifetime());
1791
1792 _niceTooltipControl = control;
1793 updateTooltipGeometry();
1794 tooltip->toggleAnimated(true);
1795 }
1796
updateTooltipGeometry()1797 void Panel::updateTooltipGeometry() {
1798 if (!_niceTooltip) {
1799 return;
1800 } else if (!_niceTooltipControl) {
1801 hideNiceTooltip();
1802 return;
1803 }
1804 const auto geometry = _niceTooltipControl->geometry();
1805 const auto weak = QPointer<QWidget>(_niceTooltip);
1806 const auto countPosition = [=](QSize size) {
1807 const auto strong = weak.data();
1808 const auto wide = (_mode.current() == PanelMode::Wide);
1809 const auto top = geometry.y()
1810 - (wide ? st::groupCallNiceTooltipTop : 0)
1811 - size.height();
1812 const auto middle = geometry.center().x();
1813 if (!strong) {
1814 return QPoint();
1815 } else if (!wide) {
1816 return QPoint(
1817 std::max(
1818 std::min(
1819 middle - size.width() / 2,
1820 (widget()->width()
1821 - st::groupCallMembersMargin.right()
1822 - size.width())),
1823 st::groupCallMembersMargin.left()),
1824 top);
1825 }
1826 const auto back = _controlsBackgroundWide.data();
1827 if (size.width() >= _viewport->widget()->width()) {
1828 return QPoint(_viewport->widget()->x(), top);
1829 } else if (back && size.width() >= back->width()) {
1830 return QPoint(
1831 back->x() - (size.width() - back->width()) / 2,
1832 top);
1833 } else if (back && (middle - back->x() < size.width() / 2)) {
1834 return QPoint(back->x(), top);
1835 } else if (back
1836 && (back->x() + back->width() - middle < size.width() / 2)) {
1837 return QPoint(back->x() + back->width() - size.width(), top);
1838 } else {
1839 return QPoint(middle - size.width() / 2, top);
1840 }
1841 };
1842 _niceTooltip->pointAt(geometry, RectPart::Top, countPosition);
1843 }
1844
trackControls(bool track)1845 void Panel::trackControls(bool track) {
1846 if (_trackControls == track) {
1847 return;
1848 }
1849 _trackControls = track;
1850 if (!track) {
1851 _trackControlsLifetime.destroy();
1852 _trackControlsOverStateLifetime.destroy();
1853 _trackControlsMenuLifetime.destroy();
1854 toggleWideControls(true);
1855 if (_wideControlsAnimation.animating()) {
1856 _wideControlsAnimation.stop();
1857 updateButtonsGeometry();
1858 }
1859 return;
1860 }
1861
1862 const auto trackOne = [=](auto &&widget) {
1863 trackControl(widget, _trackControlsOverStateLifetime);
1864 };
1865 trackOne(_mute->outer());
1866 trackOne(_video);
1867 trackOne(_screenShare);
1868 trackOne(_wideMenu);
1869 trackOne(_settings);
1870 trackOne(_hangup);
1871 trackOne(_controlsBackgroundWide);
1872 trackControl(_menu, _trackControlsMenuLifetime);
1873 }
1874
updateControlsGeometry()1875 void Panel::updateControlsGeometry() {
1876 if (widget()->size().isEmpty() || (!_settings && !_callShare)) {
1877 return;
1878 }
1879 updateButtonsGeometry();
1880 updateMembersGeometry();
1881 refreshTitle();
1882
1883 #ifdef Q_OS_MAC
1884 const auto controlsOnTheLeft = true;
1885 #else // Q_OS_MAC
1886 const auto controlsOnTheLeft = _controls->geometry().center().x()
1887 < widget()->width() / 2;
1888 #endif // Q_OS_MAC
1889 const auto menux = st::groupCallMenuTogglePosition.x();
1890 const auto menuy = st::groupCallMenuTogglePosition.y();
1891 if (controlsOnTheLeft) {
1892 if (_menuToggle) {
1893 _menuToggle->moveToRight(menux, menuy);
1894 } else if (_joinAsToggle) {
1895 _joinAsToggle->moveToRight(menux, menuy);
1896 }
1897 } else {
1898 if (_menuToggle) {
1899 _menuToggle->moveToLeft(menux, menuy);
1900 } else if (_joinAsToggle) {
1901 _joinAsToggle->moveToLeft(menux, menuy);
1902 }
1903 }
1904 }
1905
updateButtonsGeometry()1906 void Panel::updateButtonsGeometry() {
1907 if (widget()->size().isEmpty() || (!_settings && !_callShare)) {
1908 return;
1909 }
1910 const auto toggle = [](auto &widget, bool shown) {
1911 if (widget && widget->isHidden() == shown) {
1912 widget->setVisible(shown);
1913 }
1914 };
1915 if (mode() == PanelMode::Wide) {
1916 Assert(_video != nullptr);
1917 Assert(_screenShare != nullptr);
1918 Assert(_wideMenu != nullptr);
1919 Assert(_settings != nullptr);
1920 Assert(_callShare == nullptr);
1921
1922 const auto shown = _wideControlsAnimation.value(
1923 _wideControlsShown ? 1. : 0.);
1924 const auto hidden = (shown == 0.);
1925
1926 if (_viewport) {
1927 _viewport->setControlsShown(shown);
1928 }
1929
1930 const auto buttonsTop = widget()->height() - anim::interpolate(
1931 0,
1932 st::groupCallButtonBottomSkipWide,
1933 shown);
1934 const auto addSkip = st::callMuteButtonSmall.active.outerRadius;
1935 const auto muteSize = _mute->innerSize().width() + 2 * addSkip;
1936 const auto skip = st::groupCallButtonSkipSmall;
1937 const auto fullWidth = (_video->width() + skip)
1938 + (_screenShare->width() + skip)
1939 + (muteSize + skip)
1940 + (_settings ->width() + skip)
1941 + _hangup->width();
1942 const auto membersSkip = st::groupCallNarrowSkip;
1943 const auto membersWidth = st::groupCallNarrowMembersWidth
1944 + 2 * membersSkip;
1945 auto left = membersSkip + (widget()->width()
1946 - membersWidth
1947 - membersSkip
1948 - fullWidth) / 2;
1949 toggle(_screenShare, !hidden);
1950 _screenShare->moveToLeft(left, buttonsTop);
1951 left += _screenShare->width() + skip;
1952 toggle(_video, !hidden);
1953 _video->moveToLeft(left, buttonsTop);
1954 left += _video->width() + skip;
1955 toggle(_mute, !hidden);
1956 _mute->moveInner({ left + addSkip, buttonsTop + addSkip });
1957 left += muteSize + skip;
1958 const auto wideMenuShown = _call->canManage()
1959 || _call->showChooseJoinAs();
1960 toggle(_settings, !hidden && !wideMenuShown);
1961 toggle(_wideMenu, !hidden && wideMenuShown);
1962 _wideMenu->moveToLeft(left, buttonsTop);
1963 _settings->moveToLeft(left, buttonsTop);
1964 left += _settings->width() + skip;
1965 toggle(_hangup, !hidden);
1966 _hangup->moveToLeft(left, buttonsTop);
1967 left += _hangup->width();
1968 if (_controlsBackgroundWide) {
1969 const auto rect = QRect(
1970 left - fullWidth,
1971 buttonsTop,
1972 fullWidth,
1973 _hangup->height());
1974 _controlsBackgroundWide->setGeometry(
1975 rect.marginsAdded(st::groupCallControlsBackMargin));
1976 }
1977 } else {
1978 const auto muteTop = widget()->height()
1979 - st::groupCallMuteBottomSkip;
1980 const auto buttonsTop = widget()->height()
1981 - st::groupCallButtonBottomSkip;
1982 const auto muteSize = _mute->innerSize().width();
1983 const auto fullWidth = muteSize
1984 + 2 * (_settings ? _settings : _callShare)->width()
1985 + 2 * st::groupCallButtonSkip;
1986 toggle(_mute, true);
1987 _mute->moveInner({ (widget()->width() - muteSize) / 2, muteTop });
1988 const auto leftButtonLeft = (widget()->width() - fullWidth) / 2;
1989 toggle(_screenShare, false);
1990 toggle(_wideMenu, false);
1991 toggle(_callShare, true);
1992 if (_callShare) {
1993 _callShare->moveToLeft(leftButtonLeft, buttonsTop);
1994 }
1995 const auto showVideoButton = videoButtonInNarrowMode();
1996 toggle(_video, !_callShare && showVideoButton);
1997 if (_video) {
1998 _video->setStyle(st::groupCallVideo, &st::groupCallVideoActive);
1999 _video->moveToLeft(leftButtonLeft, buttonsTop);
2000 }
2001 toggle(_settings, !_callShare && !showVideoButton);
2002 if (_settings) {
2003 _settings->moveToLeft(leftButtonLeft, buttonsTop);
2004 }
2005 toggle(_hangup, true);
2006 _hangup->moveToRight(leftButtonLeft, buttonsTop);
2007 }
2008 if (_controlsBackgroundNarrow) {
2009 const auto left = st::groupCallMembersMargin.left();
2010 const auto width = (widget()->width()
2011 - st::groupCallMembersMargin.left()
2012 - st::groupCallMembersMargin.right());
2013 _controlsBackgroundNarrow->shadow.setGeometry(
2014 left,
2015 (widget()->height()
2016 - st::groupCallMembersMargin.bottom()
2017 - _controlsBackgroundNarrow->shadow.height()),
2018 width,
2019 _controlsBackgroundNarrow->shadow.height());
2020 _controlsBackgroundNarrow->blocker.setGeometry(
2021 left,
2022 (widget()->height()
2023 - st::groupCallMembersMargin.bottom()
2024 - st::groupCallMembersBottomSkip),
2025 width,
2026 st::groupCallMembersBottomSkip);
2027 }
2028 updateTooltipGeometry();
2029 }
2030
videoButtonInNarrowMode() const2031 bool Panel::videoButtonInNarrowMode() const {
2032 return (_video != nullptr) && !_call->mutedByAdmin();
2033 }
2034
updateMembersGeometry()2035 void Panel::updateMembersGeometry() {
2036 if (!_members) {
2037 return;
2038 }
2039 const auto desiredHeight = _members->desiredHeight();
2040 if (mode() == PanelMode::Wide) {
2041 const auto skip = st::groupCallNarrowSkip;
2042 const auto membersWidth = st::groupCallNarrowMembersWidth;
2043 const auto top = st::groupCallWideVideoTop;
2044 _members->setGeometry(
2045 widget()->width() - skip - membersWidth,
2046 top,
2047 membersWidth,
2048 std::min(desiredHeight, widget()->height() - top - skip));
2049 _viewport->setGeometry({
2050 skip,
2051 top,
2052 widget()->width() - membersWidth - 3 * skip,
2053 widget()->height() - top - skip,
2054 });
2055 } else {
2056 const auto membersBottom = widget()->height();
2057 const auto membersTop = st::groupCallMembersTop;
2058 const auto availableHeight = membersBottom
2059 - st::groupCallMembersMargin.bottom()
2060 - membersTop;
2061 const auto membersWidthAvailable = widget()->width()
2062 - st::groupCallMembersMargin.left()
2063 - st::groupCallMembersMargin.right();
2064 const auto membersWidthMin = st::groupCallWidth
2065 - st::groupCallMembersMargin.left()
2066 - st::groupCallMembersMargin.right();
2067 const auto membersWidth = std::clamp(
2068 membersWidthAvailable,
2069 membersWidthMin,
2070 st::groupCallMembersWidthMax);
2071 _members->setGeometry(
2072 (widget()->width() - membersWidth) / 2,
2073 membersTop,
2074 membersWidth,
2075 std::min(desiredHeight, availableHeight));
2076 }
2077 }
2078
refreshTitle()2079 void Panel::refreshTitle() {
2080 if (!_title) {
2081 auto text = rpl::combine(
2082 Info::Profile::NameValue(_peer),
2083 rpl::single(
2084 QString()
2085 ) | rpl::then(_call->real(
2086 ) | rpl::map([=](not_null<Data::GroupCall*> real) {
2087 return real->titleValue();
2088 }) | rpl::flatten_latest())
2089 ) | rpl::map([=](
2090 const TextWithEntities &name,
2091 const QString &title) {
2092 return title.isEmpty() ? name.text : title;
2093 }) | rpl::after_next([=] {
2094 refreshTitleGeometry();
2095 });
2096 _title.create(
2097 widget(),
2098 rpl::duplicate(text),
2099 st::groupCallTitleLabel);
2100 _title->show();
2101 _title->setAttribute(Qt::WA_TransparentForMouseEvents);
2102 }
2103 refreshTitleGeometry();
2104 if (!_subtitle && mode() == PanelMode::Default) {
2105 _subtitle.create(
2106 widget(),
2107 rpl::single(
2108 _call->scheduleDate()
2109 ) | rpl::then(
2110 _call->real(
2111 ) | rpl::map([=](not_null<Data::GroupCall*> real) {
2112 return real->scheduleDateValue();
2113 }) | rpl::flatten_latest()
2114 ) | rpl::map([=](TimeId scheduleDate) {
2115 if (scheduleDate) {
2116 return tr::lng_group_call_scheduled_status();
2117 } else if (!_members) {
2118 setupMembers();
2119 }
2120 return tr::lng_group_call_members(
2121 lt_count_decimal,
2122 _members->fullCountValue() | rpl::map([](int value) {
2123 return (value > 0) ? float64(value) : 1.;
2124 }));
2125 }) | rpl::flatten_latest(),
2126 st::groupCallSubtitleLabel);
2127 _subtitle->show();
2128 _subtitle->setAttribute(Qt::WA_TransparentForMouseEvents);
2129 }
2130 if (_subtitle) {
2131 const auto top = _title
2132 ? st::groupCallSubtitleTop
2133 : st::groupCallTitleTop;
2134 _subtitle->moveToLeft(
2135 (widget()->width() - _subtitle->width()) / 2,
2136 top);
2137 }
2138 }
2139
refreshTitleGeometry()2140 void Panel::refreshTitleGeometry() {
2141 if (!_title) {
2142 return;
2143 }
2144 const auto fullRect = computeTitleRect();
2145 const auto titleRect = _recordingMark
2146 ? QRect(
2147 fullRect.x(),
2148 fullRect.y(),
2149 fullRect.width() - _recordingMark->width(),
2150 fullRect.height())
2151 : fullRect;
2152 const auto best = _title->naturalWidth();
2153 const auto from = (widget()->width() - best) / 2;
2154 const auto top = (mode() == PanelMode::Default)
2155 ? st::groupCallTitleTop
2156 : (st::groupCallWideVideoTop
2157 - st::groupCallTitleLabel.style.font->height) / 2;
2158 const auto left = titleRect.x();
2159 if (from >= left && from + best <= left + titleRect.width()) {
2160 _title->resizeToWidth(best);
2161 _title->moveToLeft(from, top);
2162 } else if (titleRect.width() < best) {
2163 _title->resizeToWidth(titleRect.width());
2164 _title->moveToLeft(left, top);
2165 } else if (from < left) {
2166 _title->resizeToWidth(best);
2167 _title->moveToLeft(left, top);
2168 } else {
2169 _title->resizeToWidth(best);
2170 _title->moveToLeft(left + titleRect.width() - best, top);
2171 }
2172 if (_recordingMark) {
2173 const auto markTop = top + st::groupCallRecordingMarkTop;
2174 _recordingMark->move(
2175 _title->x() + _title->width(),
2176 markTop - st::groupCallRecordingMarkSkip);
2177 }
2178 }
2179
paint(QRect clip)2180 void Panel::paint(QRect clip) {
2181 Painter p(widget());
2182
2183 auto region = QRegion(clip);
2184 for (const auto &rect : region) {
2185 p.fillRect(rect, st::groupCallBg);
2186 }
2187 }
2188
handleClose()2189 bool Panel::handleClose() {
2190 if (_call) {
2191 window()->hide();
2192 return true;
2193 }
2194 return false;
2195 }
2196
window() const2197 not_null<Ui::RpWindow*> Panel::window() const {
2198 return _window.window();
2199 }
2200
widget() const2201 not_null<Ui::RpWidget*> Panel::widget() const {
2202 return _window.widget();
2203 }
2204
2205 } // namespace Calls::Group
2206