Two widths, one height — the symbol picker squares to 5:4, colour holds 6:4, and None's slash follows the swatch out of the grid

The owner's follow-up review on the shipped rectangles (Pipeline card
5004c540): height was close, so it stays exactly 2.1em on both controls;
width now diverges by role via PickerRectMetrics.WidthRatio (color 1.5,
symbol 1.25) instead of one shared multiplier. The symbol glyph gets a
small em-derived inset back (SymbolGlyphControl.glyphInset) — a sliver of
breath, not the old padded ring — applied to the face rect before sizing
and fitting, still centred on the same midpoint. The collapsed colour
swatch reuses ColorSwatchNoneStrike's own geometry to draw the popover's
None slash whenever the stored value is nil or unresolvable, rather than
sitting empty. Both mounting anchors (the card sidebar's side-by-side
columns, the board popover beside the rename field) pick up .fixedSize()
so the controls render at their own intrinsic size instead of stretching
into whatever slack an HStack proposal leaves them.

KanbanTests/SymbolCatalogTests.swift and ColorSwatchPickerTests.swift
updated for the per-role widths, the shared-height claim, the glyph
inset rule, and the None-face predicate.
This commit is contained in:
2026-08-09 21:37:29 -04:00
parent ac4f8b9050
commit f191e5c3ce
7 changed files with 264 additions and 63 deletions
+30 -7
View File
@@ -374,7 +374,7 @@ private struct SymbolGlyphView: NSViewRepresentable {
}
func updateNSView(_ control: SymbolGlyphControl, context: Context) {
control.metrics = .current
control.metrics = .current(.symbol)
control.isEnabled = isEnabled
control.glyphName = resolvedName
control.glyphTint = currentColor.flatMap(Palette.nsColor(for:))
@@ -441,15 +441,26 @@ final class SymbolGlyphControl: PickerRectControl {
/// so the fix reads the point size off the rect the face actually receives, via `glyphPointSize(
/// forFace:)` and `fittedSize(for:in:)` below pure functions, not inlined here, so the sizing
/// rule is assertable without an actual draw.
///
/// **A sliver of padding returns, on purpose, in the 2026-08-10 follow-up.** The edge-to-edge
/// draw above went further than the owner's original "reduce padding" asked, once the rectangle
/// itself was squared up a glyph with no breath at all reads as clipped rather than centred. Fix
/// is a small em-derived inset (`glyphInset(bodyPointSize:)`) subtracted from the face rect
/// *before* sizing and fitting not a fixed pixel margin, so it scales with the rest of the
/// control's font-derived geometry, and not re-added to the drawn image's position, since the
/// inset rect is centred on the same midpoint as the uninset one.
override func drawFace(in rect: NSRect) {
guard rect.width > 0, rect.height > 0 else { return }
let inset = Self.glyphInset(bodyPointSize: CardWindowMetrics.bodyPointSize)
let faceRect = rect.insetBy(dx: inset, dy: inset)
guard faceRect.width > 0, faceRect.height > 0 else { return }
let name = ItemSymbol.exists(glyphName) ? glyphName : "questionmark.square.dashed"
let config = NSImage.SymbolConfiguration(pointSize: Self.glyphPointSize(forFace: rect), weight: .regular)
let config = NSImage.SymbolConfiguration(pointSize: Self.glyphPointSize(forFace: faceRect), weight: .regular)
.applying(.init(paletteColors: [glyphTint ?? .labelColor]))
guard let image = NSImage(systemSymbolName: name, accessibilityDescription: nil)?
.withSymbolConfiguration(config)
else { return }
let size = Self.fittedSize(for: image.size, in: rect)
let size = Self.fittedSize(for: image.size, in: faceRect)
image.draw(in: NSRect(
x: rect.midX - size.width / 2,
y: rect.midY - size.height / 2,
@@ -458,14 +469,26 @@ final class SymbolGlyphControl: PickerRectControl {
))
}
/// The glyph's target point size for a face rect no inset subtracted, so this is simply whichever
/// of the rect's own two dimensions is smaller. SF Symbols already carry their own internal
/// margins, and the owner's evening review asked for the *dead space around* the glyph gone, not a
/// second margin layered on top of the system's own.
/// The glyph's target point size for a face rect no inset subtracted here (the caller already
/// applied `glyphInset(bodyPointSize:)` to `rect` before handing it over), so this is simply
/// whichever of the rect's own two dimensions is smaller. SF Symbols already carry their own
/// internal margins, and the owner's evening review asked for the *dead space around* the glyph
/// gone, not a second margin layered on top of the system's own the 2026-08-10 sliver is that
/// second margin reintroduced deliberately, at a much smaller, explicit figure.
static func glyphPointSize(forFace rect: NSRect) -> CGFloat {
min(rect.width, rect.height)
}
/// **"A sliver of padding around the symbol"** (owner's 2026-08-10 follow-up) a small, visible
/// breath between the glyph and the face's own edge/border, not a ring wide enough to shrink the
/// glyph noticeably. `0.18` em sits at the middle of the owner's named 0.150.2 range, and like
/// every other figure on this control is a multiple of the body point size rather than a flat
/// pixel constant (10-accessibility.md's full-relative-scaling rule). A pure static function, not
/// inlined into `drawFace`, so the figure is assertable without an actual draw.
static func glyphInset(bodyPointSize: CGFloat) -> CGFloat {
max(1, (bodyPointSize * 0.18).rounded())
}
/// `imageSize` unchanged, unless it overshoots `rect` on either axis a symbol configured at
/// `glyphPointSize(forFace:)` can still render wider (or, rarely, taller) than that on its long
/// axis, since SF Symbols are not all square glyphs. Scaled down proportionally so the drawn glyph