Skip to content

Slider

Header: winui/ui/input/Slider.hpp. Spec: winui/ui/input/Slider.hpp.

A thumb dragged along a track to pick a value in [min(), max()].

A click on the track jumps the thumb there and starts a drag; the drag snaps to step_frequency() (or the ticks, see set_snaps_to()). The arrow keys move by step(), Home and End jump to the ends, and on_value_changed() fires live on every change. An optional header sits above the track. The declarative form is ui::Slider.

Example:

auto  panel  = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
auto* label  = panel->add(std::make_unique<ui::TextControl>("50"));
auto* volume = panel->add(std::make_unique<ui::SliderControl>(ui::Orientation::Horizontal,
                                                          0.0f, 100.0f, 50.0f));
volume->set_header("Volume");
volume->set_step(5.0f);
volume->set_tick_frequency(10.0f);
volume->set_tick_placement(ui::SliderTickPlacement::BottomRight);
volume->on_value_changed([label](double v) { label->set_text(std::format("{:.0f}", v)); });
window.set_content(std::move(panel));

ui::Slider

A draggable thumb over a continuous range. Builds a ui::SliderControl.

orientation, the range and the initial value go to the control's constructor, so the value is clamped against the right range from the start and seeding it raises nothing.

Example:

ui::Property<double> volume{40.0};

window.set_content(ui::VerticalStack{
    .spacing  = 8,
    .content  = {
        ui::Slider{.header           = "Volume",
                   .value            = volume,
                   .max              = 100.0,
                   .step             = 5.0,
                   .tick_frequency   = 10.0,
                   .tick_placement   = ui::SliderTickPlacement::BottomRight,
                   .width            = 240.0f},
        ui::Text{.text = ui::bind(volume, [](double v) { return std::format("{:.0f}%", v); })},
    },
});

Every field, in the order a designated initializer names them:

ui::Slider{
    .header, .value, .min, .max, .step, .tick_frequency, .tick_placement, .step_frequency,
    .snaps_to, .orientation, .direction_reversed, .style, .margin, .width, .height, .min_width,
    .min_height, .max_width, .max_height, .horizontal_alignment, .vertical_alignment, .visible,
    .tool_tip, .enabled, .accessibility, .on_value_changed, .on_pointer_entered,
    .on_pointer_exited, .on_got_focus, .on_lost_focus, .on_key_down, .on_wheel, .control
}

Fields, in declaration order (designators must follow it):

Field Type Default
header ui::Binding<std::string> The label above the control. Empty draws none.
value ui::TwoWayField<double> {0.0} Value, clamped to [min, max]. ui::bind(prop) is two-way: dragging writes the property.
min double 0.0 Minimum, the value at the start of the track.
max double 100.0 Maximum, the value at the end of the track.
step ui::Binding<double> {1.0} What an arrow key adds or takes away; 0 turns arrow stepping off.
tick_frequency ui::Binding<double> {0.0} See ui::SliderControl::set_tick_frequency: 0 draws no ticks, except under the classic look.
tick_placement ui::Binding<ui::SliderTickPlacement> {ui::SliderTickPlacement::Inline} Where the ticks go relative to the track; Inline (across it) by default.
step_frequency ui::Binding<double> {1.0} What a drag snaps to: 1 by default, 0 for a continuous drag. step stays the arrow-key increment.
snaps_to ui::Binding<ui::SliderSnapsTo> {ui::SliderSnapsTo::StepValues} A drag lands on step_frequency multiples, or on ticks (which the arrow keys then move by too).
orientation ui::Orientation ui::Orientation::Horizontal Horizontal or Vertical; fixed at build time (the control has no setter for it).
direction_reversed ui::Binding<bool> {false} Min at the right (horizontal) or the top (vertical).
on_value_changed ui::Callback<void(double)> The new value, live on every change during a drag, click or key press.
on_pointer_entered ui::Action The pointer moved onto the control.
on_pointer_exited ui::Action The pointer left the control.
on_got_focus ui::Action Keyboard focus arrived on the control, or on anything inside it, from outside it. A control with nothing focusable in it never gets focus, and never raises this.
on_lost_focus ui::Action Keyboard focus left the control and everything inside it.
on_key_down ui::Callback<ui::Handling(const ui::KeyEvent&)> A key pressed while focus is on the control or inside it, before the control's own handling; ui::Handled stops it there (Element::on_key_down()).
on_wheel ui::Callback<ui::Handling(const ui::PointerEvent&)> A wheel turn over the control, before the control scrolls; ui::Handled stops it there (Element::on_wheel()).
control ui::ControlOut<ui::Slider> Out: the handle that receives the built control.

Plus the common fields style, margin, width, height, min_width, min_height, max_width, max_height, horizontal_alignment, vertical_alignment, visible, tool_tip, enabled, accessibility every spec carries (conventions).

build

std::unique_ptr<Element> build()&&

Consumes the spec and returns the built ui::SliderControl; the caller owns it.

ui::SliderControl

Derives from ui::Element, ui::RangeValueProvider.

A thumb dragged along a track to pick a value in [min(), max()].

A click on the track jumps the thumb there and starts a drag; the drag snaps to step_frequency() (or the ticks, see set_snaps_to()). The arrow keys move by step(), Home and End jump to the ends, and on_value_changed() fires live on every change. An optional header sits above the track. The declarative form is ui::Slider.

Example:

auto  panel  = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
auto* label  = panel->add(std::make_unique<ui::TextControl>("50"));
auto* volume = panel->add(std::make_unique<ui::SliderControl>(ui::Orientation::Horizontal,
                                                          0.0f, 100.0f, 50.0f));
volume->set_header("Volume");
volume->set_step(5.0f);
volume->set_tick_frequency(10.0f);
volume->set_tick_placement(ui::SliderTickPlacement::BottomRight);
volume->on_value_changed([label](double v) { label->set_text(std::format("{:.0f}", v)); });
window.set_content(std::move(panel));

SliderControl

SliderControl(Orientation orientation = Orientation::Horizontal, double min = 0.0, double max = 100.0, double value = 0.0)

A slider over [min, max] starting at value (clamped into the range). Raises nothing.

orientation

Orientation orientation() const

The orientation given at construction; there is no setter.

const std::string& header() const

The header text; empty when none is shown.

set_header

void set_header(std::string header)

The label above the slider. Empty draws none.

min

double min() const

The value at the start of the track.

max

double max() const

The value at the end of the track; never below min().

set_range

void set_range(double min, double max)

Sets the range and clamps the current value into it, firing on_value_changed if that moves it. A max below min is taken as min.

value

double value() const

Value, always within [min(), max()].

set_value

void set_value(double value)

Clamps to [min, max]. Fires on_value_changed only if the clamped value differs from the current one.

step

double step() const

The arrow-key increment, 1 by default. Also UI Automation's small change.

set_step

void set_step(double step)

The amount each arrow-key press moves the value by. Zero disables keyboard stepping (Home/End still jump to min/max). Negative is 0.

tick_frequency

double tick_frequency() const

The tick interval in value units; 0 (the default) means none is set.

set_tick_frequency

void set_tick_frequency(double frequency)

How often a tick mark is drawn along the track, in value units. Ticks sit at whole multiples of it counted from min(), one at min() and one per full interval after, so a range the frequency does not divide gets no tick at max(). Ticks closer than 20 DIPs are thinned to every n-th one.

At zero the Fluent look draws no ticks, while the classic look still draws the two ticks marking the ends of the range. Negative is 0. Redraws only.

tick_placement

SliderTickPlacement tick_placement() const

Where the ticks go relative to the track; Inline by default.

set_tick_placement

void set_tick_placement(SliderTickPlacement placement)

Redraws only.

step_frequency

double step_frequency() const

The drag snapping interval, 1 by default.

set_step_frequency

void set_step_frequency(double frequency)

The interval a pointer click or drag snaps to under SliderSnapsTo::StepValues. It is 1 by default, so a drag lands on whole values. Give a range narrower than a few units a finer step. Zero makes the drag continuous. Snapping lands on whole multiples of the interval counted from 0, not from min(), clamped into the range. Keyboard stepping uses step() and is unaffected. set_value() never snaps.

snaps_to

SliderSnapsTo snaps_to() const

What a drag snaps to; StepValues by default.

set_snaps_to

void set_snaps_to(SliderSnapsTo snaps_to)

SliderSnapsTo::Ticks snaps a drag to tick_frequency() instead and makes the arrow keys move by it, ignoring step(). With no tick frequency set it behaves as StepValues.

direction_reversed

bool direction_reversed() const

Reverses the direction: min at the right of a horizontal slider and at the top of a vertical one, with the value fill, the ticks, a click, a drag and the arrow keys all following (Left and Down then increase, Right and Up decrease). False by default.

set_direction_reversed

void set_direction_reversed(bool reversed)

Redraws; the value itself does not change.

set_track_gradient

void set_track_gradient(std::optional<LinearGradientBrush> gradient, bool checkerboard = false)

Draws the whole track with gradient, in place of the neutral track and the accent fill up to the thumb. checkerboard draws a 50% checkerboard (Pattern::Checkerboard50) behind the gradient first, so a channel whose stops carry partial alpha looks translucent. std::nullopt restores the default track. The retro (95/98/2000) bevel track ignores the gradient.

on_value_changed

void on_value_changed(Callback<void(double)> handler)

Fired whenever value changes, from a drag, a click-to-jump, or a keyboard step alike. A drag fires on every change, not only on release.

parallel_measure_safe

bool parallel_measure_safe() const override

True: measuring reads only the slider's own state, the measurer and the theme.

focused

bool focused() const

Whether the slider holds keyboard focus (the focus ring is drawn).

focusable

bool focusable() const override

A tab stop while enabled.

hovered

bool hovered() const

Whether the pointer is over the slider (false while disabled).

dragging

bool dragging() const

Whether a pointer drag is in progress.

is_pressed

bool is_pressed() const

Enabled and dragging.

automation_role

AutomationRole automation_role() const override

Slider. The header names it when set. Without a header, set accessibility().name, as for an icon-only Button.

automation_name_fallback

std::string automation_name_fallback() const override

The header text.

visual_state

ButtonState visual_state() const

The state the thumb is drawn in: Disabled, then Pressed while dragging, then Hover, else Normal.

measure_override

Size measure_override(Size available, const MeasureContext& context) const override

The header, when set, is stacked above the track (ControlHeader). The track asks for 200 DIPs along its axis (less if less is offered) and at least 32 across it.

arrange_override

void arrange_override(const Rect& final_rect, const ArrangeContext& context) override

Places the header; the track takes the rest of the bounds.

pointer_override

bool pointer_override(const PointerEvent& e) override

Press on the track jumps the value there and starts a drag; moves while dragging update the value (snapped per snaps_to()); release ends the drag.

render_override

void render_override(Renderer& renderer, const RenderContext& context) const override

Draws the header, the track (or set_track_gradient()'s gradient), the value fill, the ticks and the thumb, or the classic track and thumb under a retro theme.

enabled_changed

void enabled_changed() override

is_enabled() flipped, through set_enabled() here or on an ancestor, or a move to another parent. Drop any press or hover state. The element is redrawn either way. No-op by default.

focus_changed

void focus_changed(bool focused) override

Called by Window as focus moves; redraws.

key_override

bool key_override(const KeyEvent& e) override

Arrow keys step by step() (or the tick frequency under SliderSnapsTo::Ticks), Home/End jump to min()/max().

focus_ring_rect

Rect focus_ring_rect(const Theme& theme) const override

Around the thumb, around the track's row in the Fluent eras, or around the whole control in the classic look. None where focus shows on the thumb's face.

focus_ring_corner_radius

float focus_ring_corner_radius(const Theme& theme) const override

The ring's corner radius: the theme's control radius by default.

Enumerations

SliderTickPlacement

enum class SliderTickPlacement

Where tick marks are drawn relative to the track. No placement draws ticks until tick_frequency() is above zero, except under the classic look. The classic look only tells None (no ticks) from the rest, which all draw its one row of ticks on the far side (see set_tick_frequency()).

Value
None no ticks
TopLeft above a horizontal track, left of a vertical one
BottomRight below a horizontal track, right of a vertical one
Outside both of the above
Inline notches cut across the track itself (the default)

SliderSnapsTo

enum class SliderSnapsTo

What a pointer drag lands on.

Value
StepValues multiples of step_frequency()
Ticks multiples of tick_frequency(); the arrow keys move by it too

Tests

  • tests/test_slider.cpp