mirror of
https://github.com/servo/servo.git
synced 2025-08-10 07:55:33 +01:00
Make layout_2020 be layout_2013
This commit is contained in:
parent
87e7e3d429
commit
4846d76e82
49 changed files with 34595 additions and 22 deletions
336
components/layout_2020/display_list/background.rs
Normal file
336
components/layout_2020/display_list/background.rs
Normal file
|
@ -0,0 +1,336 @@
|
|||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::display_list::border;
|
||||
use app_units::Au;
|
||||
use euclid::{Point2D, Rect, SideOffsets2D, Size2D};
|
||||
use style::computed_values::background_attachment::single_value::T as BackgroundAttachment;
|
||||
use style::computed_values::background_clip::single_value::T as BackgroundClip;
|
||||
use style::computed_values::background_origin::single_value::T as BackgroundOrigin;
|
||||
use style::properties::style_structs::Background;
|
||||
use style::values::computed::{BackgroundSize, NonNegativeLengthPercentageOrAuto};
|
||||
use style::values::specified::background::BackgroundRepeatKeyword;
|
||||
use webrender_api::BorderRadius;
|
||||
|
||||
/// Placment information for both image and gradient backgrounds.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct BackgroundPlacement {
|
||||
/// Rendering bounds. The background will start in the uppper-left corner
|
||||
/// and fill the whole area.
|
||||
pub bounds: Rect<Au>,
|
||||
/// Background tile size. Some backgrounds are repeated. These are the
|
||||
/// dimensions of a single image of the background.
|
||||
pub tile_size: Size2D<Au>,
|
||||
/// Spacing between tiles. Some backgrounds are not repeated seamless
|
||||
/// but have seams between them like tiles in real life.
|
||||
pub tile_spacing: Size2D<Au>,
|
||||
/// A clip area. While the background is rendered according to all the
|
||||
/// measures above it is only shown within these bounds.
|
||||
pub clip_rect: Rect<Au>,
|
||||
/// Rounded corners for the clip_rect.
|
||||
pub clip_radii: BorderRadius,
|
||||
/// Whether or not the background is fixed to the viewport.
|
||||
pub fixed: bool,
|
||||
}
|
||||
|
||||
/// Access element at index modulo the array length.
|
||||
///
|
||||
/// Obviously it does not work with empty arrays.
|
||||
///
|
||||
/// This is used for multiple layered background images.
|
||||
/// See: https://drafts.csswg.org/css-backgrounds-3/#layering
|
||||
pub fn get_cyclic<T>(arr: &[T], index: usize) -> &T {
|
||||
&arr[index % arr.len()]
|
||||
}
|
||||
|
||||
/// For a given area and an image compute how big the
|
||||
/// image should be displayed on the background.
|
||||
fn compute_background_image_size(
|
||||
bg_size: BackgroundSize,
|
||||
bounds_size: Size2D<Au>,
|
||||
intrinsic_size: Option<Size2D<Au>>,
|
||||
) -> Size2D<Au> {
|
||||
match intrinsic_size {
|
||||
None => match bg_size {
|
||||
BackgroundSize::Cover | BackgroundSize::Contain => bounds_size,
|
||||
BackgroundSize::ExplicitSize { width, height } => Size2D::new(
|
||||
width
|
||||
.to_used_value(bounds_size.width)
|
||||
.unwrap_or(bounds_size.width),
|
||||
height
|
||||
.to_used_value(bounds_size.height)
|
||||
.unwrap_or(bounds_size.height),
|
||||
),
|
||||
},
|
||||
Some(own_size) => {
|
||||
// If `image_aspect_ratio` < `bounds_aspect_ratio`, the image is tall; otherwise, it is
|
||||
// wide.
|
||||
let image_aspect_ratio = own_size.width.to_f32_px() / own_size.height.to_f32_px();
|
||||
let bounds_aspect_ratio =
|
||||
bounds_size.width.to_f32_px() / bounds_size.height.to_f32_px();
|
||||
match (bg_size, image_aspect_ratio < bounds_aspect_ratio) {
|
||||
(BackgroundSize::Contain, false) | (BackgroundSize::Cover, true) => Size2D::new(
|
||||
bounds_size.width,
|
||||
bounds_size.width.scale_by(image_aspect_ratio.recip()),
|
||||
),
|
||||
(BackgroundSize::Contain, true) | (BackgroundSize::Cover, false) => Size2D::new(
|
||||
bounds_size.height.scale_by(image_aspect_ratio),
|
||||
bounds_size.height,
|
||||
),
|
||||
(
|
||||
BackgroundSize::ExplicitSize {
|
||||
width,
|
||||
height: NonNegativeLengthPercentageOrAuto::Auto,
|
||||
},
|
||||
_,
|
||||
) => {
|
||||
let width = width
|
||||
.to_used_value(bounds_size.width)
|
||||
.unwrap_or(own_size.width);
|
||||
Size2D::new(width, width.scale_by(image_aspect_ratio.recip()))
|
||||
},
|
||||
(
|
||||
BackgroundSize::ExplicitSize {
|
||||
width: NonNegativeLengthPercentageOrAuto::Auto,
|
||||
height,
|
||||
},
|
||||
_,
|
||||
) => {
|
||||
let height = height
|
||||
.to_used_value(bounds_size.height)
|
||||
.unwrap_or(own_size.height);
|
||||
Size2D::new(height.scale_by(image_aspect_ratio), height)
|
||||
},
|
||||
(BackgroundSize::ExplicitSize { width, height }, _) => Size2D::new(
|
||||
width
|
||||
.to_used_value(bounds_size.width)
|
||||
.unwrap_or(own_size.width),
|
||||
height
|
||||
.to_used_value(bounds_size.height)
|
||||
.unwrap_or(own_size.height),
|
||||
),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute a rounded clip rect for the background.
|
||||
pub fn clip(
|
||||
bg_clip: BackgroundClip,
|
||||
absolute_bounds: Rect<Au>,
|
||||
border: SideOffsets2D<Au>,
|
||||
border_padding: SideOffsets2D<Au>,
|
||||
border_radii: BorderRadius,
|
||||
) -> (Rect<Au>, BorderRadius) {
|
||||
match bg_clip {
|
||||
BackgroundClip::BorderBox => (absolute_bounds, border_radii),
|
||||
BackgroundClip::PaddingBox => (
|
||||
absolute_bounds.inner_rect(border),
|
||||
border::inner_radii(border_radii, border),
|
||||
),
|
||||
BackgroundClip::ContentBox => (
|
||||
absolute_bounds.inner_rect(border_padding),
|
||||
border::inner_radii(border_radii, border_padding),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines where to place an element background image or gradient.
|
||||
///
|
||||
/// Photos have their resolution as intrinsic size while gradients have
|
||||
/// no intrinsic size.
|
||||
pub fn placement(
|
||||
bg: &Background,
|
||||
viewport_size: Size2D<Au>,
|
||||
absolute_bounds: Rect<Au>,
|
||||
intrinsic_size: Option<Size2D<Au>>,
|
||||
border: SideOffsets2D<Au>,
|
||||
border_padding: SideOffsets2D<Au>,
|
||||
border_radii: BorderRadius,
|
||||
index: usize,
|
||||
) -> BackgroundPlacement {
|
||||
let bg_attachment = *get_cyclic(&bg.background_attachment.0, index);
|
||||
let bg_clip = *get_cyclic(&bg.background_clip.0, index);
|
||||
let bg_origin = *get_cyclic(&bg.background_origin.0, index);
|
||||
let bg_position_x = get_cyclic(&bg.background_position_x.0, index);
|
||||
let bg_position_y = get_cyclic(&bg.background_position_y.0, index);
|
||||
let bg_repeat = get_cyclic(&bg.background_repeat.0, index);
|
||||
let bg_size = *get_cyclic(&bg.background_size.0, index);
|
||||
|
||||
let (clip_rect, clip_radii) = clip(
|
||||
bg_clip,
|
||||
absolute_bounds,
|
||||
border,
|
||||
border_padding,
|
||||
border_radii,
|
||||
);
|
||||
|
||||
let mut fixed = false;
|
||||
let mut bounds = match bg_attachment {
|
||||
BackgroundAttachment::Scroll => match bg_origin {
|
||||
BackgroundOrigin::BorderBox => absolute_bounds,
|
||||
BackgroundOrigin::PaddingBox => absolute_bounds.inner_rect(border),
|
||||
BackgroundOrigin::ContentBox => absolute_bounds.inner_rect(border_padding),
|
||||
},
|
||||
BackgroundAttachment::Fixed => {
|
||||
fixed = true;
|
||||
Rect::new(Point2D::origin(), viewport_size)
|
||||
},
|
||||
};
|
||||
|
||||
let mut tile_size = compute_background_image_size(bg_size, bounds.size, intrinsic_size);
|
||||
|
||||
let mut tile_spacing = Size2D::zero();
|
||||
let own_position = bounds.size - tile_size;
|
||||
let pos_x = bg_position_x.to_used_value(own_position.width);
|
||||
let pos_y = bg_position_y.to_used_value(own_position.height);
|
||||
tile_image_axis(
|
||||
bg_repeat.0,
|
||||
&mut bounds.origin.x,
|
||||
&mut bounds.size.width,
|
||||
&mut tile_size.width,
|
||||
&mut tile_spacing.width,
|
||||
pos_x,
|
||||
clip_rect.origin.x,
|
||||
clip_rect.size.width,
|
||||
);
|
||||
tile_image_axis(
|
||||
bg_repeat.1,
|
||||
&mut bounds.origin.y,
|
||||
&mut bounds.size.height,
|
||||
&mut tile_size.height,
|
||||
&mut tile_spacing.height,
|
||||
pos_y,
|
||||
clip_rect.origin.y,
|
||||
clip_rect.size.height,
|
||||
);
|
||||
|
||||
BackgroundPlacement {
|
||||
bounds,
|
||||
tile_size,
|
||||
tile_spacing,
|
||||
clip_rect,
|
||||
clip_radii,
|
||||
fixed,
|
||||
}
|
||||
}
|
||||
|
||||
fn tile_image_round(
|
||||
position: &mut Au,
|
||||
size: &mut Au,
|
||||
absolute_anchor_origin: Au,
|
||||
image_size: &mut Au,
|
||||
) {
|
||||
if *size == Au(0) || *image_size == Au(0) {
|
||||
*position = Au(0);
|
||||
*size = Au(0);
|
||||
return;
|
||||
}
|
||||
|
||||
let number_of_tiles = (size.to_f32_px() / image_size.to_f32_px()).round().max(1.0);
|
||||
*image_size = *size / (number_of_tiles as i32);
|
||||
tile_image(position, size, absolute_anchor_origin, *image_size);
|
||||
}
|
||||
|
||||
fn tile_image_spaced(
|
||||
position: &mut Au,
|
||||
size: &mut Au,
|
||||
tile_spacing: &mut Au,
|
||||
absolute_anchor_origin: Au,
|
||||
image_size: Au,
|
||||
) {
|
||||
if *size == Au(0) || image_size == Au(0) {
|
||||
*position = Au(0);
|
||||
*size = Au(0);
|
||||
*tile_spacing = Au(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Per the spec, if the space available is not enough for two images, just tile as
|
||||
// normal but only display a single tile.
|
||||
if image_size * 2 >= *size {
|
||||
tile_image(position, size, absolute_anchor_origin, image_size);
|
||||
*tile_spacing = Au(0);
|
||||
*size = image_size;
|
||||
return;
|
||||
}
|
||||
|
||||
// Take the box size, remove room for two tiles on the edges, and then calculate how many
|
||||
// other tiles fit in between them.
|
||||
let size_remaining = *size - (image_size * 2);
|
||||
let num_middle_tiles = (size_remaining.to_f32_px() / image_size.to_f32_px()).floor() as i32;
|
||||
|
||||
// Allocate the remaining space as padding between tiles. background-position is ignored
|
||||
// as per the spec, so the position is just the box origin. We are also ignoring
|
||||
// background-attachment here, which seems unspecced when combined with
|
||||
// background-repeat: space.
|
||||
let space_for_middle_tiles = image_size * num_middle_tiles;
|
||||
*tile_spacing = (size_remaining - space_for_middle_tiles) / (num_middle_tiles + 1);
|
||||
}
|
||||
|
||||
/// Tile an image
|
||||
fn tile_image(position: &mut Au, size: &mut Au, absolute_anchor_origin: Au, image_size: Au) {
|
||||
// Avoid division by zero below!
|
||||
// Images with a zero width or height are not displayed.
|
||||
// Therefore the positions do not matter and can be left unchanged.
|
||||
// NOTE: A possible optimization is not to build
|
||||
// display items in this case at all.
|
||||
if image_size == Au(0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let delta_pixels = absolute_anchor_origin - *position;
|
||||
let image_size_px = image_size.to_f32_px();
|
||||
let tile_count = ((delta_pixels.to_f32_px() + image_size_px - 1.0) / image_size_px).floor();
|
||||
let offset = image_size * (tile_count as i32);
|
||||
let new_position = absolute_anchor_origin - offset;
|
||||
*size = *position - new_position + *size;
|
||||
*position = new_position;
|
||||
}
|
||||
|
||||
/// For either the x or the y axis ajust various values to account for tiling.
|
||||
///
|
||||
/// This is done separately for both axes because the repeat keywords may differ.
|
||||
fn tile_image_axis(
|
||||
repeat: BackgroundRepeatKeyword,
|
||||
position: &mut Au,
|
||||
size: &mut Au,
|
||||
tile_size: &mut Au,
|
||||
tile_spacing: &mut Au,
|
||||
offset: Au,
|
||||
clip_origin: Au,
|
||||
clip_size: Au,
|
||||
) {
|
||||
let absolute_anchor_origin = *position + offset;
|
||||
match repeat {
|
||||
BackgroundRepeatKeyword::NoRepeat => {
|
||||
*position += offset;
|
||||
*size = *tile_size;
|
||||
},
|
||||
BackgroundRepeatKeyword::Repeat => {
|
||||
*position = clip_origin;
|
||||
*size = clip_size;
|
||||
tile_image(position, size, absolute_anchor_origin, *tile_size);
|
||||
},
|
||||
BackgroundRepeatKeyword::Space => {
|
||||
tile_image_spaced(
|
||||
position,
|
||||
size,
|
||||
tile_spacing,
|
||||
absolute_anchor_origin,
|
||||
*tile_size,
|
||||
);
|
||||
let combined_tile_size = *tile_size + *tile_spacing;
|
||||
*position = clip_origin;
|
||||
*size = clip_size;
|
||||
tile_image(position, size, absolute_anchor_origin, combined_tile_size);
|
||||
},
|
||||
BackgroundRepeatKeyword::Round => {
|
||||
tile_image_round(position, size, absolute_anchor_origin, tile_size);
|
||||
*position = clip_origin;
|
||||
*size = clip_size;
|
||||
tile_image(position, size, absolute_anchor_origin, *tile_size);
|
||||
},
|
||||
}
|
||||
}
|
199
components/layout_2020/display_list/border.rs
Normal file
199
components/layout_2020/display_list/border.rs
Normal file
|
@ -0,0 +1,199 @@
|
|||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::display_list::ToLayout;
|
||||
use app_units::Au;
|
||||
use euclid::{Rect, SideOffsets2D, Size2D};
|
||||
use style::computed_values::border_image_outset::T as BorderImageOutset;
|
||||
use style::properties::style_structs::Border;
|
||||
use style::values::computed::NumberOrPercentage;
|
||||
use style::values::computed::{BorderCornerRadius, BorderImageWidth};
|
||||
use style::values::computed::{BorderImageSideWidth, NonNegativeLengthOrNumber};
|
||||
use style::values::generics::rect::Rect as StyleRect;
|
||||
use style::values::generics::NonNegative;
|
||||
use webrender_api::units::{LayoutSideOffsets, LayoutSize};
|
||||
use webrender_api::{BorderRadius, BorderSide, BorderStyle, ColorF, NormalBorder};
|
||||
|
||||
/// Computes a border radius size against the containing size.
|
||||
///
|
||||
/// Note that percentages in `border-radius` are resolved against the relevant
|
||||
/// box dimension instead of only against the width per [1]:
|
||||
///
|
||||
/// > Percentages: Refer to corresponding dimension of the border box.
|
||||
///
|
||||
/// [1]: https://drafts.csswg.org/css-backgrounds-3/#border-radius
|
||||
fn corner_radius(radius: BorderCornerRadius, containing_size: Size2D<Au>) -> Size2D<Au> {
|
||||
let w = radius.0.width().to_used_value(containing_size.width);
|
||||
let h = radius.0.height().to_used_value(containing_size.height);
|
||||
Size2D::new(w, h)
|
||||
}
|
||||
|
||||
fn scaled_radii(radii: BorderRadius, factor: f32) -> BorderRadius {
|
||||
BorderRadius {
|
||||
top_left: radii.top_left * factor,
|
||||
top_right: radii.top_right * factor,
|
||||
bottom_left: radii.bottom_left * factor,
|
||||
bottom_right: radii.bottom_right * factor,
|
||||
}
|
||||
}
|
||||
|
||||
fn overlapping_radii(size: LayoutSize, radii: BorderRadius) -> BorderRadius {
|
||||
// No two corners' border radii may add up to more than the length of the edge
|
||||
// between them. To prevent that, all radii are scaled down uniformly.
|
||||
fn scale_factor(radius_a: f32, radius_b: f32, edge_length: f32) -> f32 {
|
||||
let required = radius_a + radius_b;
|
||||
|
||||
if required <= edge_length {
|
||||
1.0
|
||||
} else {
|
||||
edge_length / required
|
||||
}
|
||||
}
|
||||
|
||||
let top_factor = scale_factor(radii.top_left.width, radii.top_right.width, size.width);
|
||||
let bottom_factor = scale_factor(
|
||||
radii.bottom_left.width,
|
||||
radii.bottom_right.width,
|
||||
size.width,
|
||||
);
|
||||
let left_factor = scale_factor(radii.top_left.height, radii.bottom_left.height, size.height);
|
||||
let right_factor = scale_factor(
|
||||
radii.top_right.height,
|
||||
radii.bottom_right.height,
|
||||
size.height,
|
||||
);
|
||||
let min_factor = top_factor
|
||||
.min(bottom_factor)
|
||||
.min(left_factor)
|
||||
.min(right_factor);
|
||||
if min_factor < 1.0 {
|
||||
scaled_radii(radii, min_factor)
|
||||
} else {
|
||||
radii
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine the four corner radii of a border.
|
||||
///
|
||||
/// Radii may either be absolute or relative to the absolute bounds.
|
||||
/// Each corner radius has a width and a height which may differ.
|
||||
/// Lastly overlapping radii are shrank so they don't collide anymore.
|
||||
pub fn radii(abs_bounds: Rect<Au>, border_style: &Border) -> BorderRadius {
|
||||
// TODO(cgaebel): Support border radii even in the case of multiple border widths.
|
||||
// This is an extension of supporting elliptical radii. For now, all percentage
|
||||
// radii will be relative to the width.
|
||||
|
||||
overlapping_radii(
|
||||
abs_bounds.size.to_layout(),
|
||||
BorderRadius {
|
||||
top_left: corner_radius(border_style.border_top_left_radius, abs_bounds.size)
|
||||
.to_layout(),
|
||||
top_right: corner_radius(border_style.border_top_right_radius, abs_bounds.size)
|
||||
.to_layout(),
|
||||
bottom_right: corner_radius(border_style.border_bottom_right_radius, abs_bounds.size)
|
||||
.to_layout(),
|
||||
bottom_left: corner_radius(border_style.border_bottom_left_radius, abs_bounds.size)
|
||||
.to_layout(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Calculates radii for the inner side.
|
||||
///
|
||||
/// Radii usually describe the outer side of a border but for the lines to look nice
|
||||
/// the inner radii need to be smaller depending on the line width.
|
||||
///
|
||||
/// This is used to determine clipping areas.
|
||||
pub fn inner_radii(mut radii: BorderRadius, offsets: SideOffsets2D<Au>) -> BorderRadius {
|
||||
fn inner_length(x: f32, offset: Au) -> f32 {
|
||||
0.0_f32.max(x - offset.to_f32_px())
|
||||
}
|
||||
radii.top_left.width = inner_length(radii.top_left.width, offsets.left);
|
||||
radii.bottom_left.width = inner_length(radii.bottom_left.width, offsets.left);
|
||||
|
||||
radii.top_right.width = inner_length(radii.top_right.width, offsets.right);
|
||||
radii.bottom_right.width = inner_length(radii.bottom_right.width, offsets.right);
|
||||
|
||||
radii.top_left.height = inner_length(radii.top_left.height, offsets.top);
|
||||
radii.top_right.height = inner_length(radii.top_right.height, offsets.top);
|
||||
|
||||
radii.bottom_left.height = inner_length(radii.bottom_left.height, offsets.bottom);
|
||||
radii.bottom_right.height = inner_length(radii.bottom_right.height, offsets.bottom);
|
||||
radii
|
||||
}
|
||||
|
||||
/// Creates a four-sided border with square corners and uniform color and width.
|
||||
pub fn simple(color: ColorF, style: BorderStyle) -> NormalBorder {
|
||||
let side = BorderSide { color, style };
|
||||
NormalBorder {
|
||||
left: side,
|
||||
right: side,
|
||||
top: side,
|
||||
bottom: side,
|
||||
radius: BorderRadius::zero(),
|
||||
do_aa: true,
|
||||
}
|
||||
}
|
||||
|
||||
fn side_image_outset(outset: NonNegativeLengthOrNumber, border_width: Au) -> Au {
|
||||
match outset {
|
||||
NonNegativeLengthOrNumber::Length(length) => length.into(),
|
||||
NonNegativeLengthOrNumber::Number(factor) => border_width.scale_by(factor.0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the additional border-image area.
|
||||
pub fn image_outset(outset: BorderImageOutset, border: SideOffsets2D<Au>) -> SideOffsets2D<Au> {
|
||||
SideOffsets2D::new(
|
||||
side_image_outset(outset.0, border.top),
|
||||
side_image_outset(outset.1, border.right),
|
||||
side_image_outset(outset.2, border.bottom),
|
||||
side_image_outset(outset.3, border.left),
|
||||
)
|
||||
}
|
||||
|
||||
fn side_image_width(
|
||||
border_image_width: BorderImageSideWidth,
|
||||
border_width: f32,
|
||||
total_length: Au,
|
||||
) -> f32 {
|
||||
match border_image_width {
|
||||
BorderImageSideWidth::LengthPercentage(v) => v.to_used_value(total_length).to_f32_px(),
|
||||
BorderImageSideWidth::Number(x) => border_width * x.0,
|
||||
BorderImageSideWidth::Auto => border_width,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn image_width(
|
||||
width: &BorderImageWidth,
|
||||
border: LayoutSideOffsets,
|
||||
border_area: Size2D<Au>,
|
||||
) -> LayoutSideOffsets {
|
||||
LayoutSideOffsets::new(
|
||||
side_image_width(width.0, border.top, border_area.height),
|
||||
side_image_width(width.1, border.right, border_area.width),
|
||||
side_image_width(width.2, border.bottom, border_area.height),
|
||||
side_image_width(width.3, border.left, border_area.width),
|
||||
)
|
||||
}
|
||||
|
||||
fn resolve_percentage(value: NonNegative<NumberOrPercentage>, length: i32) -> i32 {
|
||||
match value.0 {
|
||||
NumberOrPercentage::Percentage(p) => (p.0 * length as f32).round() as i32,
|
||||
NumberOrPercentage::Number(n) => n.round() as i32,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn image_slice(
|
||||
border_image_slice: &StyleRect<NonNegative<NumberOrPercentage>>,
|
||||
width: i32,
|
||||
height: i32,
|
||||
) -> SideOffsets2D<i32> {
|
||||
SideOffsets2D::new(
|
||||
resolve_percentage(border_image_slice.0, height),
|
||||
resolve_percentage(border_image_slice.1, width),
|
||||
resolve_percentage(border_image_slice.2, height),
|
||||
resolve_percentage(border_image_slice.3, width),
|
||||
)
|
||||
}
|
3024
components/layout_2020/display_list/builder.rs
Normal file
3024
components/layout_2020/display_list/builder.rs
Normal file
File diff suppressed because it is too large
Load diff
167
components/layout_2020/display_list/conversions.rs
Normal file
167
components/layout_2020/display_list/conversions.rs
Normal file
|
@ -0,0 +1,167 @@
|
|||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use app_units::Au;
|
||||
use euclid::{Point2D, Rect, SideOffsets2D, Size2D, Vector2D};
|
||||
use style::computed_values::image_rendering::T as ImageRendering;
|
||||
use style::computed_values::mix_blend_mode::T as MixBlendMode;
|
||||
use style::computed_values::transform_style::T as TransformStyle;
|
||||
use style::values::computed::{BorderStyle, Filter};
|
||||
use style::values::specified::border::BorderImageRepeatKeyword;
|
||||
use style::values::RGBA;
|
||||
use webrender_api as wr;
|
||||
|
||||
pub trait ToLayout {
|
||||
type Type;
|
||||
fn to_layout(&self) -> Self::Type;
|
||||
}
|
||||
|
||||
impl ToLayout for BorderStyle {
|
||||
type Type = wr::BorderStyle;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
match *self {
|
||||
BorderStyle::None => wr::BorderStyle::None,
|
||||
BorderStyle::Solid => wr::BorderStyle::Solid,
|
||||
BorderStyle::Double => wr::BorderStyle::Double,
|
||||
BorderStyle::Dotted => wr::BorderStyle::Dotted,
|
||||
BorderStyle::Dashed => wr::BorderStyle::Dashed,
|
||||
BorderStyle::Hidden => wr::BorderStyle::Hidden,
|
||||
BorderStyle::Groove => wr::BorderStyle::Groove,
|
||||
BorderStyle::Ridge => wr::BorderStyle::Ridge,
|
||||
BorderStyle::Inset => wr::BorderStyle::Inset,
|
||||
BorderStyle::Outset => wr::BorderStyle::Outset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for Filter {
|
||||
type Type = wr::FilterOp;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
match *self {
|
||||
Filter::Blur(radius) => wr::FilterOp::Blur(radius.px()),
|
||||
Filter::Brightness(amount) => wr::FilterOp::Brightness(amount.0),
|
||||
Filter::Contrast(amount) => wr::FilterOp::Contrast(amount.0),
|
||||
Filter::Grayscale(amount) => wr::FilterOp::Grayscale(amount.0),
|
||||
Filter::HueRotate(angle) => wr::FilterOp::HueRotate(angle.radians()),
|
||||
Filter::Invert(amount) => wr::FilterOp::Invert(amount.0),
|
||||
Filter::Opacity(amount) => wr::FilterOp::Opacity(amount.0.into(), amount.0),
|
||||
Filter::Saturate(amount) => wr::FilterOp::Saturate(amount.0),
|
||||
Filter::Sepia(amount) => wr::FilterOp::Sepia(amount.0),
|
||||
// Statically check that DropShadow is impossible.
|
||||
Filter::DropShadow(ref shadow) => match *shadow {},
|
||||
// Statically check that Url is impossible.
|
||||
Filter::Url(ref url) => match *url {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for ImageRendering {
|
||||
type Type = wr::ImageRendering;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
match *self {
|
||||
ImageRendering::Auto => wr::ImageRendering::Auto,
|
||||
ImageRendering::CrispEdges => wr::ImageRendering::CrispEdges,
|
||||
ImageRendering::Pixelated => wr::ImageRendering::Pixelated,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for MixBlendMode {
|
||||
type Type = wr::MixBlendMode;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
match *self {
|
||||
MixBlendMode::Normal => wr::MixBlendMode::Normal,
|
||||
MixBlendMode::Multiply => wr::MixBlendMode::Multiply,
|
||||
MixBlendMode::Screen => wr::MixBlendMode::Screen,
|
||||
MixBlendMode::Overlay => wr::MixBlendMode::Overlay,
|
||||
MixBlendMode::Darken => wr::MixBlendMode::Darken,
|
||||
MixBlendMode::Lighten => wr::MixBlendMode::Lighten,
|
||||
MixBlendMode::ColorDodge => wr::MixBlendMode::ColorDodge,
|
||||
MixBlendMode::ColorBurn => wr::MixBlendMode::ColorBurn,
|
||||
MixBlendMode::HardLight => wr::MixBlendMode::HardLight,
|
||||
MixBlendMode::SoftLight => wr::MixBlendMode::SoftLight,
|
||||
MixBlendMode::Difference => wr::MixBlendMode::Difference,
|
||||
MixBlendMode::Exclusion => wr::MixBlendMode::Exclusion,
|
||||
MixBlendMode::Hue => wr::MixBlendMode::Hue,
|
||||
MixBlendMode::Saturation => wr::MixBlendMode::Saturation,
|
||||
MixBlendMode::Color => wr::MixBlendMode::Color,
|
||||
MixBlendMode::Luminosity => wr::MixBlendMode::Luminosity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for TransformStyle {
|
||||
type Type = wr::TransformStyle;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
match *self {
|
||||
TransformStyle::Auto | TransformStyle::Flat => wr::TransformStyle::Flat,
|
||||
TransformStyle::Preserve3d => wr::TransformStyle::Preserve3D,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for RGBA {
|
||||
type Type = wr::ColorF;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
wr::ColorF::new(
|
||||
self.red_f32(),
|
||||
self.green_f32(),
|
||||
self.blue_f32(),
|
||||
self.alpha_f32(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for Point2D<Au> {
|
||||
type Type = wr::units::LayoutPoint;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
wr::units::LayoutPoint::new(self.x.to_f32_px(), self.y.to_f32_px())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for Rect<Au> {
|
||||
type Type = wr::units::LayoutRect;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
wr::units::LayoutRect::new(self.origin.to_layout(), self.size.to_layout())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for SideOffsets2D<Au> {
|
||||
type Type = wr::units::LayoutSideOffsets;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
wr::units::LayoutSideOffsets::new(
|
||||
self.top.to_f32_px(),
|
||||
self.right.to_f32_px(),
|
||||
self.bottom.to_f32_px(),
|
||||
self.left.to_f32_px(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for Size2D<Au> {
|
||||
type Type = wr::units::LayoutSize;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
wr::units::LayoutSize::new(self.width.to_f32_px(), self.height.to_f32_px())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for Vector2D<Au> {
|
||||
type Type = wr::units::LayoutVector2D;
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
wr::units::LayoutVector2D::new(self.x.to_f32_px(), self.y.to_f32_px())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLayout for BorderImageRepeatKeyword {
|
||||
type Type = wr::RepeatMode;
|
||||
|
||||
fn to_layout(&self) -> Self::Type {
|
||||
match *self {
|
||||
BorderImageRepeatKeyword::Stretch => wr::RepeatMode::Stretch,
|
||||
BorderImageRepeatKeyword::Repeat => wr::RepeatMode::Repeat,
|
||||
BorderImageRepeatKeyword::Round => wr::RepeatMode::Round,
|
||||
BorderImageRepeatKeyword::Space => wr::RepeatMode::Space,
|
||||
}
|
||||
}
|
||||
}
|
323
components/layout_2020/display_list/gradient.rs
Normal file
323
components/layout_2020/display_list/gradient.rs
Normal file
|
@ -0,0 +1,323 @@
|
|||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::display_list::ToLayout;
|
||||
use app_units::Au;
|
||||
use euclid::{Point2D, Size2D, Vector2D};
|
||||
use style::properties::ComputedValues;
|
||||
use style::values::computed::image::{EndingShape, LineDirection};
|
||||
use style::values::computed::{Angle, GradientItem, LengthPercentage, Percentage, Position};
|
||||
use style::values::generics::image::{Circle, ColorStop, Ellipse, ShapeExtent};
|
||||
use webrender_api::{ExtendMode, Gradient, GradientBuilder, GradientStop, RadialGradient};
|
||||
|
||||
/// A helper data structure for gradients.
|
||||
#[derive(Clone, Copy)]
|
||||
struct StopRun {
|
||||
start_offset: f32,
|
||||
end_offset: f32,
|
||||
start_index: usize,
|
||||
stop_count: usize,
|
||||
}
|
||||
|
||||
/// Determines the radius of a circle if it was not explictly provided.
|
||||
/// <https://drafts.csswg.org/css-images-3/#typedef-size>
|
||||
fn circle_size_keyword(
|
||||
keyword: ShapeExtent,
|
||||
size: &Size2D<Au>,
|
||||
center: &Point2D<Au>,
|
||||
) -> Size2D<Au> {
|
||||
let radius = match keyword {
|
||||
ShapeExtent::ClosestSide | ShapeExtent::Contain => {
|
||||
let dist = distance_to_sides(size, center, ::std::cmp::min);
|
||||
::std::cmp::min(dist.width, dist.height)
|
||||
},
|
||||
ShapeExtent::FarthestSide => {
|
||||
let dist = distance_to_sides(size, center, ::std::cmp::max);
|
||||
::std::cmp::max(dist.width, dist.height)
|
||||
},
|
||||
ShapeExtent::ClosestCorner => distance_to_corner(size, center, ::std::cmp::min),
|
||||
ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
|
||||
distance_to_corner(size, center, ::std::cmp::max)
|
||||
},
|
||||
};
|
||||
Size2D::new(radius, radius)
|
||||
}
|
||||
|
||||
/// Returns the radius for an ellipse with the same ratio as if it was matched to the sides.
|
||||
fn ellipse_radius<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Size2D<Au>
|
||||
where
|
||||
F: Fn(Au, Au) -> Au,
|
||||
{
|
||||
let dist = distance_to_sides(size, center, cmp);
|
||||
Size2D::new(
|
||||
dist.width.scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0),
|
||||
dist.height
|
||||
.scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0),
|
||||
)
|
||||
}
|
||||
|
||||
/// Determines the radius of an ellipse if it was not explictly provided.
|
||||
/// <https://drafts.csswg.org/css-images-3/#typedef-size>
|
||||
fn ellipse_size_keyword(
|
||||
keyword: ShapeExtent,
|
||||
size: &Size2D<Au>,
|
||||
center: &Point2D<Au>,
|
||||
) -> Size2D<Au> {
|
||||
match keyword {
|
||||
ShapeExtent::ClosestSide | ShapeExtent::Contain => {
|
||||
distance_to_sides(size, center, ::std::cmp::min)
|
||||
},
|
||||
ShapeExtent::FarthestSide => distance_to_sides(size, center, ::std::cmp::max),
|
||||
ShapeExtent::ClosestCorner => ellipse_radius(size, center, ::std::cmp::min),
|
||||
ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
|
||||
ellipse_radius(size, center, ::std::cmp::max)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_gradient_stops(
|
||||
style: &ComputedValues,
|
||||
gradient_items: &[GradientItem],
|
||||
total_length: Au,
|
||||
) -> GradientBuilder {
|
||||
// Determine the position of each stop per CSS-IMAGES § 3.4.
|
||||
|
||||
// Only keep the color stops, discard the color interpolation hints.
|
||||
let mut stop_items = gradient_items
|
||||
.iter()
|
||||
.filter_map(|item| match *item {
|
||||
GradientItem::SimpleColorStop(color) => Some(ColorStop {
|
||||
color,
|
||||
position: None,
|
||||
}),
|
||||
GradientItem::ComplexColorStop { color, position } => Some(ColorStop {
|
||||
color,
|
||||
position: Some(position),
|
||||
}),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert!(stop_items.len() >= 2);
|
||||
|
||||
// Run the algorithm from
|
||||
// https://drafts.csswg.org/css-images-3/#color-stop-syntax
|
||||
|
||||
// Step 1:
|
||||
// If the first color stop does not have a position, set its position to 0%.
|
||||
{
|
||||
let first = stop_items.first_mut().unwrap();
|
||||
if first.position.is_none() {
|
||||
first.position = Some(LengthPercentage::new_percent(Percentage(0.)));
|
||||
}
|
||||
}
|
||||
// If the last color stop does not have a position, set its position to 100%.
|
||||
{
|
||||
let last = stop_items.last_mut().unwrap();
|
||||
if last.position.is_none() {
|
||||
last.position = Some(LengthPercentage::new_percent(Percentage(1.0)));
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Move any stops placed before earlier stops to the
|
||||
// same position as the preceding stop.
|
||||
let mut last_stop_position = stop_items.first().unwrap().position.unwrap();
|
||||
for stop in stop_items.iter_mut().skip(1) {
|
||||
if let Some(pos) = stop.position {
|
||||
if position_to_offset(last_stop_position, total_length) >
|
||||
position_to_offset(pos, total_length)
|
||||
{
|
||||
stop.position = Some(last_stop_position);
|
||||
}
|
||||
last_stop_position = stop.position.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Evenly space stops without position.
|
||||
let mut stops = GradientBuilder::new();
|
||||
let mut stop_run = None;
|
||||
for (i, stop) in stop_items.iter().enumerate() {
|
||||
let offset = match stop.position {
|
||||
None => {
|
||||
if stop_run.is_none() {
|
||||
// Initialize a new stop run.
|
||||
// `unwrap()` here should never fail because this is the beginning of
|
||||
// a stop run, which is always bounded by a length or percentage.
|
||||
let start_offset =
|
||||
position_to_offset(stop_items[i - 1].position.unwrap(), total_length);
|
||||
// `unwrap()` here should never fail because this is the end of
|
||||
// a stop run, which is always bounded by a length or percentage.
|
||||
let (end_index, end_stop) = stop_items[(i + 1)..]
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|&(_, ref stop)| stop.position.is_some())
|
||||
.unwrap();
|
||||
let end_offset = position_to_offset(end_stop.position.unwrap(), total_length);
|
||||
stop_run = Some(StopRun {
|
||||
start_offset,
|
||||
end_offset,
|
||||
start_index: i - 1,
|
||||
stop_count: end_index,
|
||||
})
|
||||
}
|
||||
|
||||
let stop_run = stop_run.unwrap();
|
||||
let stop_run_length = stop_run.end_offset - stop_run.start_offset;
|
||||
stop_run.start_offset +
|
||||
stop_run_length * (i - stop_run.start_index) as f32 /
|
||||
((2 + stop_run.stop_count) as f32)
|
||||
},
|
||||
Some(position) => {
|
||||
stop_run = None;
|
||||
position_to_offset(position, total_length)
|
||||
},
|
||||
};
|
||||
assert!(offset.is_finite());
|
||||
stops.push(GradientStop {
|
||||
offset: offset,
|
||||
color: style.resolve_color(stop.color).to_layout(),
|
||||
})
|
||||
}
|
||||
stops
|
||||
}
|
||||
|
||||
fn extend_mode(repeating: bool) -> ExtendMode {
|
||||
if repeating {
|
||||
ExtendMode::Repeat
|
||||
} else {
|
||||
ExtendMode::Clamp
|
||||
}
|
||||
}
|
||||
/// Returns the the distance to the nearest or farthest corner depending on the comperator.
|
||||
fn distance_to_corner<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Au
|
||||
where
|
||||
F: Fn(Au, Au) -> Au,
|
||||
{
|
||||
let dist = distance_to_sides(size, center, cmp);
|
||||
Au::from_f32_px(dist.width.to_f32_px().hypot(dist.height.to_f32_px()))
|
||||
}
|
||||
|
||||
/// Returns the distance to the nearest or farthest sides depending on the comparator.
|
||||
///
|
||||
/// The first return value is horizontal distance the second vertical distance.
|
||||
fn distance_to_sides<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Size2D<Au>
|
||||
where
|
||||
F: Fn(Au, Au) -> Au,
|
||||
{
|
||||
let top_side = center.y;
|
||||
let right_side = size.width - center.x;
|
||||
let bottom_side = size.height - center.y;
|
||||
let left_side = center.x;
|
||||
Size2D::new(cmp(left_side, right_side), cmp(top_side, bottom_side))
|
||||
}
|
||||
|
||||
fn position_to_offset(position: LengthPercentage, total_length: Au) -> f32 {
|
||||
if total_length == Au(0) {
|
||||
return 0.0;
|
||||
}
|
||||
position.to_used_value(total_length).0 as f32 / total_length.0 as f32
|
||||
}
|
||||
|
||||
pub fn linear(
|
||||
style: &ComputedValues,
|
||||
size: Size2D<Au>,
|
||||
stops: &[GradientItem],
|
||||
direction: LineDirection,
|
||||
repeating: bool,
|
||||
) -> (Gradient, Vec<GradientStop>) {
|
||||
use style::values::specified::position::HorizontalPositionKeyword::*;
|
||||
use style::values::specified::position::VerticalPositionKeyword::*;
|
||||
let angle = match direction {
|
||||
LineDirection::Angle(angle) => angle.radians(),
|
||||
LineDirection::Horizontal(x) => match x {
|
||||
Left => Angle::from_degrees(270.).radians(),
|
||||
Right => Angle::from_degrees(90.).radians(),
|
||||
},
|
||||
LineDirection::Vertical(y) => match y {
|
||||
Top => Angle::from_degrees(0.).radians(),
|
||||
Bottom => Angle::from_degrees(180.).radians(),
|
||||
},
|
||||
LineDirection::Corner(horizontal, vertical) => {
|
||||
// This the angle for one of the diagonals of the box. Our angle
|
||||
// will either be this one, this one + PI, or one of the other
|
||||
// two perpendicular angles.
|
||||
let atan = (size.height.to_f32_px() / size.width.to_f32_px()).atan();
|
||||
match (horizontal, vertical) {
|
||||
(Right, Bottom) => ::std::f32::consts::PI - atan,
|
||||
(Left, Bottom) => ::std::f32::consts::PI + atan,
|
||||
(Right, Top) => atan,
|
||||
(Left, Top) => -atan,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Get correct gradient line length, based on:
|
||||
// https://drafts.csswg.org/css-images-3/#linear-gradients
|
||||
let dir = Point2D::new(angle.sin(), -angle.cos());
|
||||
|
||||
let line_length =
|
||||
(dir.x * size.width.to_f32_px()).abs() + (dir.y * size.height.to_f32_px()).abs();
|
||||
|
||||
let inv_dir_length = 1.0 / (dir.x * dir.x + dir.y * dir.y).sqrt();
|
||||
|
||||
// This is the vector between the center and the ending point; i.e. half
|
||||
// of the distance between the starting point and the ending point.
|
||||
let delta = Vector2D::new(
|
||||
Au::from_f32_px(dir.x * inv_dir_length * line_length / 2.0),
|
||||
Au::from_f32_px(dir.y * inv_dir_length * line_length / 2.0),
|
||||
);
|
||||
|
||||
// This is the length of the gradient line.
|
||||
let length = Au::from_f32_px((delta.x.to_f32_px() * 2.0).hypot(delta.y.to_f32_px() * 2.0));
|
||||
|
||||
let mut builder = convert_gradient_stops(style, stops, length);
|
||||
|
||||
let center = Point2D::new(size.width / 2, size.height / 2);
|
||||
|
||||
(
|
||||
builder.gradient(
|
||||
(center - delta).to_layout(),
|
||||
(center + delta).to_layout(),
|
||||
extend_mode(repeating),
|
||||
),
|
||||
builder.into_stops(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn radial(
|
||||
style: &ComputedValues,
|
||||
size: Size2D<Au>,
|
||||
stops: &[GradientItem],
|
||||
shape: EndingShape,
|
||||
center: Position,
|
||||
repeating: bool,
|
||||
) -> (RadialGradient, Vec<GradientStop>) {
|
||||
let center = Point2D::new(
|
||||
center.horizontal.to_used_value(size.width),
|
||||
center.vertical.to_used_value(size.height),
|
||||
);
|
||||
let radius = match shape {
|
||||
EndingShape::Circle(Circle::Radius(length)) => {
|
||||
let length = Au::from(length);
|
||||
Size2D::new(length, length)
|
||||
},
|
||||
EndingShape::Circle(Circle::Extent(extent)) => circle_size_keyword(extent, &size, ¢er),
|
||||
EndingShape::Ellipse(Ellipse::Radii(x, y)) => {
|
||||
Size2D::new(x.to_used_value(size.width), y.to_used_value(size.height))
|
||||
},
|
||||
EndingShape::Ellipse(Ellipse::Extent(extent)) => {
|
||||
ellipse_size_keyword(extent, &size, ¢er)
|
||||
},
|
||||
};
|
||||
|
||||
let mut builder = convert_gradient_stops(style, stops, radius.width);
|
||||
(
|
||||
builder.radial_gradient(
|
||||
center.to_layout(),
|
||||
radius.to_layout(),
|
||||
extend_mode(repeating),
|
||||
),
|
||||
builder.into_stops(),
|
||||
)
|
||||
}
|
795
components/layout_2020/display_list/items.rs
Normal file
795
components/layout_2020/display_list/items.rs
Normal file
|
@ -0,0 +1,795 @@
|
|||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! Servo heavily uses display lists, which are retained-mode lists of painting commands to
|
||||
//! perform. Using a list instead of painting elements in immediate mode allows transforms, hit
|
||||
//! testing, and invalidation to be performed using the same primitives as painting. It also allows
|
||||
//! Servo to aggressively cull invisible and out-of-bounds painting elements, to reduce overdraw.
|
||||
//!
|
||||
//! Display items describe relatively high-level drawing operations (for example, entire borders
|
||||
//! and shadows instead of lines and blur operations), to reduce the amount of allocation required.
|
||||
//! They are therefore not exactly analogous to constructs like Skia pictures, which consist of
|
||||
//! low-level drawing primitives.
|
||||
|
||||
use euclid::{SideOffsets2D, Vector2D};
|
||||
use gfx_traits::print_tree::PrintTree;
|
||||
use gfx_traits::{self, StackingContextId};
|
||||
use msg::constellation_msg::PipelineId;
|
||||
use net_traits::image::base::Image;
|
||||
use servo_geometry::MaxRect;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
use std::f32;
|
||||
use std::fmt;
|
||||
use style::computed_values::_servo_top_layer::T as InTopLayer;
|
||||
use webrender_api as wr;
|
||||
use webrender_api::units::{LayoutPoint, LayoutRect, LayoutSize, LayoutTransform};
|
||||
use webrender_api::{BorderRadius, ClipId, ClipMode, CommonItemProperties, ComplexClipRegion};
|
||||
use webrender_api::{ExternalScrollId, FilterOp, GlyphInstance, GradientStop, ImageKey};
|
||||
use webrender_api::{MixBlendMode, ScrollSensitivity, Shadow, SpatialId};
|
||||
use webrender_api::{StickyOffsetBounds, TransformStyle};
|
||||
|
||||
pub use style::dom::OpaqueNode;
|
||||
|
||||
/// The factor that we multiply the blur radius by in order to inflate the boundaries of display
|
||||
/// items that involve a blur. This ensures that the display item boundaries include all the ink.
|
||||
pub static BLUR_INFLATION_FACTOR: i32 = 3;
|
||||
|
||||
/// An index into the vector of ClipScrollNodes. During WebRender conversion these nodes
|
||||
/// are given ClipIds.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
|
||||
pub struct ClipScrollNodeIndex(usize);
|
||||
|
||||
impl ClipScrollNodeIndex {
|
||||
pub fn root_scroll_node() -> ClipScrollNodeIndex {
|
||||
ClipScrollNodeIndex(1)
|
||||
}
|
||||
|
||||
pub fn root_reference_frame() -> ClipScrollNodeIndex {
|
||||
ClipScrollNodeIndex(0)
|
||||
}
|
||||
|
||||
pub fn new(index: usize) -> ClipScrollNodeIndex {
|
||||
assert_ne!(index, 0, "Use the root_reference_frame constructor");
|
||||
assert_ne!(index, 1, "Use the root_scroll_node constructor");
|
||||
ClipScrollNodeIndex(index)
|
||||
}
|
||||
|
||||
pub fn is_root_scroll_node(&self) -> bool {
|
||||
*self == Self::root_scroll_node()
|
||||
}
|
||||
|
||||
pub fn to_define_item(&self) -> DisplayItem {
|
||||
DisplayItem::DefineClipScrollNode(Box::new(DefineClipScrollNodeItem {
|
||||
base: BaseDisplayItem::empty(),
|
||||
node_index: *self,
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn to_index(self) -> usize {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of indices into the clip scroll node vector for a given item.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
|
||||
pub struct ClippingAndScrolling {
|
||||
pub scrolling: ClipScrollNodeIndex,
|
||||
pub clipping: Option<ClipScrollNodeIndex>,
|
||||
}
|
||||
|
||||
impl ClippingAndScrolling {
|
||||
pub fn simple(scrolling: ClipScrollNodeIndex) -> ClippingAndScrolling {
|
||||
ClippingAndScrolling {
|
||||
scrolling,
|
||||
clipping: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(scrolling: ClipScrollNodeIndex, clipping: ClipScrollNodeIndex) -> Self {
|
||||
ClippingAndScrolling {
|
||||
scrolling,
|
||||
clipping: Some(clipping),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct DisplayList {
|
||||
pub list: Vec<DisplayItem>,
|
||||
pub clip_scroll_nodes: Vec<ClipScrollNode>,
|
||||
}
|
||||
|
||||
impl DisplayList {
|
||||
/// Return the bounds of this display list based on the dimensions of the root
|
||||
/// stacking context.
|
||||
pub fn bounds(&self) -> LayoutRect {
|
||||
match self.list.get(0) {
|
||||
Some(&DisplayItem::PushStackingContext(ref item)) => item.stacking_context.bounds,
|
||||
Some(_) => unreachable!("Root element of display list not stacking context."),
|
||||
None => LayoutRect::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print(&self) {
|
||||
let mut print_tree = PrintTree::new("Display List".to_owned());
|
||||
self.print_with_tree(&mut print_tree);
|
||||
}
|
||||
|
||||
pub fn print_with_tree(&self, print_tree: &mut PrintTree) {
|
||||
print_tree.new_level("ClipScrollNodes".to_owned());
|
||||
for node in &self.clip_scroll_nodes {
|
||||
print_tree.add_item(format!("{:?}", node));
|
||||
}
|
||||
print_tree.end_level();
|
||||
|
||||
print_tree.new_level("Items".to_owned());
|
||||
for item in &self.list {
|
||||
print_tree.add_item(format!(
|
||||
"{:?} StackingContext: {:?} {:?}",
|
||||
item,
|
||||
item.base().stacking_context_id,
|
||||
item.clipping_and_scrolling()
|
||||
));
|
||||
}
|
||||
print_tree.end_level();
|
||||
}
|
||||
}
|
||||
|
||||
impl gfx_traits::DisplayList for DisplayList {
|
||||
/// Analyze the display list to figure out if this may be the first
|
||||
/// contentful paint (i.e. the display list contains items of type text,
|
||||
/// image, non-white canvas or SVG). Used by metrics.
|
||||
fn is_contentful(&self) -> bool {
|
||||
for item in &self.list {
|
||||
match item {
|
||||
&DisplayItem::Text(_) | &DisplayItem::Image(_) => return true,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Display list sections that make up a stacking context. Each section here refers
|
||||
/// to the steps in CSS 2.1 Appendix E.
|
||||
///
|
||||
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
pub enum DisplayListSection {
|
||||
BackgroundAndBorders,
|
||||
BlockBackgroundsAndBorders,
|
||||
Content,
|
||||
Outlines,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
pub enum StackingContextType {
|
||||
Real,
|
||||
PseudoPositioned,
|
||||
PseudoFloat,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
/// Represents one CSS stacking context, which may or may not have a hardware layer.
|
||||
pub struct StackingContext {
|
||||
/// The ID of this StackingContext for uniquely identifying it.
|
||||
pub id: StackingContextId,
|
||||
|
||||
/// The type of this StackingContext. Used for collecting and sorting.
|
||||
pub context_type: StackingContextType,
|
||||
|
||||
/// The position and size of this stacking context.
|
||||
pub bounds: LayoutRect,
|
||||
|
||||
/// The overflow rect for this stacking context in its coordinate system.
|
||||
pub overflow: LayoutRect,
|
||||
|
||||
/// The `z-index` for this stacking context.
|
||||
pub z_index: i32,
|
||||
|
||||
/// Whether this is the top layer.
|
||||
pub in_top_layer: InTopLayer,
|
||||
|
||||
/// CSS filters to be applied to this stacking context (including opacity).
|
||||
pub filters: Vec<FilterOp>,
|
||||
|
||||
/// The blend mode with which this stacking context blends with its backdrop.
|
||||
pub mix_blend_mode: MixBlendMode,
|
||||
|
||||
/// A transform to be applied to this stacking context.
|
||||
pub transform: Option<LayoutTransform>,
|
||||
|
||||
/// The transform style of this stacking context.
|
||||
pub transform_style: TransformStyle,
|
||||
|
||||
/// The perspective matrix to be applied to children.
|
||||
pub perspective: Option<LayoutTransform>,
|
||||
|
||||
/// The clip and scroll info for this StackingContext.
|
||||
pub parent_clipping_and_scrolling: ClippingAndScrolling,
|
||||
|
||||
/// The index of the reference frame that this stacking context estalishes.
|
||||
pub established_reference_frame: Option<ClipScrollNodeIndex>,
|
||||
}
|
||||
|
||||
impl StackingContext {
|
||||
/// Creates a new stacking context.
|
||||
#[inline]
|
||||
pub fn new(
|
||||
id: StackingContextId,
|
||||
context_type: StackingContextType,
|
||||
bounds: LayoutRect,
|
||||
overflow: LayoutRect,
|
||||
z_index: i32,
|
||||
in_top_layer: InTopLayer,
|
||||
filters: Vec<FilterOp>,
|
||||
mix_blend_mode: MixBlendMode,
|
||||
transform: Option<LayoutTransform>,
|
||||
transform_style: TransformStyle,
|
||||
perspective: Option<LayoutTransform>,
|
||||
parent_clipping_and_scrolling: ClippingAndScrolling,
|
||||
established_reference_frame: Option<ClipScrollNodeIndex>,
|
||||
) -> StackingContext {
|
||||
StackingContext {
|
||||
id,
|
||||
context_type,
|
||||
bounds,
|
||||
overflow,
|
||||
z_index,
|
||||
in_top_layer,
|
||||
filters,
|
||||
mix_blend_mode,
|
||||
transform,
|
||||
transform_style,
|
||||
perspective,
|
||||
parent_clipping_and_scrolling,
|
||||
established_reference_frame,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn root() -> StackingContext {
|
||||
StackingContext::new(
|
||||
StackingContextId::root(),
|
||||
StackingContextType::Real,
|
||||
LayoutRect::zero(),
|
||||
LayoutRect::zero(),
|
||||
0,
|
||||
InTopLayer::None,
|
||||
vec![],
|
||||
MixBlendMode::Normal,
|
||||
None,
|
||||
TransformStyle::Flat,
|
||||
None,
|
||||
ClippingAndScrolling::simple(ClipScrollNodeIndex::root_scroll_node()),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_display_list_items(self) -> (DisplayItem, DisplayItem) {
|
||||
let mut base_item = BaseDisplayItem::empty();
|
||||
base_item.stacking_context_id = self.id;
|
||||
base_item.clipping_and_scrolling = self.parent_clipping_and_scrolling;
|
||||
|
||||
let pop_item = DisplayItem::PopStackingContext(Box::new(PopStackingContextItem {
|
||||
base: base_item.clone(),
|
||||
stacking_context_id: self.id,
|
||||
}));
|
||||
|
||||
let push_item = DisplayItem::PushStackingContext(Box::new(PushStackingContextItem {
|
||||
base: base_item,
|
||||
stacking_context: self,
|
||||
}));
|
||||
|
||||
(push_item, pop_item)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for StackingContext {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
if self.in_top_layer == InTopLayer::Top {
|
||||
if other.in_top_layer == InTopLayer::Top {
|
||||
return Ordering::Equal;
|
||||
} else {
|
||||
return Ordering::Greater;
|
||||
}
|
||||
} else if other.in_top_layer == InTopLayer::Top {
|
||||
return Ordering::Less;
|
||||
}
|
||||
|
||||
if self.z_index != 0 || other.z_index != 0 {
|
||||
return self.z_index.cmp(&other.z_index);
|
||||
}
|
||||
|
||||
match (self.context_type, other.context_type) {
|
||||
(StackingContextType::PseudoFloat, StackingContextType::PseudoFloat) => Ordering::Equal,
|
||||
(StackingContextType::PseudoFloat, _) => Ordering::Less,
|
||||
(_, StackingContextType::PseudoFloat) => Ordering::Greater,
|
||||
(_, _) => Ordering::Equal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for StackingContext {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for StackingContext {}
|
||||
impl PartialEq for StackingContext {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for StackingContext {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let type_string = if self.context_type == StackingContextType::Real {
|
||||
"StackingContext"
|
||||
} else {
|
||||
"Pseudo-StackingContext"
|
||||
};
|
||||
|
||||
write!(
|
||||
f,
|
||||
"{} at {:?} with overflow {:?}: {:?}",
|
||||
type_string, self.bounds, self.overflow, self.id
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
pub struct StickyFrameData {
|
||||
pub margins: SideOffsets2D<Option<f32>>,
|
||||
pub vertical_offset_bounds: StickyOffsetBounds,
|
||||
pub horizontal_offset_bounds: StickyOffsetBounds,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
pub enum ClipScrollNodeType {
|
||||
Placeholder,
|
||||
ScrollFrame(ScrollSensitivity, ExternalScrollId),
|
||||
StickyFrame(StickyFrameData),
|
||||
Clip,
|
||||
}
|
||||
|
||||
/// Defines a clip scroll node.
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct ClipScrollNode {
|
||||
/// The index of the parent of this ClipScrollNode.
|
||||
pub parent_index: ClipScrollNodeIndex,
|
||||
|
||||
/// The position of this scroll root's frame in the parent stacking context.
|
||||
pub clip: ClippingRegion,
|
||||
|
||||
/// The rect of the contents that can be scrolled inside of the scroll root.
|
||||
pub content_rect: LayoutRect,
|
||||
|
||||
/// The type of this ClipScrollNode.
|
||||
pub node_type: ClipScrollNodeType,
|
||||
}
|
||||
|
||||
impl ClipScrollNode {
|
||||
pub fn placeholder() -> ClipScrollNode {
|
||||
ClipScrollNode {
|
||||
parent_index: ClipScrollNodeIndex(0),
|
||||
clip: ClippingRegion::from_rect(LayoutRect::zero()),
|
||||
content_rect: LayoutRect::zero(),
|
||||
node_type: ClipScrollNodeType::Placeholder,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_placeholder(&self) -> bool {
|
||||
self.node_type == ClipScrollNodeType::Placeholder
|
||||
}
|
||||
}
|
||||
|
||||
/// One drawing command in the list.
|
||||
#[derive(Clone, Serialize)]
|
||||
pub enum DisplayItem {
|
||||
Rectangle(Box<CommonDisplayItem<wr::RectangleDisplayItem>>),
|
||||
Text(Box<CommonDisplayItem<wr::TextDisplayItem, Vec<GlyphInstance>>>),
|
||||
Image(Box<CommonDisplayItem<wr::ImageDisplayItem>>),
|
||||
Border(Box<CommonDisplayItem<wr::BorderDisplayItem, Vec<GradientStop>>>),
|
||||
Gradient(Box<CommonDisplayItem<wr::GradientDisplayItem, Vec<GradientStop>>>),
|
||||
RadialGradient(Box<CommonDisplayItem<wr::RadialGradientDisplayItem, Vec<GradientStop>>>),
|
||||
Line(Box<CommonDisplayItem<wr::LineDisplayItem>>),
|
||||
BoxShadow(Box<CommonDisplayItem<wr::BoxShadowDisplayItem>>),
|
||||
PushTextShadow(Box<PushTextShadowDisplayItem>),
|
||||
PopAllTextShadows(Box<PopAllTextShadowsDisplayItem>),
|
||||
Iframe(Box<IframeDisplayItem>),
|
||||
PushStackingContext(Box<PushStackingContextItem>),
|
||||
PopStackingContext(Box<PopStackingContextItem>),
|
||||
DefineClipScrollNode(Box<DefineClipScrollNodeItem>),
|
||||
}
|
||||
|
||||
/// Information common to all display items.
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct BaseDisplayItem {
|
||||
/// Metadata attached to this display item.
|
||||
pub metadata: DisplayItemMetadata,
|
||||
|
||||
/// The clip rectangle to use for this item.
|
||||
pub clip_rect: LayoutRect,
|
||||
|
||||
/// The section of the display list that this item belongs to.
|
||||
pub section: DisplayListSection,
|
||||
|
||||
/// The id of the stacking context this item belongs to.
|
||||
pub stacking_context_id: StackingContextId,
|
||||
|
||||
/// The clip and scroll info for this item.
|
||||
pub clipping_and_scrolling: ClippingAndScrolling,
|
||||
}
|
||||
|
||||
impl BaseDisplayItem {
|
||||
#[inline(always)]
|
||||
pub fn new(
|
||||
metadata: DisplayItemMetadata,
|
||||
clip_rect: LayoutRect,
|
||||
section: DisplayListSection,
|
||||
stacking_context_id: StackingContextId,
|
||||
clipping_and_scrolling: ClippingAndScrolling,
|
||||
) -> BaseDisplayItem {
|
||||
BaseDisplayItem {
|
||||
metadata,
|
||||
clip_rect,
|
||||
section,
|
||||
stacking_context_id,
|
||||
clipping_and_scrolling,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn empty() -> BaseDisplayItem {
|
||||
BaseDisplayItem {
|
||||
metadata: DisplayItemMetadata {
|
||||
node: OpaqueNode(0),
|
||||
pointing: None,
|
||||
},
|
||||
// Create a rectangle of maximal size.
|
||||
clip_rect: LayoutRect::max_rect(),
|
||||
section: DisplayListSection::Content,
|
||||
stacking_context_id: StackingContextId::root(),
|
||||
clipping_and_scrolling: ClippingAndScrolling::simple(
|
||||
ClipScrollNodeIndex::root_scroll_node(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty_common_item_properties() -> CommonItemProperties {
|
||||
CommonItemProperties {
|
||||
clip_rect: LayoutRect::max_rect(),
|
||||
clip_id: ClipId::root(wr::PipelineId::dummy()),
|
||||
spatial_id: SpatialId::root_scroll_node(wr::PipelineId::dummy()),
|
||||
hit_info: None,
|
||||
is_backface_visible: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// A clipping region for a display item. Currently, this can describe rectangles, rounded
|
||||
/// rectangles (for `border-radius`), or arbitrary intersections of the two. Arbitrary transforms
|
||||
/// are not supported because those are handled by the higher-level `StackingContext` abstraction.
|
||||
#[derive(Clone, PartialEq, Serialize)]
|
||||
pub struct ClippingRegion {
|
||||
/// The main rectangular region. This does not include any corners.
|
||||
pub main: LayoutRect,
|
||||
/// Any complex regions.
|
||||
///
|
||||
/// TODO(pcwalton): Atomically reference count these? Not sure if it's worth the trouble.
|
||||
/// Measure and follow up.
|
||||
pub complex: Vec<ComplexClipRegion>,
|
||||
}
|
||||
|
||||
impl ClippingRegion {
|
||||
/// Returns an empty clipping region that, if set, will result in no pixels being visible.
|
||||
#[inline]
|
||||
pub fn empty() -> ClippingRegion {
|
||||
ClippingRegion {
|
||||
main: LayoutRect::zero(),
|
||||
complex: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an all-encompassing clipping region that clips no pixels out.
|
||||
#[inline]
|
||||
pub fn max() -> ClippingRegion {
|
||||
ClippingRegion {
|
||||
main: LayoutRect::max_rect(),
|
||||
complex: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a clipping region that represents the given rectangle.
|
||||
#[inline]
|
||||
pub fn from_rect(rect: LayoutRect) -> ClippingRegion {
|
||||
ClippingRegion {
|
||||
main: rect,
|
||||
complex: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Intersects this clipping region with the given rounded rectangle.
|
||||
#[inline]
|
||||
pub fn intersect_with_rounded_rect(&mut self, rect: LayoutRect, radii: BorderRadius) {
|
||||
let new_complex_region = ComplexClipRegion {
|
||||
rect,
|
||||
radii,
|
||||
mode: ClipMode::Clip,
|
||||
};
|
||||
|
||||
// FIXME(pcwalton): This is O(n²) worst case for disjoint clipping regions. Is that OK?
|
||||
// They're slow anyway…
|
||||
//
|
||||
// Possibly relevant if we want to do better:
|
||||
//
|
||||
// http://www.inrg.csie.ntu.edu.tw/algorithm2014/presentation/D&C%20Lee-84.pdf
|
||||
for existing_complex_region in &mut self.complex {
|
||||
if completely_encloses(&existing_complex_region, &new_complex_region) {
|
||||
*existing_complex_region = new_complex_region;
|
||||
return;
|
||||
}
|
||||
if completely_encloses(&new_complex_region, &existing_complex_region) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.complex.push(new_complex_region);
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ClippingRegion {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if *self == ClippingRegion::max() {
|
||||
write!(f, "ClippingRegion::Max")
|
||||
} else if *self == ClippingRegion::empty() {
|
||||
write!(f, "ClippingRegion::Empty")
|
||||
} else if self.main == LayoutRect::max_rect() {
|
||||
write!(f, "ClippingRegion(Complex={:?})", self.complex)
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"ClippingRegion(Rect={:?}, Complex={:?})",
|
||||
self.main, self.complex
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(pcwalton): This could be more aggressive by considering points that touch the inside of
|
||||
// the border radius ellipse.
|
||||
fn completely_encloses(this: &ComplexClipRegion, other: &ComplexClipRegion) -> bool {
|
||||
let left = this.radii.top_left.width.max(this.radii.bottom_left.width);
|
||||
let top = this.radii.top_left.height.max(this.radii.top_right.height);
|
||||
let right = this
|
||||
.radii
|
||||
.top_right
|
||||
.width
|
||||
.max(this.radii.bottom_right.width);
|
||||
let bottom = this
|
||||
.radii
|
||||
.bottom_left
|
||||
.height
|
||||
.max(this.radii.bottom_right.height);
|
||||
let interior = LayoutRect::new(
|
||||
LayoutPoint::new(this.rect.origin.x + left, this.rect.origin.y + top),
|
||||
LayoutSize::new(
|
||||
this.rect.size.width - left - right,
|
||||
this.rect.size.height - top - bottom,
|
||||
),
|
||||
);
|
||||
interior.origin.x <= other.rect.origin.x &&
|
||||
interior.origin.y <= other.rect.origin.y &&
|
||||
interior.max_x() >= other.rect.max_x() &&
|
||||
interior.max_y() >= other.rect.max_y()
|
||||
}
|
||||
|
||||
/// Metadata attached to each display item. This is useful for performing auxiliary threads with
|
||||
/// the display list involving hit testing: finding the originating DOM node and determining the
|
||||
/// cursor to use when the element is hovered over.
|
||||
#[derive(Clone, Copy, Serialize)]
|
||||
pub struct DisplayItemMetadata {
|
||||
/// The DOM node from which this display item originated.
|
||||
pub node: OpaqueNode,
|
||||
/// The value of the `cursor` property when the mouse hovers over this display item. If `None`,
|
||||
/// this display item is ineligible for pointer events (`pointer-events: none`).
|
||||
pub pointing: Option<u16>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Serialize)]
|
||||
pub enum TextOrientation {
|
||||
Upright,
|
||||
SidewaysLeft,
|
||||
SidewaysRight,
|
||||
}
|
||||
|
||||
/// Paints an iframe.
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct IframeDisplayItem {
|
||||
pub base: BaseDisplayItem,
|
||||
pub iframe: PipelineId,
|
||||
pub bounds: LayoutRect,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct CommonDisplayItem<T, U = ()> {
|
||||
pub base: BaseDisplayItem,
|
||||
pub item: T,
|
||||
pub data: U,
|
||||
}
|
||||
|
||||
impl<T> CommonDisplayItem<T> {
|
||||
pub fn new(base: BaseDisplayItem, item: T) -> Box<CommonDisplayItem<T>> {
|
||||
Box::new(CommonDisplayItem {
|
||||
base,
|
||||
item,
|
||||
data: (),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> CommonDisplayItem<T, U> {
|
||||
pub fn with_data(base: BaseDisplayItem, item: T, data: U) -> Box<CommonDisplayItem<T, U>> {
|
||||
Box::new(CommonDisplayItem { base, item, data })
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines a text shadow that affects all items until the paired PopTextShadow.
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct PushTextShadowDisplayItem {
|
||||
/// Fields common to all display items.
|
||||
pub base: BaseDisplayItem,
|
||||
|
||||
pub shadow: Shadow,
|
||||
}
|
||||
|
||||
/// Defines a text shadow that affects all items until the next PopTextShadow.
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct PopAllTextShadowsDisplayItem {
|
||||
/// Fields common to all display items.
|
||||
pub base: BaseDisplayItem,
|
||||
}
|
||||
|
||||
/// Defines a stacking context.
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct PushStackingContextItem {
|
||||
/// Fields common to all display items.
|
||||
pub base: BaseDisplayItem,
|
||||
|
||||
pub stacking_context: StackingContext,
|
||||
}
|
||||
|
||||
/// Defines a stacking context.
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct PopStackingContextItem {
|
||||
/// Fields common to all display items.
|
||||
pub base: BaseDisplayItem,
|
||||
|
||||
pub stacking_context_id: StackingContextId,
|
||||
}
|
||||
|
||||
/// Starts a group of items inside a particular scroll root.
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct DefineClipScrollNodeItem {
|
||||
/// Fields common to all display items.
|
||||
pub base: BaseDisplayItem,
|
||||
|
||||
/// The scroll root that this item starts.
|
||||
pub node_index: ClipScrollNodeIndex,
|
||||
}
|
||||
|
||||
impl DisplayItem {
|
||||
pub fn base(&self) -> &BaseDisplayItem {
|
||||
match *self {
|
||||
DisplayItem::Rectangle(ref rect) => &rect.base,
|
||||
DisplayItem::Text(ref text) => &text.base,
|
||||
DisplayItem::Image(ref image_item) => &image_item.base,
|
||||
DisplayItem::Border(ref border) => &border.base,
|
||||
DisplayItem::Gradient(ref gradient) => &gradient.base,
|
||||
DisplayItem::RadialGradient(ref gradient) => &gradient.base,
|
||||
DisplayItem::Line(ref line) => &line.base,
|
||||
DisplayItem::BoxShadow(ref box_shadow) => &box_shadow.base,
|
||||
DisplayItem::PushTextShadow(ref push_text_shadow) => &push_text_shadow.base,
|
||||
DisplayItem::PopAllTextShadows(ref pop_text_shadow) => &pop_text_shadow.base,
|
||||
DisplayItem::Iframe(ref iframe) => &iframe.base,
|
||||
DisplayItem::PushStackingContext(ref stacking_context) => &stacking_context.base,
|
||||
DisplayItem::PopStackingContext(ref item) => &item.base,
|
||||
DisplayItem::DefineClipScrollNode(ref item) => &item.base,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clipping_and_scrolling(&self) -> ClippingAndScrolling {
|
||||
self.base().clipping_and_scrolling
|
||||
}
|
||||
|
||||
pub fn stacking_context_id(&self) -> StackingContextId {
|
||||
self.base().stacking_context_id
|
||||
}
|
||||
|
||||
pub fn section(&self) -> DisplayListSection {
|
||||
self.base().section
|
||||
}
|
||||
|
||||
pub fn bounds(&self) -> LayoutRect {
|
||||
match *self {
|
||||
DisplayItem::Rectangle(ref item) => item.item.common.clip_rect,
|
||||
DisplayItem::Text(ref item) => item.item.bounds,
|
||||
DisplayItem::Image(ref item) => item.item.bounds,
|
||||
DisplayItem::Border(ref item) => item.item.bounds,
|
||||
DisplayItem::Gradient(ref item) => item.item.bounds,
|
||||
DisplayItem::RadialGradient(ref item) => item.item.bounds,
|
||||
DisplayItem::Line(ref item) => item.item.area,
|
||||
DisplayItem::BoxShadow(ref item) => item.item.box_bounds,
|
||||
DisplayItem::PushTextShadow(_) => LayoutRect::zero(),
|
||||
DisplayItem::PopAllTextShadows(_) => LayoutRect::zero(),
|
||||
DisplayItem::Iframe(ref item) => item.bounds,
|
||||
DisplayItem::PushStackingContext(ref item) => item.stacking_context.bounds,
|
||||
DisplayItem::PopStackingContext(_) => LayoutRect::zero(),
|
||||
DisplayItem::DefineClipScrollNode(_) => LayoutRect::zero(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for DisplayItem {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if let DisplayItem::PushStackingContext(ref item) = *self {
|
||||
return write!(f, "PushStackingContext({:?})", item.stacking_context);
|
||||
}
|
||||
|
||||
if let DisplayItem::PopStackingContext(ref item) = *self {
|
||||
return write!(f, "PopStackingContext({:?}", item.stacking_context_id);
|
||||
}
|
||||
|
||||
if let DisplayItem::DefineClipScrollNode(ref item) = *self {
|
||||
return write!(f, "DefineClipScrollNode({:?}", item.node_index);
|
||||
}
|
||||
|
||||
write!(
|
||||
f,
|
||||
"{} @ {:?} {:?}",
|
||||
match *self {
|
||||
DisplayItem::Rectangle(_) => "Rectangle".to_owned(),
|
||||
DisplayItem::Text(_) => "Text".to_owned(),
|
||||
DisplayItem::Image(_) => "Image".to_owned(),
|
||||
DisplayItem::Border(_) => "Border".to_owned(),
|
||||
DisplayItem::Gradient(_) => "Gradient".to_owned(),
|
||||
DisplayItem::RadialGradient(_) => "RadialGradient".to_owned(),
|
||||
DisplayItem::Line(_) => "Line".to_owned(),
|
||||
DisplayItem::BoxShadow(_) => "BoxShadow".to_owned(),
|
||||
DisplayItem::PushTextShadow(_) => "PushTextShadow".to_owned(),
|
||||
DisplayItem::PopAllTextShadows(_) => "PopTextShadow".to_owned(),
|
||||
DisplayItem::Iframe(_) => "Iframe".to_owned(),
|
||||
DisplayItem::PushStackingContext(_) |
|
||||
DisplayItem::PopStackingContext(_) |
|
||||
DisplayItem::DefineClipScrollNode(_) => "".to_owned(),
|
||||
},
|
||||
self.bounds(),
|
||||
self.base().clip_rect
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Serialize)]
|
||||
pub struct WebRenderImageInfo {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub key: Option<ImageKey>,
|
||||
}
|
||||
|
||||
impl WebRenderImageInfo {
|
||||
#[inline]
|
||||
pub fn from_image(image: &Image) -> WebRenderImageInfo {
|
||||
WebRenderImageInfo {
|
||||
width: image.width,
|
||||
height: image.height,
|
||||
key: image.id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of the scroll offset list. This is only populated if WebRender is in use.
|
||||
pub type ScrollOffsetMap = HashMap<ExternalScrollId, Vector2D<f32>>;
|
19
components/layout_2020/display_list/mod.rs
Normal file
19
components/layout_2020/display_list/mod.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
pub use self::builder::BorderPaintingMode;
|
||||
pub use self::builder::DisplayListBuildState;
|
||||
pub use self::builder::IndexableText;
|
||||
pub use self::builder::StackingContextCollectionFlags;
|
||||
pub use self::builder::StackingContextCollectionState;
|
||||
pub use self::conversions::ToLayout;
|
||||
pub use self::webrender_helpers::WebRenderDisplayListConverter;
|
||||
|
||||
mod background;
|
||||
mod border;
|
||||
mod builder;
|
||||
mod conversions;
|
||||
mod gradient;
|
||||
pub mod items;
|
||||
mod webrender_helpers;
|
321
components/layout_2020/display_list/webrender_helpers.rs
Normal file
321
components/layout_2020/display_list/webrender_helpers.rs
Normal file
|
@ -0,0 +1,321 @@
|
|||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// TODO(gw): This contains helper traits and implementations for converting Servo display lists
|
||||
// into WebRender display lists. In the future, this step should be completely removed.
|
||||
// This might be achieved by sharing types between WR and Servo display lists, or
|
||||
// completely converting layout to directly generate WebRender display lists, for example.
|
||||
|
||||
use crate::display_list::items::{BaseDisplayItem, ClipScrollNode, ClipScrollNodeType};
|
||||
use crate::display_list::items::{DisplayItem, DisplayList, StackingContextType};
|
||||
use msg::constellation_msg::PipelineId;
|
||||
use webrender_api::units::LayoutPoint;
|
||||
use webrender_api::{self, ClipId, CommonItemProperties, DisplayItem as WrDisplayItem};
|
||||
use webrender_api::{DisplayListBuilder, PropertyBinding, PushStackingContextDisplayItem};
|
||||
use webrender_api::{
|
||||
RasterSpace, ReferenceFrameKind, SpaceAndClipInfo, SpatialId, StackingContext,
|
||||
};
|
||||
|
||||
pub trait WebRenderDisplayListConverter {
|
||||
fn convert_to_webrender(&mut self, pipeline_id: PipelineId) -> DisplayListBuilder;
|
||||
}
|
||||
|
||||
struct ClipScrollState {
|
||||
clip_ids: Vec<Option<ClipId>>,
|
||||
spatial_ids: Vec<Option<SpatialId>>,
|
||||
active_clip_id: ClipId,
|
||||
active_spatial_id: SpatialId,
|
||||
}
|
||||
|
||||
trait WebRenderDisplayItemConverter {
|
||||
fn convert_to_webrender(
|
||||
&mut self,
|
||||
clip_scroll_nodes: &[ClipScrollNode],
|
||||
state: &mut ClipScrollState,
|
||||
builder: &mut DisplayListBuilder,
|
||||
);
|
||||
}
|
||||
|
||||
impl WebRenderDisplayListConverter for DisplayList {
|
||||
fn convert_to_webrender(&mut self, pipeline_id: PipelineId) -> DisplayListBuilder {
|
||||
let mut clip_ids = vec![None; self.clip_scroll_nodes.len()];
|
||||
let mut spatial_ids = vec![None; self.clip_scroll_nodes.len()];
|
||||
|
||||
// We need to add the WebRender root reference frame and root scroll node ids
|
||||
// here manually, because WebRender creates these automatically.
|
||||
// We also follow the "old" WebRender API for clip/scroll for now,
|
||||
// hence both arrays are initialized based on FIRST_SPATIAL_NODE_INDEX,
|
||||
// while FIRST_CLIP_NODE_INDEX is not taken into account.
|
||||
|
||||
let webrender_pipeline = pipeline_id.to_webrender();
|
||||
clip_ids[0] = Some(ClipId::root(webrender_pipeline));
|
||||
clip_ids[1] = Some(ClipId::root(webrender_pipeline));
|
||||
spatial_ids[0] = Some(SpatialId::root_reference_frame(webrender_pipeline));
|
||||
spatial_ids[1] = Some(SpatialId::root_scroll_node(webrender_pipeline));
|
||||
|
||||
let mut state = ClipScrollState {
|
||||
clip_ids,
|
||||
spatial_ids,
|
||||
active_clip_id: ClipId::root(webrender_pipeline),
|
||||
active_spatial_id: SpatialId::root_scroll_node(webrender_pipeline),
|
||||
};
|
||||
|
||||
let mut builder = DisplayListBuilder::with_capacity(
|
||||
webrender_pipeline,
|
||||
self.bounds().size,
|
||||
1024 * 1024, // 1 MB of space
|
||||
);
|
||||
|
||||
for item in &mut self.list {
|
||||
item.convert_to_webrender(&self.clip_scroll_nodes, &mut state, &mut builder);
|
||||
}
|
||||
|
||||
builder
|
||||
}
|
||||
}
|
||||
|
||||
impl WebRenderDisplayItemConverter for DisplayItem {
|
||||
fn convert_to_webrender(
|
||||
&mut self,
|
||||
clip_scroll_nodes: &[ClipScrollNode],
|
||||
state: &mut ClipScrollState,
|
||||
builder: &mut DisplayListBuilder,
|
||||
) {
|
||||
// Note: for each time of a display item, if we register one of `clip_ids` or `spatial_ids`,
|
||||
// we also register the other one as inherited from the current state or the stack.
|
||||
// This is not an ideal behavior, but it is compatible with the old WebRender model
|
||||
// of the clip-scroll tree.
|
||||
|
||||
let clip_and_scroll_indices = self.base().clipping_and_scrolling;
|
||||
trace!("converting {:?}", clip_and_scroll_indices);
|
||||
|
||||
let cur_spatial_id = state.spatial_ids[clip_and_scroll_indices.scrolling.to_index()]
|
||||
.expect("Tried to use WebRender SpatialId before it was defined.");
|
||||
if cur_spatial_id != state.active_spatial_id {
|
||||
state.active_spatial_id = cur_spatial_id;
|
||||
}
|
||||
|
||||
let internal_clip_id = clip_and_scroll_indices
|
||||
.clipping
|
||||
.unwrap_or(clip_and_scroll_indices.scrolling);
|
||||
let cur_clip_id = state.clip_ids[internal_clip_id.to_index()]
|
||||
.expect("Tried to use WebRender ClipId before it was defined.");
|
||||
if cur_clip_id != state.active_clip_id {
|
||||
state.active_clip_id = cur_clip_id;
|
||||
}
|
||||
|
||||
match *self {
|
||||
DisplayItem::Rectangle(ref mut item) => {
|
||||
item.item.common = build_common_item_properties(&item.base, state);
|
||||
builder.push_item(&WrDisplayItem::Rectangle(item.item));
|
||||
},
|
||||
DisplayItem::Text(ref mut item) => {
|
||||
item.item.common = build_common_item_properties(&item.base, state);
|
||||
builder.push_item(&WrDisplayItem::Text(item.item));
|
||||
builder.push_iter(item.data.iter());
|
||||
},
|
||||
DisplayItem::Image(ref mut item) => {
|
||||
item.item.common = build_common_item_properties(&item.base, state);
|
||||
builder.push_item(&WrDisplayItem::Image(item.item));
|
||||
},
|
||||
DisplayItem::Border(ref mut item) => {
|
||||
item.item.common = build_common_item_properties(&item.base, state);
|
||||
if !item.data.is_empty() {
|
||||
builder.push_stops(item.data.as_ref());
|
||||
}
|
||||
builder.push_item(&WrDisplayItem::Border(item.item));
|
||||
},
|
||||
DisplayItem::Gradient(ref mut item) => {
|
||||
item.item.common = build_common_item_properties(&item.base, state);
|
||||
builder.push_stops(item.data.as_ref());
|
||||
builder.push_item(&WrDisplayItem::Gradient(item.item));
|
||||
},
|
||||
DisplayItem::RadialGradient(ref mut item) => {
|
||||
item.item.common = build_common_item_properties(&item.base, state);
|
||||
builder.push_stops(item.data.as_ref());
|
||||
builder.push_item(&WrDisplayItem::RadialGradient(item.item));
|
||||
},
|
||||
DisplayItem::Line(ref mut item) => {
|
||||
item.item.common = build_common_item_properties(&item.base, state);
|
||||
builder.push_item(&WrDisplayItem::Line(item.item));
|
||||
},
|
||||
DisplayItem::BoxShadow(ref mut item) => {
|
||||
item.item.common = build_common_item_properties(&item.base, state);
|
||||
builder.push_item(&WrDisplayItem::BoxShadow(item.item));
|
||||
},
|
||||
DisplayItem::PushTextShadow(ref mut item) => {
|
||||
let common = build_common_item_properties(&item.base, state);
|
||||
builder.push_shadow(
|
||||
&SpaceAndClipInfo {
|
||||
spatial_id: common.spatial_id,
|
||||
clip_id: common.clip_id,
|
||||
},
|
||||
item.shadow,
|
||||
true,
|
||||
);
|
||||
},
|
||||
DisplayItem::PopAllTextShadows(_) => {
|
||||
builder.push_item(&WrDisplayItem::PopAllShadows);
|
||||
},
|
||||
DisplayItem::Iframe(ref mut item) => {
|
||||
let common = build_common_item_properties(&item.base, state);
|
||||
builder.push_iframe(
|
||||
item.bounds,
|
||||
common.clip_rect,
|
||||
&SpaceAndClipInfo {
|
||||
spatial_id: common.spatial_id,
|
||||
clip_id: common.clip_id,
|
||||
},
|
||||
item.iframe.to_webrender(),
|
||||
true,
|
||||
);
|
||||
},
|
||||
DisplayItem::PushStackingContext(ref mut item) => {
|
||||
let stacking_context = &item.stacking_context;
|
||||
debug_assert_eq!(stacking_context.context_type, StackingContextType::Real);
|
||||
|
||||
//let mut info = webrender_api::LayoutPrimitiveInfo::new(stacking_context.bounds);
|
||||
let mut bounds = stacking_context.bounds;
|
||||
let spatial_id =
|
||||
if let Some(frame_index) = stacking_context.established_reference_frame {
|
||||
let (transform, ref_frame) =
|
||||
match (stacking_context.transform, stacking_context.perspective) {
|
||||
(None, Some(p)) => (
|
||||
p,
|
||||
ReferenceFrameKind::Perspective {
|
||||
scrolling_relative_to: None,
|
||||
},
|
||||
),
|
||||
(Some(t), None) => (t, ReferenceFrameKind::Transform),
|
||||
(Some(t), Some(p)) => (
|
||||
t.pre_mul(&p),
|
||||
ReferenceFrameKind::Perspective {
|
||||
scrolling_relative_to: None,
|
||||
},
|
||||
),
|
||||
(None, None) => unreachable!(),
|
||||
};
|
||||
|
||||
let spatial_id = builder.push_reference_frame(
|
||||
stacking_context.bounds.origin,
|
||||
state.active_spatial_id,
|
||||
stacking_context.transform_style,
|
||||
PropertyBinding::Value(transform),
|
||||
ref_frame,
|
||||
);
|
||||
|
||||
state.spatial_ids[frame_index.to_index()] = Some(spatial_id);
|
||||
state.clip_ids[frame_index.to_index()] = Some(cur_clip_id);
|
||||
|
||||
bounds.origin = LayoutPoint::zero();
|
||||
spatial_id
|
||||
} else {
|
||||
state.active_spatial_id
|
||||
};
|
||||
|
||||
if !stacking_context.filters.is_empty() {
|
||||
builder.push_item(&WrDisplayItem::SetFilterOps);
|
||||
builder.push_iter(&stacking_context.filters);
|
||||
}
|
||||
|
||||
let wr_item = PushStackingContextDisplayItem {
|
||||
origin: bounds.origin,
|
||||
spatial_id,
|
||||
is_backface_visible: true,
|
||||
stacking_context: StackingContext {
|
||||
transform_style: stacking_context.transform_style,
|
||||
mix_blend_mode: stacking_context.mix_blend_mode,
|
||||
clip_id: None,
|
||||
raster_space: RasterSpace::Screen,
|
||||
// TODO(pcwalton): Enable picture caching?
|
||||
cache_tiles: false,
|
||||
},
|
||||
};
|
||||
|
||||
builder.push_item(&WrDisplayItem::PushStackingContext(wr_item));
|
||||
},
|
||||
DisplayItem::PopStackingContext(_) => builder.pop_stacking_context(),
|
||||
DisplayItem::DefineClipScrollNode(ref mut item) => {
|
||||
let node = &clip_scroll_nodes[item.node_index.to_index()];
|
||||
let item_rect = node.clip.main;
|
||||
|
||||
let parent_spatial_id = state.spatial_ids[node.parent_index.to_index()]
|
||||
.expect("Tried to use WebRender parent SpatialId before it was defined.");
|
||||
let parent_clip_id = state.clip_ids[node.parent_index.to_index()]
|
||||
.expect("Tried to use WebRender parent ClipId before it was defined.");
|
||||
|
||||
match node.node_type {
|
||||
ClipScrollNodeType::Clip => {
|
||||
let id = builder.define_clip(
|
||||
&SpaceAndClipInfo {
|
||||
clip_id: parent_clip_id,
|
||||
spatial_id: parent_spatial_id,
|
||||
},
|
||||
item_rect,
|
||||
node.clip.complex.clone(),
|
||||
None,
|
||||
);
|
||||
|
||||
state.spatial_ids[item.node_index.to_index()] = Some(parent_spatial_id);
|
||||
state.clip_ids[item.node_index.to_index()] = Some(id);
|
||||
},
|
||||
ClipScrollNodeType::ScrollFrame(scroll_sensitivity, external_id) => {
|
||||
let space_clip_info = builder.define_scroll_frame(
|
||||
&SpaceAndClipInfo {
|
||||
clip_id: parent_clip_id,
|
||||
spatial_id: parent_spatial_id,
|
||||
},
|
||||
Some(external_id),
|
||||
node.content_rect,
|
||||
node.clip.main,
|
||||
node.clip.complex.clone(),
|
||||
None,
|
||||
scroll_sensitivity,
|
||||
webrender_api::units::LayoutVector2D::zero(),
|
||||
);
|
||||
|
||||
state.clip_ids[item.node_index.to_index()] = Some(space_clip_info.clip_id);
|
||||
state.spatial_ids[item.node_index.to_index()] =
|
||||
Some(space_clip_info.spatial_id);
|
||||
},
|
||||
ClipScrollNodeType::StickyFrame(ref sticky_data) => {
|
||||
// TODO: Add define_sticky_frame_with_parent to WebRender.
|
||||
let id = builder.define_sticky_frame(
|
||||
parent_spatial_id,
|
||||
item_rect,
|
||||
sticky_data.margins,
|
||||
sticky_data.vertical_offset_bounds,
|
||||
sticky_data.horizontal_offset_bounds,
|
||||
webrender_api::units::LayoutVector2D::zero(),
|
||||
);
|
||||
|
||||
state.spatial_ids[item.node_index.to_index()] = Some(id);
|
||||
state.clip_ids[item.node_index.to_index()] = Some(parent_clip_id);
|
||||
},
|
||||
ClipScrollNodeType::Placeholder => {
|
||||
unreachable!("Found DefineClipScrollNode for Placeholder type node.");
|
||||
},
|
||||
};
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_common_item_properties(
|
||||
base: &BaseDisplayItem,
|
||||
state: &ClipScrollState,
|
||||
) -> CommonItemProperties {
|
||||
let tag = match base.metadata.pointing {
|
||||
Some(cursor) => Some((base.metadata.node.0 as u64, cursor)),
|
||||
None => None,
|
||||
};
|
||||
CommonItemProperties {
|
||||
clip_rect: base.clip_rect,
|
||||
spatial_id: state.active_spatial_id,
|
||||
clip_id: state.active_clip_id,
|
||||
// TODO(gw): Make use of the WR backface visibility functionality.
|
||||
is_backface_visible: true,
|
||||
hit_info: tag,
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue