зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1574493 - Part 4. Snap primitives during scene building. r=kvark
Now that rounding has been removed from Gecko, we need to start snapping properly in WebRender. Snapping can change the size of a primitive, and thus it is problematic to do any later than scene building due to the GPU caching and sharing of data between clips and such that only differ in their positioning. This patch produces a snapping transform which allows any primitive to snap using information known during scene building. This excludes animated tranforms which are assumed to be the identity. This allows for primitives that are marked as will-change: transform but given no initial transform to render the same as primitives that are not. This also excludes scroll positioning because that is not known until frame building. A follow up patch will deal with that. Differential Revision: https://phabricator.services.mozilla.com/D45059 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
25866edb78
Коммит
b39a6837b4
|
@ -98,10 +98,13 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
|
|
||||||
// Apply parameters that affect where the shadow rect
|
// Apply parameters that affect where the shadow rect
|
||||||
// exists in the local space of the primitive.
|
// exists in the local space of the primitive.
|
||||||
let shadow_rect = prim_info
|
let shadow_rect = self.snap_rect(
|
||||||
.rect
|
&prim_info
|
||||||
.translate(*box_offset)
|
.rect
|
||||||
.inflate(spread_amount, spread_amount);
|
.translate(*box_offset)
|
||||||
|
.inflate(spread_amount, spread_amount),
|
||||||
|
clip_and_scroll.spatial_node_index,
|
||||||
|
);
|
||||||
|
|
||||||
// If blur radius is zero, we can use a fast path with
|
// If blur radius is zero, we can use a fast path with
|
||||||
// no blur applied.
|
// no blur applied.
|
||||||
|
|
|
@ -1002,9 +1002,9 @@ impl ClipItemKeyKind {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn image_mask(image_mask: &ImageMask) -> Self {
|
pub fn image_mask(image_mask: &ImageMask, mask_rect: LayoutRect) -> Self {
|
||||||
ClipItemKeyKind::ImageMask(
|
ClipItemKeyKind::ImageMask(
|
||||||
image_mask.rect.into(),
|
mask_rect.into(),
|
||||||
image_mask.image,
|
image_mask.image,
|
||||||
image_mask.repeat,
|
image_mask.repeat,
|
||||||
)
|
)
|
||||||
|
|
|
@ -570,12 +570,16 @@ impl ClipScrollTree {
|
||||||
self.add_spatial_node(node)
|
self.add_spatial_node(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_spatial_node(&mut self, node: SpatialNode) -> SpatialNodeIndex {
|
pub fn add_spatial_node(&mut self, mut node: SpatialNode) -> SpatialNodeIndex {
|
||||||
let index = SpatialNodeIndex::new(self.spatial_nodes.len());
|
let index = SpatialNodeIndex::new(self.spatial_nodes.len());
|
||||||
|
|
||||||
// When the parent node is None this means we are adding the root.
|
// When the parent node is None this means we are adding the root.
|
||||||
if let Some(parent_index) = node.parent {
|
if let Some(parent_index) = node.parent {
|
||||||
self.spatial_nodes[parent_index.0 as usize].add_child(index);
|
let parent_node = &mut self.spatial_nodes[parent_index.0 as usize];
|
||||||
|
parent_node.add_child(index);
|
||||||
|
node.update_snapping(Some(parent_node));
|
||||||
|
} else {
|
||||||
|
node.update_snapping(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.spatial_nodes.push(node);
|
self.spatial_nodes.push(node);
|
||||||
|
@ -640,12 +644,15 @@ impl ClipScrollTree {
|
||||||
pt.new_level(format!("ReferenceFrame"));
|
pt.new_level(format!("ReferenceFrame"));
|
||||||
pt.add_item(format!("kind: {:?}", info.kind));
|
pt.add_item(format!("kind: {:?}", info.kind));
|
||||||
pt.add_item(format!("transform_style: {:?}", info.transform_style));
|
pt.add_item(format!("transform_style: {:?}", info.transform_style));
|
||||||
|
pt.add_item(format!("source_transform: {:?}", info.source_transform));
|
||||||
|
pt.add_item(format!("origin_in_parent_reference_frame: {:?}", info.origin_in_parent_reference_frame));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pt.add_item(format!("index: {:?}", index));
|
pt.add_item(format!("index: {:?}", index));
|
||||||
pt.add_item(format!("content_transform: {:?}", node.content_transform));
|
pt.add_item(format!("content_transform: {:?}", node.content_transform));
|
||||||
pt.add_item(format!("viewport_transform: {:?}", node.viewport_transform));
|
pt.add_item(format!("viewport_transform: {:?}", node.viewport_transform));
|
||||||
|
pt.add_item(format!("snapping_transform: {:?}", node.snapping_transform));
|
||||||
pt.add_item(format!("coordinate_system_id: {:?}", node.coordinate_system_id));
|
pt.add_item(format!("coordinate_system_id: {:?}", node.coordinate_system_id));
|
||||||
|
|
||||||
for child_index in &node.children {
|
for child_index in &node.children {
|
||||||
|
|
|
@ -27,6 +27,7 @@ use crate::prim_store::{PrimitiveInstanceKind, NinePatchDescriptor, PrimitiveSto
|
||||||
use crate::prim_store::{ScrollNodeAndClipChain, PictureIndex};
|
use crate::prim_store::{ScrollNodeAndClipChain, PictureIndex};
|
||||||
use crate::prim_store::{InternablePrimitive, SegmentInstanceIndex};
|
use crate::prim_store::{InternablePrimitive, SegmentInstanceIndex};
|
||||||
use crate::prim_store::{register_prim_chase_id, get_line_decoration_sizes};
|
use crate::prim_store::{register_prim_chase_id, get_line_decoration_sizes};
|
||||||
|
use crate::prim_store::{SpaceSnapper};
|
||||||
use crate::prim_store::backdrop::Backdrop;
|
use crate::prim_store::backdrop::Backdrop;
|
||||||
use crate::prim_store::borders::{ImageBorder, NormalBorderPrim};
|
use crate::prim_store::borders::{ImageBorder, NormalBorderPrim};
|
||||||
use crate::prim_store::gradient::{GradientStopKey, LinearGradient, RadialGradient, RadialGradientParams};
|
use crate::prim_store::gradient::{GradientStopKey, LinearGradient, RadialGradient, RadialGradientParams};
|
||||||
|
@ -338,10 +339,13 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
found_explicit_tile_cache: false,
|
found_explicit_tile_cache: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let device_pixel_scale = view.accumulated_scale_factor_for_snapping();
|
||||||
|
|
||||||
flattener.push_root(
|
flattener.push_root(
|
||||||
root_pipeline_id,
|
root_pipeline_id,
|
||||||
&root_pipeline.viewport_size,
|
&root_pipeline.viewport_size,
|
||||||
&root_pipeline.content_size,
|
&root_pipeline.content_size,
|
||||||
|
device_pixel_scale,
|
||||||
);
|
);
|
||||||
|
|
||||||
// In order to ensure we have a single root stacking context for the
|
// In order to ensure we have a single root stacking context for the
|
||||||
|
@ -365,6 +369,7 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
ClipChainId::NONE,
|
ClipChainId::NONE,
|
||||||
RasterSpace::Screen,
|
RasterSpace::Screen,
|
||||||
/* is_backdrop_root = */ true,
|
/* is_backdrop_root = */ true,
|
||||||
|
device_pixel_scale,
|
||||||
);
|
);
|
||||||
|
|
||||||
flattener.flatten_items(
|
flattener.flatten_items(
|
||||||
|
@ -935,6 +940,7 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
clip_chain_id,
|
clip_chain_id,
|
||||||
stacking_context.raster_space,
|
stacking_context.raster_space,
|
||||||
stacking_context.is_backdrop_root,
|
stacking_context.is_backdrop_root,
|
||||||
|
self.sc_stack.last().unwrap().snap_to_device.device_pixel_scale,
|
||||||
);
|
);
|
||||||
|
|
||||||
if cfg!(debug_assertions) && apply_pipeline_clip && clip_chain_id != ClipChainId::NONE {
|
if cfg!(debug_assertions) && apply_pipeline_clip && clip_chain_id != ClipChainId::NONE {
|
||||||
|
@ -992,8 +998,18 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
);
|
);
|
||||||
self.pipeline_clip_chain_stack.push(clip_chain_index);
|
self.pipeline_clip_chain_stack.push(clip_chain_index);
|
||||||
|
|
||||||
let bounds = info.bounds;
|
let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device;
|
||||||
let origin = current_offset + bounds.origin.to_vector();
|
snap_to_device.set_target_spatial_node(
|
||||||
|
spatial_node_index,
|
||||||
|
self.clip_scroll_tree,
|
||||||
|
);
|
||||||
|
|
||||||
|
let bounds = snap_to_device.snap_rect(
|
||||||
|
&info.bounds.translate(current_offset),
|
||||||
|
);
|
||||||
|
|
||||||
|
let content_size = snap_to_device.snap_size(&pipeline.content_size);
|
||||||
|
|
||||||
let spatial_node_index = self.push_reference_frame(
|
let spatial_node_index = self.push_reference_frame(
|
||||||
SpatialId::root_reference_frame(iframe_pipeline_id),
|
SpatialId::root_reference_frame(iframe_pipeline_id),
|
||||||
Some(spatial_node_index),
|
Some(spatial_node_index),
|
||||||
|
@ -1001,7 +1017,7 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
TransformStyle::Flat,
|
TransformStyle::Flat,
|
||||||
PropertyBinding::Value(LayoutTransform::identity()),
|
PropertyBinding::Value(LayoutTransform::identity()),
|
||||||
ReferenceFrameKind::Transform,
|
ReferenceFrameKind::Transform,
|
||||||
origin,
|
bounds.origin.to_vector(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let iframe_rect = LayoutRect::new(LayoutPoint::zero(), bounds.size);
|
let iframe_rect = LayoutRect::new(LayoutPoint::zero(), bounds.size);
|
||||||
|
@ -1011,7 +1027,7 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
Some(ExternalScrollId(0, iframe_pipeline_id)),
|
Some(ExternalScrollId(0, iframe_pipeline_id)),
|
||||||
iframe_pipeline_id,
|
iframe_pipeline_id,
|
||||||
&iframe_rect,
|
&iframe_rect,
|
||||||
&pipeline.content_size,
|
&content_size,
|
||||||
ScrollSensitivity::ScriptAndInputEvents,
|
ScrollSensitivity::ScriptAndInputEvents,
|
||||||
ScrollFrameKind::PipelineRoot,
|
ScrollFrameKind::PipelineRoot,
|
||||||
LayoutVector2D::zero(),
|
LayoutVector2D::zero(),
|
||||||
|
@ -1055,7 +1071,12 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
common: &CommonItemProperties,
|
common: &CommonItemProperties,
|
||||||
apply_pipeline_clip: bool
|
apply_pipeline_clip: bool
|
||||||
) -> (LayoutPrimitiveInfo, ScrollNodeAndClipChain) {
|
) -> (LayoutPrimitiveInfo, ScrollNodeAndClipChain) {
|
||||||
self.process_common_properties_with_bounds(common, &common.clip_rect, apply_pipeline_clip)
|
let (layout, _, clip_and_scroll) = self.process_common_properties_with_bounds(
|
||||||
|
common,
|
||||||
|
&common.clip_rect,
|
||||||
|
apply_pipeline_clip,
|
||||||
|
);
|
||||||
|
(layout, clip_and_scroll)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_common_properties_with_bounds(
|
fn process_common_properties_with_bounds(
|
||||||
|
@ -1063,7 +1084,7 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
common: &CommonItemProperties,
|
common: &CommonItemProperties,
|
||||||
bounds: &LayoutRect,
|
bounds: &LayoutRect,
|
||||||
apply_pipeline_clip: bool
|
apply_pipeline_clip: bool
|
||||||
) -> (LayoutPrimitiveInfo, ScrollNodeAndClipChain) {
|
) -> (LayoutPrimitiveInfo, LayoutRect, ScrollNodeAndClipChain) {
|
||||||
let clip_and_scroll = self.get_clip_and_scroll(
|
let clip_and_scroll = self.get_clip_and_scroll(
|
||||||
&common.clip_id,
|
&common.clip_id,
|
||||||
&common.spatial_id,
|
&common.spatial_id,
|
||||||
|
@ -1072,16 +1093,36 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
|
|
||||||
let current_offset = self.current_offset(clip_and_scroll.spatial_node_index);
|
let current_offset = self.current_offset(clip_and_scroll.spatial_node_index);
|
||||||
|
|
||||||
|
let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device;
|
||||||
|
snap_to_device.set_target_spatial_node(
|
||||||
|
clip_and_scroll.spatial_node_index,
|
||||||
|
self.clip_scroll_tree
|
||||||
|
);
|
||||||
|
|
||||||
let clip_rect = common.clip_rect.translate(current_offset);
|
let clip_rect = common.clip_rect.translate(current_offset);
|
||||||
let rect = bounds.translate(current_offset);
|
let rect = bounds.translate(current_offset);
|
||||||
|
|
||||||
let layout = LayoutPrimitiveInfo {
|
let layout = LayoutPrimitiveInfo {
|
||||||
rect,
|
rect: snap_to_device.snap_rect(&rect),
|
||||||
clip_rect,
|
clip_rect: snap_to_device.snap_rect(&clip_rect),
|
||||||
is_backface_visible: common.is_backface_visible,
|
is_backface_visible: common.is_backface_visible,
|
||||||
hit_info: common.hit_info,
|
hit_info: common.hit_info,
|
||||||
};
|
};
|
||||||
|
|
||||||
(layout, clip_and_scroll)
|
(layout, rect, clip_and_scroll)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn snap_rect(
|
||||||
|
&mut self,
|
||||||
|
rect: &LayoutRect,
|
||||||
|
target_spatial_node: SpatialNodeIndex,
|
||||||
|
) -> LayoutRect {
|
||||||
|
let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device;
|
||||||
|
snap_to_device.set_target_spatial_node(
|
||||||
|
target_spatial_node,
|
||||||
|
self.clip_scroll_tree
|
||||||
|
);
|
||||||
|
snap_to_device.snap_rect(rect)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flatten_item<'b>(
|
fn flatten_item<'b>(
|
||||||
|
@ -1092,7 +1133,7 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
) -> Option<BuiltDisplayListIter<'a>> {
|
) -> Option<BuiltDisplayListIter<'a>> {
|
||||||
match *item.item() {
|
match *item.item() {
|
||||||
DisplayItem::Image(ref info) => {
|
DisplayItem::Image(ref info) => {
|
||||||
let (layout, clip_and_scroll) = self.process_common_properties_with_bounds(
|
let (layout, _, clip_and_scroll) = self.process_common_properties_with_bounds(
|
||||||
&info.common,
|
&info.common,
|
||||||
&info.bounds,
|
&info.bounds,
|
||||||
apply_pipeline_clip,
|
apply_pipeline_clip,
|
||||||
|
@ -1111,16 +1152,22 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
DisplayItem::RepeatingImage(ref info) => {
|
DisplayItem::RepeatingImage(ref info) => {
|
||||||
let (layout, clip_and_scroll) = self.process_common_properties_with_bounds(
|
let (layout, unsnapped_rect, clip_and_scroll) = self.process_common_properties_with_bounds(
|
||||||
&info.common,
|
&info.common,
|
||||||
&info.bounds,
|
&info.bounds,
|
||||||
apply_pipeline_clip,
|
apply_pipeline_clip,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let stretch_size = process_repeat_size(
|
||||||
|
&layout.rect,
|
||||||
|
&unsnapped_rect,
|
||||||
|
info.stretch_size,
|
||||||
|
);
|
||||||
|
|
||||||
self.add_image(
|
self.add_image(
|
||||||
clip_and_scroll,
|
clip_and_scroll,
|
||||||
&layout,
|
&layout,
|
||||||
info.stretch_size,
|
stretch_size,
|
||||||
info.tile_spacing,
|
info.tile_spacing,
|
||||||
None,
|
None,
|
||||||
info.image_key,
|
info.image_key,
|
||||||
|
@ -1130,7 +1177,7 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
DisplayItem::YuvImage(ref info) => {
|
DisplayItem::YuvImage(ref info) => {
|
||||||
let (layout, clip_and_scroll) = self.process_common_properties_with_bounds(
|
let (layout, _, clip_and_scroll) = self.process_common_properties_with_bounds(
|
||||||
&info.common,
|
&info.common,
|
||||||
&info.bounds,
|
&info.bounds,
|
||||||
apply_pipeline_clip,
|
apply_pipeline_clip,
|
||||||
|
@ -1147,7 +1194,7 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
DisplayItem::Text(ref info) => {
|
DisplayItem::Text(ref info) => {
|
||||||
let (layout, clip_and_scroll) = self.process_common_properties_with_bounds(
|
let (layout, _, clip_and_scroll) = self.process_common_properties_with_bounds(
|
||||||
&info.common,
|
&info.common,
|
||||||
&info.bounds,
|
&info.bounds,
|
||||||
apply_pipeline_clip,
|
apply_pipeline_clip,
|
||||||
|
@ -1198,7 +1245,7 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
DisplayItem::Line(ref info) => {
|
DisplayItem::Line(ref info) => {
|
||||||
let (layout, clip_and_scroll) = self.process_common_properties_with_bounds(
|
let (layout, _, clip_and_scroll) = self.process_common_properties_with_bounds(
|
||||||
&info.common,
|
&info.common,
|
||||||
&info.area,
|
&info.area,
|
||||||
apply_pipeline_clip,
|
apply_pipeline_clip,
|
||||||
|
@ -1214,19 +1261,25 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
DisplayItem::Gradient(ref info) => {
|
DisplayItem::Gradient(ref info) => {
|
||||||
let (layout, clip_and_scroll) = self.process_common_properties_with_bounds(
|
let (layout, unsnapped_rect, clip_and_scroll) = self.process_common_properties_with_bounds(
|
||||||
&info.common,
|
&info.common,
|
||||||
&info.bounds,
|
&info.bounds,
|
||||||
apply_pipeline_clip,
|
apply_pipeline_clip,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let tile_size = process_repeat_size(
|
||||||
|
&layout.rect,
|
||||||
|
&unsnapped_rect,
|
||||||
|
info.tile_size,
|
||||||
|
);
|
||||||
|
|
||||||
if let Some(prim_key_kind) = self.create_linear_gradient_prim(
|
if let Some(prim_key_kind) = self.create_linear_gradient_prim(
|
||||||
&layout,
|
&layout,
|
||||||
info.gradient.start_point,
|
info.gradient.start_point,
|
||||||
info.gradient.end_point,
|
info.gradient.end_point,
|
||||||
item.gradient_stops(),
|
item.gradient_stops(),
|
||||||
info.gradient.extend_mode,
|
info.gradient.extend_mode,
|
||||||
info.tile_size,
|
tile_size,
|
||||||
info.tile_spacing,
|
info.tile_spacing,
|
||||||
None,
|
None,
|
||||||
) {
|
) {
|
||||||
|
@ -1239,12 +1292,18 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DisplayItem::RadialGradient(ref info) => {
|
DisplayItem::RadialGradient(ref info) => {
|
||||||
let (layout, clip_and_scroll) = self.process_common_properties_with_bounds(
|
let (layout, unsnapped_rect, clip_and_scroll) = self.process_common_properties_with_bounds(
|
||||||
&info.common,
|
&info.common,
|
||||||
&info.bounds,
|
&info.bounds,
|
||||||
apply_pipeline_clip,
|
apply_pipeline_clip,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let tile_size = process_repeat_size(
|
||||||
|
&layout.rect,
|
||||||
|
&unsnapped_rect,
|
||||||
|
info.tile_size,
|
||||||
|
);
|
||||||
|
|
||||||
let prim_key_kind = self.create_radial_gradient_prim(
|
let prim_key_kind = self.create_radial_gradient_prim(
|
||||||
&layout,
|
&layout,
|
||||||
info.gradient.center,
|
info.gradient.center,
|
||||||
|
@ -1253,7 +1312,7 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
info.gradient.radius.width / info.gradient.radius.height,
|
info.gradient.radius.width / info.gradient.radius.height,
|
||||||
item.gradient_stops(),
|
item.gradient_stops(),
|
||||||
info.gradient.extend_mode,
|
info.gradient.extend_mode,
|
||||||
info.tile_size,
|
tile_size,
|
||||||
info.tile_spacing,
|
info.tile_spacing,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
@ -1266,7 +1325,7 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
DisplayItem::BoxShadow(ref info) => {
|
DisplayItem::BoxShadow(ref info) => {
|
||||||
let (layout, clip_and_scroll) = self.process_common_properties_with_bounds(
|
let (layout, _, clip_and_scroll) = self.process_common_properties_with_bounds(
|
||||||
&info.common,
|
&info.common,
|
||||||
&info.box_bounds,
|
&info.box_bounds,
|
||||||
apply_pipeline_clip,
|
apply_pipeline_clip,
|
||||||
|
@ -1284,7 +1343,7 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
DisplayItem::Border(ref info) => {
|
DisplayItem::Border(ref info) => {
|
||||||
let (layout, clip_and_scroll) = self.process_common_properties_with_bounds(
|
let (layout, _, clip_and_scroll) = self.process_common_properties_with_bounds(
|
||||||
&info.common,
|
&info.common,
|
||||||
&info.bounds,
|
&info.bounds,
|
||||||
apply_pipeline_clip,
|
apply_pipeline_clip,
|
||||||
|
@ -1701,6 +1760,7 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
clip_chain_id: ClipChainId,
|
clip_chain_id: ClipChainId,
|
||||||
requested_raster_space: RasterSpace,
|
requested_raster_space: RasterSpace,
|
||||||
is_backdrop_root: bool,
|
is_backdrop_root: bool,
|
||||||
|
device_pixel_scale: DevicePixelScale,
|
||||||
) {
|
) {
|
||||||
// Check if this stacking context is the root of a pipeline, and the caller
|
// Check if this stacking context is the root of a pipeline, and the caller
|
||||||
// has requested it as an output frame.
|
// has requested it as an output frame.
|
||||||
|
@ -1803,6 +1863,14 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
|
current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let snap_to_device = self.sc_stack.last().map_or(
|
||||||
|
SpaceSnapper::new(
|
||||||
|
ROOT_SPATIAL_NODE_INDEX,
|
||||||
|
device_pixel_scale,
|
||||||
|
),
|
||||||
|
|sc| sc.snap_to_device.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
// Push the SC onto the stack, so we know how to handle things in
|
// Push the SC onto the stack, so we know how to handle things in
|
||||||
// pop_stacking_context.
|
// pop_stacking_context.
|
||||||
self.sc_stack.push(FlattenedStackingContext {
|
self.sc_stack.push(FlattenedStackingContext {
|
||||||
|
@ -1819,6 +1887,7 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
context_3d,
|
context_3d,
|
||||||
create_tile_cache,
|
create_tile_cache,
|
||||||
is_backdrop_root,
|
is_backdrop_root,
|
||||||
|
snap_to_device,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2194,6 +2263,7 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
pipeline_id: PipelineId,
|
pipeline_id: PipelineId,
|
||||||
viewport_size: &LayoutSize,
|
viewport_size: &LayoutSize,
|
||||||
content_size: &LayoutSize,
|
content_size: &LayoutSize,
|
||||||
|
device_pixel_scale: DevicePixelScale,
|
||||||
) {
|
) {
|
||||||
if let ChasePrimitive::Id(id) = self.config.chase_primitive {
|
if let ChasePrimitive::Id(id) = self.config.chase_primitive {
|
||||||
println!("Chasing {:?} by index", id);
|
println!("Chasing {:?} by index", id);
|
||||||
|
@ -2212,13 +2282,27 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
LayoutVector2D::zero(),
|
LayoutVector2D::zero(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// We can't use this with the stacking context because it does not exist
|
||||||
|
// yet. Just create a dedicated snapper for the root.
|
||||||
|
let snap_to_device = SpaceSnapper::new_with_target(
|
||||||
|
spatial_node_index,
|
||||||
|
ROOT_SPATIAL_NODE_INDEX,
|
||||||
|
device_pixel_scale,
|
||||||
|
self.clip_scroll_tree,
|
||||||
|
);
|
||||||
|
|
||||||
|
let content_size = snap_to_device.snap_size(content_size);
|
||||||
|
let viewport_rect = snap_to_device.snap_rect(
|
||||||
|
&LayoutRect::new(LayoutPoint::zero(), *viewport_size),
|
||||||
|
);
|
||||||
|
|
||||||
self.add_scroll_frame(
|
self.add_scroll_frame(
|
||||||
SpatialId::root_scroll_node(pipeline_id),
|
SpatialId::root_scroll_node(pipeline_id),
|
||||||
spatial_node_index,
|
spatial_node_index,
|
||||||
Some(ExternalScrollId(0, pipeline_id)),
|
Some(ExternalScrollId(0, pipeline_id)),
|
||||||
pipeline_id,
|
pipeline_id,
|
||||||
&LayoutRect::new(LayoutPoint::zero(), *viewport_size),
|
&viewport_rect,
|
||||||
content_size,
|
&content_size,
|
||||||
ScrollSensitivity::ScriptAndInputEvents,
|
ScrollSensitivity::ScriptAndInputEvents,
|
||||||
ScrollFrameKind::PipelineRoot,
|
ScrollFrameKind::PipelineRoot,
|
||||||
LayoutVector2D::zero(),
|
LayoutVector2D::zero(),
|
||||||
|
@ -2242,6 +2326,14 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
// Map the ClipId for the positioning node to a spatial node index.
|
// Map the ClipId for the positioning node to a spatial node index.
|
||||||
let spatial_node_index = self.id_to_index_mapper.get_spatial_node_index(space_and_clip.spatial_id);
|
let spatial_node_index = self.id_to_index_mapper.get_spatial_node_index(space_and_clip.spatial_id);
|
||||||
|
|
||||||
|
let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device;
|
||||||
|
snap_to_device.set_target_spatial_node(
|
||||||
|
spatial_node_index,
|
||||||
|
self.clip_scroll_tree,
|
||||||
|
);
|
||||||
|
|
||||||
|
let snapped_clip_rect = snap_to_device.snap_rect(&clip_region.main);
|
||||||
|
|
||||||
let mut clip_count = 0;
|
let mut clip_count = 0;
|
||||||
|
|
||||||
// Intern each clip item in this clip node, and add the interned
|
// Intern each clip item in this clip node, and add the interned
|
||||||
|
@ -2251,7 +2343,7 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
|
|
||||||
// Build the clip sources from the supplied region.
|
// Build the clip sources from the supplied region.
|
||||||
let item = ClipItemKey {
|
let item = ClipItemKey {
|
||||||
kind: ClipItemKeyKind::rectangle(clip_region.main, ClipMode::Clip),
|
kind: ClipItemKeyKind::rectangle(snapped_clip_rect, ClipMode::Clip),
|
||||||
spatial_node_index,
|
spatial_node_index,
|
||||||
};
|
};
|
||||||
let handle = self
|
let handle = self
|
||||||
|
@ -2268,8 +2360,9 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
clip_count += 1;
|
clip_count += 1;
|
||||||
|
|
||||||
if let Some(ref image_mask) = clip_region.image_mask {
|
if let Some(ref image_mask) = clip_region.image_mask {
|
||||||
|
let snapped_mask_rect = snap_to_device.snap_rect(&image_mask.rect);
|
||||||
let item = ClipItemKey {
|
let item = ClipItemKey {
|
||||||
kind: ClipItemKeyKind::image_mask(image_mask),
|
kind: ClipItemKeyKind::image_mask(image_mask, snapped_mask_rect),
|
||||||
spatial_node_index,
|
spatial_node_index,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2288,9 +2381,10 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
for region in clip_region.complex_clips {
|
for region in clip_region.complex_clips {
|
||||||
|
let snapped_region_rect = snap_to_device.snap_rect(®ion.rect);
|
||||||
let item = ClipItemKey {
|
let item = ClipItemKey {
|
||||||
kind: ClipItemKeyKind::rounded_rect(
|
kind: ClipItemKeyKind::rounded_rect(
|
||||||
region.rect,
|
snapped_region_rect,
|
||||||
region.radii,
|
region.radii,
|
||||||
region.mode,
|
region.mode,
|
||||||
),
|
),
|
||||||
|
@ -2563,11 +2657,23 @@ impl<'a> DisplayListFlattener<'a> {
|
||||||
P: InternablePrimitive + CreateShadow,
|
P: InternablePrimitive + CreateShadow,
|
||||||
Interners: AsMut<Interner<P>>,
|
Interners: AsMut<Interner<P>>,
|
||||||
{
|
{
|
||||||
// Offset the local rect and clip rect by the shadow offset.
|
let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device;
|
||||||
|
snap_to_device.set_target_spatial_node(
|
||||||
|
pending_primitive.clip_and_scroll.spatial_node_index,
|
||||||
|
self.clip_scroll_tree
|
||||||
|
);
|
||||||
|
|
||||||
|
// Offset the local rect and clip rect by the shadow offset. The pending
|
||||||
|
// primitive has already been snapped, but we will need to snap the
|
||||||
|
// shadow after translation. We don't need to worry about the size
|
||||||
|
// changing because the shadow has the same raster space as the
|
||||||
|
// primitive, and thus we know the size is already rounded.
|
||||||
let mut info = pending_primitive.info.clone();
|
let mut info = pending_primitive.info.clone();
|
||||||
info.rect = info.rect.translate(pending_shadow.shadow.offset);
|
info.rect = snap_to_device.snap_rect(
|
||||||
info.clip_rect = info.clip_rect.translate(
|
&info.rect.translate(pending_shadow.shadow.offset),
|
||||||
pending_shadow.shadow.offset
|
);
|
||||||
|
info.clip_rect = snap_to_device.snap_rect(
|
||||||
|
&info.clip_rect.translate(pending_shadow.shadow.offset),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Construct and add a primitive for the given shadow.
|
// Construct and add a primitive for the given shadow.
|
||||||
|
@ -3456,6 +3562,13 @@ struct FlattenedStackingContext {
|
||||||
|
|
||||||
/// True if this stacking context is a backdrop root.
|
/// True if this stacking context is a backdrop root.
|
||||||
is_backdrop_root: bool,
|
is_backdrop_root: bool,
|
||||||
|
|
||||||
|
/// A helper struct to snap local rects in device space. During frame
|
||||||
|
/// building we may establish new raster roots, however typically that is in
|
||||||
|
/// cases where we won't be applying snapping (e.g. has perspective), or in
|
||||||
|
/// edge cases (e.g. SVG filter) where we can accept slightly incorrect
|
||||||
|
/// behaviour in favour of getting the common case right.
|
||||||
|
snap_to_device: SpaceSnapper,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FlattenedStackingContext {
|
impl FlattenedStackingContext {
|
||||||
|
@ -3709,3 +3822,22 @@ fn filter_primitives_for_compositing(
|
||||||
// more efficient than cloning these here.
|
// more efficient than cloning these here.
|
||||||
input_filter_primitives.iter().map(|primitive| primitive.into()).collect()
|
input_filter_primitives.iter().map(|primitive| primitive.into()).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn process_repeat_size(
|
||||||
|
snapped_rect: &LayoutRect,
|
||||||
|
unsnapped_rect: &LayoutRect,
|
||||||
|
repeat_size: LayoutSize,
|
||||||
|
) -> LayoutSize {
|
||||||
|
LayoutSize::new(
|
||||||
|
if repeat_size.width == unsnapped_rect.size.width {
|
||||||
|
snapped_rect.size.width
|
||||||
|
} else {
|
||||||
|
repeat_size.width
|
||||||
|
},
|
||||||
|
if repeat_size.height == unsnapped_rect.size.height {
|
||||||
|
snapped_rect.size.height
|
||||||
|
} else {
|
||||||
|
repeat_size.height
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ use std::{cmp, fmt, hash, ops, u32, usize, mem};
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use crate::storage;
|
use crate::storage;
|
||||||
use crate::texture_cache::TEXTURE_REGION_DIMENSIONS;
|
use crate::texture_cache::TEXTURE_REGION_DIMENSIONS;
|
||||||
use crate::util::{MatrixHelpers, MaxRect, Recycler};
|
use crate::util::{MatrixHelpers, MaxRect, Recycler, ScaleOffset, RectHelpers};
|
||||||
use crate::util::{clamp_to_scale_factor, pack_as_float, project_rect, raster_rect_to_device_pixels};
|
use crate::util::{clamp_to_scale_factor, pack_as_float, project_rect, raster_rect_to_device_pixels};
|
||||||
use crate::internal_types::{LayoutPrimitiveInfo, Filter};
|
use crate::internal_types::{LayoutPrimitiveInfo, Filter};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
@ -132,6 +132,91 @@ impl PrimitiveOpacity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct SpaceSnapper {
|
||||||
|
pub ref_spatial_node_index: SpatialNodeIndex,
|
||||||
|
current_target_spatial_node_index: SpatialNodeIndex,
|
||||||
|
snapping_transform: Option<ScaleOffset>,
|
||||||
|
pub device_pixel_scale: DevicePixelScale,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpaceSnapper {
|
||||||
|
pub fn new(
|
||||||
|
ref_spatial_node_index: SpatialNodeIndex,
|
||||||
|
device_pixel_scale: DevicePixelScale,
|
||||||
|
) -> Self {
|
||||||
|
SpaceSnapper {
|
||||||
|
ref_spatial_node_index,
|
||||||
|
current_target_spatial_node_index: SpatialNodeIndex::INVALID,
|
||||||
|
snapping_transform: None,
|
||||||
|
device_pixel_scale,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_with_target(
|
||||||
|
ref_spatial_node_index: SpatialNodeIndex,
|
||||||
|
target_node_index: SpatialNodeIndex,
|
||||||
|
device_pixel_scale: DevicePixelScale,
|
||||||
|
clip_scroll_tree: &ClipScrollTree,
|
||||||
|
) -> Self {
|
||||||
|
let mut snapper = SpaceSnapper {
|
||||||
|
ref_spatial_node_index,
|
||||||
|
current_target_spatial_node_index: SpatialNodeIndex::INVALID,
|
||||||
|
snapping_transform: None,
|
||||||
|
device_pixel_scale,
|
||||||
|
};
|
||||||
|
|
||||||
|
snapper.set_target_spatial_node(target_node_index, clip_scroll_tree);
|
||||||
|
snapper
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_target_spatial_node(
|
||||||
|
&mut self,
|
||||||
|
target_node_index: SpatialNodeIndex,
|
||||||
|
clip_scroll_tree: &ClipScrollTree,
|
||||||
|
) {
|
||||||
|
if target_node_index == self.current_target_spatial_node_index {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let ref_spatial_node = &clip_scroll_tree.spatial_nodes[self.ref_spatial_node_index.0 as usize];
|
||||||
|
let target_spatial_node = &clip_scroll_tree.spatial_nodes[target_node_index.0 as usize];
|
||||||
|
|
||||||
|
self.current_target_spatial_node_index = target_node_index;
|
||||||
|
self.snapping_transform = match (ref_spatial_node.snapping_transform, target_spatial_node.snapping_transform) {
|
||||||
|
(Some(ref ref_scale_offset), Some(ref target_scale_offset)) => {
|
||||||
|
Some(ref_scale_offset
|
||||||
|
.inverse()
|
||||||
|
.accumulate(target_scale_offset)
|
||||||
|
.scale(self.device_pixel_scale.0))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn snap_rect<F>(&self, rect: &Rect<f32, F>) -> Rect<f32, F> where F: fmt::Debug {
|
||||||
|
debug_assert!(self.current_target_spatial_node_index != SpatialNodeIndex::INVALID);
|
||||||
|
match self.snapping_transform {
|
||||||
|
Some(ref scale_offset) => {
|
||||||
|
let snapped_device_rect : DeviceRect = scale_offset.map_rect(rect).snap();
|
||||||
|
scale_offset.unmap_rect(&snapped_device_rect)
|
||||||
|
}
|
||||||
|
None => *rect,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn snap_size<F>(&self, size: &Size2D<f32, F>) -> Size2D<f32, F> where F: fmt::Debug {
|
||||||
|
debug_assert!(self.current_target_spatial_node_index != SpatialNodeIndex::INVALID);
|
||||||
|
match self.snapping_transform {
|
||||||
|
Some(ref scale_offset) => {
|
||||||
|
let rect = Rect::<f32, F>::new(Point2D::<f32, F>::zero(), *size);
|
||||||
|
let snapped_device_rect : DeviceRect = scale_offset.map_rect(&rect).snap();
|
||||||
|
scale_offset.unmap_rect(&snapped_device_rect).size
|
||||||
|
}
|
||||||
|
None => *size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SpaceMapper<F, T> {
|
pub struct SpaceMapper<F, T> {
|
||||||
|
|
|
@ -84,6 +84,13 @@ impl DocumentView {
|
||||||
self.pinch_zoom_factor
|
self.pinch_zoom_factor
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn accumulated_scale_factor_for_snapping(&self) -> DevicePixelScale {
|
||||||
|
DevicePixelScale::new(
|
||||||
|
self.device_pixel_ratio *
|
||||||
|
self.page_zoom_factor
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Hash, MallocSizeOf, PartialEq, PartialOrd, Debug, Eq, Ord)]
|
#[derive(Copy, Clone, Hash, MallocSizeOf, PartialEq, PartialOrd, Debug, Eq, Ord)]
|
||||||
|
@ -1750,6 +1757,8 @@ impl RenderBackend {
|
||||||
config.serialize(doc.frame_builder.as_ref().unwrap(), file_name);
|
config.serialize(doc.frame_builder.as_ref().unwrap(), file_name);
|
||||||
let file_name = format!("scratch-{}-{}", id.namespace_id.0, id.id);
|
let file_name = format!("scratch-{}-{}", id.namespace_id.0, id.id);
|
||||||
config.serialize(&doc.scratch, file_name);
|
config.serialize(&doc.scratch, file_name);
|
||||||
|
let file_name = format!("properties-{}-{}", id.namespace_id.0, id.id);
|
||||||
|
config.serialize(&doc.dynamic_properties, file_name);
|
||||||
let file_name = format!("render-tasks-{}-{}.svg", id.namespace_id.0, id.id);
|
let file_name = format!("render-tasks-{}-{}.svg", id.namespace_id.0, id.id);
|
||||||
let mut svg_file = fs::File::create(&config.file_path(file_name, "svg"))
|
let mut svg_file = fs::File::create(&config.file_path(file_name, "svg"))
|
||||||
.expect("Failed to open the SVG file.");
|
.expect("Failed to open the SVG file.");
|
||||||
|
|
|
@ -38,6 +38,10 @@ pub struct SpatialNode {
|
||||||
/// Content scale/offset relative to the coordinate system.
|
/// Content scale/offset relative to the coordinate system.
|
||||||
pub content_transform: ScaleOffset,
|
pub content_transform: ScaleOffset,
|
||||||
|
|
||||||
|
/// Snapping scale/offset relative to the coordinate system. If None, then
|
||||||
|
/// we should not snap entities bound to this spatial node.
|
||||||
|
pub snapping_transform: Option<ScaleOffset>,
|
||||||
|
|
||||||
/// The axis-aligned coordinate system id of this node.
|
/// The axis-aligned coordinate system id of this node.
|
||||||
pub coordinate_system_id: CoordinateSystemId,
|
pub coordinate_system_id: CoordinateSystemId,
|
||||||
|
|
||||||
|
@ -111,6 +115,7 @@ impl SpatialNode {
|
||||||
SpatialNode {
|
SpatialNode {
|
||||||
viewport_transform: ScaleOffset::identity(),
|
viewport_transform: ScaleOffset::identity(),
|
||||||
content_transform: ScaleOffset::identity(),
|
content_transform: ScaleOffset::identity(),
|
||||||
|
snapping_transform: None,
|
||||||
coordinate_system_id: CoordinateSystemId(0),
|
coordinate_system_id: CoordinateSystemId(0),
|
||||||
transform_kind: TransformedRectKind::AxisAligned,
|
transform_kind: TransformedRectKind::AxisAligned,
|
||||||
parent: parent_index,
|
parent: parent_index,
|
||||||
|
@ -635,6 +640,55 @@ impl SpatialNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Updates the snapping transform.
|
||||||
|
pub fn update_snapping(
|
||||||
|
&mut self,
|
||||||
|
parent: Option<&SpatialNode>,
|
||||||
|
) {
|
||||||
|
// Reset in case of an early return.
|
||||||
|
self.snapping_transform = None;
|
||||||
|
|
||||||
|
// We need to incorporate the parent scale/offset with the child.
|
||||||
|
// If the parent does not have a scale/offset, then we know we are
|
||||||
|
// not 2d axis aligned and thus do not need to snap its children
|
||||||
|
// either.
|
||||||
|
let parent_scale_offset = match parent {
|
||||||
|
Some(parent) => {
|
||||||
|
match parent.snapping_transform {
|
||||||
|
Some(scale_offset) => scale_offset,
|
||||||
|
None => return,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => ScaleOffset::identity(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let scale_offset = match self.node_type {
|
||||||
|
SpatialNodeType::ReferenceFrame(ref info) => {
|
||||||
|
match info.source_transform {
|
||||||
|
PropertyBinding::Value(ref value) => {
|
||||||
|
// We can only get a ScaleOffset if the transform is 2d axis
|
||||||
|
// aligned.
|
||||||
|
match ScaleOffset::from_transform(value) {
|
||||||
|
Some(scale_offset) => {
|
||||||
|
let origin_offset = info.origin_in_parent_reference_frame;
|
||||||
|
ScaleOffset::from_offset(origin_offset.to_untyped())
|
||||||
|
.accumulate(&scale_offset)
|
||||||
|
}
|
||||||
|
None => return,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume animations start at the identity transform for snapping purposes.
|
||||||
|
// TODO(aosmond): Is there a better known starting point?
|
||||||
|
PropertyBinding::Binding(..) => ScaleOffset::identity(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => ScaleOffset::identity(),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.snapping_transform = Some(parent_scale_offset.accumulate(&scale_offset));
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true for ReferenceFrames whose source_transform is
|
/// Returns true for ReferenceFrames whose source_transform is
|
||||||
/// bound to the property binding id.
|
/// bound to the property binding id.
|
||||||
pub fn is_transform_bound_to_property(&self, id: PropertyBindingId) -> bool {
|
pub fn is_transform_bound_to_property(&self, id: PropertyBindingId) -> bool {
|
||||||
|
|
|
@ -166,6 +166,13 @@ impl ScaleOffset {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_offset(offset: default::Vector2D<f32>) -> Self {
|
||||||
|
ScaleOffset {
|
||||||
|
scale: Vector2D::new(1.0, 1.0),
|
||||||
|
offset,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn inverse(&self) -> Self {
|
pub fn inverse(&self) -> Self {
|
||||||
ScaleOffset {
|
ScaleOffset {
|
||||||
scale: Vector2D::new(
|
scale: Vector2D::new(
|
||||||
|
@ -188,6 +195,15 @@ impl ScaleOffset {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
/// Produce a ScaleOffset that includes both self and other.
|
||||||
/// The 'self' ScaleOffset is applied after other.
|
/// The 'self' ScaleOffset is applied after other.
|
||||||
/// This is equivalent to `Transform3D::pre_transform`.
|
/// This is equivalent to `Transform3D::pre_transform`.
|
||||||
|
@ -405,12 +421,29 @@ impl<Src, Dst> MatrixHelpers<Src, Dst> for Transform3D<f32, Src, Dst> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait PointHelpers<U>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
fn snap(&self) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<U> PointHelpers<U> for Point2D<f32, U> {
|
||||||
|
fn snap(&self) -> Self {
|
||||||
|
Point2D::new(
|
||||||
|
(self.x + 0.5).floor(),
|
||||||
|
(self.y + 0.5).floor(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait RectHelpers<U>
|
pub trait RectHelpers<U>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
fn from_floats(x0: f32, y0: f32, x1: f32, y1: f32) -> Self;
|
fn from_floats(x0: f32, y0: f32, x1: f32, y1: f32) -> Self;
|
||||||
fn is_well_formed_and_nonempty(&self) -> bool;
|
fn is_well_formed_and_nonempty(&self) -> bool;
|
||||||
|
fn snap(&self) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<U> RectHelpers<U> for Rect<f32, U> {
|
impl<U> RectHelpers<U> for Rect<f32, U> {
|
||||||
|
@ -424,6 +457,20 @@ impl<U> RectHelpers<U> for Rect<f32, U> {
|
||||||
fn is_well_formed_and_nonempty(&self) -> bool {
|
fn is_well_formed_and_nonempty(&self) -> bool {
|
||||||
self.size.width > 0.0 && self.size.height > 0.0
|
self.size.width > 0.0 && self.size.height > 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn snap(&self) -> Self {
|
||||||
|
let origin = Point2D::new(
|
||||||
|
(self.origin.x + 0.5).floor(),
|
||||||
|
(self.origin.y + 0.5).floor(),
|
||||||
|
);
|
||||||
|
Rect::new(
|
||||||
|
origin,
|
||||||
|
Size2D::new(
|
||||||
|
(self.origin.x + self.size.width + 0.5).floor() - origin.x,
|
||||||
|
(self.origin.y + self.size.height + 0.5).floor() - origin.y,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lerp(a: f32, b: f32, t: f32) -> f32 {
|
pub fn lerp(a: f32, b: f32, t: f32) -> f32 {
|
||||||
|
|
Загрузка…
Ссылка в новой задаче