Skip to content

Commit f2a1318

Browse files
committed
Replace delete button label with image view for proper contrast on hover
The delete button used a text label with a fixed color that couldn't adapt when the row was highlighted. This made it nearly invisible on the hover background. By switching to an NSImageView with contentTintColor set to .tertiaryLabelColor, the button now maintains proper contrast during hover states, matching the approach used by family header chevrons. contentTintColor is the recommended AppKit pattern for single-color icon tinting as it automatically adapts to view state changes and works with dynamic system colors, unlike the previous approach which baked the color into an attributed string at creation time. Closes #10
1 parent 3fd0f58 commit f2a1318

File tree

1 file changed

+23
-20
lines changed

1 file changed

+23
-20
lines changed

LlamaBarn/Menu/InstalledModelItemView.swift

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ final class InstalledModelItemView: ItemView, NSGestureRecognizerDelegate {
1818
private let metadataLabel = Typography.makeSecondaryLabel()
1919
private let progressLabel = Typography.makeSecondaryLabel()
2020
private let cancelImageView = NSImageView()
21-
private let deleteLabel = Typography.makeSecondaryLabel()
21+
private let deleteImageView = NSImageView()
2222

2323
// Hover handling is provided by MenuItemView
2424
private var rowClickRecognizer: NSClickGestureRecognizer?
@@ -56,8 +56,12 @@ final class InstalledModelItemView: ItemView, NSGestureRecognizerDelegate {
5656
cancelImageView.contentTintColor = .systemRed
5757
cancelImageView.isHidden = true
5858

59-
deleteLabel.attributedStringValue = makeDeleteButtonText()
60-
deleteLabel.isHidden = true
59+
// Configure delete button
60+
deleteImageView.image = Symbols.trash
61+
deleteImageView.contentTintColor = .tertiaryLabelColor
62+
deleteImageView.symbolConfiguration = .init(
63+
pointSize: Layout.metadataIconSize, weight: .regular)
64+
deleteImageView.isHidden = true
6165

6266
// Spacer expands so trailing visuals sit flush right.
6367
let spacer = NSView()
@@ -96,9 +100,9 @@ final class InstalledModelItemView: ItemView, NSGestureRecognizerDelegate {
96100
rootStack.alignment = .centerY
97101
contentView.addSubview(rootStack)
98102

99-
// Add delete label separately so we can position it at the bottom
100-
deleteLabel.translatesAutoresizingMaskIntoConstraints = false
101-
contentView.addSubview(deleteLabel)
103+
// Add delete button separately so we can position it at the bottom
104+
deleteImageView.translatesAutoresizingMaskIntoConstraints = false
105+
contentView.addSubview(deleteImageView)
102106

103107
rootStack.pinToSuperview()
104108

@@ -111,13 +115,15 @@ final class InstalledModelItemView: ItemView, NSGestureRecognizerDelegate {
111115

112116
progressLabel.widthAnchor.constraint(lessThanOrEqualToConstant: Layout.progressWidth),
113117

114-
// Position delete label at the bottom right, aligned with line 2
115-
deleteLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
116-
deleteLabel.bottomAnchor.constraint(equalTo: metadataLabel.bottomAnchor),
118+
// Position delete button at the bottom right, aligned with line 2
119+
deleteImageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
120+
deleteImageView.centerYAnchor.constraint(equalTo: metadataLabel.centerYAnchor),
121+
deleteImageView.widthAnchor.constraint(lessThanOrEqualToConstant: Layout.metadataIconSize),
122+
deleteImageView.heightAnchor.constraint(lessThanOrEqualToConstant: Layout.metadataIconSize),
117123
])
118124
}
119125

120-
// Row click recognizer to toggle, letting the delete label handle its own action.
126+
// Row click recognizer to toggle, letting the delete button handle its own action.
121127
override func viewDidMoveToWindow() {
122128
super.viewDidMoveToWindow()
123129
if rowClickRecognizer == nil {
@@ -128,7 +134,7 @@ final class InstalledModelItemView: ItemView, NSGestureRecognizerDelegate {
128134
}
129135
if deleteClickRecognizer == nil {
130136
let click = NSClickGestureRecognizer(target: self, action: #selector(didClickDelete))
131-
deleteLabel.addGestureRecognizer(click)
137+
deleteImageView.addGestureRecognizer(click)
132138
deleteClickRecognizer = click
133139
}
134140
}
@@ -137,13 +143,13 @@ final class InstalledModelItemView: ItemView, NSGestureRecognizerDelegate {
137143

138144
@objc private func didClickDelete() { performDelete() }
139145

140-
// Prevent row toggle when clicking the delete label.
146+
// Prevent row toggle when clicking the delete button.
141147
func gestureRecognizer(
142148
_ gestureRecognizer: NSGestureRecognizer, shouldAttemptToRecognizeWith event: NSEvent
143149
) -> Bool {
144150
let loc = event.locationInWindow
145-
let localPoint = deleteLabel.convert(loc, from: nil)
146-
if deleteLabel.bounds.contains(localPoint) && !deleteLabel.isHidden {
151+
let localPoint = deleteImageView.convert(loc, from: nil)
152+
if deleteImageView.bounds.contains(localPoint) && !deleteImageView.isHidden {
147153
return false
148154
}
149155
return true
@@ -187,7 +193,7 @@ final class InstalledModelItemView: ItemView, NSGestureRecognizerDelegate {
187193
}
188194

189195
// Delete button only for installed models on hover
190-
deleteLabel.isHidden = !modelManager.isInstalled(model) || !isHighlighted
196+
deleteImageView.isHidden = !modelManager.isInstalled(model) || !isHighlighted
191197

192198
// Update icon state
193199
iconView.setLoading(isLoading)
@@ -197,16 +203,13 @@ final class InstalledModelItemView: ItemView, NSGestureRecognizerDelegate {
197203
}
198204

199205
override func highlightDidChange(_ highlighted: Bool) {
200-
deleteLabel.isHidden = !modelManager.isInstalled(model) || !highlighted
206+
deleteImageView.isHidden = !modelManager.isInstalled(model) || !highlighted
201207
}
202208

203209
override func viewDidChangeEffectiveAppearance() {
204210
super.viewDidChangeEffectiveAppearance()
205211
cancelImageView.contentTintColor = .systemRed
206-
}
207-
208-
private func makeDeleteButtonText() -> NSAttributedString {
209-
MetadataLabel.makeIconOnly(icon: Symbols.trash, color: Typography.tertiaryColor)
212+
deleteImageView.contentTintColor = .tertiaryLabelColor
210213
}
211214

212215
@objc private func performDelete() {

0 commit comments

Comments
 (0)