@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
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,13 +121,79 @@ 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
|
|
@@ -165,7 +273,7 @@ struct CommandModeView: View {
|
|
|
165
273
|
|
|
166
274
|
// MARK: - Desktop Inventory Content
|
|
167
275
|
|
|
168
|
-
private
|
|
276
|
+
private func desktopInventoryContent(contentWidth: CGFloat) -> some View {
|
|
169
277
|
VStack(spacing: 0) {
|
|
170
278
|
if state.isSearching {
|
|
171
279
|
searchBar
|
|
@@ -177,20 +285,7 @@ struct CommandModeView: View {
|
|
|
177
285
|
ZStack {
|
|
178
286
|
Group {
|
|
179
287
|
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
|
-
}
|
|
288
|
+
inventoryColumns(snapshot: snapshot, contentWidth: contentWidth)
|
|
194
289
|
} else {
|
|
195
290
|
desktopEmptyState
|
|
196
291
|
}
|
|
@@ -216,6 +311,53 @@ struct CommandModeView: View {
|
|
|
216
311
|
}
|
|
217
312
|
}
|
|
218
313
|
|
|
314
|
+
@ViewBuilder
|
|
315
|
+
private func inventoryColumns(snapshot: DesktopInventorySnapshot, contentWidth: CGFloat) -> some View {
|
|
316
|
+
if shouldShowEmbeddedSidebar(snapshot: snapshot, contentWidth: contentWidth),
|
|
317
|
+
let display = snapshot.displays.first {
|
|
318
|
+
embeddedSingleDisplayLayout(display: display, contentWidth: contentWidth)
|
|
319
|
+
} else {
|
|
320
|
+
ScrollView(.horizontal, showsIndicators: false) {
|
|
321
|
+
HStack(alignment: .top, spacing: 0) {
|
|
322
|
+
let total = snapshot.displays.count
|
|
323
|
+
ForEach(Array(snapshot.displays.enumerated()), id: \.element.id) { idx, display in
|
|
324
|
+
if idx > 0 {
|
|
325
|
+
Rectangle()
|
|
326
|
+
.fill(Palette.border)
|
|
327
|
+
.frame(width: 0.5)
|
|
328
|
+
}
|
|
329
|
+
displayColumn(display, index: idx, total: total)
|
|
330
|
+
.frame(width: displayColumnWidth(for: contentWidth))
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
private func shouldShowEmbeddedSidebar(snapshot: DesktopInventorySnapshot, contentWidth: CGFloat) -> Bool {
|
|
338
|
+
isEmbedded && snapshot.displays.count == 1 && contentWidth >= 900
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private func embeddedSingleDisplayLayout(
|
|
342
|
+
display: DesktopInventorySnapshot.DisplayInfo,
|
|
343
|
+
contentWidth: CGFloat
|
|
344
|
+
) -> some View {
|
|
345
|
+
let sidebarWidth = min(max(contentWidth * 0.28, 250), 330)
|
|
346
|
+
let mainWidth = max(contentWidth - sidebarWidth - 0.5, 620)
|
|
347
|
+
|
|
348
|
+
return HStack(alignment: .top, spacing: 0) {
|
|
349
|
+
displayColumn(display, index: 0, total: 1)
|
|
350
|
+
.frame(width: mainWidth)
|
|
351
|
+
|
|
352
|
+
Rectangle()
|
|
353
|
+
.fill(Palette.border)
|
|
354
|
+
.frame(width: 0.5)
|
|
355
|
+
|
|
356
|
+
embeddedInventorySidebar(display: display)
|
|
357
|
+
.frame(width: sidebarWidth)
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
219
361
|
private var filterPillBar: some View {
|
|
220
362
|
HStack(spacing: 6) {
|
|
221
363
|
ForEach(FilterPreset.allCases, id: \.rawValue) { preset in
|
|
@@ -378,6 +520,210 @@ struct CommandModeView: View {
|
|
|
378
520
|
.padding(.vertical, 8)
|
|
379
521
|
}
|
|
380
522
|
|
|
523
|
+
private func embeddedInventorySidebar(display: DesktopInventorySnapshot.DisplayInfo) -> some View {
|
|
524
|
+
ScrollView {
|
|
525
|
+
VStack(alignment: .leading, spacing: 12) {
|
|
526
|
+
sidebarCard(title: "Overview") {
|
|
527
|
+
sidebarMetric(label: "Display", value: display.name)
|
|
528
|
+
sidebarMetric(
|
|
529
|
+
label: "Visible",
|
|
530
|
+
value: "\(display.visibleFrame.w)×\(display.visibleFrame.h)"
|
|
531
|
+
)
|
|
532
|
+
sidebarMetric(
|
|
533
|
+
label: "Current Space",
|
|
534
|
+
value: "Space \(display.currentSpaceIndex)"
|
|
535
|
+
)
|
|
536
|
+
sidebarMetric(
|
|
537
|
+
label: "Windows",
|
|
538
|
+
value: "\(windowCount(in: display)) total"
|
|
539
|
+
)
|
|
540
|
+
sidebarMetric(
|
|
541
|
+
label: "Apps",
|
|
542
|
+
value: "\(uniqueAppCount(in: display)) active"
|
|
543
|
+
)
|
|
544
|
+
sidebarMetric(
|
|
545
|
+
label: "Lattices",
|
|
546
|
+
value: "\(latticesWindowCount(in: display)) tagged"
|
|
547
|
+
)
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
sidebarCard(title: "Spaces") {
|
|
551
|
+
VStack(alignment: .leading, spacing: 8) {
|
|
552
|
+
ForEach(display.spaces) { space in
|
|
553
|
+
HStack(alignment: .top, spacing: 8) {
|
|
554
|
+
VStack(alignment: .leading, spacing: 2) {
|
|
555
|
+
HStack(spacing: 5) {
|
|
556
|
+
Text("Space \(space.index)")
|
|
557
|
+
.font(Typo.monoBold(10))
|
|
558
|
+
.foregroundColor(space.isCurrent ? Palette.running : Palette.text)
|
|
559
|
+
if space.isCurrent {
|
|
560
|
+
Text("active")
|
|
561
|
+
.font(Typo.mono(8))
|
|
562
|
+
.foregroundColor(Palette.running.opacity(0.75))
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
Text("\(spaceWindowCount(space)) windows across \(space.apps.count) apps")
|
|
566
|
+
.font(Typo.mono(9))
|
|
567
|
+
.foregroundColor(Palette.textDim)
|
|
568
|
+
}
|
|
569
|
+
Spacer()
|
|
570
|
+
Text("\(spaceLatticesCount(space))")
|
|
571
|
+
.font(Typo.mono(9))
|
|
572
|
+
.foregroundColor(Palette.textMuted)
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
sidebarCard(title: state.selectedWindowIds.isEmpty ? "Top Apps" : "Selection") {
|
|
579
|
+
if state.selectedWindowIds.isEmpty {
|
|
580
|
+
VStack(alignment: .leading, spacing: 8) {
|
|
581
|
+
Text("Select a window to inspect it here.")
|
|
582
|
+
.font(Typo.mono(9))
|
|
583
|
+
.foregroundColor(Palette.textDim)
|
|
584
|
+
|
|
585
|
+
ForEach(topApps(in: display), id: \.name) { app in
|
|
586
|
+
HStack(spacing: 8) {
|
|
587
|
+
Text(app.name)
|
|
588
|
+
.font(Typo.monoBold(10))
|
|
589
|
+
.foregroundColor(Palette.text)
|
|
590
|
+
.lineLimit(1)
|
|
591
|
+
Spacer()
|
|
592
|
+
Text("\(app.count)")
|
|
593
|
+
.font(Typo.mono(9))
|
|
594
|
+
.foregroundColor(Palette.textMuted)
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
} else {
|
|
599
|
+
selectionSidebarContent
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
sidebarCard(title: "Keys") {
|
|
604
|
+
VStack(alignment: .leading, spacing: 7) {
|
|
605
|
+
sidebarShortcut("Arrows", "move through windows")
|
|
606
|
+
sidebarShortcut("/", "search by title or OCR")
|
|
607
|
+
sidebarShortcut("M", "jump to Screen Map")
|
|
608
|
+
sidebarShortcut("T", "tile selected window")
|
|
609
|
+
sidebarShortcut("Esc", "back or clear selection")
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
.padding(12)
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
@ViewBuilder
|
|
618
|
+
private var selectionSidebarContent: some View {
|
|
619
|
+
let selected = selectedWindows
|
|
620
|
+
|
|
621
|
+
if selected.count > 1 {
|
|
622
|
+
VStack(alignment: .leading, spacing: 8) {
|
|
623
|
+
Text("\(selected.count) windows selected")
|
|
624
|
+
.font(Typo.monoBold(10))
|
|
625
|
+
.foregroundColor(Palette.text)
|
|
626
|
+
if !state.selectedWindowSummaryText.isEmpty {
|
|
627
|
+
Text(state.selectedWindowSummaryText)
|
|
628
|
+
.font(Typo.mono(9))
|
|
629
|
+
.foregroundColor(Palette.textDim)
|
|
630
|
+
}
|
|
631
|
+
ForEach(Array(selected.prefix(5)), id: \.id) { window in
|
|
632
|
+
HStack(spacing: 8) {
|
|
633
|
+
Text(window.appName ?? "Unknown")
|
|
634
|
+
.font(Typo.monoBold(9))
|
|
635
|
+
.foregroundColor(window.isLattices ? Palette.running : Palette.text)
|
|
636
|
+
.lineLimit(1)
|
|
637
|
+
Spacer()
|
|
638
|
+
Text(sizeText(window.frame))
|
|
639
|
+
.font(Typo.mono(9))
|
|
640
|
+
.foregroundColor(Palette.textMuted)
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
} else if let window = selected.first {
|
|
645
|
+
VStack(alignment: .leading, spacing: 8) {
|
|
646
|
+
Text(window.appName ?? "Unknown")
|
|
647
|
+
.font(Typo.monoBold(10))
|
|
648
|
+
.foregroundColor(window.isLattices ? Palette.running : Palette.text)
|
|
649
|
+
Text(window.title.isEmpty ? "(untitled)" : window.title)
|
|
650
|
+
.font(Typo.mono(9))
|
|
651
|
+
.foregroundColor(Palette.textDim)
|
|
652
|
+
.fixedSize(horizontal: false, vertical: true)
|
|
653
|
+
sidebarMetric(label: "Size", value: sizeText(window.frame))
|
|
654
|
+
if let tile = window.tilePosition?.label {
|
|
655
|
+
sidebarMetric(label: "Tile", value: tile)
|
|
656
|
+
}
|
|
657
|
+
if let session = window.latticesSession {
|
|
658
|
+
sidebarMetric(label: "Session", value: session)
|
|
659
|
+
}
|
|
660
|
+
if let path = window.inventoryPath {
|
|
661
|
+
Text(path.description)
|
|
662
|
+
.font(Typo.mono(8))
|
|
663
|
+
.foregroundColor(Palette.textMuted)
|
|
664
|
+
.fixedSize(horizontal: false, vertical: true)
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
private func sidebarCard<Content: View>(
|
|
671
|
+
title: String,
|
|
672
|
+
@ViewBuilder content: () -> Content
|
|
673
|
+
) -> some View {
|
|
674
|
+
VStack(alignment: .leading, spacing: 10) {
|
|
675
|
+
Text(title.uppercased())
|
|
676
|
+
.font(Typo.mono(9))
|
|
677
|
+
.foregroundColor(Palette.textMuted)
|
|
678
|
+
content()
|
|
679
|
+
}
|
|
680
|
+
.padding(12)
|
|
681
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
682
|
+
.background(
|
|
683
|
+
RoundedRectangle(cornerRadius: 10, style: .continuous)
|
|
684
|
+
.fill(Palette.surface.opacity(0.55))
|
|
685
|
+
.overlay(
|
|
686
|
+
RoundedRectangle(cornerRadius: 10, style: .continuous)
|
|
687
|
+
.strokeBorder(Palette.border, lineWidth: 0.5)
|
|
688
|
+
)
|
|
689
|
+
)
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
private func sidebarMetric(label: String, value: String) -> some View {
|
|
693
|
+
HStack(alignment: .firstTextBaseline, spacing: 10) {
|
|
694
|
+
Text(label.uppercased())
|
|
695
|
+
.font(Typo.mono(8))
|
|
696
|
+
.foregroundColor(Palette.textMuted)
|
|
697
|
+
.frame(width: 74, alignment: .leading)
|
|
698
|
+
Text(value)
|
|
699
|
+
.font(Typo.mono(9))
|
|
700
|
+
.foregroundColor(Palette.text)
|
|
701
|
+
.fixedSize(horizontal: false, vertical: true)
|
|
702
|
+
Spacer(minLength: 0)
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
private func sidebarShortcut(_ key: String, _ label: String) -> some View {
|
|
707
|
+
HStack(spacing: 8) {
|
|
708
|
+
Text(key)
|
|
709
|
+
.font(Typo.monoBold(9))
|
|
710
|
+
.foregroundColor(Palette.text)
|
|
711
|
+
.padding(.horizontal, 6)
|
|
712
|
+
.padding(.vertical, 3)
|
|
713
|
+
.background(
|
|
714
|
+
RoundedRectangle(cornerRadius: 4, style: .continuous)
|
|
715
|
+
.fill(Palette.bg.opacity(0.8))
|
|
716
|
+
.overlay(
|
|
717
|
+
RoundedRectangle(cornerRadius: 4, style: .continuous)
|
|
718
|
+
.strokeBorder(Palette.border, lineWidth: 0.5)
|
|
719
|
+
)
|
|
720
|
+
)
|
|
721
|
+
Text(label)
|
|
722
|
+
.font(Typo.mono(9))
|
|
723
|
+
.foregroundColor(Palette.textDim)
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
381
727
|
private func spaceHeader(_ space: DesktopInventorySnapshot.SpaceGroup, display: DesktopInventorySnapshot.DisplayInfo) -> some View {
|
|
382
728
|
HStack(spacing: 5) {
|
|
383
729
|
Text("Space \(space.index)")
|
|
@@ -610,15 +956,7 @@ struct CommandModeView: View {
|
|
|
610
956
|
Menu("Tile All (\(selCount))") {
|
|
611
957
|
ForEach(TilePosition.allCases) { tile in
|
|
612
958
|
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
|
-
}
|
|
959
|
+
state.showAndDistributeSelected(in: .tile(tile))
|
|
622
960
|
} label: {
|
|
623
961
|
Label(tile.label, systemImage: tile.icon)
|
|
624
962
|
}
|
|
@@ -683,9 +1021,6 @@ struct CommandModeView: View {
|
|
|
683
1021
|
private func windowTitle(_ window: DesktopInventorySnapshot.InventoryWindowInfo) -> String {
|
|
684
1022
|
let title = window.title
|
|
685
1023
|
if title.isEmpty { return "(untitled)" }
|
|
686
|
-
if title.count > 30 {
|
|
687
|
-
return String(title.prefix(27)) + "..."
|
|
688
|
-
}
|
|
689
1024
|
return title
|
|
690
1025
|
}
|
|
691
1026
|
|
|
@@ -693,6 +1028,57 @@ struct CommandModeView: View {
|
|
|
693
1028
|
"\(Int(frame.w))×\(Int(frame.h))"
|
|
694
1029
|
}
|
|
695
1030
|
|
|
1031
|
+
private var selectedWindows: [DesktopInventorySnapshot.InventoryWindowInfo] {
|
|
1032
|
+
state.flatWindowList.filter { state.selectedWindowIds.contains($0.id) }
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
private func windowCount(in display: DesktopInventorySnapshot.DisplayInfo) -> Int {
|
|
1036
|
+
display.spaces.reduce(0) { total, space in
|
|
1037
|
+
total + spaceWindowCount(space)
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
private func uniqueAppCount(in display: DesktopInventorySnapshot.DisplayInfo) -> Int {
|
|
1042
|
+
Set(display.spaces.flatMap { $0.apps.map(\.appName) }).count
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
private func latticesWindowCount(in display: DesktopInventorySnapshot.DisplayInfo) -> Int {
|
|
1046
|
+
display.spaces.reduce(0) { total, space in
|
|
1047
|
+
total + spaceLatticesCount(space)
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
private func spaceWindowCount(_ space: DesktopInventorySnapshot.SpaceGroup) -> Int {
|
|
1052
|
+
space.apps.reduce(0) { total, app in
|
|
1053
|
+
total + app.windows.count
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
private func spaceLatticesCount(_ space: DesktopInventorySnapshot.SpaceGroup) -> Int {
|
|
1058
|
+
space.apps.reduce(0) { total, app in
|
|
1059
|
+
total + app.windows.filter(\.isLattices).count
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
private func topApps(
|
|
1064
|
+
in display: DesktopInventorySnapshot.DisplayInfo
|
|
1065
|
+
) -> [(name: String, count: Int)] {
|
|
1066
|
+
var counts: [String: Int] = [:]
|
|
1067
|
+
for space in display.spaces {
|
|
1068
|
+
for app in space.apps {
|
|
1069
|
+
counts[app.appName, default: 0] += app.windows.count
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
return counts
|
|
1073
|
+
.map { (name: $0.key, count: $0.value) }
|
|
1074
|
+
.sorted { lhs, rhs in
|
|
1075
|
+
if lhs.count == rhs.count { return lhs.name < rhs.name }
|
|
1076
|
+
return lhs.count > rhs.count
|
|
1077
|
+
}
|
|
1078
|
+
.prefix(5)
|
|
1079
|
+
.map { $0 }
|
|
1080
|
+
}
|
|
1081
|
+
|
|
696
1082
|
/// Group items by their group label
|
|
697
1083
|
private var groupedItems: [(String, [CommandModeInventory.Item])] {
|
|
698
1084
|
var result: [(String, [CommandModeInventory.Item])] = []
|
|
@@ -835,10 +1221,16 @@ struct CommandModeView: View {
|
|
|
835
1221
|
if isDesktopInventory && state.desktopMode == .gridPreview {
|
|
836
1222
|
// Grid preview hints
|
|
837
1223
|
HStack(spacing: 12) {
|
|
1224
|
+
chordHint(key: "←→↑↓", label: "region")
|
|
1225
|
+
chordHint(key: "1-7", label: "corners/thirds")
|
|
1226
|
+
chordHint(key: "c", label: "center")
|
|
838
1227
|
chordHint(key: "↩", label: "apply layout")
|
|
839
1228
|
chordHint(key: "s", label: "apply layout")
|
|
840
1229
|
chordHint(key: "esc", label: "cancel")
|
|
841
1230
|
Spacer()
|
|
1231
|
+
Text(state.gridPreviewRegionLabel.uppercased())
|
|
1232
|
+
.font(Typo.mono(9))
|
|
1233
|
+
.foregroundColor(Palette.textDim)
|
|
842
1234
|
let shape = state.gridPreviewShape
|
|
843
1235
|
Text(shape.map(String.init).joined(separator: " + "))
|
|
844
1236
|
.font(Typo.monoBold(9))
|
|
@@ -890,13 +1282,19 @@ struct CommandModeView: View {
|
|
|
890
1282
|
} else if isDesktopInventory && state.selectedWindowIds.count > 1 {
|
|
891
1283
|
// Multi-selection active
|
|
892
1284
|
HStack(spacing: 12) {
|
|
893
|
-
chordHint(key: "s", label: "
|
|
1285
|
+
chordHint(key: "s", label: "grid preview")
|
|
894
1286
|
chordHint(key: "↩", label: "front")
|
|
895
|
-
chordHint(key: "t", label: "
|
|
1287
|
+
chordHint(key: "t", label: "grid region")
|
|
896
1288
|
chordHint(key: "f", label: "focus")
|
|
897
1289
|
chordHint(key: "h", label: "highlight")
|
|
898
1290
|
chordHint(key: "esc", label: "clear")
|
|
899
1291
|
Spacer()
|
|
1292
|
+
if !state.selectedWindowSummaryText.isEmpty {
|
|
1293
|
+
Text(state.selectedWindowSummaryText)
|
|
1294
|
+
.font(Typo.mono(9))
|
|
1295
|
+
.foregroundColor(Palette.textDim)
|
|
1296
|
+
.lineLimit(1)
|
|
1297
|
+
}
|
|
900
1298
|
Text("\(state.selectedWindowIds.count) selected")
|
|
901
1299
|
.font(Typo.mono(9))
|
|
902
1300
|
.foregroundColor(Palette.running)
|
|
@@ -1082,6 +1480,9 @@ struct CommandModeView: View {
|
|
|
1082
1480
|
Text("LAYOUT PREVIEW")
|
|
1083
1481
|
.font(Typo.monoBold(10))
|
|
1084
1482
|
.foregroundColor(Palette.textDim)
|
|
1483
|
+
Text(state.gridPreviewRegionLabel.uppercased())
|
|
1484
|
+
.font(Typo.mono(9))
|
|
1485
|
+
.foregroundColor(Palette.textMuted)
|
|
1085
1486
|
Text(gridDesc)
|
|
1086
1487
|
.font(Typo.monoBold(10))
|
|
1087
1488
|
.foregroundColor(Palette.running)
|
|
@@ -1096,7 +1497,7 @@ struct CommandModeView: View {
|
|
|
1096
1497
|
divider
|
|
1097
1498
|
|
|
1098
1499
|
// Screen map: current positions (dimmed) + target grid (bright)
|
|
1099
|
-
screenMap(windows: windows, shape: shape)
|
|
1500
|
+
screenMap(windows: windows, shape: shape, placement: state.gridPreviewPlacement)
|
|
1100
1501
|
.frame(height: 160)
|
|
1101
1502
|
.padding(.horizontal, 12)
|
|
1102
1503
|
.padding(.vertical, 8)
|
|
@@ -1125,7 +1526,11 @@ struct CommandModeView: View {
|
|
|
1125
1526
|
// MARK: - Grid Preview Screen Map
|
|
1126
1527
|
|
|
1127
1528
|
/// Miniature proportional map of the screen showing current window positions and target grid slots
|
|
1128
|
-
private func screenMap(
|
|
1529
|
+
private func screenMap(
|
|
1530
|
+
windows: [DesktopInventorySnapshot.InventoryWindowInfo],
|
|
1531
|
+
shape: [Int],
|
|
1532
|
+
placement: PlacementSpec?
|
|
1533
|
+
) -> some View {
|
|
1129
1534
|
GeometryReader { geo in
|
|
1130
1535
|
let availW = geo.size.width
|
|
1131
1536
|
let availH = geo.size.height
|
|
@@ -1154,6 +1559,14 @@ struct CommandModeView: View {
|
|
|
1154
1559
|
)
|
|
1155
1560
|
.frame(width: mapW, height: mapH)
|
|
1156
1561
|
|
|
1562
|
+
if let placement {
|
|
1563
|
+
let region = placement.fractions
|
|
1564
|
+
RoundedRectangle(cornerRadius: 4)
|
|
1565
|
+
.strokeBorder(Palette.running.opacity(0.35), style: StrokeStyle(lineWidth: 1, dash: [6, 4]))
|
|
1566
|
+
.frame(width: mapW * region.2, height: mapH * region.3)
|
|
1567
|
+
.offset(x: mapW * region.0, y: mapH * region.1)
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1157
1570
|
// Current positions (dimmed)
|
|
1158
1571
|
ForEach(Array(windows.enumerated()), id: \.element.id) { idx, win in
|
|
1159
1572
|
let f = win.frame
|
|
@@ -1173,7 +1586,13 @@ struct CommandModeView: View {
|
|
|
1173
1586
|
}
|
|
1174
1587
|
|
|
1175
1588
|
// Target grid slots (bright)
|
|
1176
|
-
let slots = computeMapSlots(
|
|
1589
|
+
let slots = computeMapSlots(
|
|
1590
|
+
count: windows.count,
|
|
1591
|
+
shape: shape,
|
|
1592
|
+
mapW: mapW,
|
|
1593
|
+
mapH: mapH,
|
|
1594
|
+
region: placement?.fractions
|
|
1595
|
+
)
|
|
1177
1596
|
ForEach(Array(slots.enumerated()), id: \.offset) { idx, slot in
|
|
1178
1597
|
let win = idx < windows.count ? windows[idx] : nil
|
|
1179
1598
|
RoundedRectangle(cornerRadius: 2)
|
|
@@ -1204,15 +1623,30 @@ struct CommandModeView: View {
|
|
|
1204
1623
|
}
|
|
1205
1624
|
|
|
1206
1625
|
/// Compute grid slots scaled to the mini map dimensions
|
|
1207
|
-
private func computeMapSlots(
|
|
1626
|
+
private func computeMapSlots(
|
|
1627
|
+
count: Int,
|
|
1628
|
+
shape: [Int],
|
|
1629
|
+
mapW: CGFloat,
|
|
1630
|
+
mapH: CGFloat,
|
|
1631
|
+
region: (CGFloat, CGFloat, CGFloat, CGFloat)? = nil
|
|
1632
|
+
) -> [CGRect] {
|
|
1633
|
+
let regionX = mapW * (region?.0 ?? 0)
|
|
1634
|
+
let regionY = mapH * (region?.1 ?? 0)
|
|
1635
|
+
let regionW = mapW * (region?.2 ?? 1)
|
|
1636
|
+
let regionH = mapH * (region?.3 ?? 1)
|
|
1208
1637
|
let rowCount = shape.count
|
|
1209
|
-
let rowH =
|
|
1638
|
+
let rowH = regionH / CGFloat(rowCount)
|
|
1210
1639
|
var slots: [CGRect] = []
|
|
1211
1640
|
for (row, cols) in shape.enumerated() {
|
|
1212
|
-
let colW =
|
|
1213
|
-
let y = CGFloat(row) * rowH
|
|
1641
|
+
let colW = regionW / CGFloat(cols)
|
|
1642
|
+
let y = regionY + CGFloat(row) * rowH
|
|
1214
1643
|
for col in 0..<cols {
|
|
1215
|
-
slots.append(CGRect(
|
|
1644
|
+
slots.append(CGRect(
|
|
1645
|
+
x: regionX + CGFloat(col) * colW,
|
|
1646
|
+
y: y,
|
|
1647
|
+
width: colW,
|
|
1648
|
+
height: rowH
|
|
1649
|
+
))
|
|
1216
1650
|
}
|
|
1217
1651
|
}
|
|
1218
1652
|
return slots
|
|
@@ -1382,4 +1816,3 @@ struct CommandModeView: View {
|
|
|
1382
1816
|
}
|
|
1383
1817
|
}
|
|
1384
1818
|
}
|
|
1385
|
-
|