SPB Git

spb/zyquo-atlas Public License

The AI-native macOS web browser — every surface, intelligent.

Swift 75.2% JavaScript 22% Shell 2% Makefile 0.9%
2.8 KB · 79 lines swift
Raw Blame History
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