diff --git a/CMakeLists.txt b/CMakeLists.txt index c0f381b0f3665205529239dcacdc4091969e070c..9c7547e94244323487752282f66a4f2d0a377014 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 Binary files /dev/null and b/data/light/ic_moderator.png differ 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 Binary files /dev/null and b/data/light/ic_moderator_audio_muted.png differ 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 Binary files /dev/null and b/data/light/ic_moderator_audio_unmuted.png differ diff --git a/data/light/ic_moderator_hangup.png b/data/light/ic_moderator_hangup.png new file mode 100644 index 0000000000000000000000000000000000000000..c49d0f857be950492635019e39d0eac196c296c6 Binary files /dev/null and b/data/light/ic_moderator_hangup.png differ diff --git a/data/light/ic_moderator_maximize.png b/data/light/ic_moderator_maximize.png new file mode 100644 index 0000000000000000000000000000000000000000..1e231d6824df64eb1b9f0a7690fd656a2e595429 Binary files /dev/null and b/data/light/ic_moderator_maximize.png differ diff --git a/data/light/ic_moderator_minimize.png b/data/light/ic_moderator_minimize.png new file mode 100644 index 0000000000000000000000000000000000000000..144cfb69044b3afcdc5f08f42c8aae0088723e1a Binary files /dev/null and b/data/light/ic_moderator_minimize.png differ diff --git a/data/light/ic_star.png b/data/light/ic_star.png new file mode 100644 index 0000000000000000000000000000000000000000..f24a13afe077d523d9f3c982d0274e183bb80320 Binary files /dev/null and b/data/light/ic_star.png differ diff --git a/src/CurrentCallVC.mm b/src/CurrentCallVC.mm index edd7340a44851aae572050b3293898bcfd7bc876..e75ccc3d8b46c5dd78da9be685b293c84b9257d8 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 ab2d9a527a7845db48026a157f92bea579c57e0f..679af9391b1ef8394c510cdb11d43c1942361a2b 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 b6afaac948f623d637cdaa8c697985a1d7e3ecf8..96b56b480e5c801cfcb98ed28aa4c01a401a5ac6 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 0000000000000000000000000000000000000000..8426ad2f07e1b8263694f42a157cb18b28bdf730 --- /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 0000000000000000000000000000000000000000..f0e81da5aee54bccc9b4cf72ddbbc2a998da2a58 --- /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