Go native-only: remove the Python POC, move the Swift app to the top level
Adds --check (compile every effect without camera or window), EFFECTS.md (the contract for writing effects) and AGENTS.md. The POC stays available at the 'poc' tag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
6591138bbf
commit
a20d3310db
29 changed files with 160 additions and 775 deletions
92
Sources/MotionLab/Panel.swift
Normal file
92
Sources/MotionLab/Panel.swift
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
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))
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue