// // SelectionToolbar.swift // Zyquo Atlas // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // // The floating toolbar that appears on in-page text selection (Explain / // Summarize / Translate / Rewrite / Ask). Positioned near the selection rect; // actions route the selected text to the AI sidebar. Dismisses on click-away // or when the selection collapses (docs/AI-BROWSER-RESEARCH.md ยง4.4). // import SwiftUI struct SelectionToolbar: View { @ObservedObject var tab: Tab @EnvironmentObject private var windowState: WindowState @EnvironmentObject private var themeEngine: ThemeEngine private var theme: AtlasTheme { themeEngine.theme } var body: some View { GeometryReader { geo in if !tab.selectionText.isEmpty { let pos = anchor(in: geo.size) HStack(spacing: 2) { action("Explain", "text.magnifyingglass", .explainSelection) divider action("Summarize", "doc.text", .summarize) divider action("Translate", "globe", .translate) divider action("Rewrite", "pencil.and.outline", .rewrite) divider action("Ask", "sparkles", .ask) } .padding(.horizontal, ZyquoSpacing.xs) .frame(height: 34) .background( Capsule().fill(theme.surface) .shadow(color: ZyquoShadow.soft.color, radius: ZyquoShadow.soft.radius, y: ZyquoShadow.soft.y) ) .overlay(Capsule().strokeBorder(theme.border, lineWidth: ZyquoMetrics.hairline)) .position(x: pos.x, y: pos.y) .transition(.opacity) } } .allowsHitTesting(!tab.selectionText.isEmpty) } private var divider: some View { Rectangle().fill(theme.border).frame(width: ZyquoMetrics.hairline, height: 18) } private func action(_ title: String, _ symbol: String, _ ai: AIAction) -> some View { Button { let text = tab.selectionText windowState.runSelection(ai, text: text) } label: { Label(title, systemImage: symbol) .labelStyle(.iconOnly) .font(.system(size: 13)) .frame(width: 30, height: 30) .contentShape(Rectangle()) } .buttonStyle(.plain) .foregroundStyle(theme.accent) .help(title) } /// Places the toolbar just above the selection, clamped to the content area. private func anchor(in size: CGSize) -> CGPoint { let r = tab.selectionRect let x = min(max(r.midX, 120), size.width - 120) let y = max(r.minY - 26, 26) return CGPoint(x: x, y: y) } }