@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
package/app/Sources/{CommandModeView.swift → Core/Overlays/CommandMode/CommandModeView.swift}
RENAMED
|
@@ -22,8 +22,14 @@ private struct FocusRingSuppressor: ViewModifier {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
enum CommandModePresentation {
|
|
26
|
+
case panel
|
|
27
|
+
case embedded
|
|
28
|
+
}
|
|
29
|
+
|
|
25
30
|
struct CommandModeView: View {
|
|
26
31
|
@ObservedObject var state: CommandModeState
|
|
32
|
+
var presentation: CommandModePresentation = .panel
|
|
27
33
|
@State private var eventMonitor: Any?
|
|
28
34
|
@State private var mouseDownMonitor: Any?
|
|
29
35
|
@State private var mouseDragMonitor: Any?
|
|
@@ -36,41 +42,77 @@ struct CommandModeView: View {
|
|
|
36
42
|
state.phase == .desktopInventory
|
|
37
43
|
}
|
|
38
44
|
|
|
45
|
+
private var isEmbedded: Bool {
|
|
46
|
+
presentation == .embedded
|
|
47
|
+
}
|
|
48
|
+
|
|
39
49
|
// Column widths for inventory table
|
|
40
50
|
private static let sizeColW: CGFloat = 80
|
|
41
51
|
private static let tileColW: CGFloat = 60
|
|
42
52
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
53
|
+
var body: some View {
|
|
54
|
+
GeometryReader { geo in
|
|
55
|
+
let availableWidth = max(geo.size.width, 580)
|
|
56
|
+
let contentWidth = resolvedContentWidth(in: availableWidth)
|
|
57
|
+
|
|
58
|
+
Group {
|
|
59
|
+
if isEmbedded && isDesktopInventory {
|
|
60
|
+
embeddedInventoryPage(contentWidth: contentWidth)
|
|
61
|
+
} else {
|
|
62
|
+
inventoryCard(contentWidth: contentWidth)
|
|
63
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
|
67
|
+
}
|
|
68
|
+
.onAppear { installKeyHandler(); installMouseMonitors() }
|
|
69
|
+
.onDisappear { removeKeyHandler(); removeMouseMonitors() }
|
|
70
|
+
.onChange(of: state.desktopMode) { mode in
|
|
71
|
+
CommandModeWindow.shared.panelWindow?.isMovableByWindowBackground = true
|
|
72
|
+
}
|
|
73
|
+
.animation(.easeInOut(duration: 0.2), value: isDesktopInventory)
|
|
74
|
+
.modifier(FocusRingSuppressor())
|
|
47
75
|
}
|
|
48
76
|
|
|
49
|
-
private
|
|
77
|
+
private func resolvedContentWidth(in availableWidth: CGFloat) -> CGFloat {
|
|
50
78
|
if isDesktopInventory {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
79
|
+
if isEmbedded {
|
|
80
|
+
return min(max(availableWidth - 24, 840), 1560)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let displayCount = CGFloat(max(1, state.filteredSnapshot?.displays.count ?? 1))
|
|
84
|
+
let ideal = displayCount * 480 + CGFloat(max(0, Int(displayCount) - 1)) + 32
|
|
85
|
+
let screenWidth = NSScreen.main?.visibleFrame.width ?? availableWidth
|
|
54
86
|
return min(ideal, screenWidth * 0.92)
|
|
55
87
|
}
|
|
88
|
+
|
|
89
|
+
if isEmbedded {
|
|
90
|
+
return min(720, max(availableWidth - 32, 580))
|
|
91
|
+
}
|
|
56
92
|
return 580
|
|
57
93
|
}
|
|
58
94
|
|
|
59
|
-
|
|
95
|
+
private func displayColumnWidth(for contentWidth: CGFloat) -> CGFloat {
|
|
96
|
+
let count = CGFloat(max(1, state.filteredSnapshot?.displays.count ?? 1))
|
|
97
|
+
let available = contentWidth - 32 - (count - 1) * 0.5
|
|
98
|
+
return max(isEmbedded ? 400 : 360, (available / count).rounded(.down))
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private func inventoryCard(contentWidth: CGFloat) -> some View {
|
|
60
102
|
VStack(spacing: 0) {
|
|
61
103
|
header
|
|
62
104
|
divider
|
|
63
105
|
if isDesktopInventory && state.desktopMode == .gridPreview {
|
|
64
106
|
gridPreviewContent
|
|
65
107
|
} else if isDesktopInventory {
|
|
66
|
-
desktopInventoryContent
|
|
108
|
+
desktopInventoryContent(contentWidth: contentWidth)
|
|
67
109
|
} else {
|
|
68
110
|
inventoryGrid
|
|
69
111
|
}
|
|
70
112
|
divider
|
|
71
113
|
chordFooter
|
|
72
114
|
}
|
|
73
|
-
.frame(width:
|
|
115
|
+
.frame(width: contentWidth)
|
|
74
116
|
.background(Palette.bg)
|
|
75
117
|
.clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous))
|
|
76
118
|
.overlay(
|
|
@@ -79,23 +121,96 @@ struct CommandModeView: View {
|
|
|
79
121
|
)
|
|
80
122
|
.overlay(executingOverlay)
|
|
81
123
|
.overlay(flashOverlay)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private func embeddedInventoryPage(contentWidth: CGFloat) -> some View {
|
|
127
|
+
VStack(spacing: 12) {
|
|
128
|
+
embeddedInventorySummary
|
|
129
|
+
.frame(width: contentWidth, alignment: .leading)
|
|
130
|
+
inventoryCard(contentWidth: contentWidth)
|
|
131
|
+
.frame(maxHeight: .infinity)
|
|
86
132
|
}
|
|
87
|
-
.
|
|
88
|
-
.
|
|
133
|
+
.padding(16)
|
|
134
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private var embeddedInventorySummary: some View {
|
|
138
|
+
let snapshot = state.filteredSnapshot ?? state.desktopSnapshot
|
|
139
|
+
let displayCount = snapshot?.displays.count ?? 0
|
|
140
|
+
let spaceCount = snapshot?.displays.reduce(0) { total, display in
|
|
141
|
+
total + display.spaces.count
|
|
142
|
+
} ?? 0
|
|
143
|
+
let windowCount = snapshot?.allWindows.count ?? 0
|
|
144
|
+
|
|
145
|
+
return HStack(alignment: .center, spacing: 12) {
|
|
146
|
+
VStack(alignment: .leading, spacing: 4) {
|
|
147
|
+
Text("Grouped by display, Space, and app")
|
|
148
|
+
.font(Typo.heading(13))
|
|
149
|
+
.foregroundColor(Palette.text)
|
|
150
|
+
Text(state.isSearching
|
|
151
|
+
? "Search results stay in place so the desktop reads like a map instead of a flat list."
|
|
152
|
+
: "Live window sizes, tiling hints, and OCR search in one balanced pass across the desktop.")
|
|
153
|
+
.font(Typo.mono(10))
|
|
154
|
+
.foregroundColor(Palette.textDim)
|
|
155
|
+
.fixedSize(horizontal: false, vertical: true)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
Spacer(minLength: 12)
|
|
159
|
+
|
|
160
|
+
HStack(spacing: 8) {
|
|
161
|
+
inventoryStatPill(value: displayCount, label: "Displays")
|
|
162
|
+
inventoryStatPill(value: spaceCount, label: "Spaces")
|
|
163
|
+
inventoryStatPill(value: windowCount, label: "Windows")
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
.padding(.horizontal, 14)
|
|
167
|
+
.padding(.vertical, 12)
|
|
168
|
+
.background(
|
|
169
|
+
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
|
170
|
+
.fill(Palette.surface.opacity(0.65))
|
|
171
|
+
.overlay(
|
|
172
|
+
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
|
173
|
+
.strokeBorder(Palette.border, lineWidth: 0.5)
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private func inventoryStatPill(value: Int, label: String) -> some View {
|
|
179
|
+
VStack(alignment: .leading, spacing: 2) {
|
|
180
|
+
Text("\(value)")
|
|
181
|
+
.font(Typo.monoBold(12))
|
|
182
|
+
.foregroundColor(Palette.text)
|
|
183
|
+
Text(label.uppercased())
|
|
184
|
+
.font(Typo.mono(8))
|
|
185
|
+
.foregroundColor(Palette.textMuted)
|
|
186
|
+
}
|
|
187
|
+
.padding(.horizontal, 10)
|
|
188
|
+
.padding(.vertical, 8)
|
|
189
|
+
.background(
|
|
190
|
+
RoundedRectangle(cornerRadius: 8, style: .continuous)
|
|
191
|
+
.fill(Palette.bg.opacity(0.75))
|
|
192
|
+
.overlay(
|
|
193
|
+
RoundedRectangle(cornerRadius: 8, style: .continuous)
|
|
194
|
+
.strokeBorder(Palette.border, lineWidth: 0.5)
|
|
195
|
+
)
|
|
196
|
+
)
|
|
89
197
|
}
|
|
90
198
|
|
|
91
199
|
// MARK: - Header
|
|
92
200
|
|
|
93
201
|
private var header: some View {
|
|
94
202
|
HStack {
|
|
95
|
-
Text(isDesktopInventory ? "DESKTOP INVENTORY" : "COMMAND MODE")
|
|
203
|
+
Text(isDesktopInventory ? (state.isOrganizeFlow ? "ORGANIZE WINDOWS" : "DESKTOP INVENTORY") : "COMMAND MODE")
|
|
96
204
|
.font(Typo.monoBold(11))
|
|
97
205
|
.foregroundColor(Palette.text)
|
|
98
206
|
|
|
207
|
+
if isDesktopInventory && state.isOrganizeFlow {
|
|
208
|
+
bannerBadge("Current Space", tone: .neutral)
|
|
209
|
+
if let appName = state.organizeSeedAppName, !appName.isEmpty {
|
|
210
|
+
bannerBadge(appName, tone: .accent)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
99
214
|
if isDesktopInventory {
|
|
100
215
|
Button(action: { state.copyInventoryToClipboard() }) {
|
|
101
216
|
HStack(spacing: 3) {
|
|
@@ -165,8 +280,13 @@ struct CommandModeView: View {
|
|
|
165
280
|
|
|
166
281
|
// MARK: - Desktop Inventory Content
|
|
167
282
|
|
|
168
|
-
private
|
|
283
|
+
private func desktopInventoryContent(contentWidth: CGFloat) -> some View {
|
|
169
284
|
VStack(spacing: 0) {
|
|
285
|
+
if state.isOrganizeFlow {
|
|
286
|
+
organizeBanner
|
|
287
|
+
divider
|
|
288
|
+
}
|
|
289
|
+
|
|
170
290
|
if state.isSearching {
|
|
171
291
|
searchBar
|
|
172
292
|
} else {
|
|
@@ -177,20 +297,7 @@ struct CommandModeView: View {
|
|
|
177
297
|
ZStack {
|
|
178
298
|
Group {
|
|
179
299
|
if let snapshot = state.filteredSnapshot, !snapshot.displays.isEmpty {
|
|
180
|
-
|
|
181
|
-
HStack(alignment: .top, spacing: 0) {
|
|
182
|
-
let total = snapshot.displays.count
|
|
183
|
-
ForEach(Array(snapshot.displays.enumerated()), id: \.element.id) { idx, display in
|
|
184
|
-
if idx > 0 {
|
|
185
|
-
Rectangle()
|
|
186
|
-
.fill(Palette.border)
|
|
187
|
-
.frame(width: 0.5)
|
|
188
|
-
}
|
|
189
|
-
displayColumn(display, index: idx, total: total)
|
|
190
|
-
.frame(width: displayColumnWidth)
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
300
|
+
inventoryColumns(snapshot: snapshot, contentWidth: contentWidth)
|
|
194
301
|
} else {
|
|
195
302
|
desktopEmptyState
|
|
196
303
|
}
|
|
@@ -216,6 +323,80 @@ struct CommandModeView: View {
|
|
|
216
323
|
}
|
|
217
324
|
}
|
|
218
325
|
|
|
326
|
+
@ViewBuilder
|
|
327
|
+
private func inventoryColumns(snapshot: DesktopInventorySnapshot, contentWidth: CGFloat) -> some View {
|
|
328
|
+
if shouldShowEmbeddedSidebar(snapshot: snapshot, contentWidth: contentWidth),
|
|
329
|
+
let display = snapshot.displays.first {
|
|
330
|
+
embeddedSingleDisplayLayout(display: display, contentWidth: contentWidth)
|
|
331
|
+
} else {
|
|
332
|
+
ScrollView(.horizontal, showsIndicators: false) {
|
|
333
|
+
HStack(alignment: .top, spacing: 0) {
|
|
334
|
+
let total = snapshot.displays.count
|
|
335
|
+
ForEach(Array(snapshot.displays.enumerated()), id: \.element.id) { idx, display in
|
|
336
|
+
if idx > 0 {
|
|
337
|
+
Rectangle()
|
|
338
|
+
.fill(Palette.border)
|
|
339
|
+
.frame(width: 0.5)
|
|
340
|
+
}
|
|
341
|
+
displayColumn(display, index: idx, total: total)
|
|
342
|
+
.frame(width: displayColumnWidth(for: contentWidth))
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private func shouldShowEmbeddedSidebar(snapshot: DesktopInventorySnapshot, contentWidth: CGFloat) -> Bool {
|
|
350
|
+
isEmbedded && snapshot.displays.count == 1 && contentWidth >= 900
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
private func embeddedSingleDisplayLayout(
|
|
354
|
+
display: DesktopInventorySnapshot.DisplayInfo,
|
|
355
|
+
contentWidth: CGFloat
|
|
356
|
+
) -> some View {
|
|
357
|
+
let sidebarWidth = min(max(contentWidth * 0.28, 250), 330)
|
|
358
|
+
let mainWidth = max(contentWidth - sidebarWidth - 0.5, 620)
|
|
359
|
+
|
|
360
|
+
return HStack(alignment: .top, spacing: 0) {
|
|
361
|
+
displayColumn(display, index: 0, total: 1)
|
|
362
|
+
.frame(width: mainWidth)
|
|
363
|
+
|
|
364
|
+
Rectangle()
|
|
365
|
+
.fill(Palette.border)
|
|
366
|
+
.frame(width: 0.5)
|
|
367
|
+
|
|
368
|
+
embeddedInventorySidebar(display: display)
|
|
369
|
+
.frame(width: sidebarWidth)
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
private var organizeBanner: some View {
|
|
374
|
+
VStack(alignment: .leading, spacing: 6) {
|
|
375
|
+
HStack(alignment: .center, spacing: 8) {
|
|
376
|
+
Image(systemName: "rectangle.3.group")
|
|
377
|
+
.font(.system(size: 11, weight: .medium))
|
|
378
|
+
.foregroundColor(Palette.running)
|
|
379
|
+
Text(state.organizeSelectionSummary)
|
|
380
|
+
.font(Typo.monoBold(10))
|
|
381
|
+
.foregroundColor(Palette.text)
|
|
382
|
+
Spacer()
|
|
383
|
+
if state.selectedWindowIds.count > 1 {
|
|
384
|
+
bannerBadge("Ready", tone: .accent)
|
|
385
|
+
} else {
|
|
386
|
+
bannerBadge("Add More", tone: .neutral)
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
Text(state.organizeGuidance)
|
|
391
|
+
.font(Typo.mono(10))
|
|
392
|
+
.foregroundColor(Palette.textDim)
|
|
393
|
+
.lineLimit(2)
|
|
394
|
+
}
|
|
395
|
+
.padding(.horizontal, 14)
|
|
396
|
+
.padding(.vertical, 8)
|
|
397
|
+
.background(Palette.running.opacity(0.06))
|
|
398
|
+
}
|
|
399
|
+
|
|
219
400
|
private var filterPillBar: some View {
|
|
220
401
|
HStack(spacing: 6) {
|
|
221
402
|
ForEach(FilterPreset.allCases, id: \.rawValue) { preset in
|
|
@@ -378,6 +559,210 @@ struct CommandModeView: View {
|
|
|
378
559
|
.padding(.vertical, 8)
|
|
379
560
|
}
|
|
380
561
|
|
|
562
|
+
private func embeddedInventorySidebar(display: DesktopInventorySnapshot.DisplayInfo) -> some View {
|
|
563
|
+
ScrollView {
|
|
564
|
+
VStack(alignment: .leading, spacing: 12) {
|
|
565
|
+
sidebarCard(title: "Overview") {
|
|
566
|
+
sidebarMetric(label: "Display", value: display.name)
|
|
567
|
+
sidebarMetric(
|
|
568
|
+
label: "Visible",
|
|
569
|
+
value: "\(display.visibleFrame.w)×\(display.visibleFrame.h)"
|
|
570
|
+
)
|
|
571
|
+
sidebarMetric(
|
|
572
|
+
label: "Current Space",
|
|
573
|
+
value: "Space \(display.currentSpaceIndex)"
|
|
574
|
+
)
|
|
575
|
+
sidebarMetric(
|
|
576
|
+
label: "Windows",
|
|
577
|
+
value: "\(windowCount(in: display)) total"
|
|
578
|
+
)
|
|
579
|
+
sidebarMetric(
|
|
580
|
+
label: "Apps",
|
|
581
|
+
value: "\(uniqueAppCount(in: display)) active"
|
|
582
|
+
)
|
|
583
|
+
sidebarMetric(
|
|
584
|
+
label: "Lattices",
|
|
585
|
+
value: "\(latticesWindowCount(in: display)) tagged"
|
|
586
|
+
)
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
sidebarCard(title: "Spaces") {
|
|
590
|
+
VStack(alignment: .leading, spacing: 8) {
|
|
591
|
+
ForEach(display.spaces) { space in
|
|
592
|
+
HStack(alignment: .top, spacing: 8) {
|
|
593
|
+
VStack(alignment: .leading, spacing: 2) {
|
|
594
|
+
HStack(spacing: 5) {
|
|
595
|
+
Text("Space \(space.index)")
|
|
596
|
+
.font(Typo.monoBold(10))
|
|
597
|
+
.foregroundColor(space.isCurrent ? Palette.running : Palette.text)
|
|
598
|
+
if space.isCurrent {
|
|
599
|
+
Text("active")
|
|
600
|
+
.font(Typo.mono(8))
|
|
601
|
+
.foregroundColor(Palette.running.opacity(0.75))
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
Text("\(spaceWindowCount(space)) windows across \(space.apps.count) apps")
|
|
605
|
+
.font(Typo.mono(9))
|
|
606
|
+
.foregroundColor(Palette.textDim)
|
|
607
|
+
}
|
|
608
|
+
Spacer()
|
|
609
|
+
Text("\(spaceLatticesCount(space))")
|
|
610
|
+
.font(Typo.mono(9))
|
|
611
|
+
.foregroundColor(Palette.textMuted)
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
sidebarCard(title: state.selectedWindowIds.isEmpty ? "Top Apps" : "Selection") {
|
|
618
|
+
if state.selectedWindowIds.isEmpty {
|
|
619
|
+
VStack(alignment: .leading, spacing: 8) {
|
|
620
|
+
Text("Select a window to inspect it here.")
|
|
621
|
+
.font(Typo.mono(9))
|
|
622
|
+
.foregroundColor(Palette.textDim)
|
|
623
|
+
|
|
624
|
+
ForEach(topApps(in: display), id: \.name) { app in
|
|
625
|
+
HStack(spacing: 8) {
|
|
626
|
+
Text(app.name)
|
|
627
|
+
.font(Typo.monoBold(10))
|
|
628
|
+
.foregroundColor(Palette.text)
|
|
629
|
+
.lineLimit(1)
|
|
630
|
+
Spacer()
|
|
631
|
+
Text("\(app.count)")
|
|
632
|
+
.font(Typo.mono(9))
|
|
633
|
+
.foregroundColor(Palette.textMuted)
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
} else {
|
|
638
|
+
selectionSidebarContent
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
sidebarCard(title: "Keys") {
|
|
643
|
+
VStack(alignment: .leading, spacing: 7) {
|
|
644
|
+
sidebarShortcut("Arrows", "move through windows")
|
|
645
|
+
sidebarShortcut("/", "search by title or OCR")
|
|
646
|
+
sidebarShortcut("M", "jump to Screen Map")
|
|
647
|
+
sidebarShortcut("T", "tile selected window")
|
|
648
|
+
sidebarShortcut("Esc", "back or clear selection")
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
.padding(12)
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
@ViewBuilder
|
|
657
|
+
private var selectionSidebarContent: some View {
|
|
658
|
+
let selected = selectedWindows
|
|
659
|
+
|
|
660
|
+
if selected.count > 1 {
|
|
661
|
+
VStack(alignment: .leading, spacing: 8) {
|
|
662
|
+
Text("\(selected.count) windows selected")
|
|
663
|
+
.font(Typo.monoBold(10))
|
|
664
|
+
.foregroundColor(Palette.text)
|
|
665
|
+
if !state.selectedWindowSummaryText.isEmpty {
|
|
666
|
+
Text(state.selectedWindowSummaryText)
|
|
667
|
+
.font(Typo.mono(9))
|
|
668
|
+
.foregroundColor(Palette.textDim)
|
|
669
|
+
}
|
|
670
|
+
ForEach(Array(selected.prefix(5)), id: \.id) { window in
|
|
671
|
+
HStack(spacing: 8) {
|
|
672
|
+
Text(window.appName ?? "Unknown")
|
|
673
|
+
.font(Typo.monoBold(9))
|
|
674
|
+
.foregroundColor(window.isLattices ? Palette.running : Palette.text)
|
|
675
|
+
.lineLimit(1)
|
|
676
|
+
Spacer()
|
|
677
|
+
Text(sizeText(window.frame))
|
|
678
|
+
.font(Typo.mono(9))
|
|
679
|
+
.foregroundColor(Palette.textMuted)
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
} else if let window = selected.first {
|
|
684
|
+
VStack(alignment: .leading, spacing: 8) {
|
|
685
|
+
Text(window.appName ?? "Unknown")
|
|
686
|
+
.font(Typo.monoBold(10))
|
|
687
|
+
.foregroundColor(window.isLattices ? Palette.running : Palette.text)
|
|
688
|
+
Text(window.title.isEmpty ? "(untitled)" : window.title)
|
|
689
|
+
.font(Typo.mono(9))
|
|
690
|
+
.foregroundColor(Palette.textDim)
|
|
691
|
+
.fixedSize(horizontal: false, vertical: true)
|
|
692
|
+
sidebarMetric(label: "Size", value: sizeText(window.frame))
|
|
693
|
+
if let tile = window.tilePosition?.label {
|
|
694
|
+
sidebarMetric(label: "Tile", value: tile)
|
|
695
|
+
}
|
|
696
|
+
if let session = window.latticesSession {
|
|
697
|
+
sidebarMetric(label: "Session", value: session)
|
|
698
|
+
}
|
|
699
|
+
if let path = window.inventoryPath {
|
|
700
|
+
Text(path.description)
|
|
701
|
+
.font(Typo.mono(8))
|
|
702
|
+
.foregroundColor(Palette.textMuted)
|
|
703
|
+
.fixedSize(horizontal: false, vertical: true)
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
private func sidebarCard<Content: View>(
|
|
710
|
+
title: String,
|
|
711
|
+
@ViewBuilder content: () -> Content
|
|
712
|
+
) -> some View {
|
|
713
|
+
VStack(alignment: .leading, spacing: 10) {
|
|
714
|
+
Text(title.uppercased())
|
|
715
|
+
.font(Typo.mono(9))
|
|
716
|
+
.foregroundColor(Palette.textMuted)
|
|
717
|
+
content()
|
|
718
|
+
}
|
|
719
|
+
.padding(12)
|
|
720
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
721
|
+
.background(
|
|
722
|
+
RoundedRectangle(cornerRadius: 10, style: .continuous)
|
|
723
|
+
.fill(Palette.surface.opacity(0.55))
|
|
724
|
+
.overlay(
|
|
725
|
+
RoundedRectangle(cornerRadius: 10, style: .continuous)
|
|
726
|
+
.strokeBorder(Palette.border, lineWidth: 0.5)
|
|
727
|
+
)
|
|
728
|
+
)
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
private func sidebarMetric(label: String, value: String) -> some View {
|
|
732
|
+
HStack(alignment: .firstTextBaseline, spacing: 10) {
|
|
733
|
+
Text(label.uppercased())
|
|
734
|
+
.font(Typo.mono(8))
|
|
735
|
+
.foregroundColor(Palette.textMuted)
|
|
736
|
+
.frame(width: 74, alignment: .leading)
|
|
737
|
+
Text(value)
|
|
738
|
+
.font(Typo.mono(9))
|
|
739
|
+
.foregroundColor(Palette.text)
|
|
740
|
+
.fixedSize(horizontal: false, vertical: true)
|
|
741
|
+
Spacer(minLength: 0)
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
private func sidebarShortcut(_ key: String, _ label: String) -> some View {
|
|
746
|
+
HStack(spacing: 8) {
|
|
747
|
+
Text(key)
|
|
748
|
+
.font(Typo.monoBold(9))
|
|
749
|
+
.foregroundColor(Palette.text)
|
|
750
|
+
.padding(.horizontal, 6)
|
|
751
|
+
.padding(.vertical, 3)
|
|
752
|
+
.background(
|
|
753
|
+
RoundedRectangle(cornerRadius: 4, style: .continuous)
|
|
754
|
+
.fill(Palette.bg.opacity(0.8))
|
|
755
|
+
.overlay(
|
|
756
|
+
RoundedRectangle(cornerRadius: 4, style: .continuous)
|
|
757
|
+
.strokeBorder(Palette.border, lineWidth: 0.5)
|
|
758
|
+
)
|
|
759
|
+
)
|
|
760
|
+
Text(label)
|
|
761
|
+
.font(Typo.mono(9))
|
|
762
|
+
.foregroundColor(Palette.textDim)
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
381
766
|
private func spaceHeader(_ space: DesktopInventorySnapshot.SpaceGroup, display: DesktopInventorySnapshot.DisplayInfo) -> some View {
|
|
382
767
|
HStack(spacing: 5) {
|
|
383
768
|
Text("Space \(space.index)")
|
|
@@ -489,9 +874,17 @@ struct CommandModeView: View {
|
|
|
489
874
|
if indented {
|
|
490
875
|
Spacer().frame(width: 8)
|
|
491
876
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
877
|
+
Group {
|
|
878
|
+
if isSelected {
|
|
879
|
+
Image(systemName: "checkmark.circle.fill")
|
|
880
|
+
.font(.system(size: 10, weight: .semibold))
|
|
881
|
+
.foregroundColor(Palette.running)
|
|
882
|
+
} else {
|
|
883
|
+
Text(isLattices ? "●" : "•")
|
|
884
|
+
.font(.system(size: 7))
|
|
885
|
+
.foregroundColor(isLattices ? Palette.running : Palette.textDim)
|
|
886
|
+
}
|
|
887
|
+
}
|
|
495
888
|
if let app = appLabel {
|
|
496
889
|
Text(app)
|
|
497
890
|
.font(Typo.monoBold(10))
|
|
@@ -610,15 +1003,7 @@ struct CommandModeView: View {
|
|
|
610
1003
|
Menu("Tile All (\(selCount))") {
|
|
611
1004
|
ForEach(TilePosition.allCases) { tile in
|
|
612
1005
|
Button {
|
|
613
|
-
|
|
614
|
-
for (i, win) in windows.enumerated() {
|
|
615
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + Double(i) * 0.1) {
|
|
616
|
-
WindowTiler.tileWindowById(wid: win.id, pid: win.pid, to: tile)
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3 + Double(windows.count) * 0.1) {
|
|
620
|
-
state.desktopSnapshot = nil
|
|
621
|
-
}
|
|
1006
|
+
state.showAndDistributeSelected(in: .tile(tile))
|
|
622
1007
|
} label: {
|
|
623
1008
|
Label(tile.label, systemImage: tile.icon)
|
|
624
1009
|
}
|
|
@@ -683,9 +1068,6 @@ struct CommandModeView: View {
|
|
|
683
1068
|
private func windowTitle(_ window: DesktopInventorySnapshot.InventoryWindowInfo) -> String {
|
|
684
1069
|
let title = window.title
|
|
685
1070
|
if title.isEmpty { return "(untitled)" }
|
|
686
|
-
if title.count > 30 {
|
|
687
|
-
return String(title.prefix(27)) + "..."
|
|
688
|
-
}
|
|
689
1071
|
return title
|
|
690
1072
|
}
|
|
691
1073
|
|
|
@@ -693,6 +1075,57 @@ struct CommandModeView: View {
|
|
|
693
1075
|
"\(Int(frame.w))×\(Int(frame.h))"
|
|
694
1076
|
}
|
|
695
1077
|
|
|
1078
|
+
private var selectedWindows: [DesktopInventorySnapshot.InventoryWindowInfo] {
|
|
1079
|
+
state.flatWindowList.filter { state.selectedWindowIds.contains($0.id) }
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
private func windowCount(in display: DesktopInventorySnapshot.DisplayInfo) -> Int {
|
|
1083
|
+
display.spaces.reduce(0) { total, space in
|
|
1084
|
+
total + spaceWindowCount(space)
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
private func uniqueAppCount(in display: DesktopInventorySnapshot.DisplayInfo) -> Int {
|
|
1089
|
+
Set(display.spaces.flatMap { $0.apps.map(\.appName) }).count
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
private func latticesWindowCount(in display: DesktopInventorySnapshot.DisplayInfo) -> Int {
|
|
1093
|
+
display.spaces.reduce(0) { total, space in
|
|
1094
|
+
total + spaceLatticesCount(space)
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
private func spaceWindowCount(_ space: DesktopInventorySnapshot.SpaceGroup) -> Int {
|
|
1099
|
+
space.apps.reduce(0) { total, app in
|
|
1100
|
+
total + app.windows.count
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
private func spaceLatticesCount(_ space: DesktopInventorySnapshot.SpaceGroup) -> Int {
|
|
1105
|
+
space.apps.reduce(0) { total, app in
|
|
1106
|
+
total + app.windows.filter(\.isLattices).count
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
private func topApps(
|
|
1111
|
+
in display: DesktopInventorySnapshot.DisplayInfo
|
|
1112
|
+
) -> [(name: String, count: Int)] {
|
|
1113
|
+
var counts: [String: Int] = [:]
|
|
1114
|
+
for space in display.spaces {
|
|
1115
|
+
for app in space.apps {
|
|
1116
|
+
counts[app.appName, default: 0] += app.windows.count
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
return counts
|
|
1120
|
+
.map { (name: $0.key, count: $0.value) }
|
|
1121
|
+
.sorted { lhs, rhs in
|
|
1122
|
+
if lhs.count == rhs.count { return lhs.name < rhs.name }
|
|
1123
|
+
return lhs.count > rhs.count
|
|
1124
|
+
}
|
|
1125
|
+
.prefix(5)
|
|
1126
|
+
.map { $0 }
|
|
1127
|
+
}
|
|
1128
|
+
|
|
696
1129
|
/// Group items by their group label
|
|
697
1130
|
private var groupedItems: [(String, [CommandModeInventory.Item])] {
|
|
698
1131
|
var result: [(String, [CommandModeInventory.Item])] = []
|
|
@@ -835,10 +1268,16 @@ struct CommandModeView: View {
|
|
|
835
1268
|
if isDesktopInventory && state.desktopMode == .gridPreview {
|
|
836
1269
|
// Grid preview hints
|
|
837
1270
|
HStack(spacing: 12) {
|
|
1271
|
+
chordHint(key: "←→↑↓", label: "region")
|
|
1272
|
+
chordHint(key: "1-7", label: "corners/thirds")
|
|
1273
|
+
chordHint(key: "c", label: "center")
|
|
838
1274
|
chordHint(key: "↩", label: "apply layout")
|
|
839
1275
|
chordHint(key: "s", label: "apply layout")
|
|
840
1276
|
chordHint(key: "esc", label: "cancel")
|
|
841
1277
|
Spacer()
|
|
1278
|
+
Text(state.gridPreviewRegionLabel.uppercased())
|
|
1279
|
+
.font(Typo.mono(9))
|
|
1280
|
+
.foregroundColor(Palette.textDim)
|
|
842
1281
|
let shape = state.gridPreviewShape
|
|
843
1282
|
Text(shape.map(String.init).joined(separator: " + "))
|
|
844
1283
|
.font(Typo.monoBold(9))
|
|
@@ -850,6 +1289,9 @@ struct CommandModeView: View {
|
|
|
850
1289
|
chordHint(key: "↩", label: "select & front")
|
|
851
1290
|
chordHint(key: "⌘A", label: "select all")
|
|
852
1291
|
chordHint(key: "⇧↑↓", label: "multi-select")
|
|
1292
|
+
if state.isOrganizeFlow && state.selectedWindowIds.count > 1 {
|
|
1293
|
+
chordHint(key: "d", label: "organize")
|
|
1294
|
+
}
|
|
853
1295
|
if !state.selectedWindowIds.isEmpty {
|
|
854
1296
|
chordHint(key: "t", label: "tile")
|
|
855
1297
|
}
|
|
@@ -887,16 +1329,55 @@ struct CommandModeView: View {
|
|
|
887
1329
|
.foregroundColor(Palette.running)
|
|
888
1330
|
}
|
|
889
1331
|
}
|
|
1332
|
+
} else if isDesktopInventory && state.isOrganizeFlow && state.selectedWindowIds.count > 1 {
|
|
1333
|
+
HStack(spacing: 12) {
|
|
1334
|
+
chordHint(key: "d", label: "organize")
|
|
1335
|
+
chordHint(key: "⌘-click", label: "add/remove")
|
|
1336
|
+
chordHint(key: "⇧-click", label: "range")
|
|
1337
|
+
chordHint(key: "↩", label: "front")
|
|
1338
|
+
chordHint(key: "esc", label: "cancel")
|
|
1339
|
+
Spacer()
|
|
1340
|
+
Text("\(state.selectedWindowIds.count) selected")
|
|
1341
|
+
.font(Typo.mono(9))
|
|
1342
|
+
.foregroundColor(Palette.running)
|
|
1343
|
+
}
|
|
1344
|
+
} else if isDesktopInventory && state.isOrganizeFlow && !state.selectedWindowIds.isEmpty {
|
|
1345
|
+
HStack(spacing: 12) {
|
|
1346
|
+
chordHint(key: "⌘-click", label: "add more")
|
|
1347
|
+
chordHint(key: "d", label: "need 2+")
|
|
1348
|
+
chordHint(key: "↩", label: "front")
|
|
1349
|
+
chordHint(key: "esc", label: "cancel")
|
|
1350
|
+
Spacer()
|
|
1351
|
+
Text(state.organizeSelectionSummary)
|
|
1352
|
+
.font(Typo.mono(9))
|
|
1353
|
+
.foregroundColor(Palette.textDim)
|
|
1354
|
+
}
|
|
1355
|
+
} else if isDesktopInventory && state.isOrganizeFlow {
|
|
1356
|
+
HStack(spacing: 12) {
|
|
1357
|
+
chordHint(key: "click", label: "select")
|
|
1358
|
+
chordHint(key: "⌘-click", label: "add/remove")
|
|
1359
|
+
chordHint(key: "/", label: "search")
|
|
1360
|
+
chordHint(key: "esc", label: "cancel")
|
|
1361
|
+
Spacer()
|
|
1362
|
+
}
|
|
890
1363
|
} else if isDesktopInventory && state.selectedWindowIds.count > 1 {
|
|
891
1364
|
// Multi-selection active
|
|
892
1365
|
HStack(spacing: 12) {
|
|
893
|
-
chordHint(key: "s", label: "
|
|
1366
|
+
chordHint(key: "s", label: "grid preview")
|
|
1367
|
+
chordHint(key: "d", label: "distribute")
|
|
1368
|
+
chordHint(key: "s", label: "grid preview")
|
|
894
1369
|
chordHint(key: "↩", label: "front")
|
|
895
|
-
chordHint(key: "t", label: "
|
|
1370
|
+
chordHint(key: "t", label: "grid region")
|
|
896
1371
|
chordHint(key: "f", label: "focus")
|
|
897
1372
|
chordHint(key: "h", label: "highlight")
|
|
898
1373
|
chordHint(key: "esc", label: "clear")
|
|
899
1374
|
Spacer()
|
|
1375
|
+
if !state.selectedWindowSummaryText.isEmpty {
|
|
1376
|
+
Text(state.selectedWindowSummaryText)
|
|
1377
|
+
.font(Typo.mono(9))
|
|
1378
|
+
.foregroundColor(Palette.textDim)
|
|
1379
|
+
.lineLimit(1)
|
|
1380
|
+
}
|
|
900
1381
|
Text("\(state.selectedWindowIds.count) selected")
|
|
901
1382
|
.font(Typo.mono(9))
|
|
902
1383
|
.foregroundColor(Palette.running)
|
|
@@ -904,6 +1385,7 @@ struct CommandModeView: View {
|
|
|
904
1385
|
} else if isDesktopInventory && !state.selectedWindowIds.isEmpty {
|
|
905
1386
|
// Single selection active — browsing hints with direct shortcuts
|
|
906
1387
|
HStack(spacing: 12) {
|
|
1388
|
+
chordHint(key: "d", label: "organize")
|
|
907
1389
|
chordHint(key: "s", label: "show")
|
|
908
1390
|
chordHint(key: "↩", label: "front")
|
|
909
1391
|
chordHint(key: "f", label: "focus+close")
|
|
@@ -969,6 +1451,31 @@ struct CommandModeView: View {
|
|
|
969
1451
|
}
|
|
970
1452
|
}
|
|
971
1453
|
|
|
1454
|
+
private enum BannerTone {
|
|
1455
|
+
case neutral
|
|
1456
|
+
case accent
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
private func bannerBadge(_ text: String, tone: BannerTone) -> some View {
|
|
1460
|
+
let foreground = tone == .accent ? Palette.running : Palette.textDim
|
|
1461
|
+
let fill = tone == .accent ? Palette.running.opacity(0.10) : Palette.surface
|
|
1462
|
+
let stroke = tone == .accent ? Palette.running.opacity(0.30) : Palette.border
|
|
1463
|
+
|
|
1464
|
+
return Text(text)
|
|
1465
|
+
.font(Typo.mono(8))
|
|
1466
|
+
.foregroundColor(foreground)
|
|
1467
|
+
.padding(.horizontal, 6)
|
|
1468
|
+
.padding(.vertical, 3)
|
|
1469
|
+
.background(
|
|
1470
|
+
RoundedRectangle(cornerRadius: 8)
|
|
1471
|
+
.fill(fill)
|
|
1472
|
+
.overlay(
|
|
1473
|
+
RoundedRectangle(cornerRadius: 8)
|
|
1474
|
+
.strokeBorder(stroke, lineWidth: 0.5)
|
|
1475
|
+
)
|
|
1476
|
+
)
|
|
1477
|
+
}
|
|
1478
|
+
|
|
972
1479
|
private func actionButton(key: String, label: String, action: @escaping () -> Void) -> some View {
|
|
973
1480
|
Button(action: action) {
|
|
974
1481
|
HStack(spacing: 4) {
|
|
@@ -1082,6 +1589,9 @@ struct CommandModeView: View {
|
|
|
1082
1589
|
Text("LAYOUT PREVIEW")
|
|
1083
1590
|
.font(Typo.monoBold(10))
|
|
1084
1591
|
.foregroundColor(Palette.textDim)
|
|
1592
|
+
Text(state.gridPreviewRegionLabel.uppercased())
|
|
1593
|
+
.font(Typo.mono(9))
|
|
1594
|
+
.foregroundColor(Palette.textMuted)
|
|
1085
1595
|
Text(gridDesc)
|
|
1086
1596
|
.font(Typo.monoBold(10))
|
|
1087
1597
|
.foregroundColor(Palette.running)
|
|
@@ -1096,7 +1606,7 @@ struct CommandModeView: View {
|
|
|
1096
1606
|
divider
|
|
1097
1607
|
|
|
1098
1608
|
// Screen map: current positions (dimmed) + target grid (bright)
|
|
1099
|
-
screenMap(windows: windows, shape: shape)
|
|
1609
|
+
screenMap(windows: windows, shape: shape, placement: state.gridPreviewPlacement)
|
|
1100
1610
|
.frame(height: 160)
|
|
1101
1611
|
.padding(.horizontal, 12)
|
|
1102
1612
|
.padding(.vertical, 8)
|
|
@@ -1125,7 +1635,11 @@ struct CommandModeView: View {
|
|
|
1125
1635
|
// MARK: - Grid Preview Screen Map
|
|
1126
1636
|
|
|
1127
1637
|
/// Miniature proportional map of the screen showing current window positions and target grid slots
|
|
1128
|
-
private func screenMap(
|
|
1638
|
+
private func screenMap(
|
|
1639
|
+
windows: [DesktopInventorySnapshot.InventoryWindowInfo],
|
|
1640
|
+
shape: [Int],
|
|
1641
|
+
placement: PlacementSpec?
|
|
1642
|
+
) -> some View {
|
|
1129
1643
|
GeometryReader { geo in
|
|
1130
1644
|
let availW = geo.size.width
|
|
1131
1645
|
let availH = geo.size.height
|
|
@@ -1154,6 +1668,14 @@ struct CommandModeView: View {
|
|
|
1154
1668
|
)
|
|
1155
1669
|
.frame(width: mapW, height: mapH)
|
|
1156
1670
|
|
|
1671
|
+
if let placement {
|
|
1672
|
+
let region = placement.fractions
|
|
1673
|
+
RoundedRectangle(cornerRadius: 4)
|
|
1674
|
+
.strokeBorder(Palette.running.opacity(0.35), style: StrokeStyle(lineWidth: 1, dash: [6, 4]))
|
|
1675
|
+
.frame(width: mapW * region.2, height: mapH * region.3)
|
|
1676
|
+
.offset(x: mapW * region.0, y: mapH * region.1)
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1157
1679
|
// Current positions (dimmed)
|
|
1158
1680
|
ForEach(Array(windows.enumerated()), id: \.element.id) { idx, win in
|
|
1159
1681
|
let f = win.frame
|
|
@@ -1173,7 +1695,13 @@ struct CommandModeView: View {
|
|
|
1173
1695
|
}
|
|
1174
1696
|
|
|
1175
1697
|
// Target grid slots (bright)
|
|
1176
|
-
let slots = computeMapSlots(
|
|
1698
|
+
let slots = computeMapSlots(
|
|
1699
|
+
count: windows.count,
|
|
1700
|
+
shape: shape,
|
|
1701
|
+
mapW: mapW,
|
|
1702
|
+
mapH: mapH,
|
|
1703
|
+
region: placement?.fractions
|
|
1704
|
+
)
|
|
1177
1705
|
ForEach(Array(slots.enumerated()), id: \.offset) { idx, slot in
|
|
1178
1706
|
let win = idx < windows.count ? windows[idx] : nil
|
|
1179
1707
|
RoundedRectangle(cornerRadius: 2)
|
|
@@ -1204,15 +1732,30 @@ struct CommandModeView: View {
|
|
|
1204
1732
|
}
|
|
1205
1733
|
|
|
1206
1734
|
/// Compute grid slots scaled to the mini map dimensions
|
|
1207
|
-
private func computeMapSlots(
|
|
1735
|
+
private func computeMapSlots(
|
|
1736
|
+
count: Int,
|
|
1737
|
+
shape: [Int],
|
|
1738
|
+
mapW: CGFloat,
|
|
1739
|
+
mapH: CGFloat,
|
|
1740
|
+
region: (CGFloat, CGFloat, CGFloat, CGFloat)? = nil
|
|
1741
|
+
) -> [CGRect] {
|
|
1742
|
+
let regionX = mapW * (region?.0 ?? 0)
|
|
1743
|
+
let regionY = mapH * (region?.1 ?? 0)
|
|
1744
|
+
let regionW = mapW * (region?.2 ?? 1)
|
|
1745
|
+
let regionH = mapH * (region?.3 ?? 1)
|
|
1208
1746
|
let rowCount = shape.count
|
|
1209
|
-
let rowH =
|
|
1747
|
+
let rowH = regionH / CGFloat(rowCount)
|
|
1210
1748
|
var slots: [CGRect] = []
|
|
1211
1749
|
for (row, cols) in shape.enumerated() {
|
|
1212
|
-
let colW =
|
|
1213
|
-
let y = CGFloat(row) * rowH
|
|
1750
|
+
let colW = regionW / CGFloat(cols)
|
|
1751
|
+
let y = regionY + CGFloat(row) * rowH
|
|
1214
1752
|
for col in 0..<cols {
|
|
1215
|
-
slots.append(CGRect(
|
|
1753
|
+
slots.append(CGRect(
|
|
1754
|
+
x: regionX + CGFloat(col) * colW,
|
|
1755
|
+
y: y,
|
|
1756
|
+
width: colW,
|
|
1757
|
+
height: rowH
|
|
1758
|
+
))
|
|
1216
1759
|
}
|
|
1217
1760
|
}
|
|
1218
1761
|
return slots
|
|
@@ -1382,4 +1925,3 @@ struct CommandModeView: View {
|
|
|
1382
1925
|
}
|
|
1383
1926
|
}
|
|
1384
1927
|
}
|
|
1385
|
-
|