Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions AgentBar/Views/StatusBar/StackedBarView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ struct StackedBarView: View {
StatusBarDisplayPlanner.rankedServices(from: services)
}

private var rowHeight: CGFloat {
StatusBarDisplayPlanner.rowHeight(forServiceCount: rankedServices.count)
}

private var rowSpacing: CGFloat {
StatusBarDisplayPlanner.rowSpacing(forServiceCount: rankedServices.count)
}

private var shouldCenterRowsVertically: Bool {
StatusBarDisplayPlanner.centersRowsVertically(forServiceCount: rankedServices.count)
}

private var cycleTaskID: String {
let signature = rankedServices
.map { usage in
Expand Down Expand Up @@ -52,19 +64,22 @@ struct StackedBarView: View {
}

private var scrollingRows: some View {
ZStack(alignment: .top) {
VStack(spacing: StatusBarDisplayPlanner.rowSpacing) {
ZStack(alignment: shouldCenterRowsVertically ? .center : .top) {
VStack(spacing: rowSpacing) {
ForEach(rankedServices) { usage in
SingleBarView(usage: usage)
.frame(height: StatusBarDisplayPlanner.rowHeight)
.frame(height: rowHeight)
}
}
.offset(
y: -CGFloat(currentScrollIndex)
* (StatusBarDisplayPlanner.rowHeight + StatusBarDisplayPlanner.rowSpacing)
* (rowHeight + rowSpacing)
)
}
.frame(height: StatusBarDisplayPlanner.viewportHeight, alignment: .top)
.frame(
height: StatusBarDisplayPlanner.viewportHeight,
alignment: shouldCenterRowsVertically ? .center : .top
)
.clipped()
}

Expand Down Expand Up @@ -143,6 +158,5 @@ struct SingleBarView: View {
}
}
}
.frame(height: StatusBarDisplayPlanner.rowHeight)
}
}
49 changes: 45 additions & 4 deletions AgentBar/Views/StatusBar/StatusBarDisplayPlanner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import Foundation
import CoreGraphics

enum StatusBarDisplayPlanner {
static let visibleRowCount = 3
static let rowHeight: CGFloat = 6
static let rowSpacing: CGFloat = 1
static let maximumVisibleRowCount = 3
static let compactVisibleRowCount = 2
static let standardRowSpacing: CGFloat = 1
static let viewportHeight: CGFloat = 20

static let topPriorityHoldSeconds: TimeInterval = 8
Expand All @@ -30,11 +30,52 @@ enum StatusBarDisplayPlanner {
}

static func maxScrollIndex(for rankedServices: [UsageData]) -> Int {
max(0, rankedServices.count - visibleRowCount)
max(0, rankedServices.count - visibleRowCount(forServiceCount: rankedServices.count))
}

static func visibleRowCount(forServiceCount serviceCount: Int) -> Int {
guard serviceCount > 0 else { return maximumVisibleRowCount }
return serviceCount <= compactVisibleRowCount
? compactVisibleRowCount
: maximumVisibleRowCount
}

static func rowHeight(forServiceCount serviceCount: Int) -> CGFloat {
if usesCompactLayout(forServiceCount: serviceCount) {
return compactRowHeight
}

let rowCount = CGFloat(visibleRowCount(forServiceCount: serviceCount))
let totalSpacing = standardRowSpacing * max(0, rowCount - 1)
return (viewportHeight - totalSpacing) / rowCount
}

static func rowSpacing(forServiceCount serviceCount: Int) -> CGFloat {
switch serviceCount {
case 2:
return (viewportHeight - (rowHeight(forServiceCount: serviceCount) * 2)) / 3
case 1:
return 0
default:
return standardRowSpacing
}
}

static func centersRowsVertically(forServiceCount serviceCount: Int) -> Bool {
usesCompactLayout(forServiceCount: serviceCount)
}

private static func usageScore(_ data: UsageData) -> Double {
let weekly = data.weeklyUsage?.percentage ?? 0
return max(data.fiveHourUsage.percentage, weekly)
}

private static var compactRowHeight: CGFloat {
let fullCompactRowHeight = (viewportHeight - standardRowSpacing) / CGFloat(compactVisibleRowCount)
return fullCompactRowHeight * 0.8
}

private static func usesCompactLayout(forServiceCount serviceCount: Int) -> Bool {
serviceCount > 0 && serviceCount <= compactVisibleRowCount
}
}
38 changes: 38 additions & 0 deletions AgentBarTests/StatusBarDisplayPlannerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,44 @@ final class StatusBarDisplayPlannerTests: XCTestCase {
XCTAssertEqual(StatusBarDisplayPlanner.maxScrollIndex(for: ranked), 0)
}

func testUsesTwoVisibleRowsWhenOneOrTwoServicesAreActive() {
XCTAssertEqual(StatusBarDisplayPlanner.visibleRowCount(forServiceCount: 1), 2)
XCTAssertEqual(StatusBarDisplayPlanner.visibleRowCount(forServiceCount: 2), 2)
XCTAssertEqual(StatusBarDisplayPlanner.visibleRowCount(forServiceCount: 3), 3)
}

func testCompactLayoutUsesHalfHeightRows() {
XCTAssertEqual(
StatusBarDisplayPlanner.rowHeight(forServiceCount: 2),
7.6,
accuracy: 0.001
)
XCTAssertEqual(
StatusBarDisplayPlanner.rowHeight(forServiceCount: 3),
6,
accuracy: 0.001
)
}

func testCompactLayoutUsesEvenVerticalSpacing() {
XCTAssertEqual(
StatusBarDisplayPlanner.rowSpacing(forServiceCount: 2),
1.6,
accuracy: 0.001
)
XCTAssertEqual(
StatusBarDisplayPlanner.rowSpacing(forServiceCount: 3),
1,
accuracy: 0.001
)
}

func testCompactLayoutCentersRowsVertically() {
XCTAssertTrue(StatusBarDisplayPlanner.centersRowsVertically(forServiceCount: 1))
XCTAssertTrue(StatusBarDisplayPlanner.centersRowsVertically(forServiceCount: 2))
XCTAssertFalse(StatusBarDisplayPlanner.centersRowsVertically(forServiceCount: 3))
}

func testMaxScrollIndexEqualsOverflowRowCount() {
let services = [
makeUsage(service: .claude, fiveHourPct: 0.99),
Expand Down