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// ToolbarView.swift3// Zyquo Atlas4//5// Author: Simon-Pierre Boucher6// Mail: contact@spboucher.ai7//8// The unified browser toolbar: back / forward / reload-or-stop, the omnibox,9// and a new-tab button. A determinate progress bar hairline sits under the10// toolbar while a page loads. The AI panel toggle and profile switcher attach11// here in later phases.12//1314import SwiftUI1516struct ToolbarView: View {17 @ObservedObject var tab: Tab18 let isAISidebarOpen: Bool19 let onSubmit: (String) -> Void20 let onNewTab: () -> Void21 let onToggleAI: () -> Void22 let onCustomize: () -> Void23 var downloadCount: Int = 024 var onToggleDownloads: () -> Void = {}25 @EnvironmentObject private var themeEngine: ThemeEngine26 @EnvironmentObject private var windowState: WindowState27 private var theme: AtlasTheme { themeEngine.theme }2829 var body: some View {30 VStack(spacing: 0) {31 HStack(spacing: ZyquoSpacing.xs) {32 navButton("chevron.backward", enabled: tab.canGoBack) { tab.goBack() }33 navButton("chevron.forward", enabled: tab.canGoForward) { tab.goForward() }34 if tab.isLoading {35 navButton("xmark", enabled: true) { tab.stop() }36 } else {37 navButton("arrow.clockwise", enabled: tab.url != nil) { tab.reload() }38 }39 readerButton4041 OmniboxView(tab: tab, onSubmit: onSubmit,42 onAsk: { windowState.askOmnibox($0) })43 .frame(maxWidth: .infinity)4445 navButton("plus", enabled: true, action: onNewTab)46 downloadsButton47 navButton("paintbrush", enabled: true, action: onCustomize)48 aiToggle49 }50 .padding(.horizontal, ZyquoSpacing.sm)51 .frame(height: themeEngine.layout.toolbarHeight)5253 progressBar54 }55 .background(theme.surface)56 .overlay(alignment: .bottom) {57 Rectangle()58 .fill(theme.border)59 .frame(height: ZyquoMetrics.hairline)60 }61 }6263 // MARK: - Progress6465 @ViewBuilder66 private var progressBar: some View {67 GeometryReader { geo in68 if tab.showsProgress {69 Rectangle()70 .fill(theme.accent)71 .frame(width: geo.size.width * max(0.02, tab.estimatedProgress))72 .animation(.easeOut(duration: 0.2), value: tab.estimatedProgress)73 }74 }75 .frame(height: 2)76 }7778 private var readerButton: some View {79 Button { windowState.toggleReader() } label: {80 Image(systemName: windowState.showReader ? "doc.plaintext.fill" : "doc.plaintext")81 .font(.system(size: 13, weight: .medium))82 .frame(width: 28, height: 28)83 .contentShape(Rectangle())84 }85 .buttonStyle(.plain)86 .foregroundStyle(windowState.showReader ? theme.accent : theme.textSecondary)87 .disabled(tab.url == nil)88 .help("Reader mode")89 }9091 private var downloadsButton: some View {92 Button(action: onToggleDownloads) {93 Image(systemName: "arrow.down.circle")94 .font(.system(size: 13, weight: .medium))95 .frame(width: 28, height: 28)96 .contentShape(Rectangle())97 .overlay(alignment: .topTrailing) {98 if downloadCount > 0 {99 Circle().fill(theme.accent).frame(width: 8, height: 8).offset(x: -4, y: 4)100 }101 }102 }103 .buttonStyle(.plain)104 .foregroundStyle(theme.textSecondary)105 .help("Downloads")106 }107108 private var aiToggle: some View {109 Button(action: onToggleAI) {110 Image(systemName: "sparkles")111 .font(.system(size: 13, weight: .medium))112 .frame(width: 28, height: 28)113 .contentShape(Rectangle())114 }115 .buttonStyle(.plain)116 .foregroundStyle(isAISidebarOpen ? theme.accentIndigo : theme.textSecondary)117 .help("Toggle Atlas AI sidebar")118 }119120 // MARK: - Buttons121122 private func navButton(_ symbol: String, enabled: Bool, action: @escaping () -> Void) -> some View {123 Button(action: action) {124 Image(systemName: symbol)125 .font(.system(size: 13, weight: .medium))126 .frame(width: 28, height: 28)127 .contentShape(Rectangle())128 }129 .buttonStyle(.plain)130 .foregroundStyle(enabled ? theme.textSecondary : theme.textTertiary.opacity(0.5))131 .disabled(!enabled)132 }133}134