1use frus_core::{Rect, Scene, SizeClass};
11use frus_layout::Style;
12
13use crate::interaction::{Key, Status};
14use crate::portal::Placement;
15use crate::runtime::Edit;
16use crate::scroll::Axis;
17use crate::theme::Theme;
18use crate::widget::Widget;
19
20pub struct Responsive<Msg = crate::callback::Callback> {
22 class_rank: u8,
23 inner: Option<Box<dyn Widget<Msg>>>,
24 inner_rank: u8,
25}
26
27fn closeness(rank: u8, class: u8) -> (u8, bool) {
30 (rank.abs_diff(class), rank > class)
31}
32
33impl<Msg> Responsive<Msg> {
34 pub fn new(class: SizeClass) -> Self {
36 Self {
37 class_rank: class.rank(),
38 inner: None,
39 inner_rank: 0,
40 }
41 }
42
43 fn consider(mut self, rank: u8, widget: impl Widget<Msg> + 'static) -> Self {
46 let better = self.inner.is_none()
47 || closeness(rank, self.class_rank) < closeness(self.inner_rank, self.class_rank);
48 if better {
49 self.inner = Some(Box::new(widget));
50 self.inner_rank = rank;
51 }
52 self
53 }
54
55 pub fn compact(self, widget: impl Widget<Msg> + 'static) -> Self {
57 self.consider(SizeClass::Compact.rank(), widget)
58 }
59
60 pub fn medium(self, widget: impl Widget<Msg> + 'static) -> Self {
62 self.consider(SizeClass::Medium.rank(), widget)
63 }
64
65 pub fn expanded(self, widget: impl Widget<Msg> + 'static) -> Self {
67 self.consider(SizeClass::Expanded.rank(), widget)
68 }
69}
70
71pub fn responsive<Msg>(width: f32) -> Responsive<Msg> {
75 Responsive::new(SizeClass::from_width(width))
76}
77
78impl<Msg> Widget<Msg> for Responsive<Msg> {
79 fn style(&self) -> Style {
80 self.inner.as_ref().map(|w| w.style()).unwrap_or_default()
81 }
82
83 fn style_themed(&self, theme: &Theme) -> Style {
84 self.inner
85 .as_ref()
86 .map(|w| w.style_themed(theme))
87 .unwrap_or_default()
88 }
89
90 fn build_themed(&self, theme: &Theme) {
96 if let Some(w) = &self.inner {
97 w.build_themed(theme);
98 }
99 }
100
101 fn build_in(
102 &self,
103 id: crate::interaction::WidgetId,
104 runtime: &crate::runtime::Runtime,
105 theme: &Theme,
106 ) {
107 if let Some(w) = &self.inner {
108 w.build_in(id, runtime, theme);
109 }
110 }
111
112 fn expand(
113 &self,
114 id: crate::interaction::WidgetId,
115 runtime: &crate::runtime::Runtime,
116 theme: &Theme,
117 ) {
118 if let Some(w) = &self.inner {
119 w.expand(id, runtime, theme);
120 }
121 }
122
123 fn switches(&self) -> bool {
124 self.inner.as_ref().is_some_and(|w| w.switches())
125 }
126
127 fn system_ui_style(&self) -> Option<crate::SystemUiOverlayStyle> {
128 self.inner.as_ref().and_then(|w| w.system_ui_style())
129 }
130
131 fn debug_name(&self) -> &'static str {
132 self.inner
134 .as_ref()
135 .map(|w| w.debug_name())
136 .unwrap_or("Responsive")
137 }
138
139 fn semantics(&self) -> Option<frus_core::SemanticsProperties> {
140 self.inner.as_ref().and_then(|w| w.semantics())
141 }
142
143 fn children(&self) -> &[Box<dyn Widget<Msg>>] {
144 self.inner.as_ref().map(|w| w.children()).unwrap_or(&[])
145 }
146
147 fn paint(&self, bounds: Rect, status: Status, theme: &Theme, scene: &mut Scene) {
148 if let Some(w) = &self.inner {
149 w.paint(bounds, status, theme, scene);
150 }
151 }
152
153 fn paints_over(&self) -> bool {
154 self.inner.as_ref().is_some_and(|w| w.paints_over())
155 }
156
157 fn paint_over(&self, bounds: Rect, status: Status, theme: &Theme, scene: &mut Scene) {
158 if let Some(w) = &self.inner {
159 w.paint_over(bounds, status, theme, scene);
160 }
161 }
162
163 fn on_click(&self) -> Option<Msg> {
164 self.inner.as_ref().and_then(|w| w.on_click())
165 }
166
167 fn on_focus_change(&self, focused: bool) -> Option<Msg> {
168 self.inner.as_ref().and_then(|w| w.on_focus_change(focused))
169 }
170
171 fn on_tap_outside(&self) -> Option<Msg> {
172 self.inner.as_ref().and_then(|w| w.on_tap_outside())
173 }
174
175 fn opaque(&self) -> bool {
176 self.inner.as_ref().is_some_and(|w| w.opaque())
177 }
178
179 fn positional_click(&self, local_x: f32, local_y: f32, width: f32, height: f32) -> Option<Msg> {
180 self.inner
181 .as_ref()
182 .and_then(|w| w.positional_click(local_x, local_y, width, height))
183 }
184
185 fn cursor_icon(
186 &self,
187 local_x: f32,
188 local_y: f32,
189 width: f32,
190 height: f32,
191 ) -> Option<crate::interaction::Cursor> {
192 self.inner
193 .as_ref()
194 .and_then(|w| w.cursor_icon(local_x, local_y, width, height))
195 }
196
197 fn key(&self) -> Option<u64> {
198 self.inner.as_ref().and_then(|w| w.key())
199 }
200
201 fn on_edit(&self, edit: &mut Edit, key: &Key) -> Option<Msg> {
202 self.inner.as_ref().and_then(|w| w.on_edit(edit, key))
203 }
204
205 fn cursor_at(
206 &self,
207 local_x: f32,
208 local_y: f32,
209 width: f32,
210 scroll_cursor: usize,
211 ) -> Option<usize> {
212 self.inner
213 .as_ref()
214 .and_then(|w| w.cursor_at(local_x, local_y, width, scroll_cursor))
215 }
216
217 fn text_metrics(&self, width: f32, cursor: usize) -> Option<(f32, f32, f32, f32)> {
218 self.inner
219 .as_ref()
220 .and_then(|w| w.text_metrics(width, cursor))
221 }
222
223 fn text_viewport(&self, rect: frus_core::Rect) -> Option<frus_core::Rect> {
224 self.inner.as_ref().and_then(|w| w.text_viewport(rect))
225 }
226
227 fn caret_vertical(
228 &self,
229 width: f32,
230 cursor: usize,
231 down: bool,
232 page: bool,
233 goal_x: Option<f32>,
234 ) -> Option<(usize, f32)> {
235 self.inner
236 .as_ref()
237 .and_then(|w| w.caret_vertical(width, cursor, down, page, goal_x))
238 }
239
240 fn selected_text(&self, edit: &Edit) -> Option<String> {
241 self.inner.as_ref().and_then(|w| w.selected_text(edit))
242 }
243
244 fn text_value(&self) -> Option<&str> {
245 self.inner.as_ref().and_then(|w| w.text_value())
246 }
247
248 fn takes_typing(&self) -> bool {
249 self.inner.as_ref().is_some_and(|w| w.takes_typing())
250 }
251
252 fn selection_text(&self) -> Option<&str> {
253 self.inner.as_ref().and_then(|w| w.selection_text())
254 }
255
256 fn region_toolbar(
257 &self,
258 context: crate::ToolbarContext,
259 width: f32,
260 range: (usize, usize),
261 ) -> Option<(Rect, &dyn Widget<Msg>)> {
262 self.inner
263 .as_ref()
264 .and_then(|w| w.region_toolbar(context, width, range))
265 }
266
267 fn selection_grip(
268 &self,
269 width: f32,
270 index: usize,
271 start: bool,
272 ) -> Option<crate::SelectionHandle> {
273 self.inner
274 .as_ref()
275 .and_then(|w| w.selection_grip(width, index, start))
276 }
277
278 fn selection_hit(&self, local_x: f32, local_y: f32, width: f32) -> Option<usize> {
279 self.inner
280 .as_ref()
281 .and_then(|w| w.selection_hit(local_x, local_y, width))
282 }
283
284 fn word_at(&self, index: usize) -> Option<(usize, usize)> {
285 self.inner.as_ref().and_then(|w| w.word_at(index))
286 }
287
288 fn selection_handles(
289 &self,
290 width: f32,
291 edit: &Edit,
292 scroll_y: f32,
293 ) -> Option<[crate::SelectionHandle; 2]> {
294 self.inner
295 .as_ref()
296 .and_then(|w| w.selection_handles(width, edit, scroll_y))
297 }
298
299 fn selection_toolbar(&self, context: crate::ToolbarContext) -> Option<&dyn Widget<Msg>> {
300 self.inner
301 .as_ref()
302 .and_then(|w| w.selection_toolbar(context))
303 }
304
305 fn selection_anchor(&self, width: f32, edit: &Edit, scroll_y: f32) -> Option<Rect> {
306 self.inner
307 .as_ref()
308 .and_then(|w| w.selection_anchor(width, edit, scroll_y))
309 }
310
311 fn edit_action(&self) -> Option<crate::EditAction> {
312 self.inner.as_ref().and_then(|w| w.edit_action())
313 }
314
315 fn autofill_hints(&self) -> &[crate::AutofillHint] {
316 self.inner
317 .as_ref()
318 .map(|w| w.autofill_hints())
319 .unwrap_or(&[])
320 }
321
322 fn autofill_group(&self) -> bool {
323 self.inner.as_ref().is_some_and(|w| w.autofill_group())
324 }
325
326 fn area_toolbar(&self, context: crate::ToolbarContext) -> Option<Option<&dyn Widget<Msg>>> {
327 self.inner.as_ref().and_then(|w| w.area_toolbar(context))
328 }
329
330 fn focusable(&self) -> bool {
331 self.inner.as_ref().is_some_and(|w| w.focusable())
332 }
333
334 fn draws_own_focus(&self) -> bool {
335 self.inner.as_ref().is_some_and(|w| w.draws_own_focus())
336 }
337
338 fn stack(&self) -> bool {
339 self.inner.as_ref().is_some_and(|w| w.stack())
340 }
341
342 fn stack_fit(&self) -> crate::StackFit {
343 self.inner
344 .as_ref()
345 .map_or(crate::StackFit::Passthrough, |w| w.stack_fit())
346 }
347
348 fn stack_measured(&self) -> bool {
349 self.inner.as_ref().is_some_and(|w| w.stack_measured())
350 }
351
352 fn stack_clips(&self) -> bool {
353 self.inner.as_ref().is_none_or(|w| w.stack_clips())
354 }
355
356 fn stack_direction(&self) -> Option<frus_core::TextDirection> {
357 self.inner.as_ref().and_then(|w| w.stack_direction())
358 }
359
360 fn stack_visible(&self) -> Option<usize> {
361 self.inner.as_ref().and_then(|w| w.stack_visible())
362 }
363
364 fn continuous(&self) -> bool {
365 self.inner.as_ref().is_some_and(|w| w.continuous())
366 }
367
368 fn virtual_list(&self, viewport: frus_core::Size) -> Option<crate::list::VirtualList<'_, Msg>> {
369 self.inner.as_ref().and_then(|w| w.virtual_list(viewport))
370 }
371
372 fn page_view(&self) -> Option<crate::pageview::PagedView<'_, Msg>> {
373 self.inner.as_ref().and_then(|w| w.page_view())
374 }
375
376 fn intrinsic(&self) -> Option<(crate::constraints::IntrinsicAxis, Option<f32>)> {
377 self.inner.as_ref().and_then(|w| w.intrinsic())
378 }
379
380 fn overflow_box(&self) -> Option<crate::constraints::Overflow> {
381 self.inner.as_ref().and_then(|w| w.overflow_box())
382 }
383
384 fn hero_tag(&self) -> Option<u64> {
385 self.inner.as_ref().and_then(|w| w.hero_tag())
386 }
387
388 fn drag_payload(&self) -> Option<u64> {
389 self.inner.as_ref().and_then(|w| w.drag_payload())
390 }
391
392 fn drag_needs_long_press(&self) -> bool {
393 self.inner
394 .as_ref()
395 .is_some_and(|w| w.drag_needs_long_press())
396 }
397
398 fn drag_ghost_opacity(&self) -> f32 {
399 self.inner.as_ref().map_or(1.0, |w| w.drag_ghost_opacity())
400 }
401
402 fn on_dropped(&self, accepted: bool) -> Option<Msg> {
403 self.inner.as_ref().and_then(|w| w.on_dropped(accepted))
404 }
405
406 fn drop_zone(&self) -> bool {
407 self.inner.as_ref().is_some_and(|w| w.drop_zone())
408 }
409
410 fn accepts_drag(&self, payload: u64) -> bool {
411 self.inner.as_ref().is_none_or(|w| w.accepts_drag(payload))
412 }
413
414 fn on_drop(&self, payload: u64) -> Option<Msg> {
415 self.inner.as_ref().and_then(|w| w.on_drop(payload))
416 }
417
418 fn accepts_files(&self, paths: &[std::path::PathBuf]) -> bool {
419 self.inner.as_ref().is_some_and(|w| w.accepts_files(paths))
420 }
421
422 fn on_files_dropped(&self, paths: Vec<std::path::PathBuf>) -> Option<Msg> {
423 self.inner.as_ref().and_then(|w| w.on_files_dropped(paths))
424 }
425
426 fn on_page_changed(&self, page: usize) -> Option<Msg> {
427 self.inner.as_ref().and_then(|w| w.on_page_changed(page))
428 }
429
430 fn on_scroll(&self, position: crate::ScrollPosition) -> Option<Msg> {
431 self.inner.as_ref().and_then(|w| w.on_scroll(position))
432 }
433
434 fn scroll_grain(&self) -> f32 {
435 self.inner.as_ref().map_or(0.0, |w| w.scroll_grain())
436 }
437
438 fn layout_builder(&self) -> Option<&dyn Fn(frus_core::Size) -> Box<dyn Widget<Msg>>> {
439 self.inner.as_ref().and_then(|w| w.layout_builder())
440 }
441
442 fn scroll_overlay(&self) -> Option<&crate::scrolloverlay::OverlayBuilder<Msg>> {
443 self.inner.as_ref().and_then(|w| w.scroll_overlay())
444 }
445
446 fn draggable(&self) -> bool {
447 self.inner.as_ref().is_some_and(|w| w.draggable())
448 }
449 fn drag_track(&self) -> crate::widget::DragTrack {
450 self.inner
451 .as_ref()
452 .map_or(crate::widget::DragTrack::WHOLE, |w| w.drag_track())
453 }
454 fn on_thumb_drag(
455 &self,
456 thumb: usize,
457 moment: crate::widget::ThumbDrag,
458 fraction: f32,
459 ) -> Option<Msg> {
460 self.inner
461 .as_ref()
462 .and_then(|w| w.on_thumb_drag(thumb, moment, fraction))
463 }
464
465 fn on_drag(&self, fraction: f32) -> Option<Msg> {
466 self.inner.as_ref().and_then(|w| w.on_drag(fraction))
467 }
468
469 fn on_drag_start(&self, fraction: f32) -> Option<Msg> {
470 self.inner.as_ref().and_then(|w| w.on_drag_start(fraction))
471 }
472
473 fn on_drag_end(&self, fraction: f32) -> Option<Msg> {
474 self.inner.as_ref().and_then(|w| w.on_drag_end(fraction))
475 }
476
477 fn on_drag_delta(&self, dx: f32) -> Option<Msg> {
478 self.inner.as_ref().and_then(|w| w.on_drag_delta(dx))
479 }
480
481 fn reorder_index(&self) -> Option<usize> {
482 self.inner.as_ref().and_then(|w| w.reorder_index())
483 }
484
485 fn on_reorder(&self, to: usize) -> Option<Msg> {
486 self.inner.as_ref().and_then(|w| w.on_reorder(to))
487 }
488
489 fn announce(&self) -> Option<String> {
490 self.inner.as_ref().and_then(|w| w.announce())
491 }
492
493 fn overlay_traps_focus(&self) -> bool {
494 self.inner.as_ref().is_some_and(|w| w.overlay_traps_focus())
495 }
496
497 fn scroll_content(&self) -> Option<&dyn Widget<Msg>> {
498 self.inner.as_ref().and_then(|w| w.scroll_content())
499 }
500
501 fn scroll_axis(&self) -> Axis {
502 self.inner
503 .as_ref()
504 .map(|w| w.scroll_axis())
505 .unwrap_or(Axis::Vertical)
506 }
507
508 fn scroll_physics(&self) -> Option<crate::physics::ScrollPhysics> {
509 self.inner.as_ref().and_then(|w| w.scroll_physics())
510 }
511
512 fn keep_visible(
513 &self,
514 size: frus_core::Size,
515 theme: &crate::theme::Theme,
516 ) -> Option<crate::ui::KeepVisible> {
517 self.inner
518 .as_ref()
519 .and_then(|w| w.keep_visible(size, theme))
520 }
521
522 fn ime(&self) -> crate::ime::Ime {
523 self.inner.as_ref().map(|w| w.ime()).unwrap_or_default()
524 }
525
526 fn overlay(&self) -> Option<(&dyn Widget<Msg>, Placement)> {
527 self.inner.as_ref().and_then(|w| w.overlay())
528 }
529
530 fn overlay_dismiss(&self) -> Option<Msg> {
531 self.inner.as_ref().and_then(|w| w.overlay_dismiss())
532 }
533
534 fn overlay_scrim(&self, theme: &Theme) -> Option<frus_core::Color> {
535 self.inner.as_ref().and_then(|w| w.overlay_scrim(theme))
536 }
537
538 fn anim_target(&self) -> Option<f32> {
539 self.inner.as_ref().and_then(|w| w.anim_target())
540 }
541
542 fn anim_initial(&self) -> Option<f32> {
543 self.inner.as_ref().and_then(|w| w.anim_initial())
544 }
545
546 fn anim_duration(&self) -> f32 {
547 self.inner
548 .as_ref()
549 .map(|w| w.anim_duration())
550 .unwrap_or(crate::runtime::ANIM_DURATION)
551 }
552
553 fn anim_curve(&self) -> frus_core::Curve {
554 self.inner
555 .as_ref()
556 .map(|w| w.anim_curve())
557 .unwrap_or(frus_core::Curve::Linear)
558 }
559
560 fn opacity_group(&self) -> Option<f32> {
561 self.inner.as_ref().and_then(|w| w.opacity_group())
562 }
563
564 fn anim_color(&self) -> Option<frus_core::Color> {
565 self.inner.as_ref().and_then(|w| w.anim_color())
566 }
567
568 fn anim_size(&self) -> Option<frus_core::Size> {
569 self.inner.as_ref().and_then(|w| w.anim_size())
570 }
571
572 fn anim_radius(&self) -> Option<frus_core::BorderRadius> {
573 self.inner.as_ref().and_then(|w| w.anim_radius())
574 }
575
576 fn anim_padding(&self) -> Option<frus_core::Insets> {
577 self.inner.as_ref().and_then(|w| w.anim_padding())
578 }
579
580 fn anim_transform(&self) -> Option<crate::runtime::TransformValues> {
581 self.inner.as_ref().and_then(|w| w.anim_transform())
582 }
583
584 fn anim_offset(&self) -> Option<(f32, f32)> {
590 self.inner.as_ref().and_then(|w| w.anim_offset())
591 }
592
593 fn anim_pins(&self) -> Option<crate::positioned::Positioning> {
594 self.inner.as_ref().and_then(|w| w.anim_pins())
595 }
596
597 fn anim_fractions(&self) -> Option<(Option<f32>, Option<f32>)> {
598 self.inner.as_ref().and_then(|w| w.anim_fractions())
599 }
600 fn anim_text_style(&self) -> Option<frus_core::TextStyle> {
601 self.inner.as_ref().and_then(|w| w.anim_text_style())
602 }
603
604 fn positioned(&self) -> Option<crate::positioned::Positioning> {
605 self.inner.as_ref().and_then(|w| w.positioned())
606 }
607
608 fn alignment_geometry(&self) -> Option<frus_core::AlignmentGeometry> {
609 self.inner.as_ref().and_then(|w| w.alignment_geometry())
610 }
611
612 fn transform_translate(&self) -> Option<(f32, f32)> {
613 self.inner.as_ref().and_then(|w| w.transform_translate())
614 }
615
616 fn transform_scale(&self) -> Option<(f32, f32, frus_core::Alignment)> {
617 self.inner.as_ref().and_then(|w| w.transform_scale())
618 }
619
620 fn transform_rotate(&self) -> Option<(f32, frus_core::Alignment)> {
621 self.inner.as_ref().and_then(|w| w.transform_rotate())
622 }
623
624 fn clip_shape(&self) -> Option<frus_core::ClipShape> {
625 self.inner.as_ref().and_then(|w| w.clip_shape())
626 }
627
628 fn clip_path(&self) -> Option<&frus_core::Path> {
629 self.inner.as_ref().and_then(|w| w.clip_path())
630 }
631
632 fn barrier(&self) -> Option<crate::barrier::ModalBarrier> {
633 self.inner.as_ref().and_then(|w| w.barrier())
634 }
635
636 fn dismissible(&self) -> Option<crate::dismiss::DismissSpec> {
637 self.inner.as_ref().and_then(|w| w.dismissible())
638 }
639
640 fn on_dismissed(&self, direction: crate::dismiss::DismissDirection) -> Option<Msg> {
641 self.inner.as_ref().and_then(|w| w.on_dismissed(direction))
642 }
643
644 fn sheet(&self) -> Option<&crate::sheet::SheetSpec> {
645 self.inner.as_ref().and_then(|w| w.sheet())
646 }
647
648 fn on_sheet_dismissed(&self) -> Option<Msg> {
649 self.inner.as_ref().and_then(|w| w.on_sheet_dismissed())
650 }
651
652 fn refresh(&self) -> Option<crate::refresh::RefreshSpec> {
653 self.inner.as_ref().and_then(|w| w.refresh())
654 }
655
656 fn on_refresh(&self) -> Option<Msg> {
657 self.inner.as_ref().and_then(|w| w.on_refresh())
658 }
659
660 fn interactive(&self) -> Option<(f32, f32)> {
661 self.inner.as_ref().and_then(|w| w.interactive())
662 }
663
664 fn fitted(&self) -> Option<frus_core::BoxFit> {
665 self.inner.as_ref().and_then(|w| w.fitted())
666 }
667
668 fn rotated_quarter_turns(&self) -> Option<i32> {
669 self.inner.as_ref().and_then(|w| w.rotated_quarter_turns())
670 }
671
672 fn navigator(&self) -> Option<(f32, bool)> {
673 self.inner.as_ref().and_then(|w| w.navigator())
674 }
675
676 fn navigator_gesture(&self) -> bool {
677 self.inner.as_ref().is_some_and(|w| w.navigator_gesture())
678 }
679
680 fn navigator_retained(&self) -> usize {
681 self.inner.as_ref().map_or(0, |w| w.navigator_retained())
682 }
683
684 fn measure(&self, theme: &Theme) -> Option<frus_layout::MeasureFn<'_>> {
685 self.inner.as_ref().and_then(|w| w.measure(theme))
686 }
687
688 fn measure_key(&self, theme: &Theme) -> Option<u64> {
689 self.inner.as_ref().and_then(|w| w.measure_key(theme))
690 }
691
692 fn on_long_press(&self) -> Option<Msg> {
693 self.inner.as_ref().and_then(|w| w.on_long_press())
694 }
695
696 fn on_double_tap(&self) -> Option<Msg> {
697 self.inner.as_ref().and_then(|w| w.on_double_tap())
698 }
699
700 fn on_secondary_tap(&self) -> Option<Msg> {
701 self.inner.as_ref().and_then(|w| w.on_secondary_tap())
702 }
703
704 fn pan_axis(&self) -> Option<crate::PanAxis> {
705 self.inner.as_ref().and_then(|w| w.pan_axis())
706 }
707
708 fn hover_region(&self) -> Option<crate::HoverRegion> {
709 self.inner.as_ref().and_then(|w| w.hover_region())
710 }
711
712 fn pointer_listener(&self) -> bool {
713 self.inner.as_ref().is_some_and(|w| w.pointer_listener())
714 }
715
716 fn on_pointer_event(&self, event: crate::ListenerEvent) -> Vec<Msg> {
717 self.inner
718 .as_ref()
719 .map(|w| w.on_pointer_event(event))
720 .unwrap_or_default()
721 }
722
723 fn on_hover_event(&self, event: crate::HoverEvent) -> Option<Msg> {
724 self.inner.as_ref().and_then(|w| w.on_hover_event(event))
725 }
726
727 fn on_pan(&self, event: crate::PanEvent) -> Option<Msg> {
728 self.inner.as_ref().and_then(|w| w.on_pan(event))
729 }
730
731 fn pan_value(&self, event: crate::PanEvent, value: f32, rtl: bool) -> Option<f32> {
732 self.inner
733 .as_ref()
734 .and_then(|w| w.pan_value(event, value, rtl))
735 }
736
737 fn on_value_release(&self, value: f32) -> Option<Msg> {
738 self.inner.as_ref().and_then(|w| w.on_value_release(value))
739 }
740
741 fn on_key(&self, key: &crate::Key) -> crate::KeyResponse<Msg> {
742 self.inner
743 .as_ref()
744 .map(|w| w.on_key(key))
745 .unwrap_or(crate::KeyResponse::Ignored)
746 }
747}
748
749#[cfg(test)]
750mod tests {
751 use super::*;
752 use crate::Container;
753 use frus_layout::Dimension;
754
755 fn chosen_width(r: &Responsive<()>) -> Dimension {
758 Widget::<()>::style(r).width
759 }
760
761 fn tagged(w: f32) -> Responsive<()> {
762 responsive(w)
763 .compact(Container::new().width(100.0))
764 .medium(Container::new().width(200.0))
765 .expanded(Container::new().width(300.0))
766 }
767
768 #[test]
772 fn wrappers_forward_painting_over_the_children() {
773 let slider = || crate::RangeSlider::<()>::new(0.2, 0.8);
774 let chosen = responsive::<()>(400.0).compact(slider());
775 assert!(Widget::<()>::paints_over(&chosen));
776 assert!(!Widget::<()>::paints_over(
777 &responsive::<()>(400.0).compact(Container::new())
778 ));
779 let keyed = crate::Keyed::new(7, slider());
780 assert!(Widget::<()>::paints_over(&keyed));
781 let mut over = frus_core::Scene::new();
782 Widget::<()>::paint_over(
783 &keyed,
784 frus_core::Rect::new(0.0, 0.0, 248.0, 48.0),
785 Status::default(),
786 &crate::Theme::default(),
787 &mut over,
788 );
789 let mut chose = frus_core::Scene::new();
790 Widget::<()>::paint_over(
791 &chosen,
792 frus_core::Rect::new(0.0, 0.0, 248.0, 48.0),
793 Status::default(),
794 &crate::Theme::default(),
795 &mut chose,
796 );
797 assert!(!over.primitives().is_empty(), "the thumbs, through the key");
798 assert_eq!(over.primitives().len(), chose.primitives().len());
799 }
800
801 #[test]
802 fn picks_variant_for_width() {
803 assert_eq!(chosen_width(&tagged(400.0)), Dimension::Length(100.0));
804 assert_eq!(chosen_width(&tagged(700.0)), Dimension::Length(200.0));
805 assert_eq!(chosen_width(&tagged(1200.0)), Dimension::Length(300.0));
806 }
807
808 #[test]
809 fn falls_back_to_nearest_when_missing() {
810 let only_expanded = |w: f32| responsive::<()>(w).expanded(Container::new().width(300.0));
812 assert_eq!(
813 chosen_width(&only_expanded(300.0)),
814 Dimension::Length(300.0)
815 );
816
817 let two = responsive::<()>(700.0)
820 .compact(Container::new().width(100.0))
821 .expanded(Container::new().width(300.0));
822 assert_eq!(chosen_width(&two), Dimension::Length(100.0));
823 }
824
825 #[test]
834 fn a_selector_keeps_the_layers_place_in_the_stack() {
835 let pinned = responsive::<()>(400.0).compact(
836 crate::Positioned::new(Container::new().width(20.0).height(20.0))
837 .top(8.0)
838 .right(12.0),
839 );
840 let spec = Widget::<()>::positioned(&pinned).expect("the pins reached through");
841 assert_eq!((spec.top, spec.right), (Some(8.0), Some(12.0)));
842 }
843
844 #[test]
848 fn a_selector_keeps_the_childs_animated_offset() {
849 let sliding = responsive::<()>(400.0).compact(crate::AnimatedAlign::new(
850 frus_core::Alignment::CENTER_RIGHT,
851 0.2,
852 frus_core::Curve::Linear,
853 Container::new().width(20.0).height(20.0),
854 ));
855 assert_eq!(
856 Widget::<()>::anim_offset(&sliding),
857 Some((1.0, 0.0)),
860 "the anchor's two fractions reached through"
861 );
862 }
863
864 #[test]
869 fn a_selector_builds_a_deferred_variant() {
870 let chosen = responsive::<()>(400.0).compact(crate::ThemeBuilder::new(|_: &Theme| {
871 Container::new()
872 .width(30.0)
873 .height(10.0)
874 .color(frus_core::Color::rgb(1.0, 0.0, 0.0))
875 }));
876 let ui = crate::ui::build_ui(
877 &chosen,
878 frus_core::Size::new(100.0, 100.0),
879 &crate::Runtime::default(),
880 &Theme::default(),
881 );
882 assert!(
883 ui.scene()
884 .primitives()
885 .iter()
886 .any(|p| matches!(p, frus_core::Primitive::Rect { color, .. } if color.r > 0.5)),
887 "the variant was built, and painted"
888 );
889 }
890}