paint method
Paint the box decoration into the given location on the given canvas
Implementation
@override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
assert(configuration.size != null);
final Rect rect = offset & configuration.size!;
final TextDirection? textDirection = configuration.textDirection;
// When this element participates in an inline formatting context, backgrounds and borders
// for inline-level boxes are painted by the paragraph path (InlineFormattingContext).
// Skip BoxDecoration painting here to avoid double painting and mismatched joins.
bool skipForInlineIFC() {
// Only inline-level boxes are painted via paragraph IFC.
if (renderStyle.effectiveDisplay != CSSDisplay.inline) return false;
final RenderBoxModel? self = renderStyle.attachedRenderBoxModel;
if (self == null) return false;
RenderObject? p = self.parent;
while (p != null) {
if (p is RenderFlowLayout) {
return p.establishIFC;
}
p = (p).parent;
}
return false;
}
if (skipForInlineIFC()) {
return;
}
bool hasLocalAttachment = _hasLocalBackgroundImage();
if (!hasLocalAttachment) {
if (renderStyle.backgroundClip != CSSBackgroundBoundary.text) {
final bool hasGradients = _hasGradientLayers();
final bool hasImages = _hasImageLayers();
Rect backgroundClipRect = _getBackgroundClipRect(offset, configuration);
Rect backgroundOriginRect = _getBackgroundOriginRect(offset, configuration);
if (hasImages) {
// Paint layered images (and gradients if present) in proper order.
_paintLayeredMixedBackgrounds(canvas, backgroundClipRect, backgroundOriginRect, configuration, textDirection);
} else if (hasGradients) {
_paintBackgroundColor(canvas, backgroundClipRect, textDirection);
} else {
_paintBackgroundColor(canvas, backgroundClipRect, textDirection);
}
}
}
// Check for custom border styles (dashed/double)
bool hasDashedBorder = false;
bool hasDoubleBorder = false;
if (_decoration.border != null) {
final Border border = _decoration.border as Border;
bool topDashed = border.top is ExtendedBorderSide &&
(border.top as ExtendedBorderSide).extendBorderStyle == CSSBorderStyleType.dashed;
bool rightDashed = border.right is ExtendedBorderSide &&
(border.right as ExtendedBorderSide).extendBorderStyle == CSSBorderStyleType.dashed;
bool bottomDashed = border.bottom is ExtendedBorderSide &&
(border.bottom as ExtendedBorderSide).extendBorderStyle == CSSBorderStyleType.dashed;
bool leftDashed = border.left is ExtendedBorderSide &&
(border.left as ExtendedBorderSide).extendBorderStyle == CSSBorderStyleType.dashed;
hasDashedBorder = topDashed || rightDashed || bottomDashed || leftDashed;
bool topDouble = border.top is ExtendedBorderSide &&
(border.top as ExtendedBorderSide).extendBorderStyle == CSSBorderStyleType.double;
bool rightDouble = border.right is ExtendedBorderSide &&
(border.right as ExtendedBorderSide).extendBorderStyle == CSSBorderStyleType.double;
bool bottomDouble = border.bottom is ExtendedBorderSide &&
(border.bottom as ExtendedBorderSide).extendBorderStyle == CSSBorderStyleType.double;
bool leftDouble = border.left is ExtendedBorderSide &&
(border.left as ExtendedBorderSide).extendBorderStyle == CSSBorderStyleType.double;
hasDoubleBorder = topDouble || rightDouble || bottomDouble || leftDouble;
}
// Prefer dashed painter, then double painter, then general border painting
// (with a special-case path for solid non-uniform widths + radius).
if (hasDashedBorder) {
_paintDashedBorder(canvas, rect, textDirection);
renderStyle.target.ownerDocument.controller.reportFP();
} else if (hasDoubleBorder) {
_paintDoubleBorder(canvas, rect, textDirection);
renderStyle.target.ownerDocument.controller.reportFP();
} else if (_decoration.border != null) {
final b = _decoration.border as Border;
// Special-case: solid, uniform color, non-uniform widths with radius -> paint ring ourselves.
if (_decoration.hasBorderRadius && _decoration.borderRadius != null) {
final bool allSolid = b.top is ExtendedBorderSide &&
b.right is ExtendedBorderSide &&
b.bottom is ExtendedBorderSide &&
b.left is ExtendedBorderSide &&
(b.top as ExtendedBorderSide).extendBorderStyle == CSSBorderStyleType.solid &&
(b.right as ExtendedBorderSide).extendBorderStyle == CSSBorderStyleType.solid &&
(b.bottom as ExtendedBorderSide).extendBorderStyle == CSSBorderStyleType.solid &&
(b.left as ExtendedBorderSide).extendBorderStyle == CSSBorderStyleType.solid;
final bool sameColor = b.top.color == b.right.color && b.top.color == b.bottom.color && b.top.color == b.left.color;
final bool nonUniformWidth = !(b.isUniform);
final bool uniformWidthCheck = b.top.width == b.right.width && b.top.width == b.bottom.width && b.top.width == b.left.width;
if (DebugFlags.enableBorderRadiusLogs) {
try {
final el = renderStyle.target;
renderingLogger.finer('[BorderRadius] border solid/uniform checks on <${el.tagName.toLowerCase()}> '
'allSolid=$allSolid sameColor=$sameColor b.isUniform=${b.isUniform} uniformWidth=$uniformWidthCheck '
'w=[${b.left.width},${b.top.width},${b.right.width},${b.bottom.width}]');
} catch (_) {}
}
if (allSolid && sameColor && nonUniformWidth) {
_paintSolidNonUniformBorderWithRadius(canvas, rect, b);
renderStyle.target.ownerDocument.controller.reportFP();
return;
}
// New special-cases for solid borders with uniform width (possibly different colors):
// - Circle (rounded-full): paint per-side centered 90° arcs.
// - General rounded-rect (non-circle): paint each side with half-corner arcs + straight segment.
if (allSolid && uniformWidthCheck && b.top.width > 0.0) {
// Treat as a circle when the box is square and all corner radii are
// at least half of the side (handles "rounded-full" like 9999px).
bool isCircleByBorderRadius(BorderRadius br, Rect r, {double tol = 0.5}) {
final double w = r.width;
final double h = r.height;
if ((w - h).abs() > tol) return false;
final double half = w / 2.0;
final List<Radius> corners = [br.topLeft, br.topRight, br.bottomRight, br.bottomLeft];
for (final c in corners) {
if (c.x + tol < half || c.y + tol < half) return false;
}
return true;
}
final BorderRadius br = _decoration.borderRadius!;
final bool isCircle = isCircleByBorderRadius(br, rect);
if (DebugFlags.enableBorderRadiusLogs) {
try {
final el = renderStyle.target;
final r0 = br.topLeft; final r1 = br.topRight; final r2 = br.bottomRight; final r3 = br.bottomLeft;
renderingLogger.finer('[BorderRadius] circle detect(solid) <${el.tagName.toLowerCase()}> '
'w=${rect.width.toStringAsFixed(2)} h=${rect.height.toStringAsFixed(2)} '
'tl=(${r0.x.toStringAsFixed(2)},${r0.y.toStringAsFixed(2)}) '
'tr=(${r1.x.toStringAsFixed(2)},${r1.y.toStringAsFixed(2)}) '
'br=(${r2.x.toStringAsFixed(2)},${r2.y.toStringAsFixed(2)}) '
'bl=(${r3.x.toStringAsFixed(2)},${r3.y.toStringAsFixed(2)}) isCircle=$isCircle');
} catch (_) {}
}
if (isCircle) {
if (DebugFlags.enableBorderRadiusLogs) {
try {
final el = renderStyle.target;
renderingLogger.finer('[BorderRadius] solid per-side circle border painter for <${el.tagName.toLowerCase()}>');
} catch (_) {}
}
_paintSolidPerSideCircleBorder(canvas, rect, b);
renderStyle.target.ownerDocument.controller.reportFP();
return;
} else if (!sameColor) {
if (DebugFlags.enableBorderRadiusLogs) {
try {
final el = renderStyle.target;
renderingLogger.finer('[BorderRadius] solid per-side rounded-rect painter for <${el.tagName.toLowerCase()}>');
} catch (_) {}
}
_paintSolidPerSideRoundedRect(canvas, rect, b);
renderStyle.target.ownerDocument.controller.reportFP();
return;
}
}
}
// Fallback: use Flutter's Border.paint. Only pass radius when border is uniform.
final BorderRadius? borderRadiusForPaint = b.isUniform ? _decoration.borderRadius : null;
if (!b.isUniform && _decoration.borderRadius != null && DebugFlags.enableBorderRadiusLogs) {
try {
final el = renderStyle.target;
renderingLogger.finer('[BorderRadius] skip passing radius to border painter for <${el.tagName.toLowerCase()}> '
'due to non-uniform border');
} catch (_) {}
}
if (DebugFlags.enableBorderRadiusLogs) {
try {
final el = renderStyle.target;
renderingLogger.finer('[BorderRadius] call Flutter Border.paint for <${el.tagName.toLowerCase()}> '
'uniform=${b.isUniform} passRadius=${borderRadiusForPaint != null}');
} catch (_) {}
}
_decoration.border?.paint(
canvas,
rect,
shape: _decoration.shape,
borderRadius: borderRadiusForPaint,
textDirection: configuration.textDirection,
);
renderStyle.target.ownerDocument.controller.reportFP();
}
_paintShadows(canvas, rect, textDirection);
}