//! Scrolling moves content and stops at its ends. use iris::harness::{Harness, assert_corners}; use iris::prelude::*; #[test] fn scrollable_enables_a_region_node_but_raw_scroll_does_not() { let mut h = Harness::new((100, 100)); let default_child = ().add(&mut h.rsc); let _default = default_child.scrollable().add(&mut h.rsc); assert!(h.rsc.widgets().is_region_node(default_child)); h.rsc.widgets_mut().set_region_node(default_child, false); assert!(!h.rsc.widgets().is_region_node(default_child)); let raw_child = ().add(&mut h.rsc); let _raw = Scroll::new(raw_child.add_strong(&mut h.rsc), Axis::Y).add(&mut h.rsc); assert!(!h.rsc.widgets().is_region_node(raw_child)); let explicit = ().region_node().add(&mut h.rsc); assert!(h.rsc.widgets().is_region_node(explicit)); } #[test] fn a_scrollable_child_can_drop_its_region_node() { let mut h = Harness::new((400, 200)); let top = rect(Color::RED).height(200).add(&mut h.rsc); let bottom = rect(Color::BLUE).height(200).add(&mut h.rsc); let content = (top, bottom).span(Dir::DOWN).add(&mut h.rsc); h.set_root(content.scrollable()); h.rsc.widgets_mut().set_region_node(content, false); h.frame(); h.move_to((200, 100)); h.scroll((0, 1)); h.frame(); assert!(!h.rsc.widgets().is_region_node(content)); assert_corners!(h, top, (0, -150), (400, 50)); } #[test] fn a_wheel_scrolls_the_content_and_stops_at_its_end() { let mut h = Harness::new((400, 200)); // Twice the window's height, so there is 200 to scroll. let top = rect(Color::RED).height(200).add(&mut h.rsc); let bottom = rect(Color::BLUE).height(200).add(&mut h.rsc); h.set_root((top, bottom).span(Dir::DOWN).scrollable()); h.move_to((200, 100)); // `Scroll` starts snapped to the end. assert_corners!(h, top, (0, -200), (400, 0)); // The handler scales a wheel line by 50. h.scroll((0, 1)); h.frame(); assert_corners!(h, top, (0, -150), (400, 50)); h.scroll((0, 10)); h.frame(); assert_corners!(h, top, (0, 0), (400, 200)); } #[test] fn fixed_content_and_a_share_fill_one_viewport() { let mut h = Harness::new((900, 100)); let content = rect(Color::RED) .width(LayoutLen { px: Px::from_int(600), rel: Rel::ZERO, leftover: Weight::ONE, }) .add(&mut h.rsc); let scroll = Scroll::new(content.add_strong(&mut h.rsc), Axis::X); h.set_root(scroll); assert_corners!(h, content, (0, 0), (900, 100)); } #[test] fn fixed_content_wider_than_the_viewport_still_scrolls() { let mut h = Harness::new((900, 100)); let content = rect(Color::RED).width(1200).add(&mut h.rsc); let scroll = Scroll::new(content.add_strong(&mut h.rsc), Axis::X); h.set_root(scroll); assert_corners!(h, content, (-300, 0), (900, 100)); } #[test] fn a_lone_share_fills_without_scrolling() { let mut h = Harness::new((900, 100)); let content = rect(Color::RED).width(LayoutLen::LEFTOVER).add(&mut h.rsc); let scroll = Scroll::new(content.add_strong(&mut h.rsc), Axis::X); h.set_root(scroll); assert_corners!(h, content, (0, 0), (900, 100)); } #[test] fn wrapping_content_beside_a_fixed_length_is_stable_warm_and_cold() { fn plant(h: &mut Harness) -> (WidgetId, WidgetId) { let fixed = rect(Color::RED).width(600).add(&mut h.rsc); let text = wtext("Wrapping shapes one source into as many lines as the box leaves room for, so a paragraph's height is an answer and not a setting.") .size(16) .wrap(true) .width(LayoutLen::LEFTOVER) .add(&mut h.rsc); let content = (fixed, text).span(Dir::RIGHT).add(&mut h.rsc); let scroll = Scroll::new(content.add_strong(&mut h.rsc), Axis::X); h.set_root(scroll); (text.id(), content.id()) } let mut warm = Harness::new((900, 300)); let (text, content) = plant(&mut warm); warm.rsc.widgets_mut().get_dyn_mut(text); warm.frame(); let mut cold = Harness::new((900, 300)); let (cold_text, cold_content) = plant(&mut cold); assert_eq!(warm.region(&text), cold.region(&cold_text)); assert_eq!(warm.region(&content), cold.region(&cold_content)); } /// A widget that clips to its box may not report more than the box: its /// parent would place the part it cut off, and the framework would put a /// drawing longer than its box somewhere. `Masked` is the second of these /// after `Scroll`, and the assertion in `draw_at` is what says so. #[test] #[should_panic = "clips to"] fn a_clipping_widget_reporting_more_than_its_box_is_caught() { struct Clipper(StrongWidget); impl Widget for Clipper { fn draw(&mut self, painter: &mut Painter) -> Size { painter.set_mask(UiRegion::FULL); painter.widget(&self.0).size() } } let mut h = Harness::new((100, 100)); let tall = rect(Color::RED).height(400).add_strong(&mut h.rsc); let clipper = Clipper(tall).add(&mut h.rsc); // `set_root` lays the tree out, so this is where it is caught. h.set_root(clipper); } /// Content that fits sits in the viewport, not in a box of the window's /// length anchored at the viewport's start. `Part::From` takes window /// lengths, so a `rel(1.0)` span in one is the window, and only a scroll /// filling the window would land right. #[test] fn content_that_fits_is_placed_in_the_viewport_and_not_in_the_window() { let mut h = Harness::new((400, 400)); let head = rect(Color::RED).height(100).add(&mut h.rsc); let inner = rect(Color::BLUE).height(50).add(&mut h.rsc); let scroll = Scroll::new(inner.add_strong(&mut h.rsc), Axis::Y).add(&mut h.rsc); h.set_root((head, scroll).span(Dir::DOWN)); assert_corners!(h, scroll, (0, 100), (400, 400)); assert_corners!(h, inner, (0, 225), (400, 275)); }