calls_group_panel.cpp 73 KB

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