Files

351 lines
14 KiB
Swift

//
// PreferenceOptionsView.swift
// Privado
//
// Created by Juraldinio on 12/24/19.
// Copyright © 2019 Omicronmedia. All rights reserved.
//
import Foundation
import AppKit
protocol PreferenceOptionsViewDelegate: AnyObject {
func selected(index: Int, option: PreferenceOptions, enabled: Bool?, optionIndex: Int?)
}
final class PreferenceOptionsView: NSView, NSComboBoxDelegate, MenuDelegate {
enum Constants {
enum Geometry {
static let checkLeading: CGFloat = 10.0
static let textFieldTop: CGFloat = 20.0
static let textFieldLeading: CGFloat = 0.0
static let textFieldWidth: CGFloat = 55.0
static let dropDownTop: CGFloat = 15.0
static let dropDownLeading: CGFloat = 10.0
static let dropDownHeight: CGFloat = 30.0
static let dropDownWidth: CGFloat = 100.0
}
enum Color {
static let dropBackground = NSColor(red: 32, green: 40, blue: 100)
static let dropTitle = NSColor(red: 122, green: 134, blue: 190)
static let radioBackgroundColor = NSColor(red: 45, green: 51, blue: 82)
static let radioInnerCircleColor = NSColor(red: 42, green: 216, blue: 153)
static let radioOuterCircleColor = NSColor(red: 42, green: 216, blue: 153)
static let radioInnerSelectedColor: NSColor = .clear
}
enum Font {
enum Size {
static let checkBox: CGFloat = 12.0
static let title: CGFloat = 12
}
}
static let DeltaRadioTag = 1
}
private weak var delegate: PreferenceOptionsViewDelegate?
private var index = 0
private(set) var option: PreferenceOptions = .empty
private weak var comboboxMenu: NSComboBox?
var isEnabled = false {
didSet {
self.subviews
.compactMap { $0 as? NSButton }
.filter { $0.bezelStyle != .rounded }
.forEach { $0.isEnabled = self.isEnabled }
self.subviews
.compactMap { $0 as? NSComboBox }
.forEach { $0.isEnabled = self.isEnabled }
let textFields = self.subviews
.compactMap { $0 as? NSTextField }
self.textfieldAnimation(isEnabled: self.isEnabled, textFields: textFields)
}
}
// MARK: - Init
convenience init(index: Int, option: PreferenceOptions, delegate: PreferenceOptionsViewDelegate?) {
self.init(frame: .zero)
self.translatesAutoresizingMaskIntoConstraints = false
self.index = index
self.option = option
self.delegate = delegate
self.createOption(option)
}
// MARK: - Private
private func createOption(_ option: PreferenceOptions) {
switch option {
case .empty:
break
case let .check(identifier, enabled, value):
self.createCheck(with: identifier, enabled: enabled, value: value)
case let .toggle(identifier, enabled, value):
self.createToggle(with: identifier, enabled: enabled, value: value)
case let .menu(identifier, title, index, values):
self.createMenu(with: identifier, title: title, enabled: index, values: values)
case let .radio(values, title, index, radioForm):
self.createRadioButtons(values: values, title: title, typeIndex: index, radioForm: radioForm)
}
}
// MARK: - Creations
private func createCheck(with identifier: PreferenceOptionsID, enabled: PreferenceOptionsEnabled, value: PreferenceOptionsValue) {
let checkBox = NSButton(frame: .zero)
checkBox.isEnabled = false
checkBox.translatesAutoresizingMaskIntoConstraints = false
checkBox.setButtonType(.toggle)
checkBox.bezelStyle = .rounded
checkBox.title = value.title
if let font = checkBox.font {
checkBox.font = NSFont(name: font.fontName, size: Constants.Font.Size.checkBox)
}
checkBox.state = enabled ? .on : .off
checkBox.tag = identifier
checkBox.target = self
checkBox.action = #selector(self.onToggleAction(_:))
self.addSubview(checkBox)
NSLayoutConstraint.activate([
checkBox.topAnchor.constraint(equalTo: self.topAnchor),
checkBox.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: Constants.Geometry.checkLeading)
])
}
private func createToggle(with identifier: PreferenceOptionsID, enabled: PreferenceOptionsEnabled, value: PreferenceOptionsValue) {
let checkBox = NSButton(frame: .zero)
checkBox.isEnabled = false
checkBox.translatesAutoresizingMaskIntoConstraints = false
checkBox.setButtonType(.switch)
checkBox.bezelStyle = .rounded
checkBox.title = value.title
if let font = checkBox.font {
checkBox.font = NSFont(name: font.fontName, size: Constants.Font.Size.checkBox)
}
checkBox.state = enabled ? .on : .off
checkBox.tag = identifier
checkBox.target = self
checkBox.action = #selector(self.onToggleAction(_:))
self.addSubview(checkBox)
NSLayoutConstraint.activate([
checkBox.topAnchor.constraint(equalTo: self.topAnchor),
checkBox.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: Constants.Geometry.checkLeading)
])
}
var myMenu: Menu?
var dropDownField: DropButton?
var values: [PreferenceOptionsValue]?
// create custom menu
private func createMenu(with identifier: PreferenceOptionsID, title: String, enabled: PreferenceOptionsIndex, values: [PreferenceOptionsValue]) {
self.values = values
let textField = NSTextField(labelWithString: title)
textField.lineBreakMode = .byWordWrapping
textField.usesSingleLineMode = true
textField.translatesAutoresizingMaskIntoConstraints = false
textField.textColor = .white
textField.font = NSFont(name: PrivadoConstants.Font.semiBold, size: 12)
self.addSubview(textField)
let dropDownField = DropButton()
dropDownField.title = values[enabled].title
dropDownField.translatesAutoresizingMaskIntoConstraints = false
let menu = Menu()
menu.delegate = self
var menuItems = [WindowMenuItem]()
values.forEach {
menuItems.append(WindowMenuItem($0.title, isSelectable: true, action: { }))
}
menu.addItems(menuItems)
myMenu = menu
// dropDownField.backgroundNormalColor = Constants.Color.dropBackground
dropDownField.backgroundNormalColor = NSColor(red: 32, green: 40, blue: 100)
dropDownField.titleNormalColor = Constants.Color.dropTitle
dropDownField.strokeWidth = 1
dropDownField.strokerColor = NSColor(red: 130, green: 136, blue: 216)
dropDownField.cornerRadius = 6
dropDownField.font = NSFont.systemFont(ofSize: 18)
dropDownField.target = self
dropDownField.action = #selector(menuTapped(_:))
dropDownField.imagePosition = .imageRight
dropDownField.spacing = 7.0
dropDownField.image = NSImage(named: "arrow")
self.dropDownField = dropDownField
self.addSubview(dropDownField)
NSLayoutConstraint.activate([
textField.topAnchor.constraint(equalTo: self.topAnchor, constant: Constants.Geometry.textFieldTop),
textField.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: Constants.Geometry.textFieldLeading),
dropDownField.topAnchor.constraint(equalTo: self.topAnchor, constant: Constants.Geometry.dropDownTop),
dropDownField.leadingAnchor.constraint(equalTo: textField.trailingAnchor, constant: Constants.Geometry.dropDownLeading),
dropDownField.heightAnchor.constraint(equalToConstant: Constants.Geometry.dropDownHeight),
dropDownField.widthAnchor.constraint(equalToConstant: Constants.Geometry.dropDownWidth)
])
}
// Create custom radio buttons
let buttonsContainer = RadioButtonContainer()
var isRadio = false
private func createRadioButtons(values: [RadioButtonOption], title: PreferenceOptionsTitle, typeIndex: PreferenceOptionsIndex, radioForm: PreferenceRadioBoxForm) {
self.isRadio = true
var constraints = [NSLayoutConstraint]()
let titleView: NSView?
if !title.isEmpty {
let titleField = NSTextField()
titleField.stringValue = title
titleField.backgroundColor = .clear
titleField.isBezeled = false
titleField.isEditable = false
titleField.sizeToFit()
titleField.translatesAutoresizingMaskIntoConstraints = false
titleField.font = NSFont(name: PrivadoConstants.Font.regular, size: Constants.Font.Size.title)
titleField.textColor = .white
self.addSubview(titleField)
titleView = titleField
constraints.append(contentsOf: [
titleField.topAnchor.constraint(equalTo: self.topAnchor, constant: 10),
titleField.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10)
])
NSLayoutConstraint.activate(constraints)
} else {
titleView = nil
}
var view = NSView()
values.enumerated().forEach { index, record in
view = self.radioButton(index: index,
typeIndex: typeIndex,
title: record.title,
record: record,
radioForm: radioForm,
after: index == 0 ? titleView : view,
constaints: &constraints)
}
NSLayoutConstraint.activate(constraints)
}
private func radioButton(index: Int, typeIndex: PreferenceOptionsIndex, title: PreferenceOptionsTitle, record: RadioButtonOption, radioForm: RadioBoxForm, after: NSView?, constaints: inout [NSLayoutConstraint]) -> NSView {
let radioButton = RadioButton(frame: .zero, title: title)
radioButton.fontSize = 14
radioButton.textFont = NSFont(name: PrivadoConstants.Font.medium, size: 14)
radioButton.radioBoxSize = CGSize(width: 20, height: 20)
radioButton.textYoffset = 5
self.buttonsContainer.addButton(radioButton)
radioButton.tag = index + Constants.DeltaRadioTag
radioButton.isOn = radioButton.tag == typeIndex + Constants.DeltaRadioTag
radioButton.backgroundColor = Constants.Color.radioBackgroundColor
radioButton.innerCircleColor = Constants.Color.radioBackgroundColor
radioButton.outerCircleColor = Constants.Color.radioOuterCircleColor
radioButton.innerSelectedColor = Constants.Color.radioInnerSelectedColor
radioButton.target = self
radioButton.action = #selector(self.radioButtonAction(_:))
radioButton.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(radioButton)
var layout: [NSLayoutConstraint] = [
radioButton.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10),
radioButton.widthAnchor.constraint(equalToConstant: 250),
radioButton.heightAnchor.constraint(equalToConstant: 25)
]
if let view = after {
layout.append(radioButton.topAnchor.constraint(equalTo: view.bottomAnchor, constant: 10))
} else if let view = radioButton.superview {
layout.append(radioButton.topAnchor.constraint(equalTo: view.topAnchor))
}
constaints.append(contentsOf: layout)
return radioButton
}
private func textfieldAnimation(isEnabled: Bool, textFields: [NSTextField]) {
guard self.isRadio else { return }
NSAnimationContext.runAnimationGroup { (context) in
context.duration = 0.3
context.timingFunction = .easeInEaseOut
animator().alphaValue = isEnabled ? 1.0 : 0.25
}
}
// MARK: - Handlers
@objc
private func radioButtonAction(_ sender: RadioButton) {
self.delegate?.selected(index: self.index,
option: self.option,
enabled: nil,
optionIndex: sender.tag - Constants.DeltaRadioTag)
}
@objc
private func onToggleAction(_ sender: NSButton) {
self.delegate?.selected(index: self.index, option: self.option, enabled: sender.state == .on, optionIndex: nil)
}
@objc
private func menuTapped(_ sender: NSButton) {
switch sender.state {
case .on:
self.myMenu?.show(from: sender, animated: true)
case .off:
self.myMenu?.dismiss(animated: true)
default:
break
}
}
func menuItemDidTap(_ index: Int?) {
if index != nil {
guard let index = index, let title = values?[index].title else { return }
self.dropDownField?.title = title
self.dropDownField?.state = dropDownField?.state == .on ? .off : .on
self.delegate?.selected(index: self.index, option: self.option, enabled: nil, optionIndex: index)
} else {
self.dropDownField?.changeState()
}
}
func comboBoxSelectionDidChange(_ notification: Notification) {
guard let combobox = self.comboboxMenu
, case .menu = self.option else {
return
}
self.delegate?.selected(index: self.index, option: self.option, enabled: nil, optionIndex: combobox.indexOfSelectedItem)
}
}