aerospace fork
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177import Commonimport Foundationimport SwiftUI
@MainActorstruct MenuBarLabel: View { @Environment(\.colorScheme) var menuColorScheme: ColorScheme @EnvironmentObject var viewModel: TrayMenuModel let color: Color? let style: MenuBarStyle?
let hStackSpacing = CGFloat(6) let itemSize = CGFloat(40) let itemBorderSize = CGFloat(3) let itemCornerRadius = CGFloat(6)
private var finalColor: Color { return color ?? (menuColorScheme == .dark ? Color.white : Color.black) }
init(style: MenuBarStyle? = nil, color: Color? = nil) { self.style = style self.color = color }
var body: some View { if #available(macOS 14, *) { // https://github.com/nikitabobko/AeroSpace/issues/1122 let renderer = ImageRenderer(content: menuBarContent) switch renderer.cgImage { // Using scale: 1 results in a blurry image for unknown reasons case let cgImage?: Image(cgImage, scale: 2, label: Text(viewModel.trayText))
// In case image can't be rendered fallback to plain text case nil: Text(viewModel.trayText) } } else { // macOS 13 and lower Text(viewModel.trayText) } }
var menuBarContent: some View { return HStack(spacing: hStackSpacing) { let style = style ?? viewModel.experimentalUISettings.displayStyle switch style { case .monospacedText: getText(for: .monospaced) case .systemText: getText(for: .default) case .squares: squares case .i3: squares let workspaces = viewModel.workspaces.filter { !$0.isEffectivelyEmpty && !$0.isVisible } if !workspaces.isEmpty { otherWorkspaces(with: workspaces) } case .i3Ordered: let modeItem = viewModel.trayItems.first { $0.type == .mode } if let modeItem { itemView(for: modeItem) modeSeparator(with: .monospaced) } let orderedWorkspaces = viewModel.workspaces.filter { !$0.isEffectivelyEmpty || $0.isVisible } ForEach(orderedWorkspaces, id: \.name) { item in let trayItem = TrayItem( type: .workspace, name: item.name, isActive: item.isFocused, hasFullscreenWindows: item.hasFullscreenWindows, ) itemView(for: trayItem) .opacity(item.isVisible ? 1 : 0.5) } } } }
private func getText(for design: Font.Design) -> some View { Text(viewModel.trayText) .font(.system(.largeTitle, design: design)) .foregroundStyle(finalColor) }
private var squares: some View { ForEach(viewModel.trayItems, id: \.id) { item in itemView(for: item) if item.type == .mode { modeSeparator(with: .monospaced) } } }
private func otherWorkspaces(with otherWorkspaces: [WorkspaceViewModel]) -> some View { Group { Text("|") .font(.system(.largeTitle)) .foregroundStyle(finalColor) .bold() .padding(.bottom, 6) ForEach(otherWorkspaces, id: \.name) { item in itemView(for: TrayItem(type: .workspace, name: item.name, isActive: false, hasFullscreenWindows: item.hasFullscreenWindows)) } } .opacity(0.6) }
private func modeSeparator(with design: Font.Design) -> some View { Text(":") .font(.system(.largeTitle, design: design)) .foregroundStyle(finalColor) .bold() }
@ViewBuilder fileprivate func itemView(for item: TrayItem) -> some View { let view = itemSubView(for: item) if item.hasFullscreenWindows { let strokeStyle = StrokeStyle(lineWidth: 2, lineCap: .square, lineJoin: .miter, miterLimit: 10, dash: [10, 5], dashPhase: 3) view .padding(4) .overlay { RoundedRectangle(cornerRadius: itemCornerRadius, style: .continuous) .strokeBorder(finalColor, style: strokeStyle) } } else { view } }
@ViewBuilder fileprivate func itemSubView(for item: TrayItem) -> some View { // If workspace name contains emojis we use the plain emoji in text to avoid visibility issues scaling the emoji to fit the squares if item.name.containsEmoji() { Text(item.name) .font(.system(.largeTitle)) .foregroundStyle(finalColor) .frame(height: itemSize) } else { if let imageName = item.systemImageName { Image(systemName: imageName) .resizable() .aspectRatio(contentMode: .fit) .symbolRenderingMode(.monochrome) .foregroundStyle(finalColor) .frame(width: itemSize, height: itemSize) } else { let text = Text(item.name) .font(.system(.largeTitle)) .bold() .padding(.horizontal, itemBorderSize * 2) .frame(height: itemSize) if item.isActive { ZStack { text.background { RoundedRectangle(cornerRadius: itemCornerRadius, style: .circular) } text.blendMode(.destinationOut) } .compositingGroup() .foregroundStyle(finalColor) .frame(height: itemSize) } else { text.background { RoundedRectangle(cornerRadius: itemCornerRadius, style: .continuous) .strokeBorder(lineWidth: itemBorderSize) } .foregroundStyle(finalColor) .frame(height: itemSize) } } } }}
extension String { fileprivate func containsEmoji() -> Bool { unicodeScalars.contains { $0.properties.isEmoji && $0.properties.isEmojiPresentation } }}