@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.
- package/README.md +3 -0
- package/app/Info.plist +2 -2
- package/app/Lattices.app/Contents/Info.plist +2 -2
- package/app/Lattices.app/Contents/MacOS/Lattices +0 -0
- package/app/Package.swift +6 -0
- package/app/Sources/AppShell/App.swift +20 -0
- package/app/Sources/{AppDelegate.swift → AppShell/AppDelegate.swift} +94 -34
- package/app/Sources/{AppShellView.swift → AppShell/AppShellView.swift} +12 -1
- package/app/Sources/AppShell/AppUpdater.swift +92 -0
- package/app/Sources/AppShell/CliActionLauncher.swift +50 -0
- package/app/Sources/{HomeDashboardView.swift → AppShell/HomeDashboardView.swift} +18 -10
- package/app/Sources/AppShell/LatticesRuntime.swift +61 -0
- package/app/Sources/{MainView.swift → AppShell/MainView.swift} +351 -191
- package/app/Sources/{OnboardingView.swift → AppShell/OnboardingView.swift} +30 -16
- package/app/Sources/{Preferences.swift → AppShell/Preferences.swift} +78 -0
- package/app/Sources/{SettingsView.swift → AppShell/SettingsView.swift} +869 -152
- package/app/Sources/{HotkeyStore.swift → Core/Actions/HotkeyStore.swift} +9 -5
- package/app/Sources/{IntentEngine.swift → Core/Actions/IntentEngine.swift} +51 -27
- package/app/Sources/Core/Actions/IntentSchema.swift +94 -0
- package/app/Sources/{Intents → Core/Actions/Intents}/LatticeIntent.swift +0 -25
- package/app/Sources/{PaletteCommand.swift → Core/Actions/PaletteCommand.swift} +26 -6
- package/app/Sources/{VoiceIntentResolver.swift → Core/Actions/VoiceIntentResolver.swift} +46 -4
- package/app/Sources/Core/Companion/CompanionActivityLog.swift +70 -0
- package/app/Sources/Core/Companion/CompanionKeyboardController.swift +141 -0
- package/app/Sources/Core/Companion/LatticesCompanionBridgeServer.swift +438 -0
- package/app/Sources/Core/Companion/LatticesCompanionCockpit.swift +555 -0
- package/app/Sources/Core/Companion/LatticesCompanionSecurityCoordinator.swift +594 -0
- package/app/Sources/Core/Companion/LatticesCompanionTrackpadController.swift +204 -0
- package/app/Sources/Core/Companion/LatticesDeckHost.swift +1463 -0
- package/app/Sources/{LatticesApi.swift → Core/Daemon/LatticesApi.swift} +125 -4
- package/app/Sources/{AppTypeClassifier.swift → Core/Desktop/AppTypeClassifier.swift} +36 -0
- package/app/Sources/{DesktopModel.swift → Core/Desktop/DesktopModel.swift} +6 -8
- package/app/Sources/Core/Desktop/MouseFinder.swift +527 -0
- package/app/Sources/Core/Desktop/SessionWindowLocator.swift +139 -0
- package/app/Sources/Core/Desktop/WindowDragSnapController.swift +628 -0
- package/app/Sources/Core/Desktop/WindowPreviewCard.swift +100 -0
- package/app/Sources/Core/Desktop/WindowPreviewStore.swift +113 -0
- package/app/Sources/Core/Desktop/WindowSelectionStore.swift +76 -0
- package/app/Sources/{WindowTiler.swift → Core/Desktop/WindowTiler.swift} +351 -172
- package/app/Sources/Core/Input/MouseGestureConfig.swift +364 -0
- package/app/Sources/Core/Input/MouseGestureController.swift +1203 -0
- package/app/Sources/Core/Input/MouseInputDeviceStore.swift +98 -0
- package/app/Sources/Core/Input/MouseInputEventViewer.swift +272 -0
- package/app/Sources/Core/Input/MouseShortcutStore.swift +107 -0
- package/app/Sources/{CommandModeState.swift → Core/Overlays/CommandMode/CommandModeState.swift} +127 -24
- package/app/Sources/{CommandModeView.swift → Core/Overlays/CommandMode/CommandModeView.swift} +492 -79
- package/app/Sources/Core/Overlays/CommandPalette/CommandPaletteWindow.swift +67 -0
- package/app/Sources/{CheatSheetHUD.swift → Core/Overlays/HUD/CheatSheetHUD.swift} +1 -0
- package/app/Sources/{HUDRightBar.swift → Core/Overlays/HUD/HUDRightBar.swift} +23 -201
- package/app/Sources/{LauncherHUD.swift → Core/Overlays/HUD/LauncherHUD.swift} +12 -26
- package/app/Sources/{OmniSearchView.swift → Core/Overlays/OmniSearch/OmniSearchView.swift} +136 -2
- package/app/Sources/{OmniSearchWindow.swift → Core/Overlays/OmniSearch/OmniSearchWindow.swift} +21 -32
- package/app/Sources/Core/Overlays/OverlayPanelShell.swift +241 -0
- package/app/Sources/{ScreenMapState.swift → Core/Overlays/ScreenMap/ScreenMapState.swift} +116 -32
- package/app/Sources/{ScreenMapView.swift → Core/Overlays/ScreenMap/ScreenMapView.swift} +510 -524
- package/app/Sources/{ScreenMapWindowController.swift → Core/Overlays/ScreenMap/ScreenMapWindowController.swift} +12 -4
- package/app/Sources/{VoiceCommandWindow.swift → Core/Overlays/Voice/VoiceCommandWindow.swift} +46 -53
- package/app/Sources/Core/Pi/PiAuthNextStepCard.swift +148 -0
- package/app/Sources/Core/Pi/PiAuthPromptCard.swift +90 -0
- package/app/Sources/{PiChatDock.swift → Core/Pi/PiChatDock.swift} +137 -74
- package/app/Sources/{PiChatSession.swift → Core/Pi/PiChatSession.swift} +608 -108
- package/app/Sources/Core/Pi/PiInstallCallout.swift +86 -0
- package/app/Sources/Core/Pi/PiProviderSetupCallout.swift +99 -0
- package/app/Sources/{PiWorkspaceView.swift → Core/Pi/PiWorkspaceView.swift} +174 -77
- package/app/Sources/{PermissionChecker.swift → Core/System/PermissionChecker.swift} +76 -2
- package/app/Sources/Core/System/SystemTelemetryMonitor.swift +273 -0
- package/app/Sources/{HandsOffSession.swift → Core/Voice/HandsOffSession.swift} +15 -4
- package/app/Sources/{WorkspaceManager.swift → Core/Workspace/WorkspaceManager.swift} +288 -0
- package/bin/assistant-intelligence.ts +874 -0
- package/bin/handsoff-infer.ts +16 -209
- package/bin/handsoff-worker.ts +45 -258
- package/bin/lattices-app.ts +62 -0
- package/bin/lattices-dev +4 -0
- package/bin/lattices.ts +125 -14
- package/docs/agents.md +14 -0
- package/docs/api.md +55 -0
- package/docs/app.md +3 -0
- package/docs/companion-deck.md +180 -0
- package/docs/component-extraction-roadmap.md +392 -0
- package/docs/config.md +25 -0
- package/docs/tiling-reference.md +55 -0
- package/docs/voice-error-model.md +73 -0
- package/package.json +4 -1
- package/app/Sources/App.swift +0 -10
- package/app/Sources/CommandPaletteWindow.swift +0 -134
- package/app/Sources/MouseFinder.swift +0 -222
- /package/app/Sources/{KeyRecorderView.swift → AppShell/KeyRecorderView.swift} +0 -0
- /package/app/Sources/{MainWindow.swift → AppShell/MainWindow.swift} +0 -0
- /package/app/Sources/{SettingsWindow.swift → AppShell/SettingsWindow.swift} +0 -0
- /package/app/Sources/{HotkeyManager.swift → Core/Actions/HotkeyManager.swift} +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/CreateLayerIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/DistributeIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/FocusIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/HelpIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/KillIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/LaunchIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/ListSessionsIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/ListWindowsIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/ScanIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/SearchIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/SwitchLayerIntent.swift +0 -0
- /package/app/Sources/{Intents → Core/Actions/Intents}/TileIntent.swift +0 -0
- /package/app/Sources/{DaemonProtocol.swift → Core/Daemon/DaemonProtocol.swift} +0 -0
- /package/app/Sources/{DaemonServer.swift → Core/Daemon/DaemonServer.swift} +0 -0
- /package/app/Sources/{AccessibilityTextExtractor.swift → Core/Desktop/AccessibilityTextExtractor.swift} +0 -0
- /package/app/Sources/{DesktopModelTypes.swift → Core/Desktop/DesktopModelTypes.swift} +0 -0
- /package/app/Sources/{InventoryManager.swift → Core/Desktop/InventoryManager.swift} +0 -0
- /package/app/Sources/{InventoryPath.swift → Core/Desktop/InventoryPath.swift} +0 -0
- /package/app/Sources/{OcrModel.swift → Core/Desktop/OcrModel.swift} +0 -0
- /package/app/Sources/{OcrStore.swift → Core/Desktop/OcrStore.swift} +0 -0
- /package/app/Sources/{PlacementSpec.swift → Core/Desktop/PlacementSpec.swift} +0 -0
- /package/app/Sources/{TilePickerView.swift → Core/Desktop/TilePickerView.swift} +0 -0
- /package/app/Sources/{AppWindowShell.swift → Core/Overlays/AppWindowShell.swift} +0 -0
- /package/app/Sources/{CommandModeWindow.swift → Core/Overlays/CommandMode/CommandModeWindow.swift} +0 -0
- /package/app/Sources/{CommandPaletteView.swift → Core/Overlays/CommandPalette/CommandPaletteView.swift} +0 -0
- /package/app/Sources/{HUDBottomBar.swift → Core/Overlays/HUD/HUDBottomBar.swift} +0 -0
- /package/app/Sources/{HUDController.swift → Core/Overlays/HUD/HUDController.swift} +0 -0
- /package/app/Sources/{HUDLeftBar.swift → Core/Overlays/HUD/HUDLeftBar.swift} +0 -0
- /package/app/Sources/{HUDMinimap.swift → Core/Overlays/HUD/HUDMinimap.swift} +0 -0
- /package/app/Sources/{HUDState.swift → Core/Overlays/HUD/HUDState.swift} +0 -0
- /package/app/Sources/{HUDTopBar.swift → Core/Overlays/HUD/HUDTopBar.swift} +0 -0
- /package/app/Sources/{LayerBezel.swift → Core/Overlays/HUD/LayerBezel.swift} +0 -0
- /package/app/Sources/{OmniSearchState.swift → Core/Overlays/OmniSearch/OmniSearchState.swift} +0 -0
- /package/app/Sources/{DiagnosticLog.swift → Core/System/DiagnosticLog.swift} +0 -0
- /package/app/Sources/{EventBus.swift → Core/System/EventBus.swift} +0 -0
- /package/app/Sources/{ProcessModel.swift → Core/System/ProcessModel.swift} +0 -0
- /package/app/Sources/{ProcessQuery.swift → Core/System/ProcessQuery.swift} +0 -0
- /package/app/Sources/{AdvisorLearningStore.swift → Core/Voice/AdvisorLearningStore.swift} +0 -0
- /package/app/Sources/{AgentSession.swift → Core/Voice/AgentSession.swift} +0 -0
- /package/app/Sources/{AudioProvider.swift → Core/Voice/AudioProvider.swift} +0 -0
- /package/app/Sources/{VoiceChatView.swift → Core/Voice/VoiceChatView.swift} +0 -0
- /package/app/Sources/{VoxClient.swift → Core/Voice/VoxClient.swift} +0 -0
- /package/app/Sources/{Project.swift → Core/Workspace/Project.swift} +0 -0
- /package/app/Sources/{ProjectScanner.swift → Core/Workspace/ProjectScanner.swift} +0 -0
- /package/app/Sources/{SessionLayerStore.swift → Core/Workspace/SessionLayerStore.swift} +0 -0
- /package/app/Sources/{SessionManager.swift → Core/Workspace/SessionManager.swift} +0 -0
- /package/app/Sources/{Terminal.swift → Core/Workspace/Terminal/Terminal.swift} +0 -0
- /package/app/Sources/{TerminalQuery.swift → Core/Workspace/Terminal/TerminalQuery.swift} +0 -0
- /package/app/Sources/{TerminalSynthesizer.swift → Core/Workspace/Terminal/TerminalSynthesizer.swift} +0 -0
- /package/app/Sources/{TmuxModel.swift → Core/Workspace/Tmux/TmuxModel.swift} +0 -0
- /package/app/Sources/{TmuxQuery.swift → Core/Workspace/Tmux/TmuxQuery.swift} +0 -0
- /package/app/Sources/{ActionRow.swift → UI/ActionRow.swift} +0 -0
- /package/app/Sources/{OrphanRow.swift → UI/OrphanRow.swift} +0 -0
- /package/app/Sources/{ProjectRow.swift → UI/ProjectRow.swift} +0 -0
- /package/app/Sources/{TabGroupRow.swift → UI/TabGroupRow.swift} +0 -0
- /package/app/Sources/{Theme.swift → UI/Theme.swift} +0 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import SwiftUI
|
|
3
|
+
|
|
4
|
+
final class OverlayPanel: NSPanel {
|
|
5
|
+
var activatesOnMouseDown = false
|
|
6
|
+
var onKeyDown: ((NSEvent) -> Void)?
|
|
7
|
+
var onFlagsChanged: ((NSEvent) -> Void)?
|
|
8
|
+
|
|
9
|
+
override var canBecomeKey: Bool { true }
|
|
10
|
+
override var canBecomeMain: Bool { true }
|
|
11
|
+
|
|
12
|
+
override func sendEvent(_ event: NSEvent) {
|
|
13
|
+
if activatesOnMouseDown,
|
|
14
|
+
event.type == .leftMouseDown || event.type == .rightMouseDown {
|
|
15
|
+
if !NSApp.isActive {
|
|
16
|
+
NSApp.activate(ignoringOtherApps: true)
|
|
17
|
+
}
|
|
18
|
+
if !isKeyWindow {
|
|
19
|
+
makeKey()
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
super.sendEvent(event)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
override func keyDown(with event: NSEvent) {
|
|
26
|
+
if let onKeyDown {
|
|
27
|
+
onKeyDown(event)
|
|
28
|
+
} else {
|
|
29
|
+
super.keyDown(with: event)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
override func flagsChanged(with event: NSEvent) {
|
|
34
|
+
if let onFlagsChanged {
|
|
35
|
+
onFlagsChanged(event)
|
|
36
|
+
} else {
|
|
37
|
+
super.flagsChanged(with: event)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private final class OverlayHostingView<Content: View>: NSHostingView<Content> {
|
|
43
|
+
override func acceptsFirstMouse(for event: NSEvent?) -> Bool { true }
|
|
44
|
+
override var focusRingType: NSFocusRingType { get { .none } set {} }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
struct OverlayPanelShell {
|
|
48
|
+
enum Background {
|
|
49
|
+
case clear
|
|
50
|
+
case solid(NSColor)
|
|
51
|
+
case material(NSVisualEffectView.Material)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
enum Placement {
|
|
55
|
+
case centered(yOffsetRatio: CGFloat = 0)
|
|
56
|
+
case mouseScreenCentered(yOffsetRatio: CGFloat = 0)
|
|
57
|
+
case topCenter(margin: CGFloat = 40)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
struct Config {
|
|
61
|
+
var size: NSSize
|
|
62
|
+
var styleMask: NSWindow.StyleMask = [.nonactivatingPanel]
|
|
63
|
+
var title: String = ""
|
|
64
|
+
var titleVisible: NSWindow.TitleVisibility = .hidden
|
|
65
|
+
var titlebarAppearsTransparent = false
|
|
66
|
+
var background: Background = .clear
|
|
67
|
+
var cornerRadius: CGFloat? = nil
|
|
68
|
+
var level: NSWindow.Level = .floating
|
|
69
|
+
var hasShadow = true
|
|
70
|
+
var hidesOnDeactivate = false
|
|
71
|
+
var isReleasedWhenClosed = false
|
|
72
|
+
var isMovableByWindowBackground = false
|
|
73
|
+
var collectionBehavior: NSWindow.CollectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
|
|
74
|
+
var minSize: NSSize? = nil
|
|
75
|
+
var maxSize: NSSize? = nil
|
|
76
|
+
var activatesOnMouseDown = false
|
|
77
|
+
var onKeyDown: ((NSEvent) -> Void)? = nil
|
|
78
|
+
var onFlagsChanged: ((NSEvent) -> Void)? = nil
|
|
79
|
+
var appearance: NSAppearance? = NSAppearance(named: .darkAqua)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
static func makePanel<Content: View>(config: Config, rootView: Content) -> OverlayPanel {
|
|
83
|
+
let hosting = OverlayHostingView(rootView: rootView)
|
|
84
|
+
hosting.translatesAutoresizingMaskIntoConstraints = false
|
|
85
|
+
|
|
86
|
+
let panel = OverlayPanel(
|
|
87
|
+
contentRect: NSRect(origin: .zero, size: config.size),
|
|
88
|
+
styleMask: config.styleMask,
|
|
89
|
+
backing: .buffered,
|
|
90
|
+
defer: false
|
|
91
|
+
)
|
|
92
|
+
panel.title = config.title
|
|
93
|
+
panel.titleVisibility = config.titleVisible
|
|
94
|
+
panel.titlebarAppearsTransparent = config.titlebarAppearsTransparent
|
|
95
|
+
panel.isOpaque = false
|
|
96
|
+
panel.backgroundColor = backgroundColor(for: config.background)
|
|
97
|
+
panel.level = config.level
|
|
98
|
+
panel.hasShadow = config.hasShadow
|
|
99
|
+
panel.hidesOnDeactivate = config.hidesOnDeactivate
|
|
100
|
+
panel.isReleasedWhenClosed = config.isReleasedWhenClosed
|
|
101
|
+
panel.isMovableByWindowBackground = config.isMovableByWindowBackground
|
|
102
|
+
panel.collectionBehavior = config.collectionBehavior
|
|
103
|
+
panel.activatesOnMouseDown = config.activatesOnMouseDown
|
|
104
|
+
panel.onKeyDown = config.onKeyDown
|
|
105
|
+
panel.onFlagsChanged = config.onFlagsChanged
|
|
106
|
+
if let minSize = config.minSize {
|
|
107
|
+
panel.minSize = minSize
|
|
108
|
+
}
|
|
109
|
+
if let maxSize = config.maxSize {
|
|
110
|
+
panel.maxSize = maxSize
|
|
111
|
+
}
|
|
112
|
+
if let appearance = config.appearance {
|
|
113
|
+
panel.appearance = appearance
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
install(hosting: hosting, on: panel, background: config.background, cornerRadius: config.cornerRadius)
|
|
117
|
+
return panel
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
static func position(_ window: NSWindow, placement: Placement) {
|
|
121
|
+
let screen: NSScreen
|
|
122
|
+
switch placement {
|
|
123
|
+
case .mouseScreenCentered, .topCenter:
|
|
124
|
+
screen = mouseScreen()
|
|
125
|
+
case .centered:
|
|
126
|
+
screen = NSScreen.main ?? mouseScreen()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let visibleFrame = screen.visibleFrame
|
|
130
|
+
let size = window.frame.size
|
|
131
|
+
let origin: NSPoint
|
|
132
|
+
|
|
133
|
+
switch placement {
|
|
134
|
+
case .centered(let yOffsetRatio), .mouseScreenCentered(let yOffsetRatio):
|
|
135
|
+
origin = NSPoint(
|
|
136
|
+
x: visibleFrame.midX - size.width / 2,
|
|
137
|
+
y: visibleFrame.midY - size.height / 2 + (visibleFrame.height * yOffsetRatio)
|
|
138
|
+
)
|
|
139
|
+
case .topCenter(let margin):
|
|
140
|
+
origin = NSPoint(
|
|
141
|
+
x: visibleFrame.midX - size.width / 2,
|
|
142
|
+
y: visibleFrame.maxY - size.height - margin
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
window.setFrameOrigin(origin)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
static func present(
|
|
150
|
+
_ panel: NSPanel,
|
|
151
|
+
activate: Bool = true,
|
|
152
|
+
makeKey: Bool = true,
|
|
153
|
+
orderFrontRegardless: Bool = false
|
|
154
|
+
) {
|
|
155
|
+
if orderFrontRegardless {
|
|
156
|
+
panel.orderFrontRegardless()
|
|
157
|
+
} else if makeKey {
|
|
158
|
+
panel.makeKeyAndOrderFront(nil)
|
|
159
|
+
} else {
|
|
160
|
+
panel.orderFront(nil)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if makeKey {
|
|
164
|
+
panel.makeKey()
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if activate {
|
|
168
|
+
NSApp.activate(ignoringOtherApps: true)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private static func install(
|
|
173
|
+
hosting: NSView,
|
|
174
|
+
on panel: NSPanel,
|
|
175
|
+
background: Background,
|
|
176
|
+
cornerRadius: CGFloat?
|
|
177
|
+
) {
|
|
178
|
+
switch background {
|
|
179
|
+
case .material(let material):
|
|
180
|
+
let effectView = NSVisualEffectView()
|
|
181
|
+
effectView.blendingMode = .behindWindow
|
|
182
|
+
effectView.material = material
|
|
183
|
+
effectView.state = .active
|
|
184
|
+
effectView.wantsLayer = true
|
|
185
|
+
if let cornerRadius {
|
|
186
|
+
effectView.maskImage = maskImage(cornerRadius: cornerRadius)
|
|
187
|
+
}
|
|
188
|
+
panel.contentView = effectView
|
|
189
|
+
pin(hosting: hosting, to: effectView)
|
|
190
|
+
case .clear, .solid:
|
|
191
|
+
panel.contentView = hosting
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private static func pin(hosting: NSView, to container: NSView) {
|
|
196
|
+
container.addSubview(hosting)
|
|
197
|
+
NSLayoutConstraint.activate([
|
|
198
|
+
hosting.leadingAnchor.constraint(equalTo: container.leadingAnchor),
|
|
199
|
+
hosting.trailingAnchor.constraint(equalTo: container.trailingAnchor),
|
|
200
|
+
hosting.topAnchor.constraint(equalTo: container.topAnchor),
|
|
201
|
+
hosting.bottomAnchor.constraint(equalTo: container.bottomAnchor),
|
|
202
|
+
])
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private static func mouseScreen() -> NSScreen {
|
|
206
|
+
let mouseLocation = NSEvent.mouseLocation
|
|
207
|
+
return NSScreen.screens.first(where: { $0.frame.contains(mouseLocation) })
|
|
208
|
+
?? NSScreen.main
|
|
209
|
+
?? NSScreen.screens.first!
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private static func backgroundColor(for background: Background) -> NSColor {
|
|
213
|
+
switch background {
|
|
214
|
+
case .clear, .material:
|
|
215
|
+
return .clear
|
|
216
|
+
case .solid(let color):
|
|
217
|
+
return color
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private static func maskImage(cornerRadius: CGFloat) -> NSImage {
|
|
222
|
+
let edgeLength = 2.0 * cornerRadius + 1.0
|
|
223
|
+
let maskImage = NSImage(
|
|
224
|
+
size: NSSize(width: edgeLength, height: edgeLength),
|
|
225
|
+
flipped: false
|
|
226
|
+
) { rect in
|
|
227
|
+
let path = NSBezierPath(roundedRect: rect, xRadius: cornerRadius, yRadius: cornerRadius)
|
|
228
|
+
NSColor.black.set()
|
|
229
|
+
path.fill()
|
|
230
|
+
return true
|
|
231
|
+
}
|
|
232
|
+
maskImage.capInsets = NSEdgeInsets(
|
|
233
|
+
top: cornerRadius,
|
|
234
|
+
left: cornerRadius,
|
|
235
|
+
bottom: cornerRadius,
|
|
236
|
+
right: cornerRadius
|
|
237
|
+
)
|
|
238
|
+
maskImage.resizingMode = .stretch
|
|
239
|
+
return maskImage
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import AppKit
|
|
2
|
+
import Combine
|
|
2
3
|
import Foundation
|
|
3
4
|
import SwiftUI
|
|
4
5
|
|
|
@@ -251,7 +252,8 @@ final class ScreenMapEditorState: ObservableObject {
|
|
|
251
252
|
static let minZoom: CGFloat = 0.3
|
|
252
253
|
static let maxZoom: CGFloat = 5.0
|
|
253
254
|
|
|
254
|
-
|
|
255
|
+
/// `scale` is the synced effective canvas scale (fit scale × zoom).
|
|
256
|
+
var effectiveScale: CGFloat { scale }
|
|
255
257
|
|
|
256
258
|
func resetZoomPan() {
|
|
257
259
|
zoomLevel = 1.0
|
|
@@ -330,6 +332,12 @@ final class ScreenMapEditorState: ObservableObject {
|
|
|
330
332
|
effectiveLayers.count
|
|
331
333
|
}
|
|
332
334
|
|
|
335
|
+
/// Total windows in the current display scope before any layer filter is applied.
|
|
336
|
+
var scopedWindowCount: Int {
|
|
337
|
+
guard let dIdx = focusedDisplayIndex else { return windows.count }
|
|
338
|
+
return windows.filter { $0.displayIndex == dIdx }.count
|
|
339
|
+
}
|
|
340
|
+
|
|
333
341
|
/// Window count for a layer, scoped to the focused display
|
|
334
342
|
func effectiveWindowCount(for layer: Int) -> Int {
|
|
335
343
|
guard let dIdx = focusedDisplayIndex else {
|
|
@@ -338,6 +346,27 @@ final class ScreenMapEditorState: ObservableObject {
|
|
|
338
346
|
return windows.filter { $0.layer == layer && $0.displayIndex == dIdx }.count
|
|
339
347
|
}
|
|
340
348
|
|
|
349
|
+
/// Windows currently rendered in the main canvas.
|
|
350
|
+
var renderedCanvasWindows: [ScreenMapWindowEntry] {
|
|
351
|
+
focusedDisplayIndex != nil ? focusedVisibleWindows : visibleWindows
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
var namedEffectiveLayers: [Int] {
|
|
355
|
+
effectiveLayers.filter { layerNames[$0] != nil }
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
var unnamedEffectiveLayers: [Int] {
|
|
359
|
+
effectiveLayers.filter { layerNames[$0] == nil }
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
func layerTreeWindows(for layer: Int) -> [ScreenMapWindowEntry] {
|
|
363
|
+
var scoped = windows.filter { $0.layer == layer }
|
|
364
|
+
if let dIdx = focusedDisplayIndex {
|
|
365
|
+
scoped = scoped.filter { $0.displayIndex == dIdx }
|
|
366
|
+
}
|
|
367
|
+
return scoped.sorted { $0.zIndex < $1.zIndex }
|
|
368
|
+
}
|
|
369
|
+
|
|
341
370
|
/// Visible window count per display index
|
|
342
371
|
func visibleWindowCount(for displayIndex: Int) -> Int {
|
|
343
372
|
visibleWindows.filter { $0.displayIndex == displayIndex }.count
|
|
@@ -510,6 +539,12 @@ final class ScreenMapEditorState: ObservableObject {
|
|
|
510
539
|
return regions
|
|
511
540
|
}
|
|
512
541
|
|
|
542
|
+
func displayRegion(for displayIndex: Int) -> ScreenMapCanvasRegion? {
|
|
543
|
+
canvasExplorerRegions.first {
|
|
544
|
+
$0.kind == .display && $0.displayIndex == displayIndex
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
513
548
|
private func regionRect(for windows: [ScreenMapWindowEntry], fallback: CGRect, padding: CGFloat) -> CGRect {
|
|
514
549
|
guard !windows.isEmpty else { return fallback.insetBy(dx: -padding, dy: -padding) }
|
|
515
550
|
|
|
@@ -1350,8 +1385,12 @@ final class ScreenMapActionLog {
|
|
|
1350
1385
|
// MARK: - Screen Map Controller
|
|
1351
1386
|
|
|
1352
1387
|
final class ScreenMapController: ObservableObject {
|
|
1353
|
-
@Published var editor: ScreenMapEditorState?
|
|
1354
|
-
|
|
1388
|
+
@Published var editor: ScreenMapEditorState? {
|
|
1389
|
+
didSet { bindEditor() }
|
|
1390
|
+
}
|
|
1391
|
+
@Published var selectedWindowIds: Set<UInt32> = [] {
|
|
1392
|
+
didSet { syncSharedSelection() }
|
|
1393
|
+
}
|
|
1355
1394
|
@Published var windowSets: [ScreenMapWindowSet] = []
|
|
1356
1395
|
@Published var activeWindowSetID: UUID? = nil
|
|
1357
1396
|
@Published var flashMessage: String? = nil
|
|
@@ -1364,6 +1403,7 @@ final class ScreenMapController: ObservableObject {
|
|
|
1364
1403
|
case left, right, none
|
|
1365
1404
|
}
|
|
1366
1405
|
@Published var displayTransition: DisplayTransitionDirection = .none
|
|
1406
|
+
private var editorObserver: AnyCancellable?
|
|
1367
1407
|
|
|
1368
1408
|
var previewWindow: NSWindow? = nil
|
|
1369
1409
|
private var previewGlobalMonitor: Any? = nil
|
|
@@ -1371,6 +1411,52 @@ final class ScreenMapController: ObservableObject {
|
|
|
1371
1411
|
|
|
1372
1412
|
var onDismiss: (() -> Void)?
|
|
1373
1413
|
|
|
1414
|
+
enum DisplayFocusDirection {
|
|
1415
|
+
case previous
|
|
1416
|
+
case next
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
private func bindEditor() {
|
|
1420
|
+
editorObserver = editor?.objectWillChange.sink { [weak self] _ in
|
|
1421
|
+
self?.objectWillChange.send()
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
private func finalizeDisplayFocusChange(flashLabel: Bool) {
|
|
1426
|
+
guard let ed = editor else { return }
|
|
1427
|
+
focusViewportPreset(ed.activeViewportPreset ?? .main, flashView: false)
|
|
1428
|
+
if flashLabel {
|
|
1429
|
+
flash(ed.focusedDisplay?.label ?? "All displays")
|
|
1430
|
+
}
|
|
1431
|
+
objectWillChange.send()
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
func setDisplayFocus(_ index: Int?, flashLabel: Bool = false) {
|
|
1435
|
+
guard let ed = editor else { return }
|
|
1436
|
+
ed.focusDisplay(index)
|
|
1437
|
+
finalizeDisplayFocusChange(flashLabel: flashLabel)
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
func stepDisplayFocus(_ direction: DisplayFocusDirection, flashLabel: Bool = true) {
|
|
1441
|
+
guard let ed = editor, ed.displays.count > 1 else { return }
|
|
1442
|
+
switch direction {
|
|
1443
|
+
case .previous:
|
|
1444
|
+
ed.cyclePreviousDisplay()
|
|
1445
|
+
case .next:
|
|
1446
|
+
ed.cycleNextDisplay()
|
|
1447
|
+
}
|
|
1448
|
+
finalizeDisplayFocusChange(flashLabel: flashLabel)
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
func adjustZoom(by delta: CGFloat) {
|
|
1452
|
+
guard let ed = editor else { return }
|
|
1453
|
+
let newZoom = max(ScreenMapEditorState.minZoom, min(ScreenMapEditorState.maxZoom, ed.zoomLevel + delta))
|
|
1454
|
+
guard newZoom != ed.zoomLevel else { return }
|
|
1455
|
+
ed.activeViewportPreset = nil
|
|
1456
|
+
ed.zoomLevel = newZoom
|
|
1457
|
+
objectWillChange.send()
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1374
1460
|
// MARK: - Selection
|
|
1375
1461
|
|
|
1376
1462
|
func isSelected(_ id: UInt32) -> Bool { selectedWindowIds.contains(id) }
|
|
@@ -1400,6 +1486,28 @@ final class ScreenMapController: ObservableObject {
|
|
|
1400
1486
|
activeWindowSetID = nil
|
|
1401
1487
|
}
|
|
1402
1488
|
|
|
1489
|
+
private func syncSharedSelection() {
|
|
1490
|
+
guard !selectedWindowIds.isEmpty else {
|
|
1491
|
+
WindowSelectionStore.shared.clear(source: "screen-map")
|
|
1492
|
+
return
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
guard let editor else { return }
|
|
1496
|
+
let summaries = editor.windows
|
|
1497
|
+
.filter { selectedWindowIds.contains($0.id) }
|
|
1498
|
+
.map {
|
|
1499
|
+
SelectedWindowSummary(
|
|
1500
|
+
wid: $0.id,
|
|
1501
|
+
app: $0.app,
|
|
1502
|
+
title: $0.title,
|
|
1503
|
+
latticesSession: $0.latticesSession
|
|
1504
|
+
)
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
guard !summaries.isEmpty else { return }
|
|
1508
|
+
WindowSelectionStore.shared.setSelection(summaries, source: "screen-map")
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1403
1511
|
func selectNextWindow() {
|
|
1404
1512
|
guard let ed = editor else { return }
|
|
1405
1513
|
let wins = ed.focusedVisibleWindows.sorted(by: { $0.zIndex < $1.zIndex })
|
|
@@ -1616,7 +1724,7 @@ final class ScreenMapController: ObservableObject {
|
|
|
1616
1724
|
guard CGRectMakeWithDictionaryRepresentation(boundsDict, &rect) else { continue }
|
|
1617
1725
|
guard rect.width >= 100 && rect.height >= 50 else { continue }
|
|
1618
1726
|
let app = info[kCGWindowOwnerName as String] as? String ?? ""
|
|
1619
|
-
if app == "Lattices" || app == "lattices" || app == "
|
|
1727
|
+
if app == "Lattices" || app == "lattices" || app == "AutoFill" { continue }
|
|
1620
1728
|
let pid = info[kCGWindowOwnerPID as String] as? Int32 ?? 0
|
|
1621
1729
|
let title = info[kCGWindowName as String] as? String ?? ""
|
|
1622
1730
|
let dIdx = displayIndex(for: rect)
|
|
@@ -2052,23 +2160,11 @@ final class ScreenMapController: ObservableObject {
|
|
|
2052
2160
|
// MARK: Right hand — Navigation
|
|
2053
2161
|
|
|
2054
2162
|
case 4: // h → previous display
|
|
2055
|
-
|
|
2056
|
-
ed.cyclePreviousDisplay()
|
|
2057
|
-
focusViewportPreset(ed.activeViewportPreset ?? .main, flashView: false)
|
|
2058
|
-
let label = ed.focusedDisplay?.label ?? "All displays"
|
|
2059
|
-
flash(label)
|
|
2060
|
-
objectWillChange.send()
|
|
2061
|
-
}
|
|
2163
|
+
stepDisplayFocus(.previous)
|
|
2062
2164
|
return true
|
|
2063
2165
|
|
|
2064
2166
|
case 37: // l → next display
|
|
2065
|
-
|
|
2066
|
-
ed.cycleNextDisplay()
|
|
2067
|
-
focusViewportPreset(ed.activeViewportPreset ?? .main, flashView: false)
|
|
2068
|
-
let label = ed.focusedDisplay?.label ?? "All displays"
|
|
2069
|
-
flash(label)
|
|
2070
|
-
objectWillChange.send()
|
|
2071
|
-
}
|
|
2167
|
+
stepDisplayFocus(.next)
|
|
2072
2168
|
return true
|
|
2073
2169
|
|
|
2074
2170
|
case 38: // j → next layer
|
|
@@ -2213,23 +2309,11 @@ final class ScreenMapController: ObservableObject {
|
|
|
2213
2309
|
return true
|
|
2214
2310
|
|
|
2215
2311
|
case 123: // ← previous display (secondary)
|
|
2216
|
-
|
|
2217
|
-
ed.cyclePreviousDisplay()
|
|
2218
|
-
focusViewportPreset(ed.activeViewportPreset ?? .main, flashView: false)
|
|
2219
|
-
let label = ed.focusedDisplay?.label ?? "All displays"
|
|
2220
|
-
flash(label)
|
|
2221
|
-
objectWillChange.send()
|
|
2222
|
-
}
|
|
2312
|
+
stepDisplayFocus(.previous)
|
|
2223
2313
|
return true
|
|
2224
2314
|
|
|
2225
2315
|
case 124: // → next display (secondary)
|
|
2226
|
-
|
|
2227
|
-
ed.cycleNextDisplay()
|
|
2228
|
-
focusViewportPreset(ed.activeViewportPreset ?? .main, flashView: false)
|
|
2229
|
-
let label = ed.focusedDisplay?.label ?? "All displays"
|
|
2230
|
-
flash(label)
|
|
2231
|
-
objectWillChange.send()
|
|
2232
|
-
}
|
|
2316
|
+
stepDisplayFocus(.next)
|
|
2233
2317
|
return true
|
|
2234
2318
|
|
|
2235
2319
|
case 44: // / → open window search
|