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 
10 #[path = "common/boilerplate.rs"]
11 mod boilerplate;
12 
13 use boilerplate::{Example, HandyDandyRectBuilder};
14 use euclid::SideOffsets2D;
15 use webrender::api::*;
16 
17 struct App {
18     cursor_position: WorldPoint,
19 }
20 
21 impl Example for App {
render( &mut self, _api: &RenderApi, builder: &mut DisplayListBuilder, _resources: &mut ResourceUpdates, _framebuffer_size: DeviceUintSize, _pipeline_id: PipelineId, _document_id: DocumentId, )22     fn render(
23         &mut self,
24         _api: &RenderApi,
25         builder: &mut DisplayListBuilder,
26         _resources: &mut ResourceUpdates,
27         _framebuffer_size: DeviceUintSize,
28         _pipeline_id: PipelineId,
29         _document_id: DocumentId,
30     ) {
31         let info = LayoutPrimitiveInfo::new(
32             LayoutRect::new(LayoutPoint::zero(), builder.content_size())
33         );
34         builder.push_stacking_context(
35             &info,
36             ScrollPolicy::Scrollable,
37             None,
38             TransformStyle::Flat,
39             None,
40             MixBlendMode::Normal,
41             Vec::new(),
42         );
43 
44         if true {
45             // scrolling and clips stuff
46             // let's make a scrollbox
47             let scrollbox = (0, 0).to(300, 400);
48             builder.push_stacking_context(
49                 &LayoutPrimitiveInfo::new((10, 10).by(0, 0)),
50                 ScrollPolicy::Scrollable,
51                 None,
52                 TransformStyle::Flat,
53                 None,
54                 MixBlendMode::Normal,
55                 Vec::new(),
56             );
57             // set the scrolling clip
58             let clip_id = builder.define_scroll_frame(
59                 None,
60                 (0, 0).by(1000, 1000),
61                 scrollbox,
62                 vec![],
63                 None,
64                 ScrollSensitivity::ScriptAndInputEvents,
65             );
66             builder.push_clip_id(clip_id);
67 
68             // now put some content into it.
69             // start with a white background
70             let mut info = LayoutPrimitiveInfo::new((0, 0).to(1000, 1000));
71             info.tag = Some((0, 1));
72             builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 1.0));
73 
74             // let's make a 50x50 blue square as a visual reference
75             let mut info = LayoutPrimitiveInfo::new((0, 0).to(50, 50));
76             info.tag = Some((0, 2));
77             builder.push_rect(&info, ColorF::new(0.0, 0.0, 1.0, 1.0));
78 
79             // and a 50x50 green square next to it with an offset clip
80             // to see what that looks like
81             let mut info =
82                 LayoutPrimitiveInfo::with_clip_rect((50, 0).to(100, 50), (60, 10).to(110, 60));
83             info.tag = Some((0, 3));
84             builder.push_rect(&info, 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 nested_clip_id = builder.define_scroll_frame(
90                 None,
91                 (0, 100).to(300, 1000),
92                 (0, 100).to(200, 300),
93                 vec![],
94                 None,
95                 ScrollSensitivity::ScriptAndInputEvents,
96             );
97             builder.push_clip_id(nested_clip_id);
98 
99             // give it a giant gray background just to distinguish it and to easily
100             // visually identify the nested scrollbox
101             let mut info = LayoutPrimitiveInfo::new((-1000, -1000).to(5000, 5000));
102             info.tag = Some((0, 4));
103             builder.push_rect(&info, ColorF::new(0.5, 0.5, 0.5, 1.0));
104 
105             // add a teal square to visualize the scrolling/clipping behaviour
106             // as you scroll the nested scrollbox
107             let mut info = LayoutPrimitiveInfo::new((0, 200).to(50, 250));
108             info.tag = Some((0, 5));
109             builder.push_rect(&info, ColorF::new(0.0, 1.0, 1.0, 1.0));
110 
111             // Add a sticky frame. It will "stick" twice while scrolling, once
112             // at a margin of 10px from the bottom, for 40 pixels of scrolling,
113             // and once at a margin of 10px from the top, for 60 pixels of
114             // scrolling.
115             let sticky_id = builder.define_sticky_frame(
116                 (50, 350).by(50, 50),
117                 SideOffsets2D::new(Some(10.0), None, Some(10.0), None),
118                 StickyOffsetBounds::new(-40.0, 60.0),
119                 StickyOffsetBounds::new(0.0, 0.0),
120                 LayoutVector2D::new(0.0, 0.0)
121             );
122 
123             builder.push_clip_id(sticky_id);
124             let mut info = LayoutPrimitiveInfo::new((50, 350).by(50, 50));
125             info.tag = Some((0, 6));
126             builder.push_rect(&info, ColorF::new(0.5, 0.5, 1.0, 1.0));
127             builder.pop_clip_id(); // sticky_id
128 
129             // just for good measure add another teal square further down and to
130             // the right, which can be scrolled into view by the user
131             let mut info = LayoutPrimitiveInfo::new((250, 350).to(300, 400));
132             info.tag = Some((0, 7));
133             builder.push_rect(&info, ColorF::new(0.0, 1.0, 1.0, 1.0));
134 
135             builder.pop_clip_id(); // nested_clip_id
136 
137             builder.pop_clip_id(); // clip_id
138             builder.pop_stacking_context();
139         }
140 
141         builder.pop_stacking_context();
142     }
143 
on_event(&mut self, event: glutin::WindowEvent, api: &RenderApi, document_id: DocumentId) -> bool144     fn on_event(&mut self, event: glutin::WindowEvent, api: &RenderApi, document_id: DocumentId) -> bool {
145         let mut txn = Transaction::new();
146         match event {
147             glutin::WindowEvent::KeyboardInput {
148                 input: glutin::KeyboardInput {
149                     state: glutin::ElementState::Pressed,
150                     virtual_keycode: Some(key),
151                     ..
152                 },
153                 ..
154             } => {
155                 let offset = match key {
156                     glutin::VirtualKeyCode::Down => (0.0, -10.0),
157                     glutin::VirtualKeyCode::Up => (0.0, 10.0),
158                     glutin::VirtualKeyCode::Right => (-10.0, 0.0),
159                     glutin::VirtualKeyCode::Left => (10.0, 0.0),
160                     _ => return false,
161                 };
162 
163                 txn.scroll(
164                     ScrollLocation::Delta(LayoutVector2D::new(offset.0, offset.1)),
165                     self.cursor_position,
166                     ScrollEventPhase::Start,
167                 );
168             }
169             glutin::WindowEvent::CursorMoved { position: (x, y), .. } => {
170                 self.cursor_position = WorldPoint::new(x as f32, y as f32);
171             }
172             glutin::WindowEvent::MouseWheel { delta, .. } => {
173                 const LINE_HEIGHT: f32 = 38.0;
174                 let (dx, dy) = match delta {
175                     glutin::MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT),
176                     glutin::MouseScrollDelta::PixelDelta(dx, dy) => (dx, dy),
177                 };
178 
179                 txn.scroll(
180                     ScrollLocation::Delta(LayoutVector2D::new(dx, dy)),
181                     self.cursor_position,
182                     ScrollEventPhase::Start,
183                 );
184             }
185             glutin::WindowEvent::MouseInput { .. } => {
186                 let results = api.hit_test(
187                     document_id,
188                     None,
189                     self.cursor_position,
190                     HitTestFlags::FIND_ALL
191                 );
192 
193                 println!("Hit test results:");
194                 for item in &results.items {
195                     println!("  • {:?}", item);
196                 }
197                 println!("");
198             }
199             _ => (),
200         }
201 
202         api.send_transaction(document_id, txn);
203 
204         false
205     }
206 }
207 
main()208 fn main() {
209     let mut app = App {
210         cursor_position: WorldPoint::zero(),
211     };
212     boilerplate::main_wrapper(&mut app, None);
213 }
214