Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -266,19 +266,23 @@ final class WorkspaceRowView: SidebarRowView {
tabCount.frame = NSRect(x: trailing - Self.countWidth, y: (b.height - h) / 2, width: Self.countWidth, height: h)
trailing -= Self.countWidth + Metrics.space2
}
// The x and an unread badge share one slot, as wide as the wider of
// the two, so hover swaps them in place and the name keeps its width.
let showClose = isHovered && !isShowingPlaceholder
closeButton.isHidden = !showClose
if showClose {
closeButton.frame = NSRect(x: trailing - control, y: (b.height - control) / 2, width: control, height: control)
trailing -= control + Metrics.space2
badge.isHidden = true
} else if badge.state.isUnread {
badge.isHidden = false
badge.isHidden = showClose || !badge.state.isUnread
var slot: CGFloat = showClose ? control : 0
if badge.state.isUnread {
let w = badge.preferredWidth
let h = badge.state == .dot ? SidebarStyle.dotSize : SidebarStyle.badgeHeight
badge.frame = NSRect(x: trailing - w - (badge.state == .dot ? Metrics.space2 : 0), y: (b.height - h) / 2, width: w, height: h)
trailing = badge.frame.minX - Metrics.space2
let pad = badge.state == .dot ? Metrics.space2 : 0
badge.frame = NSRect(x: trailing - w - pad, y: (b.height - h) / 2, width: w, height: h)
slot = max(control, w + pad)
}
if showClose {
closeButton.frame = NSRect(x: trailing - control, y: (b.height - control) / 2, width: control, height: control)
}
if slot > 0 { trailing -= slot + Metrics.space2 }
let ind = SidebarStyle.indicatorSize
let markReplacesStatus = !agentMark.isHidden && agentMarkVariant == .replacesStatus
activity.isHidden = markReplacesStatus
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,25 @@ import Testing
#expect(view.nameFrame == rest)
}

/// An unread row's x takes the badge's slot on hover, so the name and
/// the activity glyph beside it stay put.
@Test(arguments: [UnreadState.dot, .count(3), .count(128)])
func anUnreadRowsNameKeepsItsWidthOnHover(_ unread: UnreadState) throws {
var sections = fixture()
sections[1].nodes[0] = .workspace(SidebarWorkspace(id: id("a"), title: "Unread work", unread: unread))
let h = MinimalChromeTests.Harness(sections: sections)
let row = try #require(h.sidebar.list.rowViews[.workspace(id("a"))] as? WorkspaceRowView)
row.layoutSubtreeIfNeeded()
let rest = row.titleFrame
row.isHovered = true
row.layoutSubtreeIfNeeded()
#expect(row.titleFrame == rest)
#expect(!row.closeButton.isHidden)
row.isHovered = false
row.layoutSubtreeIfNeeded()
#expect(row.titleFrame == rest)
}

@Test func aTitledSectionsChevronFadesInsteadOfMounting() {
let view = SidebarSectionHeaderView(frame: NSRect(x: 0, y: 0, width: 200, height: 24))
view.configure(title: "Apps", collapsed: false)
Expand Down
Loading