Skip to content

Parallax

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

Moves its single child at a different rate than another element scrolls, typically a background image behind a list or scroll view.

The child is laid out larger than the view by the shift, clipped to the view, and moved every frame through its own translation, which the Parallax controls from then on. The source is any element with Element::scroll_info(), such as a Scroll or a ListView. The Parallax does not own it and notices when it is destroyed. Set it after the tree is built. A source destroyed first stops the parallax. A Parallax is never focusable. ui::Parallax is the declarative form. It has no .source field, so set the source through .control.

Example:

auto row = std::make_unique<ui::StackControl>(ui::Orientation::Horizontal);

auto* parallax = row->add(std::make_unique<ui::ParallaxControl>());
parallax->set_vertical_shift(300.0f); // the background travels 300 DIPs over the whole scroll
parallax->set_child(std::make_unique<ui::TextControl>("Background"));

auto* scroll  = row->add(std::make_unique<ui::ScrollControl>());
auto  content = std::make_unique<ui::TextControl>("Tall content");
content->set_min_height(2000.0f);
scroll->set_child(std::move(content));

parallax->set_source(scroll); // non-owning; both already live in the same tree
window.set_content(std::move(row));

ui::Parallax

Moves one child at a different rate than another element scrolls. Builds a ui::ParallaxControl. There is no .source field. Call set_source() on .control once both the view and its source exist.

Example:

const std::vector<std::string>         titles{"Arches", "Bryce Canyon", "Zion"};
ui::Control<ui::Parallax>              parallax;
ui::Control<ui::ListView<std::string>> list;

window.set_content(ui::Grid{
    .height  = 700.0f,
    .content = {
        ui::Cell{.content = ui::Parallax{.vertical_shift     = 500.0f,
                                         .vertical_alignment = ui::Alignment::Leading,
                                         .control            = parallax,
                                         .content            = ui::Image{.source = ui::Bitmap::read("cliff.jpg")}}},
        ui::Cell{.content = ui::ListView<std::string>{.items = titles, .control = list}},
    },
});
parallax->set_source(list); // follow the list's scrolling

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

ui::Parallax{
    .horizontal_shift, .vertical_shift, .horizontal_source_start_offset,
    .horizontal_source_end_offset, .vertical_source_start_offset, .vertical_source_end_offset,
    .horizontal_source_offset_kind, .vertical_source_offset_kind, .max_horizontal_shift_ratio,
    .max_vertical_shift_ratio, .horizontal_shift_clamped, .vertical_shift_clamped, .style,
    .margin, .width, .height, .min_width, .min_height, .max_width, .max_height,
    .horizontal_alignment, .vertical_alignment, .visible, .tool_tip, .accessibility,
    .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
horizontal_shift ui::Binding<float> {0.0f} How far the child moves over the source's horizontal scroll range, in DIPs. 0, the default, is no parallax on that axis. The child is laid out this much wider.
vertical_shift ui::Binding<float> {0.0f} The vertical counterpart of horizontal_shift.
horizontal_source_start_offset ui::Binding<float> {0.0f} Where on the source's horizontal scroll range the parallax starts and ends, in DIPs, read per horizontal_source_offset_kind.
horizontal_source_end_offset ui::Binding<float> {0.0f} See above.
vertical_source_start_offset ui::Binding<float> {0.0f} Where on the source's vertical scroll range the parallax starts and ends, as the horizontal pair.
vertical_source_end_offset ui::Binding<float> {0.0f} See above.
horizontal_source_offset_kind ui::Binding<ui::ParallaxSourceOffsetKind> {ui::ParallaxSourceOffsetKind::Relative} Relative (the default) adds the offsets to the automatic range; Absolute uses them as is.
vertical_source_offset_kind ui::Binding<ui::ParallaxSourceOffsetKind> {ui::ParallaxSourceOffsetKind::Relative} As horizontal_source_offset_kind, for the vertical offsets.
max_horizontal_shift_ratio ui::Binding<float> {1.0f} The child never moves faster than this times the source.
max_vertical_shift_ratio ui::Binding<float> {1.0f} As max_horizontal_shift_ratio.
horizontal_shift_clamped bool true The child stops at the ends of the range.
vertical_shift_clamped bool true As horizontal_shift_clamped.
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::Parallax> Out: the handle that receives the built control.
content ui::Spec The moving child, typically a background image.

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

build

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

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

ui::ParallaxControl

Derives from ui::Element.

Moves its single child at a different rate than another element scrolls, typically a background image behind a list or scroll view.

The child is laid out larger than the view by the shift, clipped to the view, and moved every frame through its own translation, which the Parallax controls from then on. The source is any element with Element::scroll_info(), such as a Scroll or a ListView. The Parallax does not own it and notices when it is destroyed. Set it after the tree is built. A source destroyed first stops the parallax. A Parallax is never focusable. ui::Parallax is the declarative form. It has no .source field, so set the source through .control.

Example:

auto row = std::make_unique<ui::StackControl>(ui::Orientation::Horizontal);

auto* parallax = row->add(std::make_unique<ui::ParallaxControl>());
parallax->set_vertical_shift(300.0f); // the background travels 300 DIPs over the whole scroll
parallax->set_child(std::make_unique<ui::TextControl>("Background"));

auto* scroll  = row->add(std::make_unique<ui::ScrollControl>());
auto  content = std::make_unique<ui::TextControl>("Tall content");
content->set_min_height(2000.0f);
scroll->set_child(std::move(content));

parallax->set_source(scroll); // non-owning; both already live in the same tree
window.set_content(std::move(row));

ParallaxControl

ParallaxControl() = default

An empty view with no child, no source and zero shift on both axes.

set_child

template <typename T>
T* set_child(std::unique_ptr<T> child)

Takes ownership of the moving content and returns a non-owning pointer to it. The Parallax controls the child's translation from then on.

child

Element* child() const

The content set by set_child() (non-owning), or nullptr.

source

Element* source() const

The scrolling element to follow (Element::scroll_info()), or nullptr for none. Becomes nullptr by itself once the source is destroyed.

set_source

void set_source(Element* source)

Follows source, which the Parallax does not own. Pass nullptr to stop following. Redraws.

horizontal_shift

float horizontal_shift() const

How far the child moves over the source's horizontal range, in DIPs. 0, the default, is no parallax on that axis. The child is laid out this much larger than the view on that axis.

set_horizontal_shift

void set_horizontal_shift(float shift)

Lays the view out again, since the child's size depends on the shift, and redraws.

vertical_shift

float vertical_shift() const

Vertical counterpart of horizontal_shift().

set_vertical_shift

void set_vertical_shift(float shift)

Lays the view out again and redraws.

horizontal_source_start_offset

float horizontal_source_start_offset() const

Where the horizontal parallax starts on the source's scroll range, in DIPs. It is added to the automatic value or used as is, depending on the offset kind. 0 by default, as are the other offsets.

set_horizontal_source_start_offset

void set_horizontal_source_start_offset(float offset)

Redraws. The shift is recomputed on the next frame.

horizontal_source_end_offset

float horizontal_source_end_offset() const

Where the horizontal parallax ends on the source's scroll range (see the start offset).

set_horizontal_source_end_offset

void set_horizontal_source_end_offset(float offset)

Marks the view for redraw.

vertical_source_start_offset

float vertical_source_start_offset() const

Where the vertical parallax starts on the source's scroll range, in DIPs.

set_vertical_source_start_offset

void set_vertical_source_start_offset(float offset)

Marks the view for redraw.

vertical_source_end_offset

float vertical_source_end_offset() const

Where the vertical parallax ends on the source's scroll range, in DIPs.

set_vertical_source_end_offset

void set_vertical_source_end_offset(float offset)

Marks the view for redraw.

horizontal_source_offset_kind

ParallaxSourceOffsetKind horizontal_source_offset_kind() const

How the horizontal start/end offsets are interpreted; Relative by default.

set_horizontal_source_offset_kind

void set_horizontal_source_offset_kind(ParallaxSourceOffsetKind kind)

Marks the view for redraw.

vertical_source_offset_kind

ParallaxSourceOffsetKind vertical_source_offset_kind() const

How the vertical start/end offsets are interpreted; Relative by default.

set_vertical_source_offset_kind

void set_vertical_source_offset_kind(ParallaxSourceOffsetKind kind)

Marks the view for redraw.

max_horizontal_shift_ratio

float max_horizontal_shift_ratio() const

The child never moves faster than this times the source's own horizontal scrolling. 1.0 by default.

set_max_horizontal_shift_ratio

void set_max_horizontal_shift_ratio(float ratio)

Marks the view for redraw.

max_vertical_shift_ratio

float max_vertical_shift_ratio() const

Vertical counterpart of max_horizontal_shift_ratio(); 1.0 by default.

set_max_vertical_shift_ratio

void set_max_vertical_shift_ratio(float ratio)

Marks the view for redraw.

is_horizontal_shift_clamped

bool is_horizontal_shift_clamped() const

Whether the child stops at the ends of the horizontal range instead of moving on with the scroll. True by default.

set_horizontal_shift_clamped

void set_horizontal_shift_clamped(bool clamped)

Marks the view for redraw.

is_vertical_shift_clamped

bool is_vertical_shift_clamped() const

Vertical counterpart of is_horizontal_shift_clamped(); true by default.

set_vertical_shift_clamped

void set_vertical_shift_clamped(bool clamped)

Marks the view for redraw.

current_shift

Point current_shift() const

The child's current parallax offset on each axis, its translation.

for_each_child

void for_each_child(ChildVisitor visit) const override

Visits the child, if there is one.

children_hit_clip

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

The child is clipped to the bounds, and so is where it can be hit.

measure_override

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

Measures the child with the shift added to the available size. The Parallax takes the available size, or the child's size on an unbounded axis.

arrange_override

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

Arranges the child larger than the view by the shift. A child that stretches grows by the same ratio on the other axis.

render_override

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

Draws the child clipped to the view. Its translation carries the shift.

Enumerations

ParallaxSourceOffsetKind

enum class ParallaxSourceOffsetKind

Whether a Parallax source start or end offset is added to the automatic value (Relative, the default) or used as it is (Absolute).

Value
Absolute The offset is the scroll position itself, in unzoomed DIPs.
Relative The offset is added to the automatically computed start/end.

Tests

  • tests/test_parallax.cpp