@@ -13,7 +13,10 @@ public func menuBar(viewModel: TrayMenuModel) -> some Scene { // todo should it
1313 Divider ( )
1414 if let token: RunSessionGuard = . isServerEnabled {
1515 Text ( " Workspaces: " )
16- ForEach ( viewModel. workspaces, id: \. name) { workspace in
16+ let filteredWorkspaces = viewModel. workspaces. filter {
17+ viewModel. experimentalUISettings. filterEmptyWorkspacesFromMenu ? !$0. suffix. isEmpty : true
18+ }
19+ ForEach ( filteredWorkspaces, id: \. name) { workspace in
1720 Button {
1821 Task {
1922 try await runSession ( . menuBarButton, token) { _ = Workspace . get ( byName: workspace. name) . focusWorkspace ( ) }
@@ -26,6 +29,8 @@ public func menuBar(viewModel: TrayMenuModel) -> some Scene { // todo should it
2629 }
2730 Divider ( )
2831 }
32+ getExperimentalUISettingsMenu ( viewModel: viewModel)
33+ Divider ( )
2934 Button ( viewModel. isEnabled ? " Disable " : " Enable " ) {
3035 Task {
3136 try await runSession ( . menuBarButton, . forceRun) { ( ) throws in
@@ -62,25 +67,38 @@ public func menuBar(viewModel: TrayMenuModel) -> some Scene { // todo should it
6267 } . keyboardShortcut ( " Q " , modifiers: . command)
6368 } label: {
6469 if viewModel. isEnabled {
65- MonospacedText ( viewModel. trayText)
70+ switch viewModel. experimentalUISettings. displayStyle {
71+ case . systemText:
72+ Text ( viewModel. trayText)
73+ case . monospacedText:
74+ MenuBarLabel ( viewModel. trayText)
75+ case . squares:
76+ MenuBarLabel ( viewModel. trayText, trayItems: viewModel. trayItems)
77+ case . i3:
78+ MenuBarLabel ( viewModel. trayText, trayItems: viewModel. trayItems, workspaces: viewModel. workspaces)
79+ }
6680 } else {
67- MonospacedText ( " ⏸️ " )
81+ MenuBarLabel ( " ⏸️ " )
6882 }
6983 }
7084}
7185
72- struct MonospacedText : View {
86+ @MainActor
87+ struct MenuBarLabel : View {
7388 @Environment ( \. colorScheme) var colorScheme : ColorScheme
7489 var text : String
75- init ( _ text: String ) { self . text = text }
90+ var trayItems : [ TrayItem ] ?
91+ var workspaces : [ WorkspaceViewModel ] ?
92+
93+ init ( _ text: String , trayItems: [ TrayItem ] ? = nil , workspaces: [ WorkspaceViewModel ] ? = nil ) {
94+ self . text = text
95+ self . trayItems = trayItems
96+ self . workspaces = workspaces
97+ }
7698
7799 var body : some View {
78100 if #available( macOS 14 , * ) { // https://github.com/nikitabobko/AeroSpace/issues/1122
79- let renderer = ImageRenderer (
80- content: Text ( text)
81- . font ( . system( . largeTitle, design: . monospaced) )
82- . foregroundStyle ( colorScheme == . light ? Color . black : Color . white)
83- )
101+ let renderer = ImageRenderer ( content: menuBarContent)
84102 if let cgImage = renderer. cgImage {
85103 // Using scale: 1 results in a blurry image for unknown reasons
86104 Image ( cgImage, scale: 2 , label: Text ( text) )
@@ -92,6 +110,96 @@ struct MonospacedText: View {
92110 Text ( text)
93111 }
94112 }
113+
114+ @ViewBuilder
115+ var menuBarContent : some View {
116+ let color = colorScheme == . light ? Color . black : Color . white
117+ if let trayItems {
118+ HStack ( spacing: 4 ) {
119+ ForEach ( trayItems, id: \. name) { item in
120+ if item. name. containsEmoji ( ) {
121+ // If workspace name contains emojis we use the plain emoji in text to avoid visibility issues scaling the emoji to fit the squares
122+ Text ( item. name)
123+ . font ( . system( . largeTitle, design: . monospaced) )
124+ . foregroundStyle ( color)
125+ . bold ( )
126+ } else {
127+ Image ( systemName: item. systemImageName)
128+ . resizable ( )
129+ . aspectRatio ( contentMode: . fit)
130+ . foregroundStyle ( color)
131+ if item. type == . mode {
132+ Text ( " : " )
133+ . font ( . system( . largeTitle, design: . monospaced) )
134+ . foregroundStyle ( color)
135+ . bold ( )
136+ }
137+ }
138+ }
139+ if workspaces != nil {
140+ let otherWorkspaces = Workspace . all. filter { workspace in
141+ !workspace. isEffectivelyEmpty && !trayItems. contains ( where: { item in item. name == workspace. name } )
142+ }
143+ if !otherWorkspaces. isEmpty {
144+ Group {
145+ Text ( " | " )
146+ . font ( . system( . largeTitle, design: . monospaced) )
147+ . foregroundStyle ( color)
148+ . bold ( )
149+ . padding ( . bottom, 2 )
150+ ForEach ( otherWorkspaces, id: \. name) { item in
151+ if item. name. containsEmoji ( ) {
152+ Text ( item. name)
153+ . font ( . system( . largeTitle, design: . monospaced) )
154+ . foregroundStyle ( color)
155+ . bold ( )
156+ } else {
157+ Image ( systemName: " \( item. name. lowercased ( ) ) .square " )
158+ . resizable ( )
159+ . aspectRatio ( contentMode: . fit)
160+ . foregroundStyle ( color)
161+ }
162+ }
163+ }
164+ . opacity ( 0.6 )
165+ }
166+ }
167+ }
168+ . frame ( height: 40 )
169+ } else {
170+ Text ( text)
171+ . font ( . system( . largeTitle, design: . monospaced) )
172+ . foregroundStyle ( colorScheme == . light ? Color . black : Color . white)
173+ }
174+ }
175+ }
176+
177+ enum MenuBarStyle : String , CaseIterable , Identifiable , Equatable , Hashable {
178+ case monospacedText
179+ case systemText
180+ case squares
181+ case i3
182+ var id : Int {
183+ return self . hashValue
184+ }
185+ var title : String {
186+ switch self {
187+ case . monospacedText:
188+ " Monospaced font "
189+ case . systemText:
190+ " System font "
191+ case . squares:
192+ " Square images "
193+ case . i3:
194+ " i3 style "
195+ }
196+ }
197+ }
198+
199+ extension String {
200+ func containsEmoji( ) -> Bool {
201+ unicodeScalars. contains { $0. properties. isEmoji && $0. properties. isEmojiPresentation }
202+ }
95203}
96204
97205func getTextEditorToOpenConfig( ) -> URL {
0 commit comments