nine-slice skins¶
a nine-slice skin stretches one bitmap into a resizable frame: the four corners hold their size while the edges and center stretch to fill. it is how you build button chrome, window frames, tooltips, and health-bar borders from one small texture, at any panel size, without distortion.
nine-slice lives on Container through the BorderImage* properties (styles introduces the family). the Image blob does not nine-slice - its only scaling control is ObjectFit, which distorts the whole texture uniformly.
new Container
{
BorderImageSource = skin, // one Texture
BorderImageWidthLeft = 340f, // corner inset, in the source image's own pixels
BorderImageWidthTop = 340f,
BorderImageWidthRight = 340f,
BorderImageWidthBottom = 340f,
BorderWidth = 24f, // rendered corner size on screen
BorderImageRepeat = BorderImageRepeat.Stretch,
BorderImageFill = BorderImageFill.Filled,
}
the two widths¶
the engine feeds the shader from two independent inputs, and getting nine-slice right is entirely about keeping them apart:
BorderImageWidth* is the slice: where the corner art ends, in the source texture's own pixels. the shader divides it by the texture's dimensions, so it must be measured from the PNG, not guessed. a slice of 32 on a 1254px source marks 3% slivers as "corners" and stretches everything else - the classic smeared-borders look. open the image, measure where the corner geometry stops, use that number.
BorderWidth is the on-screen corner size. pick it the way you pick padding - 16 to 48px is normal - regardless of the source resolution. the corners downscale cleanly from however large the source art is.
do not set them equal by reflex. equal values only make sense when the source is already authored at screen scale (a 96px PNG with 32px corners). with hi-res art the equal-values habit is exactly what makes nine-slice look broken: a 512px panel with 200px corner art at BorderWidth = 200 draws 200 physical pixels of border. the right call is slice 200, BorderWidth 24 - cut the fat corners out of the source, draw them small and crisp.
the helper¶
Skins.NineSlice bundles the source, the four slice insets, and the rendered border so the slice cannot be set without a width:
var box = Skins.NineSlice( skin, sliceSrcPx: 340, renderedBorder: 24 );
box.Children.Add( new Text( "content" ) );
a path overload owns the texture loading, with the correct lazy-load behavior - a failed load is not cached, so it retries once assets mount instead of pinning a null forever:
var box = Skins.NineSlice( "ui/panel-skin.png", sliceSrcPx: 340, renderedBorder: 24 );
the returned Container fills its parent (Width and Height at 100%), so size and position it by wrapping it in a sized parent. repeat (default Stretch) and fill (default Filled) pass through to the properties below.
frame or panel¶
BorderImageFill decides what happens inside the border region. Unfilled (the property default) leaves the center transparent - a hollow frame around whatever renders behind it. Filled draws the center cell of the bitmap, stretched, for a solid skinned panel with content drawn on top.
BorderImageRepeat.Stretch scales each edge strip along its axis. Round tiles whole copies of the edge art and rescales so an integer number fits - use it when the edge has visible repeating detail that stretching would smear.
misuse warns at mount¶
the failure modes of nine-slice are silent at the engine level - a wrong configuration renders nothing or renders garbage, with no error. goo checks for the three known traps at mount and logs a console warning naming the container's HostPath:
- slice widths without a source.
BorderImageWidth*is set butBorderImageSourceis null or absent - almost always a failed texture load. a container with no background and a null border-image has nothing to draw and the engine culls the whole box. - a source without a rendered border.
BorderImageSourceis set but noBorderWidthis positive, so the image paints into a zero-width border region and shows nothing. - a slice that consumes the source. when opposite slices meet or cross (at or past 50% of a source dimension per axis) the shader drops the middle. this is the signature of a slice given in on-screen pixels instead of source pixels.
the warning a wrong-but-plausible slice cannot trigger: a value that is neither tiny nor half the texture, just not where the corner art ends. that one still renders smeared corners silently, which is why the slice is the one number you must take from the PNG itself.
when not to nine-slice¶
nine-slice needs art with corner structure: deliberate fixed-size corner geometry, straight stretchable rails, a fillable center. a full painterly panel - one continuous illustration - has none of that, and nine-slicing it will always look wrong. put it on the container as BackgroundImage instead and let it scale as a whole. that is the correct tool for that art, not a workaround.