add Carousel widget and WrapGrid::centre_last_row
Some checks failed
CI / build + test (push) Has been cancelled
CI / cargo audit (push) Has been cancelled

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:
2026-05-22 19:38:48 +02:00
parent 0e52274053
commit 88385e14b2
10 changed files with 604 additions and 22 deletions

View File

@@ -5,9 +5,9 @@ use std::sync::Arc;
use crate::types::{ Point, Rect };
use crate::render::Canvas;
use super::{
anchored_overlay, button, checkbox, container, external, flex, image,
list_item, pressable, progress_bar, radio, scroll, separator, slider,
spinner, text, text_edit, toggle, viewport, vslider, window_button,
anchored_overlay, button, carousel, checkbox, container, external, flex,
image, list_item, pressable, progress_bar, radio, scroll, separator,
slider, spinner, text, text_edit, toggle, viewport, vslider, window_button,
};
use super::handlers::WidgetHandlers;
use super::MapFn;
@@ -40,6 +40,7 @@ pub enum Element<Msg: Clone>
AnchoredOverlay( anchored_overlay::AnchoredOverlay<Msg> ),
Spinner( spinner::Spinner ),
External( external::External ),
Carousel( carousel::Carousel<Msg> ),
}
impl<Msg: Clone> Element<Msg>
@@ -74,6 +75,7 @@ impl<Msg: Clone> Element<Msg>
Element::AnchoredOverlay( a ) => a.child.preferred_size( max_width, canvas ),
Element::Spinner( s ) => s.preferred_size( max_width ),
Element::External( e ) => e.preferred_size( max_width ),
Element::Carousel( c ) => c.preferred_size( max_width, canvas ),
}
}
@@ -116,6 +118,7 @@ impl<Msg: Clone> Element<Msg>
Element::AnchoredOverlay( _ ) => {}
Element::Spinner( s ) => s.draw( canvas, rect ),
Element::External( e ) => e.draw( canvas, rect ),
Element::Carousel( _ ) => {}
}
}
@@ -408,6 +411,7 @@ impl<Msg: Clone + 'static> Element<Msg>
Element::AnchoredOverlay( a ) => Element::AnchoredOverlay( a.map_msg( f ) ),
Element::Spinner( s ) => Element::Spinner( s ),
Element::External( e ) => Element::External( e ),
Element::Carousel( c ) => Element::Carousel( c.map_msg( f ) ),
}
}
}