Skip to content
Merged
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
62 changes: 47 additions & 15 deletions Clipy/Sources/Managers/MenuManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1569,7 +1569,28 @@ final class BoardManHeaderSegmentedControl: NSSegmentedControl {
}
}

private final class BoardManHistoryCellView: NSTableCellView {
final class BoardManSearchFieldCell: NSSearchFieldCell {
private func verticallyCentered(_ contentRect: NSRect, in bounds: NSRect) -> NSRect {
guard contentRect.height > 0 else { return contentRect }
var centered = contentRect
centered.origin.y = floor(bounds.midY - (contentRect.height / 2))
return centered
}

override func searchTextRect(forBounds rect: NSRect) -> NSRect {
return verticallyCentered(super.searchTextRect(forBounds: rect), in: rect)
}

override func searchButtonRect(forBounds rect: NSRect) -> NSRect {
return verticallyCentered(super.searchButtonRect(forBounds: rect), in: rect)
}

override func cancelButtonRect(forBounds rect: NSRect) -> NSRect {
return verticallyCentered(super.cancelButtonRect(forBounds: rect), in: rect)
}
}

final class BoardManHistoryCellView: NSTableCellView {
private let primaryLabel = NSTextField(labelWithString: "")
private let metadataLabel = NSTextField(labelWithString: "")
private let countBadge = NSTextField(labelWithString: "")
Expand Down Expand Up @@ -1602,6 +1623,8 @@ private final class BoardManHistoryCellView: NSTableCellView {
countBadge.alignment = .center
countBadge.lineBreakMode = .byTruncatingTail
countBadge.maximumNumberOfLines = 1
countBadge.cell?.usesSingleLineMode = true
countBadge.cell?.wraps = false
countBadge.isBordered = false
countBadge.isEditable = false
countBadge.wantsLayer = true
Expand All @@ -1628,7 +1651,7 @@ private final class BoardManHistoryCellView: NSTableCellView {
addSubview(countBadge)
}

func configure(item: BoardManHistoryItem,
fileprivate func configure(item: BoardManHistoryItem,
isSelected: Bool,
usageStyle: String,
useLiquidGlass: Bool,
Expand Down Expand Up @@ -1664,22 +1687,35 @@ private final class BoardManHistoryCellView: NSTableCellView {
needsLayout = true
}

static func usageBadgeFrame(in bounds: NSRect, intrinsicWidth: CGFloat) -> NSRect {
let horizontalInset: CGFloat = 16
let trailingInset: CGFloat = 22
let badgeHeight: CGFloat = 20
let maxContentWidth = max(0, bounds.width - horizontalInset - trailingInset)
let badgeWidth = min(maxContentWidth, min(76, max(38, ceil(intrinsicWidth) + 20)))
return NSIntegralRect(NSRect(
x: max(horizontalInset, floor(bounds.maxX - trailingInset - badgeWidth)),
y: floor(bounds.midY - (badgeHeight / 2)),
width: badgeWidth,
height: badgeHeight
))
}

override func layout() {
super.layout()
let horizontalInset: CGFloat = 16
let trailingInset: CGFloat = 14
let trailingInset: CGFloat = 22
let accessoryGap: CGFloat = 10
let titleHeight: CGFloat = 18
let metadataHeight: CGFloat = 15
let textGap: CGFloat = 4
let textBlockHeight = titleHeight + textGap + metadataHeight
let textBottom = floor((bounds.height - textBlockHeight) / 2)
let maxContentWidth = max(0, bounds.width - horizontalInset - trailingInset)
let badgeHeight: CGFloat = 20
let badgeWidth: CGFloat = countBadge.isHidden
? 0
: min(maxContentWidth, min(72, max(34, countBadge.intrinsicContentSize.width + 16)))
let badgeX = max(horizontalInset, floor(bounds.width - trailingInset - badgeWidth))
let badgeFrame = countBadge.isHidden
? .zero
: Self.usageBadgeFrame(in: bounds, intrinsicWidth: countBadge.intrinsicContentSize.width)
let badgeWidth = badgeFrame.width
let badgeX = badgeFrame.minX
let imageSize: CGFloat = inlineImageView.isHidden ? 0 : 32
let imageX = imageSize > 0 ? max(horizontalInset, badgeX - imageSize - accessoryGap) : 0
let textRightLimit = imageSize > 0
Expand Down Expand Up @@ -1708,12 +1744,7 @@ private final class BoardManHistoryCellView: NSTableCellView {
))
}
if !countBadge.isHidden {
countBadge.frame = NSIntegralRect(NSRect(
x: badgeX,
y: floor((bounds.height - badgeHeight) / 2),
width: badgeWidth,
height: badgeHeight
))
countBadge.frame = badgeFrame
}
}
}
Expand Down Expand Up @@ -2239,6 +2270,7 @@ class BoardManPanel: NSPanel {
setupGlassBackgroundIfNeeded()

let search = NSSearchField(frame: .zero)
search.cell = BoardManSearchFieldCell(textCell: "")
search.placeholderString = "Search clipboard history and snippets"
search.target = self
search.action = #selector(searchTextChanged(_:))
Expand Down
21 changes: 19 additions & 2 deletions ClipyTests/EntitlementGateTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -515,13 +515,21 @@ final class BoardManPanelLayoutTests {
let search = descendants.compactMap { $0 as? NSSearchField }.first
#expect((search != nil) == expectsSearch || search?.isHidden == !expectsSearch)
if expectsSearch, let search {
#expect(search.cell is NSSearchFieldCell,
"Search field is not using the native AppKit search cell.")
guard let searchCell = search.cell as? BoardManSearchFieldCell else {
Issue.record("Search field is not using the vertically centered Board-Man search cell.")
return
}
#expect((search.layer?.borderWidth ?? 0) == 0,
"Search field has a second custom layer border.")
#expect(abs(search.frame.midY - tabs.frame.midY) <= 0.5,
"Search input is not vertically centered with the header tabs.")
#expect(search.frame.height == tabs.frame.height)
let textRect = searchCell.searchTextRect(forBounds: search.bounds)
let buttonRect = searchCell.searchButtonRect(forBounds: search.bounds)
#expect(abs(textRect.midY - search.bounds.midY) <= 0.5,
"Search text is not vertically centered inside its border.")
#expect(abs(buttonRect.midY - search.bounds.midY) <= 0.5,
"Search icon is not vertically centered inside its border.")
}
}

Expand All @@ -534,6 +542,15 @@ final class BoardManPanelLayoutTests {
#expect(table.rowHeight == panel.tableView(table, heightOfRow: 0),
"Configured and delegated history row heights diverge.")
#expect(table.rowHeight == 62)

let rowBounds = NSRect(x: 0, y: 0, width: max(240, table.bounds.width), height: table.rowHeight)
let badgeFrame = BoardManHistoryCellView.usageBadgeFrame(in: rowBounds, intrinsicWidth: 16)
#expect(badgeFrame.maxX <= rowBounds.maxX - 21,
"Usage badge does not reserve enough trailing space inside the rounded row.")
#expect(abs(badgeFrame.midY - rowBounds.midY) <= 0.5,
"Usage badge is not vertically centered in the row.")
#expect(badgeFrame.width >= 38,
"Usage badge is too narrow and may clip its text.")
}

private func allSubviews(of view: NSView) -> [NSView] {
Expand Down
Loading