Skip to content

ui::ButtonVisual

Header: winui/ui/themes/Theme.hpp.

How a button-shaped surface looks in one interaction state: its fill, border and label color, plus the optional era layers (bevels, gradients, inner rings, wells) that draw_control_chrome draws.

Theme holds one per state per style (button_normal, accent_button_hover, ...), and the built-in toggles, sliders and swipe items use the same type. A plain ButtonVisual{background, border, text} leaves every optional layer off and draws a flat rounded surface. The other fields are for themes that reproduce an era.

Example:

ui::Theme theme = ui::Theme::light();
theme.button_hover = ui::ButtonVisual{ui::Color::from_argb(0xFFE0E0E0), // background
                                         ui::Color::from_argb(0xFF808080), // border
                                         ui::colors::Black};               // label
theme.button_hover.border_width = 2.0f; // this state only; the rest keep Theme::border_width
window.pin_theme(std::move(theme));

background

Color background

The flat fill. Also set as the fallback color when background_gradient is set.

border

Color border

The outline color, for a surface that draws a flat border.

text

Color text

The label, glyph or mark drawn on the surface.

border_style

BorderStyle border_style = BorderStyle::Rounded

For era themes only. Every field below defaults to off, so an aggregate-initialized ButtonVisual{bg, border, text} draws a plain flat surface.

Flat and Rounded, the default, draw no separate border, and background is the whole fill. Raised and Sunken draw a classic 3-D edge around the fill with render/Bevel.hpp's draw_bevel_border(), from the bevel_* colors below, in place of the border and gradient fields.

bevel_light

Color bevel_light = colors::White

The four bevel rings. bevel_light is the outer top/left ring, bevel_highlight the inner top/left ring, bevel_shadow the inner bottom/right ring and bevel_dark_shadow the outer bottom/right ring, the hard dark edge at a raised button's bottom-right corner. Windows 95 and 98 use five distinct colors with the face: #FFFFFF, #DFDFDF, #C0C0C0, #808080 and #000000. Windows 2000 makes the light ring the face color and softens the dark ring to #404040, so the same edge has visibly lower contrast.

bevel_highlight

Color bevel_highlight = colors::White

bevel_shadow

Color bevel_shadow = Color::from_argb(0xFF808080)

bevel_dark_shadow

Color bevel_dark_shadow = colors::Black

bevel_soft

bool bevel_soft = false

The push-button arrangement, which swaps the two top/left rings (render/Bevel.hpp). Set only on push-button visuals. A window border, field well or check-box well keeps the plain arrangement, since the era draws both.

bevel_rings

int bevel_rings = 2

How many of the two bevel rings to draw. The era's hot toolbar button is a single edge with no second ring, a third raised shape next to the window border and the push button (render/Bevel.hpp's rings).

bevel_frame

std::optional<Color> bevel_frame {}

Windows 3.x only. When set, the edge is drawn by draw_win3x_border instead of draw_bevel_border: a 1px frame in this color on all four sides, with a 1px bevel_highlight on the top and left and a 2px bevel_shadow on the right and bottom inside it. The pressed state drops the bottom and right edge entirely. bevel_light and bevel_dark_shadow are not used here, since Windows 3.x has no outer bevel rings. Unset everywhere else.

background_gradient

std::optional<LinearGradientBrush> background_gradient {}

A top-to-bottom gradient fill in place of background, for Luna and Aero. background stays set alongside it as a flat fallback for code that only needs the button's rough color.

inner_border

std::optional<LinearGradientBrush> inner_border {}

Aero's inner highlight ring, the fourth layer of its button: a second 1px stroke one pixel inside the outer border, with a radius one less. The outer border sits at (0.5, 0.5, w-1, h-1) with radius 2.75 and this ring at (1.5, 1.5, w-3, h-3) with radius 1.75. It makes an Aero surface look like a lit bevel, and its color marks three states: white #FAFFFFFF to #85FFFFFF at rest and disabled, absent when pressed (the press animation fades it out first), and solid cyan #F900CCFF on the default button, the era's only default-button cue. The gradient stretches over the ring's own height, where border_gradient below spans a fixed number of pixels.

inner_border_width

float inner_border_width = 1.0f

How thick that ring is. Aero's is one pixel, the default. Luna's is two: the amber glow of a hot button sits 2 px thick inside its 1px #003C74 border, and the check box's ring is 2 px too, inset by 1 px. A 1px ring would look like a thin outline instead of a glow.

inner_shadow

std::optional<Color> inner_shadow {}

Aero's pressed inner shadow: two 2px bands inside the top and left edges, fading from this color to transparent, drawn over the fill and under the border. Aero shows a press by shading the button's inside, and the label stays where it is.

inset_well

std::optional<InsetWell> inset_well {}

Aero's check box and radio button only (see InsetWell): the rim and fill drawn inside the well, over background and under the border. Unset everywhere else.

border_gradient

std::optional<LinearGradientBrush> border_gradient {}

Fluent's elevation border: a gradient stroke drawn in place of a flat border when set, which makes the control look slightly raised. Theme::fluent() sets it only on the neutral and accent buttons at rest and on hover. Every other ButtonVisual leaves it unset: pressed and disabled states, subtle buttons, toggle indicators, and the era and high-contrast themes. The gradient spans border_gradient_extent_px device pixels whatever the control's height. A positive span starts at the top edge, and a negative one starts at the bottom edge with the stops reversed (see StrokeRoundedRectGradient).

border_gradient_extent_px

float border_gradient_extent_px = 3.0f

mark_border_gradient

std::optional<LinearGradientBrush> mark_border_gradient {}

The same raised look on the mark drawn over the indicator. The gradient runs from top to bottom over the mark's own height, with Palette::control_stroke_default at 50 % and Palette::control_stroke_secondary at 70 %, in light and dark alike. Unlike border_gradient it stretches with the mark: the upper half is the faint color and the lower 30 % the stronger one, whatever the mark's size. It is stroked 1 pixel inside the mark. Only RadioButton reads it, for its dot, and only Theme::fluent()'s checked ring sets it.

mark_gradient

std::optional<RadialGradientBrush> mark_gradient {}

Aero's radio dot: a radial fill lit from the top left and sized to the dot, with a flat mark_stroke ring 1px inside its edge, in place of the flat text disc. RadioButton only, and only where set. When it is set on an unchecked state the dot is drawn anyway, which is how the era previews a press. The gradient dot is drawn in render() only, and never animated as a one-color layer.

mark_stroke

std::optional<Color> mark_stroke {}

text_effect

TextEffect text_effect {}

The classic engraved look of a disabled label (see TextEffect). The default draws the label once, in plain color.

border_width

OptionalFloat border_width {}

This visual's own border width, for themes such as Windows 8.1, whose dark mode has a 2px Button outline and a 0px CheckBox outline, and light mode the opposite. Unset, the default, uses Theme::border_width (see resolved_border_width()).

draw_border

bool draw_border = false

Draws a flat stroke in border at the resolved width even when the caller of draw_control_chrome() did not ask for one through draw_flat_border_fallback. Metro's dark Button is only this stroke, a 2px outline with no fill. Defaults to false.

resolved_border_width

float resolved_border_width(float theme_default) const

Returns this visual's border_width if it set one, otherwise theme_default, which is Theme::border_width. A control that draws its border itself, without draw_control_chrome(), calls this to get the same width draw_control_chrome() would use.