scrolling.rs (11109B)
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 { 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_chain_id: root_space_and_clip.clip_chain_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( 76 info.clip_rect, 77 ClipChainId::INVALID, 78 info.spatial_id, 79 info.flags, 80 (0, 1) 81 ); 82 builder.push_rect(&info, info.clip_rect, ColorF::new(1.0, 1.0, 1.0, 1.0)); 83 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( 87 info.clip_rect, 88 ClipChainId::INVALID, 89 info.spatial_id, 90 info.flags, 91 (0, 2) 92 ); 93 builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 0.0, 1.0, 1.0)); 94 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(), 99 space_and_clip1, 100 ); 101 builder.push_hit_test( 102 info.clip_rect, 103 ClipChainId::INVALID, 104 info.spatial_id, 105 info.flags, 106 (0, 3) 107 ); 108 builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 1.0, 0.0, 1.0)); 109 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( 114 space1, 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), 122 ); 123 let space_and_clip2 = SpaceAndClipInfo { 124 spatial_id: space2, 125 clip_chain_id: root_space_and_clip.clip_chain_id, 126 }; 127 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), 132 space_and_clip2, 133 ); 134 builder.push_hit_test( 135 info.clip_rect, 136 ClipChainId::INVALID, 137 info.spatial_id, 138 info.flags, 139 (0, 4) 140 ); 141 builder.push_rect(&info, info.clip_rect, ColorF::new(0.5, 0.5, 0.5, 1.0)); 142 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( 147 info.clip_rect, 148 ClipChainId::INVALID, 149 info.spatial_id, 150 info.flags, 151 (0, 5) 152 ); 153 builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 1.0, 1.0, 1.0)); 154 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 158 // scrolling. 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), 167 None, 168 ); 169 170 let info = CommonItemProperties::new( 171 (50, 350).by(50, 50), 172 SpaceAndClipInfo { 173 spatial_id: sticky_id, 174 clip_chain_id: space_and_clip2.clip_chain_id, 175 }, 176 ); 177 builder.push_hit_test( 178 info.clip_rect, 179 ClipChainId::INVALID, 180 info.spatial_id, 181 info.flags, 182 (0, 6) 183 ); 184 builder.push_rect( 185 &info, 186 info.clip_rect, 187 ColorF::new(0.5, 0.5, 1.0, 1.0), 188 ); 189 190 // just for good measure add another teal square further down and to 191 // the right, which can be scrolled into view by the user 192 let info = CommonItemProperties::new( 193 (250, 350).to(300, 400), 194 space_and_clip2, 195 ); 196 builder.push_hit_test( 197 info.clip_rect, 198 ClipChainId::INVALID, 199 info.spatial_id, 200 info.flags, 201 (0, 7) 202 ); 203 builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 1.0, 1.0, 1.0)); 204 205 builder.pop_stacking_context(); 206 } 207 208 builder.pop_stacking_context(); 209 } 210 211 fn on_event( 212 &mut self, 213 event: winit::event::WindowEvent, 214 window: &winit::window::Window, 215 api: &mut RenderApi, 216 document_id: DocumentId, 217 ) -> bool { 218 let mut txn = Transaction::new(); 219 match event { 220 winit::event::WindowEvent::KeyboardInput { 221 input: winit::event::KeyboardInput { 222 state: winit::event::ElementState::Pressed, 223 virtual_keycode: Some(key), 224 .. 225 }, 226 .. 227 } => { 228 let offset = match key { 229 winit::event::VirtualKeyCode::Down => Some(LayoutVector2D::new(0.0, -10.0)), 230 winit::event::VirtualKeyCode::Up => Some(LayoutVector2D::new(0.0, 10.0)), 231 winit::event::VirtualKeyCode::Right => Some(LayoutVector2D::new(-10.0, 0.0)), 232 winit::event::VirtualKeyCode::Left => Some(LayoutVector2D::new(10.0, 0.0)), 233 _ => None, 234 }; 235 236 if let Some(offset) = offset { 237 self.scroll_offset += offset; 238 239 txn.set_scroll_offsets( 240 ExternalScrollId(EXT_SCROLL_ID_CONTENT, PipelineId::dummy()), 241 vec![SampledScrollOffset { 242 offset: self.scroll_offset, 243 generation: APZScrollGeneration::default(), 244 }], 245 ); 246 txn.generate_frame(0, true, false, RenderReasons::empty()); 247 } 248 } 249 winit::event::WindowEvent::CursorMoved { position, .. } => { 250 let pos: LogicalPosition<f32> = position.to_logical(window.scale_factor()); 251 self.cursor_position = WorldPoint::new(pos.x, pos.y); 252 } 253 winit::event::WindowEvent::MouseWheel { delta, .. } => { 254 const LINE_HEIGHT: f32 = 38.0; 255 let (dx, dy) = match delta { 256 winit::event::MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT), 257 winit::event::MouseScrollDelta::PixelDelta(pos) => (pos.x as f32, pos.y as f32), 258 }; 259 260 self.scroll_offset += LayoutVector2D::new(dx, dy); 261 262 txn.set_scroll_offsets( 263 ExternalScrollId(EXT_SCROLL_ID_CONTENT, PipelineId::dummy()), 264 vec![SampledScrollOffset { 265 offset: self.scroll_offset, 266 generation: APZScrollGeneration::default(), 267 }], 268 ); 269 270 txn.generate_frame(0, true, false, RenderReasons::empty()); 271 } 272 winit::event::WindowEvent::MouseInput { .. } => { 273 let results = api.hit_test( 274 document_id, 275 self.cursor_position, 276 ); 277 278 println!("Hit test results:"); 279 for item in &results.items { 280 println!(" • {:?}", item); 281 } 282 println!(""); 283 } 284 _ => (), 285 } 286 287 api.send_transaction(document_id, txn); 288 289 false 290 } 291 } 292 293 fn main() { 294 let mut app = App { 295 cursor_position: WorldPoint::zero(), 296 scroll_offset: LayoutVector2D::zero(), 297 }; 298 boilerplate::main_wrapper(&mut app, None); 299 }