Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 76 additions & 58 deletions Sources/MatrixScreenSaver/MatrixScreenSaverOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,23 +45,23 @@ struct MatrixScreenSaverOptions: Equatable {
static let maxNeoMessageLineCount = 10
static let maxNeoMessageLineLength = 256

static let neoMessageSceneDescription = "Show the Neo message intro before the main scene. Turned on by default."
static let neoMessageSpeedFactorDescription = "Multiplier for the Neo message typing and pause speed. A positive number. The default is 1.0."
static let neoMessageLinesDescription = "Lines typed during the Neo message intro. Between 1 and 10 lines, up to 256 characters each."
static let numberSceneDescription = "Show the startup number scene before continuous rain. Turned on by default."
static let diffuseDescription = "Turn on/off the glow effect. Turned on by default."
static let twinkleDescription = "Turn on/off the twinkling effect. Turned on by default."
static let characterSizeDescription = "Character cell size in pixels. The default is 16×30."
static let neoMessageSceneDescription = "Show the Neo message intro. On by default."
static let neoMessageSpeedFactorDescription = "Speed multiplier for Neo message typing. Default: 1.0."
static let neoMessageLinesDescription = "Lines typed during the Neo message intro (1–10, max 256 chars)."
static let numberSceneDescription = "Show the startup number scene. On by default."
static let diffuseDescription = "On by default."
static let twinkleDescription = "On by default."
static let characterSizeDescription = "Character cell size in pixels. Default: 16×30."

static let characterSizePairs: [(width: Int, height: Int)] = [
(8, 15), (9, 17), (10, 19), (11, 21), (12, 23), (13, 24), (14, 26), (15, 28), (16, 30), (17, 32), (18, 34), (19, 36), (20, 38), (21, 40), (22, 42), (23, 44), (24, 46)
]
static let defaultCharacterSizeIndex = 8
static let rainDensityDescription = "Set the factor for the density of rain drops. A positive number. The default is 1.0."
static let frameRateDescription = "Set the frame rate per second. A positive number less than or equal to 1000. The default is 25."
static let errorRateDescription = "Set the factor for the rate of character changes. A non-negative number. The default is 1.0."
static let charactersDescription = "Restrict rain glyphs to a custom set (e.g. ATGC). Leave empty for the default."
static let scanLinesDescription = "Intensity of the CRT scan line overlay (0 = off, 100 = fully opaque). Default is 25%."
static let rainDensityDescription = "Rain density multiplier. Default: 1.0."
static let frameRateDescription = "Target frame rate in fps, 1–1000. Default: 25."
static let errorRateDescription = "Character change rate factor. Default: 1.0."
static let charactersDescription = "Custom glyph set (e.g. ATGC). Empty = default."
static let scanLinesDescription = "CRT scan line intensity (0100%). Default: 25%."

var neoMessageSceneEnabled = defaultNeoMessageSceneEnabled
var neoMessageSpeedFactor = defaultNeoMessageSpeedFactor
Expand Down Expand Up @@ -225,7 +225,7 @@ final class MatrixScreenSaverOptionsSheetController: NSObject,

private let window: NSWindow
private let rootStack = NSStackView()
private let neoMessageSceneCheckbox = NSButton(checkboxWithTitle: "Neo message scene", target: nil, action: nil)
private let neoMessageSceneCheckbox = NSButton(checkboxWithTitle: "Activate", target: nil, action: nil)
private let neoMessageSpeedFactorField = NSTextField(string: "")
private let linesTableView = NSTableView()
private let linesScrollView = NSScrollView()
Expand All @@ -239,9 +239,9 @@ final class MatrixScreenSaverOptionsSheetController: NSObject,
private static let removeColumnID = NSUserInterfaceItemIdentifier("remove")
private var pendingLines: [String] = MatrixScreenSaverOptions.defaultNeoMessageLines
private var linesEditorWindow: NSWindow?
private let numberSceneCheckbox = NSButton(checkboxWithTitle: "Number scene", target: nil, action: nil)
private let twinkleCheckbox = NSButton(checkboxWithTitle: "Twinkle", target: nil, action: nil)
private let diffuseCheckbox = NSButton(checkboxWithTitle: "Diffuse", target: nil, action: nil)
private let numberSceneCheckbox = NSButton(checkboxWithTitle: "Activate", target: nil, action: nil)
private let twinkleCheckbox = NSButton(checkboxWithTitle: "Twinkling effect", target: nil, action: nil)
private let diffuseCheckbox = NSButton(checkboxWithTitle: "Glow effect", target: nil, action: nil)
private let scanLinesSlider: NSSlider = {
let s = NSSlider(value: MatrixScreenSaverOptions.defaultScanLinesIntensity * 100,
minValue: 0, maxValue: 100,
Expand Down Expand Up @@ -400,35 +400,35 @@ final class MatrixScreenSaverOptionsSheetController: NSObject,
])
rootStack.addArrangedSubview(headerContainer)

let neoMessageSceneSection = makeCheckboxSection(
checkbox: neoMessageSceneCheckbox,
description: MatrixScreenSaverOptions.neoMessageSceneDescription
)
let numberSceneSection = makeCheckboxSection(
checkbox: numberSceneCheckbox,
description: MatrixScreenSaverOptions.numberSceneDescription
)
let twinkleSection = makeCheckboxSection(checkbox: twinkleCheckbox, description: MatrixScreenSaverOptions.twinkleDescription)
let diffuseSection = makeCheckboxSection(checkbox: diffuseCheckbox, description: MatrixScreenSaverOptions.diffuseDescription)
let scanLinesSection = makeScanLinesSection()
let toggleStack = NSStackView()
toggleStack.orientation = .vertical
toggleStack.alignment = .leading
toggleStack.spacing = 16
toggleStack.setHuggingPriority(.required, for: .vertical)
toggleStack.addArrangedSubview(neoMessageSceneSection)
toggleStack.addArrangedSubview(numberSceneSection)
toggleStack.addArrangedSubview(twinkleSection)
toggleStack.addArrangedSubview(diffuseSection)
toggleStack.addArrangedSubview(scanLinesSection)
rootStack.addArrangedSubview(toggleStack)
rootStack.addArrangedSubview(makeNumericSection(title: "Neo message speed", field: neoMessageSpeedFactorField, description: MatrixScreenSaverOptions.neoMessageSpeedFactorDescription))
rootStack.addArrangedSubview(makeNeoMessageLinesButtonRow())
rootStack.addArrangedSubview(makeCharacterSizeSection())
rootStack.addArrangedSubview(makeNumericSection(title: "Rain density", field: rainDensityField, description: MatrixScreenSaverOptions.rainDensityDescription))
rootStack.addArrangedSubview(makeNumericSection(title: "Frame rate", field: frameRateField, description: MatrixScreenSaverOptions.frameRateDescription))
rootStack.addArrangedSubview(makeNumericSection(title: "Error rate", field: errorRateField, description: MatrixScreenSaverOptions.errorRateDescription))
rootStack.addArrangedSubview(makeTextSection(title: "Characters", field: charactersField, description: MatrixScreenSaverOptions.charactersDescription))
let generalGroup = makeGroupBox(title: "General", views: [
makeCharacterSizeSection(),
makeScanLinesSection(),
makeNumericSection(title: "Typing speed", field: neoMessageSpeedFactorField, description: MatrixScreenSaverOptions.neoMessageSpeedFactorDescription),
makeNumericSection(title: "Frame rate", field: frameRateField, description: MatrixScreenSaverOptions.frameRateDescription),
])
let neoGroup = makeGroupBox(title: "Neo message scene", views: [
makeCheckboxSection(checkbox: neoMessageSceneCheckbox, description: MatrixScreenSaverOptions.neoMessageSceneDescription),
makeNeoMessageLinesButtonRow(),
])
let numberGroup = makeGroupBox(title: "Number scene", views: [
makeCheckboxSection(checkbox: numberSceneCheckbox, description: MatrixScreenSaverOptions.numberSceneDescription),
])
let rainGroup = makeGroupBox(title: "Rain scene", views: [
makeCheckboxSection(checkbox: twinkleCheckbox, description: MatrixScreenSaverOptions.twinkleDescription),
makeCheckboxSection(checkbox: diffuseCheckbox, description: MatrixScreenSaverOptions.diffuseDescription),
makeNumericSection(title: "Density", field: rainDensityField, description: MatrixScreenSaverOptions.rainDensityDescription),
makeTextSection(title: "Characters", field: charactersField, description: MatrixScreenSaverOptions.charactersDescription, fieldWidth: Self.textFieldWidth / 2),
makeNumericSection(title: "Error rate", field: errorRateField, description: MatrixScreenSaverOptions.errorRateDescription),
])
rootStack.addArrangedSubview(generalGroup)
rootStack.addArrangedSubview(neoGroup)
rootStack.addArrangedSubview(numberGroup)
rootStack.addArrangedSubview(rainGroup)

let groupWidth = -(rootStack.edgeInsets.left + rootStack.edgeInsets.right)
for group in [generalGroup, neoGroup, numberGroup, rainGroup] {
group.widthAnchor.constraint(equalTo: rootStack.widthAnchor, constant: groupWidth).isActive = true
}

let buttons = NSStackView()
buttons.orientation = .horizontal
Expand All @@ -451,11 +451,6 @@ final class MatrixScreenSaverOptionsSheetController: NSObject,
footer.distribution = .fill
footer.translatesAutoresizingMaskIntoConstraints = false

let leadingPadding = NSView()
leadingPadding.translatesAutoresizingMaskIntoConstraints = false
leadingPadding.widthAnchor.constraint(equalToConstant: rootStack.edgeInsets.left).isActive = true
leadingPadding.setContentHuggingPriority(.required, for: .horizontal)

let spacerLeft = NSView()
spacerLeft.translatesAutoresizingMaskIntoConstraints = false
spacerLeft.setContentHuggingPriority(.defaultLow, for: .horizontal)
Expand All @@ -473,18 +468,16 @@ final class MatrixScreenSaverOptionsSheetController: NSObject,
spacerRight.setContentHuggingPriority(.defaultLow, for: .horizontal)
spacerRight.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)

footer.addArrangedSubview(leadingPadding)
footer.addArrangedSubview(resetButton)
footer.addArrangedSubview(spacerLeft)
footer.addArrangedSubview(versionLabel)
footer.addArrangedSubview(spacerRight)
footer.addArrangedSubview(buttons)
footer.setCustomSpacing(0, after: leadingPadding)

rootStack.addArrangedSubview(footer)
buttons.setHuggingPriority(.required, for: .horizontal)
resetButton.setContentHuggingPriority(.required, for: .horizontal)
footer.widthAnchor.constraint(equalTo: rootStack.widthAnchor).isActive = true
footer.widthAnchor.constraint(equalTo: rootStack.widthAnchor, constant: groupWidth).isActive = true

let contentView = NSView()
contentView.translatesAutoresizingMaskIntoConstraints = false
Expand Down Expand Up @@ -514,7 +507,7 @@ final class MatrixScreenSaverOptionsSheetController: NSObject,
row.spacing = 12
row.alignment = .centerY

let label = NSTextField(labelWithString: "Neo message lines")
let label = NSTextField(labelWithString: "Lines")
label.font = .systemFont(ofSize: 13, weight: .semibold)
label.setContentHuggingPriority(.required, for: .horizontal)

Expand Down Expand Up @@ -911,7 +904,7 @@ final class MatrixScreenSaverOptionsSheetController: NSObject,
}

/// Builds a labeled text input row with its description.
private func makeTextSection(title: String, field: NSTextField, description: String) -> NSView {
private func makeTextSection(title: String, field: NSTextField, description: String, fieldWidth: CGFloat? = nil) -> NSView {
let stack = NSStackView()
stack.orientation = .vertical
stack.alignment = .leading
Expand All @@ -930,7 +923,7 @@ final class MatrixScreenSaverOptionsSheetController: NSObject,
field.alignment = .left
field.controlSize = .regular
field.translatesAutoresizingMaskIntoConstraints = false
field.widthAnchor.constraint(equalToConstant: Self.textFieldWidth).isActive = true
field.widthAnchor.constraint(equalToConstant: fieldWidth ?? Self.textFieldWidth).isActive = true

row.addArrangedSubview(label)
row.addArrangedSubview(field)
Expand Down Expand Up @@ -979,9 +972,34 @@ final class MatrixScreenSaverOptionsSheetController: NSObject,
characterSizeValueLabel.stringValue = "\(pair.width)×\(pair.height)"
}

/// Builds a titled group box containing a vertical stack of option sections.
private func makeGroupBox(title: String, views: [NSView]) -> NSBox {
let box = NSBox()
box.title = title
box.contentViewMargins = NSSize(width: 8, height: 8)

let stack = NSStackView(views: views)
stack.orientation = .vertical
stack.alignment = .leading
stack.spacing = 12
stack.setHuggingPriority(.required, for: .vertical)
stack.translatesAutoresizingMaskIntoConstraints = false

guard let contentView = box.contentView else { return box }
contentView.addSubview(stack)
NSLayoutConstraint.activate([
stack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
stack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
stack.topAnchor.constraint(equalTo: contentView.topAnchor),
stack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
])

return box
}

/// Creates the smaller descriptive text used under each option row.
private func makeDescriptionLabel(_ description: String) -> NSTextField {
let label = NSTextField(wrappingLabelWithString: description)
let label = NSTextField(labelWithString: description)
label.font = .systemFont(ofSize: 11)
label.textColor = .secondaryLabelColor
label.alignment = .left
Expand Down Expand Up @@ -1128,7 +1146,7 @@ final class MatrixScreenSaverOptionsSheetController: NSObject,
contentView.layoutSubtreeIfNeeded()
let fittingSize = rootStack.fittingSize
let contentSize = NSSize(
width: ceil(fittingSize.width + rootStack.edgeInsets.right),
width: ceil(fittingSize.width),
height: ceil(fittingSize.height)
)
window.setContentSize(contentSize)
Expand Down
Loading