spb/zyquo-atlas Public License
The AI-native macOS web browser — every surface, intelligent.
Swift 75.2%
JavaScript 22%
Shell 2%
Makefile 0.9%
1//2// SelectionToolbar.swift3// Zyquo Atlas4//5// Author: Simon-Pierre Boucher6// Mail: contact@spboucher.ai7//8// The floating toolbar that appears on in-page text selection (Explain /9// Summarize / Translate / Rewrite / Ask). Positioned near the selection rect;10// actions route the selected text to the AI sidebar. Dismisses on click-away11// or when the selection collapses (docs/AI-BROWSER-RESEARCH.md §4.4).12//1314import SwiftUI1516struct SelectionToolbar: View {17 @ObservedObject var tab: Tab18 @EnvironmentObject private var windowState: WindowState19 @EnvironmentObject private var themeEngine: ThemeEngine20 private var theme: AtlasTheme { themeEngine.theme }2122 var body: some View {23 GeometryReader { geo in24 if !tab.selectionText.isEmpty {25 let pos = anchor(in: geo.size)26 HStack(spacing: 2) {27 action("Explain", "text.magnifyingglass", .explainSelection)28 divider29 action("Summarize", "doc.text", .summarize)30 divider31 action("Translate", "globe", .translate)32 divider33 action("Rewrite", "pencil.and.outline", .rewrite)34 divider35 action("Ask", "sparkles", .ask)36 }37 .padding(.horizontal, ZyquoSpacing.xs)38 .frame(height: 34)39 .background(40 Capsule().fill(theme.surface)41 .shadow(color: ZyquoShadow.soft.color, radius: ZyquoShadow.soft.radius, y: ZyquoShadow.soft.y)42 )43 .overlay(Capsule().strokeBorder(theme.border, lineWidth: ZyquoMetrics.hairline))44 .position(x: pos.x, y: pos.y)45 .transition(.opacity)46 }47 }48 .allowsHitTesting(!tab.selectionText.isEmpty)49 }5051 private var divider: some View {52 Rectangle().fill(theme.border).frame(width: ZyquoMetrics.hairline, height: 18)53 }5455 private func action(_ title: String, _ symbol: String, _ ai: AIAction) -> some View {56 Button {57 let text = tab.selectionText58 windowState.runSelection(ai, text: text)59 } label: {60 Label(title, systemImage: symbol)61 .labelStyle(.iconOnly)62 .font(.system(size: 13))63 .frame(width: 30, height: 30)64 .contentShape(Rectangle())65 }66 .buttonStyle(.plain)67 .foregroundStyle(theme.accent)68 .help(title)69 }7071 /// Places the toolbar just above the selection, clamped to the content area.72 private func anchor(in size: CGSize) -> CGPoint {73 let r = tab.selectionRect74 let x = min(max(r.midX, 120), size.width - 120)75 let y = max(r.minY - 26, 26)76 return CGPoint(x: x, y: y)77 }78}79