1use frust_core::{
20 AnyView, BoxConstraints, BuildCtx, ChangeFlags, ChildPod, EventCtx, EventResult, InputEvent,
21 LayoutCtx, PaintCtx, PaintScene, ScrollAxis, SemanticsCtx, SliverConstraints, SliverGeometry,
22 SliverWidget, View, Widget, any,
23};
24use kurbo::{Point, Size};
25
26pub(crate) fn non_negative(value: f64) -> f64 {
30 if value.is_nan() { 0.0 } else { value.max(0.0) }
31}
32
33pub(crate) fn finite_extent(value: f64) -> f64 {
37 if value.is_finite() {
38 value.max(0.0)
39 } else {
40 0.0
41 }
42}
43
44pub(crate) fn axis_point(axis: ScrollAxis, main: f64, cross: f64) -> Point {
46 match axis {
47 ScrollAxis::Vertical => Point::new(cross, main),
48 ScrollAxis::Horizontal => Point::new(main, cross),
49 }
50}
51
52fn paint_offset_of(sc: &SliverConstraints, from: f64, to: f64) -> f64 {
55 let a = sc.scroll_offset;
56 let b = a + sc.remaining_paint_extent;
57 let span = to.max(a).min(b) - from.max(a).min(b);
58 span.max(0.0).min(sc.remaining_paint_extent)
59}
60
61fn cache_offset_of(sc: &SliverConstraints, from: f64, to: f64) -> f64 {
66 let a = sc.scroll_offset + sc.cache_origin;
67 let b = a + sc.remaining_cache_extent;
68 let span = to.max(a).min(b) - from.max(a).min(b);
69 span.max(0.0).min(sc.remaining_cache_extent)
70}
71
72pub(crate) fn box_geometry(extent: f64, sc: &SliverConstraints) -> SliverGeometry {
79 let extent = finite_extent(extent);
80 let paint_extent = paint_offset_of(sc, 0.0, extent);
81 let cache_extent = cache_offset_of(sc, 0.0, extent).max(paint_extent);
82 SliverGeometry {
83 cache_extent,
84 has_visual_overflow: extent > sc.remaining_paint_extent || sc.scroll_offset > 0.0,
85 ..SliverGeometry::new(extent, paint_extent, extent)
86 }
87}
88
89pub(crate) fn box_child_origin(sc: &SliverConstraints) -> Point {
93 axis_point(sc.axis, -sc.scroll_offset, 0.0)
94}
95
96pub struct SliverBoxView<State: 'static> {
98 child: AnyView<State>,
99}
100
101pub fn sliver_box<State: 'static, V: View<State>>(child: V) -> SliverBoxView<State> {
111 SliverBoxView { child: any(child) }
112}
113
114pub struct SliverBoxWidget {
117 child: ChildPod,
118 clip: bool,
122}
123
124impl<State: 'static> View<State> for SliverBoxView<State> {
125 type Element = SliverBoxWidget;
126
127 fn build(&self, ctx: &mut BuildCtx<'_>) -> SliverBoxWidget {
128 SliverBoxWidget {
129 child: crate::authoring::build_child(&self.child, ctx),
130 clip: false,
131 }
132 }
133
134 fn rebuild(
135 &self,
136 prev: &Self,
137 element: &mut SliverBoxWidget,
138 ctx: &mut BuildCtx<'_>,
139 ) -> ChangeFlags {
140 crate::authoring::rebuild_child(&prev.child, &self.child, &mut element.child, ctx)
141 }
142
143 fn teardown(&self, element: &mut SliverBoxWidget, ctx: &mut BuildCtx<'_>) {
144 crate::authoring::teardown_child(&self.child, &mut element.child, ctx);
145 }
146}
147
148impl Widget for SliverBoxWidget {
149 fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
152 let size = self.child.layout_child(ctx, bc);
153 self.child.set_origin(Point::ZERO);
154 self.clip = false;
155 size
156 }
157
158 fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
159 if self.clip {
160 scene.push_clip(ctx.origin(), ctx.size());
161 }
162 self.child.paint_child(ctx, scene);
163 if self.clip {
164 scene.pop_clip();
165 }
166 }
167
168 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
172 crate::authoring::route_event_single(&mut self.child, ctx, event)
173 }
174
175 fn semantics(&self, ctx: &mut SemanticsCtx) {
176 self.child.semantics_child(ctx);
177 }
178
179 fn as_sliver(&mut self) -> Option<&mut dyn SliverWidget> {
180 Some(self)
181 }
182
183 crate::authoring::visit_children!(child);
184}
185
186impl SliverWidget for SliverBoxWidget {
187 fn layout_sliver(&mut self, ctx: &mut LayoutCtx, sc: &SliverConstraints) -> SliverGeometry {
188 let size = self.child.layout_child(ctx, &sc.as_box_constraints());
189 let geometry = box_geometry(sc.axis.main_extent(size), sc);
190 self.child.set_origin(box_child_origin(sc));
191 self.clip = geometry.has_visual_overflow;
192 geometry
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199 use frust_core::ScrollDirection;
200
201 fn constraints(scroll_offset: f64, remaining_paint_extent: f64) -> SliverConstraints {
202 SliverConstraints {
203 axis: ScrollAxis::Vertical,
204 scroll_offset,
205 preceding_scroll_extent: 0.0,
206 overlap: 0.0,
207 remaining_paint_extent,
208 cross_axis_extent: 400.0,
209 viewport_main_axis_extent: 500.0,
210 cache_origin: -scroll_offset.min(250.0),
211 remaining_cache_extent: remaining_paint_extent + 250.0 + scroll_offset.min(250.0),
212 user_scroll_direction: ScrollDirection::Idle,
213 }
214 }
215
216 #[test]
217 fn a_fully_visible_child_paints_its_whole_extent_uncut() {
218 let sc = constraints(0.0, 500.0);
219 let g = box_geometry(300.0, &sc);
220 assert_eq!(g.scroll_extent, 300.0);
221 assert_eq!(g.paint_extent, 300.0);
222 assert_eq!(g.layout_extent, 300.0);
223 assert_eq!(g.max_paint_extent, 300.0);
224 assert_eq!(g.cache_extent, 300.0);
225 assert!(g.visible && !g.has_visual_overflow);
226 g.debug_assert_valid(&sc);
227 }
228
229 #[test]
230 fn a_scrolled_child_paints_what_is_left_and_is_cut() {
231 let sc = constraints(250.0, 500.0);
232 let g = box_geometry(300.0, &sc);
233 assert_eq!(g.paint_extent, 50.0);
234 assert_eq!(g.hit_test_extent, 50.0);
235 assert!(g.has_visual_overflow);
236 assert_eq!(box_child_origin(&sc), Point::new(0.0, -250.0));
237 g.debug_assert_valid(&sc);
238 }
239
240 #[test]
241 fn a_child_in_the_cache_region_is_cached_but_invisible() {
242 let sc = SliverConstraints {
243 remaining_paint_extent: 0.0,
244 remaining_cache_extent: 250.0,
245 cache_origin: 0.0,
246 ..constraints(0.0, 0.0)
247 };
248 let g = box_geometry(100.0, &sc);
249 assert_eq!(g.paint_extent, 0.0);
250 assert!(!g.visible);
251 assert_eq!(g.cache_extent, 100.0);
252 g.debug_assert_valid(&sc);
253 }
254
255 #[test]
256 fn an_unbounded_or_nan_extent_scrolls_nothing() {
257 let sc = constraints(0.0, 500.0);
258 assert_eq!(box_geometry(f64::INFINITY, &sc), SliverGeometry::ZERO);
259 assert_eq!(box_geometry(f64::NAN, &sc), SliverGeometry::ZERO);
260 assert_eq!(box_geometry(-5.0, &sc), SliverGeometry::ZERO);
261 }
262
263 #[test]
264 fn extents_sanitise_without_panicking() {
265 assert_eq!(non_negative(f64::NAN), 0.0);
266 assert_eq!(non_negative(-1.0), 0.0);
267 assert_eq!(non_negative(f64::INFINITY), f64::INFINITY);
268 assert_eq!(finite_extent(f64::INFINITY), 0.0);
269 assert_eq!(finite_extent(12.5), 12.5);
270 }
271}