Files
openclaw/apps/macos/Sources/OpenClaw/QuickChatWindowPickerView.swift
Peter Steinberger c8fe8f102f feat(macos): Quick Chat area capture and focused-app text context (#110635)
* feat(macos): add Quick Chat capture context

* docs(macos): describe Quick Chat capture context

* fix(macos): bound the AX text walk with deadline, cancellation, and per-message timeout; keep distinct repeated text

* fix(macos): ancestry-scoped echo suppression and adapter-wide AX timeouts

* fix(macos): hard outer timeout abandons a hung AX walk

* fix(macos): truly abandon hung AX walks; hide the bar synchronously before area selection

* chore(i18n): refresh native locale artifacts for Quick Chat capture strings
2026-07-18 17:45:52 +01:00

59 lines
2.5 KiB
Swift

import SwiftUI
struct QuickChatWindowPickerView: View {
let candidates: [QuickChatWindowCandidate]
let onSelect: (QuickChatWindowCandidate) -> Void
let onCancel: () -> Void
@State private var hoveredWindowID: Int?
var body: some View {
ZStack(alignment: .topLeading) {
Color.black.opacity(0.18)
// Candidates arrive front-to-back for hit testing; paint back-to-front so the
// topmost window's highlight is never covered by an occluded window's cell.
ForEach(self.candidates.reversed()) { candidate in
let isHovered = self.hoveredWindowID == candidate.windowID
ZStack {
RoundedRectangle(cornerRadius: 10, style: .continuous)
.fill(Color.accentColor.opacity(isHovered ? 0.16 : 0.04))
RoundedRectangle(cornerRadius: 10, style: .continuous)
.stroke(Color.accentColor.opacity(isHovered ? 0.95 : 0.62), lineWidth: isHovered ? 3 : 2)
Text(verbatim: QuickChatWindowPickerLogic.labelText(
appName: candidate.appName,
title: candidate.title))
.font(.callout.weight(.medium))
.lineLimit(1)
.truncationMode(.tail)
.padding(.horizontal, 10)
.padding(.vertical, 6)
.frame(maxWidth: max(80, candidate.bounds.width - 20))
.background(.regularMaterial, in: Capsule())
.shadow(radius: 4, y: 1)
}
.frame(width: candidate.bounds.width, height: candidate.bounds.height)
.position(x: candidate.bounds.midX, y: candidate.bounds.midY)
.allowsHitTesting(false)
}
}
.contentShape(Rectangle())
.onContinuousHover { phase in
switch phase {
case let .active(point):
self.hoveredWindowID = QuickChatWindowPickerLogic.hitTest(self.candidates, at: point)?.windowID
case .ended:
self.hoveredWindowID = nil
}
}
.gesture(SpatialTapGesture().onEnded { value in
if let candidate = QuickChatWindowPickerLogic.hitTest(self.candidates, at: value.location) {
self.onSelect(candidate)
} else {
self.onCancel()
}
})
.ignoresSafeArea()
}
}