// // CountryServerCell.swift // PrivadoVPN // // Created by Juraldinio on 3/15/21. // Copyright © 2021 Privado LLC. All rights reserved. // import Foundation final class CountryServerCell: View { // swiftlint:disable nesting enum Constants { static let height: CGFloat = 41 enum Images { static let collapsed = "countryArrowUp" static let expanded = "countryArrowDown" } enum Geometry { static let flagLeading: CGFloat = 14 static let flagSize = CGSize(width: 20, height: 20) static let flagBetweenTitle: CGFloat = 23 static let titleHeight: CGFloat = 20 static let titleBetweenArrow: CGFloat = 23 static let arrowSize = CGSize(width: 12, height: 7) static let arrowTrailing: CGFloat = 26 static let bottomLineHeight: CGFloat = 1.0 } enum Font { enum Name { static let title = PrivadoConstants.Font.semiBold } enum Size { static let title: CGFloat = 14.0 } } enum Color { static let title = NSColor.white static let bottomLine = NSColor(red: 25, green: 33, blue: 71) } } var strokeLayer = CAShapeLayer() var backgroundLayer = CAShapeLayer() var cornerRadius: CGFloat = 6 func setupBackgroundLayer() { let path = NSBezierPath(roundedRect: CGRect(x: self.bounds.minX + 5, y: self.bounds.minY, width: self.bounds.width - 10, height: self.bounds.height - 5), xRadius: cornerRadius, yRadius: cornerRadius) self.backgroundLayer.path = path.CGPath self.backgroundLayer.fillRule = .evenOdd self.backgroundLayer.fillColor = .clear } func setupStrokeLayer() { self.layer?.addSublayer(self.strokeLayer) let path = NSBezierPath() let rect = CGRect(x: self.bounds.minX + 5, y: self.bounds.minY, width: self.bounds.width - 10, height: self.bounds.height - 5) let topRight = NSPoint(x: rect.maxX, y: rect.maxY) let topLeft = NSPoint(x: rect.minX, y: rect.maxY) let bottomLeft = NSPoint(x: rect.minX, y: rect.minY) let bottomRight = NSPoint(x: rect.maxX, y: rect.minY) path.move(to: bottomLeft) path.line(to: NSPoint(x: rect.minX, y: rect.maxY - cornerRadius)) path.appendArc(from: topLeft, to: NSPoint(x: rect.minX + cornerRadius, y: rect.maxY), radius: cornerRadius) path.line(to: NSPoint(x: rect.maxX - cornerRadius, y: rect.maxY)) path.appendArc(from: topRight, to: NSPoint(x: rect.maxX, y: rect.maxY - cornerRadius), radius: cornerRadius) path.line(to: bottomRight) path.line(to: bottomLeft) self.strokeLayer.path = path.CGPath self.strokeLayer.lineWidth = 1 self.strokeLayer.strokeColor = .clear self.strokeLayer.fillRule = .evenOdd self.strokeLayer.fillColor = NSColor(red: 58, green: 66, blue: 183).cgColor } func removeStrokeLayer() { self.strokeLayer.removeFromSuperlayer() } func removeBackgroundLayer() { self.backgroundLayer.fillColor = .clear } var strokeIsSetup = false override func layout() { super.layout() if self.expanded { self.removeBackgroundLayer() self.setupStrokeLayer() } else { self.removeStrokeLayer() self.setupBackgroundLayer() } } // swiftlint:enable nesting private let flagImage = ImageView() private let titleField = NSTextField(frame: .zero) private let arrowImage = ImageView() private let bottomLine = NSView() private weak var output: ServerCellOutput? var backgroundColor: NSColor = .clear var expanded: Bool = false { didSet { self.arrowImage.image = NSImage(named: expanded ? Constants.Images.expanded : Constants.Images.collapsed) } } // MARK: - Initializers init(flag: String, country: String, expanded: Bool = false, isPremium: Bool, premiumBackground: Bool, output: ServerCellOutput) { super.init(frame: .zero) self.output = output self.output?.scrollEmitter.addReaction({ [weak self] isScrolling in if isScrolling { self?.backgroundLayer.fillColor = .clear } return self.isExist }) self.backgroundColor = premiumBackground ? NSColor(red: 69, green: 77, blue: 181) : .clear self.viewBackgroundColor = self.backgroundColor self.isHoverEnabled = true self.layer?.addSublayer(self.backgroundLayer) self.configure(flag: flag, country: country, expanded: expanded) } // MARK: - LifeCycle override func viewDidMoveToSuperview() { super.viewDidMoveToSuperview() NSLayoutConstraint.activate([ self.heightAnchor.constraint(equalToConstant: Constants.height) ]) } override func hitTest(_ point: NSPoint) -> NSView? { nil } override func updateTrackingAreas() { super.updateTrackingAreas() for trackingArea in self.trackingAreas { self.removeTrackingArea(trackingArea) } let options: NSTrackingArea.Options = [.mouseEnteredAndExited, .activeAlways] let trackingArea = NSTrackingArea(rect: self.bounds, options: options, owner: self, userInfo: nil) self.addTrackingArea(trackingArea) } // MARK: - Lifecycle View override func addSubviews() { self.wantsLayer = true self.addSubview(self.flagImage) self.addSubview(self.titleField) self.addSubview(self.arrowImage) self.addSubview(self.bottomLine) // titleField self.titleField.translatesAutoresizingMaskIntoConstraints = false self.titleField.isEditable = false self.titleField.isSelectable = false self.titleField.usesSingleLineMode = true self.titleField.isBezeled = false self.titleField.drawsBackground = false self.titleField.focusRingType = .none self.titleField.textColor = Constants.Color.title self.titleField.font = NSFont(name: Constants.Font.Name.title, size: Constants.Font.Size.title) self.arrowImage.translatesAutoresizingMaskIntoConstraints = false self.bottomLine.translatesAutoresizingMaskIntoConstraints = false } override func addConstraints() { NSLayoutConstraint.activate([ self.flagImage.widthAnchor.constraint(equalToConstant: Constants.Geometry.flagSize.width), self.flagImage.heightAnchor.constraint(equalToConstant: Constants.Geometry.flagSize.height), self.flagImage.centerYAnchor.constraint(equalTo: self.centerYAnchor), self.flagImage.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: Constants.Geometry.flagLeading), // self.titleField.leadingAnchor.constraint(equalTo: self.flagImage.trailingAnchor, constant: Constants.Geometry.flagBetweenTitle), self.titleField.centerYAnchor.constraint(equalTo: self.centerYAnchor), self.titleField.heightAnchor.constraint(equalToConstant: Constants.Geometry.titleHeight), // self.arrowImage.widthAnchor.constraint(equalToConstant: Constants.Geometry.arrowSize.width), self.arrowImage.heightAnchor.constraint(equalToConstant: Constants.Geometry.arrowSize.height), self.arrowImage.leadingAnchor.constraint(equalTo: self.titleField.trailingAnchor, constant: Constants.Geometry.titleBetweenArrow), self.arrowImage.centerYAnchor.constraint(equalTo: self.centerYAnchor), self.arrowImage.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -Constants.Geometry.arrowTrailing), // self.bottomLine.leadingAnchor.constraint(equalTo: self.leadingAnchor), self.bottomLine.trailingAnchor.constraint(equalTo: self.trailingAnchor), self.bottomLine.bottomAnchor.constraint(equalTo: self.bottomAnchor), self.bottomLine.heightAnchor.constraint(equalToConstant: Constants.Geometry.bottomLineHeight) ]) } override func onMouseHover(_ hover: Bool) { guard !self.expanded else { return } self.backgroundLayer.fillColor = hover ? NSColor(red: 58, green: 66, blue: 183).cgColor : self.backgroundColor.cgColor } // MARK: - Private private func configure(flag: String, country: String, expanded: Bool) { self.flagImage.image = NSImage(named: flag) self.titleField.stringValue = country self.expanded = expanded } }