@lattices/cli 0.4.5 → 0.4.7
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/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/Sources/{AppDelegate.swift → AppShell/AppDelegate.swift} +9 -0
- package/app/Sources/{AppShellView.swift → AppShell/AppShellView.swift} +10 -1
- package/app/Sources/{KeyRecorderView.swift → AppShell/KeyRecorderView.swift} +1 -1
- package/app/Sources/{Preferences.swift → AppShell/Preferences.swift} +0 -2
- package/app/Sources/{SettingsView.swift → AppShell/SettingsView.swift} +27 -2
- package/app/Sources/{HotkeyStore.swift → Core/Actions/HotkeyStore.swift} +15 -2
- package/app/Sources/{IntentEngine.swift → Core/Actions/IntentEngine.swift} +44 -26
- package/app/Sources/Core/Actions/IntentSchema.swift +94 -0
- package/app/Sources/{Intents → Core/Actions/Intents}/LatticeIntent.swift +0 -25
- package/app/Sources/{VoiceIntentResolver.swift → Core/Actions/VoiceIntentResolver.swift} +46 -4
- package/app/Sources/{DesktopModel.swift → Core/Desktop/DesktopModel.swift} +2 -8
- package/app/Sources/Core/Desktop/SessionWindowLocator.swift +139 -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} +24 -110
- package/app/Sources/{CommandModeState.swift → Core/Overlays/CommandMode/CommandModeState.swift} +228 -24
- package/app/Sources/{CommandModeView.swift → Core/Overlays/CommandMode/CommandModeView.swift} +601 -59
- package/app/Sources/{CommandModeWindow.swift → Core/Overlays/CommandMode/CommandModeWindow.swift} +9 -5
- 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/Core/Overlays/OmniSearch/OmniSearchWindow.swift +94 -0
- package/app/Sources/Core/Overlays/OverlayPanelShell.swift +241 -0
- package/app/Sources/{ScreenMapState.swift → Core/Overlays/ScreenMap/ScreenMapState.swift} +25 -2
- package/app/Sources/{ScreenMapView.swift → Core/Overlays/ScreenMap/ScreenMapView.swift} +20 -7
- package/app/Sources/{VoiceCommandWindow.swift → Core/Overlays/Voice/VoiceCommandWindow.swift} +46 -74
- package/app/Sources/{WorkspaceManager.swift → Core/Workspace/WorkspaceManager.swift} +59 -4
- package/docs/component-extraction-roadmap.md +392 -0
- package/package.json +3 -1
- package/app/Sources/CommandPaletteWindow.swift +0 -134
- package/app/Sources/OmniSearchWindow.swift +0 -165
- /package/app/Sources/{App.swift → AppShell/App.swift} +0 -0
- /package/app/Sources/{AppUpdater.swift → AppShell/AppUpdater.swift} +0 -0
- /package/app/Sources/{CliActionLauncher.swift → AppShell/CliActionLauncher.swift} +0 -0
- /package/app/Sources/{HomeDashboardView.swift → AppShell/HomeDashboardView.swift} +0 -0
- /package/app/Sources/{LatticesRuntime.swift → AppShell/LatticesRuntime.swift} +0 -0
- /package/app/Sources/{MainView.swift → AppShell/MainView.swift} +0 -0
- /package/app/Sources/{MainWindow.swift → AppShell/MainWindow.swift} +0 -0
- /package/app/Sources/{OnboardingView.swift → AppShell/OnboardingView.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/{PaletteCommand.swift → Core/Actions/PaletteCommand.swift} +0 -0
- /package/app/Sources/{CompanionActivityLog.swift → Core/Companion/CompanionActivityLog.swift} +0 -0
- /package/app/Sources/{CompanionKeyboardController.swift → Core/Companion/CompanionKeyboardController.swift} +0 -0
- /package/app/Sources/{LatticesCompanionBridgeServer.swift → Core/Companion/LatticesCompanionBridgeServer.swift} +0 -0
- /package/app/Sources/{LatticesCompanionCockpit.swift → Core/Companion/LatticesCompanionCockpit.swift} +0 -0
- /package/app/Sources/{LatticesCompanionSecurityCoordinator.swift → Core/Companion/LatticesCompanionSecurityCoordinator.swift} +0 -0
- /package/app/Sources/{LatticesCompanionTrackpadController.swift → Core/Companion/LatticesCompanionTrackpadController.swift} +0 -0
- /package/app/Sources/{LatticesDeckHost.swift → Core/Companion/LatticesDeckHost.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/{LatticesApi.swift → Core/Daemon/LatticesApi.swift} +0 -0
- /package/app/Sources/{AccessibilityTextExtractor.swift → Core/Desktop/AccessibilityTextExtractor.swift} +0 -0
- /package/app/Sources/{AppTypeClassifier.swift → Core/Desktop/AppTypeClassifier.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/{MouseFinder.swift → Core/Desktop/MouseFinder.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/{WindowDragSnapController.swift → Core/Desktop/WindowDragSnapController.swift} +0 -0
- /package/app/Sources/{MouseGestureConfig.swift → Core/Input/MouseGestureConfig.swift} +0 -0
- /package/app/Sources/{MouseGestureController.swift → Core/Input/MouseGestureController.swift} +0 -0
- /package/app/Sources/{MouseInputDeviceStore.swift → Core/Input/MouseInputDeviceStore.swift} +0 -0
- /package/app/Sources/{MouseInputEventViewer.swift → Core/Input/MouseInputEventViewer.swift} +0 -0
- /package/app/Sources/{MouseShortcutStore.swift → Core/Input/MouseShortcutStore.swift} +0 -0
- /package/app/Sources/{AppWindowShell.swift → Core/Overlays/AppWindowShell.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/{OmniSearchView.swift → Core/Overlays/OmniSearch/OmniSearchView.swift} +0 -0
- /package/app/Sources/{ScreenMapWindowController.swift → Core/Overlays/ScreenMap/ScreenMapWindowController.swift} +0 -0
- /package/app/Sources/{PiAuthNextStepCard.swift → Core/Pi/PiAuthNextStepCard.swift} +0 -0
- /package/app/Sources/{PiAuthPromptCard.swift → Core/Pi/PiAuthPromptCard.swift} +0 -0
- /package/app/Sources/{PiChatDock.swift → Core/Pi/PiChatDock.swift} +0 -0
- /package/app/Sources/{PiChatSession.swift → Core/Pi/PiChatSession.swift} +0 -0
- /package/app/Sources/{PiInstallCallout.swift → Core/Pi/PiInstallCallout.swift} +0 -0
- /package/app/Sources/{PiProviderSetupCallout.swift → Core/Pi/PiProviderSetupCallout.swift} +0 -0
- /package/app/Sources/{PiWorkspaceView.swift → Core/Pi/PiWorkspaceView.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/{PermissionChecker.swift → Core/System/PermissionChecker.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/{SystemTelemetryMonitor.swift → Core/System/SystemTelemetryMonitor.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/{HandsOffSession.swift → Core/Voice/HandsOffSession.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
|
+
}
|
|
@@ -1388,7 +1388,9 @@ final class ScreenMapController: ObservableObject {
|
|
|
1388
1388
|
@Published var editor: ScreenMapEditorState? {
|
|
1389
1389
|
didSet { bindEditor() }
|
|
1390
1390
|
}
|
|
1391
|
-
@Published var selectedWindowIds: Set<UInt32> = []
|
|
1391
|
+
@Published var selectedWindowIds: Set<UInt32> = [] {
|
|
1392
|
+
didSet { syncSharedSelection() }
|
|
1393
|
+
}
|
|
1392
1394
|
@Published var windowSets: [ScreenMapWindowSet] = []
|
|
1393
1395
|
@Published var activeWindowSetID: UUID? = nil
|
|
1394
1396
|
@Published var flashMessage: String? = nil
|
|
@@ -1484,6 +1486,28 @@ final class ScreenMapController: ObservableObject {
|
|
|
1484
1486
|
activeWindowSetID = nil
|
|
1485
1487
|
}
|
|
1486
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
|
+
|
|
1487
1511
|
func selectNextWindow() {
|
|
1488
1512
|
guard let ed = editor else { return }
|
|
1489
1513
|
let wins = ed.focusedVisibleWindows.sorted(by: { $0.zIndex < $1.zIndex })
|
|
@@ -2011,7 +2035,6 @@ final class ScreenMapController: ObservableObject {
|
|
|
2011
2035
|
|
|
2012
2036
|
func handleKey(_ keyCode: UInt16, modifiers: NSEvent.ModifierFlags = []) -> Bool {
|
|
2013
2037
|
let diag = DiagnosticLog.shared
|
|
2014
|
-
diag.info("[ScreenMap] key: \(keyCode)")
|
|
2015
2038
|
|
|
2016
2039
|
// Tiling mode intercepts keys before anything else
|
|
2017
2040
|
if editor?.isTilingMode == true {
|
|
@@ -1805,6 +1805,7 @@ struct ScreenMapView: View {
|
|
|
1805
1805
|
let displays = editor?.displays ?? []
|
|
1806
1806
|
let zoomLevel = editor?.zoomLevel ?? 1.0
|
|
1807
1807
|
let panOffset = editor?.panOffset ?? .zero
|
|
1808
|
+
let canvasShape = RoundedRectangle(cornerRadius: 6, style: .continuous)
|
|
1808
1809
|
|
|
1809
1810
|
return GeometryReader { geo in
|
|
1810
1811
|
let metrics = CanvasMetrics(editor: editor, displays: displays, viewportSize: geo.size)
|
|
@@ -1852,12 +1853,14 @@ struct ScreenMapView: View {
|
|
|
1852
1853
|
}
|
|
1853
1854
|
.padding(8)
|
|
1854
1855
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
1856
|
+
.contentShape(canvasShape)
|
|
1857
|
+
.clipShape(canvasShape)
|
|
1855
1858
|
.clipped()
|
|
1856
1859
|
.background(
|
|
1857
1860
|
ZStack {
|
|
1858
|
-
|
|
1861
|
+
canvasShape
|
|
1859
1862
|
.fill(Color.black.opacity(0.25))
|
|
1860
|
-
|
|
1863
|
+
canvasShape
|
|
1861
1864
|
.strokeBorder(Color.white.opacity(0.06), lineWidth: 0.5)
|
|
1862
1865
|
Canvas { context, size in
|
|
1863
1866
|
let spacing: CGFloat = 20
|
|
@@ -2742,11 +2745,14 @@ struct ScreenMapView: View {
|
|
|
2742
2745
|
}
|
|
2743
2746
|
// Track space key for canvas drag-to-pan
|
|
2744
2747
|
if event.keyCode == 49 && !controller.isSearchActive {
|
|
2745
|
-
if event.type == .keyDown
|
|
2746
|
-
isSpaceHeld
|
|
2747
|
-
|
|
2748
|
+
if event.type == .keyDown {
|
|
2749
|
+
if !isSpaceHeld {
|
|
2750
|
+
isSpaceHeld = true
|
|
2751
|
+
NSCursor.openHand.push()
|
|
2752
|
+
}
|
|
2748
2753
|
return nil
|
|
2749
2754
|
} else if event.type == .keyUp {
|
|
2755
|
+
guard isSpaceHeld else { return nil }
|
|
2750
2756
|
isSpaceHeld = false
|
|
2751
2757
|
spaceDragStart = nil
|
|
2752
2758
|
NSCursor.pop()
|
|
@@ -2788,16 +2794,18 @@ struct ScreenMapView: View {
|
|
|
2788
2794
|
mouseDownMonitor = NSEvent.addLocalMonitorForEvents(matching: .leftMouseDown) { event in
|
|
2789
2795
|
guard let eventWindow = event.window,
|
|
2790
2796
|
eventWindow === ScreenMapWindowController.shared.nsWindow else { return event }
|
|
2797
|
+
let flippedPt = flippedScreenPoint(event)
|
|
2791
2798
|
|
|
2792
2799
|
// Space+click → begin canvas pan
|
|
2793
|
-
if isSpaceHeld,
|
|
2800
|
+
if isSpaceHeld,
|
|
2801
|
+
isCanvasPoint(flippedPt),
|
|
2802
|
+
let editor = controller.editor {
|
|
2794
2803
|
spaceDragStart = event.locationInWindow
|
|
2795
2804
|
spaceDragPanStart = editor.panOffset
|
|
2796
2805
|
NSCursor.closedHand.push()
|
|
2797
2806
|
return nil
|
|
2798
2807
|
}
|
|
2799
2808
|
|
|
2800
|
-
let flippedPt = flippedScreenPoint(event)
|
|
2801
2809
|
if let editor = controller.editor,
|
|
2802
2810
|
let hit = canvasHit(flippedScreenPt: flippedPt, editor: editor),
|
|
2803
2811
|
hoveredWindowId == hit.id {
|
|
@@ -3042,6 +3050,7 @@ struct ScreenMapView: View {
|
|
|
3042
3050
|
// MARK: - Hit Test / Coordinate Conversion
|
|
3043
3051
|
|
|
3044
3052
|
private func canvasHit(flippedScreenPt: CGPoint, editor: ScreenMapEditorState) -> CanvasHit? {
|
|
3053
|
+
guard isCanvasPoint(flippedScreenPt) else { return nil }
|
|
3045
3054
|
let projection = CanvasProjection(editor: editor)
|
|
3046
3055
|
guard projection.scale > 0 else { return nil }
|
|
3047
3056
|
let canvasLocal = CGPoint(
|
|
@@ -3059,6 +3068,10 @@ struct ScreenMapView: View {
|
|
|
3059
3068
|
return nil
|
|
3060
3069
|
}
|
|
3061
3070
|
|
|
3071
|
+
private func isCanvasPoint(_ point: CGPoint) -> Bool {
|
|
3072
|
+
CGRect(origin: screenMapCanvasOrigin, size: screenMapCanvasSize).contains(point)
|
|
3073
|
+
}
|
|
3074
|
+
|
|
3062
3075
|
private func detectDragMode(mapPoint: CGPoint, windowMapRect: CGRect) -> CanvasDragMode {
|
|
3063
3076
|
let w = windowMapRect.width
|
|
3064
3077
|
let h = windowMapRect.height
|
package/app/Sources/{VoiceCommandWindow.swift → Core/Overlays/Voice/VoiceCommandWindow.swift}
RENAMED
|
@@ -2,55 +2,12 @@ import AppKit
|
|
|
2
2
|
import Combine
|
|
3
3
|
import SwiftUI
|
|
4
4
|
|
|
5
|
-
// MARK: - Panel subclass (handles keyDown when focused)
|
|
6
|
-
|
|
7
|
-
final class VoicePanel: NSPanel {
|
|
8
|
-
var onKeyDown: ((NSEvent) -> Void)?
|
|
9
|
-
var onFlagsChanged: ((NSEvent) -> Void)?
|
|
10
|
-
|
|
11
|
-
override var canBecomeKey: Bool { true }
|
|
12
|
-
override var canBecomeMain: Bool { true }
|
|
13
|
-
|
|
14
|
-
override func sendEvent(_ event: NSEvent) {
|
|
15
|
-
if event.type == .leftMouseDown || event.type == .rightMouseDown {
|
|
16
|
-
if !NSApp.isActive {
|
|
17
|
-
NSApp.activate(ignoringOtherApps: true)
|
|
18
|
-
}
|
|
19
|
-
if !isKeyWindow {
|
|
20
|
-
makeKey()
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
super.sendEvent(event)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
override func keyDown(with event: NSEvent) {
|
|
27
|
-
if let handler = onKeyDown {
|
|
28
|
-
handler(event)
|
|
29
|
-
} else {
|
|
30
|
-
super.keyDown(with: event)
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
override func flagsChanged(with event: NSEvent) {
|
|
35
|
-
if let handler = onFlagsChanged {
|
|
36
|
-
handler(event)
|
|
37
|
-
} else {
|
|
38
|
-
super.flagsChanged(with: event)
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
private final class VoiceHostingView<Content: View>: NSHostingView<Content> {
|
|
44
|
-
override func acceptsFirstMouse(for event: NSEvent?) -> Bool { true }
|
|
45
|
-
override var focusRingType: NSFocusRingType { get { .none } set {} }
|
|
46
|
-
}
|
|
47
|
-
|
|
48
5
|
// MARK: - Window Controller
|
|
49
6
|
|
|
50
7
|
final class VoiceCommandWindow {
|
|
51
8
|
static let shared = VoiceCommandWindow()
|
|
52
9
|
|
|
53
|
-
private(set) var panel:
|
|
10
|
+
private(set) var panel: OverlayPanel?
|
|
54
11
|
private var keyMonitor: Any?
|
|
55
12
|
private var state: VoiceCommandState?
|
|
56
13
|
|
|
@@ -66,7 +23,7 @@ final class VoiceCommandWindow {
|
|
|
66
23
|
|
|
67
24
|
func show() {
|
|
68
25
|
// If panel exists but is hidden, just re-show it
|
|
69
|
-
if let p = panel,
|
|
26
|
+
if let p = panel, state != nil {
|
|
70
27
|
p.alphaValue = 0
|
|
71
28
|
p.orderFrontRegardless()
|
|
72
29
|
NSAnimationContext.runAnimationGroup { ctx in
|
|
@@ -87,42 +44,32 @@ final class VoiceCommandWindow {
|
|
|
87
44
|
.preferredColorScheme(.dark)
|
|
88
45
|
|
|
89
46
|
let mouseLocation = NSEvent.mouseLocation
|
|
90
|
-
let screen = NSScreen.screens.first(where: { $0.frame.contains(mouseLocation) })
|
|
47
|
+
let screen = NSScreen.screens.first(where: { $0.frame.contains(mouseLocation) })
|
|
48
|
+
?? NSScreen.main
|
|
49
|
+
?? NSScreen.screens.first!
|
|
91
50
|
let visible = screen.visibleFrame
|
|
92
|
-
|
|
93
51
|
let panelWidth: CGFloat = min(900, visible.width - 80)
|
|
94
52
|
let panelHeight: CGFloat = min(560, visible.height - 80)
|
|
95
53
|
|
|
96
|
-
let p =
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
54
|
+
let p = OverlayPanelShell.makePanel(
|
|
55
|
+
config: .init(
|
|
56
|
+
size: NSSize(width: panelWidth, height: panelHeight),
|
|
57
|
+
styleMask: [.titled, .nonactivatingPanel],
|
|
58
|
+
titleVisible: .hidden,
|
|
59
|
+
titlebarAppearsTransparent: true,
|
|
60
|
+
background: .clear,
|
|
61
|
+
hidesOnDeactivate: false,
|
|
62
|
+
isMovableByWindowBackground: true,
|
|
63
|
+
activatesOnMouseDown: true,
|
|
64
|
+
onKeyDown: { [weak self] event in self?.handleKey(event) },
|
|
65
|
+
onFlagsChanged: { [weak self] event in self?.handleFlags(event) }
|
|
66
|
+
),
|
|
67
|
+
rootView: view
|
|
101
68
|
)
|
|
102
|
-
p
|
|
103
|
-
p.onFlagsChanged = { [weak self] event in self?.handleFlags(event) }
|
|
104
|
-
p.titlebarAppearsTransparent = true
|
|
105
|
-
p.titleVisibility = .hidden
|
|
106
|
-
p.isOpaque = false
|
|
107
|
-
p.backgroundColor = .clear
|
|
108
|
-
p.level = .floating
|
|
109
|
-
p.hasShadow = true
|
|
110
|
-
p.hidesOnDeactivate = false
|
|
111
|
-
p.isReleasedWhenClosed = false
|
|
112
|
-
p.isMovableByWindowBackground = true
|
|
113
|
-
let hosting = VoiceHostingView(rootView: view)
|
|
114
|
-
hosting.translatesAutoresizingMaskIntoConstraints = false
|
|
115
|
-
p.contentView = hosting
|
|
116
|
-
|
|
117
|
-
// Position: top-center of screen
|
|
118
|
-
let x = visible.midX - panelWidth / 2
|
|
119
|
-
let y = visible.maxY - panelHeight - 40
|
|
120
|
-
p.setFrameOrigin(NSPoint(x: x, y: y))
|
|
69
|
+
OverlayPanelShell.position(p, placement: .topCenter(margin: 40))
|
|
121
70
|
|
|
122
71
|
p.alphaValue = 0
|
|
123
|
-
p
|
|
124
|
-
p.makeKey()
|
|
125
|
-
NSApp.activate(ignoringOtherApps: true)
|
|
72
|
+
OverlayPanelShell.present(p, activate: true, makeKey: true, orderFrontRegardless: true)
|
|
126
73
|
|
|
127
74
|
NSAnimationContext.runAnimationGroup { ctx in
|
|
128
75
|
ctx.duration = 0.15
|
|
@@ -560,6 +507,7 @@ final class VoiceCommandState: ObservableObject {
|
|
|
560
507
|
|
|
561
508
|
struct VoiceCommandView: View {
|
|
562
509
|
@ObservedObject var state: VoiceCommandState
|
|
510
|
+
@ObservedObject private var activeSelection = WindowSelectionStore.shared
|
|
563
511
|
let onDismiss: () -> Void
|
|
564
512
|
|
|
565
513
|
private let docsURL = "https://lattices.dev/docs/voice"
|
|
@@ -884,6 +832,30 @@ struct VoiceCommandView: View {
|
|
|
884
832
|
VStack(alignment: .leading, spacing: 14) {
|
|
885
833
|
// Zero-height spacer forces VStack to fill ScrollView width
|
|
886
834
|
Color.clear.frame(maxWidth: .infinity, maxHeight: 0)
|
|
835
|
+
if activeSelection.isActive {
|
|
836
|
+
commandSection("selection") {
|
|
837
|
+
VStack(alignment: .leading, spacing: 6) {
|
|
838
|
+
HStack(spacing: 6) {
|
|
839
|
+
Text("\(activeSelection.count) window\(activeSelection.count == 1 ? "" : "s")")
|
|
840
|
+
.font(Typo.geistMonoBold(11))
|
|
841
|
+
.foregroundColor(Palette.running)
|
|
842
|
+
if let source = activeSelection.sourceLabel {
|
|
843
|
+
Text(source)
|
|
844
|
+
.font(Typo.geistMono(10))
|
|
845
|
+
.foregroundColor(Palette.textMuted)
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
Text(activeSelection.summary(maxItems: 4))
|
|
849
|
+
.font(Typo.geistMono(11))
|
|
850
|
+
.foregroundColor(Palette.textDim)
|
|
851
|
+
.lineLimit(3)
|
|
852
|
+
Text("Try: grid that in the bottom half")
|
|
853
|
+
.font(Typo.geistMono(10))
|
|
854
|
+
.foregroundColor(Palette.textMuted)
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
887
859
|
// Partial transcript (while listening)
|
|
888
860
|
if state.phase == .listening, !state.partialText.isEmpty {
|
|
889
861
|
commandSection("hearing...") {
|
|
@@ -66,12 +66,40 @@ struct GridFile: Codable {
|
|
|
66
66
|
let snapZones: SnapZonesConfig?
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
enum SnapModifierKey: String, Codable, Equatable {
|
|
69
|
+
enum SnapModifierKey: String, Codable, Equatable, CaseIterable, Identifiable {
|
|
70
70
|
case command
|
|
71
71
|
case option
|
|
72
72
|
case control
|
|
73
73
|
case shift
|
|
74
74
|
|
|
75
|
+
var id: String { rawValue }
|
|
76
|
+
|
|
77
|
+
var label: String {
|
|
78
|
+
switch self {
|
|
79
|
+
case .command:
|
|
80
|
+
return "Command"
|
|
81
|
+
case .option:
|
|
82
|
+
return "Option"
|
|
83
|
+
case .control:
|
|
84
|
+
return "Control"
|
|
85
|
+
case .shift:
|
|
86
|
+
return "Shift"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
var shortLabel: String {
|
|
91
|
+
switch self {
|
|
92
|
+
case .command:
|
|
93
|
+
return "Cmd"
|
|
94
|
+
case .option:
|
|
95
|
+
return "Opt"
|
|
96
|
+
case .control:
|
|
97
|
+
return "Ctrl"
|
|
98
|
+
case .shift:
|
|
99
|
+
return "Shift"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
75
103
|
var eventFlags: NSEvent.ModifierFlags {
|
|
76
104
|
switch self {
|
|
77
105
|
case .command:
|
|
@@ -98,9 +126,6 @@ enum SnapModifierKey: String, Codable, Equatable {
|
|
|
98
126
|
}
|
|
99
127
|
}
|
|
100
128
|
|
|
101
|
-
var label: String {
|
|
102
|
-
rawValue.capitalized
|
|
103
|
-
}
|
|
104
129
|
}
|
|
105
130
|
|
|
106
131
|
enum SnapZoneTriggerSpec: Codable, Equatable {
|
|
@@ -459,6 +484,36 @@ class WorkspaceManager: ObservableObject {
|
|
|
459
484
|
self.snapZonesConfig = snapZones
|
|
460
485
|
}
|
|
461
486
|
|
|
487
|
+
func updateSnapModifier(_ modifier: SnapModifierKey) {
|
|
488
|
+
let updated = SnapZonesConfig(
|
|
489
|
+
enabled: snapZonesConfig.enabled,
|
|
490
|
+
modifier: modifier,
|
|
491
|
+
zoneOpacity: snapZonesConfig.zoneOpacity,
|
|
492
|
+
highlightOpacity: snapZonesConfig.highlightOpacity,
|
|
493
|
+
previewOpacity: snapZonesConfig.previewOpacity,
|
|
494
|
+
cornerRadius: snapZonesConfig.cornerRadius,
|
|
495
|
+
rules: snapZonesConfig.rules
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
do {
|
|
499
|
+
let url = URL(fileURLWithPath: snapZonesConfigPath)
|
|
500
|
+
try FileManager.default.createDirectory(
|
|
501
|
+
at: url.deletingLastPathComponent(),
|
|
502
|
+
withIntermediateDirectories: true
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
let encoder = JSONEncoder()
|
|
506
|
+
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
|
|
507
|
+
let data = try encoder.encode(updated)
|
|
508
|
+
try data.write(to: url, options: .atomic)
|
|
509
|
+
|
|
510
|
+
loadGridConfig()
|
|
511
|
+
DiagnosticLog.shared.info("WorkspaceManager: updated snap modifier to \(modifier.rawValue)")
|
|
512
|
+
} catch {
|
|
513
|
+
DiagnosticLog.shared.error("WorkspaceManager: failed to write snap-zones.json — \(error.localizedDescription)")
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
462
517
|
/// Resolve a tile string to fractions: check user presets first, then built-in TilePosition
|
|
463
518
|
func resolveTileFractions(_ tile: String) -> (CGFloat, CGFloat, CGFloat, CGFloat)? {
|
|
464
519
|
resolvePlacement(tile)?.fractions
|