Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ColumnFlow

ColumnFlow preview

ColumnFlow — flows children into as many columns as the width affords, re-partitioning every child when a column is gained or lost.

The newspaper / CSS multi-column model: content runs down column 0, then down column 1, and so on. The column count is derived from the available width and min_column_width — when the width no longer affords N columns the layout drops to N−1 and all children are re-partitioned across the survivors. Children are atomic: one child never straddles a column boundary.

Pair it with a ScrollArea for vertical overflow — ColumnFlow reports its true content height (the tallest column), so the scroll extent is correct.

#![allow(unused)]
fn main() {
use teksilo_widgets::primitives::column_flow::ColumnFlow;
use teksilo_widgets::primitives::TextWidget;
use teksilo_widgets::scroll_area::ScrollArea;
use teksilo_i18n::lit;
let _view = ScrollArea::new().child(
    ColumnFlow::new()
        .min_column_width(240.0)
        .max_columns(4)
        .column_spacing(16.0)
        .item_spacing(12.0)
        .child(TextWidget::new(lit!("First")))
        .child(TextWidget::new(lit!("Second")))
        .child(TextWidget::new(lit!("Third"))),
);
}

Reading order

Children are distributed as contiguous runs in source order — column 0 takes children 0..i, column 1 takes i..j. So source order, visual reading order, and focus order are the same thing, at every column count. This is why ColumnFlow does not reuse MasonryLayout's shortest-column packing, which interleaves children and would divorce the visual order from the source order.

Accessibility

By default ColumnFlow emits a bare Role::GenericContainer carrying no properties, which the accessibility walker prunes, promoting the children to its parent in source order. That is the correct outcome for a layout primitive: it contributes geometry, not semantics, and the reading order is already right. Add semantics from the outside with .access_role(..) / .access_label(..), or opt into list semantics with semantic_list.

Relationship to CSS multi-column

Close, but not identical. CSS column-fill: balance balances content within a column height it computes from a bounded block size; ColumnFlow derives the column count from the width and lets the height run free (a ScrollArea absorbs it). No CSS column-fill mode does that, so don't read this as a CSS multicol port.

Builder methods at a glance

min_column_width, max_column_width, max_columns, column_spacing, item_spacing, alignment, column_rule, semantic_list, add_child, child, children, child_opt, column_count_signal

API reference

📖 Full rustdoc API for this module

pub struct ColumnFlow

A layout that flows its children into as many columns as the available width affords, re-partitioning every child when a column is gained or lost.

 wide                            narrower
┌────┐ ┌────┐ ┌────┐            ┌────┐ ┌────┐
│ 1  │ │ 3  │ │ 5  │            │ 1  │ │ 4  │
├────┤ ├────┤ ├────┤            ├────┤ ├────┤
│ 2  │ │ 4  │ │ 6  │    ───►    │ 2  │ │ 5  │
└────┘ └────┘ └────┘            ├────┤ ├────┤
                                │ 3  │ │ 6  │
                                └────┘ └────┘

Reading order is 1..6 at both widths. See the module docs.

#![allow(unused)]
fn main() {
pub struct ColumnFlow { /* fields */ }
}

Methods

pub fn new() -> Self

Create a ColumnFlow with a 240 dp minimum column width, no maximum column width, and no column-count cap.

pub fn min_column_width(mut self, width: f32) -> Self

The narrowest a column may be. The column count is the largest N whose columns are all at least this wide — CSS column-width / SwiftUI GridItem(.adaptive(minimum:)) / Compose GridCells.Adaptive(minSize).

A value of zero or less pins the layout to a single column.

pub fn max_column_width(mut self, width: f32) -> Self

The widest a column may be. Unset by default, so columns stretch to share the full width evenly.

Set it to stop columns becoming unreadably wide when few of them fit a large display — the reason KDE's Kirigami.CardsLayout pairs minimumColumnWidth with maximumColumnWidth. When it bites, the columns no longer fill the width and alignment decides where the block sits.

pub fn max_columns(mut self, max: usize) -> Self

Never use more than max columns however wide the layout gets.

Also decides the count when the width is unconstrained (inside a size-to-content parent such as a popover): unset, that case reports one column, matching CSS column-count: auto in a shrink-to-fit context. Clamped to at least 1.

pub fn column_spacing(mut self, spacing: impl Into<Prop<f32>>) -> Self

Horizontal gap between columns. Accepts an f32 or a Signal<f32>.

pub fn item_spacing(mut self, spacing: impl Into<Prop<f32>>) -> Self

Vertical gap between items within a column. Accepts an f32 or a Signal<f32>.

Named for items rather than rows because there are no rows here: a column's items are independent of its neighbours'.

pub fn alignment(mut self, alignment: HAlignment) -> Self

Where the column block sits when it does not fill the available width.

Only observable once max_column_width clamps the columns narrower than their even share — otherwise the columns consume the whole width and there is nothing to align. Defaults to HAlignment::Leading; RTL-aware.

pub fn column_rule(mut self, width: f32, color: impl Into<ColorProp>) -> Self

Draw a rule of width dp, centred in every inter-column gap — CSS column-rule.

Purely decorative: it emits no accessibility node. Accepts a Color, a theme role, or a Signal. Pass BorderRole::Divider to track the theme's divider colour.

pub fn semantic_list(mut self, enabled: bool) -> Self

Expose the children to assistive technology as a list.

The container becomes Role::List and every child is wrapped in a layout-transparent node reporting Role::ListItem with its position and the set size, so a screen reader announces "list, 30 items" and "item 5 of 30" rather than reading 30 unrelated widgets.

Off by default: a layout primitive should not invent semantics its content may not have. Turn it on when the children genuinely are a list of peers. Costs one extra node per child.

pub fn add_child(mut self, id: WidgetId) -> Self

Add a pre-registered child by ID.

pub fn child(mut self, widget: impl Widget + 'static) -> Self

Add an inline child widget (deferred insertion).

pub fn children(mut self, iter: impl IntoIterator<Item = impl Widget + 'static>) -> Self

Add multiple inline children from an iterator.

pub fn child_opt(mut self, widget: Option<impl Widget + 'static>) -> Self

Conditionally add a child. No-op if None.

pub fn column_count_signal(&self) -> Signal<usize>

The live column count, as a reactive signal.

Lets an app follow the reflow — swapping to a compact header at one column, say. Written from the layout pass behind an equality guard, so it only fires when the count actually changes.

Binding contract. Safe for RepaintOnly / AccessibilityOnly consumers, and for Relayout consumers that do not feed back into this widget's own width. The count is a pure function of the width ColumnFlow is given — it never changes its own width, so it cannot oscillate on its own. But a Relayout consumer that resizes something which in turn resizes this ColumnFlow closes a feedback loop through the layout pass, which is exactly what Widget::place_children's own documentation warns against.