@lattices/cli 0.4.5 → 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/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} +4 -0
- package/app/Sources/{AppShellView.swift → AppShell/AppShellView.swift} +10 -1
- package/app/Sources/{HotkeyStore.swift → Core/Actions/HotkeyStore.swift} +2 -1
- 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 -108
- package/app/Sources/{CommandModeState.swift → Core/Overlays/CommandMode/CommandModeState.swift} +127 -24
- package/app/Sources/{CommandModeView.swift → Core/Overlays/CommandMode/CommandModeView.swift} +488 -55
- package/app/Sources/Core/Overlays/CommandPalette/CommandPaletteWindow.swift +67 -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 -1
- package/app/Sources/{VoiceCommandWindow.swift → Core/Overlays/Voice/VoiceCommandWindow.swift} +46 -74
- 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/{KeyRecorderView.swift → AppShell/KeyRecorderView.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/{Preferences.swift → AppShell/Preferences.swift} +0 -0
- /package/app/Sources/{SettingsView.swift → AppShell/SettingsView.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/{CommandModeWindow.swift → Core/Overlays/CommandMode/CommandModeWindow.swift} +0 -0
- /package/app/Sources/{CommandPaletteView.swift → Core/Overlays/CommandPalette/CommandPaletteView.swift} +0 -0
- /package/app/Sources/{CheatSheetHUD.swift → Core/Overlays/HUD/CheatSheetHUD.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/{ScreenMapView.swift → Core/Overlays/ScreenMap/ScreenMapView.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/{WorkspaceManager.swift → Core/Workspace/WorkspaceManager.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 })
|
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...") {
|