Skip to content

AnnotatedScrollBar

Header: winui/ui/scrolling/AnnotatedScrollBar.hpp. Spec: winui/ui/scrolling/AnnotatedScrollBar.hpp.

A vertical scroll bar with a rail you click or drag to jump, up and down step buttons, and offset annotations that stay visible and hide when they collide. It is for moving through long grouped content, such as a photo timeline or an alphabetical list.

Attach it to a Scroll with set_target(). It then follows the view's range, viewport and offset, scrolls it right away, and hides the view's own scroll bar while attached. For any other scrollable, leave the target unset, call set_maximum(), set_viewport_size() and set_value() yourself, and handle on_scroll_to_requested() and on_scroll_by_requested(). The top step button is AnnotatedScrollBarScrollingEventKind::IncrementButton and scrolls up, toward 0. on_detail_text_requested() supplies the floating tooltip shown over the rail while hovering or dragging. The arrow keys step, and Home and End jump to the ends.

Example:

const std::string                   long_text = "A text much taller than the window...";
ui::Control<ui::Scroll>             view;
ui::Control<ui::AnnotatedScrollBar> bar;

window.set_content(ui::HorizontalStack{
    .content = {
        ui::Scroll{.max_height = 500.0f,
                   .control    = view,
                   .content    = ui::Text{.text = long_text, .wrapping = ui::TextWrapping::Wrap}},
        ui::AnnotatedScrollBar{
            .annotations              = {{.text = "2024", .scroll_offset = 0.0},
                                         {.text = "2025", .scroll_offset = 2400.0},
                                         {.text = "2026", .scroll_offset = 5100.0}},
            .max_height               = 500.0f,
            .on_detail_text_requested = [](double offset) { return std::format("{:.0f} px", offset); },
            .control                  = bar,
        },
    },
});
bar->set_target(view); // bar now drives and mirrors the Scroll

bar->on_scrolling([](const ui::AnnotatedScrollBarScrollingArgs& args) {
    const bool far_drag = args.kind == ui::AnnotatedScrollBarScrollingEventKind::Drag && args.offset > 9000.0;
    return far_drag ? ui::Cancel : ui::Proceed;
});

ui::AnnotatedScrollBar

A vertical scroll bar with step buttons, a rail you click or drag to jump, and offset annotations that hide when they collide. Builds a ui::AnnotatedScrollBarControl. There is no .target field. Call set_target() on .control once both the bar and the ui::ScrollControl exist.

Example:

ui::Spec make_photo_grid(); // the app's own content
ui::Control<ui::Scroll>             view;
ui::Control<ui::AnnotatedScrollBar> bar;

window.set_content(ui::HorizontalStack{.content = {
    ui::Scroll{.max_height = 500.0f, .control = view, .content = make_photo_grid()},
    ui::AnnotatedScrollBar{
        .annotations              = {{.text = "2024", .scroll_offset = 0.0},
                                     {.text = "2025", .scroll_offset = 2400.0},
                                     {.text = "2026", .scroll_offset = 5100.0}},
        .max_height               = 500.0f,
        .on_detail_text_requested = [](double offset) { return std::format("{:.0f} px", offset); },
        .control                  = bar,
    },
}});
bar->set_target(view); // the bar now drives and mirrors the Scroll

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

ui::AnnotatedScrollBar{
    .value, .maximum, .small_change, .viewport_size, .annotations, .style, .margin, .width,
    .height, .min_width, .min_height, .max_width, .max_height, .horizontal_alignment,
    .vertical_alignment, .visible, .tool_tip, .enabled, .accessibility, .on_scrolling,
    .on_detail_text_requested, .on_scroll_to_requested, .on_scroll_by_requested,
    .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
value ui::Binding<double> {0.0} The current offset in DIPs, for an unattached bar. Moves the thumb only and raises no scroll request.
maximum ui::Binding<double> {0.0} The largest scroll offset in DIPs, for an unattached bar. The minimum is 0.
small_change ui::Binding<double> {0.0} How far a step button or arrow key moves, in DIPs. 0, the default, derives it from the viewport.
viewport_size ui::Binding<double> {0.0} The visible extent in DIPs, for an unattached bar. Sizes the thumb.
annotations ui::Binding<std::vector<ui::AnnotatedScrollBarAnnotation>> Captions pinned beside the rail at scroll offsets. One that overlaps an earlier one hides.
on_scrolling ui::Callback<ui::Decision(const ui::AnnotatedScrollBarScrollingArgs&)> Raised before every scroll request, with the target offset and what caused it. Return ui::Cancel to drop it.
on_detail_text_requested ui::Callback<std::string(double)> The tooltip text for the offset under the pointer. Unset shows no tooltip.
on_scroll_to_requested ui::Callback<void(double)> Scroll to an absolute offset in DIPs. set_target() installs its own. Set it for another kind of scrollable.
on_scroll_by_requested ui::Callback<void(double)> Scroll by a delta in DIPs, negative is up. set_target() installs its own, too.
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::AnnotatedScrollBar> 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::AnnotatedScrollBarControl; the caller owns it.

ui::AnnotatedScrollBarControl

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

A vertical scroll bar with a rail you click or drag to jump, up and down step buttons, and offset annotations that stay visible and hide when they collide. It is for moving through long grouped content, such as a photo timeline or an alphabetical list.

Attach it to a Scroll with set_target(). It then follows the view's range, viewport and offset, scrolls it right away, and hides the view's own scroll bar while attached. For any other scrollable, leave the target unset, call set_maximum(), set_viewport_size() and set_value() yourself, and handle on_scroll_to_requested() and on_scroll_by_requested(). The top step button is AnnotatedScrollBarScrollingEventKind::IncrementButton and scrolls up, toward 0. on_detail_text_requested() supplies the floating tooltip shown over the rail while hovering or dragging. The arrow keys step, and Home and End jump to the ends.

Example:

const std::string                   long_text = "A text much taller than the window...";
ui::Control<ui::Scroll>             view;
ui::Control<ui::AnnotatedScrollBar> bar;

window.set_content(ui::HorizontalStack{
    .content = {
        ui::Scroll{.max_height = 500.0f,
                   .control    = view,
                   .content    = ui::Text{.text = long_text, .wrapping = ui::TextWrapping::Wrap}},
        ui::AnnotatedScrollBar{
            .annotations              = {{.text = "2024", .scroll_offset = 0.0},
                                         {.text = "2025", .scroll_offset = 2400.0},
                                         {.text = "2026", .scroll_offset = 5100.0}},
            .max_height               = 500.0f,
            .on_detail_text_requested = [](double offset) { return std::format("{:.0f} px", offset); },
            .control                  = bar,
        },
    },
});
bar->set_target(view); // bar now drives and mirrors the Scroll

bar->on_scrolling([](const ui::AnnotatedScrollBarScrollingArgs& args) {
    const bool far_drag = args.kind == ui::AnnotatedScrollBarScrollingEventKind::Drag && args.offset > 9000.0;
    return far_drag ? ui::Cancel : ui::Proceed;
});

AnnotatedScrollBarControl

AnnotatedScrollBarControl() = default

An unattached bar with an empty range and no annotations.

maximum

double maximum() const

The largest scroll offset, in DIPs. The minimum is always 0, as for Scroll. With a target attached, the target's current scrollable_height().

set_maximum

void set_maximum(double maximum)

Sets the range for an unattached bar and redraws. Ignored while target() is attached, since maximum() then reads target()->scrollable_height().

viewport_size

double viewport_size() const

The visible extent in DIPs, which sizes the thumb and the automatic small change. With a target attached, the target's current height.

set_viewport_size

void set_viewport_size(double viewport_size)

Sets the viewport extent for an unattached bar and redraws. Ignored while target() is attached, since viewport_size() then reads target()->bounds().height.

value

double value() const

The current scroll offset in DIPs, 0 to maximum(). With a target attached, its current vertical_offset().

set_value

void set_value(double value)

Moves the thumb and annotations only, and raises no scroll request. Ignored while target() is attached, since value() then reads target()->vertical_offset().

target

ScrollControl* target() const

The Scroll this bar drives, or null, in which case the manual range values apply.

set_target

void set_target(ScrollControl* target)

Attaches the bar to target, which must outlive this control. The bar does not own it. Installs the bar's own on_scroll_to_requested() and on_scroll_by_requested() handlers, replacing any set before. While attached, the target's own scroll bar is hidden (ScrollControl::set_show_scrollbar(false)), and it comes back when the bar detaches or moves to another target. Pass nullptr to detach. That clears the two handlers and goes back to the manual maximum(), viewport_size() and value(). Set on_scroll_to_requested() and on_scroll_by_requested() again afterward to drive another kind of scrollable.

annotations

const std::vector<AnnotatedScrollBarAnnotation>& annotations() const

The annotations, in the order given.

set_annotations

void set_annotations(std::vector<AnnotatedScrollBarAnnotation> annotations)

Replaces the annotations, then lays out again, since the annotations column fits the widest one, and redraws.

small_change

double small_change() const

How far a step button or an arrow key moves value(), in DIPs. 0, the default, means automatic: an eighth of viewport_size().

set_small_change

void set_small_change(double small_change)

Sets the step size in DIPs; negative values are taken as 0 (auto).

on_scrolling

void on_scrolling(Callback<Decision(const AnnotatedScrollBarScrollingArgs&)> handler)

Raised before every scroll request, whether from a click, a drag, a step button, an arrow key, or Home or End. Returning ui::Cancel drops the request.

on_detail_text_requested

void on_detail_text_requested(Callback<std::string(double offset)> handler)

Text for the floating detail tooltip shown while hovering over or dragging the rail, given the offset under the pointer. No tooltip opens while this is unset.

on_scroll_to_requested

void on_scroll_to_requested(Callback<void(double offset)> handler)

Request to scroll to offset, in DIPs. set_target() installs its own handler. Set it yourself to drive a scrollable that is not a Scroll.

on_scroll_by_requested

void on_scroll_by_requested(Callback<void(double delta)> handler)

Low-level request to scroll by delta DIPs (step buttons, arrow keys); negative is up.

focused

bool focused() const

Whether the bar has keyboard focus.

focusable

bool focusable() const override

Focusable while enabled.

automation_role

AutomationRole automation_role() const override

Exposed to accessibility tools as a Slider, with a range value, since it shows and sets a bounded scroll value.

measure_override

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

The labels column, as wide as the widest label, plus the fixed-width rail column. The height is what's offered.

pointer_override

bool pointer_override(const PointerEvent& e) override

Clicks and drags on the rail jump, the step buttons step, and hovering over the rail asks for the detail text.

render_override

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

Draws the rail, thumb, step buttons, visible labels and focus ring.

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

Focus changes redraw the focus ring.

key_override

bool key_override(const KeyEvent& e) override

Up and Down step by small_change(). Home and End jump to 0 and maximum().

struct AnnotatedScrollBarAnnotation

A caption pinned beside a ui::AnnotatedScrollBar's rail at the position of a scroll offset. Annotations that would overlap a previous one are hidden.

Field Type Default
text std::string Caption drawn right-aligned in the annotations column.
scroll_offset double 0.0 Scroll offset (DIPs of the target's content) the annotation marks.

struct AnnotatedScrollBarScrollingArgs

What on_scrolling gets before a scroll request is raised: where it goes and what caused it. The handler returns ui::Cancel to drop the request.

Field Type Default
offset double 0.0 The target offset.
kind AnnotatedScrollBarScrollingEventKind AnnotatedScrollBarScrollingEventKind::Click What caused it.

Enumerations

AnnotatedScrollBarScrollingEventKind

enum class AnnotatedScrollBarScrollingEventKind

What caused an AnnotatedScrollBar scroll request. The top step button scrolls up, toward a smaller offset, and the bottom one scrolls down.

Value
Click A click on the rail, Home or End, or an accessibility client setting the value.
Drag Dragging along the rail.
IncrementButton The top step button or Up arrow: scrolls toward 0.
DecrementButton The bottom step button or Down arrow: scrolls toward maximum().

Tests

  • tests/test_annotated_scroll_bar.cpp