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/. */
4 
5 extern crate euclid;
6 extern crate gleam;
7 extern crate glutin;
8 extern crate webrender;
9 extern crate winit;
10 
11 #[path = "common/boilerplate.rs"]
12 mod boilerplate;
13 
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;
20 
21 
22 const EXT_SCROLL_ID_ROOT: u64 = 1;
23 const EXT_SCROLL_ID_CONTENT: u64 = 2;
24 
25 struct App {
26     cursor_position: WorldPoint,
27     scroll_offset: LayoutVector2D,
28 }
29 
30 impl Example for App {
render( &mut self, _api: &mut RenderApi, builder: &mut DisplayListBuilder, _txn: &mut Transaction, _device_size: DeviceIntSize, pipeline_id: PipelineId, _document_id: DocumentId, )31     fn render(
32         &mut self,
33         _api: &mut RenderApi,
34         builder: &mut DisplayListBuilder,
35         _txn: &mut Transaction,
36         _device_size: DeviceIntSize,
37         pipeline_id: PipelineId,
38         _document_id: DocumentId,
39     ) {
40         let root_space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
41         builder.push_simple_stacking_context(
42             LayoutPoint::zero(),
43             root_space_and_clip.spatial_id,
44             PrimitiveFlags::IS_BACKFACE_VISIBLE,
45         );
46 
47         if true {
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,
55             );
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),
61                 scrollbox,
62                 LayoutVector2D::zero(),
63                 APZScrollGeneration::default(),
64                 HasScrollLinkedEffect::No,
65                 SpatialTreeItemKey::new(0, 0),
66             );
67             let space_and_clip1 = SpaceAndClipInfo {
68                 spatial_id: space1,
69                 clip_id: root_space_and_clip.clip_id,
70             };
71 
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(&info, (0, 1));
76             builder.push_rect(&info, info.clip_rect, ColorF::new(1.0, 1.0, 1.0, 1.0));
77 
78             // let's make a 50x50 blue square as a visual reference
79             let info = CommonItemProperties::new((0, 0).to(50, 50), space_and_clip1);
80             builder.push_hit_test(&info, (0, 2));
81             builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 0.0, 1.0, 1.0));
82 
83             // and a 50x50 green square next to it with an offset clip
84             // to see what that looks like
85             let info = CommonItemProperties::new(
86                 (50, 0).to(100, 50).intersection(&(60, 10).to(110, 60)).unwrap(),
87                 space_and_clip1,
88             );
89             builder.push_hit_test(&info, (0, 3));
90             builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 1.0, 0.0, 1.0));
91 
92             // Below the above rectangles, set up a nested scrollbox. It's still in
93             // the same stacking context, so note that the rects passed in need to
94             // be relative to the stacking context.
95             let space2 = builder.define_scroll_frame(
96                 space1,
97                 ExternalScrollId(EXT_SCROLL_ID_CONTENT, PipelineId::dummy()),
98                 (0, 100).to(300, 1000),
99                 (0, 100).to(200, 300),
100                 LayoutVector2D::zero(),
101                 APZScrollGeneration::default(),
102                 HasScrollLinkedEffect::No,
103                 SpatialTreeItemKey::new(0, 1),
104             );
105             let space_and_clip2 = SpaceAndClipInfo {
106                 spatial_id: space2,
107                 clip_id: root_space_and_clip.clip_id,
108             };
109 
110             // give it a giant gray background just to distinguish it and to easily
111             // visually identify the nested scrollbox
112             let info = CommonItemProperties::new(
113                 (-1000, -1000).to(5000, 5000),
114                 space_and_clip2,
115             );
116             builder.push_hit_test(&info, (0, 4));
117             builder.push_rect(&info, info.clip_rect, ColorF::new(0.5, 0.5, 0.5, 1.0));
118 
119             // add a teal square to visualize the scrolling/clipping behaviour
120             // as you scroll the nested scrollbox
121             let info = CommonItemProperties::new((0, 200).to(50, 250), space_and_clip2);
122             builder.push_hit_test(&info, (0, 5));
123             builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 1.0, 1.0, 1.0));
124 
125             // Add a sticky frame. It will "stick" twice while scrolling, once
126             // at a margin of 10px from the bottom, for 40 pixels of scrolling,
127             // and once at a margin of 10px from the top, for 60 pixels of
128             // scrolling.
129             let sticky_id = builder.define_sticky_frame(
130                 space_and_clip2.spatial_id,
131                 (50, 350).by(50, 50),
132                 SideOffsets2D::new(Some(10.0), None, Some(10.0), None),
133                 StickyOffsetBounds::new(-40.0, 60.0),
134                 StickyOffsetBounds::new(0.0, 0.0),
135                 LayoutVector2D::new(0.0, 0.0),
136                 SpatialTreeItemKey::new(0, 2),
137             );
138 
139             let info = CommonItemProperties::new(
140                 (50, 350).by(50, 50),
141                 SpaceAndClipInfo {
142                     spatial_id: sticky_id,
143                     clip_id: space_and_clip2.clip_id,
144                 },
145             );
146             builder.push_hit_test(&info, (0, 6));
147             builder.push_rect(
148                 &info,
149                 info.clip_rect,
150                 ColorF::new(0.5, 0.5, 1.0, 1.0),
151             );
152 
153             // just for good measure add another teal square further down and to
154             // the right, which can be scrolled into view by the user
155             let info = CommonItemProperties::new(
156                 (250, 350).to(300, 400),
157                 space_and_clip2,
158             );
159             builder.push_hit_test(&info, (0, 7));
160             builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 1.0, 1.0, 1.0));
161 
162             builder.pop_stacking_context();
163         }
164 
165         builder.pop_stacking_context();
166     }
167 
on_event(&mut self, event: winit::WindowEvent, api: &mut RenderApi, document_id: DocumentId) -> bool168     fn on_event(&mut self, event: winit::WindowEvent, api: &mut RenderApi, document_id: DocumentId) -> bool {
169         let mut txn = Transaction::new();
170         match event {
171             winit::WindowEvent::KeyboardInput {
172                 input: winit::KeyboardInput {
173                     state: winit::ElementState::Pressed,
174                     virtual_keycode: Some(key),
175                     ..
176                 },
177                 ..
178             } => {
179                 let offset = match key {
180                     winit::VirtualKeyCode::Down => Some(LayoutVector2D::new(0.0, -10.0)),
181                     winit::VirtualKeyCode::Up => Some(LayoutVector2D::new(0.0, 10.0)),
182                     winit::VirtualKeyCode::Right => Some(LayoutVector2D::new(-10.0, 0.0)),
183                     winit::VirtualKeyCode::Left => Some(LayoutVector2D::new(10.0, 0.0)),
184                     _ => None,
185                 };
186 
187                 if let Some(offset) = offset {
188                     self.scroll_offset += offset;
189 
190                     txn.set_scroll_offsets(
191                         ExternalScrollId(EXT_SCROLL_ID_CONTENT, PipelineId::dummy()),
192                         vec![SampledScrollOffset {
193                             offset: self.scroll_offset,
194                             generation: APZScrollGeneration::default(),
195                         }],
196                     );
197                     txn.generate_frame(0, RenderReasons::empty());
198                 }
199             }
200             winit::WindowEvent::CursorMoved { position: LogicalPosition { x, y }, .. } => {
201                 self.cursor_position = WorldPoint::new(x as f32, y as f32);
202             }
203             winit::WindowEvent::MouseWheel { delta, .. } => {
204                 const LINE_HEIGHT: f32 = 38.0;
205                 let (dx, dy) = match delta {
206                     winit::MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT),
207                     winit::MouseScrollDelta::PixelDelta(pos) => (pos.x as f32, pos.y as f32),
208                 };
209 
210                 self.scroll_offset += LayoutVector2D::new(dx, dy);
211 
212                 txn.set_scroll_offsets(
213                     ExternalScrollId(EXT_SCROLL_ID_CONTENT, PipelineId::dummy()),
214                     vec![SampledScrollOffset {
215                             offset: self.scroll_offset,
216                             generation: APZScrollGeneration::default(),
217                     }],
218                 );
219 
220                 txn.generate_frame(0, RenderReasons::empty());
221             }
222             winit::WindowEvent::MouseInput { .. } => {
223                 let results = api.hit_test(
224                     document_id,
225                     self.cursor_position,
226                 );
227 
228                 println!("Hit test results:");
229                 for item in &results.items {
230                     println!("  • {:?}", item);
231                 }
232                 println!("");
233             }
234             _ => (),
235         }
236 
237         api.send_transaction(document_id, txn);
238 
239         false
240     }
241 }
242 
main()243 fn main() {
244     let mut app = App {
245         cursor_position: WorldPoint::zero(),
246         scroll_offset: LayoutVector2D::zero(),
247     };
248     boilerplate::main_wrapper(&mut app, None);
249 }
250