1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 extern crate webrender;
11 #[path = "common/boilerplate.rs"]
14 use crate::boilerplate::{Example, HandyDandyRectBuilder};
15 use euclid::SideOffsets2D;
16 use webrender::api::*;
17 use webrender::render_api::*;
18 use webrender::api::units::*;
19 use winit::dpi::LogicalPosition;
22 const EXT_SCROLL_ID_ROOT: u64 = 1;
23 const EXT_SCROLL_ID_CONTENT: u64 = 2;
26 cursor_position: WorldPoint,
27 scroll_offset: LayoutVector2D,
30 impl Example for App {
34 builder: &mut DisplayListBuilder,
35 _txn: &mut Transaction,
36 _device_size: DeviceIntSize,
37 pipeline_id: PipelineId,
38 _document_id: DocumentId,
40 let root_space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
41 builder.push_simple_stacking_context(
43 root_space_and_clip.spatial_id,
44 PrimitiveFlags::IS_BACKFACE_VISIBLE,
48 // scrolling and clips stuff
49 // let's make a scrollbox
50 let scrollbox = (0, 0).to(300, 400);
51 builder.push_simple_stacking_context(
52 LayoutPoint::new(10., 10.),
53 root_space_and_clip.spatial_id,
54 PrimitiveFlags::IS_BACKFACE_VISIBLE,
56 // set the scrolling clip
57 let space1 = builder.define_scroll_frame(
58 root_space_and_clip.spatial_id,
59 ExternalScrollId(EXT_SCROLL_ID_ROOT, PipelineId::dummy()),
60 (0, 0).by(1000, 1000),
62 LayoutVector2D::zero(),
63 APZScrollGeneration::default(),
64 HasScrollLinkedEffect::No,
65 SpatialTreeItemKey::new(0, 0),
67 let space_and_clip1 = SpaceAndClipInfo {
69 clip_chain_id: root_space_and_clip.clip_chain_id,
72 // now put some content into it.
73 // start with a white background
74 let info = CommonItemProperties::new((0, 0).to(1000, 1000), space_and_clip1);
75 builder.push_hit_test(
82 builder.push_rect(&info, info.clip_rect, ColorF::new(1.0, 1.0, 1.0, 1.0));
84 // let's make a 50x50 blue square as a visual reference
85 let info = CommonItemProperties::new((0, 0).to(50, 50), space_and_clip1);
86 builder.push_hit_test(
93 builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 0.0, 1.0, 1.0));
95 // and a 50x50 green square next to it with an offset clip
96 // to see what that looks like
97 let info = CommonItemProperties::new(
98 (50, 0).to(100, 50).intersection(&(60, 10).to(110, 60)).unwrap(),
101 builder.push_hit_test(
103 ClipChainId::INVALID,
108 builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 1.0, 0.0, 1.0));
110 // Below the above rectangles, set up a nested scrollbox. It's still in
111 // the same stacking context, so note that the rects passed in need to
112 // be relative to the stacking context.
113 let space2 = builder.define_scroll_frame(
115 ExternalScrollId(EXT_SCROLL_ID_CONTENT, PipelineId::dummy()),
116 (0, 100).to(300, 1000),
117 (0, 100).to(200, 300),
118 LayoutVector2D::zero(),
119 APZScrollGeneration::default(),
120 HasScrollLinkedEffect::No,
121 SpatialTreeItemKey::new(0, 1),
123 let space_and_clip2 = SpaceAndClipInfo {
125 clip_chain_id: root_space_and_clip.clip_chain_id,
128 // give it a giant gray background just to distinguish it and to easily
129 // visually identify the nested scrollbox
130 let info = CommonItemProperties::new(
131 (-1000, -1000).to(5000, 5000),
134 builder.push_hit_test(
136 ClipChainId::INVALID,
141 builder.push_rect(&info, info.clip_rect, ColorF::new(0.5, 0.5, 0.5, 1.0));
143 // add a teal square to visualize the scrolling/clipping behaviour
144 // as you scroll the nested scrollbox
145 let info = CommonItemProperties::new((0, 200).to(50, 250), space_and_clip2);
146 builder.push_hit_test(
148 ClipChainId::INVALID,
153 builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 1.0, 1.0, 1.0));
155 // Add a sticky frame. It will "stick" twice while scrolling, once
156 // at a margin of 10px from the bottom, for 40 pixels of scrolling,
157 // and once at a margin of 10px from the top, for 60 pixels of
159 let sticky_id = builder.define_sticky_frame(
160 space_and_clip2.spatial_id,
161 (50, 350).by(50, 50),
162 SideOffsets2D::new(Some(10.0), None, Some(10.0), None),
163 StickyOffsetBounds::new(-40.0, 60.0),
164 StickyOffsetBounds::new(0.0, 0.0),
165 LayoutVector2D::new(0.0, 0.0),
166 SpatialTreeItemKey::new(0, 2),
169 let info = CommonItemProperties::new(
170 (50, 350).by(50, 50),
172 spatial_id: sticky_id,
173 clip_chain_id: space_and_clip2.clip_chain_id,
176 builder.push_hit_test(
178 ClipChainId::INVALID,
186 ColorF::new(0.5, 0.5, 1.0, 1.0),
189 // just for good measure add another teal square further down and to
190 // the right, which can be scrolled into view by the user
191 let info = CommonItemProperties::new(
192 (250, 350).to(300, 400),
195 builder.push_hit_test(
197 ClipChainId::INVALID,
202 builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 1.0, 1.0, 1.0));
204 builder.pop_stacking_context();
207 builder.pop_stacking_context();
212 event: winit::event::WindowEvent,
213 window: &winit::window::Window,
215 document_id: DocumentId,
217 let mut txn = Transaction::new();
219 winit::event::WindowEvent::KeyboardInput {
220 input: winit::event::KeyboardInput {
221 state: winit::event::ElementState::Pressed,
222 virtual_keycode: Some(key),
227 let offset = match key {
228 winit::event::VirtualKeyCode::Down => Some(LayoutVector2D::new(0.0, -10.0)),
229 winit::event::VirtualKeyCode::Up => Some(LayoutVector2D::new(0.0, 10.0)),
230 winit::event::VirtualKeyCode::Right => Some(LayoutVector2D::new(-10.0, 0.0)),
231 winit::event::VirtualKeyCode::Left => Some(LayoutVector2D::new(10.0, 0.0)),
235 if let Some(offset) = offset {
236 self.scroll_offset += offset;
238 txn.set_scroll_offsets(
239 ExternalScrollId(EXT_SCROLL_ID_CONTENT, PipelineId::dummy()),
240 vec![SampledScrollOffset {
241 offset: self.scroll_offset,
242 generation: APZScrollGeneration::default(),
245 txn.generate_frame(0, RenderReasons::empty());
248 winit::event::WindowEvent::CursorMoved { position, .. } => {
249 let pos: LogicalPosition<f32> = position.to_logical(window.scale_factor());
250 self.cursor_position = WorldPoint::new(pos.x, pos.y);
252 winit::event::WindowEvent::MouseWheel { delta, .. } => {
253 const LINE_HEIGHT: f32 = 38.0;
254 let (dx, dy) = match delta {
255 winit::event::MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT),
256 winit::event::MouseScrollDelta::PixelDelta(pos) => (pos.x as f32, pos.y as f32),
259 self.scroll_offset += LayoutVector2D::new(dx, dy);
261 txn.set_scroll_offsets(
262 ExternalScrollId(EXT_SCROLL_ID_CONTENT, PipelineId::dummy()),
263 vec![SampledScrollOffset {
264 offset: self.scroll_offset,
265 generation: APZScrollGeneration::default(),
269 txn.generate_frame(0, RenderReasons::empty());
271 winit::event::WindowEvent::MouseInput { .. } => {
272 let results = api.hit_test(
274 self.cursor_position,
277 println!("Hit test results:");
278 for item in &results.items {
279 println!(" • {:?}", item);
286 api.send_transaction(document_id, txn);
294 cursor_position: WorldPoint::zero(),
295 scroll_offset: LayoutVector2D::zero(),
297 boilerplate::main_wrapper(&mut app, None);