frust-widgets 0.6.2

Baseline widget set for Frust: text, buttons, images, flex, stack and scroll containers, and the authoring helpers for custom widgets.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
//! Baseline widget set: Text, Button, Image, Column/Row, Stack, ScrollView,
//! CustomScrollView and its slivers, etc.
//!
//! Ships the [`text`] leaf plus the primitive layout containers —
//! [`Row`]/[`Column`] ([`FlexView`]), [`Stack`], [`IndexedStack`], [`Padding`],
//! [`Align`], and [`SizedBox`] — built as `View`/`Widget` pairs over `frust-core`'s
//! [`AnyView`](frust_core::AnyView)/[`ChildPod`](frust_core::ChildPod)
//! substrate. Containers own their children directly as `ChildPod`s (the arena
//! stays single-root); see [`frust_core::widget::ChildPod`] for the rationale.
//!
//! # Shared child plumbing
//!
//! The container modules build/rebuild/teardown their heterogeneous children
//! through the [`authoring::build_child`]/[`authoring::rebuild_child`]/
//! [`authoring::teardown_child`] helpers and route pointer events through
//! [`authoring::route_event`] (multi-child containers — `Flex`/`Stack`) or
//! [`authoring::route_event_single`] (one-child wrappers —
//! `Padding`/`Align`/`SizedBox`). Each child is an
//! [`AnyView`](frust_core::AnyView) whose element (`Box<dyn Widget>`) is stored
//! double-boxed inside a `ChildPod`, so a later rebuild can recover
//! `&mut Box<dyn Widget>` to drive `AnyView`'s type-erased reconciliation.
//!
//! That toolkit is **public** — see the [`authoring`] module — so a design
//! system is authored outside this crate against exactly the surface the
//! baseline widgets use themselves. No design-system catalog lives here any
//! more: the three built-in ones ship as their own plugin crates
//! (`frust-glyph`/`frust-material`/`frust-cupertino`), each depending on the
//! `frust` facade alone, so the authoring boundary is now enforced by the
//! crate graph rather than by a source scan.

mod align;
pub mod authoring;
mod button;
mod canvas;
mod checkbox;
mod container;
mod divider;
pub mod drag;
pub mod either;
mod flex;
mod gesture;
mod icon;
mod icon_button;
pub mod icons;
mod image;
mod indexed_stack;
mod list_view;
pub mod motion;
pub mod nav;
mod overlay;
mod padding;
mod pan_zoom;
pub mod physics;
pub mod pinch;
mod platform_view;
mod radio;
mod repaint_boundary;
mod safe_area;
mod scaffold;
mod scroll;
pub mod scroll_controller;
mod scroll_core;
mod selection_toolbar;
mod sized;
mod slider;
mod slivers;
mod stack;
mod text;
mod textinput;
mod texture_view;
mod viewport;

use std::hash::{Hash, Hasher};

pub use align::{Align, AlignView, AlignWidget, Alignment};
pub use button::{Button, ButtonStyle, ButtonView, ButtonWidget, button};
pub use canvas::{CanvasView, CanvasWidget, canvas};
pub use checkbox::{Checkbox, CheckboxView, CheckboxWidget, checkbox};
pub use container::{BorderStyle, ContainerView, ContainerWidget, colored_box, container};
pub use divider::{DividerView, DividerWidget, divider};
pub use drag::{
    AUTO_SCROLL_EDGE_PX, AUTO_SCROLL_MAX_PX_PER_S, AutoScroll, AutoScrollZone,
    AutoScrollZoneWidget, DRAG_THRESHOLD, DragCoordinator, DragHighlight, DragKind, DragPhase,
    DragPolicy, DragSession, DragSourceId, DragState, DragStateChange, DragSubscription,
    DragTargetId, DragTargetView, DragTargetWidget, DraggableView, DraggableWidget, GHOST_OPACITY,
    HIGHLIGHT_FALLBACK, ReorderableListView, SourceFeedback, auto_scroll_zone, drag_target,
    draggable, reorderable_list,
};
pub use either::{
    Either, EitherOf3, EitherOf3Widget, EitherOf4, EitherOf4Widget, EitherOf5, EitherOf5Widget,
    EitherOf6, EitherOf6Widget, EitherOf7, EitherOf7Widget, EitherOf8, EitherOf8Widget,
    EitherWidget, either,
};
pub use flex::{
    Axis, Column, CrossAxisAlignment, FlexChild, FlexView, FlexWidget, MainAxisAlignment, Row,
    column, flexible, inflexible, keyed, row,
};
/// The axis a [`CustomScrollView`] scrolls along, re-exported so its
/// [`axis`](CustomScrollView::axis) builder needs no direct `frust-core` import.
pub use frust_core::ScrollAxis;
pub use gesture::{GestureDetector, GestureDetectorView, GestureDetectorWidget};
pub use icon::{Icon, IconData, IconSource, IconView, IconWidget, icon};
pub use icon_button::{IconButton, IconButtonView, IconButtonWidget, icon_button};
pub use image::{Image, ImageError, ImageFit, ImageSource, ImageView, ImageWidget};
pub use indexed_stack::{IndexedStack, IndexedStackView, IndexedStackWidget, indexed_stack};
pub use list_view::{ListView, ListViewWidget, list_view};
pub use nav::hero::{HeroView, HeroWidget, hero};
pub use nav::navigator::{
    BackPolicy, NavigatorController, NavigatorId, NavigatorView, NavigatorWidget, PageBuilder,
    PageVisibility, PopResult, PushOptions, ReplaceOptions, ResultCallback, RouteChangeCallback,
    VisibilityCallback, navigator, overlay_host,
};
pub use nav::path::{Location, PathPattern, RouteParams};
pub use nav::route::{NavRequest, NavWaker, RouteNavigator};
pub use nav::route_state::{NavChange, RouteStack};
pub use nav::router::{
    DEFAULT_REDIRECT_LIMIT, ErrorBuilder, Redirect, Resolution, ResolvedPage, Route, RouteBuilder,
    Router, shell_route,
};
pub use nav::transition::{PageTransition, Timing, TransitionSpec, TransitionState};
pub use overlay::{
    DEFAULT_OFFSET, DEFAULT_PADDING, OverlayAlign, OverlayAnchor, OverlayPlacement,
    OverlayPortalView, OverlayPortalWidget, OverlaySide, OverlaySlot, overlay_portal, place,
};
pub use padding::{EdgeInsets, Padding, PaddingView, PaddingWidget};
pub use pan_zoom::{
    DEFAULT_MAX_SCALE, DEFAULT_MIN_SCALE, PanZoomController, PanZoomTransform, PanZoomView,
    PanZoomWidget, pan_zoom,
};
pub use physics::effect::OverscrollEffect;
pub use physics::parity::{
    AlwaysScrollable, Bouncing, Clamping, DecelerationRate, NeverScrollable,
};
pub use physics::rubber_band::RubberBand;
pub use physics::{
    MAX_FLING_VELOCITY, MIN_FLING_VELOCITY, ScrollMetrics, ScrollPhysics, Simulation,
    SpringDescription, Tolerance,
};
pub use pinch::{
    PINCH_SLOP, PinchDetectorView, PinchDetectorWidget, PinchRecognizer, pinch_detector,
};
pub use platform_view::{
    PlatformViewView, PlatformViewWidget, ShieldView, ShieldWidget, platform_view, shield,
};
pub use radio::{Radio, RadioView, RadioWidget, radio};
pub use repaint_boundary::{RepaintBoundaryView, RepaintBoundaryWidget, repaint_boundary};
pub use safe_area::{SafeAreaView, SafeAreaWidget, safe_area};
pub use scaffold::{ScaffoldView, ScaffoldWidget, scaffold};
pub use scroll::{ScrollInfo, ScrollView, ScrollWidget, scroll_view};
pub use scroll_controller::{AnimateTo, ScrollController, ScrollSubscription};
pub use selection_toolbar::selection_toolbar;
pub use sized::{SizedBox, SizedBoxView, SizedBoxWidget};
pub use slider::{Slider, SliderView, SliderWidget, slider};
pub use slivers::{
    KeepAliveHandle, KeepAliveView, KeepAliveWidget, SliverBoxView, SliverBoxWidget,
    SliverFillRemainingView, SliverFillRemainingWidget, SliverFixedExtentListView,
    SliverFixedExtentListWidget, SliverGridDelegate, SliverGridView, SliverGridWidget,
    SliverListView, SliverListWidget, SliverPaddingView, SliverPaddingWidget,
    SliverPersistentHeaderDelegate, SliverPersistentHeaderView, SliverPersistentHeaderWidget,
    keep_alive, sliver_box, sliver_fill_remaining, sliver_fixed_extent_list,
    sliver_fixed_extent_list_keyed, sliver_grid, sliver_grid_keyed, sliver_list, sliver_padding,
    sliver_persistent_header,
};
pub use stack::{Stack, StackView, StackWidget, stack};
pub use text::{TextView, TextWidget, text};
pub use textinput::{TextInput, TextInputView, TextInputWidget, text_input};
pub use texture_view::{TextureFit, TextureView, TextureWidget, texture_fit_rect, texture_view};
pub use viewport::{CustomScrollView, DEFAULT_CACHE_EXTENT, ViewportWidget, custom_scroll_view};

/// A stable identity for a list child, so a container's reconciliation can match
/// a child to its live widget *by key* across reorders/inserts instead of by
/// position — the difference between "the third row's widget" and "row #42's
/// widget" when the list is shuffled.
///
/// Built from any [`Hash`] value (an item id, a string name, an index) via the
/// `From` impls below and [`keyed`](crate::keyed); the hashed `u64` is what the
/// reconciler compares. Two children in the same list must not collide — a
/// duplicate key is a `debug_assert` tripwire that falls back to positional
/// reconciliation (see [`authoring::rebuild_children`]).
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct ChildKey(u64);

impl ChildKey {
    /// Hash any [`Hash`] value into a `ChildKey`. Backs the `From` impls and
    /// [`keyed`](crate::keyed)'s `impl Into<ChildKey>` argument.
    pub fn new(value: impl Hash) -> Self {
        let mut hasher = std::collections::hash_map::DefaultHasher::new();
        value.hash(&mut hasher);
        ChildKey(hasher.finish())
    }
}

macro_rules! child_key_from {
    ($($t:ty),* $(,)?) => {
        $(
            impl From<$t> for ChildKey {
                fn from(value: $t) -> Self {
                    ChildKey::new(value)
                }
            }
        )*
    };
}

// Common key types: integer ids/indices, chars, and string names. A blanket
// `impl<T: Hash> From<T>` would collide with the reflexive `From<ChildKey>`, so
// the ergonomic conversions are spelled out for the types keys are drawn from.
child_key_from!(
    u8, u16, u32, u64, usize, i8, i16, i32, i64, isize, char, &str, String
);

/// Shared, GPU-free fixtures for the container layout/paint/event tests: a
/// fixed-size [`Leaf`], a distinctive swap partner, an event-recording
/// [`Probe`], and a recording [`RecordingScene`].
///
/// Compiled into this crate's own test build, and — behind the non-default
/// `test-support` feature — into the library itself, so a design system authored
/// against [`authoring`] outside this crate can test its containers against the
/// same fixtures this crate's own container tests use — the design-system
/// plugin crates are exactly that consumer. The feature is off by default: a
/// normal app ships none of this.
#[cfg(any(test, feature = "test-support"))]
pub mod test_support {
    use frust_core::{
        AnyView, BoxConstraints, BuildCtx, ChangeFlags, EventCtx, EventResult, InputEvent,
        LayoutCtx, PaintCtx, PaintScene, View, Widget, any,
    };
    use kurbo::{Affine, Point, Size};
    use peniko::Color;

    /// A leaf view of fixed intrinsic size that fills a rect on paint.
    pub struct Leaf {
        intrinsic: Size,
    }

    /// Build a [`Leaf`] with the given intrinsic width/height.
    pub fn leaf(width: f64, height: f64) -> Leaf {
        Leaf {
            intrinsic: Size::new(width, height),
        }
    }

    /// A [`Leaf`], type-erased for a `()`-state container.
    pub fn leaf_any(width: f64, height: f64) -> AnyView<()> {
        any(leaf(width, height))
    }

    impl Leaf {
        /// Erase this leaf into an `AnyView<()>`.
        pub fn into_any(self) -> AnyView<()> {
            any(self)
        }
    }

    /// Retained widget for [`Leaf`].
    pub struct LeafWidget {
        intrinsic: Size,
    }

    impl View<()> for Leaf {
        type Element = LeafWidget;
        fn build(&self, _ctx: &mut BuildCtx<'_>) -> LeafWidget {
            LeafWidget {
                intrinsic: self.intrinsic,
            }
        }
        fn rebuild(
            &self,
            prev: &Self,
            element: &mut LeafWidget,
            _ctx: &mut BuildCtx<'_>,
        ) -> ChangeFlags {
            if prev.intrinsic != self.intrinsic {
                element.intrinsic = self.intrinsic;
                ChangeFlags::LAYOUT
            } else {
                ChangeFlags::NONE
            }
        }
    }

    impl Widget for LeafWidget {
        fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
            bc.constrain(self.intrinsic)
        }
        fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
            scene.fill_rect(ctx.origin(), ctx.size(), Color::BLACK);
        }
    }

    /// A distinctive view whose widget always reports a 7x7 size — used to prove
    /// an `AnyView` type-swap actually replaced the widget.
    pub struct SwapLeaf;

    /// Build the [`SwapLeaf`] swap partner.
    pub fn swap_leaf() -> SwapLeaf {
        SwapLeaf
    }

    impl SwapLeaf {
        /// Erase this view into an `AnyView<()>`.
        pub fn into_any(self) -> AnyView<()> {
            any(self)
        }
    }

    /// Retained widget for [`SwapLeaf`].
    pub struct SwapLeafWidget;

    impl View<()> for SwapLeaf {
        type Element = SwapLeafWidget;
        fn build(&self, _ctx: &mut BuildCtx<'_>) -> SwapLeafWidget {
            SwapLeafWidget
        }
        fn rebuild(
            &self,
            _prev: &Self,
            _element: &mut SwapLeafWidget,
            _ctx: &mut BuildCtx<'_>,
        ) -> ChangeFlags {
            ChangeFlags::NONE
        }
    }

    impl Widget for SwapLeafWidget {
        fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
            bc.constrain(Size::new(7.0, 7.0))
        }
        fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
    }

    /// A view whose widget fills its constraints and, on any event, records its
    /// `id` into the `Vec<u32>` application state and reports `Handled`. Used to
    /// prove event routing order.
    pub struct Probe {
        id: u32,
    }

    /// Build a [`Probe`] tagged with `id`.
    pub fn probe(id: u32) -> Probe {
        Probe { id }
    }

    impl Probe {
        /// Erase this probe into an `AnyView<Vec<u32>>`.
        pub fn into_any(self) -> AnyView<Vec<u32>> {
            any(self)
        }
    }

    /// Retained widget for [`Probe`].
    pub struct ProbeWidget {
        id: u32,
    }

    impl View<Vec<u32>> for Probe {
        type Element = ProbeWidget;
        fn build(&self, _ctx: &mut BuildCtx<'_>) -> ProbeWidget {
            ProbeWidget { id: self.id }
        }
        fn rebuild(
            &self,
            _prev: &Self,
            _element: &mut ProbeWidget,
            _ctx: &mut BuildCtx<'_>,
        ) -> ChangeFlags {
            ChangeFlags::NONE
        }
    }

    impl Widget for ProbeWidget {
        fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
            bc.max()
        }
        fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
        fn event(&mut self, ctx: &mut EventCtx, _event: &InputEvent) -> EventResult {
            ctx.state_mut::<Vec<u32>>().push(self.id);
            ctx.request_redraw();
            EventResult::Handled
        }
    }

    /// A GPU-free [`PaintScene`] that records filled rects in paint order, plus
    /// `push_layer`/`push_transform` calls and their pop counts — the
    /// `motion::animated` wrappers' recording-scene tests assert against
    /// these alongside the pre-existing `rects`.
    #[derive(Default)]
    pub struct RecordingScene {
        pub rects: Vec<(Point, Size)>,
        pub layers: Vec<(Point, Size, f32)>,
        pub layer_pops: u32,
        pub transforms: Vec<Affine>,
        pub transform_pops: u32,
    }

    impl PaintScene for RecordingScene {
        fn fill_rect(&mut self, origin: Point, size: Size, _color: Color) {
            self.rects.push((origin, size));
        }
        fn draw_text(&mut self, _origin: Point, _text: &str) {}
        fn push_layer(&mut self, origin: Point, size: Size, alpha: f32) {
            self.layers.push((origin, size, alpha));
        }
        fn pop_layer(&mut self) {
            self.layer_pops += 1;
        }
        fn push_transform(&mut self, transform: Affine) {
            self.transforms.push(transform);
        }
        fn pop_transform(&mut self) {
            self.transform_pops += 1;
        }
    }
}