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_origin: LayoutPoint,
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 space_and_clip1 = builder.define_scroll_frame(
58 &root_space_and_clip,
59 ExternalScrollId(EXT_SCROLL_ID_ROOT, PipelineId::dummy()),
60 (0, 0).by(1000, 1000),
61 scrollbox,
62 ScrollSensitivity::ScriptAndInputEvents,
63 LayoutVector2D::zero(),
64 );
65
66 // now put some content into it.
67 // start with a white background
68 let info = CommonItemProperties::new((0, 0).to(1000, 1000), space_and_clip1);
69 builder.push_hit_test(&info, (0, 1));
70 builder.push_rect(&info, info.clip_rect, ColorF::new(1.0, 1.0, 1.0, 1.0));
71
72 // let's make a 50x50 blue square as a visual reference
73 let info = CommonItemProperties::new((0, 0).to(50, 50), space_and_clip1);
74 builder.push_hit_test(&info, (0, 2));
75 builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 0.0, 1.0, 1.0));
76
77 // and a 50x50 green square next to it with an offset clip
78 // to see what that looks like
79 let info = CommonItemProperties::new(
80 (50, 0).to(100, 50).intersection(&(60, 10).to(110, 60)).unwrap(),
81 space_and_clip1,
82 );
83 builder.push_hit_test(&info, (0, 3));
84 builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 1.0, 0.0, 1.0));
85
86 // Below the above rectangles, set up a nested scrollbox. It's still in
87 // the same stacking context, so note that the rects passed in need to
88 // be relative to the stacking context.
89 let space_and_clip2 = builder.define_scroll_frame(
90 &space_and_clip1,
91 ExternalScrollId(EXT_SCROLL_ID_CONTENT, PipelineId::dummy()),
92 (0, 100).to(300, 1000),
93 (0, 100).to(200, 300),
94 ScrollSensitivity::ScriptAndInputEvents,
95 LayoutVector2D::zero(),
96 );
97
98 // give it a giant gray background just to distinguish it and to easily
99 // visually identify the nested scrollbox
100 let info = CommonItemProperties::new(
101 (-1000, -1000).to(5000, 5000),
102 space_and_clip2,
103 );
104 builder.push_hit_test(&info, (0, 4));
105 builder.push_rect(&info, info.clip_rect, ColorF::new(0.5, 0.5, 0.5, 1.0));
106
107 // add a teal square to visualize the scrolling/clipping behaviour
108 // as you scroll the nested scrollbox
109 let info = CommonItemProperties::new((0, 200).to(50, 250), space_and_clip2);
110 builder.push_hit_test(&info, (0, 5));
111 builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 1.0, 1.0, 1.0));
112
113 // Add a sticky frame. It will "stick" twice while scrolling, once
114 // at a margin of 10px from the bottom, for 40 pixels of scrolling,
115 // and once at a margin of 10px from the top, for 60 pixels of
116 // scrolling.
117 let sticky_id = builder.define_sticky_frame(
118 space_and_clip2.spatial_id,
119 (50, 350).by(50, 50),
120 SideOffsets2D::new(Some(10.0), None, Some(10.0), None),
121 StickyOffsetBounds::new(-40.0, 60.0),
122 StickyOffsetBounds::new(0.0, 0.0),
123 LayoutVector2D::new(0.0, 0.0)
124 );
125
126 let info = CommonItemProperties::new(
127 (50, 350).by(50, 50),
128 SpaceAndClipInfo {
129 spatial_id: sticky_id,
130 clip_id: space_and_clip2.clip_id,
131 },
132 );
133 builder.push_hit_test(&info, (0, 6));
134 builder.push_rect(
135 &info,
136 info.clip_rect,
137 ColorF::new(0.5, 0.5, 1.0, 1.0),
138 );
139
140 // just for good measure add another teal square further down and to
141 // the right, which can be scrolled into view by the user
142 let info = CommonItemProperties::new(
143 (250, 350).to(300, 400),
144 space_and_clip2,
145 );
146 builder.push_hit_test(&info, (0, 7));
147 builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 1.0, 1.0, 1.0));
148
149 builder.pop_stacking_context();
150 }
151
152 builder.pop_stacking_context();
153 }
154
on_event(&mut self, event: winit::WindowEvent, api: &mut RenderApi, document_id: DocumentId) -> bool155 fn on_event(&mut self, event: winit::WindowEvent, api: &mut RenderApi, document_id: DocumentId) -> bool {
156 let mut txn = Transaction::new();
157 match event {
158 winit::WindowEvent::KeyboardInput {
159 input: winit::KeyboardInput {
160 state: winit::ElementState::Pressed,
161 virtual_keycode: Some(key),
162 ..
163 },
164 ..
165 } => {
166 let offset = match key {
167 winit::VirtualKeyCode::Down => Some(LayoutVector2D::new(0.0, -10.0)),
168 winit::VirtualKeyCode::Up => Some(LayoutVector2D::new(0.0, 10.0)),
169 winit::VirtualKeyCode::Right => Some(LayoutVector2D::new(-10.0, 0.0)),
170 winit::VirtualKeyCode::Left => Some(LayoutVector2D::new(10.0, 0.0)),
171 _ => None,
172 };
173
174 if let Some(offset) = offset {
175 self.scroll_origin += offset;
176
177 txn.scroll_node_with_id(
178 self.scroll_origin,
179 ExternalScrollId(EXT_SCROLL_ID_CONTENT, PipelineId::dummy()),
180 ScrollClamping::ToContentBounds,
181 );
182 txn.generate_frame(0);
183 }
184 }
185 winit::WindowEvent::CursorMoved { position: LogicalPosition { x, y }, .. } => {
186 self.cursor_position = WorldPoint::new(x as f32, y as f32);
187 }
188 winit::WindowEvent::MouseWheel { delta, .. } => {
189 const LINE_HEIGHT: f32 = 38.0;
190 let (dx, dy) = match delta {
191 winit::MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT),
192 winit::MouseScrollDelta::PixelDelta(pos) => (pos.x as f32, pos.y as f32),
193 };
194
195 self.scroll_origin += LayoutVector2D::new(dx, dy);
196
197 txn.scroll_node_with_id(
198 self.scroll_origin,
199 ExternalScrollId(EXT_SCROLL_ID_CONTENT, PipelineId::dummy()),
200 ScrollClamping::ToContentBounds,
201 );
202
203 txn.generate_frame(0);
204 }
205 winit::WindowEvent::MouseInput { .. } => {
206 let results = api.hit_test(
207 document_id,
208 None,
209 self.cursor_position,
210 );
211
212 println!("Hit test results:");
213 for item in &results.items {
214 println!(" • {:?}", item);
215 }
216 println!("");
217 }
218 _ => (),
219 }
220
221 api.send_transaction(document_id, txn);
222
223 false
224 }
225 }
226
main()227 fn main() {
228 let mut app = App {
229 cursor_position: WorldPoint::zero(),
230 scroll_origin: LayoutPoint::zero(),
231 };
232 boilerplate::main_wrapper(&mut app, None);
233 }
234