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