From 50558de591d11f3303aa1b4fbe9e85e768cac53e Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 7 May 2021 19:37:28 +0400 Subject: [PATCH] Show name and information on wide large video. --- Telegram/CMakeLists.txt | 2 + .../Resources/icons/calls/voice_enlarge.png | Bin 0 -> 692 bytes .../icons/calls/voice_enlarge@2x.png | Bin 0 -> 1346 bytes .../icons/calls/voice_enlarge@3x.png | Bin 0 -> 1992 bytes .../Resources/icons/calls/voice_minimize.png | Bin 0 -> 546 bytes .../icons/calls/voice_minimize@2x.png | Bin 0 -> 1001 bytes .../icons/calls/voice_minimize@3x.png | Bin 0 -> 1409 bytes Telegram/Resources/icons/calls/voice_pin.png | Bin 0 -> 585 bytes .../Resources/icons/calls/voice_pin@2x.png | Bin 0 -> 961 bytes .../Resources/icons/calls/voice_pin@3x.png | Bin 0 -> 1336 bytes Telegram/SourceFiles/calls/calls.style | 45 ++++ .../calls/group/calls_group_call.cpp | 26 +- .../calls/group/calls_group_call.h | 23 +- .../calls/group/calls_group_large_video.cpp | 252 ++++++++++++++++++ .../calls/group/calls_group_large_video.h | 107 ++++++++ .../calls/group/calls_group_members.cpp | 196 +++++++------- .../calls/group/calls_group_members.h | 7 +- .../calls/group/calls_group_members_row.cpp | 49 +++- .../calls/group/calls_group_members_row.h | 22 +- .../calls/group/calls_group_panel.cpp | 99 ++----- .../calls/group/calls_group_panel.h | 3 +- Telegram/ThirdParty/tgcalls | 2 +- Telegram/lib_webrtc | 2 +- 23 files changed, 613 insertions(+), 222 deletions(-) create mode 100644 Telegram/Resources/icons/calls/voice_enlarge.png create mode 100644 Telegram/Resources/icons/calls/voice_enlarge@2x.png create mode 100644 Telegram/Resources/icons/calls/voice_enlarge@3x.png create mode 100644 Telegram/Resources/icons/calls/voice_minimize.png create mode 100644 Telegram/Resources/icons/calls/voice_minimize@2x.png create mode 100644 Telegram/Resources/icons/calls/voice_minimize@3x.png create mode 100644 Telegram/Resources/icons/calls/voice_pin.png create mode 100644 Telegram/Resources/icons/calls/voice_pin@2x.png create mode 100644 Telegram/Resources/icons/calls/voice_pin@3x.png create mode 100644 Telegram/SourceFiles/calls/group/calls_group_large_video.cpp create mode 100644 Telegram/SourceFiles/calls/group/calls_group_large_video.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 257a05ca29..f6e7bd8015 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -277,6 +277,8 @@ PRIVATE calls/group/calls_group_call.cpp calls/group/calls_group_call.h calls/group/calls_group_common.h + calls/group/calls_group_large_video.cpp + calls/group/calls_group_large_video.h calls/group/calls_group_members.cpp calls/group/calls_group_members.h calls/group/calls_group_members_row.cpp diff --git a/Telegram/Resources/icons/calls/voice_enlarge.png b/Telegram/Resources/icons/calls/voice_enlarge.png new file mode 100644 index 0000000000000000000000000000000000000000..19900113226d47aead30dd88081671776c052a4a GIT binary patch literal 692 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbB7>k44ofy`glX(f`xTHpSruq6Z zXaU(A42G$TlC0TWzSWdSpS4N|DZ_xKM31CyMmi(^Ox z=i6zv=NTOZjz70hUHgM8YHL?Zlb7S7h01{*08CDR&IN3wXMFSEo6U;-sVR~ z59`YCd9R(^qtBkmraje5=1h&Sbg9rxdRB^az&yJD)QC)>7EkD|GoGVK)tv;H&eeO7#8;n_%E PP>S(%^>bP0l+XkK@9-N7 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/calls/voice_enlarge@2x.png b/Telegram/Resources/icons/calls/voice_enlarge@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3c23f91e9acb1307aa57f5bc96d0aea9d8ab80e2 GIT binary patch literal 1346 zcmV-I1-<%-P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*ISF9{1$6iL~kY^0QxA{!+Z-g18PoPOsz z_hrtUpL5T-GxyHa>CByH=G^&yJoEP4!vz2exn!aca8t@<@jn6*5W^KDAPI;;fMJ#% zBmpr9FwD|}7=ZpT>|C15%F1ANb{2ASa(>;vhldB~?(T-8qa(v-xQJD_jpE+k9+a1t z8wxXHL`bEjrG`}_dWdCZWf{UjqmY0`M@Ql9?Tsle7!`yBbar+Ijg5`){{9{e28PcN z5)djnIXQu*rY89K_%IY`)hH|=6n=bs49(5W@cH>^l>@cC(11|Y;o%{)w6wt2*B4c! z6@&1AQ2fEc0SpcfTH!!#8y0{vGc%#Bt&J+uia~fl85tR{y}fN|S8W7PXh7-d>9Do6 z1$lXSMj9>IgawqEnhKknn~2xkBW+dsi`SyU6w&84@yi-gpG|2C@wCh>YA9Cfa&RJk9}oj z1^W8>JfHtJBMhK~gaj2%Nl6J+BZ8Tnob(*A*P1b)>g0f*Vos~HVDy7rI6K$i;GiscXz#n`GOi588O{Ia&}Yh?(Wp9uC9*R zj_ZhGY-}v7t*t>tMFn*@?A6QdTJi79al2t}-}u0mB+RUnOO+2Z1& zZD%m;OSzq-`}_M1;o!t;*8x?7gLiH{=<4bU($doOef8v*mzSZpw-*!zrx1G+w;n_j zk8_e!a1cGWfDnXl!cAgVSC=E6qyKK^Qb#H~UMD&_TEV@|<>jS9g7ivzd%NOxyB+Tb zw{wvGFBr-^w?izHQ1ss|0qJQ3$V)&0py-Q20;saG6887^;pXNB*4Nh|H#b+`C+_l& zt_(FbHOl?{z1QLM;^IQVLn$G{-Q}nU5jZeEKMxq?)2Hn0Y^biT))#S;cMK2>jz)s) zakMN~cSm}~f`czIot~a@1xD+P?SNQtUSD6;-_~DVUbH@ObvFeFe`dk!e^YI3ZNNE4 zdk(@OQ9U4&v{uCB-Eif=5G5G`>ebQFVOW=_^VkU-ztu_~D!skERmgjWp8EQ>oaKK+ z1?m{tt9UKRB-FqZMTS+lz)_e(EGa38aQjI;Jw0kX=BLCvXJ*d9VRe3f?%j~nPfS38 z`b~pmcFqw64r>1uAZ+Lh3E|Am&G~Htu`VWULKX=Q_S@2L^9TtOo~gnS4r*ft%*e`v z^$Zink6kW&lz$S{b2KHt9%9-9@E)#kP+L#{;e&h=dQN?Py)dhlZ~wK=wgA%L;2ZIx zCtCpR?Cd}|z`;MREr1#t8Vom+B6*c9fDnN1K1n1f`o7u*C|n@f3Md>P*$zm9L_#Hc z&lM&gl%2F0wcp?0P*hX|Pft%~s&i9^=|LDTj^xAyg#S8i71Hzbv-&oeXx@X4!5m_d z>^-QR=|K`uq!VE1mmVYmF$gfs(t{)*1_6dydJqH9U)2zL8to2-a{vGU07*qoM6N<$ Ef;YrZZ2$lO literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/calls/voice_enlarge@3x.png b/Telegram/Resources/icons/calls/voice_enlarge@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..dd75b153a57fafe7f3b0310ada73038b8de94752 GIT binary patch literal 1992 zcmV;(2RHbMP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91Y@h=G1ONa40RR91YybcN0QuY69smFbGD$>1RCodHoo`4KPZ-A^fBqCn z!#`Mv6&0Z(CRn5|RP06JD|?kx^tKn0QKVKce4zyqiGoN_AO>bZS(YXdy~sDd2tf%W zN-RtyNIFU?%onBJZ=d}oySMJ_&Rn;5Gq=ycakKM0d-Lq~b2B$Pv%8l70730VNf8D9 zQ6x?Z^N#`qOtG&50V80FDWF)m)WQguVhSi0F10WMrkDbXg-b1rfGMVcV&PH?Qw-R@ zipA@BxZAgHhbvdEKzVsNY}l~DlllUI0NlTSAKKd5;McETLUkl+-WP@Th-2sE@B47;t-@JLFL;L>yJHsP3#RSYj_t&pq0}j1^ z{P^ME6)8xGXBa@hIF#DEcQ4$#cMouuJ=TeU zxezERDbd%i($dmgJfmYKU=BJL7Z>ZWHg4SL;1yjH0b}T0R8#~50|Strp3d-yO)&wp z(Y>&+P=}R~kzr%CFY52_hvnsEdt(Gk37AF9f`S4aR#sM)#lqgabmgr%+ zWrgh+0Sf^$iI|s{2M-@UgzW5WQ>kB*BPaPs6ySY2Ip-svx_1niG^ zyLRn@M~@!CrcIl;<-B_JDqOpEEyN!$;yHEdlxNTgm?rm*9XoVbBG*`SbaX&xXJ-hk zwG9N1U8EP+i1;2DUWYh1I0!j8Ics{mz9Xpi_I5|V2M->=*|TRIeZn#N8rZgN+w_)| zo14q69d?1(74jsU9cgN6V!DRY0(}c?%a$!VtgTzOG6l67Zrr#5ILHu5ckbMQ3l}cf z?(**4JKJ)nLSJ6_i!EwsXb7-7bneQ@~jVcm?U4-*p;ELUc^ zuYlnw5MkjEo;TgPb&J`h@qR`m{d@xq&#!Pah$qLq34ediojd2r@SHaWtDi4`;hiYY zo;`!zyLVfUpSg{Tl?@FIQo+JteKATrefqQzEIfy5Y;2ScmT!O^I&_HHlB@($l9; z^|h>MEI>NN!DI8qC^0cH5pW<;QBko@li}fEsIRYw-@kvav&mY3GaYgN(x3&k#l@gD zybr#8`!=YpTLrZn9h5t6U$ih;TS_=_`QkS$9ARYLhk(gSh7lJ5V+bQ_F#(g63?nWA z#t=r{yjK538!Gg4?%mLrO}DZDYcl#w#l;VSawz)*df~N_2^Cun-2`zg1mb9WPv% z$00p1ts8V~S>_`XxmLrxpGtYDfnm$)>+557gI1wuXJ@TNaknikFrIGEOamXs@cj96 zb5UI8r35C@4X#nad$c}&{J6Fmx9`#c6YU0(4Bko7+uIwrkjw`r6|itx7Q%A7lf-<$ zs^`Oc4En)?2lc0%;lbnMyd&}3fv`jd7+AO8VBs-i5<2VFo@^E}%Yd^E=g*%P`V4EG z8m!>l+}yg)wa#dmxBs?IYYeJv3*Wuc)6)Z)nVH)083`ZCk9TFfc=5t0_AO`DAlgL{ET)u9LqrE1c;kT9RWU0w8wi$J4gP#Yfnl{g&KSy-L$*Mcv%A*2I zR5XIcR?O2bkzFm@La^A{phYTR1j`wgRKN(9J1l8{5iCAfQUD`Z{IH||CKfJv$9&Mi zGxtcC3)WWcxiSt6aLK;B|`z> zD}V9s{p93i{r^h5{7=XaA3i`!OAFw03@I532oE}pT`3m}1%$iASOiIR8VV3FLklHx z1WXA`Mvw%|&_anE0aF5#5hMXKv``{Pz?8sb1WCXQEtJR+FeNYJ-COha3dj)H{>ZvjmF;PR~(bVG-Q;Rf^G3KPf#kGdWj1Nzh4eg4YHC7uTbe)~CHo z&f1;H3fj|pGHv?Y|FdVFabspk3A=HDJNTdIKl_HLwQSu-nWmjq&6uSo(zfqlc-Z>u zgy6`5W)Q|J;$O%)yjn zHhcY}En%k}u9xyv)cx4kzby0Ak@tZi~{@bonWF=Y9E& zyzNfXVXH$Wc-Rib?OD+FH1mjb281Irt2%!aSorW=_MPjab}~xv z3xiAG5#{eT?}Il^nP0#ku_QoiYJ%D9jS8P7nH1zb16_Z{tL)kzFUt3W(egdV#mF1> Q|3UHN>FVdQ&MBb@0Hu!5$p8QV literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/calls/voice_minimize@2x.png b/Telegram/Resources/icons/calls/voice_minimize@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0705454d8e288dfd2fd204c11b4ad74d70a6bae7 GIT binary patch literal 1001 zcmVPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NFQ%OWYRA>e5na@iyK^Vu!ADKZ2 zVRh|bga;)g1i?!XBGI8sAVLKBwHj<}Y>WX84h|IIPfkt* zK(*X>0YuH?<0GWgX&^HUoxZ-l;Q07h4Vt#=FTifM(~QhAfse=IG#pRa90EQ+KOq*2 z@c>JG^cS$Ou)u6}!H`O&q+qSa5-;F(j?7t|F_|itq-FdEtc(;ojipF;oCIl?F#chL8px&8~JWci=4n!qXhW zn=|G`h6mo~XbC&BulS3`k(lLBCvySqCjR>e9z+n7rLX+gK!4fpr=iokRwd7IL)#ig@Yg%nJdLict)Z2b70ks>cyG@(>R{n+Ex+FnjYdP(;?jyvr^6i+>zUP;FTnQp zHsE7EDJ%4PJy{(Q`5(Y|W2g%V1Om`*w`HLDd>%G8H{t2&X~IU1PD~?UXJM1cN~!<5`+6FE3?)KA%rqsmYs_0pP+jlm!s@ZnrCC zhl`5~;YL}$wY3F>LIKE+{l^77JUqzY&(F_|1mk^{at}y1ZrN;>H=xme$^sIJ#Q4~c ztg2^cXGTMDKX2+A8U9xU4`qb#vG+JPs6#bKkEs@%44Z{$$*4Vd9QA=Vgrb@MZ)uNV8hqTl>38F1NUy=&dl8R|d zYmaI$YN={f(nin}oz_+*CSwd=Y$-bNGV?MI=bm%#Jumk@oWh_$9|d_Wc>n+ua9Ga} z31bf+D=n#^D^GkR1iBL9;{h~tk;{_CGd3I-7Z3m(m1tQ2bRz+fI@0H7$5*5 zVbH;h0seQ3WkCMZ2Sy6odldkrgg8(4lj)$9%HK`xf>rysQu<22qQ2!3`VGG28Gi}& zRVp%k=$b(ZQASZoGALENpUDx`u0fckFI5?TW+tlc=S3IppLZ}eS=@`&%xeJ`M|vBl zgbqn7k_jEqmlD+6lFQX7#fR?RCV~giP$(8OuQ+?s6c$2slyc;=^HlW^pumk5Cga10 z2z+X)m#(hv>+NmF;o;#q#!gttiLu@vGPAPuUCqtSr}$Gd$zrj1U`nvGv$K^W`1X~h9eB=Vhmt95sGH-$=7+9O)q*wi#N zDQIbH>pMB!X^q;Z99Bn%J_bWKH62V|c*@}OLnE1Vx(?RQ&nDE&$Vl4T+gr5R z_~!ll@*f@3F;1YGf$Hk&8PUhdNg|D=3NgIo;_NJL`e}Q3aj_P44u-&ki;9b36X^8b zwl**hhjTV0+1T5^y3}yExuxZJVq*RD5LmjQq>4EI`Lo>~yblJ0Q79CE$z)og(b10% znY%f}o~A>vqcXCfH(c((!a|G*?6_{zTkZlk7iFy1u1F=dDAzQ<&h5vWH@zVc)zsB{ z^71JQQnZQ+0h_qEv;_K^`RH~Pk%y_O?ds~9U0Bga{>|TFa5#SJo1538<`UM&keJef z26E-QPoGw%G)!V?sJh}VC@DyC%QKRpv0iqk3QqF(DED<^J<^%5Fk|=;#*U`=u76># z3$?D!xJG}D6d4|_u^Y5{iqhUNDHNJ!WMrf?gtdN`Q*9u+gT+dxsoB544#!`S7t(&zqjMXsm3vsQt9r*l2dEveK`&w->1spCih;W%O#JMMONCv>5D@u%t&8si@rt zQWTH6A>C4{GSBd@cI3`pf$d5CA?WPxHc(erXEm>tmYlUL*nXa!m?$rhzi@5Cu3fmi zoRX6xzd{hD5~xK*^7#XSFu}ORtc>{EgT~K7*|+XnTU!I=(TeG-&8h?!`N5pN@($vbDllJ}IrSh^w0-vy^)C zZ6By15D5RK!TR~l%~RWAT9jezO}P;cC;$F^15@epR0jtK>dC-x7NvO9Dx!_W@@BKy z{*kj{@rQPVX}0h@6benowg@bl;9@1rujfF5qRs2mNqLY%6I+I^GHD?YD4MnKO zCnj3AQ{ful1qBBy@%ogPeVNvQn*%BH&5s}Fve~{g#z9mF5b LE6}sq<6Pc9|F?f2 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/calls/voice_pin.png b/Telegram/Resources/icons/calls/voice_pin.png new file mode 100644 index 0000000000000000000000000000000000000000..4866e5f92bdd47edc0d1f1b2fe82cbfcd9703979 GIT binary patch literal 585 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 rmZi^4@BSTYZ<+&O1OK sink; -}; - struct GroupCall::SinkPointer { std::shared_ptr data; }; @@ -1847,18 +1839,18 @@ void GroupCall::ensureControllerCreated() { _videoEndpointLarge.changes( ) | rpl::start_with_next([=](const VideoEndpoint &endpoint) { _instance->setFullSizeVideoEndpointId(endpoint.endpoint); - _videoLargeTrack = nullptr; + _videoLargeTrack = LargeTrack(); _videoLargeTrackWrap = nullptr; - if (endpoint.empty()) { + if (!endpoint) { return; } - if (!_videoLargeTrackWrap) { - _videoLargeTrackWrap = std::make_unique(); - _videoLargeTrack = &_videoLargeTrackWrap->track; - } - _videoLargeTrackWrap->sink = Webrtc::CreateProxySink( - _videoLargeTrackWrap->track.sink()); - addVideoOutput(endpoint.endpoint, { _videoLargeTrackWrap->sink }); + _videoLargeTrackWrap = std::make_unique( + Webrtc::VideoState::Active); + _videoLargeTrack = LargeTrack{ + _videoLargeTrackWrap.get(), + endpoint.peer + }; + addVideoOutput(endpoint.endpoint, { _videoLargeTrackWrap->sink() }); }, _lifetime); updateInstanceMuteState(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index aa5c19c416..03ddc97af7 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -297,11 +297,25 @@ public: -> rpl::producer { return _videoEndpointLarge.value(); } - [[nodiscard]] Webrtc::VideoTrack *videoLargeTrack() const { + struct LargeTrack { + Webrtc::VideoTrack *track = nullptr; + PeerData *peer = nullptr; + + [[nodiscard]] explicit operator bool() const { + return (track != nullptr); + } + [[nodiscard]] bool operator==(LargeTrack other) const { + return (track == other.track) && (peer == other.peer); + } + [[nodiscard]] bool operator!=(LargeTrack other) const { + return !(*this == other); + } + }; + [[nodiscard]] LargeTrack videoLargeTrack() const { return _videoLargeTrack.current(); } [[nodiscard]] auto videoLargeTrackValue() const - -> rpl::producer { + -> rpl::producer { return _videoLargeTrack.value(); } [[nodiscard]] rpl::producer rejoinEvents() const { @@ -355,7 +369,6 @@ public: private: using GlobalShortcutValue = base::GlobalShortcutValue; - struct LargeTrack; struct SinkPointer; struct LoadingPart { @@ -524,8 +537,8 @@ private: base::flat_map _activeVideoEndpoints; rpl::variable _videoEndpointLarge; rpl::variable _videoEndpointPinned; - std::unique_ptr _videoLargeTrackWrap; - rpl::variable _videoLargeTrack; + std::unique_ptr _videoLargeTrackWrap; + rpl::variable _videoLargeTrack; base::flat_map _lastSpoke; rpl::event_stream _rejoinEvents; rpl::event_stream<> _allowedToSpeakNotifications; diff --git a/Telegram/SourceFiles/calls/group/calls_group_large_video.cpp b/Telegram/SourceFiles/calls/group/calls_group_large_video.cpp new file mode 100644 index 0000000000..9380c41c38 --- /dev/null +++ b/Telegram/SourceFiles/calls/group/calls_group_large_video.cpp @@ -0,0 +1,252 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "calls/group/calls_group_large_video.h" + +#include "calls/group/calls_group_members_row.h" +#include "media/view/media_view_pip.h" +#include "webrtc/webrtc_video_track.h" +#include "ui/painter.h" +#include "styles/style_calls.h" + +namespace Calls::Group { +namespace { + +constexpr auto kShadowMaxAlpha = 80; + +} // namespace + +LargeVideo::LargeVideo( + QWidget *parent, + const style::GroupCallLargeVideo &st, + bool visible, + rpl::producer track, + rpl::producer pinned) +: _content(parent, [=](QRect clip) { paint(clip); }) +, _st(st) +, _pin(st::groupCallLargeVideoPin) { + _content.setVisible(visible); + setup(std::move(track), std::move(pinned)); +} + +void LargeVideo::raise() { + _content.raise(); +} + +void LargeVideo::setVisible(bool visible) { + _content.setVisible(visible); +} + +void LargeVideo::setGeometry(int x, int y, int width, int height) { + _content.setGeometry(x, y, width, height); +} + +void LargeVideo::setup( + rpl::producer track, + rpl::producer pinned) { + _content.setAttribute(Qt::WA_OpaquePaintEvent); + + std::move(pinned) | rpl::start_with_next([=](bool pinned) { + _pinned = pinned; + _content.update(); + }, _content.lifetime()); + + rpl::combine( + _content.shownValue(), + std::move(track) + ) | rpl::map([=](bool shown, LargeVideoTrack track) { + return shown ? track : LargeVideoTrack(); + }) | rpl::distinct_until_changed( + ) | rpl::start_with_next([=](LargeVideoTrack track) { + _track = track; + _content.update(); + + _trackLifetime.destroy(); + if (!track.track) { + return; + } + track.track->renderNextFrame( + ) | rpl::start_with_next([=] { + const auto size = track.track->frameSize(); + if (size.isEmpty()) { + track.track->markFrameShown(); + } + _content.update(); + }, _trackLifetime); + }, _content.lifetime()); +} + +void LargeVideo::paint(QRect clip) { + auto p = Painter(&_content); + const auto [image, rotation] = _track + ? _track.track->frameOriginalWithRotation() + : std::pair(); + if (image.isNull()) { + p.fillRect(clip, Qt::black); + return; + } + auto hq = PainterHighQualityEnabler(p); + using namespace Media::View; + const auto size = _content.size(); + const auto scaled = FlipSizeByRotation( + image.size(), + rotation + ).scaled(size, Qt::KeepAspectRatio); + const auto left = (size.width() - scaled.width()) / 2; + const auto top = (size.height() - scaled.height()) / 2; + const auto target = QRect(QPoint(left, top), scaled); + if (UsePainterRotation(rotation)) { + if (rotation) { + p.save(); + p.rotate(rotation); + } + p.drawImage(RotatedRect(target, rotation), image); + if (rotation) { + p.restore(); + } + } else if (rotation) { + p.drawImage(target, RotateFrameImage(image, rotation)); + } else { + p.drawImage(target, image); + } + _track.track->markFrameShown(); + + const auto fill = [&](QRect rect) { + if (rect.intersects(clip)) { + p.fillRect(rect.intersected(clip), Qt::black); + } + }; + if (left > 0) { + fill({ 0, 0, left, size.height() }); + } + if (const auto right = left + scaled.width() + ; right < size.width()) { + fill({ right, 0, size.width() - right, size.height() }); + } + if (top > 0) { + fill({ 0, 0, size.width(), top }); + } + if (const auto bottom = top + scaled.height() + ; bottom < size.height()) { + fill({ 0, bottom, size.width(), size.height() - bottom }); + } + + paintControls(p, clip); +} + +void LargeVideo::paintControls(Painter &p, QRect clip) { + const auto width = _content.width(); + const auto height = _content.height(); + + const auto topControls = (_st.controlsAlign == style::al_top); + if (_shadow.isNull()) { + if (topControls) { + _shadow = GenerateShadow(_st.shadowHeight, kShadowMaxAlpha, 0); + } else { + _shadow = GenerateShadow(_st.shadowHeight, 0, kShadowMaxAlpha); + } + } + const auto shadowRect = QRect( + 0, + topControls ? 0 : (height - _st.shadowHeight), + width, + _st.shadowHeight); + const auto shadowFill = shadowRect.intersected(clip); + if (shadowFill.isEmpty()) { + return; + } + const auto factor = style::DevicePixelRatio(); + p.drawImage( + shadowFill, + _shadow, + QRect( + 0, + (shadowFill.y() - shadowRect.y()) * factor, + _shadow.width(), + shadowFill.height() * factor)); + _track.row->lazyInitialize(st::groupCallMembersListItem); + + p.setPen(topControls + ? st::groupCallVideoTextFg + : st::groupCallVideoSubTextFg); + const auto hasWidth = width + - (topControls ? _st.pinPosition.x() : _st.iconPosition.x()) + - _st.namePosition.x(); + const auto nameLeft = _st.namePosition.x(); + const auto nameTop = topControls + ? _st.namePosition.y() + : (height - _st.namePosition.y()); + _track.row->name().drawLeftElided(p, nameLeft, nameTop, hasWidth, width); + + p.setPen(st::groupCallVideoSubTextFg); + const auto statusLeft = _st.statusPosition.x(); + const auto statusTop = topControls + ? _st.statusPosition.y() + : (height - _st.statusPosition.y()); + _track.row->paintComplexStatusText( + p, + st::groupCallLargeVideoListItem, + statusLeft, + statusTop, + hasWidth, + width, + false, + MembersRowStyle::LargeVideo); + + const auto &icon = st::groupCallLargeVideoCrossLine.icon; + const auto iconLeft = width - _st.iconPosition.x() - icon.width(); + const auto iconTop = topControls + ? _st.iconPosition.y() + : (height - _st.iconPosition.y()); + _track.row->paintMuteIcon( + p, + { iconLeft, iconTop, icon.width(), icon.height() }, + MembersRowStyle::LargeVideo); + + const auto pinWidth = st::groupCallLargeVideoPin.icon.width(); + const auto pinLeft = topControls + ? (width - _st.pinPosition.x() - pinWidth) + : _st.pinPosition.x(); + const auto pinTop = topControls + ? _st.pinPosition.y() + : (height - _st.pinPosition.y()); + _pin.paint(p, pinLeft, pinTop, _pinned ? 1. : 0.); +} + +QImage GenerateShadow(int height, int topAlpha, int bottomAlpha) { + Expects(topAlpha >= 0 && topAlpha < 256); + Expects(bottomAlpha >= 0 && bottomAlpha < 256); + Expects(height * style::DevicePixelRatio() < 65536); + + auto result = QImage( + QSize(1, height * style::DevicePixelRatio()), + QImage::Format_ARGB32_Premultiplied); + if (topAlpha == bottomAlpha) { + result.fill(QColor(0, 0, 0, topAlpha)); + return result; + } + constexpr auto kShift = 16; + constexpr auto kMultiply = (1U << kShift); + const auto values = std::abs(topAlpha - bottomAlpha); + const auto rows = uint32(result.height()); + const auto step = (values * kMultiply) / (rows - 1); + const auto till = rows * uint32(step); + Assert(result.bytesPerLine() == sizeof(uint32)); + auto ints = reinterpret_cast(result.bits()); + if (topAlpha < bottomAlpha) { + for (auto i = uint32(0); i != till; i += step) { + *ints++ = ((topAlpha + (i >> kShift)) << 24); + } + } else { + for (auto i = uint32(0); i != till; i += step) { + *ints++ = ((topAlpha - (i >> kShift)) << 24); + } + } + return result; +} + +} // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_large_video.h b/Telegram/SourceFiles/calls/group/calls_group_large_video.h new file mode 100644 index 0000000000..551e45c7e0 --- /dev/null +++ b/Telegram/SourceFiles/calls/group/calls_group_large_video.h @@ -0,0 +1,107 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/rp_widget.h" +#include "ui/effects/cross_line.h" + +#if defined Q_OS_MAC +#define USE_OPENGL_LARGE_VIDEO +#endif // Q_OS_MAC + +namespace style { +struct GroupCallLargeVideo; +} // namespace style + +namespace Webrtc { +class VideoTrack; +} // namespace Webrtc + +namespace Calls::Group { + +class MembersRow; + +struct LargeVideoTrack { + Webrtc::VideoTrack *track = nullptr; + MembersRow *row = nullptr; + + [[nodiscard]] explicit operator bool() const { + return track != nullptr; + } +}; + +[[nodiscard]] inline bool operator==( + LargeVideoTrack a, + LargeVideoTrack b) noexcept { + return (a.track == b.track) && (a.row == b.row); +} + +[[nodiscard]] inline bool operator!=( + LargeVideoTrack a, + LargeVideoTrack b) noexcept { + return !(a == b); +} + +class LargeVideo final { +public: + LargeVideo( + QWidget *parent, + const style::GroupCallLargeVideo &st, + bool visible, + rpl::producer track, + rpl::producer pinned); + + void raise(); + void setVisible(bool visible); + void setGeometry(int x, int y, int width, int height); + +private: +#ifdef USE_OPENGL_LARGE_VIDEO + using ContentParent = Ui::RpWidgetWrap; +#else // USE_OPENGL_OVERLAY_WIDGET + using ContentParent = Ui::RpWidget; +#endif // USE_OPENGL_OVERLAY_WIDGET + + class Content final : public ContentParent { + public: + Content(QWidget *parent, Fn paint) + : ContentParent(parent), _paint(std::move(paint)) { + Expects(_paint != nullptr); + } + + private: + void paintEvent(QPaintEvent *e) override { + _paint(e->rect()); + } + + Fn _paint; + + }; + + void setup( + rpl::producer track, + rpl::producer pinned); + void paint(QRect clip); + void paintControls(Painter &p, QRect clip); + + Content _content; + const style::GroupCallLargeVideo &_st; + LargeVideoTrack _track; + QImage _shadow; + Ui::CrossLineAnimation _pin; + bool _pinned = false; + rpl::lifetime _trackLifetime; + +}; + +[[nodiscard]] QImage GenerateShadow( + int height, + int topAlpha, + int bottomAlpha); + +} // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index 0ed4d35e58..5dd8688d22 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/group/calls_group_menu.h" #include "calls/group/calls_volume_item.h" #include "calls/group/calls_group_members_row.h" +#include "calls/group/calls_group_large_video.h" #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_user.h" @@ -43,15 +44,17 @@ constexpr auto kShadowMaxAlpha = 74; using Row = MembersRow; -class MembersController final +} // namespace + +class Members::Controller final : public PeerListController , public MembersRowDelegate , public base::has_weak_ptr { public: - MembersController( + Controller( not_null call, not_null menuParent); - ~MembersController(); + ~Controller(); using MuteRequest = Group::MuteRequest; using VolumeRequest = Group::VolumeRequest; @@ -73,6 +76,8 @@ public: [[nodiscard]] auto kickParticipantRequests() const -> rpl::producer>; + Row *findRow(not_null participantPeer) const; + bool rowIsMe(not_null participantPeer) override; bool rowCanMuteMembers() override; void rowUpdateRow(not_null row) override; @@ -145,7 +150,6 @@ private: [[nodiscard]] bool allRowsAboveMoreImportantThanHand( not_null row, uint64 raiseHandRating) const; - Row *findRow(not_null participantPeer) const; const Data::GroupCallParticipant *findParticipant( const std::string &endpoint) const; const std::string &computeScreenEndpoint( @@ -189,6 +193,7 @@ private: Ui::CrossLineAnimation _inactiveNarrowCrossLine; Ui::CrossLineAnimation _coloredNarrowCrossLine; Ui::CrossLineAnimation _videoNarrowCrossLine; + Ui::CrossLineAnimation _videoLargeCrossLine; Ui::RoundRect _narrowRoundRectSelected; Ui::RoundRect _narrowRoundRect; QImage _narrowShadow; @@ -197,7 +202,7 @@ private: }; -MembersController::MembersController( +Members::Controller::Controller( not_null call, not_null menuParent) : _call(call) @@ -209,6 +214,7 @@ MembersController::MembersController( , _inactiveNarrowCrossLine(st::groupCallNarrowInactiveCrossLine) , _coloredNarrowCrossLine(st::groupCallNarrowColoredCrossLine) , _videoNarrowCrossLine(st::groupCallVideoCrossLine) +, _videoLargeCrossLine(st::groupCallLargeVideoCrossLine) , _narrowRoundRectSelected( ImageRoundRadius::Large, st::groupCallMembersBgOver) @@ -267,11 +273,11 @@ MembersController::MembersController( }, _lifetime); } -MembersController::~MembersController() { +Members::Controller::~Controller() { base::take(_menu); } -void MembersController::setRowVideoEndpoint( +void Members::Controller::setRowVideoEndpoint( not_null row, const std::string &endpoint) { const auto was = row->videoTrackEndpoint(); @@ -290,7 +296,7 @@ void MembersController::setRowVideoEndpoint( } } -void MembersController::setupListChangeViewers() { +void Members::Controller::setupListChangeViewers() { _call->real( ) | rpl::start_with_next([=](not_null real) { subscribeToChanges(real); @@ -413,7 +419,7 @@ void MembersController::setupListChangeViewers() { }, _lifetime); } -void MembersController::subscribeToChanges(not_null real) { +void Members::Controller::subscribeToChanges(not_null real) { _fullCount = real->fullCountValue(); real->participantsReloaded( @@ -449,30 +455,7 @@ void MembersController::subscribeToChanges(not_null real) { } } -void MembersController::generateNarrowShadow() { - const auto factor = style::DevicePixelRatio(); - _narrowShadow = QImage( - QSize(1, st::groupCallNarrowShadowHeight) * factor, - QImage::Format_ARGB32_Premultiplied); - const auto height = uint32(_narrowShadow.height()); - constexpr auto kShift = 24; - constexpr auto kMultiply = (1U << kShift); - const auto step = 1 + ((kShadowMaxAlpha * kMultiply) / (height - 1)); - const auto till = height * uint32(step); - auto ints = reinterpret_cast(_narrowShadow.bits()); - const auto perline = _narrowShadow.bytesPerLine() / sizeof(uint32); - for (auto i = uint32(0); i != till; i += step) { - const auto alpha = (i >> kShift); - const auto color = alpha << 24; - ranges::fill( - gsl::span{ ints, size_t(_narrowShadow.width()) }, - color); - ints += perline; - LOG(("ALPHA: %1").arg(alpha)); - } -} - -void MembersController::appendInvitedUsers() { +void Members::Controller::appendInvitedUsers() { if (const auto id = _call->id()) { for (const auto user : _peer->owner().invitedToCallUsers(id)) { if (auto row = createInvitedRow(user)) { @@ -494,7 +477,7 @@ void MembersController::appendInvitedUsers() { }, _lifetime); } -void MembersController::updateRow( +void Members::Controller::updateRow( const std::optional &was, const Data::GroupCallParticipant &now) { auto reorderIfInvitedBefore = 0; @@ -561,7 +544,7 @@ void MembersController::updateRow( } } -bool MembersController::allRowsAboveAreSpeaking(not_null row) const { +bool Members::Controller::allRowsAboveAreSpeaking(not_null row) const { const auto count = delegate()->peerListFullRowsCount(); for (auto i = 0; i != count; ++i) { const auto above = delegate()->peerListRowAt(i); @@ -575,7 +558,7 @@ bool MembersController::allRowsAboveAreSpeaking(not_null row) const { return false; } -bool MembersController::allRowsAboveMoreImportantThanHand( +bool Members::Controller::allRowsAboveMoreImportantThanHand( not_null row, uint64 raiseHandRating) const { Expects(raiseHandRating > 0); @@ -598,7 +581,7 @@ bool MembersController::allRowsAboveMoreImportantThanHand( return false; } -bool MembersController::needToReorder(not_null row) const { +bool Members::Controller::needToReorder(not_null row) const { // All reorder cases: // - bring speaking up // - bring raised hand up @@ -634,7 +617,7 @@ bool MembersController::needToReorder(not_null row) const { return false; } -void MembersController::checkRowPosition(not_null row) { +void Members::Controller::checkRowPosition(not_null row) { if (_menu) { // Don't reorder rows while we show the popup menu. _menuCheckRowsAfterHidden.emplace(row->peer()); @@ -680,7 +663,7 @@ void MembersController::checkRowPosition(not_null row) { : makeComparator(projForOther)); } -void MembersController::updateRow( +void Members::Controller::updateRow( not_null row, const Data::GroupCallParticipant *participant) { const auto wasSounding = row->sounding(); @@ -717,12 +700,12 @@ void MembersController::updateRow( delegate()->peerListUpdateRow(row); } -void MembersController::removeRow(not_null row) { +void Members::Controller::removeRow(not_null row) { _soundingRowBySsrc.remove(row->ssrc()); delegate()->peerListRemoveRow(row); } -void MembersController::updateRowLevel( +void Members::Controller::updateRowLevel( not_null row, float level) { if (_skipRowLevelUpdate) { @@ -731,12 +714,13 @@ void MembersController::updateRowLevel( row->updateLevel(level); } -Row *MembersController::findRow(not_null participantPeer) const { +Row *Members::Controller::findRow( + not_null participantPeer) const { return static_cast( delegate()->peerListFindRow(participantPeer->id.value)); } -const Data::GroupCallParticipant *MembersController::findParticipant( +const Data::GroupCallParticipant *Members::Controller::findParticipant( const std::string &endpoint) const { if (endpoint.empty()) { return nullptr; @@ -757,25 +741,25 @@ const Data::GroupCallParticipant *MembersController::findParticipant( } } -const std::string &MembersController::computeScreenEndpoint( +const std::string &Members::Controller::computeScreenEndpoint( not_null participant) const { return (participant->peer == _call->joinAs()) ? _call->screenSharingEndpoint() : participant->screenEndpoint(); } -const std::string &MembersController::computeCameraEndpoint( +const std::string &Members::Controller::computeCameraEndpoint( not_null participant) const { return (participant->peer == _call->joinAs()) ? _call->cameraSharingEndpoint() : participant->cameraEndpoint(); } -Main::Session &MembersController::session() const { +Main::Session &Members::Controller::session() const { return _call->peer()->session(); } -void MembersController::prepare() { +void Members::Controller::prepare() { delegate()->peerListSetSearchMode(PeerListSearchMode::Disabled); //delegate()->peerListSetTitle(std::move(title)); setDescriptionText(tr::lng_contacts_loading(tr::now)); @@ -793,11 +777,11 @@ void MembersController::prepare() { _prepared = true; } -bool MembersController::isMe(not_null participantPeer) const { +bool Members::Controller::isMe(not_null participantPeer) const { return (_call->joinAs() == participantPeer); } -void MembersController::prepareRows(not_null real) { +void Members::Controller::prepareRows(not_null real) { auto foundMe = false; auto changed = false; const auto &participants = real->participants(); @@ -847,35 +831,35 @@ void MembersController::prepareRows(not_null real) { } } -void MembersController::loadMoreRows() { +void Members::Controller::loadMoreRows() { if (const auto real = _call->lookupReal()) { real->requestParticipants(); } } -auto MembersController::toggleMuteRequests() const +auto Members::Controller::toggleMuteRequests() const -> rpl::producer { return _toggleMuteRequests.events(); } -auto MembersController::changeVolumeRequests() const +auto Members::Controller::changeVolumeRequests() const -> rpl::producer { return _changeVolumeRequests.events(); } -bool MembersController::rowIsMe(not_null participantPeer) { +bool Members::Controller::rowIsMe(not_null participantPeer) { return isMe(participantPeer); } -bool MembersController::rowCanMuteMembers() { +bool Members::Controller::rowCanMuteMembers() { return _peer->canManageGroupCall(); } -void MembersController::rowUpdateRow(not_null row) { +void Members::Controller::rowUpdateRow(not_null row) { delegate()->peerListUpdateRow(row); } -void MembersController::rowScheduleRaisedHandStatusRemove( +void Members::Controller::rowScheduleRaisedHandStatusRemove( not_null row) { const auto id = row->id(); const auto when = crl::now() + kKeepRaisedHandStatusDuration; @@ -888,7 +872,7 @@ void MembersController::rowScheduleRaisedHandStatusRemove( scheduleRaisedHandStatusRemove(); } -void MembersController::scheduleRaisedHandStatusRemove() { +void Members::Controller::scheduleRaisedHandStatusRemove() { auto waiting = crl::time(0); const auto now = crl::now(); for (auto i = begin(_raisedHandStatusRemoveAt) @@ -913,13 +897,16 @@ void MembersController::scheduleRaisedHandStatusRemove() { } } -void MembersController::rowPaintIcon( +void Members::Controller::rowPaintIcon( Painter &p, QRect rect, const IconState &state) { - const auto narrowUserpic = (state.narrowStyle == NarrowStyle::Userpic); - const auto narrowVideo = (state.narrowStyle == NarrowStyle::Video); - const auto &greenIcon = narrowVideo + const auto narrowUserpic = (state.style == MembersRowStyle::Userpic); + const auto narrowVideo = (state.style == MembersRowStyle::Video); + const auto largeVideo = (state.style == MembersRowStyle::LargeVideo); + const auto &greenIcon = largeVideo + ? st::groupCallLargeVideoCrossLine.icon + : narrowVideo ? st::groupCallVideoCrossLine.icon : narrowUserpic ? st::groupCallNarrowColoredCrossLine.icon @@ -933,7 +920,9 @@ void MembersController::rowPaintIcon( } else if (state.speaking == 0.) { if (state.active == 1.) { // Just gray icon, no cross, no coloring. - const auto &grayIcon = narrowVideo + const auto &grayIcon = largeVideo + ? st::groupCallLargeVideoCrossLine.icon + : narrowVideo ? st::groupCallVideoCrossLine.icon : narrowUserpic ? st::groupCallNarrowInactiveCrossLine.icon @@ -948,12 +937,14 @@ void MembersController::rowPaintIcon( return; } // Red crossed icon, colorized once, cached as last frame. - auto &line = narrowVideo + auto &line = largeVideo + ? _videoLargeCrossLine + : narrowVideo ? _videoNarrowCrossLine : narrowUserpic ? _coloredNarrowCrossLine : _coloredCrossLine; - const auto color = narrowVideo + const auto color = (largeVideo || narrowVideo) ? std::nullopt : std::make_optional(st::groupCallMemberMutedIcon->c); line.paint( @@ -965,7 +956,9 @@ void MembersController::rowPaintIcon( return; } else if (state.muted == 0.) { // Gray crossed icon, no coloring, cached as last frame. - auto &line = narrowVideo + auto &line = largeVideo + ? _videoLargeCrossLine + : narrowVideo ? _videoNarrowCrossLine : narrowUserpic ? _inactiveNarrowCrossLine @@ -985,14 +978,16 @@ void MembersController::rowPaintIcon( activeInactiveColor, st::groupCallMemberMutedIcon, state.muted); - const auto color = narrowVideo + const auto color = (largeVideo || narrowVideo) ? std::nullopt : std::make_optional(iconColor); // Don't use caching of the last frame, // because 'muted' may animate color. const auto crossProgress = std::min(1. - state.active, 0.9999); - auto &line = narrowVideo + auto &line = largeVideo + ? _videoLargeCrossLine + : narrowVideo ? _videoNarrowCrossLine : narrowUserpic ? _inactiveNarrowCrossLine @@ -1000,7 +995,7 @@ void MembersController::rowPaintIcon( line.paint(p, left, top, crossProgress, color); } -void MembersController::rowPaintNarrowBackground( +void Members::Controller::rowPaintNarrowBackground( Painter &p, int x, int y, @@ -1010,7 +1005,7 @@ void MembersController::rowPaintNarrowBackground( { QPoint(x, y), st::groupCallNarrowSize }); } -void MembersController::rowPaintNarrowBorder( +void Members::Controller::rowPaintNarrowBorder( Painter &p, int x, int y, @@ -1029,14 +1024,17 @@ void MembersController::rowPaintNarrowBorder( st::roundRadiusLarge); } -void MembersController::rowPaintNarrowShadow( +void Members::Controller::rowPaintNarrowShadow( Painter &p, int x, int y, int sizew, int sizeh) { if (_narrowShadow.isNull()) { - generateNarrowShadow(); + _narrowShadow = GenerateShadow( + st::groupCallNarrowShadowHeight, + 0, + kShadowMaxAlpha); } const auto height = st::groupCallNarrowShadowHeight; p.drawImage( @@ -1044,11 +1042,11 @@ void MembersController::rowPaintNarrowShadow( _narrowShadow); } -int MembersController::customRowHeight() { +int Members::Controller::customRowHeight() { return st::groupCallNarrowSize.height() + st::groupCallNarrowRowSkip * 2; } -void MembersController::customRowPaint( +void Members::Controller::customRowPaint( Painter &p, crl::time now, not_null row, @@ -1067,7 +1065,7 @@ void MembersController::customRowPaint( selected); } -bool MembersController::customRowSelectionPoint( +bool Members::Controller::customRowSelectionPoint( not_null row, int x, int y) { @@ -1077,7 +1075,7 @@ bool MembersController::customRowSelectionPoint( && y < st::groupCallNarrowRowSkip + st::groupCallNarrowSize.height(); } -Fn MembersController::customRowRippleMaskGenerator() { +Fn Members::Controller::customRowRippleMaskGenerator() { return [] { return Ui::RippleAnimation::roundRectMask( st::groupCallNarrowSize, @@ -1085,12 +1083,12 @@ Fn MembersController::customRowRippleMaskGenerator() { }; } -auto MembersController::kickParticipantRequests() const +auto Members::Controller::kickParticipantRequests() const -> rpl::producer>{ return _kickParticipantRequests.events(); } -void MembersController::rowClicked(not_null row) { +void Members::Controller::rowClicked(not_null row) { delegate()->peerListShowRowMenu(row, [=](not_null menu) { if (!_menu || _menu.get() != menu) { return; @@ -1105,12 +1103,12 @@ void MembersController::rowClicked(not_null row) { }); } -void MembersController::rowActionClicked( +void Members::Controller::rowActionClicked( not_null row) { rowClicked(row); } -base::unique_qptr MembersController::rowContextMenu( +base::unique_qptr Members::Controller::rowContextMenu( QWidget *parent, not_null row) { auto result = createRowContextMenu(parent, row); @@ -1127,7 +1125,7 @@ base::unique_qptr MembersController::rowContextMenu( return result; } -base::unique_qptr MembersController::createRowContextMenu( +base::unique_qptr Members::Controller::createRowContextMenu( QWidget *parent, not_null row) { const auto participantPeer = row->peer(); @@ -1278,7 +1276,7 @@ base::unique_qptr MembersController::createRowContextMenu( return result; } -void MembersController::addMuteActionsToContextMenu( +void Members::Controller::addMuteActionsToContextMenu( not_null menu, not_null participantPeer, bool participantIsCallAdmin, @@ -1399,20 +1397,20 @@ void MembersController::addMuteActionsToContextMenu( } } -std::unique_ptr MembersController::createRowForMe() { +std::unique_ptr Members::Controller::createRowForMe() { auto result = std::make_unique(this, _call->joinAs()); updateRow(result.get(), nullptr); return result; } -std::unique_ptr MembersController::createRow( +std::unique_ptr Members::Controller::createRow( const Data::GroupCallParticipant &participant) { auto result = std::make_unique(this, participant.peer); updateRow(result.get(), &participant); return result; } -std::unique_ptr MembersController::createInvitedRow( +std::unique_ptr Members::Controller::createInvitedRow( not_null participantPeer) { if (findRow(participantPeer)) { return nullptr; @@ -1422,15 +1420,13 @@ std::unique_ptr MembersController::createInvitedRow( return result; } -} // namespace - Members::Members( not_null parent, not_null call) : RpWidget(parent) , _call(call) , _scroll(this) -, _listController(std::make_unique(call, parent)) +, _listController(std::make_unique(call, parent)) , _layout(_scroll->setOwnedWidget( object_ptr(_scroll.data()))) , _pinnedVideo(_layout->add(object_ptr(_layout.get()))) { @@ -1442,22 +1438,21 @@ Members::Members( _listController->setDelegate(static_cast(this)); } +Members::~Members() = default; + auto Members::toggleMuteRequests() const -> rpl::producer { - return static_cast( - _listController.get())->toggleMuteRequests(); + return _listController->toggleMuteRequests(); } auto Members::changeVolumeRequests() const -> rpl::producer { - return static_cast( - _listController.get())->changeVolumeRequests(); + return _listController->changeVolumeRequests(); } auto Members::kickParticipantRequests() const -> rpl::producer> { - return static_cast( - _listController.get())->kickParticipantRequests(); + return _listController->kickParticipantRequests(); } int Members::desiredHeight() const { @@ -1480,12 +1475,10 @@ int Members::desiredHeight() const { } rpl::producer Members::desiredHeightValue() const { - const auto controller = static_cast( - _listController.get()); return rpl::combine( heightValue(), _addMemberButton.value(), - controller->fullCountValue() + _listController->fullCountValue() ) | rpl::map([=] { return desiredHeight(); }); @@ -1572,6 +1565,10 @@ void Members::setupAddMember(not_null call) { }, lifetime()); } +Row *Members::lookupRow(not_null peer) const { + return _listController->findRow(peer); +} + void Members::setMode(PanelMode mode) { if (_mode.current() == mode) { return; @@ -1583,8 +1580,7 @@ void Members::setMode(PanelMode mode) { } rpl::producer Members::fullCountValue() const { - return static_cast( - _listController.get())->fullCountValue(); + return _listController->fullCountValue(); } void Members::setupList() { @@ -1624,8 +1620,8 @@ void Members::setupPinnedVideo() { rpl::combine( _mode.value(), _call->videoLargeTrackValue() - ) | rpl::map([](PanelMode mode, Webrtc::VideoTrack *track) { - return (mode == PanelMode::Default) ? track : nullptr; + ) | rpl::map([](PanelMode mode, GroupCall::LargeTrack track) { + return (mode == PanelMode::Default) ? track.track : nullptr; }) | rpl::distinct_until_changed( ) | rpl::start_with_next([=](Webrtc::VideoTrack *track) { _pinnedTrackLifetime.destroy(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.h b/Telegram/SourceFiles/calls/group/calls_group_members.h index 5c43d8cf8b..7a830b68a1 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.h +++ b/Telegram/SourceFiles/calls/group/calls_group_members.h @@ -26,6 +26,7 @@ class GroupCall; namespace Calls::Group { +class MembersRow; struct VolumeRequest; struct MuteRequest; enum class PanelMode; @@ -37,6 +38,7 @@ public: Members( not_null parent, not_null call); + ~Members(); [[nodiscard]] int desiredHeight() const; [[nodiscard]] rpl::producer desiredHeightValue() const override; @@ -51,9 +53,12 @@ public: return _addMemberRequests.events(); } + [[nodiscard]] MembersRow *lookupRow(not_null peer) const; + void setMode(PanelMode mode); private: + class Controller; using ListWidget = PeerListContent; void resizeEvent(QResizeEvent *e) override; @@ -84,7 +89,7 @@ private: const not_null _call; rpl::variable _mode = PanelMode(); object_ptr _scroll; - std::unique_ptr _listController; + std::unique_ptr _listController; not_null _layout; const not_null _pinnedVideo; rpl::variable _addMemberButton = nullptr; diff --git a/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp b/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp index 965f86e20d..eca54186b3 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp @@ -495,20 +495,27 @@ void MembersRow::paintScaledUserpic( _blobsAnimation->userpicCache); } +void MembersRow::paintMuteIcon( + Painter &p, + QRect iconRect, + MembersRowStyle style) { + _delegate->rowPaintIcon(p, iconRect, computeIconState(style)); +} + void MembersRow::paintNarrowName( Painter &p, int x, int y, int sizew, int sizeh, - NarrowStyle style) { + MembersRowStyle style) { if (_narrowName.isEmpty()) { _narrowName.setText( st::semiboldTextStyle, generateShortName(), Ui::NameTextOptions()); } - if (style == NarrowStyle::Video) { + if (style == MembersRowStyle::Video) { _delegate->rowPaintNarrowShadow(p, x, y, sizew, sizeh); } const auto &icon = st::groupCallVideoCrossLine.icon; @@ -525,7 +532,7 @@ void MembersRow::paintNarrowName( _delegate->rowPaintIcon(p, iconRect, state); p.setPen([&] { - if (style == NarrowStyle::Video) { + if (style == MembersRowStyle::Video) { return st::groupCallVideoTextFg->p; } else if (state.speaking == 1. && !state.mutedByMe) { return st::groupCallMemberActiveIcon->p; @@ -580,7 +587,7 @@ void MembersRow::paintComplexUserpic( bool selected) { if (mode == PanelMode::Wide) { if (paintVideo(p, x, y, sizew, sizeh, mode)) { - paintNarrowName(p, x, y, sizew, sizeh, NarrowStyle::Video); + paintNarrowName(p, x, y, sizew, sizeh, MembersRowStyle::Video); _delegate->rowPaintNarrowBorder(p, x, y, this); return; } @@ -602,7 +609,7 @@ void MembersRow::paintComplexUserpic( sizeh, mode); if (mode == PanelMode::Wide) { - paintNarrowName(p, x, y, sizew, sizeh, NarrowStyle::Userpic); + paintNarrowName(p, x, y, sizew, sizeh, MembersRowStyle::Userpic); _delegate->rowPaintNarrowBorder(p, x, y, this); } } @@ -699,6 +706,26 @@ void MembersRow::paintStatusText( int availableWidth, int outerWidth, bool selected) { + paintComplexStatusText( + p, + st, + x, + y, + availableWidth, + outerWidth, + selected, + MembersRowStyle::None); +} + +void MembersRow::paintComplexStatusText( + Painter &p, + const style::PeerListItem &st, + int x, + int y, + int availableWidth, + int outerWidth, + bool selected, + MembersRowStyle style) { const auto &font = st::normalFont; const auto about = (_state == State::Inactive || _state == State::Muted @@ -727,7 +754,9 @@ void MembersRow::paintStatusText( return; } p.setFont(font); - if (_state == State::MutedByMe) { + if (style == MembersRowStyle::LargeVideo) { + p.setPen(st::groupCallVideoSubTextFg); + } else if (_state == State::MutedByMe) { p.setPen(st::groupCallMemberMutedIcon); } else { p.setPen(st::groupCallMemberNotJoinedStatus); @@ -738,7 +767,7 @@ void MembersRow::paintStatusText( outerWidth, (_state == State::MutedByMe ? tr::lng_group_call_muted_by_me_status(tr::now) - : !about.isEmpty() + : (!about.isEmpty() && style != MembersRowStyle::LargeVideo) ? font->m.elidedText(about, Qt::ElideRight, availableWidth) : _delegate->rowIsMe(peer()) ? tr::lng_status_connecting(tr::now) @@ -797,11 +826,11 @@ void MembersRow::paintAction( _actionRipple.reset(); } } - _delegate->rowPaintIcon(p, iconRect, computeIconState()); + paintMuteIcon(p, iconRect); } MembersRowDelegate::IconState MembersRow::computeIconState( - NarrowStyle style) const { + MembersRowStyle style) const { const auto speaking = _speakingAnimation.value(_speaking ? 1. : 0.); const auto active = _activeAnimation.value( (_state == State::Active) ? 1. : 0.); @@ -814,7 +843,7 @@ MembersRowDelegate::IconState MembersRow::computeIconState( .muted = muted, .mutedByMe = (_state == State::MutedByMe), .raisedHand = (_state == State::RaisedHand), - .narrowStyle = style, + .style = style, }; } diff --git a/Telegram/SourceFiles/calls/group/calls_group_members_row.h b/Telegram/SourceFiles/calls/group/calls_group_members_row.h index 430d2800ec..ac2fbc423f 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members_row.h +++ b/Telegram/SourceFiles/calls/group/calls_group_members_row.h @@ -27,10 +27,11 @@ class RippleAnimation; namespace Calls::Group { -enum class NarrowStyle { +enum class MembersRowStyle { None, Userpic, Video, + LargeVideo, }; class MembersRow; @@ -42,7 +43,7 @@ public: float64 muted = 0.; bool mutedByMe = false; bool raisedHand = false; - NarrowStyle narrowStyle = NarrowStyle::None; + MembersRowStyle style = MembersRowStyle::None; }; virtual bool rowIsMe(not_null participantPeer) = 0; virtual bool rowCanMuteMembers() = 0; @@ -155,6 +156,19 @@ public: int availableWidth, int outerWidth, bool selected) override; + void paintComplexStatusText( + Painter &p, + const style::PeerListItem &st, + int x, + int y, + int availableWidth, + int outerWidth, + bool selected, + MembersRowStyle style); + void paintMuteIcon( + Painter &p, + QRect iconRect, + MembersRowStyle style = MembersRowStyle::None); private: struct BlobsAnimation; @@ -211,9 +225,9 @@ private: int y, int sizew, int sizeh, - NarrowStyle style); + MembersRowStyle style); [[nodiscard]] MembersRowDelegate::IconState computeIconState( - NarrowStyle style = NarrowStyle::None) const; + MembersRowStyle style = MembersRowStyle::None) const; const not_null _delegate; State _state = State::Inactive; diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 101fe14e56..063f99359a 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/group/calls_group_members.h" #include "calls/group/calls_group_settings.h" #include "calls/group/calls_group_menu.h" +#include "calls/group/calls_group_large_video.h" #include "calls/group/ui/desktop_capture_choose_source.h" #include "ui/platform/ui_platform_window_title.h" #include "ui/platform/ui_platform_utility.h" @@ -48,7 +49,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/timer_rpl.h" #include "app.h" #include "apiwrap.h" // api().kickParticipant. -#include "media/view/media_view_pip.h" #include "webrtc/webrtc_video_track.h" #include "styles/style_calls.h" #include "styles/style_layers.h" @@ -994,89 +994,24 @@ void Panel::raiseControls() { } void Panel::setupPinnedVideo() { - _pinnedVideo.create(widget()); - _pinnedVideo->setVisible(_mode == PanelMode::Wide); - _pinnedVideo->setAttribute(Qt::WA_OpaquePaintEvent, true); + auto track = _call->videoLargeTrackValue( + ) | rpl::map([=](GroupCall::LargeTrack track) { + const auto row = track ? _members->lookupRow(track.peer) : nullptr; + Assert(!track || row != nullptr); + return LargeVideoTrack{ + row ? track.track : nullptr, + row + }; + }); + const auto visible = (_mode == PanelMode::Wide); + _pinnedVideo = std::make_unique( + widget(), + st::groupCallLargeVideoWide, + visible, + std::move(track), + _call->videoEndpointPinnedValue()); raiseControls(); - - rpl::combine( - _pinnedVideo->shownValue(), - _call->videoLargeTrackValue() - ) | rpl::map([](bool shown, Webrtc::VideoTrack *track) { - return shown ? track : nullptr; - }) | rpl::distinct_until_changed( - ) | rpl::start_with_next([=](Webrtc::VideoTrack *track) { - _pinnedTrackLifetime.destroy(); - if (!track) { - _pinnedVideo->paintRequest( - ) | rpl::start_with_next([=](QRect clip) { - QPainter(_pinnedVideo.data()).fillRect(clip, Qt::black); - }, _pinnedTrackLifetime); - _pinnedVideo->update(); - return; - } - track->renderNextFrame( - ) | rpl::start_with_next([=] { - const auto size = track->frameSize(); - if (size.isEmpty()) { - track->markFrameShown(); - } else { - _pinnedVideo->update(); - } - }, _pinnedTrackLifetime); - - _pinnedVideo->paintRequest( - ) | rpl::start_with_next([=](QRect clip) { - const auto [image, rotation] - = track->frameOriginalWithRotation(); - if (image.isNull()) { - return; - } - auto p = QPainter(_pinnedVideo); - auto hq = PainterHighQualityEnabler(p); - using namespace Media::View; - const auto size = _pinnedVideo->size(); - const auto scaled = FlipSizeByRotation( - image.size(), - rotation - ).scaled(size, Qt::KeepAspectRatio); - const auto left = (size.width() - scaled.width()) / 2; - const auto top = (size.height() - scaled.height()) / 2; - const auto target = QRect(QPoint(left, top), scaled); - if (UsePainterRotation(rotation)) { - if (rotation) { - p.save(); - p.rotate(rotation); - } - p.drawImage(RotatedRect(target, rotation), image); - if (rotation) { - p.restore(); - } - } else if (rotation) { - p.drawImage(target, RotateFrameImage(image, rotation)); - } else { - p.drawImage(target, image); - } - if (left > 0) { - p.fillRect(0, 0, left, size.height(), Qt::black); - } - if (const auto right = left + scaled.width() - ; right < size.width()) { - const auto fill = size.width() - right; - p.fillRect(right, 0, fill, size.height(), Qt::black); - } - if (top > 0) { - p.fillRect(0, 0, size.width(), top, Qt::black); - } - if (const auto bottom = top + scaled.height() - ; bottom < size.height()) { - const auto fill = size.height() - bottom; - p.fillRect(0, bottom, size.width(), fill, Qt::black); - } - track->markFrameShown(); - }, _pinnedTrackLifetime); - }, widget()->lifetime()); } void Panel::setupJoinAsChangedToasts() { diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.h b/Telegram/SourceFiles/calls/group/calls_group_panel.h index 54522397db..f82e4bc052 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.h @@ -54,6 +54,7 @@ struct CallBodyLayout; namespace Calls::Group { class Members; +class LargeVideo; enum class PanelMode; class Panel final : private Ui::DesktopCapture::ChooseSourceDelegate { @@ -141,7 +142,7 @@ private: object_ptr _menu = { nullptr }; object_ptr _joinAsToggle = { nullptr }; object_ptr _members = { nullptr }; - object_ptr _pinnedVideo = { nullptr }; + std::unique_ptr _pinnedVideo; rpl::lifetime _pinnedTrackLifetime; object_ptr _startsIn = { nullptr }; object_ptr _countdown = { nullptr }; diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index 445d42bacf..63dcf796d7 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit 445d42bacf80d8c7fa2a9d2ff790341cb656ff25 +Subproject commit 63dcf796d7721e02ee179c61aec7045b2699a3eb diff --git a/Telegram/lib_webrtc b/Telegram/lib_webrtc index 86ca2dd27e..0c867b0b6a 160000 --- a/Telegram/lib_webrtc +++ b/Telegram/lib_webrtc @@ -1 +1 @@ -Subproject commit 86ca2dd27e52fa929423b61cd7861a0bc9483e28 +Subproject commit 0c867b0b6ae29e6ae5d5c2fda3824cdb595900eb