Files
downterm/Downterm/CommandNotch/Models/NotchSettings.swift

271 lines
10 KiB
Swift

import Foundation
import AppKit
/// Central registry of all user-configurable notch settings.
enum NotchSettings {
enum Keys {
// General
static let showOnAllDisplays = "showOnAllDisplays"
static let openNotchOnHover = "openNotchOnHover"
static let minimumHoverDuration = "minimumHoverDuration"
static let showMenuBarIcon = "showMenuBarIcon"
static let launchAtLogin = "launchAtLogin"
// Sizing closed state
static let notchHeight = "notchHeight"
static let nonNotchHeight = "nonNotchHeight"
static let notchHeightMode = "notchHeightMode"
static let nonNotchHeightMode = "nonNotchHeightMode"
// Sizing open state
static let openWidth = "openWidth"
static let openHeight = "openHeight"
// Appearance
static let enableShadow = "enableShadow"
static let shadowRadius = "shadowRadius"
static let shadowOpacity = "shadowOpacity"
static let cornerRadiusScaling = "cornerRadiusScaling"
static let notchOpacity = "notchOpacity"
static let blurRadius = "blurRadius"
// Animation
static let openSpringResponse = "openSpringResponse"
static let openSpringDamping = "openSpringDamping"
static let closeSpringResponse = "closeSpringResponse"
static let closeSpringDamping = "closeSpringDamping"
static let hoverSpringResponse = "hoverSpringResponse"
static let hoverSpringDamping = "hoverSpringDamping"
static let resizeAnimationDuration = "resizeAnimationDuration"
// Behavior
static let enableGestures = "enableGestures"
static let gestureSensitivity = "gestureSensitivity"
// Terminal
static let terminalFontSize = "terminalFontSize"
static let terminalShell = "terminalShell"
static let terminalTheme = "terminalTheme"
static let terminalSizePresets = "terminalSizePresets"
static let workspaceSummaries = "workspaceSummaries"
static let screenAssignments = "screenAssignments"
// Hotkeys each stores a HotkeyBinding JSON string
static let hotkeyToggle = "hotkey_toggle"
static let hotkeyNewTab = "hotkey_newTab"
static let hotkeyCloseTab = "hotkey_closeTab"
static let hotkeyNextTab = "hotkey_nextTab"
static let hotkeyPreviousTab = "hotkey_previousTab"
static let hotkeyDetachTab = "hotkey_detachTab"
}
enum Defaults {
static let showOnAllDisplays: Bool = true
static let openNotchOnHover: Bool = true
static let minimumHoverDuration: Double = 0.3
static let showMenuBarIcon: Bool = true
static let launchAtLogin: Bool = false
static let notchHeight: Double = 32
static let nonNotchHeight: Double = 32
static let notchHeightMode: Int = 0
static let nonNotchHeightMode: Int = 1
static let openWidth: Double = 640
static let openHeight: Double = 350
static let enableShadow: Bool = true
static let shadowRadius: Double = 6
static let shadowOpacity: Double = 0.5
static let cornerRadiusScaling: Bool = true
static let notchOpacity: Double = 1.0
static let blurRadius: Double = 0
static let openSpringResponse: Double = 0.42
static let openSpringDamping: Double = 0.8
static let closeSpringResponse: Double = 0.45
static let closeSpringDamping: Double = 1.0
static let hoverSpringResponse: Double = 0.38
static let hoverSpringDamping: Double = 0.8
static let resizeAnimationDuration: Double = 0.42
static let enableGestures: Bool = true
static let gestureSensitivity: Double = 0.5
static let terminalFontSize: Double = 13
static let terminalShell: String = ""
static let terminalTheme: String = TerminalTheme.terminalApp.rawValue
static let terminalSizePresets: String = TerminalSizePresetStore.defaultPresetsJSON()
// Default hotkey bindings as JSON
static let hotkeyToggle: String = HotkeyBinding.cmdReturn.toJSON()
static let hotkeyNewTab: String = HotkeyBinding.cmdT.toJSON()
static let hotkeyCloseTab: String = HotkeyBinding.cmdW.toJSON()
static let hotkeyNextTab: String = HotkeyBinding.cmdShiftRB.toJSON()
static let hotkeyPreviousTab: String = HotkeyBinding.cmdShiftLB.toJSON()
static let hotkeyDetachTab: String = HotkeyBinding.cmdD.toJSON()
}
static func registerDefaults() {
UserDefaults.standard.register(defaults: [
Keys.showOnAllDisplays: Defaults.showOnAllDisplays,
Keys.openNotchOnHover: Defaults.openNotchOnHover,
Keys.minimumHoverDuration: Defaults.minimumHoverDuration,
Keys.showMenuBarIcon: Defaults.showMenuBarIcon,
Keys.launchAtLogin: Defaults.launchAtLogin,
Keys.notchHeight: Defaults.notchHeight,
Keys.nonNotchHeight: Defaults.nonNotchHeight,
Keys.notchHeightMode: Defaults.notchHeightMode,
Keys.nonNotchHeightMode: Defaults.nonNotchHeightMode,
Keys.openWidth: Defaults.openWidth,
Keys.openHeight: Defaults.openHeight,
Keys.enableShadow: Defaults.enableShadow,
Keys.shadowRadius: Defaults.shadowRadius,
Keys.shadowOpacity: Defaults.shadowOpacity,
Keys.cornerRadiusScaling: Defaults.cornerRadiusScaling,
Keys.notchOpacity: Defaults.notchOpacity,
Keys.blurRadius: Defaults.blurRadius,
Keys.openSpringResponse: Defaults.openSpringResponse,
Keys.openSpringDamping: Defaults.openSpringDamping,
Keys.closeSpringResponse: Defaults.closeSpringResponse,
Keys.closeSpringDamping: Defaults.closeSpringDamping,
Keys.hoverSpringResponse: Defaults.hoverSpringResponse,
Keys.hoverSpringDamping: Defaults.hoverSpringDamping,
Keys.resizeAnimationDuration: Defaults.resizeAnimationDuration,
Keys.enableGestures: Defaults.enableGestures,
Keys.gestureSensitivity: Defaults.gestureSensitivity,
Keys.terminalFontSize: Defaults.terminalFontSize,
Keys.terminalShell: Defaults.terminalShell,
Keys.terminalTheme: Defaults.terminalTheme,
Keys.terminalSizePresets: Defaults.terminalSizePresets,
Keys.hotkeyToggle: Defaults.hotkeyToggle,
Keys.hotkeyNewTab: Defaults.hotkeyNewTab,
Keys.hotkeyCloseTab: Defaults.hotkeyCloseTab,
Keys.hotkeyNextTab: Defaults.hotkeyNextTab,
Keys.hotkeyPreviousTab: Defaults.hotkeyPreviousTab,
Keys.hotkeyDetachTab: Defaults.hotkeyDetachTab,
])
}
}
enum NotchHeightMode: Int, CaseIterable, Identifiable {
case matchRealNotchSize = 0
case matchMenuBar = 1
case custom = 2
var id: Int { rawValue }
var label: String {
switch self {
case .matchRealNotchSize: return "Match Notch"
case .matchMenuBar: return "Match Menu Bar"
case .custom: return "Custom"
}
}
}
enum NonNotchHeightMode: Int, CaseIterable, Identifiable {
case matchMenuBar = 1
case custom = 2
var id: Int { rawValue }
var label: String {
switch self {
case .matchMenuBar: return "Match Menu Bar"
case .custom: return "Custom"
}
}
}
struct TerminalSizePreset: Codable, Equatable, Identifiable {
var id: UUID
var name: String
var width: Double
var height: Double
var hotkey: HotkeyBinding?
init(
id: UUID = UUID(),
name: String,
width: Double,
height: Double,
hotkey: HotkeyBinding? = nil
) {
self.id = id
self.name = name
self.width = width
self.height = height
self.hotkey = hotkey
}
var size: CGSize {
CGSize(width: width, height: height)
}
}
enum TerminalSizePresetStore {
static func load() -> [TerminalSizePreset] {
let defaults = UserDefaults.standard
guard let json = defaults.string(forKey: NotchSettings.Keys.terminalSizePresets),
let presets = decodePresets(from: json) else {
return defaultPresets()
}
return presets
}
static func save(_ presets: [TerminalSizePreset]) {
UserDefaults.standard.set(encodePresets(presets), forKey: NotchSettings.Keys.terminalSizePresets)
}
static func reset() {
save(defaultPresets())
}
static func loadDefaults() -> [TerminalSizePreset] {
defaultPresets()
}
static func defaultPresetsJSON() -> String {
encodePresets(defaultPresets())
}
static func suggestedHotkey(for presets: [TerminalSizePreset]) -> HotkeyBinding? {
let used = Set(presets.compactMap(\.hotkey))
for digit in 1...9 {
guard let candidate = HotkeyBinding.cmdShiftDigit(digit) else { continue }
if !used.contains(candidate) {
return candidate
}
}
return nil
}
private static func defaultPresets() -> [TerminalSizePreset] {
[
TerminalSizePreset(name: "Compact", width: 480, height: 220, hotkey: HotkeyBinding.cmdShiftDigit(1)),
TerminalSizePreset(name: "Default", width: 640, height: 350, hotkey: HotkeyBinding.cmdShiftDigit(2)),
TerminalSizePreset(name: "Large", width: 900, height: 500, hotkey: HotkeyBinding.cmdShiftDigit(3)),
]
}
static func decodePresets(from json: String) -> [TerminalSizePreset]? {
guard let data = json.data(using: .utf8) else { return nil }
return try? JSONDecoder().decode([TerminalSizePreset].self, from: data)
}
static func encodePresets(_ presets: [TerminalSizePreset]) -> String {
guard let data = try? JSONEncoder().encode(presets),
let json = String(data: data, encoding: .utf8) else {
return "[]"
}
return json
}
}