A borderless TextField that has a background color is 4pt wider than the stack it fills on macOS. It overhangs the stack by 2pt on each side, so its border and background draw outside the column.
Repro
import {
App, Text, TextField, VStack, setPadding, textfieldSetBackgroundColor, textfieldSetBorderless,
widgetMatchParentWidth, widgetSetBackgroundColor, widgetSetBorderColor, widgetSetBorderWidth,
widgetSetHeight, widgetSetWidth,
} from "perry/ui"
function field(withBackground: boolean) {
const f = TextField("", () => {})
textfieldSetBorderless(f, 1)
if (withBackground) textfieldSetBackgroundColor(f, 1, 1, 1, 1)
widgetSetBorderColor(f, 0, 0, 0, 1)
widgetSetBorderWidth(f, 1)
return f
}
const a = field(true)
const b = field(false)
const column = VStack(6, [Text("textfieldSetBackgroundColor:"), a, Text("no background color:"), b])
widgetSetWidth(column, 300)
widgetSetBackgroundColor(column, 0.75, 0.87, 1, 1)
for (const f of [a, b]) {
widgetMatchParentWidth(f)
widgetSetHeight(f, 32)
}
const root = VStack(0, [column])
setPadding(root, 24, 40, 24, 40)
widgetSetBackgroundColor(root, 1, 1, 1, 1)
App({ title: "TextField width", width: 380, height: 160, body: root })
Expected: both fields are 300pt wide at x = 0.
Actual: field a is 304pt wide at x = -2. Field b is 300pt wide at x = 0. Geisterhand's /widgets?tree=true reports the same frames.
Cause
15158cd "pad text fields as CSS padding does" (#11665) made textfieldSetBackgroundColor paint the layer and call setDrawsBackground(false) on the field. An NSTextField with no bezel, no border, and no background is shaped like a label to AppKit. AppKit gives it a label's alignmentRectInsets: 2pt on the left and the right. Auto Layout pins the alignment rect to the stack, so the frame grows 2pt past each edge. The layer border and the layer background draw on the frame.
Measured with the field pinned to a 590pt stack:
| Build |
textfieldSetBackgroundColor |
drawsBackground |
alignmentRectInsets |
Frame |
| main (d368f62) |
no |
true |
0, 0 |
x 0, w 590 |
| main (d368f62) |
yes |
false |
2, 2 |
x -2, w 594 |
| main (d368f62) |
yes, bezeled |
false |
0, 0 |
x 0, w 590 |
| before #11661 (4d4cd17) |
yes |
true |
0, 0 |
x 0, w 590 |
native_textfield_cell.rs sets each field's frame by hand, so it does not see the inset.
Proposed fix
Make PerryTextField and PerrySecureTextField return zero alignmentRectInsets. A field must fill the width it is pinned to, whatever it draws. Add a native test that pins a borderless field with a background to a stack and checks that its frame matches the stack's width.
A borderless
TextFieldthat has a background color is 4pt wider than the stack it fills on macOS. It overhangs the stack by 2pt on each side, so its border and background draw outside the column.Repro
Expected: both fields are 300pt wide at x = 0.
Actual: field
ais 304pt wide at x = -2. Fieldbis 300pt wide at x = 0. Geisterhand's/widgets?tree=truereports the same frames.Cause
15158cd "pad text fields as CSS padding does" (#11665) made
textfieldSetBackgroundColorpaint the layer and callsetDrawsBackground(false)on the field. AnNSTextFieldwith no bezel, no border, and no background is shaped like a label to AppKit. AppKit gives it a label'salignmentRectInsets: 2pt on the left and the right. Auto Layout pins the alignment rect to the stack, so the frame grows 2pt past each edge. The layer border and the layer background draw on the frame.Measured with the field pinned to a 590pt stack:
textfieldSetBackgroundColordrawsBackgroundalignmentRectInsetsnative_textfield_cell.rssets each field's frame by hand, so it does not see the inset.Proposed fix
Make
PerryTextFieldandPerrySecureTextFieldreturn zeroalignmentRectInsets. A field must fill the width it is pinned to, whatever it draws. Add a native test that pins a borderless field with a background to a stack and checks that its frame matches the stack's width.