layout: Implement basic overflow: scroll functionality.

Known issues:

* Display list optimization can sometimes optimize out elements that
  should be shown. This affects the Enyo demo.

* The `overflow: scroll` container doesn't clip the inner layer properly
  when borders, border radius, etc. are present.

* `overflow-x: scroll` and `overflow-y: scroll` don't work individually;
  elements are scrolled all at once.

* Scrolling only works on absolutely-positioned elements.
This commit is contained in:
Patrick Walton 2015-08-07 18:10:48 -07:00
parent fc13dd1169
commit df4acbac04
8 changed files with 221 additions and 72 deletions

View file

@ -629,6 +629,7 @@ impl<Window: WindowMethods> IOCompositor<Window> {
transform: Matrix4::identity(),
perspective: Matrix4::identity(),
establishes_3d_context: true,
scrolls_overflow_area: false,
};
let root_layer = CompositorData::new_layer(pipeline.id,
@ -740,10 +741,21 @@ impl<Window: WindowMethods> IOCompositor<Window> {
if let Some(parent_layer) = self.find_layer_with_pipeline_and_layer_id(pipeline_id,
parent_id) {
let wants_scroll_events = if layer_properties.scrolls_overflow_area {
WantsScrollEventsFlag::WantsScrollEvents
} else {
WantsScrollEventsFlag::DoesntWantScrollEvents
};
let new_layer = CompositorData::new_layer(pipeline_id,
layer_properties,
WantsScrollEventsFlag::DoesntWantScrollEvents,
wants_scroll_events,
parent_layer.tile_size);
if layer_properties.scrolls_overflow_area {
*new_layer.masks_to_bounds.borrow_mut() = true
}
parent_layer.add_child(new_layer);
}
}

View file

@ -252,6 +252,9 @@ pub struct StackingContext {
/// Whether this stacking context creates a new 3d rendering context.
pub establishes_3d_context: bool,
/// Whether this stacking context scrolls its overflow area.
pub scrolls_overflow_area: bool,
}
impl StackingContext {
@ -266,7 +269,8 @@ impl StackingContext {
layer: Option<PaintLayer>,
transform: Matrix4,
perspective: Matrix4,
establishes_3d_context: bool)
establishes_3d_context: bool,
scrolls_overflow_area: bool)
-> StackingContext {
StackingContext {
display_list: display_list,
@ -279,6 +283,7 @@ impl StackingContext {
transform: transform,
perspective: perspective,
establishes_3d_context: establishes_3d_context,
scrolls_overflow_area: scrolls_overflow_area,
}
}

View file

@ -342,29 +342,27 @@ impl<C> PaintTask<C> where C: PaintListener + Send + 'static {
transform: &Matrix4,
perspective: &Matrix4,
parent_id: Option<LayerId>) {
let transform = transform.mul(&stacking_context.transform);
let perspective = perspective.mul(&stacking_context.perspective);
let (next_parent_id, page_position, transform, perspective) =
match stacking_context.layer {
Some(ref paint_layer) => {
// Layers start at the top left of their overflow rect, as far as the info we
// give to the compositor is concerned.
let overflow_size =
Size2D::new(stacking_context.overflow.size.width.to_nearest_px() as f32,
stacking_context.overflow.size.height.to_nearest_px() as f32);
let establishes_3d_context = stacking_context.establishes_3d_context;
let scrolls_overflow_area = stacking_context.scrolls_overflow_area;
// Layers start at the top left of their overflow rect, as far as the info
// we give to the compositor is concerned.
let overflow_relative_page_position = *page_position +
stacking_context.bounds.origin +
stacking_context.overflow.origin;
let layer_position =
Rect::new(Point2D::new(overflow_relative_page_position.x.to_nearest_px() as
f32,
overflow_relative_page_position.y.to_nearest_px() as
f32),
Size2D::new(stacking_context.overflow.size.width.to_nearest_px()
as f32,
stacking_context.overflow.size.height.to_nearest_px()
as f32));
let establishes_3d_context = stacking_context.establishes_3d_context;
let layer_position = Rect::new(
Point2D::new(overflow_relative_page_position.x.to_nearest_px() as f32,
overflow_relative_page_position.y.to_nearest_px() as f32),
overflow_size);
properties.push(LayerProperties {
id: paint_layer.id,
@ -375,6 +373,7 @@ impl<C> PaintTask<C> where C: PaintListener + Send + 'static {
transform: transform,
perspective: perspective,
establishes_3d_context: establishes_3d_context,
scrolls_overflow_area: scrolls_overflow_area,
});
// When there is a new layer, the transforms and origin

View file

@ -1756,6 +1756,17 @@ impl Flow for BlockFlow {
}
if self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) {
// `overflow: auto` and `overflow: scroll` force creation of layers, since we can only
// scroll layers.
match (self.fragment.style().get_box().overflow_x,
self.fragment.style().get_box().overflow_y.0) {
(overflow_x::T::auto, _) | (overflow_x::T::scroll, _) |
(_, overflow_x::T::auto) | (_, overflow_x::T::scroll) => {
self.base.flags.insert(NEEDS_LAYER);
}
_ => {}
}
let position_start = self.base.position.start.to_physical(self.base.writing_mode,
container_size);
@ -1892,8 +1903,10 @@ impl Flow for BlockFlow {
.absolute_position_info
.relative_containing_block_mode,
CoordinateSystem::Own);
let clip = self.fragment.clipping_region_for_children(&clip_in_child_coordinate_system,
&stacking_relative_border_box);
let clip = self.fragment.clipping_region_for_children(
&clip_in_child_coordinate_system,
&stacking_relative_border_box,
self.base.flags.contains(IS_ABSOLUTELY_POSITIONED));
// Process children.
for kid in self.base.child_iter() {

View file

@ -62,6 +62,11 @@ use util::geometry::{Au, ZERO_POINT};
use util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize, WritingMode};
use util::opts;
/// The fake fragment ID we use to indicate the inner display list for `overflow: scroll`.
///
/// FIXME(pcwalton): This is pretty ugly. Consider modifying `LayerId` somehow.
const FAKE_FRAGMENT_ID_FOR_OVERFLOW_SCROLL: u32 = 1000000;
/// A possible `PaintLayer` for an stacking context
pub enum StackingContextLayer {
Existing(PaintLayer),
@ -208,10 +213,11 @@ pub trait FragmentDisplayListBuilding {
offset: Point2D<Au>,
layout_context: &LayoutContext);
/// Returns the appropriate clipping region for descendants of this flow.
/// Returns the appropriate clipping region for descendants of this fragment.
fn clipping_region_for_children(&self,
current_clip: &ClippingRegion,
stacking_relative_border_box: &Rect<Au>)
stacking_relative_border_box: &Rect<Au>,
is_absolutely_positioned: bool)
-> ClippingRegion;
/// Calculates the clipping rectangle for a fragment, taking the `clip` property into account
@ -253,7 +259,8 @@ pub trait FragmentDisplayListBuilding {
base_flow: &BaseFlow,
display_list: Box<DisplayList>,
layout_context: &LayoutContext,
layer: StackingContextLayer)
layer: StackingContextLayer,
mode: StackingContextCreationMode)
-> Arc<StackingContext>;
}
@ -1129,17 +1136,37 @@ impl FragmentDisplayListBuilding for Fragment {
base_flow: &BaseFlow,
display_list: Box<DisplayList>,
layout_context: &LayoutContext,
layer: StackingContextLayer)
layer: StackingContextLayer,
mode: StackingContextCreationMode)
-> Arc<StackingContext> {
let border_box = self.stacking_relative_border_box(&base_flow.stacking_relative_position,
&base_flow.absolute_position_info
.relative_containing_block_size,
base_flow.absolute_position_info
.relative_containing_block_mode,
CoordinateSystem::Parent);
// FIXME(pcwalton): Is this vertical-writing-direction-safe?
let margin = self.margin.to_physical(base_flow.writing_mode);
let border_box = match mode {
StackingContextCreationMode::Normal |
StackingContextCreationMode::OuterScrollWrapper => {
self.stacking_relative_border_box(&base_flow.stacking_relative_position,
&base_flow.absolute_position_info
.relative_containing_block_size,
base_flow.absolute_position_info
.relative_containing_block_mode,
CoordinateSystem::Parent)
}
StackingContextCreationMode::InnerScrollWrapper => {
Rect::new(ZERO_POINT, base_flow.overflow.size)
}
};
let overflow = match mode {
StackingContextCreationMode::Normal => {
base_flow.overflow.translate(&-Point2D::new(margin.left, Au(0)))
}
StackingContextCreationMode::InnerScrollWrapper |
StackingContextCreationMode::OuterScrollWrapper => {
Rect::new(ZERO_POINT, border_box.size)
}
};
let mut transform = Matrix4::identity();
if let Some(ref operations) = self.style().get_effects().transform.0 {
let transform_origin = self.style().get_effects().transform_origin;
let transform_origin =
@ -1213,10 +1240,6 @@ impl FragmentDisplayListBuilding for Fragment {
}
};
// FIXME(pcwalton): Is this vertical-writing-direction-safe?
let margin = self.margin.to_physical(base_flow.writing_mode);
let overflow = base_flow.overflow.translate(&-Point2D::new(margin.left, Au(0)));
// Create the filter pipeline.
let effects = self.style().get_effects();
let mut filters = effects.filter.clone();
@ -1247,7 +1270,10 @@ impl FragmentDisplayListBuilding for Fragment {
}
}
let scrolls_overflow_area = mode == StackingContextCreationMode::OuterScrollWrapper;
let transform_style = self.style().get_used_transform_style();
let establishes_3d_context = scrolls_overflow_area ||
transform_style == transform_style::T::flat;
Arc::new(StackingContext::new(display_list,
&border_box,
@ -1258,7 +1284,8 @@ impl FragmentDisplayListBuilding for Fragment {
layer,
transform,
perspective,
transform_style == transform_style::T::flat))
establishes_3d_context,
scrolls_overflow_area))
}
#[inline(never)]
@ -1284,7 +1311,8 @@ impl FragmentDisplayListBuilding for Fragment {
fn clipping_region_for_children(&self,
current_clip: &ClippingRegion,
stacking_relative_border_box: &Rect<Au>)
stacking_relative_border_box: &Rect<Au>,
is_absolutely_positioned: bool)
-> ClippingRegion {
// Don't clip if we're text.
if self.is_scanned_text_fragment() {
@ -1297,12 +1325,14 @@ impl FragmentDisplayListBuilding for Fragment {
// Clip according to the values of `overflow-x` and `overflow-y`.
//
// TODO(pcwalton): Support scrolling.
// TODO(pcwalton): Support scrolling of non-absolutely-positioned elements.
// FIXME(pcwalton): This may be more complex than it needs to be, since it seems to be
// impossible with the computed value rules as they are to have `overflow-x: visible` with
// `overflow-y: <scrolling>` or vice versa!
match self.style.get_box().overflow_x {
overflow_x::T::hidden | overflow_x::T::auto | overflow_x::T::scroll => {
match (self.style.get_box().overflow_x, is_absolutely_positioned) {
(overflow_x::T::hidden, _) |
(overflow_x::T::auto, false) |
(overflow_x::T::scroll, false) => {
let mut bounds = current_clip.bounding_rect();
let max_x = cmp::min(bounds.max_x(), stacking_relative_border_box.max_x());
bounds.origin.x = cmp::max(bounds.origin.x, stacking_relative_border_box.origin.x);
@ -1311,8 +1341,10 @@ impl FragmentDisplayListBuilding for Fragment {
}
_ => {}
}
match self.style.get_box().overflow_y.0 {
overflow_x::T::hidden | overflow_x::T::auto | overflow_x::T::scroll => {
match (self.style.get_box().overflow_y.0, is_absolutely_positioned) {
(overflow_x::T::hidden, _) |
(overflow_x::T::auto, false) |
(overflow_x::T::scroll, false) => {
let mut bounds = current_clip.bounding_rect();
let max_y = cmp::min(bounds.max_y(), stacking_relative_border_box.max_y());
bounds.origin.y = cmp::max(bounds.origin.y, stacking_relative_border_box.origin.y);
@ -1527,17 +1559,21 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
let paint_layer = PaintLayer::new(self.layer_id(0), color::transparent(), scroll_policy);
let layer = StackingContextLayer::Existing(paint_layer);
let stacking_context = self.fragment.create_stacking_context(&self.base,
display_list,
layout_context,
layer);
let stacking_context = self.fragment.create_stacking_context(
&self.base,
display_list,
layout_context,
layer,
StackingContextCreationMode::Normal);
DisplayListBuildingResult::StackingContext(stacking_context)
} else {
DisplayListBuildingResult::StackingContext(
self.fragment.create_stacking_context(&self.base,
display_list,
layout_context,
StackingContextLayer::IfCanvas(self.layer_id(0))))
self.fragment.create_stacking_context(
&self.base,
display_list,
layout_context,
StackingContextLayer::IfCanvas(self.layer_id(0)),
StackingContextCreationMode::Normal))
}
} else {
match self.fragment.style.get_box().position {
@ -1560,19 +1596,57 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
mut display_list: Box<DisplayList>,
layout_context: &LayoutContext,
border_painting_mode: BorderPaintingMode) {
self.build_display_list_for_block_base(&mut *display_list,
layout_context,
border_painting_mode,
BackgroundAndBorderLevel::RootOfStackingContext);
// If `overflow: scroll` is in effect, we add this fragment's display items to a new
// stacking context.
let outer_display_list_for_overflow_scroll =
match (self.fragment.style().get_box().overflow_x,
self.fragment.style().get_box().overflow_y.0) {
(overflow_x::T::auto, _) |
(overflow_x::T::scroll, _) |
(_, overflow_x::T::auto) |
(_, overflow_x::T::scroll) => {
// Create a separate display list for our own fragment.
let mut outer_display_list_for_overflow_scroll = box DisplayList::new();
let clip = self.base.clip.translate(&-self.base.stacking_relative_position);
self.fragment.build_display_list(
&mut outer_display_list_for_overflow_scroll,
layout_context,
&self.base.stacking_relative_position,
&self.base.absolute_position_info.relative_containing_block_size,
self.base.absolute_position_info.relative_containing_block_mode,
border_painting_mode,
BackgroundAndBorderLevel::RootOfStackingContext,
&clip,
&self.base.stacking_relative_position_of_display_port);
// Add the fragments of our children to the display list we'll use for the inner
// stacking context.
for kid in self.base.children.iter_mut() {
flow::mut_base(kid).display_list_building_result.add_to(&mut *display_list);
}
Some(outer_display_list_for_overflow_scroll)
}
_ => {
self.build_display_list_for_block_base(
&mut *display_list,
layout_context,
border_painting_mode,
BackgroundAndBorderLevel::RootOfStackingContext);
None
}
};
if !self.will_get_layer() {
// We didn't need a layer.
self.base.display_list_building_result =
DisplayListBuildingResult::StackingContext(
self.fragment.create_stacking_context(&self.base,
display_list,
layout_context,
StackingContextLayer::IfCanvas(self.layer_id(0))));
self.fragment.create_stacking_context(
&self.base,
display_list,
layout_context,
StackingContextLayer::IfCanvas(self.layer_id(0)),
StackingContextCreationMode::Normal));
return
}
@ -1583,13 +1657,44 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
ScrollPolicy::Scrollable
};
let paint_layer = PaintLayer::new(self.layer_id(0), color::transparent(), scroll_policy);
let stacking_context = self.fragment.create_stacking_context(&self.base,
display_list,
layout_context,
StackingContextLayer::Existing(paint_layer));
let stacking_context_creation_mode = if outer_display_list_for_overflow_scroll.is_some() {
StackingContextCreationMode::InnerScrollWrapper
} else {
StackingContextCreationMode::Normal
};
let layer_id = if outer_display_list_for_overflow_scroll.is_some() {
self.layer_id(FAKE_FRAGMENT_ID_FOR_OVERFLOW_SCROLL)
} else {
self.layer_id(0)
};
let paint_layer = PaintLayer::new(layer_id, color::transparent(), scroll_policy);
let stacking_context = self.fragment.create_stacking_context(
&self.base,
display_list,
layout_context,
StackingContextLayer::Existing(paint_layer),
stacking_context_creation_mode);
let outermost_stacking_context = match outer_display_list_for_overflow_scroll {
Some(mut outer_display_list_for_overflow_scroll) => {
outer_display_list_for_overflow_scroll.children.push_back(stacking_context);
let paint_layer = PaintLayer::new(self.layer_id(0),
color::transparent(),
scroll_policy);
self.fragment.create_stacking_context(
&self.base,
outer_display_list_for_overflow_scroll,
layout_context,
StackingContextLayer::Existing(paint_layer),
StackingContextCreationMode::OuterScrollWrapper)
}
None => stacking_context,
};
self.base.display_list_building_result =
DisplayListBuildingResult::StackingContext(stacking_context)
DisplayListBuildingResult::StackingContext(outermost_stacking_context)
}
fn build_display_list_for_floating_block(&mut self,
@ -1604,10 +1709,12 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
self.base.display_list_building_result = if self.fragment.establishes_stacking_context() {
DisplayListBuildingResult::StackingContext(
self.fragment.create_stacking_context(&self.base,
display_list,
layout_context,
StackingContextLayer::IfCanvas(self.layer_id(0))))
self.fragment.create_stacking_context(
&self.base,
display_list,
layout_context,
StackingContextLayer::IfCanvas(self.layer_id(0)),
StackingContextCreationMode::Normal))
} else {
DisplayListBuildingResult::Normal(display_list)
}
@ -1702,10 +1809,12 @@ impl InlineFlowDisplayListBuilding for InlineFlow {
self.base.display_list_building_result = if has_stacking_context {
DisplayListBuildingResult::StackingContext(
self.fragments.fragments[0].create_stacking_context(&self.base,
display_list,
layout_context,
StackingContextLayer::IfCanvas(self.layer_id(0))))
self.fragments.fragments[0].create_stacking_context(
&self.base,
display_list,
layout_context,
StackingContextLayer::IfCanvas(self.layer_id(0)),
StackingContextCreationMode::Normal))
} else {
DisplayListBuildingResult::Normal(display_list)
};
@ -1895,3 +2004,10 @@ pub enum BorderPaintingMode<'a> {
Hidden,
}
#[derive(Copy, Clone, PartialEq)]
pub enum StackingContextCreationMode {
Normal,
OuterScrollWrapper,
InnerScrollWrapper,
}

View file

@ -1627,7 +1627,8 @@ impl Flow for InlineFlow {
.relative_containing_block_mode,
CoordinateSystem::Parent);
let clip = fragment.clipping_region_for_children(&self.base.clip,
&stacking_relative_border_box);
&stacking_relative_border_box,
false);
match fragment.specific {
SpecificFragmentInfo::InlineBlock(ref mut info) => {
flow::mut_base(&mut *info.flow_ref).clip = clip;

View file

@ -1046,7 +1046,8 @@ impl LayoutTask {
Some(paint_layer),
Matrix4::identity(),
Matrix4::identity(),
true));
true,
false));
if opts::get().dump_display_list {
println!("#### start printing display list.");

View file

@ -88,6 +88,8 @@ pub struct LayerProperties {
pub perspective: Matrix4,
/// Whether this layer establishes a new 3d rendering context.
pub establishes_3d_context: bool,
/// Whether this layer scrolls its overflow area.
pub scrolls_overflow_area: bool,
}
/// The interface used by the painter to acquire draw targets for each paint frame and