Skip to content

Carousel

Header: winui/ui/navigation/Carousel.hpp. Spec: winui/ui/navigation/Carousel.hpp.

Shows one item at a time, filling the whole view, and flips between them with previous/next buttons, the mouse wheel or the keyboard.

Items are any elements, owned by the view (add_item()). They sit side by side along the flip axis, each the size of the view. Only the items in view are drawn or get input, and Tab reaches only the selected item's focusable content. The previous/next buttons appear while the mouse moves over the view and for 3 s after, or once keyboard focus arrives. A button never shows on a side with no item beyond it. One wheel turn flips one item. Along the flip axis the arrow keys and PageUp/PageDown step one item, and Home/End jump to the ends. A step to a neighbor slides over 300 ms, and longer jumps land at once. For a page indicator, pair it with a PipsPager bound to the same index.

Example:

// Declarative: the pages are children, each filling the view.
ui::Property<std::size_t> page{0};
window.set_content(ui::VerticalStack{
    .spacing  = 8,
    .content  = {
        ui::Carousel{
            .selected_index = page,
            .height         = 270.0f,
            .max_width      = 400.0f,
            .content        = {
                ui::Image{.source = ui::Bitmap::read("cliff.jpg")},
                ui::Image{.source = ui::Bitmap::read("grapes.jpg")},
                ui::Text{.text = "The end", .horizontal_alignment = ui::Alignment::Center},
            },
        },
        ui::PipsPager{.selected_index = page, .number_of_pages = 3}, // two-way
    },
});

// Imperative.
auto  stack = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
auto* flip  = stack->add(std::make_unique<ui::CarouselControl>());
flip->set_height(200.0f);
flip->add_item(std::make_unique<ui::TextControl>("First")); // becomes the selection
flip->add_item(std::make_unique<ui::TextControl>("Second"));
flip->on_selection_changed([&page](std::size_t i) { page = i; });
flip->set_selected_index(1); // slides once laid out; fires the handler

ui::Carousel

One item at a time, flipped with arrows, the wheel or the keys. Builds a ui::CarouselControl. Its items are child elements, and each fills the whole view.

Example:

ui::Property<std::size_t> page{0};

window.set_content(ui::Carousel{
    .selected_index = page,
    .height         = 270.0f,
    .max_width      = 400.0f,
    .content        = {
        ui::Image{.source = ui::Bitmap::read("cliff.jpg")},
        ui::Image{.source = ui::Bitmap::read("grapes.jpg")},
        ui::Text{.text = "The end", .horizontal_alignment = ui::Alignment::Center},
    },
});

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

ui::Carousel{
    .selected_index, .orientation, .use_animations, .background, .border_brush,
    .border_thickness, .style, .margin, .width, .height, .min_width, .min_height, .max_width,
    .max_height, .horizontal_alignment, .vertical_alignment, .visible, .tool_tip, .enabled,
    .accessibility, .on_selection_changed, .on_pointer_entered, .on_pointer_exited,
    .on_got_focus, .on_lost_focus, .on_key_down, .on_wheel, .control, .content
}

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

Field Type Default
selected_index ui::TwoWayField<std::size_t> {0} The index of the item in view.
orientation ui::Binding<ui::Orientation> {ui::Orientation::Horizontal} Horizontal (the default) flips left/right; Vertical flips up/down.
use_animations ui::Binding<bool> {true} One-item flips slide.
background ui::Binding<ui::Paint> The fill behind the items: a fixed color or a theme resource; unset is the window's background color.
border_brush ui::Binding<ui::Paint> A border drawn over the items' edges: a fixed color or a theme resource; unset draws none.
border_thickness ui::Binding<ui::Thickness> See border_brush.
on_selection_changed ui::Callback<void(std::size_t)> Called with the index of the item now in view.
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::Carousel> Out: the handle that receives the built control.
content ui::SpecList The pages, in flip order; each fills the whole view.

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::CarouselControl; the caller owns it.

ui::CarouselControl

Derives from ui::Element, ui::SelectionProvider.

Shows one item at a time, filling the whole view, and flips between them with previous/next buttons, the mouse wheel or the keyboard.

Items are any elements, owned by the view (add_item()). They sit side by side along the flip axis, each the size of the view. Only the items in view are drawn or get input, and Tab reaches only the selected item's focusable content. The previous/next buttons appear while the mouse moves over the view and for 3 s after, or once keyboard focus arrives. A button never shows on a side with no item beyond it. One wheel turn flips one item. Along the flip axis the arrow keys and PageUp/PageDown step one item, and Home/End jump to the ends. A step to a neighbor slides over 300 ms, and longer jumps land at once. For a page indicator, pair it with a PipsPager bound to the same index.

Example:

// Declarative: the pages are children, each filling the view.
ui::Property<std::size_t> page{0};
window.set_content(ui::VerticalStack{
    .spacing  = 8,
    .content  = {
        ui::Carousel{
            .selected_index = page,
            .height         = 270.0f,
            .max_width      = 400.0f,
            .content        = {
                ui::Image{.source = ui::Bitmap::read("cliff.jpg")},
                ui::Image{.source = ui::Bitmap::read("grapes.jpg")},
                ui::Text{.text = "The end", .horizontal_alignment = ui::Alignment::Center},
            },
        },
        ui::PipsPager{.selected_index = page, .number_of_pages = 3}, // two-way
    },
});

// Imperative.
auto  stack = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
auto* flip  = stack->add(std::make_unique<ui::CarouselControl>());
flip->set_height(200.0f);
flip->add_item(std::make_unique<ui::TextControl>("First")); // becomes the selection
flip->add_item(std::make_unique<ui::TextControl>("Second"));
flip->on_selection_changed([&page](std::size_t i) { page = i; });
flip->set_selected_index(1); // slides once laid out; fires the handler

CarouselControl

CarouselControl() = default

An empty view: no items, nothing selected, not focusable.

add_item

template <typename T>
T* add_item(std::unique_ptr<T> item)

Append an item, taking ownership; returns a non-owning observer. The first item added becomes the selection (without firing on_selection_changed()).

item_count

std::size_t item_count() const

How many items the view holds.

item

Element* item(std::size_t index) const

The item at index (owned by the view), or nullptr past the end.

selected_index

std::optional<std::size_t> selected_index() const

The index of the item in view; unset with no items.

set_selected_index

void set_selected_index(std::size_t index)

Selects the item at index, clamped to the items there are. A change fires on_selection_changed. A step to a neighbor slides (use_animations()), and anything else lands at once. Ignored with no items, since a carousel with items always shows one.

orientation

Orientation orientation() const

The flip axis; Horizontal by default.

set_orientation

void set_orientation(Orientation orientation)

Changes the flip axis and which arrow keys flip; updates the layout.

use_animations

bool use_animations() const

Whether a one-item flip slides. On by default.

set_use_animations

void set_use_animations(bool animate)

Turns the one-item slide on or off for later flips.

set_background

void set_background(Paint color)

The fill behind the items: a fixed color or a theme resource. Empty, the default, is the window's background color.

set_border_brush

void set_border_brush(Paint color)

The border color around the items (none by default), drawn over their edges.

set_border_thickness

void set_border_thickness(const Thickness& thickness)

The border's thickness per side, DIPs (0 by default). Drawn over the items, so it does not shrink them; redraws only.

on_selection_changed

void on_selection_changed(Callback<void(std::size_t)> handler)

Called with the new index whenever the selected item changes, by a button, the wheel, a key or set_selected_index(). One handler; setting another replaces it.

previous_button_visible

bool previous_button_visible() const

Whether the previous/next buttons are showing right now (see the class comment).

next_button_visible

bool next_button_visible() const

As previous_button_visible(), for the next button.

previous_button_bounds

Rect previous_button_bounds() const

Where the previous button sits (window DIPs): 16x38 centered 1 DIP inside the left edge, or 38x16 at the top when vertical. Valid after arrange(), shown or not.

next_button_bounds

Rect next_button_bounds() const

As previous_button_bounds(), at the right (or bottom) edge.

focusable

bool focusable() const override

Focusable while enabled and holding at least one item.

focused

bool focused() const

Whether the view itself holds keyboard focus.

for_each_child

void for_each_child(ChildVisitor visit) const override

Every item, in order.

collect_focusables

void collect_focusables(std::vector<Element*>& out) override

The view itself, then the selected item's focusable descendants. Tab cannot reach the other items.

automation_role

AutomationRole automation_role() const override

A List.

measure_override

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

Takes the whole available size on a bounded axis; on an unbounded one, the largest item's.

arrange_override

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

Gives every item the view's size and places them along the flip axis at the current position.

render_override

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

Draws the background, the items in view (clipped), the border, the buttons while shown and the focus ring, which sits just inside the bounds.

pointer_override

bool pointer_override(const PointerEvent& e) override

Mouse movement shows the buttons; a click on a button flips. Other input reaches the items in view.

wheel_override

bool wheel_override(const PointerEvent& e) override

An item in view that scrolls gets the turn first. Otherwise each turn flips one item, and a repeat in the same direction within 200 ms is ignored. Returns false (not handled) at the first or last item and for Ctrl+wheel, so an outer scroller can react.

hit_test_children

void hit_test_children(Point p, std::vector<Element*>& out) const override

The children p (in this element's layout coordinates) is tested against, topmost first. Default: what for_each_child() lists, last first. A container that draws in another order (Panel's z-index), a control with parts for_each_child() does not list, or one whose parts cover others in places (a pane drawn over the content) lists them here.

covers_children_at

bool covers_children_at(Point p) const override

Whether a part of this element drawn over its children is at p (a scroll bar over the content), so the element is hit there instead of a child. Default: false.

children_hit_clip

std::optional<Rect> children_hit_clip() const override

Where the children can be hit, in this element's layout coordinates, or nullopt for anywhere (the default): a scrolling control returns its viewport, so content scrolled out of view is not hit.

tick_override

void tick_override(Duration delta) override

Advances the slide and the buttons' show timer, then ticks the items.

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.

key_override

bool key_override(const KeyEvent& e) override

Arrows along the flip axis step, Home/End jump to the ends, PageUp/PageDown step one item.

focus_changed

void focus_changed(bool focused) override

Called by Window; gaining keyboard focus (focus_visible()) shows the buttons. Redraws.

focus_ring_inside

bool focus_ring_inside() const override

Inside the view, which fills its slot edge to edge.

Tests

  • tests/test_carousel.cpp