import AppKit private final class TopAlignedView: NSView { override var isFlipped: Bool { true } // so the scroll view starts at the top } /// Side panel: every effect with a checkbox, and the sliders of the ones that are switched on. final class Panel: NSObject { static let width: CGFloat = 230 let view = NSScrollView() private let stack = NSStackView() private let statusLabel = NSTextField(labelWithString: "") private let renderer: Renderer init(renderer: Renderer) { self.renderer = renderer super.init() stack.orientation = .vertical stack.alignment = .leading stack.spacing = 4 stack.translatesAutoresizingMaskIntoConstraints = false let document = TopAlignedView() document.translatesAutoresizingMaskIntoConstraints = false document.addSubview(stack) view.documentView = document view.hasVerticalScroller = true view.drawsBackground = false NSLayoutConstraint.activate([ view.widthAnchor.constraint(equalToConstant: Panel.width), document.widthAnchor.constraint(equalTo: view.contentView.widthAnchor), stack.topAnchor.constraint(equalTo: document.topAnchor, constant: 14), stack.leadingAnchor.constraint(equalTo: document.leadingAnchor, constant: 14), stack.trailingAnchor.constraint(equalTo: document.trailingAnchor, constant: -14), stack.bottomAnchor.constraint(equalTo: document.bottomAnchor, constant: -14), ]) statusLabel.font = .boldSystemFont(ofSize: 11) statusLabel.textColor = .systemRed rebuild() } private func label(_ text: String, dim: Bool = true) -> NSTextField { let field = NSTextField(labelWithString: text) field.font = .systemFont(ofSize: 11) field.textColor = dim ? .secondaryLabelColor : .labelColor return field } func rebuild() { stack.arrangedSubviews.forEach { $0.removeFromSuperview() } statusLabel.stringValue = renderer.status stack.addArrangedSubview(statusLabel) for (layerIndex, layer) in renderer.layers.enumerated() { let title = layer.error == nil ? layer.name : "\(layer.name) (shader error)" let checkbox = NSButton(checkboxWithTitle: title, target: self, action: #selector(toggled(_:))) checkbox.state = layer.enabled ? .on : .off checkbox.tag = layerIndex checkbox.font = .systemFont(ofSize: 13, weight: .medium) stack.addArrangedSubview(checkbox) guard layer.enabled else { continue } for (paramIndex, param) in layer.params.enumerated() { let slider = NSSlider(value: Double(param.value), minValue: Double(param.min), maxValue: Double(param.max), target: self, action: #selector(sliderMoved(_:))) slider.tag = layerIndex * maxParams + paramIndex slider.controlSize = .small stack.addArrangedSubview(label(param.name)) stack.addArrangedSubview(slider) slider.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true } stack.setCustomSpacing(12, after: stack.arrangedSubviews.last!) } if renderer.layers.isEmpty { stack.addArrangedSubview(label("no .metal files found")) } stack.setCustomSpacing(16, after: stack.arrangedSubviews.last!) stack.addArrangedSubview(label("s screenshot\nr record\nq quit")) } func refreshStatus() { statusLabel.stringValue = renderer.status } @objc private func toggled(_ checkbox: NSButton) { renderer.setEnabled(checkbox.tag, checkbox.state == .on) } @objc private func sliderMoved(_ slider: NSSlider) { renderer.setParam(layer: slider.tag / maxParams, param: slider.tag % maxParams, value: Float(slider.doubleValue)) } }