Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
extern crate euclid;
extern crate gleam;
extern crate glutin;
extern crate webrender;
extern crate winit;
#[path = "common/boilerplate.rs"]
mod boilerplate;
use crate::boilerplate::{Example, HandyDandyRectBuilder};
use euclid::SideOffsets2D;
use webrender::api::*;
use webrender::render_api::*;
use webrender::api::units::*;
use winit::dpi::LogicalPosition;
const EXT_SCROLL_ID_ROOT: u64 = 1;
const EXT_SCROLL_ID_CONTENT: u64 = 2;
struct App {
cursor_position: WorldPoint,
scroll_offset: LayoutVector2D,
}
impl Example for App {
fn render(
&mut self,
_api: &mut RenderApi,
builder: &mut DisplayListBuilder,
_txn: &mut Transaction,
_device_size: DeviceIntSize,
pipeline_id: PipelineId,
_document_id: DocumentId,
) {
let root_space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
builder.push_simple_stacking_context(
LayoutPoint::zero(),
root_space_and_clip.spatial_id,
PrimitiveFlags::IS_BACKFACE_VISIBLE,
);
if true {
// scrolling and clips stuff
// let's make a scrollbox
let scrollbox = (0, 0).to(300, 400);
builder.push_simple_stacking_context(
LayoutPoint::new(10., 10.),
root_space_and_clip.spatial_id,
PrimitiveFlags::IS_BACKFACE_VISIBLE,
);
// set the scrolling clip
let space1 = builder.define_scroll_frame(
root_space_and_clip.spatial_id,
ExternalScrollId(EXT_SCROLL_ID_ROOT, PipelineId::dummy()),
(0, 0).by(1000, 1000),
scrollbox,
LayoutVector2D::zero(),
APZScrollGeneration::default(),
HasScrollLinkedEffect::No,
SpatialTreeItemKey::new(0, 0),
);
let space_and_clip1 = SpaceAndClipInfo {
spatial_id: space1,
clip_chain_id: root_space_and_clip.clip_chain_id,
};
// now put some content into it.
// start with a white background
let info = CommonItemProperties::new((0, 0).to(1000, 1000), space_and_clip1);
builder.push_hit_test(
info.clip_rect,
ClipChainId::INVALID,
info.spatial_id,
info.flags,
(0, 1)
);
builder.push_rect(&info, info.clip_rect, ColorF::new(1.0, 1.0, 1.0, 1.0));
// let's make a 50x50 blue square as a visual reference
let info = CommonItemProperties::new((0, 0).to(50, 50), space_and_clip1);
builder.push_hit_test(
info.clip_rect,
ClipChainId::INVALID,
info.spatial_id,
info.flags,
(0, 2)
);
builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 0.0, 1.0, 1.0));
// and a 50x50 green square next to it with an offset clip
// to see what that looks like
let info = CommonItemProperties::new(
(50, 0).to(100, 50).intersection(&(60, 10).to(110, 60)).unwrap(),
space_and_clip1,
);
builder.push_hit_test(
info.clip_rect,
ClipChainId::INVALID,
info.spatial_id,
info.flags,
(0, 3)
);
builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 1.0, 0.0, 1.0));
// Below the above rectangles, set up a nested scrollbox. It's still in
// the same stacking context, so note that the rects passed in need to
// be relative to the stacking context.
let space2 = builder.define_scroll_frame(
space1,
ExternalScrollId(EXT_SCROLL_ID_CONTENT, PipelineId::dummy()),
(0, 100).to(300, 1000),
(0, 100).to(200, 300),
LayoutVector2D::zero(),
APZScrollGeneration::default(),
HasScrollLinkedEffect::No,
SpatialTreeItemKey::new(0, 1),
);
let space_and_clip2 = SpaceAndClipInfo {
spatial_id: space2,
clip_chain_id: root_space_and_clip.clip_chain_id,
};
// give it a giant gray background just to distinguish it and to easily
// visually identify the nested scrollbox
let info = CommonItemProperties::new(
(-1000, -1000).to(5000, 5000),
space_and_clip2,
);
builder.push_hit_test(
info.clip_rect,
ClipChainId::INVALID,
info.spatial_id,
info.flags,
(0, 4)
);
builder.push_rect(&info, info.clip_rect, ColorF::new(0.5, 0.5, 0.5, 1.0));
// add a teal square to visualize the scrolling/clipping behaviour
// as you scroll the nested scrollbox
let info = CommonItemProperties::new((0, 200).to(50, 250), space_and_clip2);
builder.push_hit_test(
info.clip_rect,
ClipChainId::INVALID,
info.spatial_id,
info.flags,
(0, 5)
);
builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 1.0, 1.0, 1.0));
// Add a sticky frame. It will "stick" twice while scrolling, once
// at a margin of 10px from the bottom, for 40 pixels of scrolling,
// and once at a margin of 10px from the top, for 60 pixels of
// scrolling.
let sticky_id = builder.define_sticky_frame(
space_and_clip2.spatial_id,
(50, 350).by(50, 50),
SideOffsets2D::new(Some(10.0), None, Some(10.0), None),
StickyOffsetBounds::new(-40.0, 60.0),
StickyOffsetBounds::new(0.0, 0.0),
LayoutVector2D::new(0.0, 0.0),
SpatialTreeItemKey::new(0, 2),
None,
);
let info = CommonItemProperties::new(
(50, 350).by(50, 50),
SpaceAndClipInfo {
spatial_id: sticky_id,
clip_chain_id: space_and_clip2.clip_chain_id,
},
);
builder.push_hit_test(
info.clip_rect,
ClipChainId::INVALID,
info.spatial_id,
info.flags,
(0, 6)
);
builder.push_rect(
&info,
info.clip_rect,
ColorF::new(0.5, 0.5, 1.0, 1.0),
);
// just for good measure add another teal square further down and to
// the right, which can be scrolled into view by the user
let info = CommonItemProperties::new(
(250, 350).to(300, 400),
space_and_clip2,
);
builder.push_hit_test(
info.clip_rect,
ClipChainId::INVALID,
info.spatial_id,
info.flags,
(0, 7)
);
builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 1.0, 1.0, 1.0));
builder.pop_stacking_context();
}
builder.pop_stacking_context();
}
fn on_event(
&mut self,
event: winit::event::WindowEvent,
window: &winit::window::Window,
api: &mut RenderApi,
document_id: DocumentId,
) -> bool {
let mut txn = Transaction::new();
match event {
winit::event::WindowEvent::KeyboardInput {
input: winit::event::KeyboardInput {
state: winit::event::ElementState::Pressed,
virtual_keycode: Some(key),
..
},
..
} => {
let offset = match key {
winit::event::VirtualKeyCode::Down => Some(LayoutVector2D::new(0.0, -10.0)),
winit::event::VirtualKeyCode::Up => Some(LayoutVector2D::new(0.0, 10.0)),
winit::event::VirtualKeyCode::Right => Some(LayoutVector2D::new(-10.0, 0.0)),
winit::event::VirtualKeyCode::Left => Some(LayoutVector2D::new(10.0, 0.0)),
_ => None,
};
if let Some(offset) = offset {
self.scroll_offset += offset;
txn.set_scroll_offsets(
ExternalScrollId(EXT_SCROLL_ID_CONTENT, PipelineId::dummy()),
vec![SampledScrollOffset {
offset: self.scroll_offset,
generation: APZScrollGeneration::default(),
}],
);
txn.generate_frame(0, RenderReasons::empty());
}
}
winit::event::WindowEvent::CursorMoved { position, .. } => {
let pos: LogicalPosition<f32> = position.to_logical(window.scale_factor());
self.cursor_position = WorldPoint::new(pos.x, pos.y);
}
winit::event::WindowEvent::MouseWheel { delta, .. } => {
const LINE_HEIGHT: f32 = 38.0;
let (dx, dy) = match delta {
winit::event::MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT),
winit::event::MouseScrollDelta::PixelDelta(pos) => (pos.x as f32, pos.y as f32),
};
self.scroll_offset += LayoutVector2D::new(dx, dy);
txn.set_scroll_offsets(
ExternalScrollId(EXT_SCROLL_ID_CONTENT, PipelineId::dummy()),
vec![SampledScrollOffset {
offset: self.scroll_offset,
generation: APZScrollGeneration::default(),
}],
);
txn.generate_frame(0, RenderReasons::empty());
}
winit::event::WindowEvent::MouseInput { .. } => {
let results = api.hit_test(
document_id,
self.cursor_position,
);
println!("Hit test results:");
for item in &results.items {
println!(" • {:?}", item);
}
println!("");
}
_ => (),
}
api.send_transaction(document_id, txn);
false
}
}
fn main() {
let mut app = App {
cursor_position: WorldPoint::zero(),
scroll_offset: LayoutVector2D::zero(),
};
boilerplate::main_wrapper(&mut app, None);
}