mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
1493 lines
56 KiB
Rust
1493 lines
56 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
//! 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.
|
|
//! Finally, display lists allow tiles to be farmed out onto multiple CPUs and painted in parallel
|
|
//! (although this benefit does not apply to GPU-based painting).
|
|
//!
|
|
//! 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 app_units::Au;
|
|
use azure::azure::AzFloat;
|
|
use azure::azure_hl::Color;
|
|
use euclid::approxeq::ApproxEq;
|
|
use euclid::num::{One, Zero};
|
|
use euclid::rect::TypedRect;
|
|
use euclid::side_offsets::SideOffsets2D;
|
|
use euclid::{Matrix2D, Matrix4D, Point2D, Rect, Size2D};
|
|
use fnv::FnvHasher;
|
|
use gfx_traits::print_tree::PrintTree;
|
|
use gfx_traits::{LayerId, ScrollPolicy, StackingContextId};
|
|
use ipc_channel::ipc::IpcSharedMemory;
|
|
use msg::constellation_msg::PipelineId;
|
|
use net_traits::image::base::{Image, PixelFormat};
|
|
use paint_context::PaintContext;
|
|
use range::Range;
|
|
use serde::de::{self, Deserialize, Deserializer, MapVisitor, Visitor};
|
|
use serde::ser::impls::MapIteratorVisitor;
|
|
use serde::ser::{Serialize, Serializer};
|
|
use std::cmp::{self, Ordering};
|
|
use std::collections::HashMap;
|
|
use std::fmt;
|
|
use std::hash::{BuildHasherDefault, Hash};
|
|
use std::marker::PhantomData;
|
|
use std::mem;
|
|
use std::ops::{Deref, DerefMut};
|
|
use std::sync::Arc;
|
|
use style::computed_values::{border_style, filter, image_rendering, mix_blend_mode};
|
|
use style_traits::cursor::Cursor;
|
|
use text::TextRun;
|
|
use text::glyph::ByteIndex;
|
|
use util::geometry::{self, max_rect, ScreenPx};
|
|
use webrender_traits::{self, WebGLContextId};
|
|
|
|
pub use style::dom::OpaqueNode;
|
|
|
|
// It seems cleaner to have layout code not mention Azure directly, so let's just reexport this for
|
|
// layout to use.
|
|
pub use azure::azure_hl::GradientStop;
|
|
|
|
/// 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;
|
|
|
|
/// LayerInfo is used to store PaintLayer metadata during DisplayList construction.
|
|
/// It is also used for tracking LayerIds when creating layers to preserve ordering when
|
|
/// layered DisplayItems should render underneath unlayered DisplayItems.
|
|
#[derive(Clone, Copy, HeapSizeOf, Deserialize, Serialize, Debug)]
|
|
pub struct LayerInfo {
|
|
/// The base LayerId of this layer.
|
|
pub layer_id: LayerId,
|
|
|
|
/// The scroll policy of this layer.
|
|
pub scroll_policy: ScrollPolicy,
|
|
|
|
/// The subpage that this layer represents, if there is one.
|
|
pub subpage_pipeline_id: Option<PipelineId>,
|
|
|
|
/// The id for the next layer in the sequence. This is used for synthesizing
|
|
/// layers for content that needs to be displayed on top of this layer.
|
|
pub next_layer_id: LayerId,
|
|
|
|
/// The color of the background in this layer. Used for unpainted content.
|
|
pub background_color: Color,
|
|
}
|
|
|
|
impl LayerInfo {
|
|
pub fn new(id: LayerId,
|
|
scroll_policy: ScrollPolicy,
|
|
subpage_pipeline_id: Option<PipelineId>,
|
|
background_color: Color)
|
|
-> LayerInfo {
|
|
LayerInfo {
|
|
layer_id: id,
|
|
scroll_policy: scroll_policy,
|
|
subpage_pipeline_id: subpage_pipeline_id,
|
|
next_layer_id: id.companion_layer_id(),
|
|
background_color: background_color,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct DisplayListTraversal<'a> {
|
|
pub display_list: &'a DisplayList,
|
|
pub current_item_index: usize,
|
|
pub last_item_index: usize,
|
|
}
|
|
|
|
impl<'a> DisplayListTraversal<'a> {
|
|
fn can_draw_item_at_index(&self, index: usize) -> bool {
|
|
index <= self.last_item_index && index < self.display_list.list.len()
|
|
}
|
|
|
|
pub fn advance(&mut self, context: &StackingContext) -> Option<&'a DisplayItem> {
|
|
if !self.can_draw_item_at_index(self.current_item_index) {
|
|
return None
|
|
}
|
|
if self.display_list.list[self.current_item_index].base().stacking_context_id != context.id {
|
|
return None
|
|
}
|
|
|
|
self.current_item_index += 1;
|
|
Some(&self.display_list.list[self.current_item_index - 1])
|
|
}
|
|
|
|
fn current_item_offset(&self) -> u32 {
|
|
self.display_list.get_offset_for_item(&self.display_list.list[self.current_item_index])
|
|
}
|
|
|
|
pub fn skip_past_stacking_context(&mut self, stacking_context: &StackingContext) {
|
|
let next_stacking_context_offset =
|
|
self.display_list.offsets[&stacking_context.id].outlines + 1;
|
|
while self.can_draw_item_at_index(self.current_item_index + 1) &&
|
|
self.current_item_offset() < next_stacking_context_offset {
|
|
self.current_item_index += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(HeapSizeOf, Deserialize, Serialize, Debug)]
|
|
pub struct StackingContextOffsets {
|
|
pub start: u32,
|
|
pub block_backgrounds_and_borders: u32,
|
|
pub content: u32,
|
|
pub outlines: u32,
|
|
}
|
|
|
|
/// A FNV-based hash map. This is not serializable by `serde` by default, so we provide an
|
|
/// implementation ourselves.
|
|
pub struct FnvHashMap<K, V>(pub HashMap<K, V, BuildHasherDefault<FnvHasher>>);
|
|
|
|
impl<K, V> Deref for FnvHashMap<K, V> {
|
|
type Target = HashMap<K, V, BuildHasherDefault<FnvHasher>>;
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl<K, V> DerefMut for FnvHashMap<K, V> {
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
&mut self.0
|
|
}
|
|
}
|
|
|
|
impl<K, V> Serialize for FnvHashMap<K, V> where K: Eq + Hash + Serialize, V: Serialize {
|
|
#[inline]
|
|
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error> where S: Serializer {
|
|
serializer.serialize_map(MapIteratorVisitor::new(self.iter(), Some(self.len())))
|
|
}
|
|
}
|
|
|
|
impl<K, V> Deserialize for FnvHashMap<K, V> where K: Eq + Hash + Deserialize, V: Deserialize {
|
|
#[inline]
|
|
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error> where D: Deserializer {
|
|
deserializer.deserialize_map(FnvHashMapVisitor::new())
|
|
}
|
|
}
|
|
/// A visitor that produces a map.
|
|
pub struct FnvHashMapVisitor<K, V> {
|
|
marker: PhantomData<FnvHashMap<K, V>>,
|
|
}
|
|
|
|
impl<K, V> FnvHashMapVisitor<K, V> {
|
|
/// Construct a `FnvHashMapVisitor<T>`.
|
|
pub fn new() -> Self {
|
|
FnvHashMapVisitor {
|
|
marker: PhantomData,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<K, V> Visitor for FnvHashMapVisitor<K, V> where K: Eq + Hash + Deserialize, V: Deserialize {
|
|
type Value = FnvHashMap<K, V>;
|
|
|
|
#[inline]
|
|
fn visit_unit<E>(&mut self) -> Result<FnvHashMap<K, V>, E> where E: de::Error {
|
|
Ok(FnvHashMap(HashMap::with_hasher(Default::default())))
|
|
}
|
|
|
|
#[inline]
|
|
fn visit_map<Visitor>(&mut self, mut visitor: Visitor)
|
|
-> Result<FnvHashMap<K, V>, Visitor::Error>
|
|
where Visitor: MapVisitor {
|
|
let mut values = FnvHashMap(HashMap::with_hasher(Default::default()));
|
|
while let Some((key, value)) = try!(visitor.visit()) {
|
|
HashMap::insert(&mut values, key, value);
|
|
}
|
|
try!(visitor.end());
|
|
Ok(values)
|
|
}
|
|
}
|
|
|
|
#[derive(HeapSizeOf, Deserialize, Serialize)]
|
|
pub struct DisplayList {
|
|
pub list: Vec<DisplayItem>,
|
|
pub offsets: FnvHashMap<StackingContextId, StackingContextOffsets>,
|
|
pub root_stacking_context: StackingContext,
|
|
}
|
|
|
|
impl DisplayList {
|
|
pub fn new(mut root_stacking_context: StackingContext,
|
|
items: Vec<DisplayItem>)
|
|
-> DisplayList {
|
|
let mut offsets = FnvHashMap(HashMap::with_hasher(Default::default()));
|
|
DisplayList::sort_and_count_stacking_contexts(&mut root_stacking_context, &mut offsets, 0);
|
|
|
|
let mut display_list = DisplayList {
|
|
list: items,
|
|
offsets: offsets,
|
|
root_stacking_context: root_stacking_context,
|
|
};
|
|
display_list.sort();
|
|
display_list
|
|
}
|
|
|
|
pub fn get_offset_for_item(&self, item: &DisplayItem) -> u32 {
|
|
let offsets = &self.offsets[&item.base().stacking_context_id];
|
|
match item.base().section {
|
|
DisplayListSection::BackgroundAndBorders => offsets.start,
|
|
DisplayListSection::BlockBackgroundsAndBorders =>
|
|
offsets.block_backgrounds_and_borders,
|
|
DisplayListSection::Content => offsets.content,
|
|
DisplayListSection::Outlines => offsets.outlines,
|
|
}
|
|
}
|
|
|
|
fn sort(&mut self) {
|
|
let mut list = mem::replace(&mut self.list, Vec::new());
|
|
|
|
list.sort_by(|a, b| {
|
|
if a.base().stacking_context_id == b.base().stacking_context_id {
|
|
return a.base().section.cmp(&b.base().section);
|
|
}
|
|
self.get_offset_for_item(a).cmp(&self.get_offset_for_item(b))
|
|
});
|
|
|
|
mem::replace(&mut self.list, list);
|
|
}
|
|
|
|
pub fn print(&self) {
|
|
let mut print_tree = PrintTree::new("Display List".to_owned());
|
|
self.print_with_tree(&mut print_tree);
|
|
}
|
|
|
|
fn sort_and_count_stacking_contexts(
|
|
stacking_context: &mut StackingContext,
|
|
offsets: &mut HashMap<StackingContextId,
|
|
StackingContextOffsets,
|
|
BuildHasherDefault<FnvHasher>>,
|
|
mut current_offset: u32)
|
|
-> u32 {
|
|
stacking_context.children.sort();
|
|
|
|
let start_offset = current_offset;
|
|
let mut block_backgrounds_and_borders_offset = None;
|
|
let mut content_offset = None;
|
|
|
|
for child in stacking_context.children.iter_mut() {
|
|
if child.z_index >= 0 {
|
|
if block_backgrounds_and_borders_offset.is_none() {
|
|
current_offset += 1;
|
|
block_backgrounds_and_borders_offset = Some(current_offset);
|
|
}
|
|
|
|
if child.context_type != StackingContextType::PseudoFloat &&
|
|
content_offset.is_none() {
|
|
current_offset += 1;
|
|
content_offset = Some(current_offset);
|
|
}
|
|
}
|
|
|
|
current_offset += 1;
|
|
current_offset =
|
|
DisplayList::sort_and_count_stacking_contexts(child, offsets, current_offset);
|
|
}
|
|
|
|
let block_backgrounds_and_borders_offset =
|
|
block_backgrounds_and_borders_offset.unwrap_or_else(|| {
|
|
current_offset += 1;
|
|
current_offset
|
|
});
|
|
|
|
let content_offset = content_offset.unwrap_or_else(|| {
|
|
current_offset += 1;
|
|
current_offset
|
|
});
|
|
|
|
current_offset += 1;
|
|
|
|
offsets.insert(
|
|
stacking_context.id,
|
|
StackingContextOffsets {
|
|
start: start_offset,
|
|
block_backgrounds_and_borders: block_backgrounds_and_borders_offset,
|
|
content: content_offset,
|
|
outlines: current_offset,
|
|
});
|
|
|
|
current_offset + 1
|
|
}
|
|
|
|
pub fn print_with_tree(&self, print_tree: &mut PrintTree) {
|
|
print_tree.new_level("Items".to_owned());
|
|
for item in &self.list {
|
|
print_tree.add_item(format!("{:?} StackingContext: {:?}",
|
|
item,
|
|
item.base().stacking_context_id));
|
|
}
|
|
print_tree.end_level();
|
|
|
|
print_tree.new_level("Stacking Contexts".to_owned());
|
|
self.root_stacking_context.print_with_tree(print_tree);
|
|
print_tree.end_level();
|
|
}
|
|
|
|
/// Draws a single DisplayItem into the given PaintContext.
|
|
pub fn draw_item_at_index_into_context(&self,
|
|
paint_context: &mut PaintContext,
|
|
transform: &Matrix4D<f32>,
|
|
index: usize) {
|
|
let old_transform = paint_context.draw_target.get_transform();
|
|
paint_context.draw_target.set_transform(
|
|
&Matrix2D::new(transform.m11, transform.m12,
|
|
transform.m21, transform.m22,
|
|
transform.m41, transform.m42));
|
|
|
|
let item = &self.list[index];
|
|
item.draw_into_context(paint_context);
|
|
|
|
paint_context.draw_target.set_transform(&old_transform);
|
|
}
|
|
|
|
pub fn find_stacking_context<'a>(&'a self,
|
|
stacking_context_id: StackingContextId)
|
|
-> Option<&'a StackingContext> {
|
|
fn find_stacking_context_in_stacking_context<'a>(stacking_context: &'a StackingContext,
|
|
stacking_context_id: StackingContextId)
|
|
-> Option<&'a StackingContext> {
|
|
if stacking_context.id == stacking_context_id {
|
|
return Some(stacking_context);
|
|
}
|
|
|
|
for kid in stacking_context.children.iter() {
|
|
let result = find_stacking_context_in_stacking_context(kid, stacking_context_id);
|
|
if result.is_some() {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
find_stacking_context_in_stacking_context(&self.root_stacking_context,
|
|
stacking_context_id)
|
|
}
|
|
|
|
/// Draws the DisplayList in order.
|
|
pub fn draw_into_context<'a>(&self,
|
|
paint_context: &mut PaintContext,
|
|
transform: &Matrix4D<f32>,
|
|
stacking_context_id: StackingContextId,
|
|
start: usize,
|
|
end: usize) {
|
|
let stacking_context = self.find_stacking_context(stacking_context_id).unwrap();
|
|
let mut traversal = DisplayListTraversal {
|
|
display_list: self,
|
|
current_item_index: start,
|
|
last_item_index: end,
|
|
};
|
|
self.draw_stacking_context(stacking_context,
|
|
&mut traversal,
|
|
paint_context,
|
|
transform,
|
|
&Point2D::zero());
|
|
}
|
|
|
|
fn draw_stacking_context_contents<'a>(&'a self,
|
|
stacking_context: &StackingContext,
|
|
traversal: &mut DisplayListTraversal<'a>,
|
|
paint_context: &mut PaintContext,
|
|
transform: &Matrix4D<f32>,
|
|
subpixel_offset: &Point2D<Au>,
|
|
tile_rect: Option<Rect<Au>>) {
|
|
for child in stacking_context.children.iter() {
|
|
while let Some(item) = traversal.advance(stacking_context) {
|
|
if item.intersects_rect_in_parent_context(tile_rect) {
|
|
item.draw_into_context(paint_context);
|
|
}
|
|
}
|
|
|
|
if child.intersects_rect_in_parent_context(tile_rect) {
|
|
self.draw_stacking_context(child,
|
|
traversal,
|
|
paint_context,
|
|
&transform,
|
|
subpixel_offset);
|
|
} else {
|
|
traversal.skip_past_stacking_context(child);
|
|
}
|
|
}
|
|
|
|
while let Some(item) = traversal.advance(stacking_context) {
|
|
if item.intersects_rect_in_parent_context(tile_rect) {
|
|
item.draw_into_context(paint_context);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
fn draw_stacking_context<'a>(&'a self,
|
|
stacking_context: &StackingContext,
|
|
traversal: &mut DisplayListTraversal<'a>,
|
|
paint_context: &mut PaintContext,
|
|
transform: &Matrix4D<f32>,
|
|
subpixel_offset: &Point2D<Au>) {
|
|
if stacking_context.context_type != StackingContextType::Real {
|
|
self.draw_stacking_context_contents(stacking_context,
|
|
traversal,
|
|
paint_context,
|
|
transform,
|
|
subpixel_offset,
|
|
None);
|
|
return;
|
|
}
|
|
|
|
let draw_target = paint_context.get_or_create_temporary_draw_target(
|
|
&stacking_context.filters,
|
|
stacking_context.blend_mode);
|
|
|
|
let old_transform = paint_context.draw_target.get_transform();
|
|
let pixels_per_px = paint_context.screen_pixels_per_px();
|
|
let (transform, subpixel_offset) = match stacking_context.layer_info {
|
|
// If this stacking context starts a layer, the offset and transformation are handled
|
|
// by layer position within the compositor.
|
|
Some(..) => (*transform, *subpixel_offset),
|
|
None => {
|
|
let origin = stacking_context.bounds.origin + *subpixel_offset;
|
|
let pixel_snapped_origin =
|
|
Point2D::new(origin.x.to_nearest_pixel(pixels_per_px.get()),
|
|
origin.y.to_nearest_pixel(pixels_per_px.get()));
|
|
|
|
let transform = transform.translate(pixel_snapped_origin.x as AzFloat,
|
|
pixel_snapped_origin.y as AzFloat,
|
|
0.0).mul(&stacking_context.transform);
|
|
|
|
if transform.is_identity_or_simple_translation() {
|
|
let pixel_snapped_origin = Point2D::new(Au::from_f32_px(pixel_snapped_origin.x),
|
|
Au::from_f32_px(pixel_snapped_origin.y));
|
|
(transform, origin - pixel_snapped_origin)
|
|
} else {
|
|
// In the case of a more complicated transformation, don't attempt to
|
|
// preserve subpixel offsets. This causes problems with reference tests
|
|
// that do scaling and rotation and it's unclear if we even want to be doing
|
|
// this.
|
|
(transform, Point2D::zero())
|
|
}
|
|
}
|
|
};
|
|
|
|
{
|
|
let mut paint_subcontext = PaintContext {
|
|
draw_target: draw_target.clone(),
|
|
font_context: &mut *paint_context.font_context,
|
|
page_rect: paint_context.page_rect,
|
|
screen_rect: paint_context.screen_rect,
|
|
clip_rect: Some(stacking_context.overflow),
|
|
transient_clip: None,
|
|
layer_kind: paint_context.layer_kind,
|
|
subpixel_offset: subpixel_offset,
|
|
};
|
|
|
|
// Set up our clip rect and transform.
|
|
paint_subcontext.draw_target.set_transform(
|
|
&Matrix2D::new(transform.m11, transform.m12,
|
|
transform.m21, transform.m22,
|
|
transform.m41, transform.m42));
|
|
paint_subcontext.push_clip_if_applicable();
|
|
|
|
self.draw_stacking_context_contents(
|
|
stacking_context,
|
|
traversal,
|
|
&mut paint_subcontext,
|
|
&transform,
|
|
&subpixel_offset,
|
|
Some(transformed_tile_rect(paint_context.screen_rect, &transform)));
|
|
|
|
paint_subcontext.remove_transient_clip_if_applicable();
|
|
paint_subcontext.pop_clip_if_applicable();
|
|
}
|
|
|
|
draw_target.set_transform(&old_transform);
|
|
paint_context.draw_temporary_draw_target_if_necessary(
|
|
&draw_target, &stacking_context.filters, stacking_context.blend_mode);
|
|
}
|
|
|
|
/// Return all nodes containing the point of interest, bottommost first, and
|
|
/// respecting the `pointer-events` CSS property.
|
|
pub fn hit_test(&self,
|
|
translated_point: &Point2D<Au>,
|
|
client_point: &Point2D<Au>,
|
|
scroll_offsets: &ScrollOffsetMap)
|
|
-> Vec<DisplayItemMetadata> {
|
|
let mut traversal = DisplayListTraversal {
|
|
display_list: self,
|
|
current_item_index: 0,
|
|
last_item_index: self.list.len() - 1,
|
|
};
|
|
let mut result = Vec::new();
|
|
self.root_stacking_context.hit_test(&mut traversal,
|
|
translated_point,
|
|
client_point,
|
|
scroll_offsets,
|
|
&mut result);
|
|
result
|
|
}
|
|
}
|
|
|
|
fn transformed_tile_rect(tile_rect: TypedRect<usize, ScreenPx>, transform: &Matrix4D<f32>) -> Rect<Au> {
|
|
// Invert the current transform, then use this to back transform
|
|
// the tile rect (placed at the origin) into the space of this
|
|
// stacking context.
|
|
let inverse_transform = transform.invert();
|
|
let inverse_transform_2d = Matrix2D::new(inverse_transform.m11, inverse_transform.m12,
|
|
inverse_transform.m21, inverse_transform.m22,
|
|
inverse_transform.m41, inverse_transform.m42);
|
|
let tile_size = Size2D::new(tile_rect.as_f32().size.width, tile_rect.as_f32().size.height);
|
|
let tile_rect = Rect::new(Point2D::zero(), tile_size).to_untyped();
|
|
geometry::f32_rect_to_au_rect(inverse_transform_2d.transform_rect(&tile_rect))
|
|
}
|
|
|
|
|
|
/// 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, Deserialize, Eq, HeapSizeOf, Ord, PartialEq, PartialOrd, RustcEncodable, Serialize)]
|
|
pub enum DisplayListSection {
|
|
BackgroundAndBorders,
|
|
BlockBackgroundsAndBorders,
|
|
Content,
|
|
Outlines,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Deserialize, Eq, HeapSizeOf, Ord, PartialEq, PartialOrd, RustcEncodable, Serialize)]
|
|
pub enum StackingContextType {
|
|
Real,
|
|
PseudoPositioned,
|
|
PseudoFloat,
|
|
}
|
|
|
|
#[derive(HeapSizeOf, Deserialize, 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: Rect<Au>,
|
|
|
|
/// The overflow rect for this stacking context in its coordinate system.
|
|
pub overflow: Rect<Au>,
|
|
|
|
/// The `z-index` for this stacking context.
|
|
pub z_index: i32,
|
|
|
|
/// CSS filters to be applied to this stacking context (including opacity).
|
|
pub filters: filter::T,
|
|
|
|
/// The blend mode with which this stacking context blends with its backdrop.
|
|
pub blend_mode: mix_blend_mode::T,
|
|
|
|
/// A transform to be applied to this stacking context.
|
|
pub transform: Matrix4D<f32>,
|
|
|
|
/// The perspective matrix to be applied to children.
|
|
pub perspective: Matrix4D<f32>,
|
|
|
|
/// 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,
|
|
|
|
/// The layer info for this stacking context, if there is any.
|
|
pub layer_info: Option<LayerInfo>,
|
|
|
|
/// Children of this StackingContext.
|
|
pub children: Vec<Box<StackingContext>>,
|
|
}
|
|
|
|
impl StackingContext {
|
|
/// Creates a new stacking context.
|
|
#[inline]
|
|
pub fn new(id: StackingContextId,
|
|
context_type: StackingContextType,
|
|
bounds: &Rect<Au>,
|
|
overflow: &Rect<Au>,
|
|
z_index: i32,
|
|
filters: filter::T,
|
|
blend_mode: mix_blend_mode::T,
|
|
transform: Matrix4D<f32>,
|
|
perspective: Matrix4D<f32>,
|
|
establishes_3d_context: bool,
|
|
scrolls_overflow_area: bool,
|
|
layer_info: Option<LayerInfo>)
|
|
-> StackingContext {
|
|
StackingContext {
|
|
id: id,
|
|
context_type: context_type,
|
|
bounds: *bounds,
|
|
overflow: *overflow,
|
|
z_index: z_index,
|
|
filters: filters,
|
|
blend_mode: blend_mode,
|
|
transform: transform,
|
|
perspective: perspective,
|
|
establishes_3d_context: establishes_3d_context,
|
|
scrolls_overflow_area: scrolls_overflow_area,
|
|
layer_info: layer_info,
|
|
children: Vec::new(),
|
|
}
|
|
}
|
|
|
|
pub fn hit_test<'a>(&self,
|
|
traversal: &mut DisplayListTraversal<'a>,
|
|
translated_point: &Point2D<Au>,
|
|
client_point: &Point2D<Au>,
|
|
scroll_offsets: &ScrollOffsetMap,
|
|
result: &mut Vec<DisplayItemMetadata>) {
|
|
let is_fixed = match self.layer_info {
|
|
Some(ref layer_info) => layer_info.scroll_policy == ScrollPolicy::FixedPosition,
|
|
None => false,
|
|
};
|
|
|
|
let effective_point = if is_fixed { client_point } else { translated_point };
|
|
|
|
// Convert the point into stacking context local transform space.
|
|
let mut point = if self.context_type == StackingContextType::Real {
|
|
let point = *effective_point - self.bounds.origin;
|
|
let inv_transform = self.transform.invert();
|
|
let frac_point = inv_transform.transform_point(&Point2D::new(point.x.to_f32_px(),
|
|
point.y.to_f32_px()));
|
|
Point2D::new(Au::from_f32_px(frac_point.x), Au::from_f32_px(frac_point.y))
|
|
} else {
|
|
*effective_point
|
|
};
|
|
|
|
// Adjust the translated point to account for the scroll offset if
|
|
// necessary. This can only happen when WebRender is in use.
|
|
//
|
|
// We don't perform this adjustment on the root stacking context because
|
|
// the DOM-side code has already translated the point for us (e.g. in
|
|
// `Window::hit_test_query()`) by now.
|
|
if !is_fixed && self.id != StackingContextId::root() {
|
|
if let Some(scroll_offset) = scroll_offsets.get(&self.id) {
|
|
point.x -= Au::from_f32_px(scroll_offset.x);
|
|
point.y -= Au::from_f32_px(scroll_offset.y);
|
|
}
|
|
}
|
|
|
|
for child in self.children.iter() {
|
|
while let Some(item) = traversal.advance(self) {
|
|
if let Some(meta) = item.hit_test(point) {
|
|
result.push(meta);
|
|
}
|
|
}
|
|
child.hit_test(traversal, translated_point, client_point, scroll_offsets, result);
|
|
}
|
|
|
|
while let Some(item) = traversal.advance(self) {
|
|
if let Some(meta) = item.hit_test(point) {
|
|
result.push(meta);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn print_with_tree(&self, print_tree: &mut PrintTree) {
|
|
print_tree.new_level(format!("{:?}", self));
|
|
for kid in self.children.iter() {
|
|
kid.print_with_tree(print_tree);
|
|
}
|
|
print_tree.end_level();
|
|
}
|
|
|
|
pub fn intersects_rect_in_parent_context(&self, rect: Option<Rect<Au>>) -> bool {
|
|
// We only do intersection checks for real stacking contexts, since
|
|
// pseudo stacking contexts might not have proper position information.
|
|
if self.context_type != StackingContextType::Real {
|
|
return true;
|
|
}
|
|
|
|
let rect = match rect {
|
|
Some(ref rect) => rect,
|
|
None => return true,
|
|
};
|
|
|
|
// Transform this stacking context to get it into the same space as
|
|
// the parent stacking context.
|
|
let origin_x = self.bounds.origin.x.to_f32_px();
|
|
let origin_y = self.bounds.origin.y.to_f32_px();
|
|
|
|
let transform = Matrix4D::identity().translate(origin_x,
|
|
origin_y,
|
|
0.0)
|
|
.mul(&self.transform);
|
|
let transform_2d = Matrix2D::new(transform.m11, transform.m12,
|
|
transform.m21, transform.m22,
|
|
transform.m41, transform.m42);
|
|
|
|
let overflow = geometry::au_rect_to_f32_rect(self.overflow);
|
|
let overflow = transform_2d.transform_rect(&overflow);
|
|
let overflow = geometry::f32_rect_to_au_rect(overflow);
|
|
|
|
rect.intersects(&overflow)
|
|
}
|
|
}
|
|
|
|
impl Ord for StackingContext {
|
|
fn cmp(&self, other: &Self) -> Ordering {
|
|
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.layer_info.is_some() {
|
|
"Layered StackingContext"
|
|
} else if self.context_type == StackingContextType::Real {
|
|
"StackingContext"
|
|
} else {
|
|
"Pseudo-StackingContext"
|
|
};
|
|
|
|
let scrollable_string = if self.scrolls_overflow_area {
|
|
" (scrolls overflow area)"
|
|
} else {
|
|
""
|
|
};
|
|
|
|
write!(f, "{}{} at {:?} with overflow {:?}: {:?}",
|
|
type_string,
|
|
scrollable_string,
|
|
self.bounds,
|
|
self.overflow,
|
|
self.id)
|
|
}
|
|
}
|
|
|
|
/// One drawing command in the list.
|
|
#[derive(Clone, Deserialize, HeapSizeOf, Serialize)]
|
|
#[serde(bound = "")] // Prevent serde from generating cyclic bounds.
|
|
pub enum DisplayItem {
|
|
SolidColorClass(Box<SolidColorDisplayItem>),
|
|
TextClass(Box<TextDisplayItem>),
|
|
ImageClass(Box<ImageDisplayItem>),
|
|
WebGLClass(Box<WebGLDisplayItem>),
|
|
BorderClass(Box<BorderDisplayItem>),
|
|
GradientClass(Box<GradientDisplayItem>),
|
|
LineClass(Box<LineDisplayItem>),
|
|
BoxShadowClass(Box<BoxShadowDisplayItem>),
|
|
LayeredItemClass(Box<LayeredItem>),
|
|
IframeClass(Box<IframeDisplayItem>),
|
|
}
|
|
|
|
/// Information common to all display items.
|
|
#[derive(Clone, Deserialize, HeapSizeOf, Serialize)]
|
|
pub struct BaseDisplayItem {
|
|
/// The boundaries of the display item, in layer coordinates.
|
|
pub bounds: Rect<Au>,
|
|
|
|
/// Metadata attached to this display item.
|
|
pub metadata: DisplayItemMetadata,
|
|
|
|
/// The region to clip to.
|
|
pub clip: ClippingRegion,
|
|
|
|
/// 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,
|
|
}
|
|
|
|
impl BaseDisplayItem {
|
|
#[inline(always)]
|
|
pub fn new(bounds: &Rect<Au>,
|
|
metadata: DisplayItemMetadata,
|
|
clip: &ClippingRegion,
|
|
section: DisplayListSection,
|
|
stacking_context_id: StackingContextId)
|
|
-> BaseDisplayItem {
|
|
// Detect useless clipping regions here and optimize them to `ClippingRegion::max()`.
|
|
// The painting backend may want to optimize out clipping regions and this makes it easier
|
|
// for it to do so.
|
|
BaseDisplayItem {
|
|
bounds: *bounds,
|
|
metadata: metadata,
|
|
clip: if clip.does_not_clip_rect(&bounds) {
|
|
ClippingRegion::max()
|
|
} else {
|
|
(*clip).clone()
|
|
},
|
|
section: section,
|
|
stacking_context_id: stacking_context_id,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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, Debug, HeapSizeOf, Deserialize, Serialize)]
|
|
pub struct ClippingRegion {
|
|
/// The main rectangular region. This does not include any corners.
|
|
pub main: Rect<Au>,
|
|
/// Any complex regions.
|
|
///
|
|
/// TODO(pcwalton): Atomically reference count these? Not sure if it's worth the trouble.
|
|
/// Measure and follow up.
|
|
pub complex: Vec<ComplexClippingRegion>,
|
|
}
|
|
|
|
/// A complex clipping region. These don't as easily admit arbitrary intersection operations, so
|
|
/// they're stored in a list over to the side. Currently a complex clipping region is just a
|
|
/// rounded rectangle, but the CSS WGs will probably make us throw more stuff in here eventually.
|
|
#[derive(Clone, PartialEq, Debug, HeapSizeOf, Deserialize, Serialize)]
|
|
pub struct ComplexClippingRegion {
|
|
/// The boundaries of the rectangle.
|
|
pub rect: Rect<Au>,
|
|
/// Border radii of this rectangle.
|
|
pub radii: BorderRadii<Au>,
|
|
}
|
|
|
|
impl ClippingRegion {
|
|
/// Returns an empty clipping region that, if set, will result in no pixels being visible.
|
|
#[inline]
|
|
pub fn empty() -> ClippingRegion {
|
|
ClippingRegion {
|
|
main: Rect::zero(),
|
|
complex: Vec::new(),
|
|
}
|
|
}
|
|
|
|
/// Returns an all-encompassing clipping region that clips no pixels out.
|
|
#[inline]
|
|
pub fn max() -> ClippingRegion {
|
|
ClippingRegion {
|
|
main: max_rect(),
|
|
complex: Vec::new(),
|
|
}
|
|
}
|
|
|
|
/// Returns a clipping region that represents the given rectangle.
|
|
#[inline]
|
|
pub fn from_rect(rect: &Rect<Au>) -> ClippingRegion {
|
|
ClippingRegion {
|
|
main: *rect,
|
|
complex: Vec::new(),
|
|
}
|
|
}
|
|
|
|
/// Mutates this clipping region to intersect with the given rectangle.
|
|
///
|
|
/// TODO(pcwalton): This could more eagerly eliminate complex clipping regions, at the cost of
|
|
/// complexity.
|
|
#[inline]
|
|
pub fn intersect_rect(&mut self, rect: &Rect<Au>) {
|
|
self.main = self.main.intersection(rect).unwrap_or(Rect::zero())
|
|
}
|
|
|
|
/// Returns true if this clipping region might be nonempty. This can return false positives,
|
|
/// but never false negatives.
|
|
#[inline]
|
|
pub fn might_be_nonempty(&self) -> bool {
|
|
!self.main.is_empty()
|
|
}
|
|
|
|
/// Returns true if this clipping region might contain the given point and false otherwise.
|
|
/// This is a quick, not a precise, test; it can yield false positives.
|
|
#[inline]
|
|
pub fn might_intersect_point(&self, point: &Point2D<Au>) -> bool {
|
|
self.main.contains(point) &&
|
|
self.complex.iter().all(|complex| complex.rect.contains(point))
|
|
}
|
|
|
|
/// Returns true if this clipping region might intersect the given rectangle and false
|
|
/// otherwise. This is a quick, not a precise, test; it can yield false positives.
|
|
#[inline]
|
|
pub fn might_intersect_rect(&self, rect: &Rect<Au>) -> bool {
|
|
self.main.intersects(rect) &&
|
|
self.complex.iter().all(|complex| complex.rect.intersects(rect))
|
|
}
|
|
|
|
/// Returns true if this clipping region completely surrounds the given rect.
|
|
#[inline]
|
|
pub fn does_not_clip_rect(&self, rect: &Rect<Au>) -> bool {
|
|
self.main.contains(&rect.origin) && self.main.contains(&rect.bottom_right()) &&
|
|
self.complex.iter().all(|complex| {
|
|
complex.rect.contains(&rect.origin) && complex.rect.contains(&rect.bottom_right())
|
|
})
|
|
}
|
|
|
|
/// Returns a bounding rect that surrounds this entire clipping region.
|
|
#[inline]
|
|
pub fn bounding_rect(&self) -> Rect<Au> {
|
|
let mut rect = self.main;
|
|
for complex in &*self.complex {
|
|
rect = rect.union(&complex.rect)
|
|
}
|
|
rect
|
|
}
|
|
|
|
/// Intersects this clipping region with the given rounded rectangle.
|
|
#[inline]
|
|
pub fn intersect_with_rounded_rect(&mut self, rect: &Rect<Au>, radii: &BorderRadii<Au>) {
|
|
let new_complex_region = ComplexClippingRegion {
|
|
rect: *rect,
|
|
radii: *radii,
|
|
};
|
|
|
|
// 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 existing_complex_region.completely_encloses(&new_complex_region) {
|
|
*existing_complex_region = new_complex_region;
|
|
return
|
|
}
|
|
if new_complex_region.completely_encloses(existing_complex_region) {
|
|
return
|
|
}
|
|
}
|
|
|
|
self.complex.push(ComplexClippingRegion {
|
|
rect: *rect,
|
|
radii: *radii,
|
|
});
|
|
}
|
|
|
|
/// Translates this clipping region by the given vector.
|
|
#[inline]
|
|
pub fn translate(&self, delta: &Point2D<Au>) -> ClippingRegion {
|
|
ClippingRegion {
|
|
main: self.main.translate(delta),
|
|
complex: self.complex.iter().map(|complex| {
|
|
ComplexClippingRegion {
|
|
rect: complex.rect.translate(delta),
|
|
radii: complex.radii,
|
|
}
|
|
}).collect(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ComplexClippingRegion {
|
|
// TODO(pcwalton): This could be more aggressive by considering points that touch the inside of
|
|
// the border radius ellipse.
|
|
fn completely_encloses(&self, other: &ComplexClippingRegion) -> bool {
|
|
let left = cmp::max(self.radii.top_left.width, self.radii.bottom_left.width);
|
|
let top = cmp::max(self.radii.top_left.height, self.radii.top_right.height);
|
|
let right = cmp::max(self.radii.top_right.width, self.radii.bottom_right.width);
|
|
let bottom = cmp::max(self.radii.bottom_left.height, self.radii.bottom_right.height);
|
|
let interior = Rect::new(Point2D::new(self.rect.origin.x + left, self.rect.origin.y + top),
|
|
Size2D::new(self.rect.size.width - left - right,
|
|
self.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, HeapSizeOf, Deserialize, 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<Cursor>,
|
|
}
|
|
|
|
/// Paints a solid color.
|
|
#[derive(Clone, HeapSizeOf, Deserialize, Serialize)]
|
|
pub struct SolidColorDisplayItem {
|
|
/// Fields common to all display items.
|
|
pub base: BaseDisplayItem,
|
|
|
|
/// The color.
|
|
pub color: Color,
|
|
}
|
|
|
|
/// Paints text.
|
|
#[derive(Clone, HeapSizeOf, Deserialize, Serialize)]
|
|
pub struct TextDisplayItem {
|
|
/// Fields common to all display items.
|
|
pub base: BaseDisplayItem,
|
|
|
|
/// The text run.
|
|
#[ignore_heap_size_of = "Because it is non-owning"]
|
|
pub text_run: Arc<TextRun>,
|
|
|
|
/// The range of text within the text run.
|
|
pub range: Range<ByteIndex>,
|
|
|
|
/// The color of the text.
|
|
pub text_color: Color,
|
|
|
|
/// The position of the start of the baseline of this text.
|
|
pub baseline_origin: Point2D<Au>,
|
|
|
|
/// The orientation of the text: upright or sideways left/right.
|
|
pub orientation: TextOrientation,
|
|
|
|
/// The blur radius for this text. If zero, this text is not blurred.
|
|
pub blur_radius: Au,
|
|
}
|
|
|
|
#[derive(Clone, Eq, PartialEq, HeapSizeOf, Deserialize, Serialize)]
|
|
pub enum TextOrientation {
|
|
Upright,
|
|
SidewaysLeft,
|
|
SidewaysRight,
|
|
}
|
|
|
|
/// Paints an image.
|
|
#[derive(Clone, HeapSizeOf, Deserialize, Serialize)]
|
|
pub struct ImageDisplayItem {
|
|
pub base: BaseDisplayItem,
|
|
|
|
pub webrender_image: WebRenderImageInfo,
|
|
|
|
#[ignore_heap_size_of = "Because it is non-owning"]
|
|
pub image_data: Option<Arc<IpcSharedMemory>>,
|
|
|
|
/// The dimensions to which the image display item should be stretched. If this is smaller than
|
|
/// the bounds of this display item, then the image will be repeated in the appropriate
|
|
/// direction to tile the entire bounds.
|
|
pub stretch_size: Size2D<Au>,
|
|
|
|
/// The algorithm we should use to stretch the image. See `image_rendering` in CSS-IMAGES-3 §
|
|
/// 5.3.
|
|
pub image_rendering: image_rendering::T,
|
|
}
|
|
|
|
#[derive(Clone, HeapSizeOf, Deserialize, Serialize)]
|
|
pub struct WebGLDisplayItem {
|
|
pub base: BaseDisplayItem,
|
|
#[ignore_heap_size_of = "Defined in webrender_traits"]
|
|
pub context_id: WebGLContextId,
|
|
}
|
|
|
|
|
|
/// Paints an iframe.
|
|
#[derive(Clone, HeapSizeOf, Deserialize, Serialize)]
|
|
pub struct IframeDisplayItem {
|
|
pub base: BaseDisplayItem,
|
|
pub iframe: PipelineId,
|
|
}
|
|
|
|
/// Paints a gradient.
|
|
#[derive(Clone, Deserialize, HeapSizeOf, Serialize)]
|
|
pub struct GradientDisplayItem {
|
|
/// Fields common to all display items.
|
|
pub base: BaseDisplayItem,
|
|
|
|
/// The start point of the gradient (computed during display list construction).
|
|
pub start_point: Point2D<Au>,
|
|
|
|
/// The end point of the gradient (computed during display list construction).
|
|
pub end_point: Point2D<Au>,
|
|
|
|
/// A list of color stops.
|
|
pub stops: Vec<GradientStop>,
|
|
}
|
|
|
|
/// Paints a border.
|
|
#[derive(Clone, HeapSizeOf, Deserialize, Serialize)]
|
|
pub struct BorderDisplayItem {
|
|
/// Fields common to all display items.
|
|
pub base: BaseDisplayItem,
|
|
|
|
/// Border widths.
|
|
pub border_widths: SideOffsets2D<Au>,
|
|
|
|
/// Border colors.
|
|
pub color: SideOffsets2D<Color>,
|
|
|
|
/// Border styles.
|
|
pub style: SideOffsets2D<border_style::T>,
|
|
|
|
/// Border radii.
|
|
///
|
|
/// TODO(pcwalton): Elliptical radii.
|
|
pub radius: BorderRadii<Au>,
|
|
}
|
|
|
|
/// Information about the border radii.
|
|
///
|
|
/// TODO(pcwalton): Elliptical radii.
|
|
#[derive(Clone, PartialEq, Debug, Copy, HeapSizeOf, Deserialize, Serialize)]
|
|
pub struct BorderRadii<T> {
|
|
pub top_left: Size2D<T>,
|
|
pub top_right: Size2D<T>,
|
|
pub bottom_right: Size2D<T>,
|
|
pub bottom_left: Size2D<T>,
|
|
}
|
|
|
|
impl<T> Default for BorderRadii<T> where T: Default, T: Clone {
|
|
fn default() -> Self {
|
|
let top_left = Size2D::new(Default::default(),
|
|
Default::default());
|
|
let top_right = Size2D::new(Default::default(),
|
|
Default::default());
|
|
let bottom_left = Size2D::new(Default::default(),
|
|
Default::default());
|
|
let bottom_right = Size2D::new(Default::default(),
|
|
Default::default());
|
|
BorderRadii { top_left: top_left,
|
|
top_right: top_right,
|
|
bottom_left: bottom_left,
|
|
bottom_right: bottom_right }
|
|
}
|
|
}
|
|
|
|
impl BorderRadii<Au> {
|
|
// Scale the border radii by the specified factor
|
|
pub fn scale_by(&self, s: f32) -> BorderRadii<Au> {
|
|
BorderRadii { top_left: BorderRadii::scale_corner_by(self.top_left, s),
|
|
top_right: BorderRadii::scale_corner_by(self.top_right, s),
|
|
bottom_left: BorderRadii::scale_corner_by(self.bottom_left, s),
|
|
bottom_right: BorderRadii::scale_corner_by(self.bottom_right, s) }
|
|
}
|
|
|
|
// Scale the border corner radius by the specified factor
|
|
pub fn scale_corner_by(corner: Size2D<Au>, s: f32) -> Size2D<Au> {
|
|
Size2D::new(corner.width.scale_by(s), corner.height.scale_by(s))
|
|
}
|
|
}
|
|
|
|
impl<T> BorderRadii<T> where T: PartialEq + Zero {
|
|
/// Returns true if all the radii are zero.
|
|
pub fn is_square(&self) -> bool {
|
|
let zero = Zero::zero();
|
|
self.top_left == zero && self.top_right == zero && self.bottom_right == zero &&
|
|
self.bottom_left == zero
|
|
}
|
|
}
|
|
|
|
impl<T> BorderRadii<T> where T: PartialEq + Zero + Clone {
|
|
/// Returns a set of border radii that all have the given value.
|
|
pub fn all_same(value: T) -> BorderRadii<T> {
|
|
BorderRadii {
|
|
top_left: Size2D::new(value.clone(), value.clone()),
|
|
top_right: Size2D::new(value.clone(), value.clone()),
|
|
bottom_right: Size2D::new(value.clone(), value.clone()),
|
|
bottom_left: Size2D::new(value.clone(), value.clone()),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Paints a line segment.
|
|
#[derive(Clone, HeapSizeOf, Deserialize, Serialize)]
|
|
pub struct LineDisplayItem {
|
|
pub base: BaseDisplayItem,
|
|
|
|
/// The line segment color.
|
|
pub color: Color,
|
|
|
|
/// The line segment style.
|
|
pub style: border_style::T
|
|
}
|
|
|
|
/// Paints a box shadow per CSS-BACKGROUNDS.
|
|
#[derive(Clone, HeapSizeOf, Deserialize, Serialize)]
|
|
pub struct BoxShadowDisplayItem {
|
|
/// Fields common to all display items.
|
|
pub base: BaseDisplayItem,
|
|
|
|
/// The dimensions of the box that we're placing a shadow around.
|
|
pub box_bounds: Rect<Au>,
|
|
|
|
/// The offset of this shadow from the box.
|
|
pub offset: Point2D<Au>,
|
|
|
|
/// The color of this shadow.
|
|
pub color: Color,
|
|
|
|
/// The blur radius for this shadow.
|
|
pub blur_radius: Au,
|
|
|
|
/// The spread radius of this shadow.
|
|
pub spread_radius: Au,
|
|
|
|
/// The border radius of this shadow.
|
|
///
|
|
/// TODO(pcwalton): Elliptical radii; different radii for each corner.
|
|
pub border_radius: Au,
|
|
|
|
/// How we should clip the result.
|
|
pub clip_mode: BoxShadowClipMode,
|
|
}
|
|
|
|
/// Contains an item that should get its own layer during layer creation.
|
|
#[derive(Clone, HeapSizeOf, Deserialize, Serialize)]
|
|
pub struct LayeredItem {
|
|
/// Fields common to all display items.
|
|
pub item: DisplayItem,
|
|
|
|
/// The id of the layer this item belongs to.
|
|
pub layer_info: LayerInfo,
|
|
}
|
|
|
|
/// How a box shadow should be clipped.
|
|
#[derive(Clone, Copy, Debug, PartialEq, HeapSizeOf, Deserialize, Serialize)]
|
|
pub enum BoxShadowClipMode {
|
|
/// No special clipping should occur. This is used for (shadowed) text decorations.
|
|
None,
|
|
/// The area inside `box_bounds` should be clipped out. Corresponds to the normal CSS
|
|
/// `box-shadow`.
|
|
Outset,
|
|
/// The area outside `box_bounds` should be clipped out. Corresponds to the `inset` flag on CSS
|
|
/// `box-shadow`.
|
|
Inset,
|
|
}
|
|
|
|
impl DisplayItem {
|
|
/// Paints this display item into the given painting context.
|
|
fn draw_into_context(&self, paint_context: &mut PaintContext) {
|
|
let this_clip = &self.base().clip;
|
|
match paint_context.transient_clip {
|
|
Some(ref transient_clip) if transient_clip == this_clip => {}
|
|
Some(_) | None => paint_context.push_transient_clip((*this_clip).clone()),
|
|
}
|
|
|
|
match *self {
|
|
DisplayItem::SolidColorClass(ref solid_color) => {
|
|
if !solid_color.color.a.approx_eq(&0.0) {
|
|
paint_context.draw_solid_color(&solid_color.base.bounds, solid_color.color)
|
|
}
|
|
}
|
|
|
|
DisplayItem::TextClass(ref text) => {
|
|
debug!("Drawing text at {:?}.", text.base.bounds);
|
|
paint_context.draw_text(&**text);
|
|
}
|
|
|
|
DisplayItem::ImageClass(ref image_item) => {
|
|
debug!("Drawing image at {:?}.", image_item.base.bounds);
|
|
paint_context.draw_image(
|
|
&image_item.base.bounds,
|
|
&image_item.stretch_size,
|
|
&image_item.webrender_image,
|
|
&image_item.image_data
|
|
.as_ref()
|
|
.expect("Non-WR painting needs image data!")[..],
|
|
image_item.image_rendering.clone());
|
|
}
|
|
|
|
DisplayItem::WebGLClass(_) => {
|
|
panic!("Shouldn't be here, WebGL display items are created just with webrender");
|
|
}
|
|
|
|
DisplayItem::BorderClass(ref border) => {
|
|
paint_context.draw_border(&border.base.bounds,
|
|
&border.border_widths,
|
|
&border.radius,
|
|
&border.color,
|
|
&border.style)
|
|
}
|
|
|
|
DisplayItem::GradientClass(ref gradient) => {
|
|
paint_context.draw_linear_gradient(&gradient.base.bounds,
|
|
&gradient.start_point,
|
|
&gradient.end_point,
|
|
&gradient.stops);
|
|
}
|
|
|
|
DisplayItem::LineClass(ref line) => {
|
|
paint_context.draw_line(&line.base.bounds, line.color, line.style)
|
|
}
|
|
|
|
DisplayItem::BoxShadowClass(ref box_shadow) => {
|
|
paint_context.draw_box_shadow(&box_shadow.box_bounds,
|
|
&box_shadow.offset,
|
|
box_shadow.color,
|
|
box_shadow.blur_radius,
|
|
box_shadow.spread_radius,
|
|
box_shadow.clip_mode);
|
|
}
|
|
|
|
DisplayItem::LayeredItemClass(ref item) => item.item.draw_into_context(paint_context),
|
|
|
|
DisplayItem::IframeClass(..) => {}
|
|
}
|
|
}
|
|
|
|
pub fn intersects_rect_in_parent_context(&self, rect: Option<Rect<Au>>) -> bool {
|
|
let rect = match rect {
|
|
Some(ref rect) => rect,
|
|
None => return true,
|
|
};
|
|
|
|
if !rect.intersects(&self.bounds()) {
|
|
return false;
|
|
}
|
|
|
|
self.base().clip.might_intersect_rect(&rect)
|
|
}
|
|
|
|
pub fn base(&self) -> &BaseDisplayItem {
|
|
match *self {
|
|
DisplayItem::SolidColorClass(ref solid_color) => &solid_color.base,
|
|
DisplayItem::TextClass(ref text) => &text.base,
|
|
DisplayItem::ImageClass(ref image_item) => &image_item.base,
|
|
DisplayItem::WebGLClass(ref webgl_item) => &webgl_item.base,
|
|
DisplayItem::BorderClass(ref border) => &border.base,
|
|
DisplayItem::GradientClass(ref gradient) => &gradient.base,
|
|
DisplayItem::LineClass(ref line) => &line.base,
|
|
DisplayItem::BoxShadowClass(ref box_shadow) => &box_shadow.base,
|
|
DisplayItem::LayeredItemClass(ref layered_item) => layered_item.item.base(),
|
|
DisplayItem::IframeClass(ref iframe) => &iframe.base,
|
|
}
|
|
}
|
|
|
|
pub fn bounds(&self) -> Rect<Au> {
|
|
self.base().bounds
|
|
}
|
|
|
|
pub fn debug_with_level(&self, level: u32) {
|
|
let mut indent = String::new();
|
|
for _ in 0..level {
|
|
indent.push_str("| ")
|
|
}
|
|
println!("{}+ {:?}", indent, self);
|
|
}
|
|
|
|
fn hit_test(&self, point: Point2D<Au>) -> Option<DisplayItemMetadata> {
|
|
// TODO(pcwalton): Use a precise algorithm here. This will allow us to properly hit
|
|
// test elements with `border-radius`, for example.
|
|
let base_item = self.base();
|
|
|
|
if !base_item.clip.might_intersect_point(&point) {
|
|
// Clipped out.
|
|
return None;
|
|
}
|
|
if !self.bounds().contains(&point) {
|
|
// Can't possibly hit.
|
|
return None;
|
|
}
|
|
if base_item.metadata.pointing.is_none() {
|
|
// `pointer-events` is `none`. Ignore this item.
|
|
return None;
|
|
}
|
|
|
|
match *self {
|
|
DisplayItem::BorderClass(ref border) => {
|
|
// If the point is inside the border, it didn't hit the border!
|
|
let interior_rect =
|
|
Rect::new(
|
|
Point2D::new(border.base.bounds.origin.x +
|
|
border.border_widths.left,
|
|
border.base.bounds.origin.y +
|
|
border.border_widths.top),
|
|
Size2D::new(border.base.bounds.size.width -
|
|
(border.border_widths.left +
|
|
border.border_widths.right),
|
|
border.base.bounds.size.height -
|
|
(border.border_widths.top +
|
|
border.border_widths.bottom)));
|
|
if interior_rect.contains(&point) {
|
|
return None;
|
|
}
|
|
}
|
|
DisplayItem::BoxShadowClass(_) => {
|
|
// Box shadows can never be hit.
|
|
return None;
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
Some(base_item.metadata)
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for DisplayItem {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "{} @ {:?} {:?}",
|
|
match *self {
|
|
DisplayItem::SolidColorClass(ref solid_color) =>
|
|
format!("SolidColor rgba({}, {}, {}, {})",
|
|
solid_color.color.r,
|
|
solid_color.color.g,
|
|
solid_color.color.b,
|
|
solid_color.color.a),
|
|
DisplayItem::TextClass(_) => "Text".to_owned(),
|
|
DisplayItem::ImageClass(_) => "Image".to_owned(),
|
|
DisplayItem::WebGLClass(_) => "WebGL".to_owned(),
|
|
DisplayItem::BorderClass(_) => "Border".to_owned(),
|
|
DisplayItem::GradientClass(_) => "Gradient".to_owned(),
|
|
DisplayItem::LineClass(_) => "Line".to_owned(),
|
|
DisplayItem::BoxShadowClass(_) => "BoxShadow".to_owned(),
|
|
DisplayItem::LayeredItemClass(ref layered_item) =>
|
|
format!("LayeredItem({:?})", layered_item.item),
|
|
DisplayItem::IframeClass(_) => "Iframe".to_owned(),
|
|
},
|
|
self.bounds(),
|
|
self.base().clip
|
|
)
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, HeapSizeOf, Deserialize, Serialize)]
|
|
pub struct WebRenderImageInfo {
|
|
pub width: u32,
|
|
pub height: u32,
|
|
pub format: PixelFormat,
|
|
#[ignore_heap_size_of = "WebRender traits type, and tiny"]
|
|
pub key: Option<webrender_traits::ImageKey>,
|
|
}
|
|
|
|
impl WebRenderImageInfo {
|
|
#[inline]
|
|
pub fn from_image(image: &Image) -> WebRenderImageInfo {
|
|
WebRenderImageInfo {
|
|
width: image.width,
|
|
height: image.height,
|
|
format: image.format,
|
|
key: image.id,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The type of the scroll offset list. This is only populated if WebRender is in use.
|
|
pub type ScrollOffsetMap = HashMap<StackingContextId, Point2D<f32>>;
|
|
|
|
|
|
pub trait SimpleMatrixDetection {
|
|
fn is_identity_or_simple_translation(&self) -> bool;
|
|
}
|
|
|
|
impl SimpleMatrixDetection for Matrix4D<f32> {
|
|
#[inline]
|
|
fn is_identity_or_simple_translation(&self) -> bool {
|
|
let (_0, _1) = (Zero::zero(), One::one());
|
|
self.m11 == _1 && self.m12 == _0 && self.m13 == _0 && self.m14 == _0 &&
|
|
self.m21 == _0 && self.m22 == _1 && self.m23 == _0 && self.m24 == _0 &&
|
|
self.m31 == _0 && self.m32 == _0 && self.m33 == _1 && self.m34 == _0 &&
|
|
self.m44 == _1
|
|
}
|
|
}
|