|
@@ -15,13 +15,6 @@ private enum SidebarPage: Int {
|
|
|
case settings = 4
|
|
case settings = 4
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-private enum MeetingProvider: Int {
|
|
|
|
|
- case meet = 0
|
|
|
|
|
- case zoom = 1
|
|
|
|
|
- case teams = 2
|
|
|
|
|
- case zoho = 3
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
private enum ZoomJoinMode: Int {
|
|
private enum ZoomJoinMode: Int {
|
|
|
case id = 0
|
|
case id = 0
|
|
|
case url = 1
|
|
case url = 1
|
|
@@ -50,13 +43,10 @@ final class ViewController: NSViewController {
|
|
|
|
|
|
|
|
private var mainContentHost: NSView?
|
|
private var mainContentHost: NSView?
|
|
|
private var sidebarRowViews: [SidebarPage: NSView] = [:]
|
|
private var sidebarRowViews: [SidebarPage: NSView] = [:]
|
|
|
- private var tabViews: [MeetingProvider: NSView] = [:]
|
|
|
|
|
private var selectedSidebarPage: SidebarPage = .joinMeetings
|
|
private var selectedSidebarPage: SidebarPage = .joinMeetings
|
|
|
- private var selectedMeetingProvider: MeetingProvider = .meet
|
|
|
|
|
private var selectedZoomJoinMode: ZoomJoinMode = .id
|
|
private var selectedZoomJoinMode: ZoomJoinMode = .id
|
|
|
private var pageCache: [SidebarPage: NSView] = [:]
|
|
private var pageCache: [SidebarPage: NSView] = [:]
|
|
|
private var sidebarPageByView = [ObjectIdentifier: SidebarPage]()
|
|
private var sidebarPageByView = [ObjectIdentifier: SidebarPage]()
|
|
|
- private var meetingProviderByView = [ObjectIdentifier: MeetingProvider]()
|
|
|
|
|
private var zoomJoinModeByView = [ObjectIdentifier: ZoomJoinMode]()
|
|
private var zoomJoinModeByView = [ObjectIdentifier: ZoomJoinMode]()
|
|
|
private var zoomJoinModeViews: [ZoomJoinMode: NSView] = [:]
|
|
private var zoomJoinModeViews: [ZoomJoinMode: NSView] = [:]
|
|
|
private var settingsActionByView = [ObjectIdentifier: SettingsAction]()
|
|
private var settingsActionByView = [ObjectIdentifier: SettingsAction]()
|
|
@@ -169,28 +159,13 @@ private extension ViewController {
|
|
|
showSidebarPage(page)
|
|
showSidebarPage(page)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- @objc private func meetingTabClicked(_ sender: NSClickGestureRecognizer) {
|
|
|
|
|
- guard let view = sender.view,
|
|
|
|
|
- let provider = meetingProviderByView[ObjectIdentifier(view)],
|
|
|
|
|
- provider != selectedMeetingProvider else { return }
|
|
|
|
|
- selectedMeetingProvider = provider
|
|
|
|
|
- if provider == .zoom {
|
|
|
|
|
- selectedZoomJoinMode = .id
|
|
|
|
|
- }
|
|
|
|
|
- updateTabAppearance()
|
|
|
|
|
- if selectedSidebarPage == .joinMeetings {
|
|
|
|
|
- pageCache[.joinMeetings] = nil
|
|
|
|
|
- showSidebarPage(.joinMeetings)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
@objc private func zoomJoinModeClicked(_ sender: NSClickGestureRecognizer) {
|
|
@objc private func zoomJoinModeClicked(_ sender: NSClickGestureRecognizer) {
|
|
|
guard let view = sender.view,
|
|
guard let view = sender.view,
|
|
|
let mode = zoomJoinModeByView[ObjectIdentifier(view)],
|
|
let mode = zoomJoinModeByView[ObjectIdentifier(view)],
|
|
|
mode != selectedZoomJoinMode else { return }
|
|
mode != selectedZoomJoinMode else { return }
|
|
|
selectedZoomJoinMode = mode
|
|
selectedZoomJoinMode = mode
|
|
|
updateZoomJoinModeAppearance()
|
|
updateZoomJoinModeAppearance()
|
|
|
- if selectedSidebarPage == .joinMeetings, selectedMeetingProvider == .zoom {
|
|
|
|
|
|
|
+ if selectedSidebarPage == .joinMeetings {
|
|
|
pageCache[.joinMeetings] = nil
|
|
pageCache[.joinMeetings] = nil
|
|
|
showSidebarPage(.joinMeetings)
|
|
showSidebarPage(.joinMeetings)
|
|
|
}
|
|
}
|
|
@@ -420,12 +395,6 @@ private extension ViewController {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private func updateTabAppearance() {
|
|
|
|
|
- for (provider, tab) in tabViews {
|
|
|
|
|
- applyTabStyle(tab, provider: provider, logoTemplate: logoTemplateForMeetingProvider(provider))
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
private func logoTemplateForSidebarPage(_ page: SidebarPage) -> Bool {
|
|
private func logoTemplateForSidebarPage(_ page: SidebarPage) -> Bool {
|
|
|
switch page {
|
|
switch page {
|
|
|
case .photo, .tutorials: return false
|
|
case .photo, .tutorials: return false
|
|
@@ -433,13 +402,6 @@ private extension ViewController {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private func logoTemplateForMeetingProvider(_ provider: MeetingProvider) -> Bool {
|
|
|
|
|
- switch provider {
|
|
|
|
|
- case .teams: return false
|
|
|
|
|
- case .meet, .zoom, .zoho: return true
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
func makeSidebar() -> NSView {
|
|
func makeSidebar() -> NSView {
|
|
|
let sidebar = NSView()
|
|
let sidebar = NSView()
|
|
|
sidebar.translatesAutoresizingMaskIntoConstraints = false
|
|
sidebar.translatesAutoresizingMaskIntoConstraints = false
|
|
@@ -580,19 +542,11 @@ private extension ViewController {
|
|
|
contentStack.spacing = 14
|
|
contentStack.spacing = 14
|
|
|
contentStack.alignment = .leading
|
|
contentStack.alignment = .leading
|
|
|
|
|
|
|
|
|
|
+ let joinActions = meetJoinActionsRow()
|
|
|
contentStack.addArrangedSubview(textLabel("Join Meetings", font: typography.pageTitle, color: palette.textPrimary))
|
|
contentStack.addArrangedSubview(textLabel("Join Meetings", font: typography.pageTitle, color: palette.textPrimary))
|
|
|
- contentStack.addArrangedSubview(meetingTypeTabs())
|
|
|
|
|
- if selectedMeetingProvider == .zoom {
|
|
|
|
|
- contentStack.addArrangedSubview(zoomJoinModeTabs())
|
|
|
|
|
- if selectedZoomJoinMode == .id {
|
|
|
|
|
- contentStack.addArrangedSubview(zoomMeetingIDSection())
|
|
|
|
|
- } else {
|
|
|
|
|
- contentStack.addArrangedSubview(meetingUrlSection())
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- contentStack.addArrangedSubview(joinWithURLHeading())
|
|
|
|
|
- contentStack.addArrangedSubview(meetingUrlSection())
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ contentStack.addArrangedSubview(meetJoinSectionRow())
|
|
|
|
|
+ contentStack.addArrangedSubview(joinActions)
|
|
|
|
|
+ contentStack.setCustomSpacing(26, after: joinActions)
|
|
|
contentStack.addArrangedSubview(scheduleHeader())
|
|
contentStack.addArrangedSubview(scheduleHeader())
|
|
|
contentStack.addArrangedSubview(textLabel("Tuesday, 14 Apr", font: typography.dateHeading, color: palette.textSecondary))
|
|
contentStack.addArrangedSubview(textLabel("Tuesday, 14 Apr", font: typography.dateHeading, color: palette.textSecondary))
|
|
|
contentStack.addArrangedSubview(scheduleCardsRow())
|
|
contentStack.addArrangedSubview(scheduleCardsRow())
|
|
@@ -608,6 +562,123 @@ private extension ViewController {
|
|
|
return panel
|
|
return panel
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ func meetJoinSectionRow() -> NSView {
|
|
|
|
|
+ let row = NSStackView()
|
|
|
|
|
+ row.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+ row.orientation = .horizontal
|
|
|
|
|
+ row.spacing = 12
|
|
|
|
|
+ row.alignment = .top
|
|
|
|
|
+ row.distribution = .fillEqually
|
|
|
|
|
+ row.widthAnchor.constraint(greaterThanOrEqualToConstant: 880).isActive = true
|
|
|
|
|
+ row.heightAnchor.constraint(equalToConstant: 140).isActive = true
|
|
|
|
|
+
|
|
|
|
|
+ let instant = HoverSurfaceView()
|
|
|
|
|
+ instant.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+ instant.wantsLayer = true
|
|
|
|
|
+ instant.layer?.cornerRadius = 14
|
|
|
|
|
+ instant.layer?.backgroundColor = palette.sectionCard.cgColor
|
|
|
|
|
+ styleSurface(instant, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
|
|
|
|
|
+
|
|
|
|
|
+ let iconWrap = roundedContainer(cornerRadius: 12, color: NSColor.clear)
|
|
|
|
|
+ iconWrap.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+ iconWrap.widthAnchor.constraint(equalToConstant: 58).isActive = true
|
|
|
|
|
+ iconWrap.heightAnchor.constraint(equalToConstant: 58).isActive = true
|
|
|
|
|
+ iconWrap.layer?.borderWidth = 0
|
|
|
|
|
+ let meetLogoImage = NSImage(named: "MeetLogo") ?? NSImage()
|
|
|
|
|
+ meetLogoImage.isTemplate = false
|
|
|
|
|
+ let meetLogo = NSImageView(image: meetLogoImage)
|
|
|
|
|
+ meetLogo.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+ meetLogo.imageScaling = .scaleProportionallyDown
|
|
|
|
|
+ meetLogo.contentTintColor = nil
|
|
|
|
|
+ iconWrap.addSubview(meetLogo)
|
|
|
|
|
+
|
|
|
|
|
+ let instantTitle = textLabel("New Instant Meet", font: NSFont.systemFont(ofSize: 40 / 2, weight: .semibold), color: palette.textPrimary)
|
|
|
|
|
+ let instantSub = textLabel("Start instant Meet in more section with\nGoogle Meet meet.", font: NSFont.systemFont(ofSize: 16 / 2, weight: .medium), color: palette.textSecondary)
|
|
|
|
|
+ instantSub.maximumNumberOfLines = 2
|
|
|
|
|
+ instant.addSubview(iconWrap)
|
|
|
|
|
+ instant.addSubview(instantTitle)
|
|
|
|
|
+ instant.addSubview(instantSub)
|
|
|
|
|
+
|
|
|
|
|
+ let codeCard = HoverSurfaceView()
|
|
|
|
|
+ codeCard.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+ codeCard.wantsLayer = true
|
|
|
|
|
+ codeCard.layer?.cornerRadius = 14
|
|
|
|
|
+ codeCard.layer?.backgroundColor = palette.sectionCard.cgColor
|
|
|
|
|
+ styleSurface(codeCard, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
|
|
|
|
|
+ let codeTitle = textLabel("Join with Link", font: NSFont.systemFont(ofSize: 40 / 2, weight: .semibold), color: palette.textPrimary)
|
|
|
|
|
+ let codeInputShell = roundedContainer(cornerRadius: 8, color: palette.inputBackground)
|
|
|
|
|
+ codeInputShell.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+ codeInputShell.heightAnchor.constraint(equalToConstant: 52).isActive = true
|
|
|
|
|
+ styleSurface(codeInputShell, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
|
|
|
|
|
+ let codeField = NSTextField(string: "")
|
|
|
|
|
+ codeField.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+ codeField.isEditable = true
|
|
|
|
|
+ codeField.isBordered = false
|
|
|
|
|
+ codeField.drawsBackground = false
|
|
|
|
|
+ codeField.focusRingType = .none
|
|
|
|
|
+ codeField.font = NSFont.systemFont(ofSize: 36 / 2, weight: .regular)
|
|
|
|
|
+ codeField.textColor = palette.textPrimary
|
|
|
|
|
+ codeField.placeholderString = "Enter Link"
|
|
|
|
|
+ codeInputShell.addSubview(codeField)
|
|
|
|
|
+ codeCard.addSubview(codeTitle)
|
|
|
|
|
+ codeCard.addSubview(codeInputShell)
|
|
|
|
|
+
|
|
|
|
|
+ NSLayoutConstraint.activate([
|
|
|
|
|
+ meetLogo.centerXAnchor.constraint(equalTo: iconWrap.centerXAnchor),
|
|
|
|
|
+ meetLogo.centerYAnchor.constraint(equalTo: iconWrap.centerYAnchor),
|
|
|
|
|
+ meetLogo.widthAnchor.constraint(equalToConstant: 46),
|
|
|
|
|
+ meetLogo.heightAnchor.constraint(equalToConstant: 46),
|
|
|
|
|
+
|
|
|
|
|
+ iconWrap.leadingAnchor.constraint(equalTo: instant.leadingAnchor, constant: 18),
|
|
|
|
|
+ iconWrap.topAnchor.constraint(equalTo: instant.topAnchor, constant: 22),
|
|
|
|
|
+ instantTitle.leadingAnchor.constraint(equalTo: iconWrap.trailingAnchor, constant: 14),
|
|
|
|
|
+ instantTitle.topAnchor.constraint(equalTo: instant.topAnchor, constant: 24),
|
|
|
|
|
+ instantSub.leadingAnchor.constraint(equalTo: instantTitle.leadingAnchor),
|
|
|
|
|
+ instantSub.topAnchor.constraint(equalTo: instantTitle.bottomAnchor, constant: 6),
|
|
|
|
|
+ instantSub.trailingAnchor.constraint(lessThanOrEqualTo: instant.trailingAnchor, constant: -16),
|
|
|
|
|
+
|
|
|
|
|
+ codeTitle.leadingAnchor.constraint(equalTo: codeCard.leadingAnchor, constant: 18),
|
|
|
|
|
+ codeTitle.topAnchor.constraint(equalTo: codeCard.topAnchor, constant: 22),
|
|
|
|
|
+ codeInputShell.leadingAnchor.constraint(equalTo: codeCard.leadingAnchor, constant: 18),
|
|
|
|
|
+ codeInputShell.trailingAnchor.constraint(equalTo: codeCard.trailingAnchor, constant: -18),
|
|
|
|
|
+ codeInputShell.topAnchor.constraint(equalTo: codeTitle.bottomAnchor, constant: 12),
|
|
|
|
|
+ codeField.leadingAnchor.constraint(equalTo: codeInputShell.leadingAnchor, constant: 14),
|
|
|
|
|
+ codeField.trailingAnchor.constraint(equalTo: codeInputShell.trailingAnchor, constant: -14),
|
|
|
|
|
+ codeField.centerYAnchor.constraint(equalTo: codeInputShell.centerYAnchor)
|
|
|
|
|
+ ])
|
|
|
|
|
+
|
|
|
|
|
+ let baseColor = palette.sectionCard
|
|
|
|
|
+ let hoverColor = baseColor.blended(withFraction: 0.10, of: NSColor.white) ?? baseColor
|
|
|
|
|
+ instant.onHoverChanged = { hovering in
|
|
|
|
|
+ instant.layer?.backgroundColor = (hovering ? hoverColor : baseColor).cgColor
|
|
|
|
|
+ }
|
|
|
|
|
+ codeCard.onHoverChanged = { hovering in
|
|
|
|
|
+ codeCard.layer?.backgroundColor = (hovering ? hoverColor : baseColor).cgColor
|
|
|
|
|
+ }
|
|
|
|
|
+ instant.onHoverChanged?(false)
|
|
|
|
|
+ codeCard.onHoverChanged?(false)
|
|
|
|
|
+
|
|
|
|
|
+ row.addArrangedSubview(instant)
|
|
|
|
|
+ row.addArrangedSubview(codeCard)
|
|
|
|
|
+ return row
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ func meetJoinActionsRow() -> NSView {
|
|
|
|
|
+ let row = NSStackView()
|
|
|
|
|
+ row.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+ row.orientation = .horizontal
|
|
|
|
|
+ row.spacing = 12
|
|
|
|
|
+ row.alignment = .centerY
|
|
|
|
|
+ row.widthAnchor.constraint(greaterThanOrEqualToConstant: 880).isActive = true
|
|
|
|
|
+
|
|
|
|
|
+ let spacer = NSView()
|
|
|
|
|
+ spacer.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+ row.addArrangedSubview(spacer)
|
|
|
|
|
+ row.addArrangedSubview(actionButton(title: "Cancel", color: palette.cancelButton, textColor: palette.textSecondary, width: 110))
|
|
|
|
|
+ row.addArrangedSubview(actionButton(title: "Join", color: palette.primaryBlue, textColor: .white, width: 116))
|
|
|
|
|
+ return row
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
func makePaywallContent() -> NSView {
|
|
func makePaywallContent() -> NSView {
|
|
|
paywallPlanViews.removeAll()
|
|
paywallPlanViews.removeAll()
|
|
|
premiumPlanByView.removeAll()
|
|
premiumPlanByView.removeAll()
|
|
@@ -1170,51 +1241,6 @@ private extension ViewController {
|
|
|
return container
|
|
return container
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- func meetingTypeTabs() -> NSView {
|
|
|
|
|
- let wrapper = NSView()
|
|
|
|
|
- wrapper.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
-
|
|
|
|
|
- let shell = roundedContainer(cornerRadius: 24, color: palette.tabBarBackground)
|
|
|
|
|
- shell.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
- shell.heightAnchor.constraint(equalToConstant: 48).isActive = true
|
|
|
|
|
-
|
|
|
|
|
- let stack = NSStackView()
|
|
|
|
|
- stack.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
- stack.orientation = .horizontal
|
|
|
|
|
- stack.distribution = .fillEqually
|
|
|
|
|
- stack.spacing = 4
|
|
|
|
|
-
|
|
|
|
|
- let meetTab = topTab("Meet", icon: "", provider: .meet, logoImageName: "MeetLogo")
|
|
|
|
|
- stack.addArrangedSubview(meetTab)
|
|
|
|
|
- tabViews[.meet] = meetTab
|
|
|
|
|
- let zoomTab = topTab("Zoom", icon: "", provider: .zoom, logoImageName: "ZoomLogo", logoPointSize: 34)
|
|
|
|
|
- stack.addArrangedSubview(zoomTab)
|
|
|
|
|
- tabViews[.zoom] = zoomTab
|
|
|
|
|
- let teamsTab = topTab("Teams", icon: "", provider: .teams, logoImageName: "TeamsLogo", logoPointSize: 26, logoHeightMultiplier: 62.0 / 50.0, logoTemplate: false)
|
|
|
|
|
- stack.addArrangedSubview(teamsTab)
|
|
|
|
|
- tabViews[.teams] = teamsTab
|
|
|
|
|
- let zohoTab = topTab("Zoho", icon: "", provider: .zoho, logoImageName: "ZohoLogo", logoPointSize: 28)
|
|
|
|
|
- stack.addArrangedSubview(zohoTab)
|
|
|
|
|
- tabViews[.zoho] = zohoTab
|
|
|
|
|
-
|
|
|
|
|
- shell.addSubview(stack)
|
|
|
|
|
- wrapper.addSubview(shell)
|
|
|
|
|
- NSLayoutConstraint.activate([
|
|
|
|
|
- wrapper.widthAnchor.constraint(greaterThanOrEqualToConstant: 780),
|
|
|
|
|
- shell.leadingAnchor.constraint(equalTo: wrapper.leadingAnchor),
|
|
|
|
|
- shell.trailingAnchor.constraint(equalTo: wrapper.trailingAnchor),
|
|
|
|
|
- shell.topAnchor.constraint(equalTo: wrapper.topAnchor),
|
|
|
|
|
- shell.bottomAnchor.constraint(equalTo: wrapper.bottomAnchor),
|
|
|
|
|
- // More horizontal breathing room inside the bar.
|
|
|
|
|
- stack.leadingAnchor.constraint(equalTo: shell.leadingAnchor, constant: 64),
|
|
|
|
|
- stack.trailingAnchor.constraint(equalTo: shell.trailingAnchor, constant: -44),
|
|
|
|
|
- stack.topAnchor.constraint(equalTo: shell.topAnchor, constant: 6),
|
|
|
|
|
- stack.bottomAnchor.constraint(equalTo: shell.bottomAnchor, constant: -6)
|
|
|
|
|
- ])
|
|
|
|
|
-
|
|
|
|
|
- return wrapper
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
func meetingUrlSection() -> NSView {
|
|
func meetingUrlSection() -> NSView {
|
|
|
let wrapper = NSView()
|
|
let wrapper = NSView()
|
|
|
wrapper.translatesAutoresizingMaskIntoConstraints = false
|
|
wrapper.translatesAutoresizingMaskIntoConstraints = false
|
|
@@ -1430,6 +1456,44 @@ private final class HoverTrackingView: RowHitTestView {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/// Hover tracking without overriding hit-testing; keeps controls like text fields interactive.
|
|
|
|
|
+private final class HoverSurfaceView: NSView {
|
|
|
|
|
+ var onHoverChanged: ((Bool) -> Void)?
|
|
|
|
|
+
|
|
|
|
|
+ private var trackingAreaRef: NSTrackingArea?
|
|
|
|
|
+ private var isHovering = false {
|
|
|
|
|
+ didSet {
|
|
|
|
|
+ guard isHovering != oldValue else { return }
|
|
|
|
|
+ onHoverChanged?(isHovering)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ override func updateTrackingAreas() {
|
|
|
|
|
+ super.updateTrackingAreas()
|
|
|
|
|
+ if let trackingAreaRef {
|
|
|
|
|
+ removeTrackingArea(trackingAreaRef)
|
|
|
|
|
+ }
|
|
|
|
|
+ let options: NSTrackingArea.Options = [
|
|
|
|
|
+ .activeInKeyWindow,
|
|
|
|
|
+ .inVisibleRect,
|
|
|
|
|
+ .mouseEnteredAndExited
|
|
|
|
|
+ ]
|
|
|
|
|
+ let area = NSTrackingArea(rect: bounds, options: options, owner: self, userInfo: nil)
|
|
|
|
|
+ addTrackingArea(area)
|
|
|
|
|
+ trackingAreaRef = area
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ override func mouseEntered(with event: NSEvent) {
|
|
|
|
|
+ super.mouseEntered(with: event)
|
|
|
|
|
+ isHovering = true
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ override func mouseExited(with event: NSEvent) {
|
|
|
|
|
+ super.mouseExited(with: event)
|
|
|
|
|
+ isHovering = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
private final class SettingsMenuViewController: NSViewController {
|
|
private final class SettingsMenuViewController: NSViewController {
|
|
|
private let palette: Palette
|
|
private let palette: Palette
|
|
|
private let typography: Typography
|
|
private let typography: Typography
|
|
@@ -1748,79 +1812,6 @@ private extension ViewController {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- func topTab(_ title: String, icon: String, provider: MeetingProvider, logoImageName: String? = nil, logoPointSize: CGFloat = 26, logoHeightMultiplier: CGFloat = 1, logoTemplate: Bool = true) -> NSView {
|
|
|
|
|
- let tab = HoverTrackingView()
|
|
|
|
|
- tab.wantsLayer = true
|
|
|
|
|
- tab.layer?.cornerRadius = 19
|
|
|
|
|
- tab.layer?.backgroundColor = NSColor.clear.cgColor
|
|
|
|
|
- tab.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
- meetingProviderByView[ObjectIdentifier(tab)] = provider
|
|
|
|
|
-
|
|
|
|
|
- let leadingView: NSView
|
|
|
|
|
- if let name = logoImageName, let logo = NSImage(named: name) {
|
|
|
|
|
- let imageView = NSImageView(image: logo)
|
|
|
|
|
- imageView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
- imageView.imageScaling = .scaleProportionallyDown
|
|
|
|
|
- imageView.imageAlignment = .alignCenter
|
|
|
|
|
- imageView.isEditable = false
|
|
|
|
|
- if logoTemplate {
|
|
|
|
|
- imageView.contentTintColor = palette.textPrimary
|
|
|
|
|
- }
|
|
|
|
|
- leadingView = imageView
|
|
|
|
|
- } else {
|
|
|
|
|
- leadingView = textLabel(icon, font: typography.tabIcon, color: palette.textPrimary)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let titleLabel = textLabel(title, font: typography.tabTitle, color: palette.textPrimary)
|
|
|
|
|
-
|
|
|
|
|
- tab.addSubview(leadingView)
|
|
|
|
|
- tab.addSubview(titleLabel)
|
|
|
|
|
-
|
|
|
|
|
- var constraints: [NSLayoutConstraint] = [
|
|
|
|
|
- leadingView.leadingAnchor.constraint(equalTo: tab.leadingAnchor, constant: 18),
|
|
|
|
|
- leadingView.centerYAnchor.constraint(equalTo: tab.centerYAnchor),
|
|
|
|
|
- titleLabel.leadingAnchor.constraint(equalTo: leadingView.trailingAnchor, constant: 8),
|
|
|
|
|
- titleLabel.centerYAnchor.constraint(equalTo: tab.centerYAnchor),
|
|
|
|
|
- titleLabel.trailingAnchor.constraint(lessThanOrEqualTo: tab.trailingAnchor, constant: -18)
|
|
|
|
|
- ]
|
|
|
|
|
- if logoImageName != nil {
|
|
|
|
|
- constraints.append(contentsOf: [
|
|
|
|
|
- leadingView.widthAnchor.constraint(equalToConstant: logoPointSize),
|
|
|
|
|
- leadingView.heightAnchor.constraint(equalToConstant: logoPointSize * logoHeightMultiplier)
|
|
|
|
|
- ])
|
|
|
|
|
- }
|
|
|
|
|
- NSLayoutConstraint.activate(constraints)
|
|
|
|
|
-
|
|
|
|
|
- applyTabStyle(tab, provider: provider, logoTemplate: logoTemplate)
|
|
|
|
|
- tab.onHoverChanged = { [weak self, weak tab] hovering in
|
|
|
|
|
- guard let self, let tab else { return }
|
|
|
|
|
- self.applyTabStyle(tab, provider: provider, logoTemplate: logoTemplate, hovering: hovering)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let click = NSClickGestureRecognizer(target: self, action: #selector(meetingTabClicked(_:)))
|
|
|
|
|
- tab.addGestureRecognizer(click)
|
|
|
|
|
-
|
|
|
|
|
- return tab
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- func applyTabStyle(_ tab: NSView, provider: MeetingProvider, logoTemplate: Bool, hovering: Bool = false) {
|
|
|
|
|
- let selected = (provider == selectedMeetingProvider)
|
|
|
|
|
- let hoverColor = NSColor(calibratedWhite: 1, alpha: 0.07)
|
|
|
|
|
- tab.layer?.backgroundColor = (selected ? palette.primaryBlue : (hovering ? hoverColor : NSColor.clear)).cgColor
|
|
|
|
|
- guard tab.subviews.count >= 2 else { return }
|
|
|
|
|
- let leading = tab.subviews[0]
|
|
|
|
|
- let title = tab.subviews[1] as? NSTextField
|
|
|
|
|
- let textColor = palette.textPrimary
|
|
|
|
|
- title?.textColor = textColor
|
|
|
|
|
- if let imageView = leading as? NSImageView {
|
|
|
|
|
- if logoTemplate {
|
|
|
|
|
- imageView.contentTintColor = textColor
|
|
|
|
|
- }
|
|
|
|
|
- } else if let iconField = leading as? NSTextField {
|
|
|
|
|
- iconField.textColor = textColor
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
func actionButton(title: String, color: NSColor, textColor: NSColor, width: CGFloat) -> NSView {
|
|
func actionButton(title: String, color: NSColor, textColor: NSColor, width: CGFloat) -> NSView {
|
|
|
let button = HoverTrackingView()
|
|
let button = HoverTrackingView()
|
|
|
button.wantsLayer = true
|
|
button.wantsLayer = true
|