Files
Snaggle/GreenshotMac/Preferences/PreferencesView.swift
T
2026-09-17 16:15:02 -05:00

175 lines
7.7 KiB
Swift

// PreferencesView.swift
// GreenshotMac
//
// Port of Greenshot's SettingsForm.
import SwiftUI
import AppKit
struct PreferencesView: View {
var body: some View {
TabView {
GeneralPreferences()
.tabItem { Label("General", systemImage: "gearshape") }
OutputPreferences()
.tabItem { Label("Output", systemImage: "square.and.arrow.down") }
HotkeyPreferences()
.tabItem { Label("Hotkeys", systemImage: "keyboard") }
EditorPreferences()
.tabItem { Label("Editor", systemImage: "pencil.tip.crop.circle") }
}
.padding(16)
.frame(width: 520, height: 380)
}
}
private struct GeneralPreferences: View {
@AppStorage(Configuration.Keys.defaultDestination) private var destination = DestinationID.editor.rawValue
@AppStorage(Configuration.Keys.captureMousepointer) private var captureMousePointer = false
@AppStorage(Configuration.Keys.captureDelayMillis) private var captureDelay = 0
@AppStorage(Configuration.Keys.playCameraSound) private var cameraSound = true
@AppStorage(Configuration.Keys.showFlashAnimation) private var flash = true
@AppStorage(Configuration.Keys.showMagnifier) private var magnifier = true
@AppStorage(Configuration.Keys.showSelectionSize) private var selectionSize = true
@AppStorage(Configuration.Keys.windowCaptureMode) private var windowMode = WindowCaptureMode.window.rawValue
@AppStorage(Configuration.Keys.appearanceMode) private var appearance = AppearanceMode.system.rawValue
@State private var launchAtLogin = LaunchAtLogin.isEnabled
var body: some View {
Form {
Picker("Appearance:", selection: $appearance) {
ForEach(AppearanceMode.allCases, id: \.rawValue) { mode in
Text(mode.title).tag(mode.rawValue)
}
}
.onChange(of: appearance) { _, _ in
NotificationCenter.default.post(name: .appearanceChanged, object: nil)
}
Picker("After capture:", selection: $destination) {
ForEach(DestinationID.allCases.filter { $0 != .openWith }, id: \.rawValue) { item in
Text(item.title).tag(item.rawValue)
}
}
Picker("Window capture:", selection: $windowMode) {
Text("Window only").tag(WindowCaptureMode.window.rawValue)
Text("Window with shadow").tag(WindowCaptureMode.windowWithShadow.rawValue)
Text("Crop from screen").tag(WindowCaptureMode.screen.rawValue)
}
Toggle("Capture the mouse pointer", isOn: $captureMousePointer)
Toggle("Play a shutter sound", isOn: $cameraSound)
Toggle("Flash the captured area", isOn: $flash)
Toggle("Show magnifier while selecting", isOn: $magnifier)
Toggle("Show selection size while selecting", isOn: $selectionSize)
Toggle("Launch at login", isOn: $launchAtLogin)
.onChange(of: launchAtLogin) { _, newValue in
LaunchAtLogin.isEnabled = newValue
}
HStack {
Text("Capture delay:")
Slider(value: Binding(get: { Double(captureDelay) },
set: { captureDelay = Int($0) }),
in: 0...5000, step: 250)
Text("\(captureDelay) ms").frame(width: 70, alignment: .trailing)
}
}
.formStyle(.grouped)
}
}
private struct OutputPreferences: View {
@AppStorage(Configuration.Keys.outputFilePath) private var outputPath = ""
@AppStorage(Configuration.Keys.outputFileFilenamePattern) private var pattern = ""
@AppStorage(Configuration.Keys.outputFileFormat) private var format = OutputFormat.png.rawValue
@AppStorage(Configuration.Keys.outputFileJpegQuality) private var jpegQuality = 80
@AppStorage(Configuration.Keys.copyPathToClipboard) private var copyPath = false
var body: some View {
Form {
HStack {
Text("Save to:")
Text(outputPath).lineLimit(1).truncationMode(.middle)
.foregroundStyle(.secondary)
Spacer()
Button("Choose…", action: chooseFolder)
}
TextField("Filename pattern:", text: $pattern)
Text("Variables: ${capturetime}, ${title}, ${appname}, ${user}, ${hostname}, ${mode}, ${NUM}. "
+ "Example: ${capturetime:d\"yyyy-MM-dd HH_mm_ss\"}-${title}")
.font(.caption)
.foregroundStyle(.secondary)
Picker("Format:", selection: $format) {
ForEach(OutputFormat.allCases, id: \.rawValue) { item in
Text(item.displayName).tag(item.rawValue)
}
}
if format == OutputFormat.jpg.rawValue {
HStack {
Text("JPEG quality:")
Slider(value: Binding(get: { Double(jpegQuality) },
set: { jpegQuality = Int($0) }),
in: 10...100, step: 5)
Text("\(jpegQuality)%").frame(width: 50, alignment: .trailing)
}
}
Toggle("Copy the file path to the clipboard after saving", isOn: $copyPath)
}
.formStyle(.grouped)
}
private func chooseFolder() {
let panel = NSOpenPanel()
panel.canChooseDirectories = true
panel.canChooseFiles = false
panel.allowsMultipleSelection = false
panel.directoryURL = URL(fileURLWithPath: outputPath, isDirectory: true)
if panel.runModal() == .OK, let url = panel.url {
outputPath = url.path
}
}
}
private struct HotkeyPreferences: View {
var body: some View {
Form {
ForEach(HotkeyAction.allCases, id: \.rawValue) { action in
HStack {
Text(action.title)
Spacer()
HotkeyRecorder(action: action)
.frame(width: 150, height: 24)
}
}
Text("Click a shortcut, then press the new combination. Backspace clears it, Esc cancels. "
+ "A modifier key is required.")
.font(.caption)
.foregroundStyle(.secondary)
}
.formStyle(.grouped)
}
}
private struct EditorPreferences: View {
@AppStorage(Configuration.Keys.editorLineThickness) private var lineThickness = 2
@AppStorage(Configuration.Keys.editorFontFamily) private var fontFamily = "Helvetica"
@AppStorage(Configuration.Keys.editorFontSize) private var fontSize = 14
@AppStorage(Configuration.Keys.editorPixelSize) private var pixelSize = 8
@AppStorage(Configuration.Keys.editorBlurRadius) private var blurRadius = 12
@AppStorage(Configuration.Keys.editorDropShadow) private var dropShadow = false
var body: some View {
Form {
Stepper("Default line thickness: \(lineThickness) px", value: $lineThickness, in: 0...20)
Picker("Default font:", selection: $fontFamily) {
ForEach(["Helvetica", "Helvetica Neue", "Menlo", "Times New Roman", "Arial", "Georgia"], id: \.self) {
Text($0).tag($0)
}
}
Stepper("Default font size: \(fontSize) pt", value: $fontSize, in: 6...96)
Stepper("Pixelation size: \(pixelSize) px", value: $pixelSize, in: 2...40)
Stepper("Blur radius: \(blurRadius) px", value: $blurRadius, in: 1...40)
Toggle("Drop shadow on new elements", isOn: $dropShadow)
}
.formStyle(.grouped)
}
}