@lattices/cli 0.4.2 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/README.md +3 -0
  2. package/app/Info.plist +2 -2
  3. package/app/Lattices.app/Contents/Info.plist +2 -2
  4. package/app/Lattices.app/Contents/MacOS/Lattices +0 -0
  5. package/app/Package.swift +6 -0
  6. package/app/Sources/AppShell/App.swift +20 -0
  7. package/app/Sources/{AppDelegate.swift → AppShell/AppDelegate.swift} +94 -34
  8. package/app/Sources/{AppShellView.swift → AppShell/AppShellView.swift} +12 -1
  9. package/app/Sources/AppShell/AppUpdater.swift +92 -0
  10. package/app/Sources/AppShell/CliActionLauncher.swift +50 -0
  11. package/app/Sources/{HomeDashboardView.swift → AppShell/HomeDashboardView.swift} +18 -10
  12. package/app/Sources/AppShell/LatticesRuntime.swift +61 -0
  13. package/app/Sources/{MainView.swift → AppShell/MainView.swift} +351 -191
  14. package/app/Sources/{OnboardingView.swift → AppShell/OnboardingView.swift} +30 -16
  15. package/app/Sources/{Preferences.swift → AppShell/Preferences.swift} +78 -0
  16. package/app/Sources/{SettingsView.swift → AppShell/SettingsView.swift} +869 -152
  17. package/app/Sources/{HotkeyStore.swift → Core/Actions/HotkeyStore.swift} +9 -5
  18. package/app/Sources/{IntentEngine.swift → Core/Actions/IntentEngine.swift} +51 -27
  19. package/app/Sources/Core/Actions/IntentSchema.swift +94 -0
  20. package/app/Sources/{Intents → Core/Actions/Intents}/LatticeIntent.swift +0 -25
  21. package/app/Sources/{PaletteCommand.swift → Core/Actions/PaletteCommand.swift} +26 -6
  22. package/app/Sources/{VoiceIntentResolver.swift → Core/Actions/VoiceIntentResolver.swift} +46 -4
  23. package/app/Sources/Core/Companion/CompanionActivityLog.swift +70 -0
  24. package/app/Sources/Core/Companion/CompanionKeyboardController.swift +141 -0
  25. package/app/Sources/Core/Companion/LatticesCompanionBridgeServer.swift +438 -0
  26. package/app/Sources/Core/Companion/LatticesCompanionCockpit.swift +555 -0
  27. package/app/Sources/Core/Companion/LatticesCompanionSecurityCoordinator.swift +594 -0
  28. package/app/Sources/Core/Companion/LatticesCompanionTrackpadController.swift +204 -0
  29. package/app/Sources/Core/Companion/LatticesDeckHost.swift +1463 -0
  30. package/app/Sources/{LatticesApi.swift → Core/Daemon/LatticesApi.swift} +125 -4
  31. package/app/Sources/{AppTypeClassifier.swift → Core/Desktop/AppTypeClassifier.swift} +36 -0
  32. package/app/Sources/{DesktopModel.swift → Core/Desktop/DesktopModel.swift} +6 -8
  33. package/app/Sources/Core/Desktop/MouseFinder.swift +527 -0
  34. package/app/Sources/Core/Desktop/SessionWindowLocator.swift +139 -0
  35. package/app/Sources/Core/Desktop/WindowDragSnapController.swift +628 -0
  36. package/app/Sources/Core/Desktop/WindowPreviewCard.swift +100 -0
  37. package/app/Sources/Core/Desktop/WindowPreviewStore.swift +113 -0
  38. package/app/Sources/Core/Desktop/WindowSelectionStore.swift +76 -0
  39. package/app/Sources/{WindowTiler.swift → Core/Desktop/WindowTiler.swift} +351 -172
  40. package/app/Sources/Core/Input/MouseGestureConfig.swift +364 -0
  41. package/app/Sources/Core/Input/MouseGestureController.swift +1203 -0
  42. package/app/Sources/Core/Input/MouseInputDeviceStore.swift +98 -0
  43. package/app/Sources/Core/Input/MouseInputEventViewer.swift +272 -0
  44. package/app/Sources/Core/Input/MouseShortcutStore.swift +107 -0
  45. package/app/Sources/{CommandModeState.swift → Core/Overlays/CommandMode/CommandModeState.swift} +127 -24
  46. package/app/Sources/{CommandModeView.swift → Core/Overlays/CommandMode/CommandModeView.swift} +492 -79
  47. package/app/Sources/Core/Overlays/CommandPalette/CommandPaletteWindow.swift +67 -0
  48. package/app/Sources/{CheatSheetHUD.swift → Core/Overlays/HUD/CheatSheetHUD.swift} +1 -0
  49. package/app/Sources/{HUDRightBar.swift → Core/Overlays/HUD/HUDRightBar.swift} +23 -201
  50. package/app/Sources/{LauncherHUD.swift → Core/Overlays/HUD/LauncherHUD.swift} +12 -26
  51. package/app/Sources/{OmniSearchView.swift → Core/Overlays/OmniSearch/OmniSearchView.swift} +136 -2
  52. package/app/Sources/{OmniSearchWindow.swift → Core/Overlays/OmniSearch/OmniSearchWindow.swift} +21 -32
  53. package/app/Sources/Core/Overlays/OverlayPanelShell.swift +241 -0
  54. package/app/Sources/{ScreenMapState.swift → Core/Overlays/ScreenMap/ScreenMapState.swift} +116 -32
  55. package/app/Sources/{ScreenMapView.swift → Core/Overlays/ScreenMap/ScreenMapView.swift} +510 -524
  56. package/app/Sources/{ScreenMapWindowController.swift → Core/Overlays/ScreenMap/ScreenMapWindowController.swift} +12 -4
  57. package/app/Sources/{VoiceCommandWindow.swift → Core/Overlays/Voice/VoiceCommandWindow.swift} +46 -53
  58. package/app/Sources/Core/Pi/PiAuthNextStepCard.swift +148 -0
  59. package/app/Sources/Core/Pi/PiAuthPromptCard.swift +90 -0
  60. package/app/Sources/{PiChatDock.swift → Core/Pi/PiChatDock.swift} +137 -74
  61. package/app/Sources/{PiChatSession.swift → Core/Pi/PiChatSession.swift} +608 -108
  62. package/app/Sources/Core/Pi/PiInstallCallout.swift +86 -0
  63. package/app/Sources/Core/Pi/PiProviderSetupCallout.swift +99 -0
  64. package/app/Sources/{PiWorkspaceView.swift → Core/Pi/PiWorkspaceView.swift} +174 -77
  65. package/app/Sources/{PermissionChecker.swift → Core/System/PermissionChecker.swift} +76 -2
  66. package/app/Sources/Core/System/SystemTelemetryMonitor.swift +273 -0
  67. package/app/Sources/{HandsOffSession.swift → Core/Voice/HandsOffSession.swift} +15 -4
  68. package/app/Sources/{WorkspaceManager.swift → Core/Workspace/WorkspaceManager.swift} +288 -0
  69. package/bin/assistant-intelligence.ts +874 -0
  70. package/bin/handsoff-infer.ts +16 -209
  71. package/bin/handsoff-worker.ts +45 -258
  72. package/bin/lattices-app.ts +62 -0
  73. package/bin/lattices-dev +4 -0
  74. package/bin/lattices.ts +125 -14
  75. package/docs/agents.md +14 -0
  76. package/docs/api.md +55 -0
  77. package/docs/app.md +3 -0
  78. package/docs/companion-deck.md +180 -0
  79. package/docs/component-extraction-roadmap.md +392 -0
  80. package/docs/config.md +25 -0
  81. package/docs/tiling-reference.md +55 -0
  82. package/docs/voice-error-model.md +73 -0
  83. package/package.json +4 -1
  84. package/app/Sources/App.swift +0 -10
  85. package/app/Sources/CommandPaletteWindow.swift +0 -134
  86. package/app/Sources/MouseFinder.swift +0 -222
  87. /package/app/Sources/{KeyRecorderView.swift → AppShell/KeyRecorderView.swift} +0 -0
  88. /package/app/Sources/{MainWindow.swift → AppShell/MainWindow.swift} +0 -0
  89. /package/app/Sources/{SettingsWindow.swift → AppShell/SettingsWindow.swift} +0 -0
  90. /package/app/Sources/{HotkeyManager.swift → Core/Actions/HotkeyManager.swift} +0 -0
  91. /package/app/Sources/{Intents → Core/Actions/Intents}/CreateLayerIntent.swift +0 -0
  92. /package/app/Sources/{Intents → Core/Actions/Intents}/DistributeIntent.swift +0 -0
  93. /package/app/Sources/{Intents → Core/Actions/Intents}/FocusIntent.swift +0 -0
  94. /package/app/Sources/{Intents → Core/Actions/Intents}/HelpIntent.swift +0 -0
  95. /package/app/Sources/{Intents → Core/Actions/Intents}/KillIntent.swift +0 -0
  96. /package/app/Sources/{Intents → Core/Actions/Intents}/LaunchIntent.swift +0 -0
  97. /package/app/Sources/{Intents → Core/Actions/Intents}/ListSessionsIntent.swift +0 -0
  98. /package/app/Sources/{Intents → Core/Actions/Intents}/ListWindowsIntent.swift +0 -0
  99. /package/app/Sources/{Intents → Core/Actions/Intents}/ScanIntent.swift +0 -0
  100. /package/app/Sources/{Intents → Core/Actions/Intents}/SearchIntent.swift +0 -0
  101. /package/app/Sources/{Intents → Core/Actions/Intents}/SwitchLayerIntent.swift +0 -0
  102. /package/app/Sources/{Intents → Core/Actions/Intents}/TileIntent.swift +0 -0
  103. /package/app/Sources/{DaemonProtocol.swift → Core/Daemon/DaemonProtocol.swift} +0 -0
  104. /package/app/Sources/{DaemonServer.swift → Core/Daemon/DaemonServer.swift} +0 -0
  105. /package/app/Sources/{AccessibilityTextExtractor.swift → Core/Desktop/AccessibilityTextExtractor.swift} +0 -0
  106. /package/app/Sources/{DesktopModelTypes.swift → Core/Desktop/DesktopModelTypes.swift} +0 -0
  107. /package/app/Sources/{InventoryManager.swift → Core/Desktop/InventoryManager.swift} +0 -0
  108. /package/app/Sources/{InventoryPath.swift → Core/Desktop/InventoryPath.swift} +0 -0
  109. /package/app/Sources/{OcrModel.swift → Core/Desktop/OcrModel.swift} +0 -0
  110. /package/app/Sources/{OcrStore.swift → Core/Desktop/OcrStore.swift} +0 -0
  111. /package/app/Sources/{PlacementSpec.swift → Core/Desktop/PlacementSpec.swift} +0 -0
  112. /package/app/Sources/{TilePickerView.swift → Core/Desktop/TilePickerView.swift} +0 -0
  113. /package/app/Sources/{AppWindowShell.swift → Core/Overlays/AppWindowShell.swift} +0 -0
  114. /package/app/Sources/{CommandModeWindow.swift → Core/Overlays/CommandMode/CommandModeWindow.swift} +0 -0
  115. /package/app/Sources/{CommandPaletteView.swift → Core/Overlays/CommandPalette/CommandPaletteView.swift} +0 -0
  116. /package/app/Sources/{HUDBottomBar.swift → Core/Overlays/HUD/HUDBottomBar.swift} +0 -0
  117. /package/app/Sources/{HUDController.swift → Core/Overlays/HUD/HUDController.swift} +0 -0
  118. /package/app/Sources/{HUDLeftBar.swift → Core/Overlays/HUD/HUDLeftBar.swift} +0 -0
  119. /package/app/Sources/{HUDMinimap.swift → Core/Overlays/HUD/HUDMinimap.swift} +0 -0
  120. /package/app/Sources/{HUDState.swift → Core/Overlays/HUD/HUDState.swift} +0 -0
  121. /package/app/Sources/{HUDTopBar.swift → Core/Overlays/HUD/HUDTopBar.swift} +0 -0
  122. /package/app/Sources/{LayerBezel.swift → Core/Overlays/HUD/LayerBezel.swift} +0 -0
  123. /package/app/Sources/{OmniSearchState.swift → Core/Overlays/OmniSearch/OmniSearchState.swift} +0 -0
  124. /package/app/Sources/{DiagnosticLog.swift → Core/System/DiagnosticLog.swift} +0 -0
  125. /package/app/Sources/{EventBus.swift → Core/System/EventBus.swift} +0 -0
  126. /package/app/Sources/{ProcessModel.swift → Core/System/ProcessModel.swift} +0 -0
  127. /package/app/Sources/{ProcessQuery.swift → Core/System/ProcessQuery.swift} +0 -0
  128. /package/app/Sources/{AdvisorLearningStore.swift → Core/Voice/AdvisorLearningStore.swift} +0 -0
  129. /package/app/Sources/{AgentSession.swift → Core/Voice/AgentSession.swift} +0 -0
  130. /package/app/Sources/{AudioProvider.swift → Core/Voice/AudioProvider.swift} +0 -0
  131. /package/app/Sources/{VoiceChatView.swift → Core/Voice/VoiceChatView.swift} +0 -0
  132. /package/app/Sources/{VoxClient.swift → Core/Voice/VoxClient.swift} +0 -0
  133. /package/app/Sources/{Project.swift → Core/Workspace/Project.swift} +0 -0
  134. /package/app/Sources/{ProjectScanner.swift → Core/Workspace/ProjectScanner.swift} +0 -0
  135. /package/app/Sources/{SessionLayerStore.swift → Core/Workspace/SessionLayerStore.swift} +0 -0
  136. /package/app/Sources/{SessionManager.swift → Core/Workspace/SessionManager.swift} +0 -0
  137. /package/app/Sources/{Terminal.swift → Core/Workspace/Terminal/Terminal.swift} +0 -0
  138. /package/app/Sources/{TerminalQuery.swift → Core/Workspace/Terminal/TerminalQuery.swift} +0 -0
  139. /package/app/Sources/{TerminalSynthesizer.swift → Core/Workspace/Terminal/TerminalSynthesizer.swift} +0 -0
  140. /package/app/Sources/{TmuxModel.swift → Core/Workspace/Tmux/TmuxModel.swift} +0 -0
  141. /package/app/Sources/{TmuxQuery.swift → Core/Workspace/Tmux/TmuxQuery.swift} +0 -0
  142. /package/app/Sources/{ActionRow.swift → UI/ActionRow.swift} +0 -0
  143. /package/app/Sources/{OrphanRow.swift → UI/OrphanRow.swift} +0 -0
  144. /package/app/Sources/{ProjectRow.swift → UI/ProjectRow.swift} +0 -0
  145. /package/app/Sources/{TabGroupRow.swift → UI/TabGroupRow.swift} +0 -0
  146. /package/app/Sources/{Theme.swift → UI/Theme.swift} +0 -0
@@ -1,134 +0,0 @@
1
- import AppKit
2
- import SwiftUI
3
-
4
- /// NSPanel subclass that accepts key events even without a titlebar
5
- private class KeyablePanel: NSPanel {
6
- override var canBecomeKey: Bool { true }
7
- override var canBecomeMain: Bool { true }
8
- }
9
-
10
- final class CommandPaletteWindow {
11
- static let shared = CommandPaletteWindow()
12
-
13
- private var panel: NSPanel?
14
- private var scanner: ProjectScanner?
15
-
16
- func configure(scanner: ProjectScanner) {
17
- self.scanner = scanner
18
- }
19
-
20
- var isVisible: Bool { panel?.isVisible ?? false }
21
-
22
- func toggle() {
23
- if let p = panel, p.isVisible {
24
- dismiss()
25
- } else {
26
- show()
27
- }
28
- }
29
-
30
- func show() {
31
- // Always rebuild for fresh command state
32
- dismiss()
33
-
34
- guard let scanner = scanner else { return }
35
-
36
- // Ensure projects are up to date (full scan if list is empty,
37
- // e.g. palette opened via hotkey before main popover appeared)
38
- if scanner.projects.isEmpty {
39
- scanner.scan()
40
- } else {
41
- scanner.refreshStatus()
42
- }
43
-
44
- let commands = CommandBuilder.build(scanner: scanner)
45
- let view = CommandPaletteView(commands: commands) { [weak self] in
46
- self?.dismiss()
47
- }
48
- .preferredColorScheme(.dark)
49
-
50
- let hosting = NSHostingView(rootView: view)
51
- hosting.translatesAutoresizingMaskIntoConstraints = false
52
-
53
- let panel = KeyablePanel(
54
- contentRect: NSRect(x: 0, y: 0, width: 540, height: 440),
55
- styleMask: [.nonactivatingPanel],
56
- backing: .buffered,
57
- defer: false
58
- )
59
-
60
- panel.isOpaque = false
61
- panel.backgroundColor = .clear
62
- panel.hasShadow = true
63
- panel.level = .floating
64
- panel.isMovableByWindowBackground = true
65
- panel.hidesOnDeactivate = true
66
- panel.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
67
- panel.isReleasedWhenClosed = false
68
-
69
- // Use NSVisualEffectView as contentView with a maskImage to communicate
70
- // the rounded shape to the window server (layer.cornerRadius only clips
71
- // at the view level — the window backing store stays rectangular)
72
- let cornerRadius: CGFloat = 14
73
-
74
- let effectView = NSVisualEffectView()
75
- effectView.blendingMode = .behindWindow
76
- effectView.material = .popover
77
- effectView.state = .active
78
- effectView.wantsLayer = true
79
- effectView.maskImage = Self.maskImage(cornerRadius: cornerRadius)
80
-
81
- panel.contentView = effectView
82
-
83
- effectView.addSubview(hosting)
84
- NSLayoutConstraint.activate([
85
- hosting.leadingAnchor.constraint(equalTo: effectView.leadingAnchor),
86
- hosting.trailingAnchor.constraint(equalTo: effectView.trailingAnchor),
87
- hosting.topAnchor.constraint(equalTo: effectView.topAnchor),
88
- hosting.bottomAnchor.constraint(equalTo: effectView.bottomAnchor),
89
- ])
90
-
91
- // Center horizontally, slightly above vertical center (Spotlight-style)
92
- if let screen = NSScreen.main {
93
- let screenFrame = screen.visibleFrame
94
- let x = screenFrame.midX - 270
95
- let y = screenFrame.midY - 220 + (screenFrame.height * 0.1)
96
- panel.setFrameOrigin(NSPoint(x: x, y: y))
97
- }
98
-
99
- panel.makeKeyAndOrderFront(nil)
100
- NSApp.activate(ignoringOtherApps: true)
101
-
102
- self.panel = panel
103
- AppDelegate.updateActivationPolicy()
104
- }
105
-
106
- func dismiss() {
107
- panel?.orderOut(nil)
108
- panel = nil
109
- AppDelegate.updateActivationPolicy()
110
- }
111
-
112
- /// Stretchable mask image for rounded corners — capInsets preserve the
113
- /// corner arcs while the center stretches to any window size
114
- private static func maskImage(cornerRadius: CGFloat) -> NSImage {
115
- let edgeLength = 2.0 * cornerRadius + 1.0
116
- let maskImage = NSImage(
117
- size: NSSize(width: edgeLength, height: edgeLength),
118
- flipped: false
119
- ) { rect in
120
- let path = NSBezierPath(roundedRect: rect, xRadius: cornerRadius, yRadius: cornerRadius)
121
- NSColor.black.set()
122
- path.fill()
123
- return true
124
- }
125
- maskImage.capInsets = NSEdgeInsets(
126
- top: cornerRadius,
127
- left: cornerRadius,
128
- bottom: cornerRadius,
129
- right: cornerRadius
130
- )
131
- maskImage.resizingMode = .stretch
132
- return maskImage
133
- }
134
- }
@@ -1,222 +0,0 @@
1
- import AppKit
2
- import CoreGraphics
3
-
4
- /// Locates the mouse cursor with an animated sonar pulse overlay.
5
- /// "Find" shows rings at the current cursor position.
6
- /// "Summon" warps the cursor to screen center (or a given point).
7
- final class MouseFinder {
8
- static let shared = MouseFinder()
9
-
10
- private var overlayWindows: [NSWindow] = []
11
- private var dismissTimer: Timer?
12
- private var animationTimer: Timer?
13
- private var animationStart: CFTimeInterval = 0
14
- private let animationDuration: CFTimeInterval = 1.5
15
-
16
- // MARK: - Find (highlight current position)
17
-
18
- func find() {
19
- let pos = NSEvent.mouseLocation
20
- showSonar(at: pos)
21
- }
22
-
23
- // MARK: - Summon (warp to center of the screen the mouse is on, or a specific point)
24
-
25
- func summon(to point: CGPoint? = nil) {
26
- let target: NSPoint
27
- if let point {
28
- target = point
29
- } else {
30
- let screen = mouseScreen()
31
- let frame = screen.frame
32
- target = NSPoint(x: frame.midX, y: frame.midY)
33
- }
34
-
35
- // CGWarpMouseCursorPosition uses top-left origin
36
- let primaryHeight = NSScreen.screens.first?.frame.height ?? 0
37
- let cgPoint = CGPoint(x: target.x, y: primaryHeight - target.y)
38
- CGWarpMouseCursorPosition(cgPoint)
39
- // Re-associate mouse with cursor position after warp
40
- CGAssociateMouseAndMouseCursorPosition(1)
41
-
42
- showSonar(at: target)
43
- }
44
-
45
- // MARK: - Sonar Animation
46
-
47
- private func showSonar(at nsPoint: NSPoint) {
48
- dismiss()
49
-
50
- let screens = NSScreen.screens
51
- guard !screens.isEmpty else { return }
52
-
53
- let ringCount = 3
54
- let maxRadius: CGFloat = 120
55
- let totalSize = maxRadius * 2 + 20
56
-
57
- for screen in screens {
58
- // Only show on screens near the cursor
59
- let extendedBounds = screen.frame.insetBy(dx: -maxRadius, dy: -maxRadius)
60
- guard extendedBounds.contains(nsPoint) else { continue }
61
-
62
- let windowFrame = NSRect(
63
- x: nsPoint.x - totalSize / 2,
64
- y: nsPoint.y - totalSize / 2,
65
- width: totalSize,
66
- height: totalSize
67
- )
68
-
69
- let window = NSWindow(
70
- contentRect: windowFrame,
71
- styleMask: .borderless,
72
- backing: .buffered,
73
- defer: false
74
- )
75
- window.isOpaque = false
76
- window.backgroundColor = .clear
77
- window.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.maximumWindow)))
78
- window.hasShadow = false
79
- window.ignoresMouseEvents = true
80
- window.collectionBehavior = [.canJoinAllSpaces, .stationary]
81
-
82
- let sonarView = SonarView(
83
- frame: NSRect(origin: .zero, size: windowFrame.size),
84
- ringCount: ringCount,
85
- maxRadius: maxRadius
86
- )
87
- window.contentView = sonarView
88
-
89
- window.alphaValue = 0
90
- window.orderFrontRegardless()
91
- overlayWindows.append(window)
92
-
93
- // Fade in
94
- NSAnimationContext.runAnimationGroup { ctx in
95
- ctx.duration = 0.1
96
- window.animator().alphaValue = 1.0
97
- }
98
- }
99
-
100
- // Animate the rings expanding using CACurrentMediaTime for state
101
- animationStart = CACurrentMediaTime()
102
- let interval = 1.0 / 60.0
103
-
104
- animationTimer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { [weak self] timer in
105
- guard let self else { timer.invalidate(); return }
106
- let elapsed = CACurrentMediaTime() - self.animationStart
107
- let progress = CGFloat(min(elapsed / self.animationDuration, 1.0))
108
-
109
- for window in self.overlayWindows {
110
- (window.contentView as? SonarView)?.progress = progress
111
- window.contentView?.needsDisplay = true
112
- }
113
-
114
- if progress >= 1.0 {
115
- timer.invalidate()
116
- self.animationTimer = nil
117
- }
118
- }
119
-
120
- // Auto-dismiss after animation + hold
121
- dismissTimer = Timer.scheduledTimer(withTimeInterval: 2.5, repeats: false) { [weak self] _ in
122
- self?.fadeOut()
123
- }
124
- }
125
-
126
- private func fadeOut() {
127
- let windows = overlayWindows
128
- NSAnimationContext.runAnimationGroup({ ctx in
129
- ctx.duration = 0.4
130
- for window in windows {
131
- window.animator().alphaValue = 0
132
- }
133
- }, completionHandler: { [weak self] in
134
- self?.dismiss()
135
- })
136
- }
137
-
138
- func dismiss() {
139
- animationTimer?.invalidate()
140
- animationTimer = nil
141
- dismissTimer?.invalidate()
142
- dismissTimer = nil
143
- for window in overlayWindows {
144
- window.orderOut(nil)
145
- }
146
- overlayWindows.removeAll()
147
- }
148
-
149
- private func mouseScreen() -> NSScreen {
150
- let pos = NSEvent.mouseLocation
151
- return NSScreen.screens.first(where: { $0.frame.contains(pos) }) ?? NSScreen.screens[0]
152
- }
153
- }
154
-
155
- // MARK: - Sonar Ring View
156
-
157
- private class SonarView: NSView {
158
- let ringCount: Int
159
- let maxRadius: CGFloat
160
- var progress: CGFloat = 0
161
-
162
- init(frame: NSRect, ringCount: Int, maxRadius: CGFloat) {
163
- self.ringCount = ringCount
164
- self.maxRadius = maxRadius
165
- super.init(frame: frame)
166
- }
167
-
168
- required init?(coder: NSCoder) { fatalError() }
169
-
170
- override func draw(_ dirtyRect: NSRect) {
171
- guard let ctx = NSGraphicsContext.current?.cgContext else { return }
172
-
173
- let center = CGPoint(x: bounds.midX, y: bounds.midY)
174
-
175
- // Draw rings from outermost to innermost
176
- for i in 0..<ringCount {
177
- let ringDelay = CGFloat(i) * 0.15
178
- let denom = 1.0 - ringDelay * CGFloat(ringCount - 1) / CGFloat(ringCount)
179
- let ringProgress = max(0, min(1, (progress - ringDelay) / denom))
180
-
181
- guard ringProgress > 0 else { continue }
182
-
183
- // Ease out cubic
184
- let eased = 1.0 - pow(1.0 - ringProgress, 3)
185
-
186
- let radius = maxRadius * eased
187
- let alpha = (1.0 - eased) * 0.8
188
-
189
- // Ring stroke
190
- ctx.setStrokeColor(NSColor(calibratedRed: 0.4, green: 0.7, blue: 1.0, alpha: alpha).cgColor)
191
- ctx.setLineWidth(2.5 - CGFloat(i) * 0.5)
192
- ctx.addEllipse(in: CGRect(
193
- x: center.x - radius,
194
- y: center.y - radius,
195
- width: radius * 2,
196
- height: radius * 2
197
- ))
198
- ctx.strokePath()
199
- }
200
-
201
- // Center dot — stays visible
202
- let dotRadius: CGFloat = 6
203
- let dotAlpha = max(0.3, 1.0 - progress * 0.5)
204
- ctx.setFillColor(NSColor(calibratedRed: 0.4, green: 0.7, blue: 1.0, alpha: dotAlpha).cgColor)
205
- ctx.fillEllipse(in: CGRect(
206
- x: center.x - dotRadius,
207
- y: center.y - dotRadius,
208
- width: dotRadius * 2,
209
- height: dotRadius * 2
210
- ))
211
-
212
- // Outer glow on center dot
213
- ctx.setFillColor(NSColor(calibratedRed: 0.4, green: 0.7, blue: 1.0, alpha: dotAlpha * 0.2).cgColor)
214
- let glowRadius: CGFloat = 12
215
- ctx.fillEllipse(in: CGRect(
216
- x: center.x - glowRadius,
217
- y: center.y - glowRadius,
218
- width: glowRadius * 2,
219
- height: glowRadius * 2
220
- ))
221
- }
222
- }
File without changes