From 962c8d89e52a8c92bc7987eba4cfaef77a3a9ebf Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 28 Nov 2020 14:59:01 +0300 Subject: [PATCH] Add two new button types. --- CMakeLists.txt | 4 + icons/calls/voice_muted_large.png | Bin 0 -> 1229 bytes icons/calls/voice_muted_large@2x.png | Bin 0 -> 2288 bytes icons/calls/voice_muted_large@3x.png | Bin 0 -> 3575 bytes icons/calls/voice_unmuted_large.png | Bin 0 -> 963 bytes icons/calls/voice_unmuted_large@2x.png | Bin 0 -> 1800 bytes icons/calls/voice_unmuted_large@3x.png | Bin 0 -> 2708 bytes ui/widgets/call_button.cpp | 213 +++++++++++++++++++++++++ ui/widgets/call_button.h | 55 +++++++ ui/widgets/call_mute_button.cpp | 112 +++++++++++++ ui/widgets/call_mute_button.h | 59 +++++++ ui/widgets/widgets.style | 41 +++++ 12 files changed, 484 insertions(+) create mode 100644 icons/calls/voice_muted_large.png create mode 100644 icons/calls/voice_muted_large@2x.png create mode 100644 icons/calls/voice_muted_large@3x.png create mode 100644 icons/calls/voice_unmuted_large.png create mode 100644 icons/calls/voice_unmuted_large@2x.png create mode 100644 icons/calls/voice_unmuted_large@3x.png create mode 100644 ui/widgets/call_button.cpp create mode 100644 ui/widgets/call_button.h create mode 100644 ui/widgets/call_mute_button.cpp create mode 100644 ui/widgets/call_mute_button.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ecf3f2b..4959564 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,6 +123,10 @@ PRIVATE ui/widgets/box_content_divider.h ui/widgets/buttons.cpp ui/widgets/buttons.h + ui/widgets/call_button.cpp + ui/widgets/call_button.h + ui/widgets/call_mute_button.cpp + ui/widgets/call_mute_button.h ui/widgets/checkbox.cpp ui/widgets/checkbox.h ui/widgets/dropdown_menu.cpp diff --git a/icons/calls/voice_muted_large.png b/icons/calls/voice_muted_large.png new file mode 100644 index 0000000000000000000000000000000000000000..ae5a118860945395e7ffabea5fdc1bb93497a842 GIT binary patch literal 1229 zcmV;;1Ty=HP)_Pkx83pGC4kNg@eZ z$VTxG$U<275v43p3ME-s*vLjUHYf|p@1I4MN`8~rutAdY<9z4bueoOK%)Qrr-S=|p zb!X6BEhd;UPIVI3SOYkFJ=< zN9^}6E-ntn$H(F8>kD{!etw3Ah6cawa!fK@XlN)TB_-J!_xXIVv$Mll)$udg*n^Yil%5fP!9q>c6T^hiL)fsc<5C@3f}wP6E?^F+Xojt;@c%F2o< znFH^3BZz=bAQKW203m}KK?d2`*(vNZ>}+goKvYzekb)2r_bQv`$5s3m|JFcOR+a>4 z1{oV08yLX&_;^@aS^`>pLQPGL-~{iP$>!#!6iI&JAt50!GBN@uCnqpGJS^Dd^}gHY z5o~H|(iyM-4G#|&j2#^v>GzEdE69q93PIhf_Ov{c+}zyIMf{hTm>5!8TFScGm6lOL zpYPDBh%78D5V|M%VXe*Ed3$>!4-XHL4nH77h2GHnxOcGNWHL21rQbt6uh$D$E=}DW0d6f65X#W5yZo=H$obLbq{T(tgG6aM8xtpAv)OA!16=Y6M zj+TDv>goi03I=Pyva&M4Cq1!A5kaH5xw(Kxfd2k|7#$sz4pAydtb&aD+A$zbn3 zc_B37Bt1Qy)2+Xm8qKz=f@#Vq%DqCB9E}$j7wcb*ySqEnU0n^dG=sjrKCMDB=r{v0OcoY_koe}} r|20;6PVPDSl2F4z7uWV+pveCLXSK$G7W&?p00000NkvXXu0mjfDBLGG literal 0 HcmV?d00001 diff --git a/icons/calls/voice_muted_large@2x.png b/icons/calls/voice_muted_large@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..cd1161ce379baba7d11a682029b993499072a897 GIT binary patch literal 2288 zcmVPc^Bva0u`R&TMR zMvNFC`t|E48Z~MpYSyeNzI^#2of_wQU*%5O^rd z4I4HT6)IGS3kl|Dn>KBNQDmvbctv{l?8zG7+IZsf<;(pznKsYKlP8UJk!*ra!&5kZ z{CE~bIpt!viOZHP(*m)LlvBN+;c)cv0OHEoK$LS@PT{n4=~7!x%Jjt^ARvw#H;(Oz zT!>4SEKx$SiO2|mVL@EHc(DyD&$Jl>&<5g*7cZzqix#ZgdC{Upo?y6v83iybhzl1k z(vWi3@S=+_`Ls84oZlh;!!5VL{9afVL1@wQ9w>ooCOUZG@OL00D8#m@%x51-CO7 z;;dP-v=Fljpbf-VuU@es&YU?@2{G#c+Cs!bCgd@FX3Uu32{9`Hh6Qo@^yx_uvld`j z5ECDEWi`OCAWoe+mHz(y8}Y`h2MCCxM~^P@d^zbpEDU)4`ZdXC9LDw#I;TvT5`kDE z0HVU&=OKXDwryJ$#L1H<)77h2S!rQIfS*8>u?(9qVS>u>lEQ5u_UhG(Repf*P+sZ* z4jVQs36w6t24WiO;|8!#pFXsI|9(RIvth#qYS5s8(F;0t>ZIf48TaPR8*10Col&<5 zVCBk{2~PvhoH;`mE?kKGE?v5`i(dZ#`}gmk)FFKO^oe@(=%F>JYSpTqkW7KD5Gz%x zM7M6;N-AdGzI|GI4Hv~f!1L$NCmDl)>({Rv4SM|ev1eXWpbJEFG%DfFf)Zh}wsy!& zo>Hey9nbJCUAhQt-_Xg{ty^``%D5UeYKYUPPYZcFE7PbV1`HUWiV~{2C^N)G7`;WN z^-Oc*$Pv{AB@A#?`4BGm(fl6hA=a&1mmWNL;Hh%^_U&3bHSmznKR|5m*6kf!xpJk^FnESehi8ow0s*l@hYrTF za6z$?HBm6Vbl~70APU{Mabu!AvHv)_Aol?pjl%<`d-v|e^7;9}g$S=dc<^AX2-sMz zTsfnS8o+Jkl}6~ zi2OJ^q7!UV_j961o|B^{O`1fGQ=q*aK72UqP>v160P61!5Jq4+q?5gS_YyXjyBb^! zAqE2w17pmCb%Hhy?T;%&tmgdu`BPU^BrZjs7z{u_!R*5%yLRnj^~@&7HV|#vU{kps z@xcMabZO6?Jtl^9|NebeZ|Pxd5$_#3bSTRc-@kuPAwmoeAS#S870%I_4GKNlvfxazk-$e_3MkHM~@1*&c;^!=+Pr_{P=Nk>Cz=3H)9I9fBZJIc7BFQ$6r6K@`sRJ1GM$B5UR*21&LCLvu=V;)-fw5Z>y)C-}HB9!dk-ailpTl~dtzf9MZrwU%<*^otBd{ECfS5jjQIPn?ECx$i zc=Q>gJYNWK;R3T+XMZ}IBJH)6_qlBDZdZv&M%RIy51GO$H6Q(>~H+%5=vNKaX-7qVdHKAOK^nPHv=(3~I4& z23zTZc&==^c((DOE$K@`N`w^4P9&4a=KwlhtsKA{Kt}?O+5Z8ZL&@M?>Oe360000< KMNUMnLSTZtj4W6H literal 0 HcmV?d00001 diff --git a/icons/calls/voice_muted_large@3x.png b/icons/calls/voice_muted_large@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..3907746876820f9094207689a98f73532a12bb83 GIT binary patch literal 3575 zcmV^#u%rMAL3L4DdV(Wezo3czy}=Xw}KDY2OQ@bi1X_C7VHC# za}C6K^-{-q{Q}j?ojW%xQ>F~dlP6E$lQLyWcICxK;!D!EyH~ zQltnUJ9aGJvu6)~{rWYx3rCI|;p4`Q;}t4Yh&w&t&W)=|0qnea^Z4`U&+V9oy|!}Y zN?xi|sd&;!`~dFUxif$K_;FZxq8DDgc)=4A5_qaqsp0{2>;q1hE*)R8WQiz0;dmQ1 zY~b0mXODHru?-kjwtDqyVQ8`uPnmVeZ>F% z`|o#jcHqDPUcP*JqYC-pgAe%a+qd75ZSsdtKmF8boeaQ5dDQ>|u3d{2-ISIsTN+cy z!i5V>vJ9E`wr$&t)kl?sKstHAXxQx9wJW3&f`3GJ!vthBdY(La5-iHKzZ*7e7;BK_ z0avP2Nm!wP+@eJbN0l;o@L*y2PDlLp*Iym2i+}~{%997&ty?!Ky*X{kyLa!FlJ8{X zyLa!#REt_^x-dnG6m;W>2WnL_y7O79R;|Png^8IqZCXYRxG-1E|C}vbHddoX4fBE= zlAr{5|Ni~b;sJT-(xr^v$Ub@P$xmM1MD^A{Q2{{3OQK<2IMck_(JQQ+@?(%WlIL+lP6E=7^ZOA zWDD49$kct||Ni^0FeufC$r^BSfSl|BI{~?E+qTM&>%4jM@_+vMN7X3ZqD zlcN=~4>+iAtqb|fj0r}XmV~$v|4>$~9Cm^?L*G}1zX_p~)0Y$K6>=1mL>auBLMaqa;{vtIJS(c1|P5hU_fr%xUur00lbsH{q|es$F&Y*AFv?c zXn=h9@L_2~_5n)(b^>zy_U)A|IcLtC{LrC8QjmSXl7M3kvJY4RuoI9wbm*XL$=D8a z@ZdoqOZEXP0*(gA*cllq_<%J419GEAjkH?V*f)%PR6aR!|;399qSPC&Lcp8J3e0S4q* zvu0U_B>fG@UAuNwdQ?L;EE$kv39!=sV5+QVfwrD1%Z6uyxpU_-^690N2f1%xb?Ve% zuIM=n2!%lwzNK z_8BWytQb3g{yZb+Y7A~IXaR$H+pBXZ3DV=Q03{QrL({0j}QnKNfHg6vqXs2?5jA(Wq~hL+=Z z4b_Ij-LPnwGG&TX83+W|UA%5rHYY6^KBh%O_3G87^Z-2ze1sy2WRxflJi$|+Rj6** zP9jjyL~*bg&17&2@((}!AXH4aM0`Wx@@#W5*8R z{8b_Xa?hSUwUiDg;vxZEr7(cBemC|GroysKn>OJ~D9Ab~CFKXkl*zNaQ2r{Btaoy+ zUcFS32hxZR%xvAdRar))gj#n9Wga&D#~*(za`Oh_piiuFvI(}YxDg0)*m~OO00)$a z96wOIc5S8n?PR~X79Nm5V%tKYF$8d;L3K6ck3aq>Dnct>@7}#_>uaa4(W6I)m3{N( zO`bk|dgZ+BGO_k(K6SoS1(cT0u4Eucu>wy zK(1N9D8S;yi-mO#KhT*oXI8E-2JLBfhGToF$)E!nZ4)}XI8eOx>(?vSRRAvXHV?RB z#fnn8rs+HFO2aB>REj@(^hip!Oys_O`zn_cWA%6?VUM&)1~^?KKpCV>2I{l2YSpR< zi>P0}KBLn=h2w{dNKXS+rAie>BT9k8JThd+5IFOJ#=rw})AUGZ)Bs``GiD5zh3)VC z`t@ToX3VgS6Ye4{$z}KM-DBwTnG8nnAS!qu{{{^j$QTE|eEBlRnq(`z$!}VWpk?U< z*_CoC>cIRX+A7$xWy_=>V|0fs&K2c-{`u$e3fVota1x4xrDOsv?U9~Nv~DQbZ$QTM zrD}*$OLh-16pl%C*|2u)TBT7gS-^;2vt~^#$o>2GcjbvAQ8#x1V<2Pu_U*Ea`O1|m zIW>e_QC^%ub{8=2ywashC+h5r(uU{Qa^=do0-27l)3W3N0|vNKP611HA214qeMUGt zNj6|qOm*O2KpBak#0g|?07KcBYgG-u|NeVm)rc%K+4!`ES?OtG)ob9ufr)M!S@{mc z_YN=$kL5b50mG5#dYOD|<1p-r6DO(`F_6w<$le0R$_(t*(+L=`U$bTn$3VVQ{bt&Q z!N-mr%P(EJq*JuFX-%w{6tr${0i%$Bq6^v5B-S?35GO|?2Mhm1rXLXp3+i$F!1U?U z!|p(^WT<}QU6rESf528?Z+xAt8a05^UqiUZjQ`yK&P8qcWC)u)d9oX#-pX+7ZlN5t^TIx%P?O@SRuhfsT()F=C{BP4&ZBPK zx}0WNqLGbbWg<(~HilrG+58=SfKgc54Zyc-+2WW&qcJ0wAV())>yE@iJU5o%cy$9{ zEtyqUL*LO27?p-k=S7PadBq^CTyi?~K^_%+-sql0ZU}Ph0k)J1?*PMx4>xi?p#}HKl`9IS&tVEsDTp)AU zuwfD9gp(iU_t*s-Tp_HY#G-U84geI@K!YM2t4J_RS-Em$p^>oQa=>$Zu-ikGaySe* zJ^%+R8Gpl?7A#mGtY6Gd$?kAWD%1KuYu2p%ufP5hmK`c0o=jGLn&0sW7)p`ZHsi!v z>|hXBeS#-}PH+OU(e0yD;ba>gKYq+;Q9GkqE@d4|m@t7+Yh5`#68eC()MuXmRhfy1fe;LcU|>Wv(1~oKQR91{gVr<)D?%o&aHfJ0;V=%Z5iWo0Af>VRqQI`+AtcwP68R2>PD^;rW);UY4a-TbQj_1miOSYWxJK2C; z6FYbA45h0?ztK4ou5C>v%d!Q8Lot^uS(0e!FukKWCPOew6)Am)>-GXeBv29JS(=h1 zOWJM&#TslwZhW=s49|DuJch$Vk*ve?B+TL)Fe-~BxS=c=Zj3OgpF|;X^?%fCT_GhK z4cHn}wrp8O?>R&)kYa#MO-;b(ST!cqwbj*CVL;(kJUBSW>~_1ZMSzP(v1(09oVK(m=`WU!Gzvl%e!-*y>3|{XMSxZpbVo`g1 ze9Y46G@F>1&~h#7zN|Fn=H}$eRMd(CC%{oC7HVxtt4*&7)dAtsSXgv6{I|Fi!OsEQIUFE2?zx7$qu_V@S0 z>FFu4K>>@|#l=P7Pl4DU92^i^uh&cTRT#7Z1Azbucy)CJot>Q|sMTtP(a}*@US5V^ zFbF=M4|okoY;fR}l@$^zpU(r%*R%ooN>*)*;_*1xY_`fEHT#p3lkoojP5|fU z=WF_Lcx^z`kByDN$HxZ=espvMPN!4P0DvHmkB`LvAX)1&UkJnVA_lJ3CXl0e`PTcXzkS#}ptsudlDG29qw_+}uDSk$_k%2B}mEUS3`# z6**@z8Q}jy=!HW&eHoPOW@l%iSS*r3^$Pp@`_SIrZUoLaAcEp5-rU>-+?4BS7Z(>W zH8o`vOtLqYK`E1XZfk1;zuynxa9H)i#zllvVt9C18c6gk4p<4hy}eD6Xu`W!@oe<^#~pdFyH_H002ovPDHLkV1gE4tS|ro literal 0 HcmV?d00001 diff --git a/icons/calls/voice_unmuted_large@2x.png b/icons/calls/voice_unmuted_large@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c2e70b23c8c6ba9f6b7c08a3de1db0beb05107ed GIT binary patch literal 1800 zcmbtV={wtr7XF2ZiY-ygsCCjQW>9NMnIf3l5;7GLI#+5*LnaklG&G1^uNmqYJC%s7 z6YZcQT}H)7a7~-5T3p*ST7=P}VyU9)ruW0Wf583lp7%ZHJm=ec&bf{a^ihYNfC2!Z z?&s@$LC)AuI0li&68PpFIf2P23x8ZD#>#siQsn`g8{yh>tg_@Fb)9A6-bT~ zasYq=N8!H}EJyKw`cqTOBAyNakZXS4C>#T{Rv+MNX!jYb={;4J{qFN96lyS+U=zNx z)M$#p@swrx7C966_MC9-(hIADp1t9y7Hy*MxgtFiWw_GdF3Luicn3cSb)#UmY}V~l zoNK22Qiq;{D{C;D?we9}4caNpIS*!LQj?;UF!`nVVEi#(R(+!>$e&dYMh>1sO7n}& z&CQ#{Vsv?J?caq$AvPhwc3-xjJTo)n866!Rn~(s*1O=(yf6@eN5VmGDe4UmfALy!d zw6n`Te*8FQcdwvsZ-YXiY)nj4=%(It!5;0rnY<6;J_P0j8QP`jt`+I5{e+{GTzY-M|spaRr9_Nb7 z1CUNm_+nb%WMV}`<;hk+OFcb$z&dV8#l_;)1~FGU(zmK zG*CE7`)OSYD=RD0sEQq*n6RVcaX4&M?1uIuhAbzp>NdM8STg|4<#MZ&z_#u6VZ&Mj zhlhu$8mOTTV&6vZwcT5HBzXG=bG zEmoA5V{dNCWc_VzZMd*=O72-72P6^+uCAq}1;-!N=rw#`jEJewNnwe+x&4uDF%4{X zQ)A=jl!pxp1ZHMpB1(s}=cV(Uz^QXv(<=ByGB-y)?VNj{@!f00bE))H!GT>|he*?^ z%@v8{)V^L@qg(ilwTasbZ$S1J78b_C7mZ`XhR(Nl3IrvR-fKRVsbBT+`8rpqR4nPz z8!)wsa|-VoYyX(}VtZkJ{#GwkDI+5T{Q{;h5{b}8l>8Xq9F%4pY)4U31&??#f9tMQ zuQMq{L=ko(%Bifz{dWhuG(#h!f2NE~PU>atZ(rx-hK!Dl<-W=u*mN*6gAj?t;0fm8 zennb)4JP+>VbtUSZ?A-dqv4fNseb%mX{0Lc|67a2nG)WGooY7QMyr9r8YNiodXWNN-7Prl{Qw--d3z6b z1=}GIM2ty!-6uB$TvAKdC(xKdIR5q8fy*K1?9Z{Q2j=Q?~n6 z`uLYS2FfrGxh3`bn0s2H_M5|Cnh9rjZ<(7T(p<(_Snc8PWLo zUe@$Y>4lE|r`_+#kMMvuCQ1q=%;FZnLe-JZW z3{`p71O!uUD>(9rc9Xb3S5MCsOu*p=%7Yyr52xSPG-a{o{4bNqc2@OyP5lm5wXVOD yg7?9~tg`qQUW>%0)1ahx)nE%ylGaP@BSl>CMYA{BX6B#Xk>59g-oJQW%K104elwo{ literal 0 HcmV?d00001 diff --git a/icons/calls/voice_unmuted_large@3x.png b/icons/calls/voice_unmuted_large@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..d15a5fcbbccdccd2830ff49227ae8bf194435a84 GIT binary patch literal 2708 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.width, _stFrom->button.height); + + _bgMask = RippleAnimation::ellipseMask(QSize(_stFrom->bgSize, _stFrom->bgSize)); + _bgFrom = Ui::PixmapFromImage(style::colorizeImage(_bgMask, _stFrom->bg)); + if (_stTo) { + Assert(_stFrom->button.width == _stTo->button.width); + Assert(_stFrom->button.height == _stTo->button.height); + Assert(_stFrom->bgPosition == _stTo->bgPosition); + Assert(_stFrom->bgSize == _stTo->bgSize); + + _bg = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied); + _bg.setDevicePixelRatio(style::DevicePixelRatio()); + _bgTo = Ui::PixmapFromImage(style::colorizeImage(_bgMask, _stTo->bg)); + _iconMixedMask = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied); + _iconMixedMask.setDevicePixelRatio(style::DevicePixelRatio()); + _iconFrom = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied); + _iconFrom.setDevicePixelRatio(style::DevicePixelRatio()); + _iconFrom.fill(Qt::black); + { + QPainter p(&_iconFrom); + p.drawImage( + (_stFrom->bgSize + - _stFrom->button.icon.width()) / 2, + (_stFrom->bgSize + - _stFrom->button.icon.height()) / 2, + _stFrom->button.icon.instance(Qt::white)); + } + _iconTo = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied); + _iconTo.setDevicePixelRatio(style::DevicePixelRatio()); + _iconTo.fill(Qt::black); + { + QPainter p(&_iconTo); + p.drawImage( + (_stTo->bgSize + - _stTo->button.icon.width()) / 2, + (_stTo->bgSize + - _stTo->button.icon.height()) / 2, + _stTo->button.icon.instance(Qt::white)); + } + _iconMixed = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied); + _iconMixed.setDevicePixelRatio(style::DevicePixelRatio()); + } +} + +void CallButton::setOuterValue(float64 value) { + if (_outerValue != value) { + _outerAnimation.start([this] { + if (_progress == 0. || _progress == 1.) { + update(); + } + }, _outerValue, value, kOuterBounceDuration); + _outerValue = value; + } +} + +void CallButton::setText(rpl::producer text) { + _label.create(this, std::move(text), _stFrom->label); + _label->show(); + rpl::combine( + sizeValue(), + _label->sizeValue() + ) | rpl::start_with_next([=](QSize my, QSize label) { + _label->moveToLeft( + (my.width() - label.width()) / 2, + my.height() - label.height(), + my.width()); + }, _label->lifetime()); +} + +void CallButton::setProgress(float64 progress) { + _progress = progress; + update(); +} + +void CallButton::paintEvent(QPaintEvent *e) { + QPainter p(this); + + auto bgPosition = myrtlpoint(_stFrom->bgPosition); + auto paintFrom = (_progress == 0.) || !_stTo; + auto paintTo = !paintFrom && (_progress == 1.); + + auto outerValue = _outerAnimation.value(_outerValue); + if (outerValue > 0.) { + auto outerRadius = paintFrom ? _stFrom->outerRadius : paintTo ? _stTo->outerRadius : (_stFrom->outerRadius * (1. - _progress) + _stTo->outerRadius * _progress); + auto outerPixels = outerValue * outerRadius; + auto outerRect = QRectF(myrtlrect(bgPosition.x(), bgPosition.y(), _stFrom->bgSize, _stFrom->bgSize)); + outerRect = outerRect.marginsAdded(QMarginsF(outerPixels, outerPixels, outerPixels, outerPixels)); + + PainterHighQualityEnabler hq(p); + if (paintFrom) { + p.setBrush(_stFrom->outerBg); + } else if (paintTo) { + p.setBrush(_stTo->outerBg); + } else { + p.setBrush(anim::brush(_stFrom->outerBg, _stTo->outerBg, _progress)); + } + p.setPen(Qt::NoPen); + p.drawEllipse(outerRect); + } + + if (paintFrom) { + p.drawPixmap(bgPosition, _bgFrom); + } else if (paintTo) { + p.drawPixmap(bgPosition, _bgTo); + } else { + style::colorizeImage(_bgMask, anim::color(_stFrom->bg, _stTo->bg, _progress), &_bg); + p.drawImage(bgPosition, _bg); + } + + auto rippleColorInterpolated = QColor(); + auto rippleColorOverride = &rippleColorInterpolated; + if (paintFrom) { + rippleColorOverride = nullptr; + } else if (paintTo) { + rippleColorOverride = &_stTo->button.ripple.color->c; + } else { + rippleColorInterpolated = anim::color(_stFrom->button.ripple.color, _stTo->button.ripple.color, _progress); + } + paintRipple(p, _stFrom->button.rippleAreaPosition.x(), _stFrom->button.rippleAreaPosition.y(), rippleColorOverride); + + auto positionFrom = iconPosition(_stFrom); + if (paintFrom) { + const auto icon = &_stFrom->button.icon; + icon->paint(p, positionFrom, width()); + } else { + auto positionTo = iconPosition(_stTo); + if (paintTo) { + _stTo->button.icon.paint(p, positionTo, width()); + } else { + mixIconMasks(); + style::colorizeImage(_iconMixedMask, st::callIconFg->c, &_iconMixed); + p.drawImage(myrtlpoint(_stFrom->bgPosition), _iconMixed); + } + } +} + +QPoint CallButton::iconPosition(not_null st) const { + auto result = st->button.iconPosition; + if (result.x() < 0) { + result.setX((width() - st->button.icon.width()) / 2); + } + if (result.y() < 0) { + result.setY((height() - st->button.icon.height()) / 2); + } + return result; +} + +void CallButton::mixIconMasks() { + _iconMixedMask.fill(Qt::black); + + Painter p(&_iconMixedMask); + PainterHighQualityEnabler hq(p); + auto paintIconMask = [this, &p](const QImage &mask, float64 angle) { + auto skipFrom = _stFrom->bgSize / 2; + p.translate(skipFrom, skipFrom); + p.rotate(angle); + p.translate(-skipFrom, -skipFrom); + p.drawImage(0, 0, mask); + }; + p.save(); + paintIconMask(_iconFrom, (_stFrom->angle - _stTo->angle) * _progress); + p.restore(); + p.setOpacity(_progress); + paintIconMask(_iconTo, (_stTo->angle - _stFrom->angle) * (1. - _progress)); +} + +void CallButton::onStateChanged(State was, StateChangeSource source) { + RippleButton::onStateChanged(was, source); + + auto over = isOver(); + auto wasOver = static_cast(was & StateFlag::Over); + if (over != wasOver) { + update(); + } +} + +QPoint CallButton::prepareRippleStartPosition() const { + return mapFromGlobal(QCursor::pos()) - _stFrom->button.rippleAreaPosition; +} + +QImage CallButton::prepareRippleMask() const { + return RippleAnimation::ellipseMask(QSize(_stFrom->button.rippleAreaSize, _stFrom->button.rippleAreaSize)); +} + +} // namespace Ui diff --git a/ui/widgets/call_button.h b/ui/widgets/call_button.h new file mode 100644 index 0000000..c02c441 --- /dev/null +++ b/ui/widgets/call_button.h @@ -0,0 +1,55 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#pragma once + +#include "base/object_ptr.h" +#include "ui/widgets/buttons.h" +#include "ui/effects/animations.h" + +namespace Ui { + +class FlatLabel; + +class CallButton final : public RippleButton { +public: + CallButton( + QWidget *parent, + const style::CallButton &stFrom, + const style::CallButton *stTo = nullptr); + + void setProgress(float64 progress); + void setOuterValue(float64 value); + void setText(rpl::producer text); + +protected: + void paintEvent(QPaintEvent *e) override; + + void onStateChanged(State was, StateChangeSource source) override; + + QImage prepareRippleMask() const override; + QPoint prepareRippleStartPosition() const override; + +private: + QPoint iconPosition(not_null st) const; + void mixIconMasks(); + + not_null _stFrom; + const style::CallButton *_stTo = nullptr; + float64 _progress = 0.; + + object_ptr _label = { nullptr }; + + QImage _bgMask, _bg; + QPixmap _bgFrom, _bgTo; + QImage _iconMixedMask, _iconFrom, _iconTo, _iconMixed; + + float64 _outerValue = 0.; + Animations::Simple _outerAnimation; + +}; + +} // namespace Ui diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp new file mode 100644 index 0000000..26081b6 --- /dev/null +++ b/ui/widgets/call_mute_button.cpp @@ -0,0 +1,112 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#include "ui/widgets/call_mute_button.h" + +#include "styles/style_widgets.h" + +namespace Ui { + +CallMuteButton::CallMuteButton( + not_null parent, + CallMuteButtonState initial) +: _state(initial) +, _content(parent, st::callMuteButtonActive, &st::callMuteButtonMuted) +, _connecting(parent, st::callMuteButtonConnecting) { + if (_state.type == CallMuteButtonType::Connecting + || _state.type == CallMuteButtonType::ForceMuted) { + _connecting.setText(rpl::single(_state.text)); + _connecting.show(); + _content.hide(); + } else { + _content.setText(rpl::single(_state.text)); + _content.setProgress((_state.type == CallMuteButtonType::Muted) ? 1. : 0.); + _connecting.hide(); + _content.show(); + } + _connecting.setAttribute(Qt::WA_TransparentForMouseEvents); +} + +void CallMuteButton::setState(const CallMuteButtonState &state) { + if (state.type == CallMuteButtonType::Connecting + || state.type == CallMuteButtonType::ForceMuted) { + if (_state.text != state.text) { + _connecting.setText(rpl::single(state.text)); + } + if (!_connecting.isHidden() || !_content.isHidden()) { + _connecting.show(); + } + _content.setOuterValue(0.); + _content.hide(); + } else { + if (_state.text != state.text) { + _content.setText(rpl::single(state.text)); + } + _content.setProgress((state.type == CallMuteButtonType::Muted) ? 1. : 0.); + if (!_connecting.isHidden() || !_content.isHidden()) { + _content.show(); + } + _connecting.hide(); + if (state.type == CallMuteButtonType::Active) { + _content.setOuterValue(_level); + } else { + _content.setOuterValue(0.); + } + } + _state = state; +} + +void CallMuteButton::setLevel(float level) { + _level = level; + if (_state.type == CallMuteButtonType::Active) { + _content.setOuterValue(level); + } +} + +rpl::producer CallMuteButton::clicks() const { + return _content.clicks(); +} + +QSize CallMuteButton::innerSize() const { + const auto skip = st::callMuteButtonActive.outerRadius; + return QSize( + _content.width() - 2 * skip, + _content.width() - 2 * skip); +} + +void CallMuteButton::moveInner(QPoint position) { + const auto skip = st::callMuteButtonActive.outerRadius; + _content.move(position - QPoint(skip, skip)); + _connecting.move(_content.pos()); +} + +void CallMuteButton::setVisible(bool visible) { + if (!visible) { + _content.hide(); + _connecting.hide(); + } else if (_state.type == CallMuteButtonType::Connecting + || _state.type == CallMuteButtonType::ForceMuted) { + _connecting.show(); + } else { + _content.show(); + } +} + +void CallMuteButton::raise() { + _content.raise(); + _connecting.raise(); +} + +void CallMuteButton::lower() { + _content.lower(); + _connecting.lower(); +} + +rpl::lifetime &CallMuteButton::lifetime() { + return _content.lifetime(); +} + +} // namespace Ui diff --git a/ui/widgets/call_mute_button.h b/ui/widgets/call_mute_button.h new file mode 100644 index 0000000..b0faea7 --- /dev/null +++ b/ui/widgets/call_mute_button.h @@ -0,0 +1,59 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#pragma once + +#include "ui/widgets/call_button.h" + +namespace Ui { + +enum class CallMuteButtonType { + Connecting, + Active, + Muted, + ForceMuted, +}; + +struct CallMuteButtonState { + QString text; + CallMuteButtonType type = CallMuteButtonType::Connecting; +}; + +class CallMuteButton final { +public: + explicit CallMuteButton( + not_null parent, + CallMuteButtonState initial = CallMuteButtonState()); + + void setState(const CallMuteButtonState &state); + void setLevel(float level); + [[nodiscard]] rpl::producer clicks() const; + + [[nodiscard]] QSize innerSize() const; + void moveInner(QPoint position); + + void setVisible(bool visible); + void show() { + setVisible(true); + } + void hide() { + setVisible(false); + } + void raise(); + void lower(); + + [[nodiscard]] rpl::lifetime &lifetime(); + +private: + CallMuteButtonState _state; + float _level = 0.; + + CallButton _content; + CallButton _connecting; + +}; + +} // namespace Ui diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index bc1678d..01d9cf4 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -379,6 +379,8 @@ MultiSelect { CallButton { button: IconButton; bg: color; + bgSize: pixels; + bgPosition: point; angle: double; outerRadius: pixels; outerBg: color; @@ -1299,6 +1301,45 @@ defaultToast: Toast { durationSlide: 160; } +callMuteButtonActiveInner: IconButton { + width: 136px; + height: 151px; + + iconPosition: point(-1px, 50px); + icon: icon {{ "calls/voice_unmuted_large", groupCallIconFg }}; +} +callMuteButtonLabel: FlatLabel(defaultFlatLabel) { + textFg: callNameFg; + style: TextStyle(defaultTextStyle) { + font: font(13px); + linkFont: font(13px); + linkFontOver: font(13px underline); + } +} +callMuteButtonActive: CallButton { + button: callMuteButtonActiveInner; + bg: groupCallLive1; + bgSize: 100px; + bgPosition: point(18px, 18px); + outerRadius: 18px; + outerBg: callAnswerBgOuter; + label: callMuteButtonLabel; +} +callMuteButtonMuted: CallButton(callMuteButtonActive) { + button: IconButton(callMuteButtonActiveInner) { + icon: icon {{ "calls/voice_muted_large", groupCallIconFg }}; + } + bg: groupCallMuted1; + label: callMuteButtonLabel; +} +callMuteButtonConnecting: CallButton(callMuteButtonMuted) { + button: IconButton(callMuteButtonActiveInner) { + icon: icon {{ "calls/voice_muted_large", groupCallIconFg }}; + } + bg: callIconBg; + label: callMuteButtonLabel; +} + // Windows specific title windowTitleButton: IconButton {