The combo drops its padding and turns portrait — a 4:5 field puts the symbol dead center
Owner's first review of the combo rework (2026-08-09): remove the face padding, make the field taller and narrower at about a 4:5 width:height ratio, and center the symbol glyph in its face. All three land in ComboFieldMetrics, so ColorComboControl and SymbolComboControl stay the identical shape they were built to share. - ComboFieldMetrics grows a width figure (height * widthToHeightRatio, 0.8), replacing NSView.noIntrinsicMetric — every combo now carries its own taller, narrower intrinsic size instead of stretching to whatever a caller's frame proposed. - facePaddingH/facePaddingV/glyphPadding are gone; a face fills its zone edge to edge. glyphPointSize is now whichever of the face's own width/height is smaller, with nothing subtracted for padding that no longer exists. - SymbolComboControl.drawFace centers the glyph on both axes — it only ever centered vertically before, despite its own doc comment claiming otherwise. - ComboFieldControl.drawTrigger bounds its chevron square by the smaller of the trigger strip's own width/height, not height alone, since the strip is no longer close to square once the field is much taller than it is wide. - CardSidebarSections drops the sidebar's old '* 0.55' fixed-width frame on both combo rows; each control now sizes itself, and both anchors (card sidebar, board popover) compose the narrower field with no other changes needed. - ComboFieldMetricsTests updated for the new figures, plus a ratio-holds-at-every-size test and a rewritten glyph-fit test matching the no-padding rule. Verification: xcodebuild build succeeded; xcodebuild test -only-testing:KanbanTests — 3220 tests in 559 suites, 3 failures, all PointerLatencyTests (documented locked-screen environmental mode, confirmed unrelated by isolated rerun). Pixel verification unexercised — same locked-screen constraint the first pass hit. Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
+11
-16
@@ -210,12 +210,6 @@ struct ColorComboView: NSViewRepresentable {
|
||||
/// debounce and never feeds style recents.
|
||||
var onPanelChange: @MainActor (String?) -> Void
|
||||
|
||||
/// The width `sizeThatFits` hands back when SwiftUI has no concrete proposal to fill — an
|
||||
/// unconstrained measuring pass, not the normal case. The normal case is a finite proposal (this
|
||||
/// view sits under `.frame(maxWidth: .infinity)` in the sidebar row, `CardActionsSection`'s
|
||||
/// Delete button's own trick), which this default never has to stand in for.
|
||||
private static let defaultFaceWidth: CGFloat = 120
|
||||
|
||||
func makeNSView(context: Context) -> ColorComboControl {
|
||||
let control = ColorComboControl(frame: .zero)
|
||||
// The two zones' jobs, wired once (`ComboFieldControl`'s grammar): the face opens the same
|
||||
@@ -240,18 +234,17 @@ struct ColorComboView: NSViewRepresentable {
|
||||
context.coordinator.rebuild(control, value: value)
|
||||
}
|
||||
|
||||
/// Obeys whatever width SwiftUI proposes, like any other control — never the widest menu item,
|
||||
/// which is what the old caller-supplied `width` input existed to work around. Height is the
|
||||
/// control's own fitting height (`ColorComboControl.intrinsicContentSize`, about half the old
|
||||
/// regular `NSPopUpButton`'s — the whole point of this rework); width is the proposal's when it
|
||||
/// is an actual number, else `defaultFaceWidth`, since a `nil`/infinite proposal happens on an
|
||||
/// unconstrained measuring pass, not a sidebar row.
|
||||
/// Obeys an explicit finite proposal when SwiftUI hands one over — never the widest menu item,
|
||||
/// which is what the old caller-supplied `width` input existed to work around. Falls back to the
|
||||
/// control's own intrinsic width (`ComboFieldControl.intrinsicContentSize`, the 2026-08-09
|
||||
/// iteration's taller-narrower field) on an unconstrained measuring pass, which is the normal case
|
||||
/// now that the sidebar row no longer forces a wider frame of its own (`CardStyleSection`).
|
||||
func sizeThatFits(_ proposal: ProposedViewSize, nsView: ColorComboControl, context: Context) -> CGSize? {
|
||||
let width: CGFloat
|
||||
if let proposed = proposal.width, proposed.isFinite {
|
||||
width = proposed
|
||||
} else {
|
||||
width = Self.defaultFaceWidth
|
||||
width = nsView.intrinsicContentSize.width
|
||||
}
|
||||
return CGSize(width: width, height: nsView.intrinsicContentSize.height)
|
||||
}
|
||||
@@ -430,10 +423,12 @@ final class ColorComboControl: ComboFieldControl {
|
||||
/// underlay so a translucent stored colour composites the same way in light and dark, the
|
||||
/// resolved colour on top, a `separatorColor` hairline stroke last. `nil`/unresolvable value →
|
||||
/// underlay + stroke only, the same "there is no colour, so show none" rule.
|
||||
///
|
||||
/// **No inset, since 2026-08-09** — the swatch fills the whole face zone rather than sitting in a
|
||||
/// padded ring inside it (the owner's "remove padding from the combo").
|
||||
override func drawFace(in rect: NSRect) {
|
||||
let inset = rect.insetBy(dx: metrics.facePaddingH, dy: metrics.facePaddingV)
|
||||
guard inset.width > 0, inset.height > 0 else { return }
|
||||
let path = NSBezierPath(roundedRect: inset, xRadius: metrics.cornerRadius, yRadius: metrics.cornerRadius)
|
||||
guard rect.width > 0, rect.height > 0 else { return }
|
||||
let path = NSBezierPath(roundedRect: rect, xRadius: metrics.cornerRadius, yRadius: metrics.cornerRadius)
|
||||
NSColor.textBackgroundColor.setFill()
|
||||
path.fill()
|
||||
if let swatchValue, let color = Palette.nsColor(for: swatchValue) {
|
||||
|
||||
Reference in New Issue
Block a user