import AppKit import SwiftUI /// What the tab bar shows: one tab per connection, in Settings' order. final class TabBarModel: ObservableObject { struct Tab: Identifiable, Equatable { let id: UUID var title: String var address: String var unreachable: Bool } @Published var tabs: [Tab] = [] @Published var selection: UUID? var onSelect: (UUID) -> Void = { _ in } } /// Hosts the tabs. A click on a tab works even while another app is in /// front, rather than only bringing the viewer forward. final class TabBarHostingView: NSHostingView { override func acceptsFirstMouse(for event: NSEvent?) -> Bool { true } } /// The slim bar of installation names. It sits in the window's title bar /// area, so it takes the title bar's material in light and dark alike. struct TabBar: View { @ObservedObject var model: TabBarModel var body: some View { HStack(spacing: 4) { ForEach(model.tabs) { tab in TabButton(tab: tab, selected: tab.id == model.selection) { model.onSelect(tab.id) } } } .padding(.horizontal, 8) .frame(maxWidth: .infinity, maxHeight: .infinity) } } private struct TabButton: View { let tab: TabBarModel.Tab let selected: Bool let action: () -> Void @State private var hovering = false var body: some View { Button(action: action) { HStack(spacing: 5) { if tab.unreachable { Image(systemName: "exclamationmark.triangle.fill") .imageScale(.small) .foregroundStyle(.orange) } Text(tab.title) .lineLimit(1) .truncationMode(.tail) } .font(.system(size: 12, weight: selected ? .semibold : .regular)) .foregroundStyle(selected ? .primary : .secondary) .padding(.horizontal, 10) .frame(maxWidth: .infinity, minHeight: 24) .background( RoundedRectangle(cornerRadius: 6, style: .continuous) .fill(Color.primary.opacity(selected ? 0.12 : hovering ? 0.06 : 0)) ) .contentShape(Rectangle()) } .buttonStyle(.plain) .focusable(false) // the keyboard belongs to the dashboard; ⌃Tab switches .onHover { hovering = $0 } .help(tab.unreachable ? "\(tab.address) — not answering" : tab.address) .accessibilityAddTraits(selected ? .isSelected : []) } }