add Carousel widget and WrapGrid::centre_last_row
Forge's app switcher needs two layouts the existing widget set didn't cover. The desktop grid wants a partial last row centred under the rows above (3 tiles → row 1: two, row 2: one centred) so a 7-of-9 leftover band reads balanced rather than left-aligned. The mobile variant wants a horizontal carousel where the focused tile sits centred in the viewport at a configurable fraction of its width and its neighbours peek out on the sides at a fixed gap.
Extend `WrapGrid` with `centre_last_row( bool )`. When set, layout offsets a row that has fewer than `columns` children by `(missing * (cell_w + spacing)) / 2` so it stays centred inside the content rect. Defaults to false; every existing call site continues to land tiles flush-left. Covered by three layout tests (centred partial row, full row no-op, off-by-default).
Add the `Carousel` widget at `src/widget/carousel/`. It is a pure layout primitive: `focused_width_frac` (0.05–1.0, clamped), `gap` and `offset` are owned by the caller, leaving drag / inertia / snap policy to the host so the compositor can plug in its existing touch pipeline. Each child gets a rect at `base_x + idx * (child_w + gap)` and the full viewport height; `snap_offset( viewport_w, idx )` translates index to centring offset and `focused_index( viewport_w )` rounds the current offset back to the nearest tile. Plumbed into `Element::Carousel` with the matching arms in `widget/element.rs` and walker in `draw/layout.rs`; re-exported as `ltk::{ Carousel, carousel }`. Covered by nine unit tests (layout, offset shift, snap / focus round-trip, frac clamp, child height) plus a `cargo run --example carousel` demo with Prev / Next / arrow-key navigation against an external offset state. The example is wired into the `examples` Makefile target.
Updates the widget catalogue and the `widget/mod.rs` landing comment to list the carousel under "Clipping wrappers" and to mention `centre_last_row` in the grid section.
This commit is contained in:
@@ -22,7 +22,7 @@ patterns built from these widgets, see [`docs/cookbook.md`](./cookbook.md).
|
||||
- [Decoration and chrome](#decoration-and-chrome)
|
||||
- [`container`](#container) · [`separator`](#separator) · [`img_widget`](#img_widget)
|
||||
- [Clipping wrappers](#clipping-wrappers)
|
||||
- [`scroll`](#scroll) · [`viewport`](#viewport) · [`flex`](#flex)
|
||||
- [`scroll`](#scroll) · [`viewport`](#viewport) · [`flex`](#flex) · [`carousel`](#carousel)
|
||||
- [Overlays and feedback](#overlays-and-feedback)
|
||||
- [`spinner`](#spinner) · [`toast`](#toast) · [`tooltip`](#tooltip) · [`combo`](#combo) · [`tabs`](#tabs) · [`notebook`](#notebook) · [`dialog`](#dialog)
|
||||
- [Pickers](#pickers)
|
||||
@@ -522,6 +522,47 @@ spacer siblings.
|
||||
**See also**: [`spacer`](#spacer) for invisible fillers,
|
||||
[`column`](#column) and [`row`](#row).
|
||||
|
||||
### `carousel`
|
||||
|
||||
Horizontal carousel: the focused child sits centred in the viewport at
|
||||
`focused_width_frac` of the viewport width and its neighbours peek out
|
||||
on the left / right at `gap` separation. The widget is a pure layout
|
||||
primitive — the `offset` (positive shifts content right) is owned by
|
||||
the caller, so drag / inertia / snap-ease live in the host (compositor,
|
||||
gesture recogniser, etc.).
|
||||
|
||||
**When**: mobile-style app switchers, image / story strips, any
|
||||
single-focus horizontal navigation where neighbours hint at the next /
|
||||
previous tile.
|
||||
|
||||
```rust,no_run
|
||||
# use ltk::{ button, carousel, container, Element, Color, Corners };
|
||||
# #[ derive( Clone ) ] enum Msg { Open( usize ) }
|
||||
# fn _ex( offset: f32 ) -> Element<Msg> {
|
||||
carousel()
|
||||
.focused_width_frac( 0.8 )
|
||||
.gap( 16.0 )
|
||||
.offset( offset )
|
||||
.push( container( button::<Msg>( "Tile 1" ).on_press( Msg::Open( 0 ) ) )
|
||||
.background( Color::rgb( 0.95, 0.4, 0.4 ) )
|
||||
.radius( Corners::all( 12.0 ) ) )
|
||||
.push( container( button::<Msg>( "Tile 2" ).on_press( Msg::Open( 1 ) ) )
|
||||
.background( Color::rgb( 0.95, 0.85, 0.3 ) )
|
||||
.radius( Corners::all( 12.0 ) ) )
|
||||
.into()
|
||||
# }
|
||||
```
|
||||
|
||||
Helpers on the widget translate between offsets and indices:
|
||||
`snap_offset( viewport_w, idx )` returns the offset that centres tile
|
||||
`idx`; `focused_index( viewport_w )` rounds the current offset to the
|
||||
nearest tile. The runnable demo at
|
||||
[`examples/carousel.rs`](../examples/carousel.rs) shows Prev / Next
|
||||
buttons and arrow-key navigation against an external `offset` state.
|
||||
|
||||
**See also**: [`scroll`](#scroll) for free vertical / horizontal panning
|
||||
of arbitrary content; [`tabs`](#tabs) for a non-touch alternative.
|
||||
|
||||
---
|
||||
|
||||
## Overlays and feedback
|
||||
@@ -834,6 +875,11 @@ grid( 4 )
|
||||
|
||||
Wrap inside [`scroll`](#scroll) when the grid may overflow.
|
||||
|
||||
`centre_last_row( true )` shifts a partial last row so its tiles sit
|
||||
centred under the full rows above instead of left-aligned — useful for
|
||||
app switchers and gallery layouts where a 7-of-9 leftover band reads
|
||||
better balanced.
|
||||
|
||||
### `spacer`
|
||||
|
||||
An invisible flexible filler. Inside a column / row, absorbs leftover
|
||||
|
||||
Reference in New Issue
Block a user