From 3a2c9f7367d4f1a0be416da7d0baab96c5e33a44 Mon Sep 17 00:00:00 2001 From: kkostiuk <kateryna.kostiuk@savoirfairelinux.com> Date: Wed, 27 Jan 2021 10:30:49 -0500 Subject: [PATCH] UI: change conference overlay for participant Gitlab: #270 Change-Id: Ib460410f0df98640a835e37abcddfed85a34d311 --- CMakeLists.txt | 14 +- data/light/ic_moderator.png | Bin 0 -> 401 bytes data/light/ic_moderator_audio_muted.png | Bin 0 -> 638 bytes data/light/ic_moderator_audio_unmuted.png | Bin 0 -> 541 bytes data/light/ic_moderator_hangup.png | Bin 0 -> 513 bytes data/light/ic_moderator_maximize.png | Bin 0 -> 407 bytes data/light/ic_moderator_minimize.png | Bin 0 -> 427 bytes data/light/ic_star.png | Bin 0 -> 911 bytes src/CurrentCallVC.mm | 14 +- src/views/ConferenceOverlayView.h | 23 +- src/views/ConferenceOverlayView.mm | 279 +++++++++++++++------- src/views/CustomBackgroundView.h | 39 +++ src/views/CustomBackgroundView.mm | 98 ++++++++ 13 files changed, 361 insertions(+), 106 deletions(-) create mode 100644 data/light/ic_moderator.png create mode 100644 data/light/ic_moderator_audio_muted.png create mode 100644 data/light/ic_moderator_audio_unmuted.png create mode 100644 data/light/ic_moderator_hangup.png create mode 100644 data/light/ic_moderator_maximize.png create mode 100644 data/light/ic_moderator_minimize.png create mode 100644 data/light/ic_star.png create mode 100644 src/views/CustomBackgroundView.h create mode 100644 src/views/CustomBackgroundView.mm diff --git a/CMakeLists.txt b/CMakeLists.txt index c0f381b0..9c7547e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,11 +62,12 @@ add_custom_command (OUTPUT ${CMAKE_SOURCE_DIR}/Shader.metallib COMMAND ${CMAKE_SOURCE_DIR}/generateShaderLib.sh COMMENT "Creating Shader.metallib") - add_custom_target( + add_custom_target( shader ALL DEPENDS ${CMAKE_SOURCE_DIR}/Shader.metallib ) + IF(NOT (${ENABLE_SPARKLE} MATCHES false)) MESSAGE("Sparkle auto-update enabled") @@ -239,6 +240,8 @@ SET(ringclient_VIEWS src/views/VideoRendering.h src/views/ConferenceOverlayView.h src/views/ConferenceOverlayView.mm + src/views/CustomBackgroundView.h + src/views/CustomBackgroundView.mm ) SET(ringclient_OTHERS @@ -357,7 +360,14 @@ ${CMAKE_CURRENT_SOURCE_DIR}/data/dark/ic_record_stop.png ${CMAKE_CURRENT_SOURCE_DIR}/data/dark/ic_camera.png ${CMAKE_CURRENT_SOURCE_DIR}/data/dark/ic_audio_msg.png ${CMAKE_CURRENT_SOURCE_DIR}/data/dark/ic_group.png -${CMAKE_CURRENT_SOURCE_DIR}/data/light/ic_picture.png) +${CMAKE_CURRENT_SOURCE_DIR}/data/light/ic_picture.png +${CMAKE_CURRENT_SOURCE_DIR}/data/light/ic_moderator.png +${CMAKE_CURRENT_SOURCE_DIR}/data/light/ic_moderator_audio_muted.png +${CMAKE_CURRENT_SOURCE_DIR}/data/light/ic_moderator_audio_unmuted.png +${CMAKE_CURRENT_SOURCE_DIR}/data/light/ic_moderator_maximize.png +${CMAKE_CURRENT_SOURCE_DIR}/data/light/ic_moderator_minimize.png +${CMAKE_CURRENT_SOURCE_DIR}/data/light/ic_moderator_hangup.png +${CMAKE_CURRENT_SOURCE_DIR}/data/light/ic_star.png) SET_SOURCE_FILES_PROPERTIES(${ring_ICONS} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) diff --git a/data/light/ic_moderator.png b/data/light/ic_moderator.png new file mode 100644 index 0000000000000000000000000000000000000000..039a4dcd3824a52ab303ee28843c539c99aeb8a7 GIT binary patch literal 401 zcmV;C0dD?@P)<h;3K|Lk000e1NJLTq000;O000;W1^@s6;CDUv00001b5ch_0Itp) z=>Px$OG!jQR7ef&l08d8Q5eTP)lee#CK`SM2ZJ_28X{U-oLpLT@(cJ08r@w)Cx^zC zprKjl10*yAK~O@WlLTtl-;?LWxtFWAu%_;TAJ2LI&l~5tHxdcY|Ji`!I4wvodNJ`s zC<Wxi$!Wl*D-WRueK>Okw1b|2Cm2F@PR<FjN077S%0Z9;w=jlF*fR;-6<onR+`t_~ zt#}N-4stp(<3FHGFb7?Dg`zaUR^c5EAO$bb)UhM(5AY19AZwK4`+~AAS|_0f6S#&E zR3s*cyR1TD@~RJL!;X#x5BTgdYm}!@)^cGqW6{n*)><%{Sp4q~&;?P7Vs;a)ucGhz z=&k{4-UP3&lp1<9@zE!kH@}#zt=P_A+`|EZTlIeA41dOPVAbM;eLJj-L*pLzr15{j vZu6%N>;W4z{qmO`Fz@$Xwr^Pf^9@V^k6llBD$A9w00000NkvXXu0mjf$%3sA literal 0 HcmV?d00001 diff --git a/data/light/ic_moderator_audio_muted.png b/data/light/ic_moderator_audio_muted.png new file mode 100644 index 0000000000000000000000000000000000000000..2a366edca84178687342885a95214e9606379489 GIT binary patch literal 638 zcmV-^0)hRBP)<h;3K|Lk000e1NJLTq000;O000;W1^@s6;CDUv00001b5ch_0Itp) z=>Px%I7vi7R7efw)jMbuQ4|2sY&0=3#t$e62GkTHXeCN))W#2psAwUka4R7oC|HWN zA%a3G3!8v~uol`WvGJ2iZG>oHK+^ajs2EB7#Kf%UhU~JLnPs=I(hG;#cjvzM=FR7A zL&JX&#bR+UcX`b{PW-in8|)mjyx|tZMD%A(B7cfQS?5wYg`NDQKvNVr=D0${uF6i@ z7l@1LuFO~ct?)Dv9hF7eO?;!bl=r)j8S<rE&EtHhA6bRYz2Y{pqH9cXELpoanMkyA z+Qme!)Qo&1H;91`=W@A0#|4p^z*Q_#(Z#a`mFY%IzLR#&^N`wMf(?wbpUZqt_L5u5 z1n~kK<ReFub<vI`CL@-<g308GXd5WbJzxos7-kt0q#|{TQWX^mgRjwy`@E7-G6$6} zN3|9FQn8$`G#j{;b$&mgg?Rtl7^jtHqE9NaCYVz7f$cme9(oT$#4Edp^~8j6eh1UM zr*^o~7UKOLB(D4^zZqp8J;XzeeW{%RhKSo%UnuV43s?Ef7?lwN#x`=6NsiPv@_dyJ zjTDG0A7M2e#0p~IHqH<eUnibRX2i#m3LOqe-r*&kyyFEUY-9^t>8H;ZocdPev%}!W ziG3yZmsrU*E^(jODIe3zZsvB_?2$6EI^>BRu`hXAF23+-Vs&*#`9ssOTdx5wTJqdB zEEliwN!Ah(w=|x^X2(wy)Xq3ojq)?lDgK34>2R}DEf*hCv^SM<jxp&hW8~Gpb^M3p Y58>C_>;j)!Z~y=R07*qoM6N<$g3+ZWJpcdz literal 0 HcmV?d00001 diff --git a/data/light/ic_moderator_audio_unmuted.png b/data/light/ic_moderator_audio_unmuted.png new file mode 100644 index 0000000000000000000000000000000000000000..dd90a2b81d424e0049ff315fcba5c22f8e5870ac GIT binary patch literal 541 zcmV+&0^<FNP)<h;3K|Lk000e1NJLTq000;O000;W1^@s6;CDUv00001b5ch_0Itp) z=>Px$*GWV{R7efw)J-TvQ53-Oc{66rG`^NoM)^vLg$2=+jYzSuu#lC7lu~44ZzDyS zwT*>RV`HHvRu+>iY?KwHEPOOk$j~sF=YRClW6peJtT^@Sy?5?C_jKR9xemv_2-7rs zv4s;vG4g90XR#TiafS^XLn2&s6BemQ5)rs%L9PatA<<n_6Bd~GSjIGi_Jzo~@q`6? zUb}Z{Nn5Ug`=ntQ7q*;(=M>N9Xtd|G`+N-yicGE0%FB4p$Y{W}T?nBhXQegBJSV!~ z(dK6~U{VdD=+O$4Ts66E3@Bp`t%1CfN+h&`EV(lBkJ^~lK%4jDoYp{g@QMmNz{C6X zas3`uB<>+&^8TeJBeVuD%(_N94&lNuK7)oK9^~E2cn9ROy+Zz=*o#f<!iPO1FbDa% zCUFBfm&&n*7-UC<1(Q2*hgk$52c11+K{-aIA<c&hTiKdLfqdI5kS0s0L=b(Dh5eX9 z9INnVYmoJyNKq+8<Cw=Kj&O=Pw4fDXEOK-_b5$d21DQ<|`3TxD2>D_A5yL)qaA2KF z+!h%$t3?XZq~OST-dH;rWwn5{qU>0Hhi0y~h_Hh<YfQc5dqsY#*3o3Eme-a>9Ah;2 fa+KTV{gK;uc=$#Mr@w_Q00000NkvXXu0mjf?7;O5 literal 0 HcmV?d00001 diff --git a/data/light/ic_moderator_hangup.png b/data/light/ic_moderator_hangup.png new file mode 100644 index 0000000000000000000000000000000000000000..c49d0f857be950492635019e39d0eac196c296c6 GIT binary patch literal 513 zcmV+c0{;DpP)<h;3K|Lk000e1NJLTq000;O000;W1^@s6;CDUv00001b5ch_0Itp) z=>Px$yGcYrR7efol}#%|Q5eT()RYgQ46?KF3Gfjt?59NXnwOE1q>$M81SqyP*vj6^ znpnt3uu>C#Kg~6_x#yT`$-jQ?@qeE4+_~pG_uN9^zpKOpj=(Y8!)qPKddAF&su+zF z3d6+qpa(AD4j$kQI$#h6;2ElL3c1uq)-HU&3G_isWdes_u5+B*QYO}5x@Be~!R1PB z(r^PKm&L?NAQrP`2)u(!<Xme6<z_v?Suh$lXR$5n9;kD$MsVZp2$Q=YkLF1-e=(Kp zT%X2T8M%(P88>V!U|ZaqH0v6}S{d2fn;AE2EMZxEE*tuCw)ktUjC_e=lal0R1{Qbi zV{+52^`+*%b^T&mA+Q4$*pDUQ7h^FWYdaEL#rNiuk$Bp6nS)E@#@wVc>*F=97M}>j zLY9pZ++<tJLfxrZ>veBD<^43YP2Hnlo0bUplAMFu0B<<vKRM+~8hz2MksK8lj4eK@ znNt<={PoeJBB_&{qz8$S<2pr74V>Fj>Err{?D7vYJjy-N{pA*KZu@x8lI)}U!Rdsn zIxk$?p%?mL2%ew{xj#7Me#?!wBaXq|{`Du5Ie%QA{_<iGz}ORN00000NkvXXu0mjf DEd%q0 literal 0 HcmV?d00001 diff --git a/data/light/ic_moderator_maximize.png b/data/light/ic_moderator_maximize.png new file mode 100644 index 0000000000000000000000000000000000000000..1e231d6824df64eb1b9f0a7690fd656a2e595429 GIT binary patch literal 407 zcmV;I0cie-P)<h;3K|Lk000e1NJLTq000;O000;W1^@s6;CDUv00001b5ch_0Itp) z=>Px$QAtEWR7ef&mCs7UKoEx;wX}`(&r9^+vG3sn=p*?CJ@!x$J@ily3Kaw+iYQ(L zU!bQxfrmo3-z+;}Xe=aIJ?X&5%<S&`CTw<-M&sXpV+Wjb8EmpoCKBztqd`Ug#QI!> zIE8-DI7*Vlg*e{vw7YN*xy7pn1r)pR5R@nQSBZb50Xlo|D1z7+n&9*31_<rLGl*S+ zf0y|928bNO3y6(iTc4OXt^opVcmuH^Y^k_-@-xWnSf!Za9xPx0_Lx@EDJ({;1t%b< zrdwE0W`IID6@`n(m~6`}h@F<v`gRS)Gq|vm<SE>N*c3W;zEbQZI0afnZg1!fd0Xdd zdE0<kwo5-~*Tk(_qP9pI;157Ak-wpKUF|qD_`v(`3H}NSFFm$F=;O`_@*7%Pl*0V7 zh_Vry6E98v&Nu$p<fTKq1!Jo8r?D>ZwLb87@)anec3bghO~wEK002ovPDHLkV1kl| BwnYE{ literal 0 HcmV?d00001 diff --git a/data/light/ic_moderator_minimize.png b/data/light/ic_moderator_minimize.png new file mode 100644 index 0000000000000000000000000000000000000000..144cfb69044b3afcdc5f08f42c8aae0088723e1a GIT binary patch literal 427 zcmV;c0aX5pP)<h;3K|Lk000e1NJLTq000;O000;W1^@s6;CDUv00001b5ch_0Itp) z=>Px$Wl2OqR7ef&l)ElOQ51#`8H`IrM8ZR;6bd5I=qbb$o<Snf%?s#grcod+iH1Z$ z+zZVV8c(3}3gR*@-ydgBww<$!y)sdVKlx@~*T2rbO_u%Z7k8k8Tsv?FZPvW~1YW^( zV9-hX7(U@77)xqS<|eCNRGIo|-hV;a?Tuw6zhS7$OkX|&XD|oP&{TV~mR{*4MB5>A zm9y}}oOZI@7S)Kn1?{<3pfhNRT!MGpWPSv?JyBWDKwH~@C)mi{m2e-f^R}rNCF%oY zR>W558a{{-CM-bFvR~-mg)Y#02&IG%pbv<u%1F_dxxS@nWMm*bgEi*egIj^F;yMh0 z)Uw+mvp#TF@sYY#u>whoJv8m{IZT1>`7)SPlwbg-OPGK|n1@#|DNHbki|+9_96$x$ zz$`z34)O-ZU=J2Sk4bV#T>0Ib_*-Yb4ssLx#IosuDh#V()i#0ppAR3O)$fnQeE|%i VNRIDE@Kpc+002ovPDHLkV1nu(yFCB^ literal 0 HcmV?d00001 diff --git a/data/light/ic_star.png b/data/light/ic_star.png new file mode 100644 index 0000000000000000000000000000000000000000..f24a13afe077d523d9f3c982d0274e183bb80320 GIT binary patch literal 911 zcmV;A191F_P)<h;3K|Lk000e1NJLTq002k;002k`1^@s6RqeA!000A4Nkl<ZcmeI& z0YqeL6ae5KgT){`gvH<?EJ6rjS%eT4VGu$HA%qYXVG+V2gh2?45JCup5JG@K7(9f* zVDLS>$FRqpTVrnBY3>K`!_2++{OA1VKWP#wl}e>jsbB?-jO7Y0@H15+e8vFJ_>^W` zfFncOX{drv=o)%RLlrbdE9pNK+=(tyB_%A2V1Uon1EM9{QJQ6#M_;z?Os9Mt$k$A> z2sJ#(cb8@nzKc5OiI&r>!L4WyC!$uGC0G>oF^7eWwfQtF@Jn<$Tz4%xNV5QQ=!+K9 z|FACVqLw~7?1|bj!|8}N(nt5wKaPFTZTiTt_R`S7U+9aLrmfg(U<u!_i9H<Q3|DBQ zgFc9UqzpKft%G}9;|xEsk4>y%84c8mY@lyq7l$~*Z`_1}ZhV7L=^INXqlXS|@EfN% z#15KceU(^6Pq8ji4zv}kJ9gM&5naVuNjb2gIFD#dq9yS#fw%OC8?<nO{TJ_Z!w|p3 zZQRqwa)WxB1k`aQTOU8LizdF}3+9V&_G@4nYuLmAPB4(Ig<26A>r}Q&)YCgd9XGNa zrXpcqw!8N{RHA`L5e%@I-U2qz7r_%2(i_7Px+3V|>w63ul;Q_r9X%0ru#nyZ7T)`l z#tsH03;A$nx_#?S5j7l_IBA4*d;V5w6?I(5c7j^TCp3Z^%uVPntu2B9_EMQ)3j+}x z7kCrf!}nBb`3K!*L3LPn>aK=xFKQGt>~NW;3F<M-bSIY(LXrBMrU_O>=LKInE~aUL zL(y)**>XEg3tYeU4WovAY>nCcZ%t7nKV-088Uf$%XsC^)(U*@wCrtw^iLOS>MxTpd zfL~d~B_2fcsqC;TI?M>Co_znHnSZ~}R1}K15PkilP4ejVZt*2&boe-x9l9}8)G?2i z(FXbJjxmSE)J>z|Z_zz=@ocnfEMOUTM(bh&PsEpz$_kra?+F`mkFuTrPz^-irfV=N zKV0fL&A67=IZS1R$8k2?JXpblm$@qQ<XE0An&TIs`Fk09sf@6mA5IhND7K8YI8E)= zHol|@%c-Y!QrVy#D_~oxAUPHJc`A+A#ENIAg2gkPPAU_u#8Y_ja=HSIvfX5CrS}xW lZY@<Rl}e>jsZ=UO`4@rrK9g?5G{OJ?002ovPDHLkV1g@3pGyD$ literal 0 HcmV?d00001 diff --git a/src/CurrentCallVC.mm b/src/CurrentCallVC.mm index edd7340a..e75ccc3d 100644 --- a/src/CurrentCallVC.mm +++ b/src/CurrentCallVC.mm @@ -1306,17 +1306,7 @@ CVPixelBufferRef pixelBufferPreview; if (accountInfo_ == nil) return; auto* callModel = accountInfo_->callModel.get(); - - auto convOpt = getConversationFromURI(QString::fromNSString(uri), *accountInfo_->conversationModel); - if (!convOpt.has_value()) { - return; - } - lrc::api::conversation::Info& conversation = *convOpt; - auto callId = conversation.callId; - if (not callModel->hasCall(callId)){ - return; - } - callModel->hangUp(callId); + callModel->hangupParticipant([self getcallID], QString::fromNSString(uri)); } -(void)minimizeParticipant { @@ -1377,7 +1367,7 @@ CVPixelBufferRef pixelBufferPreview; -(BOOL)isParticipantHost:(NSString*)uri { if (accountInfo_ == nil) return false; - if ([self isMasterCall]) { + if ([self isMasterCall] ) { return accountInfo_->profileInfo.uri == QString::fromNSString(uri); } auto convOpt = getConversationFromUid(convUid_, *accountInfo_->conversationModel.get()); diff --git a/src/views/ConferenceOverlayView.h b/src/views/ConferenceOverlayView.h index ab2d9a52..679af939 100644 --- a/src/views/ConferenceOverlayView.h +++ b/src/views/ConferenceOverlayView.h @@ -20,6 +20,7 @@ #import <Cocoa/Cocoa.h> #import "GradientView.h" #import "IconButton.h" +#import "CustomBackgroundView.h" NS_ASSUME_NONNULL_BEGIN @@ -60,12 +61,28 @@ struct ConferenceParticipant { @property (nonatomic, weak) NSLayoutConstraint* heightConstraint; @property (nonatomic, weak) NSLayoutConstraint* centerXConstraint; @property (nonatomic, weak) NSLayoutConstraint* centerYConstraint; -@property GradientView* gradientView; -@property IconButton* settingsButton; +@property NSView* backgroundView; +@property NSView* increasedBackgroundView; +@property NSStackView* states; +@property NSStackView* buttonsContainer; +@property NSStackView* infoContainer; @property NSTextView* usernameLabel; -@property NSMenu *contextualMenu; @property (retain, nonatomic) id <ConferenceLayoutDelegate> delegate; +//actions +@property IconButton* maximize; +@property IconButton* minimize; +@property IconButton* hangup; +@property IconButton* setModerator; +@property IconButton* muteAudio; + +//state +@property CustomBackgroundView* moderatorState; +@property CustomBackgroundView* audioState; +@property CustomBackgroundView* hostState; +@property CustomBackgroundView* cusp; + + - (void)configureView; - (void)updateViewWithParticipant:(ConferenceParticipant) participant; - (void)sizeChanged; diff --git a/src/views/ConferenceOverlayView.mm b/src/views/ConferenceOverlayView.mm index b6afaac9..96b56b48 100644 --- a/src/views/ConferenceOverlayView.mm +++ b/src/views/ConferenceOverlayView.mm @@ -18,12 +18,14 @@ */ #import "ConferenceOverlayView.h" +#import "CustomBackgroundView.h" @implementation ConferenceOverlayView -@synthesize contextualMenu; -CGFloat const margin = 6; -CGFloat const controlSize = 40; +CGFloat const margin = 2; +CGFloat const controlSize = 25; +CGFloat const minWidth = 140; +CGFloat const minHeight = 80; - (instancetype)initWithFrame:(NSRect)frame { @@ -32,111 +34,169 @@ CGFloat const controlSize = 40; self.translatesAutoresizingMaskIntoConstraints = false; [self setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable | NSViewMinYMargin | NSViewMaxYMargin | NSViewMinXMargin | NSViewMaxXMargin]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sizeChanged) name:NSWindowDidResizeNotification object:nil]; - self.alphaValue = 0; + self.wantsLayer = true; + self.layer.masksToBounds = false; + [self addViews]; } return self; } -- (void)configureView { - self.gradientView = [[GradientView alloc] init]; - self.gradientView.startingColor = [NSColor clearColor]; - self.gradientView.endingColor = [NSColor blackColor]; - self.gradientView.angle = 270; - self.gradientView.translatesAutoresizingMaskIntoConstraints = false; - [self addSubview: self.gradientView]; - [self.gradientView.widthAnchor constraintEqualToAnchor:self.widthAnchor multiplier: 1].active = TRUE; - [self.gradientView.heightAnchor constraintEqualToConstant: controlSize].active = true; - [self.gradientView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor].active = true; - [self.gradientView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor].active = true; - self.settingsButton = [[IconButton alloc] init]; - self.settingsButton.transparent = true; - self.settingsButton.title = @""; - NSImage* settingsImage = [NSImage imageNamed: @"ic_more.png"]; - [self.settingsButton setImage:settingsImage]; - self.settingsButton.bgColor = [NSColor clearColor]; - self.settingsButton.imageColor = [NSColor whiteColor]; - self.settingsButton.imagePressedColor = [NSColor lightGrayColor]; - self.settingsButton.imageInsets = margin; - self.settingsButton.translatesAutoresizingMaskIntoConstraints = false; - [self.gradientView addSubview:self.settingsButton]; - - [self.settingsButton.widthAnchor constraintEqualToConstant: controlSize].active = TRUE; - [self.settingsButton.heightAnchor constraintEqualToConstant: controlSize].active = true; - [self.settingsButton.trailingAnchor constraintEqualToAnchor: self.gradientView.trailingAnchor].active = true; - [self.settingsButton.bottomAnchor constraintEqualToAnchor:self.gradientView.bottomAnchor].active = true; - [self.settingsButton setAction:@selector(triggerMenu:)]; - [self.settingsButton setTarget:self]; - BOOL showSettings = [self.delegate isMasterCall] || [self.delegate isCallModerator]; - [self.settingsButton setHidden: !showSettings]; +- (void)addViews { + self.increasedBackgroundView = [[NSView alloc] init]; + [self.increasedBackgroundView setWantsLayer: YES]; + self.increasedBackgroundView.layer.backgroundColor = [[NSColor colorWithCalibratedRed: 0 green: 0 blue: 0 alpha: 0.8] CGColor]; + self.increasedBackgroundView.translatesAutoresizingMaskIntoConstraints = false; + [self addSubview: self.increasedBackgroundView]; + self.increasedBackgroundView.hidden = true; + self.increasedBackgroundView.layer.masksToBounds = true; + self.increasedBackgroundView.layer.cornerRadius = 6; + + self.backgroundView = [[NSView alloc] init]; + [self.backgroundView setWantsLayer: YES]; + self.backgroundView.layer.backgroundColor = [[NSColor colorWithCalibratedRed: 0 green: 0 blue: 0 alpha: 0.6] CGColor]; + self.backgroundView.translatesAutoresizingMaskIntoConstraints = false; + [self addSubview: self.backgroundView]; + self.backgroundView.hidden = true; + + //participat state + self.audioState = [[CustomBackgroundView alloc] init]; + self.audioState.backgroundType = RECTANGLE_WITH_ROUNDED_RIGHT_CORNER; + [self.audioState.widthAnchor constraintEqualToConstant: controlSize].active = true; + [self.audioState.heightAnchor constraintEqualToConstant: controlSize].active = true; + NSImage* audioImage = [NSImage imageNamed: @"ic_moderator_audio_muted.png"]; + self.audioState.image = audioImage; + + self.moderatorState = [[CustomBackgroundView alloc] init]; + self.moderatorState.backgroundType = RECTANGLE; + [self.moderatorState.widthAnchor constraintEqualToConstant: controlSize].active = true; + [self.moderatorState.heightAnchor constraintEqualToConstant: controlSize].active = true; + NSImage* moderatorImage = [NSImage imageNamed: @"ic_moderator.png"]; + self.moderatorState.image = moderatorImage; + + self.hostState = [[CustomBackgroundView alloc] init]; + self.hostState.backgroundType = RECTANGLE; + [self.hostState.widthAnchor constraintEqualToConstant: controlSize].active = true; + [self.hostState.heightAnchor constraintEqualToConstant: controlSize].active = true; + NSImage* hostImage = [NSImage imageNamed: @"ic_star.png"]; + self.hostState.image = hostImage; + + self.cusp = [[CustomBackgroundView alloc] init]; + self.cusp.backgroundType = CUSP; + [self.cusp.widthAnchor constraintEqualToConstant: 6].active = true; + [self.cusp.heightAnchor constraintEqualToConstant: controlSize].active = true; + + NSArray *statesViews = [NSArray arrayWithObjects: self.hostState, self.moderatorState, self.audioState, self.cusp, nil]; + self.states = [NSStackView stackViewWithViews: statesViews]; + self.states.spacing = 0; + [self addSubview: self.states]; + + //actions + self.maximize = [self getActionbutton]; + NSImage* maximizeImage = [NSImage imageNamed: @"ic_moderator_maximize.png"]; + [self.maximize setImage: maximizeImage]; + [self.maximize setAction:@selector(maximize:)]; + [self.maximize setTarget:self]; + + self.minimize = [self getActionbutton]; + NSImage* minimizeImage = [NSImage imageNamed: @"ic_moderator_minimize.png"]; + [self.minimize setImage: minimizeImage]; + [self.minimize setAction:@selector(minimize:)]; + [self.minimize setTarget:self]; + + self.hangup = [self getActionbutton]; + NSImage* hangupImage = [NSImage imageNamed: @"ic_moderator_hangup.png"]; + [self.hangup setImage: hangupImage]; + [self.hangup setAction:@selector(finishCall:)]; + [self.hangup setTarget:self]; + + self.setModerator = [self getActionbutton]; + NSImage* setModeratorImage = [NSImage imageNamed: @"ic_moderator.png"]; + [self.setModerator setImage: setModeratorImage]; + [self.setModerator setAction:@selector(setModerator:)]; + [self.setModerator setTarget:self]; + + self.muteAudio = [self getActionbutton]; + NSImage* muteAudioImage = [NSImage imageNamed: @"ic_moderator_audio_muted.png"]; + [self.muteAudio setImage: muteAudioImage]; + [self.muteAudio setAction:@selector(muteAudio:)]; + [self.muteAudio setTarget:self]; + + NSArray *actions = [NSArray arrayWithObjects: self.setModerator, self.muteAudio, self.maximize, self.minimize, self.hangup, nil]; + self.buttonsContainer = [NSStackView stackViewWithViews: actions]; + self.buttonsContainer.orientation = NSUserInterfaceLayoutOrientationHorizontal; + self.buttonsContainer.spacing = 5; + self.usernameLabel = [[NSTextView alloc] init]; + self.usernameLabel.alignment = NSTextAlignmentCenter; self.usernameLabel.textColor = [NSColor whiteColor]; - self.usernameLabel.editable = NO; - self.usernameLabel.drawsBackground = NO; - self.usernameLabel.backgroundColor = [NSColor clearColor]; - self.usernameLabel.font = [NSFont userFontOfSize: 14.0]; + self.usernameLabel.editable = false; + self.usernameLabel.drawsBackground = false; + self.usernameLabel.font = [NSFont userFontOfSize: 13.0]; self.usernameLabel.translatesAutoresizingMaskIntoConstraints = false; + [self.usernameLabel.heightAnchor constraintEqualToConstant: 20].active = true; + [self.usernameLabel.widthAnchor constraintGreaterThanOrEqualToConstant: 60].active = true; self.usernameLabel.textContainer.maximumNumberOfLines = 1; - [self.gradientView addSubview:self.usernameLabel]; + + NSArray* infoItems = [NSArray arrayWithObjects: self.usernameLabel, self.buttonsContainer, nil]; + + self.infoContainer = [NSStackView stackViewWithViews: infoItems]; + self.infoContainer.orientation = NSUserInterfaceLayoutOrientationVertical; + self.infoContainer.spacing = 0; + self.infoContainer.distribution = NSStackViewDistributionFillEqually; + self.infoContainer.alignment = NSLayoutAttributeCenterX; + [self.backgroundView addSubview: self.infoContainer]; +} - [self.usernameLabel.leadingAnchor constraintEqualToAnchor: self.gradientView.leadingAnchor constant: margin * 2].active = true; - [self.usernameLabel.trailingAnchor constraintEqualToAnchor: self.gradientView.trailingAnchor constant: -(controlSize + margin)].active = true; - [self.usernameLabel.bottomAnchor constraintEqualToAnchor:self.gradientView.bottomAnchor constant: - margin * 2].active = true; +- (IconButton*) getActionbutton { + IconButton* button = [[IconButton alloc] init]; + button.transparent = true; + [button.widthAnchor constraintEqualToConstant: controlSize].active = true; + [button.heightAnchor constraintEqualToConstant: controlSize].active = true; + button.title = @""; + button.buttonDisableColor = [NSColor lightGrayColor]; + button.bgColor = [NSColor clearColor]; + button.imageColor = [NSColor whiteColor]; + button.imagePressedColor = [NSColor lightGrayColor]; + button.imageInsets = margin; + button.translatesAutoresizingMaskIntoConstraints = false; + return button; } -- (IBAction)triggerMenu:(id)sender { - int layout = [self.delegate getCurrentLayout]; - if (layout < 0) - return; - BOOL showConferenceHostOnly = !self.participant.isLocal && [self.delegate isMasterCall]; - BOOL showHangup = !self.participant.isLocal && [self.delegate isParticipantHost:self.participant.uri]; - BOOL showMaximized = layout != 2; - BOOL showMinimized = !(layout == 0 || (layout == 1 && !self.participant.active)); - contextualMenu = [[NSMenu alloc] initWithTitle:@""]; - if (showMinimized) { - NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Minimize participant", @"Conference action") action:@selector(minimize:) keyEquivalent:@""]; - [menuItem setTarget:self]; - [contextualMenu insertItem:menuItem atIndex:contextualMenu.itemArray.count]; - } - if (showMaximized) { - NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Maximize participant", @"Conference action") action:@selector(maximize:) keyEquivalent:@""]; - [menuItem setTarget:self]; - [contextualMenu insertItem:menuItem atIndex:contextualMenu.itemArray.count]; - } - if (showConferenceHostOnly) { - auto setModeratorTitle = self.participant.isModerator ? NSLocalizedString(@"Unset moderator", @"Conference action") : NSLocalizedString(@"Set moderator", @"Conference action"); - NSMenuItem *menuItemModerator = [[NSMenuItem alloc] initWithTitle: setModeratorTitle action:@selector(setModerator:) keyEquivalent:@""]; - [menuItemModerator setTarget:self]; - [contextualMenu insertItem:menuItemModerator atIndex:contextualMenu.itemArray.count]; - } - auto audioTitle = self.participant.audioModeratorMuted ? NSLocalizedString(@"Unmute audio", @"Conference action") : NSLocalizedString(@"Mute audio", @"Conference action"); - NSMenuItem *menuItemAudio = [[NSMenuItem alloc] initWithTitle: audioTitle action:@selector(muteAudio:) keyEquivalent:@""]; - [menuItemAudio setTarget:self]; - [contextualMenu insertItem:menuItemAudio atIndex:contextualMenu.itemArray.count]; - if (showHangup) { - NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Hangup", @"Conference action") action:@selector(finishCall:) keyEquivalent:@""]; - [menuItem setTarget:self]; - [contextualMenu insertItem:menuItem atIndex:contextualMenu.itemArray.count]; - } - [contextualMenu popUpMenuPositioningItem:nil atLocation:[NSEvent mouseLocation] inView:nil]; +- (void)configureView { + [self.backgroundView.widthAnchor constraintEqualToAnchor:self.widthAnchor multiplier: 1].active = TRUE; + [self.backgroundView.topAnchor constraintEqualToAnchor:self.topAnchor].active = true; + [self.backgroundView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor].active = true; + [self.backgroundView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor].active = true; + + [self.states.topAnchor constraintEqualToAnchor:self.topAnchor].active = true; + [self.states.leadingAnchor constraintEqualToAnchor:self.leadingAnchor].active = true; + + [self.infoContainer.centerYAnchor constraintEqualToAnchor:self.centerYAnchor constant:1].active = true; + [self.infoContainer.centerXAnchor constraintEqualToAnchor:self.centerXAnchor constant:1].active = true; + + [self.increasedBackgroundView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor constant:1].active = true; + [self.increasedBackgroundView.centerXAnchor constraintEqualToAnchor:self.centerXAnchor constant:1].active = true; + [self.increasedBackgroundView.widthAnchor constraintEqualToConstant:minWidth].active = true; + [self.increasedBackgroundView.heightAnchor constraintEqualToConstant:minHeight].active = true; } -- (void)minimize:(NSMenuItem*) sender { +- (IBAction)minimize:(id) sender { [self.delegate minimizeParticipant]; } -- (void)maximize:(NSMenuItem*) sender { +- (IBAction)maximize:(id) sender { [self.delegate maximizeParticipant:self.participant.uri active: self.participant.active]; } -- (void)finishCall:(NSMenuItem*) sender { +- (IBAction)finishCall:(id) sender { [self.delegate hangUpParticipant: self.participant.uri]; } -- (void)muteAudio:(NSMenuItem*) sender { +- (IBAction)muteAudio:(id) sender { [self.delegate muteParticipantAudio: self.participant.uri state: !self.participant.audioModeratorMuted]; } -- (void)setModerator:(NSMenuItem*) sender { +- (IBAction)setModerator:(id) sender { [self.delegate setModerator:self.participant.uri state: !self.participant.isModerator]; } @@ -193,25 +253,66 @@ CGFloat const controlSize = 40; self.centerXConstraint.active = YES; self.centerYConstraint.active = YES; [self layoutSubtreeIfNeeded]; - CGFloat width = self.frame.size.width; - self.usernameLabel.hidden = width < 150; } - (void)updateViewWithParticipant:(ConferenceParticipant) participant { + bool sizeChanged = self.participant.width != participant.width || self.participant.hight != participant.hight + || self.participant.x != participant.x || self.participant.y != participant.y; self.participant = participant; - BOOL showSettings = [self.delegate isMasterCall] || [self.delegate isCallModerator]; - [self.settingsButton setHidden: !showSettings]; + [self updateButtonsState]; [self sizeChanged]; self.usernameLabel.string = self.participant.bestName; } +-(void) updateButtonsState { + self.audioState.hidden = !self.participant.audioModeratorMuted; + self.moderatorState.hidden = !self.participant.isModerator || [self.delegate isParticipantHost: self.participant.uri]; + self.hostState.hidden = ![self.delegate isParticipantHost: self.participant.uri]; + self.cusp.hidden = (self.audioState.hidden && self.moderatorState.hidden && self.hostState.hidden); + BackgroundType type = self.audioState.hidden ? RECTANGLE_WITH_ROUNDED_RIGHT_CORNER : RECTANGLE; + if (!self.moderatorState.hidden && self.moderatorState.backgroundType != type) { + self.moderatorState.backgroundType = type; + [self.moderatorState setNeedsDisplay:YES]; + } + if (!self.hostState.hidden && self.hostState.backgroundType != type) { + self.hostState.backgroundType = type; + [self.hostState setNeedsDisplay:YES]; + } + bool couldManageConference = [self.delegate isMasterCall] || [self.delegate isCallModerator]; + self.buttonsContainer.hidden = !couldManageConference; + if (!couldManageConference) { + return; + } + int layout = [self.delegate getCurrentLayout]; + if (layout < 0) + return; + BOOL showConferenceHostOnly = !self.participant.isLocal && [self.delegate isMasterCall]; + BOOL hangupEnabled = ![self.delegate isParticipantHost: self.participant.uri]; + BOOL showMaximized = layout != 2; + BOOL showMinimized = !(layout == 0 || (layout == 1 && !self.participant.active)); + self.setModerator.enabled = showConferenceHostOnly; + self.hangup.enabled = hangupEnabled; + self.minimize.hidden = !showMinimized; + self.maximize.hidden = !showMaximized; + NSImage* muteAudioImage = self.participant.audioModeratorMuted ? [NSImage imageNamed: @"ic_moderator_audio_muted.png"] : + [NSImage imageNamed: @"ic_moderator_audio_unmuted.png"]; + [self.muteAudio setImage: muteAudioImage]; +} + -(void)mouseEntered:(NSEvent *)theEvent { - self.alphaValue = 1; + self.backgroundView.hidden = NO; + auto size1 = self.frame.size; + if (size1.width < minWidth && size1.height < minHeight) { + self.increasedBackgroundView.hidden = false; + self.backgroundView.layer.backgroundColor = [[NSColor clearColor] CGColor]; + } [super mouseEntered:theEvent]; } -(void)mouseExited:(NSEvent *)theEvent { - self.alphaValue = 0; + self.backgroundView.hidden = YES; + self.increasedBackgroundView.hidden = true; + self.backgroundView.layer.backgroundColor = [[NSColor colorWithCalibratedRed: 0 green: 0 blue: 0 alpha: 0.6] CGColor]; [super mouseExited:theEvent]; } diff --git a/src/views/CustomBackgroundView.h b/src/views/CustomBackgroundView.h new file mode 100644 index 00000000..8426ad2f --- /dev/null +++ b/src/views/CustomBackgroundView.h @@ -0,0 +1,39 @@ +/* +* Copyright (C) 2021 Savoir-faire Linux Inc. +* Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com> +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#import <Cocoa/Cocoa.h> + +NS_ASSUME_NONNULL_BEGIN + +typedef enum { + RECTANGLE = 1, + RECTANGLE_WITH_ROUNDED_RIGHT_CORNER, + CUSP +} BackgroundType; + +@interface CustomBackgroundView : NSView + +@property (nonatomic, strong) NSImage* image; +@property (nonatomic, strong) NSColor* imageColor; +@property (nonatomic, strong) NSColor* backgroundColor; +@property BackgroundType backgroundType; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/views/CustomBackgroundView.mm b/src/views/CustomBackgroundView.mm new file mode 100644 index 00000000..f0e81da5 --- /dev/null +++ b/src/views/CustomBackgroundView.mm @@ -0,0 +1,98 @@ +/* +* Copyright (C) 2021 Savoir-faire Linux Inc. +* Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com> +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#import "NSColor+RingTheme.h" +#import "CustomBackgroundView.h" + +#import <QuartzCore/QuartzCore.h> + +@implementation CustomBackgroundView + + +- (void)drawRect:(NSRect)dirtyRect +{ + [super drawRect:dirtyRect]; + NSColor *backgroundColor = self.backgroundColor ? self.backgroundColor : [NSColor colorWithCalibratedRed: 0 green: 0 blue: 0 alpha: 0.8]; + NSColor *backgroundStrokeColor = [NSColor clearColor]; + NSColor *tintColor = self.imageColor ? self.imageColor : [NSColor whiteColor]; + + NSBezierPath* path; + + switch (self.backgroundType) { + case RECTANGLE: { + path = [NSBezierPath bezierPathWithRoundedRect:dirtyRect xRadius:0 yRadius:0]; + break; + } + case RECTANGLE_WITH_ROUNDED_RIGHT_CORNER: { + path = [[NSBezierPath alloc] init]; + NSPoint bottomLeft = dirtyRect.origin; + NSPoint topLeft = CGPointMake(dirtyRect.origin.x, dirtyRect.size.height); + NSPoint topRight = CGPointMake(dirtyRect.size.width, dirtyRect.size.height); + NSPoint bottomRight = CGPointMake(dirtyRect.size.width * 0.5, dirtyRect.origin.y); + NSPoint middle = CGPointMake(dirtyRect.size.width, dirtyRect.size.height * 0.5); + NSPoint controlPoint1 = CGPointMake(dirtyRect.size.width * 0.96, dirtyRect.size.height * 0.01); + NSPoint controlPoint2 = CGPointMake(dirtyRect.size.width * 0.98, dirtyRect.size.height * 0.2); + [path setLineWidth:1.0]; + [path moveToPoint:topLeft]; + [path lineToPoint:bottomLeft]; + + [path lineToPoint:bottomRight]; + [path curveToPoint:middle controlPoint1:controlPoint1 controlPoint2:controlPoint2]; + [path lineToPoint:topRight]; + [path closePath]; + break; + } + case CUSP: { + path = [[NSBezierPath alloc] init]; + NSPoint bottomLeft = CGPointMake(dirtyRect.origin.x, dirtyRect.size.height * 0.5); + NSPoint topLeft = CGPointMake(dirtyRect.origin.x, dirtyRect.size.height); + NSPoint topRight = CGPointMake(dirtyRect.size.width, dirtyRect.size.height); + NSPoint controlPoint1 = CGPointMake(dirtyRect.size.width * 0.1, dirtyRect.size.height * 0.7); + NSPoint controlPoint2 = CGPointMake(dirtyRect.size.width * 0.01, dirtyRect.size.height * 0.9); + [path setLineWidth:1.0]; + [path moveToPoint:topLeft]; + [path lineToPoint:bottomLeft]; + + [path curveToPoint:topRight controlPoint1:controlPoint1 controlPoint2:controlPoint2]; + [path closePath]; + break; + } + } + + [backgroundColor set]; + [path fill]; + [[NSColor clearColor] set]; + [path stroke]; + + if (self.backgroundType == CUSP) { + return; + } + + NSRect rectImage = NSInsetRect(dirtyRect, 4, 4); + rectImage.size.width = rectImage.size.height; + + [[NSColor image: self.image tintedWithColor:tintColor] drawInRect:rectImage + fromRect:NSZeroRect + operation:NSCompositeSourceOver + fraction:1.0 + respectFlipped:YES + hints:nil]; +} + +@end -- GitLab