351 lines
14 KiB
Swift
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)
|
|
}
|
|
|
|
}
|