2017-08-12 10:53:13 -07:00
|
|
|
//
|
|
|
|
// PreferencesViewController.swift
|
|
|
|
// Metaballs
|
|
|
|
//
|
|
|
|
// Created by Eryn Wells on 8/12/17.
|
|
|
|
// Copyright © 2017 Eryn Wells. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
import Cocoa
|
|
|
|
|
2017-08-13 09:43:09 -07:00
|
|
|
internal let PreferencesDidChange_Color = Notification.Name("PreferencesDidChange_Color")
|
|
|
|
|
2017-08-16 21:35:48 -07:00
|
|
|
private struct StyleItem {
|
|
|
|
let name: String
|
|
|
|
let tag: Int
|
|
|
|
let colorNames: [String]
|
2018-10-09 16:51:35 -07:00
|
|
|
|
|
|
|
var allowsRotation: Bool {
|
|
|
|
return colorNames.count > 1
|
|
|
|
}
|
2017-08-16 21:35:48 -07:00
|
|
|
}
|
|
|
|
|
2017-08-18 09:01:42 -07:00
|
|
|
public class PreferencesViewController: NSViewController {
|
2017-08-16 21:35:48 -07:00
|
|
|
private static var styleItems: [StyleItem] {
|
2017-08-16 21:15:03 -07:00
|
|
|
return [
|
2017-08-16 21:35:48 -07:00
|
|
|
StyleItem(name: NSLocalizedString("Single Color", comment: "single color menu item"),
|
|
|
|
tag: Int(ColorStyle.singleColor.rawValue),
|
2018-10-09 17:44:07 -07:00
|
|
|
colorNames: [NSLocalizedString("A", comment: "first color")]),
|
|
|
|
StyleItem(name: NSLocalizedString("Two Color Gradient", comment: "two color gradient menu item"),
|
|
|
|
tag: Int(ColorStyle.gradient2.rawValue),
|
|
|
|
colorNames: [NSLocalizedString("A", comment: "first color"),
|
|
|
|
NSLocalizedString("B", comment: "second color")]),
|
|
|
|
StyleItem(name: NSLocalizedString("Four Color Gradient", comment: "four color gradient menu item"),
|
|
|
|
tag: Int(ColorStyle.gradient4.rawValue),
|
|
|
|
colorNames: [NSLocalizedString("A", comment: "first color"),
|
|
|
|
NSLocalizedString("B", comment: "second color"),
|
|
|
|
NSLocalizedString("C", comment: "third color"),
|
|
|
|
NSLocalizedString("D", comment: "fourth color")]),
|
2017-08-16 21:15:03 -07:00
|
|
|
]
|
|
|
|
}
|
|
|
|
|
2017-08-12 10:53:13 -07:00
|
|
|
public var defaults = UserDefaults.standard
|
2017-08-24 17:57:50 -07:00
|
|
|
public var showsCloseButton: Bool = true {
|
|
|
|
didSet {
|
|
|
|
showCloseButtonIfNeeded()
|
|
|
|
}
|
|
|
|
}
|
2017-08-12 10:53:13 -07:00
|
|
|
|
|
|
|
private var colorStackView = NSStackView()
|
|
|
|
private var colorViews = [ColorView]()
|
|
|
|
|
2018-10-09 16:51:35 -07:00
|
|
|
private lazy var colorRotationSlider: RotationSliderView = {
|
|
|
|
let rotationSlider = RotationSliderView(label: NSLocalizedString("Color θ", comment: "name of color rotation slider"))
|
|
|
|
rotationSlider.slider.target = self
|
|
|
|
rotationSlider.slider.action = #selector(PreferencesViewController.sliderDidUpdate(sender:))
|
|
|
|
rotationSlider.slider.floatValue = self.defaults.colorRotation
|
|
|
|
return rotationSlider
|
|
|
|
}()
|
|
|
|
|
2017-08-25 11:19:09 -07:00
|
|
|
private lazy var targetSlider: SliderView = {
|
|
|
|
let targetSlider = SliderView(label: NSLocalizedString("Target", comment: "name of the target slider"))
|
|
|
|
if #available(OSX 10.12.2, *) {
|
|
|
|
targetSlider.slider.trackFillColor = nil
|
|
|
|
}
|
|
|
|
targetSlider.slider.minValue = 0
|
|
|
|
targetSlider.slider.maxValue = 1
|
|
|
|
targetSlider.slider.target = self
|
|
|
|
targetSlider.slider.action = #selector(PreferencesViewController.sliderDidUpdate(sender:))
|
|
|
|
targetSlider.slider.floatValue = self.defaults.target
|
|
|
|
return targetSlider
|
|
|
|
}()
|
|
|
|
|
|
|
|
private lazy var featherSlider: SliderView = {
|
|
|
|
let featherSlider = SliderView(label: NSLocalizedString("Feather", comment: "name of the feather slider"))
|
|
|
|
if #available(OSX 10.12.2, *) {
|
|
|
|
featherSlider.slider.trackFillColor = nil
|
|
|
|
}
|
|
|
|
featherSlider.slider.minValue = 0
|
|
|
|
featherSlider.slider.maxValue = 1
|
|
|
|
featherSlider.slider.target = self
|
|
|
|
featherSlider.slider.action = #selector(PreferencesViewController.sliderDidUpdate(sender:))
|
|
|
|
featherSlider.slider.floatValue = self.defaults.feather
|
|
|
|
return featherSlider
|
|
|
|
}()
|
|
|
|
|
2017-08-12 10:53:13 -07:00
|
|
|
private lazy var styleMenu: NSPopUpButton = {
|
|
|
|
let button = NSPopUpButton()
|
|
|
|
button.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
|
|
|
let menu = NSMenu()
|
2017-08-16 21:15:03 -07:00
|
|
|
for item in PreferencesViewController.styleItems {
|
|
|
|
// TODO: Set action here.
|
2017-08-16 21:35:48 -07:00
|
|
|
let menuItem = NSMenuItem(title: item.name, action: #selector(PreferencesViewController.styleDidUpdate(sender:)), keyEquivalent: "")
|
2017-08-16 21:15:03 -07:00
|
|
|
menuItem.target = self
|
|
|
|
menuItem.tag = item.tag
|
|
|
|
menu.addItem(menuItem)
|
|
|
|
}
|
2017-08-12 10:53:13 -07:00
|
|
|
button.menu = menu
|
|
|
|
|
|
|
|
return button
|
|
|
|
}()
|
|
|
|
|
2017-08-24 17:57:50 -07:00
|
|
|
private lazy var closeView: NSView = {
|
|
|
|
let container = NSView()
|
|
|
|
container.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
|
|
|
let buttonTitle = NSLocalizedString("Close", comment: "close button label")
|
|
|
|
let button = NSButton(title: buttonTitle, target: self, action: #selector(PreferencesViewController.closeWindow))
|
|
|
|
button.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
|
|
|
container.addSubview(button)
|
|
|
|
NSLayoutConstraint.activate([
|
|
|
|
button.topAnchor.constraint(equalTo: container.topAnchor),
|
|
|
|
button.bottomAnchor.constraint(equalTo: container.bottomAnchor),
|
|
|
|
button.trailingAnchor.constraint(equalTo: container.trailingAnchor),
|
|
|
|
])
|
|
|
|
|
|
|
|
return container
|
|
|
|
}()
|
|
|
|
|
2017-08-18 09:01:42 -07:00
|
|
|
override public func loadView() {
|
2017-08-12 10:53:13 -07:00
|
|
|
let view = NSView()
|
|
|
|
view.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
|
|
|
colorStackView.setAccessibilityIdentifier("colorStackView")
|
|
|
|
colorStackView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
colorStackView.orientation = .vertical
|
|
|
|
colorStackView.alignment = .left
|
|
|
|
colorStackView.distribution = .fillProportionally
|
|
|
|
colorStackView.spacing = 8
|
|
|
|
view.addSubview(colorStackView)
|
|
|
|
|
|
|
|
let centerX = colorStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
|
2018-10-05 13:38:32 -07:00
|
|
|
centerX.priority = NSLayoutConstraint.Priority(rawValue: 999)
|
2017-08-12 10:53:13 -07:00
|
|
|
let centerY = colorStackView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
|
2018-10-05 13:38:32 -07:00
|
|
|
centerY.priority = NSLayoutConstraint.Priority(rawValue: 999)
|
2017-08-12 10:53:13 -07:00
|
|
|
NSLayoutConstraint.activate([
|
|
|
|
centerX, centerY,
|
|
|
|
colorStackView.topAnchor.constraint(greaterThanOrEqualTo: view.topAnchor, constant: 8),
|
|
|
|
colorStackView.leftAnchor.constraint(greaterThanOrEqualTo: view.leftAnchor, constant: 8),
|
|
|
|
colorStackView.bottomAnchor.constraint(lessThanOrEqualTo: view.bottomAnchor, constant: -8),
|
|
|
|
colorStackView.rightAnchor.constraint(lessThanOrEqualTo: view.rightAnchor, constant: -8),
|
|
|
|
])
|
|
|
|
|
|
|
|
colorStackView.addArrangedSubview(styleMenu)
|
|
|
|
for i in 0..<4 {
|
2017-08-25 11:19:09 -07:00
|
|
|
let colorView = ColorView(label: "Color \(i+1)")
|
2017-08-12 10:53:13 -07:00
|
|
|
colorView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
colorStackView.addArrangedSubview(colorView)
|
|
|
|
colorViews.append(colorView)
|
|
|
|
}
|
2018-10-09 16:51:35 -07:00
|
|
|
colorStackView.addArrangedSubview(colorRotationSlider)
|
2017-08-12 10:53:13 -07:00
|
|
|
|
2017-08-25 11:19:09 -07:00
|
|
|
colorStackView.addArrangedSubview(targetSlider)
|
|
|
|
colorStackView.addArrangedSubview(featherSlider)
|
|
|
|
|
2017-08-24 17:57:50 -07:00
|
|
|
showCloseButtonIfNeeded()
|
|
|
|
|
2017-08-12 10:53:13 -07:00
|
|
|
self.view = view
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-08-18 09:01:42 -07:00
|
|
|
override public func viewWillAppear() {
|
2017-08-12 10:53:13 -07:00
|
|
|
super.viewWillAppear()
|
2017-08-16 22:01:00 -07:00
|
|
|
if let style = defaults.colorStyle {
|
|
|
|
styleMenu.selectItem(withTag: Int(style.rawValue))
|
|
|
|
updateColorViewVisibility()
|
|
|
|
}
|
2017-08-12 10:53:13 -07:00
|
|
|
prepareColorViews()
|
2017-08-13 09:43:09 -07:00
|
|
|
prepareColorPanel()
|
|
|
|
}
|
|
|
|
|
2017-08-18 09:01:42 -07:00
|
|
|
override public func viewWillDisappear() {
|
2017-08-13 09:43:09 -07:00
|
|
|
super.viewWillDisappear()
|
2018-10-05 13:38:32 -07:00
|
|
|
NSColorPanel.shared.close()
|
2017-08-12 10:53:13 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
private func prepareColorViews() {
|
|
|
|
for (idx, cv) in colorViews.enumerated() {
|
2017-08-16 21:12:00 -07:00
|
|
|
if let fColor = defaults.float4(forKey: "color\(idx)") {
|
|
|
|
let color = NSColor(float4: fColor)
|
2017-08-12 10:53:13 -07:00
|
|
|
cv.colorWell.color = color
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-08-13 09:43:09 -07:00
|
|
|
|
|
|
|
private func prepareColorPanel() {
|
2018-10-05 13:38:32 -07:00
|
|
|
let colorPanel = NSColorPanel.shared
|
2017-08-13 09:43:09 -07:00
|
|
|
colorPanel.isContinuous = true
|
|
|
|
colorPanel.setTarget(self)
|
|
|
|
colorPanel.setAction(#selector(PreferencesViewController.colorPanelDidUpdateValue))
|
|
|
|
}
|
|
|
|
|
2017-08-24 17:57:50 -07:00
|
|
|
private func showCloseButtonIfNeeded() {
|
|
|
|
if showsCloseButton {
|
|
|
|
colorStackView.addArrangedSubview(closeView)
|
|
|
|
} else {
|
|
|
|
colorStackView.removeArrangedSubview(closeView)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-16 21:35:48 -07:00
|
|
|
// MARK: - Actions
|
|
|
|
|
2018-10-05 13:38:32 -07:00
|
|
|
@objc func colorPanelDidUpdateValue(_ colorPanel: NSColorPanel) {
|
2017-08-16 21:56:20 -07:00
|
|
|
postColorNotification()
|
2017-08-13 09:43:09 -07:00
|
|
|
}
|
2017-08-16 21:35:48 -07:00
|
|
|
|
2018-10-05 13:38:32 -07:00
|
|
|
@objc func styleDidUpdate(sender: NSMenuItem) {
|
2017-08-16 21:56:20 -07:00
|
|
|
updateColorViewVisibility()
|
|
|
|
postColorNotification()
|
|
|
|
}
|
|
|
|
|
2018-10-05 13:38:32 -07:00
|
|
|
@objc func sliderDidUpdate(sender: NSSlider) {
|
2017-08-25 11:19:09 -07:00
|
|
|
postColorNotification()
|
|
|
|
}
|
|
|
|
|
2018-10-05 13:38:32 -07:00
|
|
|
@objc func closeWindow() {
|
2017-08-24 17:57:50 -07:00
|
|
|
self.view.window?.close()
|
|
|
|
}
|
|
|
|
|
2017-08-16 21:56:20 -07:00
|
|
|
// MARK: - Private
|
|
|
|
|
|
|
|
func updateColorViewVisibility() {
|
|
|
|
let idx = styleMenu.indexOfSelectedItem
|
2017-08-16 21:35:48 -07:00
|
|
|
guard idx != -1 && idx < PreferencesViewController.styleItems.count else { return }
|
|
|
|
let styleItem = PreferencesViewController.styleItems[idx]
|
|
|
|
for (idx, colorView) in colorViews.enumerated() {
|
|
|
|
if idx < styleItem.colorNames.count {
|
|
|
|
colorView.isHidden = false
|
|
|
|
colorView.label.stringValue = styleItem.colorNames[idx]
|
|
|
|
} else {
|
|
|
|
colorView.isHidden = true
|
|
|
|
}
|
|
|
|
}
|
2018-10-09 16:51:35 -07:00
|
|
|
|
|
|
|
// Hide the color rotation slider if there's only one color
|
|
|
|
colorRotationSlider.isHidden = styleItem.colorNames.count == 1
|
2017-08-16 21:35:48 -07:00
|
|
|
}
|
2017-08-16 21:56:20 -07:00
|
|
|
|
|
|
|
func postColorNotification() {
|
|
|
|
var info = [String:Any]()
|
|
|
|
if let item = styleMenu.selectedItem {
|
2018-10-07 19:32:29 -07:00
|
|
|
info["colorStyle"] = ColorStyle(rawValue: UInt32(item.tag))
|
2017-08-16 21:56:20 -07:00
|
|
|
}
|
|
|
|
for (idx, cv) in colorViews.enumerated() {
|
|
|
|
info["color\(idx)"] = cv.colorWell.color
|
|
|
|
}
|
2017-08-25 11:19:09 -07:00
|
|
|
info["target"] = targetSlider.slider.floatValue
|
|
|
|
info["feather"] = featherSlider.slider.floatValue
|
2018-10-09 16:51:35 -07:00
|
|
|
info["colorRotation"] = colorRotationSlider.slider.floatValue
|
2017-08-16 21:56:20 -07:00
|
|
|
NotificationCenter.default.post(name: PreferencesDidChange_Color, object: nil, userInfo: info)
|
|
|
|
}
|
2017-08-12 10:53:13 -07:00
|
|
|
}
|
|
|
|
|
2017-08-25 11:19:09 -07:00
|
|
|
class ParameterView: NSView {
|
2017-08-12 10:53:13 -07:00
|
|
|
private let stackView = NSStackView()
|
2017-08-25 11:19:09 -07:00
|
|
|
internal let control: NSControl
|
|
|
|
internal let label: NSTextField
|
2017-08-12 10:53:13 -07:00
|
|
|
|
2017-08-25 11:19:09 -07:00
|
|
|
init(frame f: NSRect, control c: NSControl, label: String = "Hello") {
|
|
|
|
control = c
|
|
|
|
self.label = NSTextField(labelWithString: label)
|
|
|
|
super.init(frame: f)
|
2017-08-12 10:53:13 -07:00
|
|
|
commonInit()
|
|
|
|
}
|
|
|
|
|
2017-08-25 11:19:09 -07:00
|
|
|
convenience init(control c: NSControl) {
|
|
|
|
self.init(frame: NSRect(), control: c)
|
|
|
|
}
|
|
|
|
|
2017-08-12 10:53:13 -07:00
|
|
|
required init?(coder: NSCoder) {
|
2017-08-25 11:19:09 -07:00
|
|
|
fatalError("init(coder:) has not been implemented")
|
2017-08-12 10:53:13 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
private func commonInit() {
|
|
|
|
stackView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
stackView.orientation = .horizontal
|
2018-10-05 20:27:47 -07:00
|
|
|
stackView.spacing = 12
|
2017-08-12 10:53:13 -07:00
|
|
|
stackView.alignment = .centerY
|
2017-08-25 11:24:10 -07:00
|
|
|
stackView.distribution = .fill
|
|
|
|
|
|
|
|
label.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
stackView.addArrangedSubview(label)
|
2017-08-12 10:53:13 -07:00
|
|
|
|
2017-08-25 11:19:09 -07:00
|
|
|
control.translatesAutoresizingMaskIntoConstraints = false
|
2018-10-05 13:38:32 -07:00
|
|
|
control.setContentHuggingPriority(NSLayoutConstraint.Priority(rawValue: 251), for: .horizontal)
|
2017-08-25 11:19:09 -07:00
|
|
|
stackView.addArrangedSubview(control)
|
2017-08-12 10:53:13 -07:00
|
|
|
|
|
|
|
addSubview(stackView)
|
|
|
|
NSLayoutConstraint.activate([
|
|
|
|
stackView.topAnchor.constraint(equalTo: topAnchor),
|
|
|
|
stackView.leftAnchor.constraint(equalTo: leftAnchor),
|
|
|
|
stackView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
|
|
stackView.rightAnchor.constraint(equalTo: rightAnchor),
|
|
|
|
])
|
|
|
|
}
|
|
|
|
}
|
2017-08-25 11:19:09 -07:00
|
|
|
|
|
|
|
class ColorView: ParameterView {
|
|
|
|
var colorWell: NSColorWell {
|
|
|
|
return control as! NSColorWell
|
|
|
|
}
|
|
|
|
|
|
|
|
init(label: String) {
|
|
|
|
super.init(frame: NSRect(), control: NSColorWell(), label: label)
|
|
|
|
}
|
|
|
|
|
|
|
|
required init?(coder: NSCoder) {
|
|
|
|
fatalError("init(coder:) has not been implemented")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class SliderView: ParameterView {
|
|
|
|
var slider: NSSlider {
|
|
|
|
return control as! NSSlider
|
|
|
|
}
|
|
|
|
|
|
|
|
init(label: String) {
|
|
|
|
super.init(frame: NSRect(), control: NSSlider(), label: label)
|
|
|
|
}
|
|
|
|
|
|
|
|
required init?(coder: NSCoder) {
|
|
|
|
fatalError("init(coder:) has not been implemented")
|
|
|
|
}
|
|
|
|
}
|
2018-10-09 16:51:35 -07:00
|
|
|
|
|
|
|
class RotationSliderView: SliderView {
|
|
|
|
override init(label: String) {
|
|
|
|
super.init(label: label)
|
|
|
|
|
|
|
|
let slider = control as! NSSlider
|
|
|
|
slider.minValue = -Double.pi
|
|
|
|
slider.maxValue = Double.pi
|
|
|
|
slider.numberOfTickMarks = 5
|
|
|
|
}
|
|
|
|
|
|
|
|
required init?(coder: NSCoder) {
|
|
|
|
fatalError("init(coder:) has not been implemented")
|
|
|
|
}
|
|
|
|
}
|