Render Atoms

A single control is rarely just one flat shape - a button has a background, an optional icon and a text label; a slider has a track and a handle. Rather than give every one of these pieces its own bespoke style syntax, renderers built on <RenderAtom> break a control down into a small, fixed set of named, reusable building blocks called render atoms (e.g. background, icon, value) - referred to simply as atoms for brevity throughout the rest of this chapter. Each render atom is a generic rectangular building block that can carry a fill (solid color, gradient or image) or text, and (optionally) a border - and every one of those aspects is addressed as an ordinary property in a Style.

Which atoms a control exposes, and what they are called, depends on the renderer behind it - different controls declare different atoms. What all of them share is the <RenderAtom> property set and the addressing scheme described here; BIVRenderer (covered below) is one concrete example.

Atom Properties

An atom’s properties are grouped like this:

offsetx, offsety

point offset added to the atom’s position while drawing

scale

uniform scale factor applied around the atom’s own center; left unset (or 0) it behaves as 1 (no scaling). It scales the drawing only - the atom keeps the size it was laid out at.

rotation

rotation in degrees, applied around the atom’s own center while drawing

color

the atom’s fill - a solid color or a gradient, see Colors

opacity

0..1 applies to images, gradients, solid fills and text

bordercolor, borderwidth

an outline drawn around the atom

borderlefttopradius, borderleftbottomradius, borderrighttopradius, borderrightbottomradius

independent corner rounding for both the fill and the outline

contrastcolorbright, contrastcolordark, contrastcolortransparent, contrastcoloralphablend

colors a renderer can draw text or an icon in to stay legible against a color known only at runtime, see Contrast Colors

image, imageframe

an image (or one frame of a multi-frame image) drawn instead of the solid/gradient fill

All of these accept an <Animation> and can be overridden per <StyleCondition>, exactly like any other style property.

By default, an atom with an image does not fill its area with color. If the image has the template or adaptive attribute (see <Image>), color colorizes it; any other image is drawn as it is.

Contrast Colors

Some controls know a color only at runtime - typically the color of a colorname parameter - and keep text or an icon legible against it. The contrast colors of the atom decide the color it is drawn in, checked in this order:

  1. the runtime color is transparent: contrastcolortransparent

  2. contrastcolorbright is set: contrastcolorbright on a dark runtime color (luminance below 0.35), contrastcolordark on a bright one

  3. contrastcoloralphablend is set: the runtime color, blended with contrastcoloralphablend

  4. otherwise: the runtime color itself

contrastcolordark and contrastcolortransparent default to the color of the atom. A control can also keep the color of the atom as long as neither contrastcolorbright nor contrastcoloralphablend is set - its style says so.

Addressing Atom Properties

An atom’s properties are addressed from a <Style> by prefixing the property name with the atom’s name and a dot: <atomName>.<property>. A plain <Metric>, <Color> or gradient element then targets it like any other style property:

<Style name="AccentButton" inherit="Standard.Button">
  <LinearGradient name="background.color" startcolor="red" endcolor="magenta"/>
  <Metric name="background.borderlefttopradius" value="20"/>
  <Metric name="background.borderrightbottomradius" value="20"/>
  <Color name="value.color" color="white"/>

  <StyleCondition name="mouseover">
    <Metric name="value.scale" value="1.3"/>
    <Metric name="value.rotation" value="30"/>
  </StyleCondition>
</Style>

Here background and value are atom names; background.color, value.scale and value.rotation each address one property on one atom. A <StyleCondition> (here: mouseover) overrides atom properties the same way it overrides any other style property.

When color is set to a gradient, its start/end points (or center/radius) are computed automatically to cover the atom - explicit geometry authored on the <LinearGradient> or <RadialGradient> element itself is only honored when isContentFit and isContentFill are both unset (see below).

Fitting Content

An atom’s image and (gradient) fill both react to two flags, isContentFit and isContentFill, in the same way images do on the web with object-fit: contain / cover / fill / none. These two flags are not style properties - they are fixed per atom by the renderer that owns it (e.g. a background atom that is meant to always cover its control regardless of aspect ratio), not something a skin author overrides per instance.

four content modes (none, fit, fill, fit + fill) shown for an image and a radial gradient atom
  • fit + fill - cover the area, preserving aspect ratio: the content gets cropped

  • fit - fit into the area, preserving aspect ratio: the area is not fully covered

  • fill - fill the area, distorting the content to match it exactly

  • none - keep the content at its own original size, centered in the atom

A radial gradient’s shape is a plain, non-elliptical circle, so it cannot be distorted the way an image can: isContentFill alone already produces the same cropped, fully-covering circle as fit + fill. A linear gradient has no aspect ratio of its own either way, so it keeps spanning the atom’s full width whenever either flag is set. For both kinds of gradient, leaving both flags unset leaves the gradient at whatever size and position it was last given, instead of resizing it to the atom.

The BIVRenderer

BIVRenderer (“Background-Icon-Value”) is the renderer behind controls that show an icon and/or a text value on top of a background - for example <Button> and <SelectBox>. It declares exactly three atoms - background, icon and value - addressed as described above (e.g. icon.opacity, value.color).

layout anatomy of a BIVRenderer-based control, showing background, icon and value atoms, padding and icon/value spacing

Besides the three atoms, its <BIVStyle> adds a handful of layout-only properties that arrange the icon and value within the control, rather than styling either one of them directly:

iconvaluespacing:

gap kept between the icon and the value when both are present

iconleading:

draws the icon before the value instead of after it

iconfillfactor:

the icon’s size relative to the content area, aspect ratio kept - 1 fills the area, more than 1 reaches beyond it; left unset (or 0) the icon keeps the size of its image

iconalign:

vertical alignment of the icon within the content area (default: vertically centered); left/right also align an icon that has no value next to it

valuealign:

vertical alignment of the value within the content area, and horizontal alignment of the icon/value group as a whole within the control (default: falls back to the general textalign property)

<Style name="Standard.Button">
  <Metric name="iconvaluespacing" value="6"/>
  <Metric name="iconleading" value="1"/>
  <Metric name="iconfillfactor" value="0.8"/>
  <Align name="valuealign" align="left vcenter"/>
</Style>

iconfillfactor and the icon atom’s icon.scale look similar but are not interchangeable. iconfillfactor is a layout property: it sizes the icon’s box relative to the control’s content area, so the icon grows and shrinks with the control, and iconvaluespacing, the alignments and the control’s minimum size all see that box. icon.scale is a draw-time transform: it scales the drawing inside a box that keeps its layout size, relative to the image’s own pixel size rather than to the control. Use iconfillfactor to give an icon its size, and icon.scale in a <StyleCondition> or <Animation> to emphasize it temporarily.