// // ToolbarView.swift // Zyquo Atlas // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // // The unified browser toolbar: back / forward / reload-or-stop, the omnibox, // and a new-tab button. A determinate progress bar hairline sits under the // toolbar while a page loads. The AI panel toggle and profile switcher attach // here in later phases. // import SwiftUI struct ToolbarView: View { @ObservedObject var tab: Tab let isAISidebarOpen: Bool let onSubmit: (String) -> Void let onNewTab: () -> Void let onToggleAI: () -> Void let onCustomize: () -> Void var downloadCount: Int = 0 var onToggleDownloads: () -> Void = {} @EnvironmentObject private var themeEngine: ThemeEngine @EnvironmentObject private var windowState: WindowState private var theme: AtlasTheme { themeEngine.theme } var body: some View { VStack(spacing: 0) { HStack(spacing: ZyquoSpacing.xs) { navButton("chevron.backward", enabled: tab.canGoBack) { tab.goBack() } navButton("chevron.forward", enabled: tab.canGoForward) { tab.goForward() } if tab.isLoading { navButton("xmark", enabled: true) { tab.stop() } } else { navButton("arrow.clockwise", enabled: tab.url != nil) { tab.reload() } } readerButton OmniboxView(tab: tab, onSubmit: onSubmit, onAsk: { windowState.askOmnibox($0) }) .frame(maxWidth: .infinity) navButton("plus", enabled: true, action: onNewTab) downloadsButton navButton("paintbrush", enabled: true, action: onCustomize) aiToggle } .padding(.horizontal, ZyquoSpacing.sm) .frame(height: themeEngine.layout.toolbarHeight) progressBar } .background(theme.surface) .overlay(alignment: .bottom) { Rectangle() .fill(theme.border) .frame(height: ZyquoMetrics.hairline) } } // MARK: - Progress @ViewBuilder private var progressBar: some View { GeometryReader { geo in if tab.showsProgress { Rectangle() .fill(theme.accent) .frame(width: geo.size.width * max(0.02, tab.estimatedProgress)) .animation(.easeOut(duration: 0.2), value: tab.estimatedProgress) } } .frame(height: 2) } private var readerButton: some View { Button { windowState.toggleReader() } label: { Image(systemName: windowState.showReader ? "doc.plaintext.fill" : "doc.plaintext") .font(.system(size: 13, weight: .medium)) .frame(width: 28, height: 28) .contentShape(Rectangle()) } .buttonStyle(.plain) .foregroundStyle(windowState.showReader ? theme.accent : theme.textSecondary) .disabled(tab.url == nil) .help("Reader mode") } private var downloadsButton: some View { Button(action: onToggleDownloads) { Image(systemName: "arrow.down.circle") .font(.system(size: 13, weight: .medium)) .frame(width: 28, height: 28) .contentShape(Rectangle()) .overlay(alignment: .topTrailing) { if downloadCount > 0 { Circle().fill(theme.accent).frame(width: 8, height: 8).offset(x: -4, y: 4) } } } .buttonStyle(.plain) .foregroundStyle(theme.textSecondary) .help("Downloads") } private var aiToggle: some View { Button(action: onToggleAI) { Image(systemName: "sparkles") .font(.system(size: 13, weight: .medium)) .frame(width: 28, height: 28) .contentShape(Rectangle()) } .buttonStyle(.plain) .foregroundStyle(isAISidebarOpen ? theme.accentIndigo : theme.textSecondary) .help("Toggle Atlas AI sidebar") } // MARK: - Buttons private func navButton(_ symbol: String, enabled: Bool, action: @escaping () -> Void) -> some View { Button(action: action) { Image(systemName: symbol) .font(.system(size: 13, weight: .medium)) .frame(width: 28, height: 28) .contentShape(Rectangle()) } .buttonStyle(.plain) .foregroundStyle(enabled ? theme.textSecondary : theme.textTertiary.opacity(0.5)) .disabled(!enabled) } }