Skip to content

Commit d3acd0d

Browse files
committed
Refine menu bar icon with terminal prompt
1 parent 4555748 commit d3acd0d

5 files changed

Lines changed: 57 additions & 20 deletions

File tree

Resources/Info.plist

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
<key>CFBundlePackageType</key>
2020
<string>APPL</string>
2121
<key>CFBundleShortVersionString</key>
22-
<string>1.0.1</string>
22+
<string>1.0.2</string>
2323
<key>CFBundleVersion</key>
24-
<string>2</string>
24+
<string>3</string>
2525
<key>LSMinimumSystemVersion</key>
2626
<string>13.0</string>
2727
<key>LSUIElement</key>

Sources/CodexUsageMenuBar/CodexAppServerClient.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ private final class AppServerSession: @unchecked Sendable {
138138
"clientInfo": [
139139
"name": "codex_usage_menubar",
140140
"title": "Codex Usage",
141-
"version": "1.0.1",
141+
"version": "1.0.2",
142142
],
143143
"capabilities": ["experimentalApi": true],
144144
],

Sources/CodexUsageMenuBar/CodexAuthenticationClient.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ private final class DeviceLoginSession: @unchecked Sendable {
104104
"clientInfo": [
105105
"name": "codex_usage_menubar",
106106
"title": "Codex Usage",
107-
"version": "1.0.1",
107+
"version": "1.0.2",
108108
],
109109
"capabilities": ["experimentalApi": true],
110110
],

Sources/CodexUsageMenuBar/MenuBarUsageLabel.swift

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ enum MenuBarIndicator: Equatable {
1717
}
1818
}
1919

20+
var terminalText: String { "> \(text)" }
21+
22+
var usageUnderlineRange: NSRange? {
23+
guard case .remaining = self else { return nil }
24+
return NSRange(location: 2, length: (text as NSString).length)
25+
}
26+
2027
var accessibilityLabel: String {
2128
switch self {
2229
case .remaining(let percent):
@@ -43,8 +50,8 @@ struct MenuBarUsageLabel: View {
4350
/// them in one image prevents macOS menu bar label simplification from dropping
4451
/// the custom outline while preserving the text.
4552
enum CodexMenuBarIconRenderer {
46-
static let size = NSSize(width: 42, height: 22)
47-
static let outlineLineWidth: CGFloat = 1.35
53+
static let size = NSSize(width: 46, height: 22)
54+
static let outlineLineWidth: CGFloat = 1.2
4855

4956
static func image(for indicator: MenuBarIndicator) -> NSImage {
5057
let image = NSImage(size: size, flipped: false) { rect in
@@ -57,19 +64,12 @@ enum CodexMenuBarIconRenderer {
5764
NSColor.black.setStroke()
5865
outline.stroke()
5966

60-
let paragraph = NSMutableParagraphStyle()
61-
paragraph.alignment = .center
62-
let attributes: [NSAttributedString.Key: Any] = [
63-
.font: NSFont.monospacedDigitSystemFont(ofSize: 9, weight: .bold),
64-
.foregroundColor: NSColor.black,
65-
.paragraphStyle: paragraph,
66-
]
67-
let text = NSAttributedString(string: indicator.text, attributes: attributes)
67+
let text = attributedText(for: indicator)
6868
let textSize = text.size()
6969
let textRect = NSRect(
70-
x: 5,
70+
x: 4,
7171
y: ((rect.height - textSize.height) / 2) + 0.5,
72-
width: rect.width - 10,
72+
width: rect.width - 8,
7373
height: textSize.height
7474
)
7575
text.draw(in: textRect)
@@ -79,8 +79,31 @@ enum CodexMenuBarIconRenderer {
7979
return image
8080
}
8181

82+
static func attributedText(for indicator: MenuBarIndicator) -> NSAttributedString {
83+
let paragraph = NSMutableParagraphStyle()
84+
paragraph.alignment = .center
85+
let text = NSMutableAttributedString(
86+
string: indicator.terminalText,
87+
attributes: [
88+
.font: NSFont.monospacedSystemFont(ofSize: 8.5, weight: .semibold),
89+
.foregroundColor: NSColor.black,
90+
.paragraphStyle: paragraph,
91+
]
92+
)
93+
if let range = indicator.usageUnderlineRange {
94+
text.addAttributes(
95+
[
96+
.underlineStyle: NSUnderlineStyle.single.rawValue,
97+
.underlineColor: NSColor.black,
98+
],
99+
range: range
100+
)
101+
}
102+
return text
103+
}
104+
82105
static func outlinePath(in rect: NSRect) -> NSBezierPath {
83-
let drawingRect = rect.insetBy(dx: 2.5, dy: 2.5)
106+
let drawingRect = rect.insetBy(dx: 2, dy: 2)
84107
let center = CGPoint(x: drawingRect.midX, y: drawingRect.midY)
85108
let radiusX = drawingRect.width / 2
86109
let radiusY = drawingRect.height / 2
@@ -89,7 +112,7 @@ enum CodexMenuBarIconRenderer {
89112

90113
for index in 0...sampleCount {
91114
let angle = (Double(index) / Double(sampleCount)) * 2 * Double.pi
92-
let lobe = 1 + 0.07 * cos(8 * angle)
115+
let lobe = 1 + 0.04 * cos(8 * angle)
93116
let point = CGPoint(
94117
x: center.x + CGFloat(cos(angle) * lobe) * radiusX,
95118
y: center.y + CGFloat(sin(angle) * lobe) * radiusY

Tests/CodexUsageMenuBarTests/MenuBarUsageLabelTests.swift

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,24 @@ struct MenuBarUsageLabelTests {
1010
@Test("Shows the remaining percentage inside the outline")
1111
func remainingPercentageText() {
1212
#expect(MenuBarIndicator.remaining(18).text == "18%")
13+
#expect(MenuBarIndicator.remaining(18).terminalText == "> 18%")
1314
#expect(MenuBarIndicator.remaining(100).text == "100%")
1415
#expect(MenuBarIndicator.remaining(-2).text == "0%")
1516
}
1617

18+
@Test("Underlines only the remaining usage value after the terminal prompt")
19+
func terminalPromptUnderline() throws {
20+
let text = CodexMenuBarIconRenderer.attributedText(for: .remaining(18))
21+
let underlineRange = try #require(MenuBarIndicator.remaining(18).usageUnderlineRange)
22+
23+
#expect(text.string == "> 18%")
24+
#expect(
25+
text.attribute(.underlineStyle, at: underlineRange.location, effectiveRange: nil) as? Int
26+
== NSUnderlineStyle.single.rawValue
27+
)
28+
#expect(text.attribute(.underlineStyle, at: 0, effectiveRange: nil) == nil)
29+
}
30+
1731
@Test("Renders the compact Codex-inspired indicator")
1832
func rendersIndicator() throws {
1933
let nativeImage = CodexMenuBarIconRenderer.image(for: .remaining(18))
@@ -28,7 +42,7 @@ struct MenuBarUsageLabelTests {
2842
renderer.scale = 2
2943

3044
let image = try #require(renderer.nsImage)
31-
#expect(image.size.width == 62)
45+
#expect(image.size.width == 66)
3246
#expect(image.size.height == 42)
3347

3448
if let outputPath = ProcessInfo.processInfo.environment["CODEX_ICON_PREVIEW_PATH"],
@@ -42,7 +56,7 @@ struct MenuBarUsageLabelTests {
4256

4357
@Test("Keeps the cloud outline inside its menu bar bounds")
4458
func outlineBounds() {
45-
let target = CGRect(x: 0, y: 0, width: 42, height: 22)
59+
let target = CGRect(origin: .zero, size: CodexMenuBarIconRenderer.size)
4660
let bounds = CodexMenuBarIconRenderer.outlinePath(in: target).bounds
4761
let strokeInset = CodexMenuBarIconRenderer.outlineLineWidth / 2
4862

0 commit comments

Comments
 (0)