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
112 changes: 59 additions & 53 deletions Sources/ZoomItMacCore/Settings/SettingsWindowController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,27 @@ final class SettingsWindowController: NSObject, NSWindowDelegate {
return stack
}

/// Creates a checkbox styled like the Windows ZoomIt options dialog, where
/// the caption sits to the left of the box (BS_LEFTTEXT). Grouping several
/// of these in a `makeCheckboxColumn` lines their boxes up in a single
/// right-hand column, matching the Windows layout.
private func makeCheckbox(_ title: String, action: Selector, state: Bool) -> NSButton {
let button = NSButton(checkboxWithTitle: title, target: self, action: action)
button.imagePosition = .imageRight
button.state = state ? .on : .off
return button
}

/// Stacks Windows-style checkboxes so their boxes align in a right-hand
/// column, the way the ZoomIt options dialog aligns them.
private func makeCheckboxColumn(_ checkboxes: [NSView], spacing: CGFloat = 6) -> NSView {
let stack = NSStackView(views: checkboxes)
stack.orientation = .vertical
stack.alignment = .trailing
stack.spacing = spacing
return stack
}

private func makeIndentedColumn(_ rows: [NSView], indent: CGFloat = 18, spacing: CGFloat = 6) -> NSView {
let stack = NSStackView(views: rows)
stack.orientation = .vertical
Expand Down Expand Up @@ -437,7 +458,7 @@ final class SettingsWindowController: NSObject, NSWindowDelegate {
hotKeyButton.setButtonType(.momentaryPushIn)
hotKeyButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 140).isActive = true
self.hotKeyButton = hotKeyButton
let hotKeyRow = makeRow([makeLabel("Zoom toggle:"), hotKeyButton])
let hotKeyRow = makeRow([makeLabel("Zoom Toggle:"), hotKeyButton])

let magHelp = makeLabel("Specify the initial level of magnification when zooming in:", wraps: true)

Expand All @@ -454,13 +475,10 @@ final class SettingsWindowController: NSObject, NSWindowDelegate {
magPopup.action = #selector(zoomLevelChanged(_:))
let magRow = makeRow([makeLabel("Initial magnification:"), magPopup])

let animateCheck = NSButton(checkboxWithTitle: "Animate zoom in and zoom out", target: self, action: #selector(animateZoomChanged(_:)))
animateCheck.state = settings.animateZoom ? .on : .off
let animateCheck = makeCheckbox("Animate zoom in and zoom out:", action: #selector(animateZoomChanged(_:)), state: settings.animateZoom)
let smoothCheck = makeCheckbox("Smooth zoomed image:", action: #selector(smoothImageChanged(_:)), state: settings.smoothImage)

let smoothCheck = NSButton(checkboxWithTitle: "Smooth zoomed image", target: self, action: #selector(smoothImageChanged(_:)))
smoothCheck.state = settings.smoothImage ? .on : .off

return makeColumn([help, hotKeyRow, magHelp, magRow, animateCheck, smoothCheck])
return makeColumn([help, hotKeyRow, magHelp, magRow, makeCheckboxColumn([animateCheck, smoothCheck])])
}

// MARK: - Live Zoom tab
Expand All @@ -478,7 +496,7 @@ final class SettingsWindowController: NSObject, NSWindowDelegate {
liveHotKeyButton.setButtonType(.momentaryPushIn)
liveHotKeyButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 140).isActive = true
self.liveHotKeyButton = liveHotKeyButton
let liveHotKeyRow = makeRow([makeLabel("Live zoom toggle:"), liveHotKeyButton])
let liveHotKeyRow = makeRow([makeLabel("LiveZoom Toggle:"), liveHotKeyButton])

return makeColumn([liveHelp, liveHotKeyRow])
}
Expand Down Expand Up @@ -817,7 +835,7 @@ final class SettingsWindowController: NSObject, NSWindowDelegate {
drawHotKeyButton.setButtonType(.momentaryPushIn)
drawHotKeyButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 140).isActive = true
self.drawHotKeyButton = drawHotKeyButton
let drawHotKeyRow = makeRow([makeLabel("Draw w/out zoom:"), drawHotKeyButton])
let drawHotKeyRow = makeRow([makeLabel("Draw w/out Zoom:"), drawHotKeyButton])

return makeColumn([
help,
Expand Down Expand Up @@ -885,8 +903,7 @@ final class SettingsWindowController: NSObject, NSWindowDelegate {
updateBreakDurationLabel()
let durationControls = makeRow([durationStepper, durationLabel])

let expiredCheck = NSButton(checkboxWithTitle: "Show expired time after 0:00", target: self, action: #selector(breakShowExpiredChanged(_:)))
expiredCheck.state = settings.breakShowExpiredTime ? .on : .off
let expiredCheck = makeCheckbox("Show Time Elapsed After Expiration:", action: #selector(breakShowExpiredChanged(_:)), state: settings.breakShowExpiredTime)

let textColorPopup = makeColorPopup(selected: settings.breakTextColorRGB, action: #selector(breakTextColorChanged(_:)))
let backgroundColorPopup = makeColorPopup(selected: settings.breakBackgroundColorRGB, action: #selector(breakBackgroundColorChanged(_:)))
Expand All @@ -907,8 +924,7 @@ final class SettingsWindowController: NSObject, NSWindowDelegate {
opacityPopup.target = self
opacityPopup.action = #selector(breakOpacityChanged(_:))

let soundCheck = NSButton(checkboxWithTitle: "Play sound at 0:00", target: self, action: #selector(breakPlaySoundChanged(_:)))
soundCheck.state = settings.breakPlaySound ? .on : .off
let soundCheck = makeCheckbox("Play Sound on Expiration:", action: #selector(breakPlaySoundChanged(_:)), state: settings.breakPlaySound)
let soundField = makePathField(settings.breakSoundFile)
breakSoundFileField = soundField
let soundBrowse = NSButton(title: "Browse…", target: self, action: #selector(chooseBreakSoundFile(_:)))
Expand All @@ -926,15 +942,14 @@ final class SettingsWindowController: NSObject, NSWindowDelegate {
let backgroundBrowse = NSButton(title: "Browse…", target: self, action: #selector(chooseBreakBackgroundFile(_:)))
backgroundBrowse.bezelStyle = .rounded
breakBackgroundBrowseButton = backgroundBrowse
let stretchCheck = NSButton(checkboxWithTitle: "Stretch image", target: self, action: #selector(breakBackgroundStretchChanged(_:)))
stretchCheck.state = settings.breakBackgroundStretch ? .on : .off
let stretchCheck = makeCheckbox("Scale to screen:", action: #selector(breakBackgroundStretchChanged(_:)), state: settings.breakBackgroundStretch)
breakBackgroundStretchCheckbox = stretchCheck

// A single grid keeps every label/control pair in aligned columns:
// column 0 = right-aligned labels, column 1 = primary control,
// column 2 = secondary label, column 3 = secondary control.
let grid = makeFormGrid([
[makeLabel("Break timer:"), hotKeyButton, makeLabel("Duration:"), durationControls],
[makeLabel("Start Timer:"), hotKeyButton, makeLabel("Duration:"), durationControls],
[makeLabel("Timer color:"), textColorPopup, makeLabel("Background:"), backgroundColorPopup],
[makeLabel("Position:"), positionPopup, makeLabel("Opacity:"), opacityPopup],
[makeLabel("Backdrop:"), backgroundModePopup, stretchCheck],
Expand All @@ -946,7 +961,7 @@ final class SettingsWindowController: NSObject, NSWindowDelegate {
return makeColumn([
help,
grid,
makeRow([expiredCheck, soundCheck])
makeCheckboxColumn([expiredCheck, soundCheck])
], spacing: 12)
}

Expand Down Expand Up @@ -1086,28 +1101,26 @@ final class SettingsWindowController: NSObject, NSWindowDelegate {
snipHotKeyButton.setButtonType(.momentaryPushIn)
snipHotKeyButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 140).isActive = true
self.snipHotKeyButton = snipHotKeyButton
let snipHotKeyRow = makeRow([makeLabel("Snip region:"), snipHotKeyButton])
let snipHotKeyRow = makeRow([makeLabel("Snip Toggle:"), snipHotKeyButton])

let snipOcrHotKeyButton = NSButton(title: snipOcrHotKeyDisplayString(), target: self, action: #selector(toggleSnipOcrHotKeyRecording(_:)))
snipOcrHotKeyButton.bezelStyle = .rounded
snipOcrHotKeyButton.setButtonType(.momentaryPushIn)
snipOcrHotKeyButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 140).isActive = true
self.snipOcrHotKeyButton = snipOcrHotKeyButton
let snipOcrHotKeyRow = makeRow([makeLabel("OCR region to text:"), snipOcrHotKeyButton])
let snipOcrHotKeyRow = makeRow([makeLabel("Text Toggle:"), snipOcrHotKeyButton])

let copyOnSaveCheck = NSButton(
checkboxWithTitle: "Also copy to clipboard when saving to a file",
target: self,
action: #selector(copySnipToClipboardOnSaveChanged(_:))
let copyOnSaveCheck = makeCheckbox(
"Also copy to clipboard when saving to a file:",
action: #selector(copySnipToClipboardOnSaveChanged(_:)),
state: settings.copySnipToClipboardOnSave
)
copyOnSaveCheck.state = settings.copySnipToClipboardOnSave ? .on : .off

let saveToDirectoryCheck = NSButton(
checkboxWithTitle: "Save to a folder instead of asking each time",
target: self,
action: #selector(saveSnipToDirectoryChanged(_:))
let saveToDirectoryCheck = makeCheckbox(
"Save to a folder instead of asking each time:",
action: #selector(saveSnipToDirectoryChanged(_:)),
state: settings.saveSnipToDirectory
)
saveToDirectoryCheck.state = settings.saveSnipToDirectory ? .on : .off

let directoryField = NSTextField(labelWithString: snipSaveDirectoryDisplayPath())
directoryField.translatesAutoresizingMaskIntoConstraints = false
Expand All @@ -1121,7 +1134,7 @@ final class SettingsWindowController: NSObject, NSWindowDelegate {

updateSnipSaveDirectoryControlsEnabled()

return makeColumn([help, snipHotKeyRow, snipOcrHotKeyRow, copyOnSaveCheck, saveToDirectoryCheck, directoryRow])
return makeColumn([help, snipHotKeyRow, snipOcrHotKeyRow, makeCheckboxColumn([copyOnSaveCheck, saveToDirectoryCheck]), directoryRow])
}

private func snipSaveDirectoryDisplayPath() -> String {
Expand Down Expand Up @@ -1178,16 +1191,13 @@ final class SettingsWindowController: NSObject, NSWindowDelegate {
recordHotKeyButton.setButtonType(.momentaryPushIn)
recordHotKeyButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 140).isActive = true
self.recordHotKeyButton = recordHotKeyButton
let recordHotKeyRow = makeRow([makeLabel("Record:"), recordHotKeyButton])
let recordHotKeyRow = makeRow([makeLabel("Record Toggle:"), recordHotKeyButton])

let systemAudioCheck = NSButton(checkboxWithTitle: "Capture system audio", target: self, action: #selector(recordSystemAudioChanged(_:)))
systemAudioCheck.state = settings.recordSystemAudio ? .on : .off
let systemAudioCheck = makeCheckbox("Capture system audio:", action: #selector(recordSystemAudioChanged(_:)), state: settings.recordSystemAudio)

let micCheck = NSButton(checkboxWithTitle: "Capture microphone", target: self, action: #selector(recordMicrophoneChanged(_:)))
micCheck.state = settings.recordMicrophone ? .on : .off
let micCheck = makeCheckbox("Capture audio input:", action: #selector(recordMicrophoneChanged(_:)), state: settings.recordMicrophone)

let windNoiseCheck = NSButton(checkboxWithTitle: "Wind noise removal", target: self, action: #selector(recordNoiseCancellationChanged(_:)))
windNoiseCheck.state = settings.recordNoiseCancellation ? .on : .off
let windNoiseCheck = makeCheckbox("Noise cancellation:", action: #selector(recordNoiseCancellationChanged(_:)), state: settings.recordNoiseCancellation)
noiseCancellationCheckbox = windNoiseCheck

let micPopup = NSPopUpButton(frame: .zero, pullsDown: false)
Expand All @@ -1207,15 +1217,15 @@ final class SettingsWindowController: NSObject, NSWindowDelegate {
micPopup.isEnabled = settings.recordMicrophone
micPopup.widthAnchor.constraint(equalToConstant: 220).isActive = true
microphonePopup = micPopup
let micDeviceRow = makeRow([makeLabel("Device:"), micPopup])
let micDeviceRow = makeRow([makeLabel("Microphone:"), micPopup])
let micOptions = makeIndentedColumn([windNoiseCheck, micDeviceRow])
updateMicrophoneOptionControls()

let trimButton = NSButton(title: "Trim…", target: self, action: #selector(openTrimEditor(_:)))
trimButton.bezelStyle = .rounded
let trimRow = makeRow([makeLabel("Edit existing video:"), trimButton])

return makeColumn([help, recordHotKeyRow, systemAudioCheck, micCheck, micOptions, trimRow] + makeWebcamRows(), spacing: 10)
return makeColumn([help, recordHotKeyRow, makeCheckboxColumn([systemAudioCheck, micCheck]), micOptions] + makeWebcamRows() + [trimRow], spacing: 10)
}

// MARK: - Panorama tab
Expand All @@ -1237,7 +1247,7 @@ final class SettingsWindowController: NSObject, NSWindowDelegate {
panoramaHotKeyButton.setButtonType(.momentaryPushIn)
panoramaHotKeyButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 140).isActive = true
self.panoramaHotKeyButton = panoramaHotKeyButton
let panoramaHotKeyRow = makeRow([makeLabel("Panorama:"), panoramaHotKeyButton])
let panoramaHotKeyRow = makeRow([makeLabel("Panorama Toggle:"), panoramaHotKeyButton])

return makeColumn([help, panoramaHotKeyRow])
}
Expand Down Expand Up @@ -1271,7 +1281,7 @@ final class SettingsWindowController: NSObject, NSWindowDelegate {
private func updateMicrophoneOptionControls() {
microphonePopup?.isEnabled = settings.recordMicrophone
let supported = settings.recordMicrophone && AudioDevices.supportsWindNoiseRemoval(deviceID: settings.microphoneDeviceID)
noiseCancellationCheckbox?.title = supported ? "Wind noise removal" : "Wind noise removal (not supported by selected microphone)"
noiseCancellationCheckbox?.title = supported ? "Noise cancellation:" : "Noise cancellation (not supported by selected microphone):"
noiseCancellationCheckbox?.isEnabled = supported
noiseCancellationCheckbox?.state = (settings.recordNoiseCancellation && supported) ? .on : .off
noiseCancellationCheckbox?.toolTip = supported
Expand All @@ -1289,8 +1299,7 @@ final class SettingsWindowController: NSObject, NSWindowDelegate {
let heading = makeLabel("Webcam overlay:")
heading.font = NSFont.systemFont(ofSize: NSFont.systemFontSize, weight: .semibold)

let enableCheck = NSButton(checkboxWithTitle: "Show webcam in recordings", target: self, action: #selector(webcamEnabledChanged(_:)))
enableCheck.state = settings.webcamEnabled ? .on : .off
let enableCheck = makeCheckbox("", action: #selector(webcamEnabledChanged(_:)), state: settings.webcamEnabled)

Comment on lines +1302 to 1303
let devicePopup = NSPopUpButton(frame: .zero, pullsDown: false)
devicePopup.translatesAutoresizingMaskIntoConstraints = false
Expand Down Expand Up @@ -1344,17 +1353,15 @@ final class SettingsWindowController: NSObject, NSWindowDelegate {
shapePopup.widthAnchor.constraint(equalToConstant: 165).isActive = true

let webcamEnableRow = makeRow([heading, enableCheck])
let webcamPlacementRow = makeRow([
makeLabel("Camera:"), devicePopup,
makeLabel("Position:"), positionPopup
])
let webcamAppearanceRow = makeRow([
makeLabel("Size:"), sizePopup,
makeLabel("Shape:"), shapePopup
// A grid keeps the two placement/appearance columns aligned so the
// Position and Shape labels (and their popups) line up vertically.
let webcamGrid = makeFormGrid([
[makeLabel("Camera:"), devicePopup, makeLabel("Position:"), positionPopup],
[makeLabel("Size:"), sizePopup, makeLabel("Shape:"), shapePopup]
])

updateWebcamControlsEnabled()
return [webcamEnableRow, webcamPlacementRow, webcamAppearanceRow]
return [webcamEnableRow, webcamGrid]
}

/// Builds a popup whose item indices map directly to a settings integer.
Expand Down Expand Up @@ -1486,8 +1493,7 @@ final class SettingsWindowController: NSObject, NSWindowDelegate {
speedSlider.widthAnchor.constraint(equalToConstant: 240).isActive = true
let speedRow = makeRow([makeLabel("DemoType typing speed:"), makeLabel("Slow"), speedSlider, makeLabel("Fast")])

let userDrivenCheck = NSButton(checkboxWithTitle: "Drive input with typing", target: self, action: #selector(demoTypeUserDrivenChanged(_:)))
userDrivenCheck.state = settings.demoTypeUserDriven ? .on : .off
let userDrivenCheck = makeCheckbox("Drive input with typing:", action: #selector(demoTypeUserDrivenChanged(_:)), state: settings.demoTypeUserDriven)

return makeColumn([help, controlsHelp, hotKeyRow, fileRow, speedRow, userDrivenCheck], spacing: 8)
}
Expand Down