fix(mac): render context bar as image
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
|
import AppKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ContextUsageBar: View {
|
struct ContextUsageBar: View {
|
||||||
let usedTokens: Int
|
let usedTokens: Int
|
||||||
let contextTokens: Int
|
let contextTokens: Int
|
||||||
|
var width: CGFloat = 220
|
||||||
var height: CGFloat = 6
|
var height: CGFloat = 6
|
||||||
|
|
||||||
private var clampedFractionUsed: Double {
|
private var clampedFractionUsed: Double {
|
||||||
@@ -24,17 +26,16 @@ struct ContextUsageBar: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
// Prefer the native progress indicator in menus; `GeometryReader` can get wonky
|
// SwiftUI menus (MenuBarExtraStyle.menu) drop certain view types (including ProgressView/Canvas).
|
||||||
// inside `MenuBarExtra`-backed menus (often receiving zero width).
|
// Render the bar as an image to reliably display inside the menu.
|
||||||
ZStack {
|
Image(nsImage: Self.renderBar(
|
||||||
Capsule()
|
width: self.width,
|
||||||
.fill(Color.secondary.opacity(0.25))
|
height: self.height,
|
||||||
ProgressView(value: self.clampedFractionUsed, total: 1)
|
fractionUsed: self.clampedFractionUsed,
|
||||||
.progressViewStyle(.linear)
|
percentUsed: self.percentUsed))
|
||||||
.tint(self.tint)
|
.resizable()
|
||||||
.clipShape(Capsule())
|
.interpolation(.none)
|
||||||
}
|
.frame(width: self.width, height: self.height)
|
||||||
.frame(height: self.height)
|
|
||||||
.accessibilityLabel("Context usage")
|
.accessibilityLabel("Context usage")
|
||||||
.accessibilityValue(self.accessibilityValue)
|
.accessibilityValue(self.accessibilityValue)
|
||||||
}
|
}
|
||||||
@@ -44,4 +45,49 @@ struct ContextUsageBar: View {
|
|||||||
let pct = Int(round(self.clampedFractionUsed * 100))
|
let pct = Int(round(self.clampedFractionUsed * 100))
|
||||||
return "\(pct) percent used"
|
return "\(pct) percent used"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static func renderBar(
|
||||||
|
width: CGFloat,
|
||||||
|
height: CGFloat,
|
||||||
|
fractionUsed: Double,
|
||||||
|
percentUsed: Int?) -> NSImage
|
||||||
|
{
|
||||||
|
let clamped = min(1, max(0, fractionUsed))
|
||||||
|
let size = NSSize(width: max(1, width), height: max(1, height))
|
||||||
|
let image = NSImage(size: size)
|
||||||
|
image.isTemplate = false
|
||||||
|
|
||||||
|
image.lockFocus()
|
||||||
|
defer { image.unlockFocus() }
|
||||||
|
|
||||||
|
let rect = NSRect(origin: .zero, size: size)
|
||||||
|
let radius = rect.height / 2
|
||||||
|
|
||||||
|
let background = NSColor.white.withAlphaComponent(0.12)
|
||||||
|
let stroke = NSColor.white.withAlphaComponent(0.18)
|
||||||
|
|
||||||
|
let fill: NSColor = {
|
||||||
|
guard let pct = percentUsed else { return NSColor.secondaryLabelColor }
|
||||||
|
if pct >= 95 { return .systemRed }
|
||||||
|
if pct >= 80 { return .systemOrange }
|
||||||
|
if pct >= 60 { return .systemYellow }
|
||||||
|
return .systemGreen
|
||||||
|
}()
|
||||||
|
|
||||||
|
let track = NSBezierPath(roundedRect: rect, xRadius: radius, yRadius: radius)
|
||||||
|
background.setFill()
|
||||||
|
track.fill()
|
||||||
|
stroke.setStroke()
|
||||||
|
track.lineWidth = 0.75
|
||||||
|
track.stroke()
|
||||||
|
|
||||||
|
let fillWidth = max(1, floor(rect.width * clamped))
|
||||||
|
let fillRect = NSRect(x: rect.minX, y: rect.minY, width: fillWidth, height: rect.height)
|
||||||
|
let clip = NSBezierPath(roundedRect: rect, xRadius: radius, yRadius: radius)
|
||||||
|
clip.addClip()
|
||||||
|
fill.setFill()
|
||||||
|
NSBezierPath(rect: fillRect).fill()
|
||||||
|
|
||||||
|
return image
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -265,8 +265,8 @@ struct MenuContent: View {
|
|||||||
}
|
}
|
||||||
ContextUsageBar(
|
ContextUsageBar(
|
||||||
usedTokens: row.tokens.total,
|
usedTokens: row.tokens.total,
|
||||||
contextTokens: row.tokens.contextTokens)
|
contextTokens: row.tokens.contextTokens,
|
||||||
.frame(width: 220)
|
width: 220)
|
||||||
}
|
}
|
||||||
.padding(.vertical, 2)
|
.padding(.vertical, 2)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -151,8 +151,10 @@ struct SessionsSettings: View {
|
|||||||
.font(.caption.monospacedDigit())
|
.font(.caption.monospacedDigit())
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
ContextUsageBar(usedTokens: row.tokens.total, contextTokens: row.tokens.contextTokens)
|
ContextUsageBar(
|
||||||
.frame(maxWidth: .infinity)
|
usedTokens: row.tokens.total,
|
||||||
|
contextTokens: row.tokens.contextTokens,
|
||||||
|
width: 260)
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack(spacing: 10) {
|
HStack(spacing: 10) {
|
||||||
|
|||||||
Reference in New Issue
Block a user