mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-22 13:31:16 +00:00
* 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
59 lines
2.5 KiB
Swift
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()
|
|
}
|
|
}
|