Skip to content

Swipe

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

Reveals a strip of action items beside its content when the content is dragged sideways: the "swipe a list row to reveal Delete/Archive" pattern.

Dragging right reveals the left items, and dragging left the right ones. The control owns its content, which stays fully interactive. The items work with the mouse only and are not exposed to accessibility tools. The declarative form is ui::Swipe.

Example:

void reply();      // the app's own
void delete_row(); // the app's own
ui::SwipeItem reply_item{ .text = "Reply", .icon = ui::IconGlyph{0xE8C2}, .on_invoked = [&] { reply(); } };
ui::SwipeItem delete_item{ .text       = "Delete",
                           .icon       = ui::IconGlyph{0xE74D},
                           .background = ui::theme_colors::Critical,
                           .on_invoked = [&] { delete_row(); } };

window.set_content(ui::Swipe{
    .left_items  = { reply_item },
    .right_items = { delete_item },
    .right_mode  = ui::SwipeMode::Execute,
    .height      = 68,
    .content     = ui::Text{ .text               = "Swipe me",
                             .margin             = ui::Thickness{12},
                             .vertical_alignment = ui::Alignment::Center },
});

// Imperatively:
auto swipe = std::make_unique<ui::SwipeControl>();
swipe->set_content(std::make_unique<ui::TextControl>("Swipe me"));
swipe->set_left_items({ .items = { reply_item } });
swipe->set_right_items({ .items = { delete_item }, .mode = ui::SwipeMode::Execute });

ui::Swipe

Reveals a strip of action items when its child is dragged sideways, as a list row's swipe actions. Builds a ui::SwipeControl. The callbacks are on each ui::SwipeItem, so the spec has no .on_* handler of its own for them.

Example:

struct Row { std::string name; };

ui::Spec swipe_row(const Row& row, std::function<void()> remove) // captures by value
{
    return ui::Swipe{
        .left_items  = {{.text = "Flag", .icon = ui::Symbol::Flag}},
        .right_items = {{.text       = "Delete",
                         .background = ui::Color::from_rgba(196, 43, 28),
                         .on_invoked = [remove] { remove(); }}},
        .right_mode  = ui::SwipeMode::Execute,
        .content     = ui::Text{.text = row.name, .margin = ui::Thickness{12.0f}},
    };
}

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

ui::Swipe{
    .left_items, .left_mode, .right_items, .right_mode, .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_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
left_items ui::Binding<std::vector<ui::SwipeItem>> Revealed by dragging the content to the right.
left_mode ui::SwipeMode ui::SwipeMode::Reveal Reveal, the default, shows the items to click. Execute invokes the single item on release.
right_items ui::Binding<std::vector<ui::SwipeItem>> Revealed by dragging the content to the left.
right_mode ui::SwipeMode ui::SwipeMode::Reveal As left_mode, for right_items.
background ui::Binding<ui::Paint> The fill, border color and border width behind the content, which move with it. A color is fixed or a theme resource, read again when the theme changes.
border_brush ui::Binding<ui::Paint> See background.
border_thickness ui::Binding<ui::Thickness> See background.
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::Swipe> Out: the handle that receives the built control.
content ui::Spec The row content that slides to reveal the items.

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

ui::SwipeControl

Derives from ui::Element.

Reveals a strip of action items beside its content when the content is dragged sideways: the "swipe a list row to reveal Delete/Archive" pattern.

Dragging right reveals the left items, and dragging left the right ones. The control owns its content, which stays fully interactive. The items work with the mouse only and are not exposed to accessibility tools. The declarative form is ui::Swipe.

Example:

void reply();      // the app's own
void delete_row(); // the app's own
ui::SwipeItem reply_item{ .text = "Reply", .icon = ui::IconGlyph{0xE8C2}, .on_invoked = [&] { reply(); } };
ui::SwipeItem delete_item{ .text       = "Delete",
                           .icon       = ui::IconGlyph{0xE74D},
                           .background = ui::theme_colors::Critical,
                           .on_invoked = [&] { delete_row(); } };

window.set_content(ui::Swipe{
    .left_items  = { reply_item },
    .right_items = { delete_item },
    .right_mode  = ui::SwipeMode::Execute,
    .height      = 68,
    .content     = ui::Text{ .text               = "Swipe me",
                             .margin             = ui::Thickness{12},
                             .vertical_alignment = ui::Alignment::Center },
});

// Imperatively:
auto swipe = std::make_unique<ui::SwipeControl>();
swipe->set_content(std::make_unique<ui::TextControl>("Swipe me"));
swipe->set_left_items({ .items = { reply_item } });
swipe->set_right_items({ .items = { delete_item }, .mode = ui::SwipeMode::Execute });

SwipeControl

SwipeControl() = default

No content and no items; closed.

~SwipeControl

~SwipeControl() override

SwipeControl

SwipeControl(SwipeControl&&) = default

set_content

template <typename T>
T* set_content(std::unique_ptr<T> content)

Takes ownership of the swiped content and returns a non-owning pointer to it. The content is always measured, arranged, drawn and reachable. Only its drawn position changes.

content

Element* content() const

The swiped content (owned by the control), or null.

left_items

const SwipeItems& left_items() const

Revealed by dragging the content to the right.

set_left_items

void set_left_items(SwipeItems items)

Replaces the item list and snaps closed.

right_items

const SwipeItems& right_items() const

Revealed by dragging the content to the left.

set_right_items

void set_right_items(SwipeItems items)

Replaces the item list and snaps closed.

set_background

void set_background(std::optional<Color> color)

The fill behind the content, moving with it. Transparent by default.

set_background

void set_background(ThemeColor resource)

The fill behind the content as a theme resource, re-read on a theme change.

set_border_brush

void set_border_brush(std::optional<Color> color)

The border color around the content, moving with it.

set_border_brush

void set_border_brush(ThemeColor resource)

The border color as a theme resource, re-read on a theme change.

set_border_thickness

void set_border_thickness(Thickness thickness)

The border width in DIPs, inside the control's bounds. Lays the control out again.

offset

float offset() const

Positive: the left items revealed by this many DIPs. Negative: the right items revealed by |offset|. Zero: closed.

is_open

bool is_open() const

Whether a strip shows, or is sliding open.

close

void close()

Slides the strip shut.

for_each_child

void for_each_child(ChildVisitor visit) const override

Visits the content, the only child. The control itself is not focusable, and whatever is focusable in the content is always in the tab order. The items are not children: they work with the mouse only and are not exposed to accessibility tools.

measure_override

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

The content's size plus the border, and at least an item's minimum height while there are items to reveal. Also measures the items' widths.

arrange_override

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

Arranges the content inside the border. offset() shifts it only when drawing.

render_override

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

Draws the revealed strip, then the background, border and content shifted by offset().

pointer_override

bool pointer_override(const PointerEvent& e) override

A horizontal drag moves the strip and takes the gesture from the content. A tap on an item invokes it, and a tap on the content while open closes the strip.

handled_pointer_override

void handled_pointer_override(const PointerEvent& e) override

A press and release the content took still start and end a drag, so a swipe can begin on a button.

covers_children_at

bool covers_children_at(Point p) const override

Open or dragged, the strip covers the content.

tick_override

void tick_override(Duration delta) override

Ticks the content, then advances the 250 ms snap, invoking an Execute item as it settles.

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.

struct SwipeItem

One action in a SwipeControl's strip: a label under an icon, its own colors, and what happens when it is invoked. A value type, built with designated initializers and handed over in a SwipeItems list.

Example:

void archive_message(); // the app's own
ui::SwipeItem archive{
    .text       = "Archive",
    .icon       = ui::IconGlyph{0xE7B8},
    .background = ui::Color::from_argb(0xFF3E6FA7),
    .foreground = ui::colors::White,
    .on_invoked = [&] { archive_message(); },
};

text

std::string text {}

The text. Falls back to the command's text.

icon

IconSource icon {}

The icon, drawn 16 DIPs tall above the text. Falls back to the command's icon.

background

Paint background {}

A color, a brush or a ThemeColor. In Execute mode a gradient spans the whole control. Empty uses the theme's item background in Reveal, and in Execute the theme's two colors for before and after the swipe passes its threshold.

foreground

std::optional<Color> foreground {}

The text and icon color. Unset uses the theme's item foreground, which in Execute is a pair for before and after the threshold.

behavior_on_invoked

SwipeBehaviorOnInvoked behavior_on_invoked = SwipeBehaviorOnInvoked::Auto

What the strip does after the item is invoked.

command

CommandRef command {}

A command run after on_invoked, while it is enabled. Its text and icon fill the text and icon left empty.

command_parameter

std::string command_parameter {}

Handed to the command's execute.

on_invoked

SwipeItemInvokedHandler on_invoked {}

Fires on a tap in Reveal mode or a completed swipe in Execute mode, before the command runs.

display_text

std::string display_text() const

The text drawn: text, or the command's text when text is empty.

display_icon

const IconSource& display_icon() const

The icon drawn: icon, or the command's icon when icon is empty.

struct SwipeItemInvokedEventArgs

The arguments of an item's on_invoked: the item that was invoked, whose text or icon the handler may change, and the control it was swiped from.

Field Type Default
swipe_control SwipeControl& The control the item was swiped from.
item SwipeItem& The invoked item. Its text and icon may be changed here.

class SwipeItemInvokedHandler

SwipeItem::on_invoked's handler: a callable that takes the event args, or one that takes nothing.

Copyable, since SwipeItem lives in a std::vector that a caller builds with a brace list.

Example:

bool flagged = false;
ui::SwipeItem flag{ .text = "Flag", .on_invoked = [&] { flagged = true; } };
ui::SwipeItem mark_read{ .text       = "Mark read",
                         .on_invoked = [](ui::SwipeItemInvokedEventArgs& e) { e.item.text = "Marked"; } };

SwipeItemInvokedHandler

SwipeItemInvokedHandler() = default

No handler. Invoking it does nothing.

SwipeItemInvokedHandler

template <typename F>
SwipeItemInvokedHandler(F handler)
    requires std::invocable<F&> || std::invocable<F&, SwipeItemInvokedEventArgs&>

Wraps a callable taking SwipeItemInvokedEventArgs&, or one taking nothing.

operator()

void operator()(SwipeItemInvokedEventArgs& args) const

Calls the handler, if there is one.

operator bool

operator bool() const

Whether a handler is set.

struct SwipeItems

One side's items and how they are revealed. An Execute set shows a single item, as wide as the control.

Field Type Default
items std::vector<SwipeItem> The strip's items, in order from the left.
mode SwipeMode SwipeMode::Reveal Reveal (a drawer) or Execute (swipe to run).

Enumerations

SwipeMode

enum class SwipeMode

How a Swipe side shows its items. Reveal opens a drawer of items at their natural widths, as far as it is dragged. Execute shows one item as wide as the control, invoked by swiping it all the way, as in "swipe to archive".

Value
Reveal A drawer of items at their natural widths. A tap invokes one.
Execute One item as wide as the control, invoked by swiping past the threshold.

SwipeBehaviorOnInvoked

enum class SwipeBehaviorOnInvoked

What the strip does after an item is invoked. Auto closes, as Close does, in both modes.

Value
Auto Close after the item runs.
Close Close after the item runs.
RemainOpen Leave the strip open after the item runs.

Tests

  • tests/test_swipe.cpp