metaballs/MetaballsKit/PreferencesViewController.swift

341 lines
12 KiB
Swift
Raw Normal View History

//
// PreferencesViewController.swift
// Metaballs
//
// Created by Eryn Wells on 8/12/17.
// Copyright © 2017 Eryn Wells. All rights reserved.
//
import Cocoa
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]
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] {
return [
2017-08-16 21:35:48 -07:00
StyleItem(name: NSLocalizedString("Single Color", comment: "single color menu item"),
tag: Int(ColorStyle.singleColor.rawValue),
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")]),
]
}
public var defaults = UserDefaults.standard
public var showsCloseButton: Bool = true {
didSet {
showCloseButtonIfNeeded()
}
}
private var colorStackView = NSStackView()
private var colorViews = [ColorView]()
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
}()
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
}()
private lazy var styleMenu: NSPopUpButton = {
let button = NSPopUpButton()
button.translatesAutoresizingMaskIntoConstraints = false
let menu = NSMenu()
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: "")
menuItem.target = self
menuItem.tag = item.tag
menu.addItem(menuItem)
}
button.menu = menu
return button
}()
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() {
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)
let centerY = colorStackView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
2018-10-05 13:38:32 -07:00
centerY.priority = NSLayoutConstraint.Priority(rawValue: 999)
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 {
let colorView = ColorView(label: "Color \(i+1)")
colorView.translatesAutoresizingMaskIntoConstraints = false
colorStackView.addArrangedSubview(colorView)
colorViews.append(colorView)
}
colorStackView.addArrangedSubview(colorRotationSlider)
colorStackView.addArrangedSubview(targetSlider)
colorStackView.addArrangedSubview(featherSlider)
showCloseButtonIfNeeded()
self.view = view
}
2017-08-18 09:01:42 -07:00
override public func viewWillAppear() {
super.viewWillAppear()
if let style = defaults.colorStyle {
styleMenu.selectItem(withTag: Int(style.rawValue))
updateColorViewVisibility()
}
prepareColorViews()
prepareColorPanel()
}
2017-08-18 09:01:42 -07:00
override public func viewWillDisappear() {
super.viewWillDisappear()
2018-10-05 13:38:32 -07:00
NSColorPanel.shared.close()
}
private func prepareColorViews() {
for (idx, cv) in colorViews.enumerated() {
if let fColor = defaults.float4(forKey: "color\(idx)") {
let color = NSColor(float4: fColor)
cv.colorWell.color = color
}
}
}
private func prepareColorPanel() {
2018-10-05 13:38:32 -07:00
let colorPanel = NSColorPanel.shared
colorPanel.isContinuous = true
colorPanel.setTarget(self)
colorPanel.setAction(#selector(PreferencesViewController.colorPanelDidUpdateValue))
}
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-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) {
postColorNotification()
}
2018-10-05 13:38:32 -07:00
@objc func closeWindow() {
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
}
}
// 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
}
info["target"] = targetSlider.slider.floatValue
info["feather"] = featherSlider.slider.floatValue
info["colorRotation"] = colorRotationSlider.slider.floatValue
2017-08-16 21:56:20 -07:00
NotificationCenter.default.post(name: PreferencesDidChange_Color, object: nil, userInfo: info)
}
}
class ParameterView: NSView {
private let stackView = NSStackView()
internal let control: NSControl
internal let label: NSTextField
init(frame f: NSRect, control c: NSControl, label: String = "Hello") {
control = c
self.label = NSTextField(labelWithString: label)
super.init(frame: f)
commonInit()
}
convenience init(control c: NSControl) {
self.init(frame: NSRect(), control: c)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func commonInit() {
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.orientation = .horizontal
2018-10-05 20:27:47 -07:00
stackView.spacing = 12
stackView.alignment = .centerY
2017-08-25 11:24:10 -07:00
stackView.distribution = .fill
label.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubview(label)
control.translatesAutoresizingMaskIntoConstraints = false
2018-10-05 13:38:32 -07:00
control.setContentHuggingPriority(NSLayoutConstraint.Priority(rawValue: 251), for: .horizontal)
stackView.addArrangedSubview(control)
addSubview(stackView)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: topAnchor),
stackView.leftAnchor.constraint(equalTo: leftAnchor),
stackView.bottomAnchor.constraint(equalTo: bottomAnchor),
stackView.rightAnchor.constraint(equalTo: rightAnchor),
])
}
}
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")
}
}
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")
}
}