зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1723665 - Move coordinate mapping and snapping from scene building to display list building r=aosmond,gfx-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D121591
This commit is contained in:
Родитель
6558b120fa
Коммит
75728e5f1e
|
@ -89,7 +89,6 @@ impl InternablePrimitive for Backdrop {
|
|||
_key: BackdropKey,
|
||||
data_handle: BackdropDataHandle,
|
||||
_prim_store: &mut PrimitiveStore,
|
||||
_reference_frame_relative_offset: LayoutVector2D,
|
||||
) -> PrimitiveInstanceKind {
|
||||
PrimitiveInstanceKind::Backdrop {
|
||||
data_handle,
|
||||
|
|
|
@ -167,7 +167,6 @@ impl InternablePrimitive for NormalBorderPrim {
|
|||
_key: NormalBorderKey,
|
||||
data_handle: NormalBorderDataHandle,
|
||||
_: &mut PrimitiveStore,
|
||||
_reference_frame_relative_offset: LayoutVector2D,
|
||||
) -> PrimitiveInstanceKind {
|
||||
PrimitiveInstanceKind::NormalBorder {
|
||||
data_handle,
|
||||
|
@ -355,7 +354,6 @@ impl InternablePrimitive for ImageBorder {
|
|||
_key: ImageBorderKey,
|
||||
data_handle: ImageBorderDataHandle,
|
||||
_: &mut PrimitiveStore,
|
||||
_reference_frame_relative_offset: LayoutVector2D,
|
||||
) -> PrimitiveInstanceKind {
|
||||
PrimitiveInstanceKind::ImageBorder {
|
||||
data_handle
|
||||
|
|
|
@ -288,7 +288,6 @@ impl InternablePrimitive for ConicGradient {
|
|||
_key: ConicGradientKey,
|
||||
data_handle: ConicGradientDataHandle,
|
||||
_prim_store: &mut PrimitiveStore,
|
||||
_reference_frame_relative_offset: LayoutVector2D,
|
||||
) -> PrimitiveInstanceKind {
|
||||
PrimitiveInstanceKind::ConicGradient {
|
||||
data_handle,
|
||||
|
|
|
@ -594,7 +594,6 @@ impl InternablePrimitive for LinearGradient {
|
|||
key: LinearGradientKey,
|
||||
data_handle: LinearGradientDataHandle,
|
||||
_prim_store: &mut PrimitiveStore,
|
||||
_reference_frame_relative_offset: LayoutVector2D,
|
||||
) -> PrimitiveInstanceKind {
|
||||
if key.cached {
|
||||
PrimitiveInstanceKind::CachedLinearGradient {
|
||||
|
|
|
@ -293,7 +293,6 @@ impl InternablePrimitive for RadialGradient {
|
|||
_key: RadialGradientKey,
|
||||
data_handle: RadialGradientDataHandle,
|
||||
_prim_store: &mut PrimitiveStore,
|
||||
_reference_frame_relative_offset: LayoutVector2D,
|
||||
) -> PrimitiveInstanceKind {
|
||||
PrimitiveInstanceKind::RadialGradient {
|
||||
data_handle,
|
||||
|
|
|
@ -441,7 +441,6 @@ impl InternablePrimitive for Image {
|
|||
_key: ImageKey,
|
||||
data_handle: ImageDataHandle,
|
||||
prim_store: &mut PrimitiveStore,
|
||||
_reference_frame_relative_offset: LayoutVector2D,
|
||||
) -> PrimitiveInstanceKind {
|
||||
// TODO(gw): Refactor this to not need a separate image
|
||||
// instance (see ImageInstance struct).
|
||||
|
@ -647,7 +646,6 @@ impl InternablePrimitive for YuvImage {
|
|||
_key: YuvImageKey,
|
||||
data_handle: YuvImageDataHandle,
|
||||
_prim_store: &mut PrimitiveStore,
|
||||
_reference_frame_relative_offset: LayoutVector2D,
|
||||
) -> PrimitiveInstanceKind {
|
||||
PrimitiveInstanceKind::YuvImage {
|
||||
data_handle,
|
||||
|
|
|
@ -144,7 +144,6 @@ impl InternablePrimitive for LineDecoration {
|
|||
_key: LineDecorationKey,
|
||||
data_handle: LineDecorationDataHandle,
|
||||
_: &mut PrimitiveStore,
|
||||
_reference_frame_relative_offset: LayoutVector2D,
|
||||
) -> PrimitiveInstanceKind {
|
||||
PrimitiveInstanceKind::LineDecoration {
|
||||
data_handle,
|
||||
|
|
|
@ -650,7 +650,6 @@ impl InternablePrimitive for PrimitiveKeyKind {
|
|||
key: PrimitiveKey,
|
||||
data_handle: PrimitiveDataHandle,
|
||||
prim_store: &mut PrimitiveStore,
|
||||
_reference_frame_relative_offset: LayoutVector2D,
|
||||
) -> PrimitiveInstanceKind {
|
||||
match key.kind {
|
||||
PrimitiveKeyKind::Clear => {
|
||||
|
@ -1453,7 +1452,6 @@ pub trait InternablePrimitive: intern::Internable<InternData = ()> + Sized {
|
|||
key: Self::Key,
|
||||
data_handle: intern::Handle<Self>,
|
||||
prim_store: &mut PrimitiveStore,
|
||||
reference_frame_relative_offset: LayoutVector2D,
|
||||
) -> PrimitiveInstanceKind;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ use api::{
|
|||
ColorU, MixBlendMode, FilterPrimitiveInput, FilterPrimitiveKind, ColorSpace,
|
||||
PropertyBinding, PropertyBindingId, CompositeOperator,
|
||||
};
|
||||
use api::units::{Au, LayoutVector2D};
|
||||
use api::units::Au;
|
||||
use crate::scene_building::IsVisible;
|
||||
use crate::filterdata::SFilterData;
|
||||
use crate::intern::ItemUid;
|
||||
|
@ -292,7 +292,6 @@ impl InternablePrimitive for Picture {
|
|||
_key: PictureKey,
|
||||
_: PictureDataHandle,
|
||||
_: &mut PrimitiveStore,
|
||||
_reference_frame_relative_offset: LayoutVector2D,
|
||||
) -> PrimitiveInstanceKind {
|
||||
// Should never be hit as this method should not be
|
||||
// called for pictures.
|
||||
|
|
|
@ -16,7 +16,7 @@ use crate::prim_store::{PrimitiveStore, PrimKeyCommonData, PrimTemplateCommonDat
|
|||
use crate::renderer::{MAX_VERTEX_TEXTURE_WIDTH};
|
||||
use crate::resource_cache::{ResourceCache};
|
||||
use crate::util::{MatrixHelpers};
|
||||
use crate::prim_store::{InternablePrimitive, PrimitiveInstanceKind};
|
||||
use crate::prim_store::{InternablePrimitive, PrimitiveInstanceKind, VectorKey};
|
||||
use crate::spatial_tree::{SpatialTree, SpatialNodeIndex, ROOT_SPATIAL_NODE_INDEX};
|
||||
use crate::space::SpaceSnapper;
|
||||
use crate::util::PrimaryArc;
|
||||
|
@ -36,6 +36,7 @@ pub struct TextRunKey {
|
|||
pub glyphs: PrimaryArc<Vec<GlyphInstance>>,
|
||||
pub shadow: bool,
|
||||
pub requested_raster_space: RasterSpace,
|
||||
pub reference_frame_relative_offset: VectorKey,
|
||||
}
|
||||
|
||||
impl TextRunKey {
|
||||
|
@ -49,6 +50,7 @@ impl TextRunKey {
|
|||
glyphs: PrimaryArc(text_run.glyphs),
|
||||
shadow: text_run.shadow,
|
||||
requested_raster_space: text_run.requested_raster_space,
|
||||
reference_frame_relative_offset: text_run.reference_frame_relative_offset.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -149,6 +151,7 @@ pub struct TextRun {
|
|||
pub glyphs: Arc<Vec<GlyphInstance>>,
|
||||
pub shadow: bool,
|
||||
pub requested_raster_space: RasterSpace,
|
||||
pub reference_frame_relative_offset: LayoutVector2D,
|
||||
}
|
||||
|
||||
impl intern::Internable for TextRun {
|
||||
|
@ -173,13 +176,12 @@ impl InternablePrimitive for TextRun {
|
|||
key: TextRunKey,
|
||||
data_handle: TextRunDataHandle,
|
||||
prim_store: &mut PrimitiveStore,
|
||||
reference_frame_relative_offset: LayoutVector2D,
|
||||
) -> PrimitiveInstanceKind {
|
||||
let run_index = prim_store.text_runs.push(TextRunPrimitive {
|
||||
used_font: key.font.clone(),
|
||||
glyph_keys_range: storage::Range::empty(),
|
||||
reference_frame_relative_offset,
|
||||
snapped_reference_frame_relative_offset: reference_frame_relative_offset,
|
||||
reference_frame_relative_offset: key.reference_frame_relative_offset.into(),
|
||||
snapped_reference_frame_relative_offset: key.reference_frame_relative_offset.into(),
|
||||
shadow: key.shadow,
|
||||
raster_scale: 1.0,
|
||||
requested_raster_space: key.requested_raster_space,
|
||||
|
@ -215,6 +217,7 @@ impl CreateShadow for TextRun {
|
|||
glyphs: self.glyphs.clone(),
|
||||
shadow: true,
|
||||
requested_raster_space,
|
||||
reference_frame_relative_offset: self.reference_frame_relative_offset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -497,8 +500,8 @@ fn test_struct_sizes() {
|
|||
// test expectations and move on.
|
||||
// (b) You made a structure larger. This is not necessarily a problem, but should only
|
||||
// be done with care, and after checking if talos performance regresses badly.
|
||||
assert_eq!(mem::size_of::<TextRun>(), 64, "TextRun size changed");
|
||||
assert_eq!(mem::size_of::<TextRun>(), 72, "TextRun size changed");
|
||||
assert_eq!(mem::size_of::<TextRunTemplate>(), 80, "TextRunTemplate size changed");
|
||||
assert_eq!(mem::size_of::<TextRunKey>(), 80, "TextRunKey size changed");
|
||||
assert_eq!(mem::size_of::<TextRunKey>(), 88, "TextRunKey size changed");
|
||||
assert_eq!(mem::size_of::<TextRunPrimitive>(), 80, "TextRunPrimitive size changed");
|
||||
}
|
||||
|
|
|
@ -89,110 +89,6 @@ use crate::util::{MaxRect, VecHelper};
|
|||
use crate::filterdata::{SFilterDataComponent, SFilterData, SFilterDataKey};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
/// The offset stack for a given reference frame.
|
||||
struct ReferenceFrameState {
|
||||
/// A stack of current offsets from the current reference frame scope.
|
||||
offsets: Vec<LayoutVector2D>,
|
||||
}
|
||||
|
||||
/// Maps from stacking context layout coordinates into reference frame
|
||||
/// relative coordinates.
|
||||
struct ReferenceFrameMapper {
|
||||
/// A stack of reference frame scopes.
|
||||
frames: Vec<ReferenceFrameState>,
|
||||
}
|
||||
|
||||
impl ReferenceFrameMapper {
|
||||
fn new() -> Self {
|
||||
ReferenceFrameMapper {
|
||||
frames: vec![
|
||||
ReferenceFrameState {
|
||||
offsets: vec![
|
||||
LayoutVector2D::zero(),
|
||||
],
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
/// Push a new scope. This resets the current offset to zero, and is
|
||||
/// used when a new reference frame or iframe is pushed.
|
||||
fn push_scope(&mut self) {
|
||||
self.frames.push(ReferenceFrameState {
|
||||
offsets: vec![
|
||||
LayoutVector2D::zero(),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
/// Pop a reference frame scope off the stack.
|
||||
fn pop_scope(&mut self) {
|
||||
self.frames.pop().unwrap();
|
||||
}
|
||||
|
||||
/// Push a new offset for the current scope. This is used when
|
||||
/// a new stacking context is pushed.
|
||||
fn push_offset(&mut self, offset: LayoutVector2D) {
|
||||
let frame = self.frames.last_mut().unwrap();
|
||||
let current_offset = *frame.offsets.last().unwrap();
|
||||
frame.offsets.push(current_offset + offset);
|
||||
}
|
||||
|
||||
/// Pop a local stacking context offset from the current scope.
|
||||
fn pop_offset(&mut self) {
|
||||
let frame = self.frames.last_mut().unwrap();
|
||||
frame.offsets.pop().unwrap();
|
||||
}
|
||||
|
||||
/// Retrieve the current offset to allow converting a stacking context
|
||||
/// relative coordinate to be relative to the owing reference frame.
|
||||
/// TODO(gw): We could perhaps have separate coordinate spaces for this,
|
||||
/// however that's going to either mean a lot of changes to
|
||||
/// public API code, or a lot of changes to internal code.
|
||||
/// Before doing that, we should revisit how Gecko would
|
||||
/// prefer to provide coordinates.
|
||||
/// TODO(gw): For now, this includes only the reference frame relative
|
||||
/// offset. Soon, we will expand this to include the initial
|
||||
/// scroll offsets that are now available on scroll nodes. This
|
||||
/// will allow normalizing the coordinates even between display
|
||||
/// lists where APZ has scrolled the content.
|
||||
fn current_offset(&self) -> LayoutVector2D {
|
||||
*self.frames.last().unwrap().offsets.last().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Offsets primitives (and clips) by the external scroll offset
|
||||
/// supplied to scroll nodes.
|
||||
pub struct ScrollOffsetMapper {
|
||||
pub current_spatial_node: SpatialNodeIndex,
|
||||
pub current_offset: LayoutVector2D,
|
||||
}
|
||||
|
||||
impl ScrollOffsetMapper {
|
||||
fn new() -> Self {
|
||||
ScrollOffsetMapper {
|
||||
current_spatial_node: SpatialNodeIndex::INVALID,
|
||||
current_offset: LayoutVector2D::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the accumulated external scroll offset for a spatial
|
||||
/// node. This caches the last result, which is the common case,
|
||||
/// or defers to the spatial tree to build the value.
|
||||
fn external_scroll_offset(
|
||||
&mut self,
|
||||
spatial_node_index: SpatialNodeIndex,
|
||||
spatial_tree: &SpatialTree,
|
||||
) -> LayoutVector2D {
|
||||
if spatial_node_index != self.current_spatial_node {
|
||||
self.current_spatial_node = spatial_node_index;
|
||||
self.current_offset = spatial_tree.external_scroll_offset(spatial_node_index);
|
||||
}
|
||||
|
||||
self.current_offset
|
||||
}
|
||||
}
|
||||
|
||||
/// A data structure that keeps track of mapping between API Ids for spatials and the indices
|
||||
/// used internally in the SpatialTree to avoid having to do HashMap lookups for primitives
|
||||
/// and clips during frame building.
|
||||
|
@ -486,12 +382,6 @@ pub struct SceneBuilder<'a> {
|
|||
/// Reference to the set of data that is interned across display lists.
|
||||
interners: &'a mut Interners,
|
||||
|
||||
/// Helper struct to map stacking context coords <-> reference frame coords.
|
||||
rf_mapper: ReferenceFrameMapper,
|
||||
|
||||
/// Helper struct to map spatial nodes to external scroll offsets.
|
||||
external_scroll_mapper: ScrollOffsetMapper,
|
||||
|
||||
/// The current recursion depth of iframes encountered. Used to restrict picture
|
||||
/// caching slices to only the top-level content frame.
|
||||
iframe_size: Vec<LayoutSize>,
|
||||
|
@ -567,8 +457,6 @@ impl<'a> SceneBuilder<'a> {
|
|||
prim_store: PrimitiveStore::new(&stats.prim_store_stats),
|
||||
clip_store: ClipStore::new(&stats.clip_store_stats),
|
||||
interners,
|
||||
rf_mapper: ReferenceFrameMapper::new(),
|
||||
external_scroll_mapper: ScrollOffsetMapper::new(),
|
||||
iframe_size: Vec::new(),
|
||||
root_iframe_clip: None,
|
||||
quality_settings: view.quality_settings,
|
||||
|
@ -610,28 +498,6 @@ impl<'a> SceneBuilder<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Retrieve the current offset to allow converting a stacking context
|
||||
/// relative coordinate to be relative to the owing reference frame,
|
||||
/// also considering any external scroll offset on the provided
|
||||
/// spatial node.
|
||||
fn current_offset(
|
||||
&mut self,
|
||||
spatial_node_index: SpatialNodeIndex,
|
||||
) -> LayoutVector2D {
|
||||
// Get the current offset from stacking context <-> reference frame space.
|
||||
let rf_offset = self.rf_mapper.current_offset();
|
||||
|
||||
// Get the external scroll offset, if applicable.
|
||||
let scroll_offset = self
|
||||
.external_scroll_mapper
|
||||
.external_scroll_offset(
|
||||
spatial_node_index,
|
||||
&self.spatial_tree,
|
||||
);
|
||||
|
||||
rf_offset + scroll_offset
|
||||
}
|
||||
|
||||
fn build_all(&mut self, root_pipeline: &ScenePipeline) {
|
||||
enum ContextKind<'a> {
|
||||
Root,
|
||||
|
@ -699,7 +565,6 @@ impl<'a> SceneBuilder<'a> {
|
|||
bc.pipeline_id,
|
||||
);
|
||||
|
||||
self.rf_mapper.push_offset(info.origin.to_vector());
|
||||
let new_context = BuildContext {
|
||||
pipeline_id: bc.pipeline_id,
|
||||
kind: ContextKind::StackingContext {
|
||||
|
@ -717,7 +582,6 @@ impl<'a> SceneBuilder<'a> {
|
|||
profile_scope!("build_reference_frame");
|
||||
let parent_space = self.get_space(info.parent_spatial_id);
|
||||
let mut subtraversal = item.sub_iter();
|
||||
let current_offset = self.current_offset(parent_space);
|
||||
|
||||
let transform = match info.reference_frame.transform {
|
||||
ReferenceTransformBinding::Static { binding } => binding,
|
||||
|
@ -772,10 +636,9 @@ impl<'a> SceneBuilder<'a> {
|
|||
info.reference_frame.transform_style,
|
||||
transform,
|
||||
info.reference_frame.kind,
|
||||
current_offset + info.origin.to_vector(),
|
||||
info.origin.to_vector(),
|
||||
);
|
||||
|
||||
self.rf_mapper.push_scope();
|
||||
let new_context = BuildContext {
|
||||
pipeline_id: bc.pipeline_id,
|
||||
kind: ContextKind::ReferenceFrame,
|
||||
|
@ -813,8 +676,6 @@ impl<'a> SceneBuilder<'a> {
|
|||
assert!(self.root_iframe_clip.is_none());
|
||||
self.root_iframe_clip = Some(clip_chain_id);
|
||||
}
|
||||
|
||||
self.rf_mapper.push_scope();
|
||||
self.iframe_size.push(size);
|
||||
|
||||
let new_context = BuildContext {
|
||||
|
@ -836,16 +697,11 @@ impl<'a> SceneBuilder<'a> {
|
|||
match bc.kind {
|
||||
ContextKind::Root => {}
|
||||
ContextKind::StackingContext { sc_info } => {
|
||||
self.rf_mapper.pop_offset();
|
||||
self.pop_stacking_context(sc_info);
|
||||
}
|
||||
ContextKind::ReferenceFrame => {
|
||||
self.rf_mapper.pop_scope();
|
||||
}
|
||||
ContextKind::ReferenceFrame => {}
|
||||
ContextKind::Iframe { parent_traversal } => {
|
||||
self.iframe_size.pop();
|
||||
self.rf_mapper.pop_scope();
|
||||
|
||||
self.clip_store.pop_clip_root();
|
||||
if self.iframe_size.is_empty() {
|
||||
assert!(self.root_iframe_clip.is_some());
|
||||
|
@ -883,10 +739,8 @@ impl<'a> SceneBuilder<'a> {
|
|||
info: &StickyFrameDisplayItem,
|
||||
parent_node_index: SpatialNodeIndex,
|
||||
) {
|
||||
let current_offset = self.current_offset(parent_node_index);
|
||||
let frame_rect = info.bounds.translate(current_offset);
|
||||
let sticky_frame_info = StickyFrameInfo::new(
|
||||
frame_rect,
|
||||
info.bounds,
|
||||
info.margins,
|
||||
info.vertical_offset_bounds,
|
||||
info.horizontal_offset_bounds,
|
||||
|
@ -907,11 +761,9 @@ impl<'a> SceneBuilder<'a> {
|
|||
parent_node_index: SpatialNodeIndex,
|
||||
pipeline_id: PipelineId,
|
||||
) {
|
||||
let current_offset = self.current_offset(parent_node_index);
|
||||
// This is useful when calculating scroll extents for the
|
||||
// SpatialNode::scroll(..) API as well as for properly setting sticky
|
||||
// positioning offsets.
|
||||
let frame_rect = info.frame_rect.translate(current_offset);
|
||||
let content_size = info.content_rect.size();
|
||||
|
||||
self.add_scroll_frame(
|
||||
|
@ -919,7 +771,7 @@ impl<'a> SceneBuilder<'a> {
|
|||
parent_node_index,
|
||||
info.external_id,
|
||||
pipeline_id,
|
||||
&frame_rect,
|
||||
&info.frame_rect,
|
||||
&content_size,
|
||||
info.scroll_sensitivity,
|
||||
ScrollFrameKind::Explicit,
|
||||
|
@ -941,13 +793,10 @@ impl<'a> SceneBuilder<'a> {
|
|||
},
|
||||
};
|
||||
|
||||
let current_offset = self.current_offset(spatial_node_index);
|
||||
let clip_rect = info.clip_rect.translate(current_offset);
|
||||
|
||||
self.add_rect_clip_node(
|
||||
ClipId::root(iframe_pipeline_id),
|
||||
&info.space_and_clip,
|
||||
&clip_rect,
|
||||
&info.clip_rect,
|
||||
);
|
||||
|
||||
self.clip_store.push_clip_root(
|
||||
|
@ -955,11 +804,6 @@ impl<'a> SceneBuilder<'a> {
|
|||
true,
|
||||
);
|
||||
|
||||
let bounds = self.snap_rect(
|
||||
&info.bounds.translate(current_offset),
|
||||
spatial_node_index,
|
||||
);
|
||||
|
||||
let spatial_node_index = self.push_reference_frame(
|
||||
SpatialId::root_reference_frame(iframe_pipeline_id),
|
||||
Some(spatial_node_index),
|
||||
|
@ -970,10 +814,10 @@ impl<'a> SceneBuilder<'a> {
|
|||
is_2d_scale_translation: false,
|
||||
should_snap: false
|
||||
},
|
||||
bounds.min.to_vector(),
|
||||
info.bounds.min.to_vector(),
|
||||
);
|
||||
|
||||
let iframe_rect = LayoutRect::from_size(bounds.size());
|
||||
let iframe_rect = LayoutRect::from_size(info.bounds.size());
|
||||
let is_root_pipeline = self.iframe_size.is_empty();
|
||||
|
||||
self.add_scroll_frame(
|
||||
|
@ -982,7 +826,7 @@ impl<'a> SceneBuilder<'a> {
|
|||
ExternalScrollId(0, iframe_pipeline_id),
|
||||
iframe_pipeline_id,
|
||||
&iframe_rect,
|
||||
&bounds.size(),
|
||||
&info.bounds.size(),
|
||||
ScrollSensitivity::ScriptAndInputEvents,
|
||||
ScrollFrameKind::PipelineRoot {
|
||||
is_root_pipeline,
|
||||
|
@ -990,7 +834,7 @@ impl<'a> SceneBuilder<'a> {
|
|||
LayoutVector2D::zero(),
|
||||
);
|
||||
|
||||
Some((bounds.size(), pipeline.display_list.iter()))
|
||||
Some((info.bounds.size(), pipeline.display_list.iter()))
|
||||
}
|
||||
|
||||
fn get_space(
|
||||
|
@ -1011,44 +855,24 @@ impl<'a> SceneBuilder<'a> {
|
|||
&mut self,
|
||||
common: &CommonItemProperties,
|
||||
bounds: Option<&LayoutRect>,
|
||||
) -> (LayoutPrimitiveInfo, LayoutRect, SpatialNodeIndex, ClipChainId) {
|
||||
) -> (LayoutPrimitiveInfo, SpatialNodeIndex, ClipChainId) {
|
||||
let spatial_node_index = self.get_space(common.spatial_id);
|
||||
let clip_chain_id = self.get_clip_chain(common.clip_id);
|
||||
|
||||
let current_offset = self.current_offset(spatial_node_index);
|
||||
|
||||
let unsnapped_clip_rect = common.clip_rect.translate(current_offset);
|
||||
let clip_rect = self.snap_rect(
|
||||
&unsnapped_clip_rect,
|
||||
spatial_node_index,
|
||||
);
|
||||
|
||||
let unsnapped_rect = bounds.map(|bounds| {
|
||||
bounds.translate(current_offset)
|
||||
});
|
||||
|
||||
// If no bounds rect is given, default to clip rect.
|
||||
let rect = unsnapped_rect.map_or(clip_rect, |bounds| {
|
||||
self.snap_rect(
|
||||
&bounds,
|
||||
spatial_node_index,
|
||||
)
|
||||
});
|
||||
|
||||
let layout = LayoutPrimitiveInfo {
|
||||
rect,
|
||||
clip_rect,
|
||||
rect: bounds.cloned().unwrap_or(common.clip_rect),
|
||||
clip_rect: common.clip_rect,
|
||||
flags: common.flags,
|
||||
};
|
||||
|
||||
(layout, unsnapped_rect.unwrap_or(unsnapped_clip_rect), spatial_node_index, clip_chain_id)
|
||||
(layout, spatial_node_index, clip_chain_id)
|
||||
}
|
||||
|
||||
fn process_common_properties_with_bounds(
|
||||
&mut self,
|
||||
common: &CommonItemProperties,
|
||||
bounds: &LayoutRect,
|
||||
) -> (LayoutPrimitiveInfo, LayoutRect, SpatialNodeIndex, ClipChainId) {
|
||||
) -> (LayoutPrimitiveInfo, SpatialNodeIndex, ClipChainId) {
|
||||
self.process_common_properties(
|
||||
common,
|
||||
Some(bounds),
|
||||
|
@ -1076,7 +900,7 @@ impl<'a> SceneBuilder<'a> {
|
|||
DisplayItem::Image(ref info) => {
|
||||
profile_scope!("image");
|
||||
|
||||
let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
|
||||
let (layout, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
|
||||
&info.common,
|
||||
&info.bounds,
|
||||
);
|
||||
|
@ -1096,14 +920,14 @@ impl<'a> SceneBuilder<'a> {
|
|||
DisplayItem::RepeatingImage(ref info) => {
|
||||
profile_scope!("repeating_image");
|
||||
|
||||
let (layout, unsnapped_rect, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
|
||||
let (layout, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
|
||||
&info.common,
|
||||
&info.bounds,
|
||||
);
|
||||
|
||||
let stretch_size = process_repeat_size(
|
||||
&layout.rect,
|
||||
&unsnapped_rect,
|
||||
&info.unsnapped_rect,
|
||||
info.stretch_size,
|
||||
);
|
||||
|
||||
|
@ -1122,7 +946,7 @@ impl<'a> SceneBuilder<'a> {
|
|||
DisplayItem::YuvImage(ref info) => {
|
||||
profile_scope!("yuv_image");
|
||||
|
||||
let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
|
||||
let (layout, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
|
||||
&info.common,
|
||||
&info.bounds,
|
||||
);
|
||||
|
@ -1147,7 +971,7 @@ impl<'a> SceneBuilder<'a> {
|
|||
// are subtle interactions between the primitive origin and the glyph offset
|
||||
// which appear to be significant (presumably due to some sort of accumulated
|
||||
// error throughout the layers). We should fix this at some point.
|
||||
let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
|
||||
let (layout, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
|
||||
&info.common,
|
||||
&info.bounds,
|
||||
);
|
||||
|
@ -1160,12 +984,13 @@ impl<'a> SceneBuilder<'a> {
|
|||
&info.color,
|
||||
item.glyphs(),
|
||||
info.glyph_options,
|
||||
info.reference_frame_relative_offset,
|
||||
);
|
||||
}
|
||||
DisplayItem::Rectangle(ref info) => {
|
||||
profile_scope!("rect");
|
||||
|
||||
let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
|
||||
let (layout, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
|
||||
&info.common,
|
||||
&info.bounds,
|
||||
);
|
||||
|
@ -1185,7 +1010,7 @@ impl<'a> SceneBuilder<'a> {
|
|||
|
||||
// TODO(gw): We could skip building the clip-chain here completely, as it's not used by
|
||||
// hit-test items.
|
||||
let (layout, _, spatial_node_index, _) = self.process_common_properties(
|
||||
let (layout, spatial_node_index, _) = self.process_common_properties(
|
||||
&info.common,
|
||||
None,
|
||||
);
|
||||
|
@ -1203,7 +1028,7 @@ impl<'a> SceneBuilder<'a> {
|
|||
DisplayItem::ClearRectangle(ref info) => {
|
||||
profile_scope!("clear");
|
||||
|
||||
let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
|
||||
let (layout, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
|
||||
&info.common,
|
||||
&info.bounds,
|
||||
);
|
||||
|
@ -1217,7 +1042,7 @@ impl<'a> SceneBuilder<'a> {
|
|||
DisplayItem::Line(ref info) => {
|
||||
profile_scope!("line");
|
||||
|
||||
let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
|
||||
let (layout, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
|
||||
&info.common,
|
||||
&info.area,
|
||||
);
|
||||
|
@ -1239,14 +1064,14 @@ impl<'a> SceneBuilder<'a> {
|
|||
return;
|
||||
}
|
||||
|
||||
let (mut layout, unsnapped_rect, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
|
||||
let (mut layout, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
|
||||
&info.common,
|
||||
&info.bounds,
|
||||
);
|
||||
|
||||
let mut tile_size = process_repeat_size(
|
||||
&layout.rect,
|
||||
&unsnapped_rect,
|
||||
&info.unsnapped_rect,
|
||||
info.tile_size,
|
||||
);
|
||||
|
||||
|
@ -1315,7 +1140,7 @@ impl<'a> SceneBuilder<'a> {
|
|||
return;
|
||||
}
|
||||
|
||||
let (mut layout, unsnapped_rect, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
|
||||
let (mut layout, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
|
||||
&info.common,
|
||||
&info.bounds,
|
||||
);
|
||||
|
@ -1326,7 +1151,7 @@ impl<'a> SceneBuilder<'a> {
|
|||
|
||||
let mut tile_size = process_repeat_size(
|
||||
&layout.rect,
|
||||
&unsnapped_rect,
|
||||
&info.unsnapped_rect,
|
||||
info.tile_size,
|
||||
);
|
||||
|
||||
|
@ -1393,14 +1218,14 @@ impl<'a> SceneBuilder<'a> {
|
|||
return;
|
||||
}
|
||||
|
||||
let (mut layout, unsnapped_rect, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
|
||||
let (mut layout, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
|
||||
&info.common,
|
||||
&info.bounds,
|
||||
);
|
||||
|
||||
let tile_size = process_repeat_size(
|
||||
&layout.rect,
|
||||
&unsnapped_rect,
|
||||
&info.unsnapped_rect,
|
||||
info.tile_size,
|
||||
);
|
||||
|
||||
|
@ -1438,7 +1263,7 @@ impl<'a> SceneBuilder<'a> {
|
|||
DisplayItem::BoxShadow(ref info) => {
|
||||
profile_scope!("box_shadow");
|
||||
|
||||
let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
|
||||
let (layout, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
|
||||
&info.common,
|
||||
&info.box_bounds,
|
||||
);
|
||||
|
@ -1458,7 +1283,7 @@ impl<'a> SceneBuilder<'a> {
|
|||
DisplayItem::Border(ref info) => {
|
||||
profile_scope!("border");
|
||||
|
||||
let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
|
||||
let (layout, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
|
||||
&info.common,
|
||||
&info.bounds,
|
||||
);
|
||||
|
@ -1474,18 +1299,10 @@ impl<'a> SceneBuilder<'a> {
|
|||
DisplayItem::ImageMaskClip(ref info) => {
|
||||
profile_scope!("image_clip");
|
||||
|
||||
let parent_space = self.get_space(info.parent_space_and_clip.spatial_id);
|
||||
let current_offset = self.current_offset(parent_space);
|
||||
|
||||
let image_mask = ImageMask {
|
||||
rect: info.image_mask.rect.translate(current_offset),
|
||||
..info.image_mask
|
||||
};
|
||||
|
||||
self.add_image_mask_clip_node(
|
||||
info.id,
|
||||
&info.parent_space_and_clip,
|
||||
&image_mask,
|
||||
&info.image_mask,
|
||||
info.fill_rule,
|
||||
item.points(),
|
||||
);
|
||||
|
@ -1493,27 +1310,19 @@ impl<'a> SceneBuilder<'a> {
|
|||
DisplayItem::RoundedRectClip(ref info) => {
|
||||
profile_scope!("rounded_clip");
|
||||
|
||||
let parent_space = self.get_space(info.parent_space_and_clip.spatial_id);
|
||||
let current_offset = self.current_offset(parent_space);
|
||||
|
||||
self.add_rounded_rect_clip_node(
|
||||
info.id,
|
||||
&info.parent_space_and_clip,
|
||||
&info.clip,
|
||||
current_offset,
|
||||
);
|
||||
}
|
||||
DisplayItem::RectClip(ref info) => {
|
||||
profile_scope!("rect_clip");
|
||||
|
||||
let parent_space = self.get_space(info.parent_space_and_clip.spatial_id);
|
||||
let current_offset = self.current_offset(parent_space);
|
||||
let clip_rect = info.clip_rect.translate(current_offset);
|
||||
|
||||
self.add_rect_clip_node(
|
||||
info.id,
|
||||
&info.parent_space_and_clip,
|
||||
&clip_rect,
|
||||
&info.clip_rect,
|
||||
);
|
||||
}
|
||||
DisplayItem::ClipChain(ref info) => {
|
||||
|
@ -1556,7 +1365,7 @@ impl<'a> SceneBuilder<'a> {
|
|||
DisplayItem::BackdropFilter(ref info) => {
|
||||
profile_scope!("backdrop");
|
||||
|
||||
let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties(
|
||||
let (layout, spatial_node_index, clip_chain_id) = self.process_common_properties(
|
||||
&info.common,
|
||||
None,
|
||||
);
|
||||
|
@ -1664,7 +1473,6 @@ impl<'a> SceneBuilder<'a> {
|
|||
fn create_primitive<P>(
|
||||
&mut self,
|
||||
info: &LayoutPrimitiveInfo,
|
||||
spatial_node_index: SpatialNodeIndex,
|
||||
clip_chain_id: ClipChainId,
|
||||
prim: P,
|
||||
) -> PrimitiveInstance
|
||||
|
@ -1675,7 +1483,6 @@ impl<'a> SceneBuilder<'a> {
|
|||
// Build a primitive key.
|
||||
let prim_key = prim.into_key(info);
|
||||
|
||||
let current_offset = self.current_offset(spatial_node_index);
|
||||
let interner = self.interners.as_mut();
|
||||
let prim_data_handle = interner
|
||||
.intern(&prim_key, || ());
|
||||
|
@ -1684,7 +1491,6 @@ impl<'a> SceneBuilder<'a> {
|
|||
prim_key,
|
||||
prim_data_handle,
|
||||
&mut self.prim_store,
|
||||
current_offset,
|
||||
);
|
||||
|
||||
PrimitiveInstance::new(
|
||||
|
@ -1832,7 +1638,6 @@ impl<'a> SceneBuilder<'a> {
|
|||
{
|
||||
let prim_instance = self.create_primitive(
|
||||
info,
|
||||
spatial_node_index,
|
||||
clip_chain_id,
|
||||
prim,
|
||||
);
|
||||
|
@ -2441,10 +2246,6 @@ impl<'a> SceneBuilder<'a> {
|
|||
) {
|
||||
let spatial_node_index = self.id_to_index_mapper.get_spatial_node_index(space_and_clip.spatial_id);
|
||||
|
||||
let snapped_mask_rect = self.snap_rect(
|
||||
&image_mask.rect,
|
||||
spatial_node_index,
|
||||
);
|
||||
let points: Vec<LayoutPoint> = points_range.iter().collect();
|
||||
|
||||
// If any points are provided, then intern a polygon with the points and fill rule.
|
||||
|
@ -2460,7 +2261,7 @@ impl<'a> SceneBuilder<'a> {
|
|||
}
|
||||
|
||||
let item = ClipItemKey {
|
||||
kind: ClipItemKeyKind::image_mask(image_mask, snapped_mask_rect, polygon_handle),
|
||||
kind: ClipItemKeyKind::image_mask(image_mask, image_mask.rect, polygon_handle),
|
||||
};
|
||||
|
||||
let handle = self
|
||||
|
@ -2493,13 +2294,8 @@ impl<'a> SceneBuilder<'a> {
|
|||
) {
|
||||
let spatial_node_index = self.id_to_index_mapper.get_spatial_node_index(space_and_clip.spatial_id);
|
||||
|
||||
let snapped_clip_rect = self.snap_rect(
|
||||
clip_rect,
|
||||
spatial_node_index,
|
||||
);
|
||||
|
||||
let item = ClipItemKey {
|
||||
kind: ClipItemKeyKind::rectangle(snapped_clip_rect, ClipMode::Clip),
|
||||
kind: ClipItemKeyKind::rectangle(*clip_rect, ClipMode::Clip),
|
||||
};
|
||||
let handle = self
|
||||
.interners
|
||||
|
@ -2522,22 +2318,17 @@ impl<'a> SceneBuilder<'a> {
|
|||
);
|
||||
}
|
||||
|
||||
pub fn add_rounded_rect_clip_node(
|
||||
fn add_rounded_rect_clip_node(
|
||||
&mut self,
|
||||
new_node_id: ClipId,
|
||||
space_and_clip: &SpaceAndClipInfo,
|
||||
clip: &ComplexClipRegion,
|
||||
current_offset: LayoutVector2D,
|
||||
) {
|
||||
let spatial_node_index = self.id_to_index_mapper.get_spatial_node_index(space_and_clip.spatial_id);
|
||||
|
||||
let snapped_region_rect = self.snap_rect(
|
||||
&clip.rect.translate(current_offset),
|
||||
spatial_node_index,
|
||||
);
|
||||
let item = ClipItemKey {
|
||||
kind: ClipItemKeyKind::rounded_rect(
|
||||
snapped_region_rect,
|
||||
clip.rect,
|
||||
clip.radii,
|
||||
clip.mode,
|
||||
),
|
||||
|
@ -2814,7 +2605,6 @@ impl<'a> SceneBuilder<'a> {
|
|||
// Construct and add a primitive for the given shadow.
|
||||
let shadow_prim_instance = self.create_primitive(
|
||||
&info,
|
||||
pending_primitive.spatial_node_index,
|
||||
pending_primitive.clip_chain_id,
|
||||
pending_primitive.prim.create_shadow(
|
||||
&pending_shadow.shadow,
|
||||
|
@ -3229,9 +3019,8 @@ impl<'a> SceneBuilder<'a> {
|
|||
text_color: &ColorF,
|
||||
glyph_range: ItemRange<GlyphInstance>,
|
||||
glyph_options: Option<GlyphOptions>,
|
||||
reference_frame_relative_offset: LayoutVector2D,
|
||||
) {
|
||||
let offset = self.current_offset(spatial_node_index);
|
||||
|
||||
let text_run = {
|
||||
let instance_map = self.font_instances.lock().unwrap();
|
||||
let font_instance = match instance_map.get(font_instance_key) {
|
||||
|
@ -3267,20 +3056,12 @@ impl<'a> SceneBuilder<'a> {
|
|||
flags,
|
||||
);
|
||||
|
||||
// TODO(gw): It'd be nice not to have to allocate here for creating
|
||||
// the primitive key, when the common case is that the
|
||||
// hash will match and we won't end up creating a new
|
||||
// primitive template.
|
||||
let prim_offset = prim_info.rect.min.to_vector() - offset;
|
||||
let glyphs = glyph_range
|
||||
.iter()
|
||||
.map(|glyph| {
|
||||
GlyphInstance {
|
||||
index: glyph.index,
|
||||
point: glyph.point - prim_offset,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
// TODO(gw): Although we no longer need to remap the glyph coords here,
|
||||
// we still allocate. Instead, perhaps we could have a flat
|
||||
// array of glyphs inside the display list builder, and text
|
||||
// runs store a glyph range? That would reduce a lot of allocations
|
||||
// that we do here.
|
||||
let glyphs = glyph_range.iter().collect();
|
||||
|
||||
// Query the current requested raster space (stack handled by push/pop
|
||||
// stacking context).
|
||||
|
@ -3294,6 +3075,7 @@ impl<'a> SceneBuilder<'a> {
|
|||
font,
|
||||
shadow: false,
|
||||
requested_raster_space,
|
||||
reference_frame_relative_offset,
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -3415,7 +3197,6 @@ impl<'a> SceneBuilder<'a> {
|
|||
// region. By makings sure to include this, the clip chain instance computes the correct clip rect,
|
||||
// but we don't actually apply the filtered backdrop clip yet (this is done to the last instance in
|
||||
// the filter chain below).
|
||||
backdrop_spatial_node_index,
|
||||
clip_chain_id,
|
||||
Backdrop {
|
||||
pic_index: backdrop_pic_index,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
use api::BorderRadius;
|
||||
use api::units::*;
|
||||
use euclid::{Point2D, Rect, Box2D, Size2D, Vector2D, point2};
|
||||
use euclid::{default, Transform2D, Transform3D, Scale};
|
||||
use euclid::{Transform2D, Transform3D, Scale};
|
||||
use malloc_size_of::{MallocShallowSizeOf, MallocSizeOf, MallocSizeOfOps};
|
||||
use plane_split::{Clipper, Polygon};
|
||||
use std::{i32, f32, fmt, ptr};
|
||||
|
@ -15,6 +15,7 @@ use std::os::raw::c_void;
|
|||
use std::sync::Arc;
|
||||
use std::mem::replace;
|
||||
|
||||
pub use api::ScaleOffset;
|
||||
|
||||
// Matches the definition of SK_ScalarNearlyZero in Skia.
|
||||
const NEARLY_ZERO: f32 = 1.0 / 4096.0;
|
||||
|
@ -118,253 +119,6 @@ impl<T> VecHelper<T> for Vec<T> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// Represents an optimized transform where there is only
|
||||
// a scale and translation (which are guaranteed to maintain
|
||||
// an axis align rectangle under transformation). The
|
||||
// scaling is applied first, followed by the translation.
|
||||
// TODO(gw): We should try and incorporate F <-> T units here,
|
||||
// but it's a bit tricky to do that now with the
|
||||
// way the current spatial tree works.
|
||||
#[derive(Debug, Clone, Copy, MallocSizeOf)]
|
||||
#[cfg_attr(feature = "capture", derive(Serialize))]
|
||||
#[cfg_attr(feature = "replay", derive(Deserialize))]
|
||||
pub struct ScaleOffset {
|
||||
pub scale: default::Vector2D<f32>,
|
||||
pub offset: default::Vector2D<f32>,
|
||||
}
|
||||
|
||||
impl ScaleOffset {
|
||||
pub fn identity() -> Self {
|
||||
ScaleOffset {
|
||||
scale: Vector2D::new(1.0, 1.0),
|
||||
offset: Vector2D::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
// Construct a ScaleOffset from a transform. Returns
|
||||
// None if the matrix is not a pure scale / translation.
|
||||
pub fn from_transform<F, T>(
|
||||
m: &Transform3D<f32, F, T>,
|
||||
) -> Option<ScaleOffset> {
|
||||
|
||||
// To check that we have a pure scale / translation:
|
||||
// Every field must match an identity matrix, except:
|
||||
// - Any value present in tx,ty
|
||||
// - Any value present in sx,sy
|
||||
|
||||
if m.m12.abs() > NEARLY_ZERO ||
|
||||
m.m13.abs() > NEARLY_ZERO ||
|
||||
m.m14.abs() > NEARLY_ZERO ||
|
||||
m.m21.abs() > NEARLY_ZERO ||
|
||||
m.m23.abs() > NEARLY_ZERO ||
|
||||
m.m24.abs() > NEARLY_ZERO ||
|
||||
m.m31.abs() > NEARLY_ZERO ||
|
||||
m.m32.abs() > NEARLY_ZERO ||
|
||||
(m.m33 - 1.0).abs() > NEARLY_ZERO ||
|
||||
m.m34.abs() > NEARLY_ZERO ||
|
||||
m.m43.abs() > NEARLY_ZERO ||
|
||||
(m.m44 - 1.0).abs() > NEARLY_ZERO {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(ScaleOffset {
|
||||
scale: Vector2D::new(m.m11, m.m22),
|
||||
offset: Vector2D::new(m.m41, m.m42),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_offset(offset: default::Vector2D<f32>) -> Self {
|
||||
ScaleOffset {
|
||||
scale: Vector2D::new(1.0, 1.0),
|
||||
offset,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_scale(scale: default::Vector2D<f32>) -> Self {
|
||||
ScaleOffset {
|
||||
scale,
|
||||
offset: Vector2D::new(0.0, 0.0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inverse(&self) -> Self {
|
||||
ScaleOffset {
|
||||
scale: Vector2D::new(
|
||||
1.0 / self.scale.x,
|
||||
1.0 / self.scale.y,
|
||||
),
|
||||
offset: Vector2D::new(
|
||||
-self.offset.x / self.scale.x,
|
||||
-self.offset.y / self.scale.y,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn offset(&self, offset: default::Vector2D<f32>) -> Self {
|
||||
self.accumulate(
|
||||
&ScaleOffset {
|
||||
scale: Vector2D::new(1.0, 1.0),
|
||||
offset,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
pub fn scale(&self, scale: f32) -> Self {
|
||||
self.accumulate(
|
||||
&ScaleOffset {
|
||||
scale: Vector2D::new(scale, scale),
|
||||
offset: Vector2D::zero(),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Produce a ScaleOffset that includes both self and other.
|
||||
/// The 'self' ScaleOffset is applied after other.
|
||||
/// This is equivalent to `Transform3D::pre_transform`.
|
||||
pub fn accumulate(&self, other: &ScaleOffset) -> Self {
|
||||
ScaleOffset {
|
||||
scale: Vector2D::new(
|
||||
self.scale.x * other.scale.x,
|
||||
self.scale.y * other.scale.y,
|
||||
),
|
||||
offset: Vector2D::new(
|
||||
self.offset.x + self.scale.x * other.offset.x,
|
||||
self.offset.y + self.scale.y * other.offset.y,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_rect<F, T>(&self, rect: &Box2D<f32, F>) -> Box2D<f32, T> {
|
||||
// TODO(gw): The logic below can return an unexpected result if the supplied
|
||||
// rect is invalid (has size < 0). Since Gecko currently supplied
|
||||
// invalid rects in some cases, adding a max(0) here ensures that
|
||||
// mapping an invalid rect retains the property that rect.is_empty()
|
||||
// will return true (the mapped rect output will have size 0 instead
|
||||
// of a negative size). In future we could catch / assert / fix
|
||||
// these invalid rects earlier, and assert here instead.
|
||||
|
||||
let w = rect.width().max(0.0);
|
||||
let h = rect.height().max(0.0);
|
||||
|
||||
let mut x0 = rect.min.x * self.scale.x + self.offset.x;
|
||||
let mut y0 = rect.min.y * self.scale.y + self.offset.y;
|
||||
|
||||
let mut sx = w * self.scale.x;
|
||||
let mut sy = h * self.scale.y;
|
||||
// Handle negative scale. Previously, branchless float math was used to find the
|
||||
// min / max vertices and size. However, that sequence of operations was producind
|
||||
// additional floating point accuracy on android emulator builds, causing one test
|
||||
// to fail an assert. Instead, we retain the same math as previously, and adjust
|
||||
// the origin / size if required.
|
||||
|
||||
if self.scale.x < 0.0 {
|
||||
x0 += sx;
|
||||
sx = -sx;
|
||||
}
|
||||
if self.scale.y < 0.0 {
|
||||
y0 += sy;
|
||||
sy = -sy;
|
||||
}
|
||||
|
||||
Box2D::from_origin_and_size(
|
||||
Point2D::new(x0, y0),
|
||||
Size2D::new(sx, sy),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn unmap_rect<F, T>(&self, rect: &Box2D<f32, F>) -> Box2D<f32, T> {
|
||||
// TODO(gw): The logic below can return an unexpected result if the supplied
|
||||
// rect is invalid (has size < 0). Since Gecko currently supplied
|
||||
// invalid rects in some cases, adding a max(0) here ensures that
|
||||
// mapping an invalid rect retains the property that rect.is_empty()
|
||||
// will return true (the mapped rect output will have size 0 instead
|
||||
// of a negative size). In future we could catch / assert / fix
|
||||
// these invalid rects earlier, and assert here instead.
|
||||
|
||||
let w = rect.width().max(0.0);
|
||||
let h = rect.height().max(0.0);
|
||||
|
||||
let mut x0 = (rect.min.x - self.offset.x) / self.scale.x;
|
||||
let mut y0 = (rect.min.y - self.offset.y) / self.scale.y;
|
||||
|
||||
let mut sx = w / self.scale.x;
|
||||
let mut sy = h / self.scale.y;
|
||||
|
||||
// Handle negative scale. Previously, branchless float math was used to find the
|
||||
// min / max vertices and size. However, that sequence of operations was producind
|
||||
// additional floating point accuracy on android emulator builds, causing one test
|
||||
// to fail an assert. Instead, we retain the same math as previously, and adjust
|
||||
// the origin / size if required.
|
||||
|
||||
if self.scale.x < 0.0 {
|
||||
x0 += sx;
|
||||
sx = -sx;
|
||||
}
|
||||
if self.scale.y < 0.0 {
|
||||
y0 += sy;
|
||||
sy = -sy;
|
||||
}
|
||||
|
||||
Box2D::from_origin_and_size(
|
||||
Point2D::new(x0, y0),
|
||||
Size2D::new(sx, sy),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn map_vector<F, T>(&self, vector: &Vector2D<f32, F>) -> Vector2D<f32, T> {
|
||||
Vector2D::new(
|
||||
vector.x * self.scale.x,
|
||||
vector.y * self.scale.y,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn unmap_vector<F, T>(&self, vector: &Vector2D<f32, F>) -> Vector2D<f32, T> {
|
||||
Vector2D::new(
|
||||
vector.x / self.scale.x,
|
||||
vector.y / self.scale.y,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn map_point<F, T>(&self, point: &Point2D<f32, F>) -> Point2D<f32, T> {
|
||||
Point2D::new(
|
||||
point.x * self.scale.x + self.offset.x,
|
||||
point.y * self.scale.y + self.offset.y,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn unmap_point<F, T>(&self, point: &Point2D<f32, F>) -> Point2D<f32, T> {
|
||||
Point2D::new(
|
||||
(point.x - self.offset.x) / self.scale.x,
|
||||
(point.y - self.offset.y) / self.scale.y,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_transform<F, T>(&self) -> Transform3D<f32, F, T> {
|
||||
Transform3D::new(
|
||||
self.scale.x,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
|
||||
0.0,
|
||||
self.scale.y,
|
||||
0.0,
|
||||
0.0,
|
||||
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
|
||||
self.offset.x,
|
||||
self.offset.y,
|
||||
0.0,
|
||||
1.0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Implement these in euclid!
|
||||
pub trait MatrixHelpers<Src, Dst> {
|
||||
/// A port of the preserves2dAxisAlignment function in Skia.
|
||||
|
|
|
@ -391,6 +391,7 @@ pub struct TextDisplayItem {
|
|||
pub font_key: font::FontInstanceKey,
|
||||
pub color: ColorF,
|
||||
pub glyph_options: Option<font::GlyphOptions>,
|
||||
pub reference_frame_relative_offset: LayoutVector2D,
|
||||
} // IMPLICIT: glyphs: Vec<font::GlyphInstance>
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize, PeekPoke)]
|
||||
|
@ -646,6 +647,9 @@ pub struct GradientDisplayItem {
|
|||
/// The space between tiles of the gradient (common case: 0)
|
||||
pub tile_spacing: LayoutSize,
|
||||
pub gradient: Gradient,
|
||||
/// The unsnapped rect is used for calculating repeats, so is replicated here
|
||||
/// to maintain existing behavior.
|
||||
pub unsnapped_rect: LayoutRect,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
@ -709,6 +713,9 @@ pub struct RadialGradientDisplayItem {
|
|||
pub gradient: RadialGradient,
|
||||
pub tile_size: LayoutSize,
|
||||
pub tile_spacing: LayoutSize,
|
||||
/// The unsnapped rect is used for calculating repeats, so is replicated here
|
||||
/// to maintain existing behavior.
|
||||
pub unsnapped_rect: LayoutRect,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
|
||||
|
@ -721,6 +728,9 @@ pub struct ConicGradientDisplayItem {
|
|||
pub gradient: ConicGradient,
|
||||
pub tile_size: LayoutSize,
|
||||
pub tile_spacing: LayoutSize,
|
||||
/// The unsnapped rect is used for calculating repeats, so is replicated here
|
||||
/// to maintain existing behavior.
|
||||
pub unsnapped_rect: LayoutRect,
|
||||
}
|
||||
|
||||
/// Renders a filtered region of its backdrop
|
||||
|
@ -1307,6 +1317,9 @@ pub struct RepeatingImageDisplayItem {
|
|||
pub alpha_type: AlphaType,
|
||||
/// A hack used by gecko to color a simple bitmap font used for tofu glyphs
|
||||
pub color: ColorF,
|
||||
/// The unsnapped rect is used for calculating repeats, so is replicated here
|
||||
/// to maintain existing behavior.
|
||||
pub unsnapped_rect: LayoutRect,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
|
|
|
@ -26,6 +26,7 @@ use crate::color::ColorF;
|
|||
use crate::font::{FontInstanceKey, GlyphInstance, GlyphOptions};
|
||||
use crate::image::{ColorDepth, ImageKey};
|
||||
use crate::units::*;
|
||||
use crate::util::{RectHelpers, ScaleOffset};
|
||||
|
||||
|
||||
// We don't want to push a long text-run. If a text-run is too long, split it into several parts.
|
||||
|
@ -1016,6 +1017,26 @@ pub enum DisplayListSection {
|
|||
Chunk,
|
||||
}
|
||||
|
||||
/// A small portion of a normal spatial node that we store during DL construction to
|
||||
/// enable snapping and reference frame <-> stacking context coord mapping. In future
|
||||
/// we'll aim to remove this and have the full spatial tree available during DL build.
|
||||
#[derive(Clone)]
|
||||
pub struct SpatialNodeInfo {
|
||||
/// The total external scroll offset applicable at this node
|
||||
accumulated_external_scroll_offset: LayoutVector2D,
|
||||
/// The 2d-axis-aligned snapping transform, if this node is in the root coord space
|
||||
snapping_transform: Option<ScaleOffset>,
|
||||
}
|
||||
|
||||
impl SpatialNodeInfo {
|
||||
fn identity() -> Self {
|
||||
SpatialNodeInfo {
|
||||
accumulated_external_scroll_offset: LayoutVector2D::zero(),
|
||||
snapping_transform: Some(ScaleOffset::identity()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DisplayListBuilder {
|
||||
payload: DisplayListPayload,
|
||||
|
@ -1033,6 +1054,12 @@ pub struct DisplayListBuilder {
|
|||
|
||||
cache_size: usize,
|
||||
serialized_content_buffer: Option<String>,
|
||||
|
||||
/// Helper struct to map stacking context coords <-> reference frame coords.
|
||||
rf_mapper: ReferenceFrameMapper,
|
||||
|
||||
/// Minimal info about encountered spatial nodes to allow snapping during DL building
|
||||
spatial_nodes: Vec<SpatialNodeInfo>,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
@ -1078,6 +1105,9 @@ impl DisplayListBuilder {
|
|||
save_state: None,
|
||||
cache_size: 0,
|
||||
serialized_content_buffer: None,
|
||||
|
||||
rf_mapper: ReferenceFrameMapper::new(),
|
||||
spatial_nodes: vec![SpatialNodeInfo::identity(); FIRST_SPATIAL_NODE_INDEX + 1],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1260,14 +1290,61 @@ impl DisplayListBuilder {
|
|||
Self::push_iter_impl(&mut buffer, iter);
|
||||
}
|
||||
|
||||
/// Apply snapping and coord space mapping to item props
|
||||
fn process_common_props(
|
||||
&mut self,
|
||||
common: &di::CommonItemProperties,
|
||||
) -> di::CommonItemProperties {
|
||||
let current_offset = self.current_offset(common.spatial_id);
|
||||
|
||||
let clip_rect = self.snap_rect(
|
||||
&common.clip_rect.translate(current_offset),
|
||||
common.spatial_id,
|
||||
);
|
||||
|
||||
di::CommonItemProperties {
|
||||
clip_rect,
|
||||
..*common
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply snapping and coord space mapping to item props + prim bounds
|
||||
fn process_common_props_with_bounds(
|
||||
&mut self,
|
||||
bounds: &LayoutRect,
|
||||
common: &di::CommonItemProperties,
|
||||
) -> (LayoutRect, di::CommonItemProperties) {
|
||||
let current_offset = self.current_offset(common.spatial_id);
|
||||
|
||||
let clip_rect = self.snap_rect(
|
||||
&common.clip_rect.translate(current_offset),
|
||||
common.spatial_id,
|
||||
);
|
||||
|
||||
let bounds = self.snap_rect(
|
||||
&bounds.translate(current_offset),
|
||||
common.spatial_id,
|
||||
);
|
||||
|
||||
(
|
||||
bounds,
|
||||
di::CommonItemProperties {
|
||||
clip_rect,
|
||||
..*common
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
pub fn push_rect(
|
||||
&mut self,
|
||||
common: &di::CommonItemProperties,
|
||||
bounds: LayoutRect,
|
||||
color: ColorF,
|
||||
) {
|
||||
let (bounds, common) = self.process_common_props_with_bounds(&bounds, common);
|
||||
|
||||
let item = di::DisplayItem::Rectangle(di::RectangleDisplayItem {
|
||||
common: *common,
|
||||
common,
|
||||
color: PropertyBinding::Value(color),
|
||||
bounds,
|
||||
});
|
||||
|
@ -1280,8 +1357,10 @@ impl DisplayListBuilder {
|
|||
bounds: LayoutRect,
|
||||
color: PropertyBinding<ColorF>,
|
||||
) {
|
||||
let (bounds, common) = self.process_common_props_with_bounds(&bounds, common);
|
||||
|
||||
let item = di::DisplayItem::Rectangle(di::RectangleDisplayItem {
|
||||
common: *common,
|
||||
common,
|
||||
color,
|
||||
bounds,
|
||||
});
|
||||
|
@ -1293,8 +1372,10 @@ impl DisplayListBuilder {
|
|||
common: &di::CommonItemProperties,
|
||||
bounds: LayoutRect,
|
||||
) {
|
||||
let (bounds, common) = self.process_common_props_with_bounds(&bounds, common);
|
||||
|
||||
let item = di::DisplayItem::ClearRectangle(di::ClearRectangleDisplayItem {
|
||||
common: *common,
|
||||
common,
|
||||
bounds,
|
||||
});
|
||||
self.push_item(&item);
|
||||
|
@ -1305,8 +1386,10 @@ impl DisplayListBuilder {
|
|||
common: &di::CommonItemProperties,
|
||||
tag: di::ItemTag,
|
||||
) {
|
||||
let common = self.process_common_props(common);
|
||||
|
||||
let item = di::DisplayItem::HitTest(di::HitTestDisplayItem {
|
||||
common: *common,
|
||||
common,
|
||||
tag,
|
||||
});
|
||||
self.push_item(&item);
|
||||
|
@ -1321,8 +1404,10 @@ impl DisplayListBuilder {
|
|||
color: &ColorF,
|
||||
style: di::LineStyle,
|
||||
) {
|
||||
let common = self.process_common_props(common);
|
||||
|
||||
let item = di::DisplayItem::Line(di::LineDisplayItem {
|
||||
common: *common,
|
||||
common,
|
||||
area: *area,
|
||||
wavy_line_thickness,
|
||||
orientation,
|
||||
|
@ -1342,8 +1427,10 @@ impl DisplayListBuilder {
|
|||
key: ImageKey,
|
||||
color: ColorF,
|
||||
) {
|
||||
let (bounds, common) = self.process_common_props_with_bounds(&bounds, common);
|
||||
|
||||
let item = di::DisplayItem::Image(di::ImageDisplayItem {
|
||||
common: *common,
|
||||
common,
|
||||
bounds,
|
||||
image_key: key,
|
||||
image_rendering,
|
||||
|
@ -1357,7 +1444,7 @@ impl DisplayListBuilder {
|
|||
pub fn push_repeating_image(
|
||||
&mut self,
|
||||
common: &di::CommonItemProperties,
|
||||
bounds: LayoutRect,
|
||||
rect: LayoutRect,
|
||||
stretch_size: LayoutSize,
|
||||
tile_spacing: LayoutSize,
|
||||
image_rendering: di::ImageRendering,
|
||||
|
@ -1365,8 +1452,10 @@ impl DisplayListBuilder {
|
|||
key: ImageKey,
|
||||
color: ColorF,
|
||||
) {
|
||||
let (bounds, common) = self.process_common_props_with_bounds(&rect, common);
|
||||
|
||||
let item = di::DisplayItem::RepeatingImage(di::RepeatingImageDisplayItem {
|
||||
common: *common,
|
||||
common,
|
||||
bounds,
|
||||
image_key: key,
|
||||
stretch_size,
|
||||
|
@ -1374,6 +1463,7 @@ impl DisplayListBuilder {
|
|||
image_rendering,
|
||||
alpha_type,
|
||||
color,
|
||||
unsnapped_rect: rect,
|
||||
});
|
||||
|
||||
self.push_item(&item);
|
||||
|
@ -1390,8 +1480,10 @@ impl DisplayListBuilder {
|
|||
color_range: di::ColorRange,
|
||||
image_rendering: di::ImageRendering,
|
||||
) {
|
||||
let (bounds, common) = self.process_common_props_with_bounds(&bounds, common);
|
||||
|
||||
let item = di::DisplayItem::YuvImage(di::YuvImageDisplayItem {
|
||||
common: *common,
|
||||
common,
|
||||
bounds,
|
||||
yuv_data,
|
||||
color_depth,
|
||||
|
@ -1411,17 +1503,29 @@ impl DisplayListBuilder {
|
|||
color: ColorF,
|
||||
glyph_options: Option<GlyphOptions>,
|
||||
) {
|
||||
let (bounds, common) = self.process_common_props_with_bounds(&bounds, common);
|
||||
|
||||
let current_offset = self.current_offset(common.spatial_id);
|
||||
|
||||
let item = di::DisplayItem::Text(di::TextDisplayItem {
|
||||
common: *common,
|
||||
common,
|
||||
bounds,
|
||||
color,
|
||||
font_key,
|
||||
glyph_options,
|
||||
reference_frame_relative_offset: current_offset,
|
||||
});
|
||||
|
||||
let prim_offset = bounds.min.to_vector() - current_offset;
|
||||
|
||||
for split_glyphs in glyphs.chunks(MAX_TEXT_RUN_LENGTH) {
|
||||
self.push_item(&item);
|
||||
self.push_iter(split_glyphs);
|
||||
self.push_iter(split_glyphs.iter().map(|glyph| {
|
||||
GlyphInstance {
|
||||
index: glyph.index,
|
||||
point: glyph.point - prim_offset,
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1477,8 +1581,10 @@ impl DisplayListBuilder {
|
|||
widths: LayoutSideOffsets,
|
||||
details: di::BorderDetails,
|
||||
) {
|
||||
let (bounds, common) = self.process_common_props_with_bounds(&bounds, common);
|
||||
|
||||
let item = di::DisplayItem::Border(di::BorderDisplayItem {
|
||||
common: *common,
|
||||
common,
|
||||
bounds,
|
||||
details,
|
||||
widths,
|
||||
|
@ -1498,8 +1604,10 @@ impl DisplayListBuilder {
|
|||
border_radius: di::BorderRadius,
|
||||
clip_mode: di::BoxShadowClipMode,
|
||||
) {
|
||||
let (box_bounds, common) = self.process_common_props_with_bounds(&box_bounds, common);
|
||||
|
||||
let item = di::DisplayItem::BoxShadow(di::BoxShadowDisplayItem {
|
||||
common: *common,
|
||||
common,
|
||||
box_bounds,
|
||||
offset,
|
||||
color,
|
||||
|
@ -1529,17 +1637,20 @@ impl DisplayListBuilder {
|
|||
pub fn push_gradient(
|
||||
&mut self,
|
||||
common: &di::CommonItemProperties,
|
||||
bounds: LayoutRect,
|
||||
rect: LayoutRect,
|
||||
gradient: di::Gradient,
|
||||
tile_size: LayoutSize,
|
||||
tile_spacing: LayoutSize,
|
||||
) {
|
||||
let (bounds, common) = self.process_common_props_with_bounds(&rect, common);
|
||||
|
||||
let item = di::DisplayItem::Gradient(di::GradientDisplayItem {
|
||||
common: *common,
|
||||
common,
|
||||
bounds,
|
||||
gradient,
|
||||
tile_size,
|
||||
tile_spacing,
|
||||
unsnapped_rect: rect,
|
||||
});
|
||||
|
||||
self.push_item(&item);
|
||||
|
@ -1551,17 +1662,20 @@ impl DisplayListBuilder {
|
|||
pub fn push_radial_gradient(
|
||||
&mut self,
|
||||
common: &di::CommonItemProperties,
|
||||
bounds: LayoutRect,
|
||||
rect: LayoutRect,
|
||||
gradient: di::RadialGradient,
|
||||
tile_size: LayoutSize,
|
||||
tile_spacing: LayoutSize,
|
||||
) {
|
||||
let (bounds, common) = self.process_common_props_with_bounds(&rect, common);
|
||||
|
||||
let item = di::DisplayItem::RadialGradient(di::RadialGradientDisplayItem {
|
||||
common: *common,
|
||||
common,
|
||||
bounds,
|
||||
gradient,
|
||||
tile_size,
|
||||
tile_spacing,
|
||||
unsnapped_rect: rect,
|
||||
});
|
||||
|
||||
self.push_item(&item);
|
||||
|
@ -1573,17 +1687,20 @@ impl DisplayListBuilder {
|
|||
pub fn push_conic_gradient(
|
||||
&mut self,
|
||||
common: &di::CommonItemProperties,
|
||||
bounds: LayoutRect,
|
||||
rect: LayoutRect,
|
||||
gradient: di::ConicGradient,
|
||||
tile_size: LayoutSize,
|
||||
tile_spacing: LayoutSize,
|
||||
) {
|
||||
let (bounds, common) = self.process_common_props_with_bounds(&rect, common);
|
||||
|
||||
let item = di::DisplayItem::ConicGradient(di::ConicGradientDisplayItem {
|
||||
common: *common,
|
||||
common,
|
||||
bounds,
|
||||
gradient,
|
||||
tile_size,
|
||||
tile_spacing,
|
||||
unsnapped_rect: rect,
|
||||
});
|
||||
|
||||
self.push_item(&item);
|
||||
|
@ -1599,6 +1716,39 @@ impl DisplayListBuilder {
|
|||
) -> di::SpatialId {
|
||||
let id = self.generate_spatial_index();
|
||||
|
||||
let current_offset = self.current_offset(parent_spatial_id);
|
||||
let origin = origin + current_offset;
|
||||
|
||||
let parent = &self.spatial_nodes[parent_spatial_id.0];
|
||||
|
||||
let snapping_transform = parent.snapping_transform.and_then(|parent| {
|
||||
let snapping_transform = match transform {
|
||||
PropertyBinding::Value(ref value) => {
|
||||
// We can only get a ScaleOffset if the transform is 2d axis
|
||||
// aligned.
|
||||
ScaleOffset::from_transform(value).map(|scale_offset| {
|
||||
ScaleOffset::from_offset(origin.to_vector().to_untyped())
|
||||
.accumulate(&scale_offset)
|
||||
})
|
||||
}
|
||||
|
||||
// Assume animations start at the identity transform for snapping purposes.
|
||||
// We still want to incorporate the reference frame offset however.
|
||||
// TODO(aosmond): Is there a better known starting point?
|
||||
PropertyBinding::Binding(..) => {
|
||||
Some(ScaleOffset::from_offset(origin.to_vector().to_untyped()))
|
||||
}
|
||||
};
|
||||
|
||||
snapping_transform.map(|ref s| parent.accumulate(s))
|
||||
});
|
||||
|
||||
self.add_spatial_node_info(
|
||||
id,
|
||||
LayoutVector2D::zero(),
|
||||
snapping_transform,
|
||||
);
|
||||
|
||||
let item = di::DisplayItem::PushReferenceFrame(di::ReferenceFrameDisplayListItem {
|
||||
parent_spatial_id,
|
||||
origin,
|
||||
|
@ -1612,6 +1762,7 @@ impl DisplayListBuilder {
|
|||
},
|
||||
});
|
||||
|
||||
self.rf_mapper.push_scope();
|
||||
self.push_item(&item);
|
||||
id
|
||||
}
|
||||
|
@ -1626,6 +1777,9 @@ impl DisplayListBuilder {
|
|||
) -> di::SpatialId {
|
||||
let id = self.generate_spatial_index();
|
||||
|
||||
let current_offset = self.current_offset(parent_spatial_id);
|
||||
let origin = origin + current_offset;
|
||||
|
||||
let item = di::DisplayItem::PushReferenceFrame(di::ReferenceFrameDisplayListItem {
|
||||
parent_spatial_id,
|
||||
origin,
|
||||
|
@ -1649,6 +1803,7 @@ impl DisplayListBuilder {
|
|||
}
|
||||
|
||||
pub fn pop_reference_frame(&mut self) {
|
||||
self.rf_mapper.pop_scope();
|
||||
self.push_item(&di::DisplayItem::PopReferenceFrame);
|
||||
}
|
||||
|
||||
|
@ -1668,8 +1823,10 @@ impl DisplayListBuilder {
|
|||
) {
|
||||
self.push_filters(filters, filter_datas, filter_primitives);
|
||||
|
||||
let current_offset = self.current_offset(spatial_id);
|
||||
|
||||
let item = di::DisplayItem::PushStackingContext(di::PushStackingContextDisplayItem {
|
||||
origin,
|
||||
origin: origin + current_offset,
|
||||
spatial_id,
|
||||
prim_flags,
|
||||
stacking_context: di::StackingContext {
|
||||
|
@ -1681,6 +1838,8 @@ impl DisplayListBuilder {
|
|||
},
|
||||
});
|
||||
|
||||
self.rf_mapper.push_offset(origin.to_vector());
|
||||
|
||||
self.push_item(&item);
|
||||
}
|
||||
|
||||
|
@ -1727,6 +1886,7 @@ impl DisplayListBuilder {
|
|||
}
|
||||
|
||||
pub fn pop_stacking_context(&mut self) {
|
||||
self.rf_mapper.pop_offset();
|
||||
self.push_item(&di::DisplayItem::PopStackingContext);
|
||||
}
|
||||
|
||||
|
@ -1745,10 +1905,12 @@ impl DisplayListBuilder {
|
|||
filter_datas: &[di::FilterData],
|
||||
filter_primitives: &[di::FilterPrimitive],
|
||||
) {
|
||||
let common = self.process_common_props(common);
|
||||
|
||||
self.push_filters(filters, filter_datas, filter_primitives);
|
||||
|
||||
let item = di::DisplayItem::BackdropFilter(di::BackdropFilterDisplayItem {
|
||||
common: *common,
|
||||
common,
|
||||
});
|
||||
self.push_item(&item);
|
||||
}
|
||||
|
@ -1807,6 +1969,19 @@ impl DisplayListBuilder {
|
|||
external_scroll_offset: LayoutVector2D,
|
||||
) -> di::SpatialId {
|
||||
let scroll_frame_id = self.generate_spatial_index();
|
||||
|
||||
let current_offset = self.current_offset(parent_space);
|
||||
let content_rect = content_rect.translate(current_offset);
|
||||
let frame_rect = frame_rect.translate(current_offset);
|
||||
|
||||
let parent = self.spatial_nodes[parent_space.0].clone();
|
||||
|
||||
self.add_spatial_node_info(
|
||||
scroll_frame_id,
|
||||
parent.accumulated_external_scroll_offset + external_scroll_offset,
|
||||
parent.snapping_transform,
|
||||
);
|
||||
|
||||
let item = di::DisplayItem::ScrollFrame(di::ScrollFrameDisplayItem {
|
||||
content_rect,
|
||||
frame_rect,
|
||||
|
@ -1840,11 +2015,18 @@ impl DisplayListBuilder {
|
|||
pub fn define_clip_image_mask(
|
||||
&mut self,
|
||||
parent_space_and_clip: &di::SpaceAndClipInfo,
|
||||
image_mask: di::ImageMask,
|
||||
mut image_mask: di::ImageMask,
|
||||
points: &[LayoutPoint],
|
||||
fill_rule: di::FillRule,
|
||||
) -> di::ClipId {
|
||||
let id = self.generate_clip_index();
|
||||
|
||||
let current_offset = self.current_offset(parent_space_and_clip.spatial_id);
|
||||
image_mask.rect = self.snap_rect(
|
||||
&image_mask.rect.translate(current_offset),
|
||||
parent_space_and_clip.spatial_id,
|
||||
);
|
||||
|
||||
let item = di::DisplayItem::ImageMaskClip(di::ImageMaskClipDisplayItem {
|
||||
id,
|
||||
parent_space_and_clip: *parent_space_and_clip,
|
||||
|
@ -1870,6 +2052,14 @@ impl DisplayListBuilder {
|
|||
clip_rect: LayoutRect,
|
||||
) -> di::ClipId {
|
||||
let id = self.generate_clip_index();
|
||||
|
||||
let current_offset = self.current_offset(parent_space_and_clip.spatial_id);
|
||||
|
||||
let clip_rect = self.snap_rect(
|
||||
&clip_rect.translate(current_offset),
|
||||
parent_space_and_clip.spatial_id,
|
||||
);
|
||||
|
||||
let item = di::DisplayItem::RectClip(di::RectClipDisplayItem {
|
||||
id,
|
||||
parent_space_and_clip: *parent_space_and_clip,
|
||||
|
@ -1883,9 +2073,16 @@ impl DisplayListBuilder {
|
|||
pub fn define_clip_rounded_rect(
|
||||
&mut self,
|
||||
parent_space_and_clip: &di::SpaceAndClipInfo,
|
||||
clip: di::ComplexClipRegion,
|
||||
mut clip: di::ComplexClipRegion,
|
||||
) -> di::ClipId {
|
||||
let id = self.generate_clip_index();
|
||||
|
||||
let current_offset = self.current_offset(parent_space_and_clip.spatial_id);
|
||||
clip.rect = self.snap_rect(
|
||||
&clip.rect.translate(current_offset),
|
||||
parent_space_and_clip.spatial_id,
|
||||
);
|
||||
|
||||
let item = di::DisplayItem::RoundedRectClip(di::RoundedRectClipDisplayItem {
|
||||
id,
|
||||
parent_space_and_clip: *parent_space_and_clip,
|
||||
|
@ -1906,6 +2103,18 @@ impl DisplayListBuilder {
|
|||
previously_applied_offset: LayoutVector2D,
|
||||
) -> di::SpatialId {
|
||||
let id = self.generate_spatial_index();
|
||||
|
||||
let current_offset = self.current_offset(parent_spatial_id);
|
||||
let frame_rect = frame_rect.translate(current_offset);
|
||||
|
||||
let parent = self.spatial_nodes[parent_spatial_id.0].clone();
|
||||
|
||||
self.add_spatial_node_info(
|
||||
id,
|
||||
parent.accumulated_external_scroll_offset,
|
||||
parent.snapping_transform,
|
||||
);
|
||||
|
||||
let item = di::DisplayItem::StickyFrame(di::StickyFrameDisplayItem {
|
||||
parent_spatial_id,
|
||||
id,
|
||||
|
@ -1928,6 +2137,18 @@ impl DisplayListBuilder {
|
|||
pipeline_id: PipelineId,
|
||||
ignore_missing_pipeline: bool
|
||||
) {
|
||||
let current_offset = self.current_offset(space_and_clip.spatial_id);
|
||||
|
||||
let bounds = self.snap_rect(
|
||||
&bounds.translate(current_offset),
|
||||
space_and_clip.spatial_id,
|
||||
);
|
||||
|
||||
let clip_rect = self.snap_rect(
|
||||
&clip_rect.translate(current_offset),
|
||||
space_and_clip.spatial_id,
|
||||
);
|
||||
|
||||
let item = di::DisplayItem::Iframe(di::IframeDisplayItem {
|
||||
bounds,
|
||||
clip_rect,
|
||||
|
@ -2047,4 +2268,124 @@ impl DisplayListBuilder {
|
|||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Retrieve the current offset to allow converting a stacking context
|
||||
/// relative coordinate to be relative to the owing reference frame,
|
||||
/// also considering any external scroll offset on the provided
|
||||
/// spatial node.
|
||||
fn current_offset(
|
||||
&mut self,
|
||||
spatial_id: di::SpatialId,
|
||||
) -> LayoutVector2D {
|
||||
// Get the current offset from stacking context <-> reference frame space.
|
||||
let rf_offset = self.rf_mapper.current_offset();
|
||||
|
||||
// Get the external scroll offset, if applicable.
|
||||
let scroll_offset = self.spatial_nodes[spatial_id.0].accumulated_external_scroll_offset;
|
||||
|
||||
rf_offset + scroll_offset
|
||||
}
|
||||
|
||||
/// Add info about a spatial node that is needed during DL building.
|
||||
fn add_spatial_node_info(
|
||||
&mut self,
|
||||
id: di::SpatialId,
|
||||
accumulated_external_scroll_offset: LayoutVector2D,
|
||||
snapping_transform: Option<ScaleOffset>,
|
||||
) {
|
||||
self.spatial_nodes.resize(id.0 + 1, SpatialNodeInfo::identity());
|
||||
|
||||
let info = &mut self.spatial_nodes[id.0];
|
||||
info.accumulated_external_scroll_offset = accumulated_external_scroll_offset;
|
||||
info.snapping_transform = snapping_transform;
|
||||
}
|
||||
|
||||
/// Snap a local rect, if applicable
|
||||
fn snap_rect(
|
||||
&self,
|
||||
rect: &LayoutRect,
|
||||
spatial_id: di::SpatialId,
|
||||
) -> LayoutRect {
|
||||
match self.spatial_nodes[spatial_id.0].snapping_transform {
|
||||
Some(ref scale_offset) => {
|
||||
let snapped_device_rect: LayoutRect = scale_offset.map_rect(rect).snap();
|
||||
scale_offset.unmap_rect(&snapped_device_rect)
|
||||
}
|
||||
None => *rect,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The offset stack for a given reference frame.
|
||||
#[derive(Clone)]
|
||||
struct ReferenceFrameState {
|
||||
/// A stack of current offsets from the current reference frame scope.
|
||||
offsets: Vec<LayoutVector2D>,
|
||||
}
|
||||
|
||||
/// Maps from stacking context layout coordinates into reference frame
|
||||
/// relative coordinates.
|
||||
#[derive(Clone)]
|
||||
struct ReferenceFrameMapper {
|
||||
/// A stack of reference frame scopes.
|
||||
frames: Vec<ReferenceFrameState>,
|
||||
}
|
||||
|
||||
impl ReferenceFrameMapper {
|
||||
fn new() -> Self {
|
||||
ReferenceFrameMapper {
|
||||
frames: vec![
|
||||
ReferenceFrameState {
|
||||
offsets: vec![
|
||||
LayoutVector2D::zero(),
|
||||
],
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
/// Push a new scope. This resets the current offset to zero, and is
|
||||
/// used when a new reference frame or iframe is pushed.
|
||||
fn push_scope(&mut self) {
|
||||
self.frames.push(ReferenceFrameState {
|
||||
offsets: vec![
|
||||
LayoutVector2D::zero(),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
/// Pop a reference frame scope off the stack.
|
||||
fn pop_scope(&mut self) {
|
||||
self.frames.pop().unwrap();
|
||||
}
|
||||
|
||||
/// Push a new offset for the current scope. This is used when
|
||||
/// a new stacking context is pushed.
|
||||
fn push_offset(&mut self, offset: LayoutVector2D) {
|
||||
let frame = self.frames.last_mut().unwrap();
|
||||
let current_offset = *frame.offsets.last().unwrap();
|
||||
frame.offsets.push(current_offset + offset);
|
||||
}
|
||||
|
||||
/// Pop a local stacking context offset from the current scope.
|
||||
fn pop_offset(&mut self) {
|
||||
let frame = self.frames.last_mut().unwrap();
|
||||
frame.offsets.pop().unwrap();
|
||||
}
|
||||
|
||||
/// Retrieve the current offset to allow converting a stacking context
|
||||
/// relative coordinate to be relative to the owing reference frame.
|
||||
/// TODO(gw): We could perhaps have separate coordinate spaces for this,
|
||||
/// however that's going to either mean a lot of changes to
|
||||
/// public API code, or a lot of changes to internal code.
|
||||
/// Before doing that, we should revisit how Gecko would
|
||||
/// prefer to provide coordinates.
|
||||
/// TODO(gw): For now, this includes only the reference frame relative
|
||||
/// offset. Soon, we will expand this to include the initial
|
||||
/// scroll offsets that are now available on scroll nodes. This
|
||||
/// will allow normalizing the coordinates even between display
|
||||
/// lists where APZ has scrolled the content.
|
||||
fn current_offset(&self) -> LayoutVector2D {
|
||||
*self.frames.last().unwrap().offsets.last().unwrap()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ mod font;
|
|||
mod gradient_builder;
|
||||
mod image;
|
||||
pub mod units;
|
||||
mod util;
|
||||
|
||||
pub use crate::color::*;
|
||||
pub use crate::display_item::*;
|
||||
|
@ -56,6 +57,7 @@ pub use crate::display_list::*;
|
|||
pub use crate::font::*;
|
||||
pub use crate::gradient_builder::*;
|
||||
pub use crate::image::*;
|
||||
pub use crate::util::ScaleOffset;
|
||||
|
||||
use crate::units::*;
|
||||
use crate::channel::Receiver;
|
||||
|
|
|
@ -0,0 +1,266 @@
|
|||
/* 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/. */
|
||||
|
||||
use euclid::{Point2D, Box2D, Size2D, Vector2D};
|
||||
use euclid::{default, Transform3D};
|
||||
|
||||
// Matches the definition of SK_ScalarNearlyZero in Skia.
|
||||
const NEARLY_ZERO: f32 = 1.0 / 4096.0;
|
||||
|
||||
// Represents an optimized transform where there is only
|
||||
// a scale and translation (which are guaranteed to maintain
|
||||
// an axis align rectangle under transformation). The
|
||||
// scaling is applied first, followed by the translation.
|
||||
// TODO(gw): We should try and incorporate F <-> T units here,
|
||||
// but it's a bit tricky to do that now with the
|
||||
// way the current spatial tree works.
|
||||
#[derive(Debug, Clone, Copy, MallocSizeOf)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ScaleOffset {
|
||||
pub scale: default::Vector2D<f32>,
|
||||
pub offset: default::Vector2D<f32>,
|
||||
}
|
||||
|
||||
impl ScaleOffset {
|
||||
pub fn identity() -> Self {
|
||||
ScaleOffset {
|
||||
scale: Vector2D::new(1.0, 1.0),
|
||||
offset: Vector2D::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
// Construct a ScaleOffset from a transform. Returns
|
||||
// None if the matrix is not a pure scale / translation.
|
||||
pub fn from_transform<F, T>(
|
||||
m: &Transform3D<f32, F, T>,
|
||||
) -> Option<ScaleOffset> {
|
||||
// To check that we have a pure scale / translation:
|
||||
// Every field must match an identity matrix, except:
|
||||
// - Any value present in tx,ty
|
||||
// - Any value present in sx,sy
|
||||
|
||||
if m.m12.abs() > NEARLY_ZERO ||
|
||||
m.m13.abs() > NEARLY_ZERO ||
|
||||
m.m14.abs() > NEARLY_ZERO ||
|
||||
m.m21.abs() > NEARLY_ZERO ||
|
||||
m.m23.abs() > NEARLY_ZERO ||
|
||||
m.m24.abs() > NEARLY_ZERO ||
|
||||
m.m31.abs() > NEARLY_ZERO ||
|
||||
m.m32.abs() > NEARLY_ZERO ||
|
||||
(m.m33 - 1.0).abs() > NEARLY_ZERO ||
|
||||
m.m34.abs() > NEARLY_ZERO ||
|
||||
m.m43.abs() > NEARLY_ZERO ||
|
||||
(m.m44 - 1.0).abs() > NEARLY_ZERO {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(ScaleOffset {
|
||||
scale: Vector2D::new(m.m11, m.m22),
|
||||
offset: Vector2D::new(m.m41, m.m42),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_offset(offset: default::Vector2D<f32>) -> Self {
|
||||
ScaleOffset {
|
||||
scale: Vector2D::new(1.0, 1.0),
|
||||
offset,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_scale(scale: default::Vector2D<f32>) -> Self {
|
||||
ScaleOffset {
|
||||
scale,
|
||||
offset: Vector2D::new(0.0, 0.0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inverse(&self) -> Self {
|
||||
ScaleOffset {
|
||||
scale: Vector2D::new(
|
||||
1.0 / self.scale.x,
|
||||
1.0 / self.scale.y,
|
||||
),
|
||||
offset: Vector2D::new(
|
||||
-self.offset.x / self.scale.x,
|
||||
-self.offset.y / self.scale.y,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn offset(&self, offset: default::Vector2D<f32>) -> Self {
|
||||
self.accumulate(
|
||||
&ScaleOffset {
|
||||
scale: Vector2D::new(1.0, 1.0),
|
||||
offset,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
pub fn scale(&self, scale: f32) -> Self {
|
||||
self.accumulate(
|
||||
&ScaleOffset {
|
||||
scale: Vector2D::new(scale, scale),
|
||||
offset: Vector2D::zero(),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Produce a ScaleOffset that includes both self and other.
|
||||
/// The 'self' ScaleOffset is applied after other.
|
||||
/// This is equivalent to `Transform3D::pre_transform`.
|
||||
pub fn accumulate(&self, other: &ScaleOffset) -> Self {
|
||||
ScaleOffset {
|
||||
scale: Vector2D::new(
|
||||
self.scale.x * other.scale.x,
|
||||
self.scale.y * other.scale.y,
|
||||
),
|
||||
offset: Vector2D::new(
|
||||
self.offset.x + self.scale.x * other.offset.x,
|
||||
self.offset.y + self.scale.y * other.offset.y,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_rect<F, T>(&self, rect: &Box2D<f32, F>) -> Box2D<f32, T> {
|
||||
// TODO(gw): The logic below can return an unexpected result if the supplied
|
||||
// rect is invalid (has size < 0). Since Gecko currently supplied
|
||||
// invalid rects in some cases, adding a max(0) here ensures that
|
||||
// mapping an invalid rect retains the property that rect.is_empty()
|
||||
// will return true (the mapped rect output will have size 0 instead
|
||||
// of a negative size). In future we could catch / assert / fix
|
||||
// these invalid rects earlier, and assert here instead.
|
||||
|
||||
let w = rect.width().max(0.0);
|
||||
let h = rect.height().max(0.0);
|
||||
|
||||
let mut x0 = rect.min.x * self.scale.x + self.offset.x;
|
||||
let mut y0 = rect.min.y * self.scale.y + self.offset.y;
|
||||
|
||||
let mut sx = w * self.scale.x;
|
||||
let mut sy = h * self.scale.y;
|
||||
// Handle negative scale. Previously, branchless float math was used to find the
|
||||
// min / max vertices and size. However, that sequence of operations was producind
|
||||
// additional floating point accuracy on android emulator builds, causing one test
|
||||
// to fail an assert. Instead, we retain the same math as previously, and adjust
|
||||
// the origin / size if required.
|
||||
|
||||
if self.scale.x < 0.0 {
|
||||
x0 += sx;
|
||||
sx = -sx;
|
||||
}
|
||||
if self.scale.y < 0.0 {
|
||||
y0 += sy;
|
||||
sy = -sy;
|
||||
}
|
||||
|
||||
Box2D::from_origin_and_size(
|
||||
Point2D::new(x0, y0),
|
||||
Size2D::new(sx, sy),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn unmap_rect<F, T>(&self, rect: &Box2D<f32, F>) -> Box2D<f32, T> {
|
||||
// TODO(gw): The logic below can return an unexpected result if the supplied
|
||||
// rect is invalid (has size < 0). Since Gecko currently supplied
|
||||
// invalid rects in some cases, adding a max(0) here ensures that
|
||||
// mapping an invalid rect retains the property that rect.is_empty()
|
||||
// will return true (the mapped rect output will have size 0 instead
|
||||
// of a negative size). In future we could catch / assert / fix
|
||||
// these invalid rects earlier, and assert here instead.
|
||||
|
||||
let w = rect.width().max(0.0);
|
||||
let h = rect.height().max(0.0);
|
||||
|
||||
let mut x0 = (rect.min.x - self.offset.x) / self.scale.x;
|
||||
let mut y0 = (rect.min.y - self.offset.y) / self.scale.y;
|
||||
|
||||
let mut sx = w / self.scale.x;
|
||||
let mut sy = h / self.scale.y;
|
||||
|
||||
// Handle negative scale. Previously, branchless float math was used to find the
|
||||
// min / max vertices and size. However, that sequence of operations was producind
|
||||
// additional floating point accuracy on android emulator builds, causing one test
|
||||
// to fail an assert. Instead, we retain the same math as previously, and adjust
|
||||
// the origin / size if required.
|
||||
|
||||
if self.scale.x < 0.0 {
|
||||
x0 += sx;
|
||||
sx = -sx;
|
||||
}
|
||||
if self.scale.y < 0.0 {
|
||||
y0 += sy;
|
||||
sy = -sy;
|
||||
}
|
||||
|
||||
Box2D::from_origin_and_size(
|
||||
Point2D::new(x0, y0),
|
||||
Size2D::new(sx, sy),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn map_vector<F, T>(&self, vector: &Vector2D<f32, F>) -> Vector2D<f32, T> {
|
||||
Vector2D::new(
|
||||
vector.x * self.scale.x,
|
||||
vector.y * self.scale.y,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn unmap_vector<F, T>(&self, vector: &Vector2D<f32, F>) -> Vector2D<f32, T> {
|
||||
Vector2D::new(
|
||||
vector.x / self.scale.x,
|
||||
vector.y / self.scale.y,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn map_point<F, T>(&self, point: &Point2D<f32, F>) -> Point2D<f32, T> {
|
||||
Point2D::new(
|
||||
point.x * self.scale.x + self.offset.x,
|
||||
point.y * self.scale.y + self.offset.y,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn unmap_point<F, T>(&self, point: &Point2D<f32, F>) -> Point2D<f32, T> {
|
||||
Point2D::new(
|
||||
(point.x - self.offset.x) / self.scale.x,
|
||||
(point.y - self.offset.y) / self.scale.y,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_transform<F, T>(&self) -> Transform3D<f32, F, T> {
|
||||
Transform3D::new(
|
||||
self.scale.x,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
|
||||
0.0,
|
||||
self.scale.y,
|
||||
0.0,
|
||||
0.0,
|
||||
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
|
||||
self.offset.x,
|
||||
self.offset.y,
|
||||
0.0,
|
||||
1.0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RectHelpers<U>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fn snap(&self) -> Self;
|
||||
}
|
||||
|
||||
impl<U> RectHelpers<U> for Box2D<f32, U> {
|
||||
fn snap(&self) -> Self {
|
||||
self.round()
|
||||
}
|
||||
}
|
|
@ -76,7 +76,7 @@ fuzzy(1,7) == tiling-conic-3.yaml tiling-conic-3-ref.yaml
|
|||
== radial-zero-size-2.yaml radial-zero-size-ref.yaml
|
||||
== radial-zero-size-3.yaml radial-zero-size-ref.yaml
|
||||
|
||||
== linear-adjust-tile-size.yaml linear-adjust-tile-size-ref.yaml
|
||||
fuzzy(2,34) == linear-adjust-tile-size.yaml linear-adjust-tile-size-ref.yaml
|
||||
== linear-repeat-clip.yaml linear-repeat-clip-ref.yaml
|
||||
|
||||
platform(linux,mac) == linear-aligned-border-radius.yaml linear-aligned-border-radius.png
|
||||
|
|
Загрузка…
Ссылка в новой задаче