340 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
			
		
		
	
	
			340 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
//
 | 
						|
//  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")
 | 
						|
 | 
						|
private struct StyleItem {
 | 
						|
    let name: String
 | 
						|
    let tag: Int
 | 
						|
    let colorNames: [String]
 | 
						|
 | 
						|
    var allowsRotation: Bool {
 | 
						|
        return colorNames.count > 1
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
public class PreferencesViewController: NSViewController {
 | 
						|
    private static var styleItems: [StyleItem] {
 | 
						|
        return [
 | 
						|
            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.
 | 
						|
            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
 | 
						|
    }()
 | 
						|
 | 
						|
    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)
 | 
						|
        centerX.priority = NSLayoutConstraint.Priority(rawValue: 999)
 | 
						|
        let centerY = colorStackView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
 | 
						|
        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
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
    override public func viewWillAppear() {
 | 
						|
        super.viewWillAppear()
 | 
						|
        if let style = defaults.colorStyle {
 | 
						|
            styleMenu.selectItem(withTag: Int(style.rawValue))
 | 
						|
            updateColorViewVisibility()
 | 
						|
        }
 | 
						|
        prepareColorViews()
 | 
						|
        prepareColorPanel()
 | 
						|
    }
 | 
						|
 | 
						|
    override public func viewWillDisappear() {
 | 
						|
        super.viewWillDisappear()
 | 
						|
        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() {
 | 
						|
        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)
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // MARK: - Actions
 | 
						|
 | 
						|
    @objc func colorPanelDidUpdateValue(_ colorPanel: NSColorPanel) {
 | 
						|
        postColorNotification()
 | 
						|
    }
 | 
						|
 | 
						|
    @objc func styleDidUpdate(sender: NSMenuItem) {
 | 
						|
        updateColorViewVisibility()
 | 
						|
        postColorNotification()
 | 
						|
    }
 | 
						|
 | 
						|
    @objc func sliderDidUpdate(sender: NSSlider) {
 | 
						|
        postColorNotification()
 | 
						|
    }
 | 
						|
 | 
						|
    @objc func closeWindow() {
 | 
						|
        self.view.window?.close()
 | 
						|
    }
 | 
						|
 | 
						|
    // MARK: - Private
 | 
						|
 | 
						|
    func updateColorViewVisibility() {
 | 
						|
        let idx = styleMenu.indexOfSelectedItem
 | 
						|
        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
 | 
						|
    }
 | 
						|
 | 
						|
    func postColorNotification() {
 | 
						|
        var info = [String:Any]()
 | 
						|
        if let item = styleMenu.selectedItem {
 | 
						|
            info["colorStyle"] = ColorStyle(rawValue: UInt32(item.tag))
 | 
						|
        }
 | 
						|
        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
 | 
						|
        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
 | 
						|
        stackView.spacing = 12
 | 
						|
        stackView.alignment = .centerY
 | 
						|
        stackView.distribution = .fill
 | 
						|
 | 
						|
        label.translatesAutoresizingMaskIntoConstraints = false
 | 
						|
        stackView.addArrangedSubview(label)
 | 
						|
 | 
						|
        control.translatesAutoresizingMaskIntoConstraints = false
 | 
						|
        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")
 | 
						|
    }
 | 
						|
}
 |