// // MenuController.swift // PrivadoVPN // // Created by Murad Shabanov on 21.06.2021. // Copyright © 2021 Privado LLC. All rights reserved. // import Foundation protocol MenuControllerInput: AnyObject { // func changeTitle(for index: Int) func reloadData() } protocol MenuControllerOutput: AnyObject { func menuControllerClose(_ controller: MenuControllerInput) func menuControllerItem(_ controller: MenuControllerInput, at index: Int) -> MenuItem? func menuControllerItem(_ controller: MenuControllerInput, selected tag: Int) func menuControllerItemsCount(_ controller: MenuControllerInput) -> Int func menuControllerQuit(_ controller: MenuControllerInput) } final class MenuController: NSViewController, MenuControllerInput, NSTableViewDelegate, NSTableViewDataSource, AdvancedTextFieldDelegate { // swiftlint:disable nesting private enum Constants { static let columnIdentifier = NSUserInterfaceItemIdentifier("MenuColumn") static let cellIdentifier = NSUserInterfaceItemIdentifier("MenuCell") enum Geometry { enum Close { static let width: CGFloat = 15 static let height: CGFloat = 15 static let top: CGFloat = 15 static let trailing: CGFloat = 15 } enum Main { static let leading: CGFloat = 100 static let bottom: CGFloat = 200 } enum TableView { static let top: CGFloat = 10 static let bottom: CGFloat = 40 } enum Bottom { enum Quit { static let leading: CGFloat = 25 } enum Text { static let centerY: CGFloat = 3.0 static let leading: CGFloat = 15 } } enum Table { static let columnWidth: CGFloat = 200 static let rowHeight: CGFloat = 30 } } enum Image { static let close = "closeMenu" static let quit = "quit" } enum Color { static let topBackground = NSColor(red: 32, green: 40, blue: 100) static let bottomBackground = NSColor(red: 3, green: 15, blue: 75) static let background = NSColor(red: 0, green: 0, blue: 0, alpha: 0.53) static let bottomText = NSColor(red: 122 / 255, green: 134 / 255, blue: 190 / 255, alpha: 1) static let bottomTextHover = NSColor.white } enum Font { enum Size { static let quitText: CGFloat = 12 } } enum Text { static let quitString = "Quit PrivadoVPN" } } // swiftlint:enable nesting private var output: MenuControllerOutput? private let tableView = NSTableView() private let closeButton = MenuController.createButton(image: Constants.Image.close) private let quitButton = MenuController.createButton(image: Constants.Image.quit) private let mainView = NSView(frame: .zero) private let bottomView = BottomView(backgroundColor: Constants.Color.bottomBackground) private let bottomText = AdvancedTextField(frame: .zero) // MARK: - Init init(output: MenuControllerOutput) { self.output = output super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } // MARK: - LifeCycle override func loadView() { self.view = BackgroundView(with: Constants.Color.background) } override func viewDidLoad() { super.viewDidLoad() self.congigureUI() self.bind() self.layoutUI() } override func viewDidAppear() { super.viewDidAppear() if let superview = self.view.superview { NSLayoutConstraint.activate([ self.view.topAnchor.constraint(equalTo: superview.topAnchor), self.view.leadingAnchor.constraint(equalTo: superview.leadingAnchor), self.view.trailingAnchor.constraint(equalTo: superview.trailingAnchor), self.view.bottomAnchor.constraint(equalTo: superview.bottomAnchor) ]) } } private func layoutUI() { NSLayoutConstraint.activate([ self.mainView.topAnchor.constraint(equalTo: self.view.topAnchor), self.mainView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -Constants.Geometry.Main.bottom), self.mainView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: Constants.Geometry.Main.leading), self.mainView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), // self.closeButton.topAnchor.constraint(equalTo: self.mainView.topAnchor, constant: Constants.Geometry.Close.top), self.closeButton.trailingAnchor.constraint(equalTo: self.mainView.trailingAnchor, constant: -Constants.Geometry.Close.trailing), self.closeButton.widthAnchor.constraint(equalToConstant: Constants.Geometry.Close.width), self.closeButton.heightAnchor.constraint(equalToConstant: Constants.Geometry.Close.height), // self.tableView.topAnchor.constraint(equalTo: self.closeButton.bottomAnchor, constant: Constants.Geometry.TableView.top), self.tableView.leadingAnchor.constraint(equalTo: self.mainView.leadingAnchor), self.tableView.trailingAnchor.constraint(equalTo: self.mainView.trailingAnchor), self.tableView.bottomAnchor.constraint(equalTo: self.mainView.bottomAnchor, constant: -Constants.Geometry.TableView.bottom), // self.bottomView.topAnchor.constraint(equalTo: self.tableView.bottomAnchor), self.bottomView.leadingAnchor.constraint(equalTo: self.mainView.leadingAnchor), self.bottomView.trailingAnchor.constraint(equalTo: self.mainView.trailingAnchor), self.bottomView.bottomAnchor.constraint(equalTo: self.mainView.bottomAnchor), // self.quitButton.centerYAnchor.constraint(equalTo: self.bottomView.centerYAnchor), self.quitButton.leadingAnchor.constraint(equalTo: self.bottomView.leadingAnchor, constant: Constants.Geometry.Bottom.Quit.leading), self.quitButton.widthAnchor.constraint(equalTo: self.quitButton.heightAnchor, multiplier: 1.0), // self.bottomText.centerYAnchor.constraint(equalTo: self.bottomView.centerYAnchor, constant: Constants.Geometry.Bottom.Text.centerY), self.bottomText.leadingAnchor.constraint(equalTo: self.quitButton.trailingAnchor, constant: Constants.Geometry.Bottom.Text.leading) ]) } private func congigureUI() { self.mainView.translatesAutoresizingMaskIntoConstraints = false self.mainView.wantsLayer = true self.mainView.layer?.cornerRadius = 5 self.mainView.viewBackgroundColor = Constants.Color.topBackground let shadow = NSShadow() shadow.shadowColor = .black shadow.shadowBlurRadius = 6 shadow.shadowOffset = .zero self.mainView.shadow = shadow self.bottomText.stringValue = Constants.Text.quitString self.bottomText.textColor = Constants.Color.bottomText self.bottomText.font = NSFont(name: PrivadoConstants.Font.medium, size: Constants.Font.Size.quitText) self.bottomText.useHandCursor = true self.bottomText.advancedDelegate = self self.tableView.translatesAutoresizingMaskIntoConstraints = false self.tableView.backgroundColor = .clear self.tableView.columnAutoresizingStyle = .uniformColumnAutoresizingStyle self.tableView.allowsColumnResizing = true self.tableView.selectionHighlightStyle = .none self.tableView.focusRingType = .none if #available(OSX 11.0, *) { self.tableView.style = .fullWidth } self.tableView.cornerView = NSView() } private func bind() { self.view.addSubview(self.mainView) self.quitButton.target = self self.quitButton.action = #selector(self.onQuitAction) self.bottomView.translatesAutoresizingMaskIntoConstraints = false self.bottomView.addSubview(self.quitButton) self.bottomView.addSubview(self.bottomText) self.closeButton.target = self self.closeButton.action = #selector(self.onCloseAction) self.tableView.delegate = self self.tableView.dataSource = self let col = NSTableColumn(identifier: Constants.columnIdentifier) col.isEditable = false self.tableView.addTableColumn(col) self.mainView.addSubview(self.bottomView) self.mainView.addSubview(self.tableView) self.mainView.addSubview(self.closeButton) } func reloadData() { self.tableView.reloadData() } @objc private func onCloseAction() { self.output?.menuControllerClose(self) } @objc private func onQuitAction() { self.output?.menuControllerQuit(self) } // MARK: - NSTableViewDelegate func tableView(_ tableView: NSTableView, sizeToFitWidthOfColumn column: Int) -> CGFloat { Constants.Geometry.Table.columnWidth } func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { Constants.Geometry.Table.rowHeight } func tableViewSelectionDidChange(_ notification: Notification) { guard let output = self.output else { return } let row = self.tableView.selectedRow output.menuControllerClose(self) output.menuControllerItem(self, selected: row) } func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { tableView.makeView(withIdentifier: Constants.cellIdentifier, owner: nil) guard let item = self.output?.menuControllerItem(self, at: row) else { return nil } let cell = MenuTableViewCell() cell.updateText(text: item.title) return cell } // MARK: - NSTableViewDataSource func numberOfRows(in tableView: NSTableView) -> Int { return self.output?.menuControllerItemsCount(self) ?? 0 } // MARK: - AdvancedTextFieldDelegate func advancedTextFieldClick(on target: AdvancedTextField) { self.onQuitAction() } func advancedTextFieldHover(on target: AdvancedTextField, isHover: Bool) { target.textColor = isHover ? Constants.Color.bottomTextHover : Constants.Color.bottomText } }