use std::{ops::Range, rc::Rc, sync::Arc};
use cranpose_core::NodeId;
use cranpose_render_common::{
Brush,
graph::{
InlineNode, LayerNode, PrimitiveEntry, PrimitiveNode, ProjectiveTransform, RenderGraph,
RenderNode, TextPrimitiveNode, for_each_in_draw_order,
},
graph_scene::RenderDiagnostics,
hit_graph::collect_hits_from_graph,
layer_composition::folded_layer_alpha,
layer_shadow::ShadowLight,
layer_transform::{apply_layer_affine_to_rect, apply_layer_to_rect, layer_uniform_scale},
primitive_emit::resolve_clip,
text_decoration::{visit_text_backgrounds, visit_text_lines},
text_raster::GlyphInk,
};
use cranpose_ui::{LayoutBox, text::TextStyle};
use cranpose_ui_graphics::{
BlendMode, Color, ColorFilter, GraphicsLayer, Point, Rect, RenderEffect, TransformOrigin,
};
use crate::{
draw::TextRaster,
scene::{ItemCounts, PassedDown, RasterScene, Scene, Subtree, TextDraw},
style::{apply_layer_to_brush, apply_layer_to_color, compose_color_filters},
surface::{SurfacePlacement, rasterize_surface, scales_and_moves},
};
fn graphics_layer_supports_rigid_snap(layer: &GraphicsLayer) -> bool {
(layer.scale - 1.0).abs() <= f32::EPSILON
&& (layer.scale_x - 1.0).abs() <= f32::EPSILON
&& (layer.scale_y - 1.0).abs() <= f32::EPSILON
&& layer.rotation_x.abs() <= f32::EPSILON
&& layer.rotation_y.abs() <= f32::EPSILON
&& layer.rotation_z.abs() <= f32::EPSILON
}
fn rigid_snap_anchor(layer_bounds: Rect, layer: &GraphicsLayer) -> Option<Point> {
if !graphics_layer_supports_rigid_snap(layer) {
return None;
}
let mapped = apply_layer_affine_to_rect(layer_bounds, layer_bounds, layer);
Some(Point::new(mapped.x, mapped.y))
}
#[derive(Clone, Copy)]
struct PrimitiveRenderContext<'a> {
layer_bounds: RasterLayerBounds,
node_layer: &'a GraphicsLayer,
visual_clip: Option<Rect>,
motion_context_animated: bool,
content_offset_translation: bool,
layer_snap_anchor: Option<Point>,
}
#[derive(Clone)]
struct ContentStyle {
alpha: f32,
color_filter: Option<ColorFilter>,
}
impl ContentStyle {
const PLAIN: Self = Self {
alpha: 1.0,
color_filter: None,
};
fn under(&self, layer: &GraphicsLayer) -> Self {
Self {
alpha: (self.alpha * folded_layer_alpha(layer)).clamp(0.0, 1.0),
color_filter: compose_color_filters(
self.color_filter.as_ref(),
layer.color_filter.as_ref(),
),
}
}
}
struct RasterTraversalContext<'a> {
parent_transform: &'a ProjectiveTransform,
parent_content_style: &'a ContentStyle,
parent_visual_clip: Option<Rect>,
inherited_translated_snap_anchor: Option<Point>,
inherited_translated_content_context: bool,
diagnostics: &'a RenderDiagnostics,
light: ShadowLight,
same_as_before: bool,
text: &'a TextRaster,
surface_root: bool,
in_space: bool,
}
struct Reuse<'a> {
old: &'a RasterScene,
cursor: usize,
pending: Option<(Range<usize>, ItemCounts)>,
}
impl Reuse<'_> {
fn find(&self, identity: (Option<NodeId>, Option<NodeId>)) -> Option<usize> {
let records = &self.old.subtrees;
let at = records.get(self.cursor)?;
if at.identity == identity {
return Some(self.cursor);
}
records
.get(at.end)
.is_some_and(|next| next.identity == identity)
.then_some(at.end)
}
fn copy_later(&mut self, scene: &mut RasterScene, index: usize, end: usize, stop: ItemCounts) {
match &mut self.pending {
Some((records, pending_stop)) if records.end == index => {
records.end = end;
*pending_stop = stop;
}
_ => {
self.flush(scene);
self.pending = Some((index..end, stop));
}
}
}
fn flush(&mut self, scene: &mut RasterScene) {
if let Some((records, stop)) = self.pending.take() {
scene.copy_subtrees(self.old, records, stop);
}
}
}
fn flush_copies(reuse: &mut Option<Reuse<'_>>, scene: &mut RasterScene) {
if let Some(reuse) = reuse.as_mut() {
reuse.flush(scene);
}
}
fn contains_text_primitives(nodes: &[RenderNode]) -> bool {
nodes.iter().any(|child| {
matches!(
child,
RenderNode::Primitive(PrimitiveEntry {
node: PrimitiveNode::Text(_),
..
})
)
})
}
fn contains_draw_primitives(nodes: &[RenderNode]) -> bool {
nodes.iter().any(|child| {
matches!(
child,
RenderNode::Primitive(PrimitiveEntry {
node: PrimitiveNode::Draw(_),
..
})
)
})
}
fn content_needs_rigid_snap(nodes: &[RenderNode], translated_content_context: bool) -> bool {
(translated_content_context
&& (contains_draw_primitives(nodes) || contains_text_primitives(nodes)))
|| (contains_text_primitives(nodes) && contains_draw_primitives(nodes))
}
fn is_render_effect_supported(_effect: &RenderEffect) -> bool {
false
}
fn layer_requires_effect_fallback(layer: &GraphicsLayer) -> bool {
layer
.render_effect
.as_ref()
.is_some_and(|effect| !is_render_effect_supported(effect))
|| layer
.backdrop_effect
.as_ref()
.is_some_and(|effect| !is_render_effect_supported(effect))
|| layer.blend_mode != BlendMode::SrcOver
}
fn report_unsupported_effects(layer: &GraphicsLayer, diagnostics: &RenderDiagnostics) {
if layer_requires_effect_fallback(layer)
&& diagnostics.claim_warning_once("pixels.unsupported-layer-effect")
{
log::warn!(
"Pixels renderer does not support render/backdrop effects or non-SrcOver layer blend modes; falling back to base layer rendering"
);
}
}
pub(crate) fn render_layout_tree(root: &LayoutBox, scene: &mut Scene) {
let graph = cranpose_render_common::scene_builder::build_graph_from_layout_tree(root, 1.0);
collect_hits_from_graph(&graph.root, ProjectiveTransform::identity(), scene, None);
scene.replace_graph(graph);
}
fn resolve_text_color_without_gradient_fallback(text_style: &TextStyle, default: Color) -> Color {
let mut color = text_style
.span_style
.color
.or(match text_style.span_style.brush.as_ref() {
Some(Brush::Solid(color)) => Some(*color),
_ => None,
})
.unwrap_or(default);
if let Some(alpha) = text_style.span_style.alpha {
color.3 *= alpha.clamp(0.0, 1.0);
}
color
}
#[derive(Clone, Copy)]
struct TextPlacement<'a> {
bounds: Rect,
text_rect: Rect,
layer: &'a GraphicsLayer,
clip: Option<Rect>,
}
fn push_text_style_draws(
scene: &mut RasterScene,
node: &TextPrimitiveNode,
placement: TextPlacement<'_>,
) {
let (rect, node_layer, text_clip) = (placement.bounds, placement.layer, placement.clip);
let (text, text_style, layout) = (&node.text, &node.text_style, &node.layout);
let text_rect = placement.text_rect;
let transformed_text_rect = apply_layer_to_rect(text_rect, rect, node_layer);
let placed = |part: Rect| {
apply_layer_to_rect(part.translate(text_rect.x, text_rect.y), rect, node_layer)
};
visit_text_backgrounds(layout, text.as_ref().into(), text_style, |part, brush| {
scene.push_shape(
placed(part),
apply_layer_to_brush(brush.clone(), node_layer),
None,
text_clip,
BlendMode::SrcOver,
);
});
let text_color =
resolve_text_color_without_gradient_fallback(text_style, Color(1.0, 1.0, 1.0, 1.0));
let transformed_text_color = apply_layer_to_color(text_color, node_layer);
let transformed_text_style =
if text_style.span_style.shadow.is_none() && text_style.span_style.brush.is_none() {
Arc::clone(text_style)
} else {
let mut style = TextStyle::clone(text_style);
style.span_style.shadow = None;
style.span_style.brush = text_style
.span_style
.brush
.clone()
.map(|brush| apply_layer_to_brush(brush, node_layer));
Arc::new(style)
};
if let Some(shadow) = text_style.span_style.shadow {
let shadow_rect = text_rect.translate(shadow.offset.x, shadow.offset.y);
let mut shadow_text_style = TextStyle::clone(&transformed_text_style);
shadow_text_style.span_style.shadow = Some(cranpose_ui::text::Shadow {
color: apply_layer_to_color(shadow.color, node_layer),
offset: Point::new(0.0, 0.0),
blur_radius: shadow.blur_radius,
});
scene.push_text(TextDraw {
rect: apply_layer_to_rect(shadow_rect, rect, node_layer),
snap_anchor: None,
text: Rc::clone(text),
layout: layout.clone(),
color: Color::TRANSPARENT,
text_style: Arc::new(shadow_text_style),
font_size: node.font_size,
scale: layer_uniform_scale(node_layer),
ink: GlyphInk::Hidden,
clip: text_clip,
});
}
scene.push_text(TextDraw {
rect: transformed_text_rect,
snap_anchor: None,
text: Rc::clone(text),
layout: layout.clone(),
color: transformed_text_color,
text_style: transformed_text_style,
font_size: node.font_size,
scale: layer_uniform_scale(node_layer),
ink: GlyphInk::Painted,
clip: text_clip,
});
visit_text_lines(
layout,
text.as_ref().into(),
text_style,
text_color,
|part, brush| {
scene.push_pixel_snapped_shape(
placed(part),
apply_layer_to_brush(brush.clone(), node_layer),
None,
text_clip,
BlendMode::SrcOver,
);
},
);
}
pub(crate) fn build_raster_scene(
graph: &RenderGraph,
diagnostics: &RenderDiagnostics,
scale: f32,
text: &TextRaster,
) -> RasterScene {
let mut scene = RasterScene::new();
fill_raster_scene(&mut scene, graph, diagnostics, scale, None, text);
scene
}
mod draw_child;
mod in_place;
mod layer_shadow;
mod space;
#[cfg(test)]
pub(crate) use draw_child::push_draw_primitive;
use draw_child::render_draw_child;
pub(crate) use in_place::{InPlaceUpdate, UpdatedReach};
use layer_shadow::push_layer_shadow;
use space::{push_seen_surfaces, seen_surfaces};
fn scene_light(graph: &RenderGraph, scale: f32) -> ShadowLight {
ShadowLight::for_window(
graph.root.local_bounds.width * scale,
graph.root.local_bounds.height * scale,
scale,
1.0,
)
}
pub(crate) fn fill_raster_scene(
scene: &mut RasterScene,
graph: &RenderGraph,
diagnostics: &RenderDiagnostics,
scale: f32,
previous: Option<&RasterScene>,
text: &TextRaster,
) {
scene.clear();
let light = scene_light(graph, scale);
scene.start = Some((scale, light));
let mut reuse = previous.map(|old| Reuse {
old,
cursor: 0,
pending: None,
});
populate_draws_from_graph(
Subtree::Layer(&graph.root),
scene,
&RasterTraversalContext {
parent_transform: &ProjectiveTransform::uniform_scale(scale),
parent_content_style: &ContentStyle::PLAIN,
parent_visual_clip: None,
inherited_translated_snap_anchor: None,
inherited_translated_content_context: false,
diagnostics,
light,
same_as_before: previous.is_some_and(|old| old.start == scene.start),
text,
surface_root: false,
in_space: false,
},
&mut reuse,
);
flush_copies(&mut reuse, scene);
}
#[derive(Clone, Copy)]
struct RasterLayerBounds {
device_origin: Point,
local_bounds: Rect,
}
impl RasterLayerBounds {
fn from_transformed_bounds(transformed_bounds: Rect, local_bounds: Rect) -> Self {
Self {
device_origin: Point::new(transformed_bounds.x, transformed_bounds.y),
local_bounds,
}
}
fn raster_rect(self) -> Rect {
Rect {
x: self.device_origin.x,
y: self.device_origin.y,
width: self.local_bounds.width,
height: self.local_bounds.height,
}
}
}
struct RasterLayerMapping {
layer_bounds: RasterLayerBounds,
content_bounds: RasterLayerBounds,
transformed_bounds: Rect,
content_style: ContentStyle,
scale_x: f32,
scale_y: f32,
}
const PLAIN_CONTENT_LAYER: GraphicsLayer = GraphicsLayer {
transform_origin: TransformOrigin::new(0.0, 0.0),
..GraphicsLayer::DEFAULT
};
impl RasterLayerMapping {
fn draws_plain(&self) -> bool {
self.content_style.alpha == 1.0
&& self.content_style.color_filter.is_none()
&& self.scale_x == 1.0
&& self.scale_y == 1.0
}
fn content_layer(&self) -> GraphicsLayer {
GraphicsLayer {
alpha: self.content_style.alpha,
color_filter: self.content_style.color_filter.clone(),
scale_x: self.scale_x,
scale_y: self.scale_y,
transform_origin: TransformOrigin::new(0.0, 0.0),
..GraphicsLayer::default()
}
}
}
fn raster_layer_scale(transformed_bounds: Rect, local_bounds: Rect) -> (f32, f32) {
(
axis_scale(transformed_bounds.width, local_bounds.width).max(0.0),
axis_scale(transformed_bounds.height, local_bounds.height).max(0.0),
)
}
fn axis_scale(transformed: f32, local: f32) -> f32 {
if local.abs() <= f32::EPSILON || (transformed == local && local.is_finite()) {
1.0
} else {
transformed / local
}
}
fn layer_raster_mapping(
layer: &LayerNode,
transform: &ProjectiveTransform,
parent_content_style: &ContentStyle,
) -> RasterLayerMapping {
let content_style = if layer.draw_phase.is_some() {
parent_content_style.clone()
} else {
parent_content_style.under(&layer.graphics_layer)
};
let bounds = layer.local_bounds;
let node_rect = layer.node_rect();
let same = |a: f32, b: f32| a.to_bits() == b.to_bits();
let content_bounds = (!(same(node_rect.x, bounds.x)
&& same(node_rect.y, bounds.y)
&& same(node_rect.width, bounds.width)
&& same(node_rect.height, bounds.height)))
.then(|| {
RasterLayerBounds::from_transformed_bounds(transform.bounds_for_rect(node_rect), node_rect)
});
raster_layer_mapping(bounds, content_bounds, transform, content_style)
}
fn raster_layer_mapping(
local_bounds: Rect,
content_bounds: Option<RasterLayerBounds>,
transform: &ProjectiveTransform,
content_style: ContentStyle,
) -> RasterLayerMapping {
let transformed_bounds = transform.bounds_for_rect(local_bounds);
let (scale_x, scale_y) = raster_layer_scale(transformed_bounds, local_bounds);
let layer_bounds = RasterLayerBounds::from_transformed_bounds(transformed_bounds, local_bounds);
RasterLayerMapping {
layer_bounds,
content_bounds: content_bounds.unwrap_or(layer_bounds),
transformed_bounds,
content_style,
scale_x,
scale_y,
}
}
#[inline(never)]
fn push_layer_shadow_of(
scene: &mut RasterScene,
layer: &LayerNode,
mapping: &RasterLayerMapping,
transform: &ProjectiveTransform,
parent_visual_clip: Option<Rect>,
light: ShadowLight,
) {
if layer.graphics_layer.shadow_elevation <= 0.0 {
return;
}
let shadow_clip = resolve_clip(
parent_visual_clip,
layer
.shadow_clip()
.map(|clip| transform.bounds_for_rect(clip)),
);
let shadow_layer = GraphicsLayer {
alpha: layer.graphics_layer.alpha,
scale_x: mapping.scale_x,
scale_y: mapping.scale_y,
transform_origin: TransformOrigin::new(0.0, 0.0),
shadow_elevation: layer.graphics_layer.shadow_elevation,
ambient_shadow_color: layer.graphics_layer.ambient_shadow_color,
spot_shadow_color: layer.graphics_layer.spot_shadow_color,
shape: layer.graphics_layer.shape,
..GraphicsLayer::default()
};
push_layer_shadow(
scene,
&shadow_layer,
mapping.layer_bounds,
mapping.transformed_bounds,
shadow_clip,
light,
);
}
fn populate_draws_from_graph(
subtree: Subtree<'_>,
scene: &mut RasterScene,
context: &RasterTraversalContext<'_>,
reuse: &mut Option<Reuse<'_>>,
) {
let mut before = None;
if let Some(reuse) = reuse.as_mut()
&& let Some(index) = reuse.find(subtree.identity())
&& let Some(record) = reuse.old.subtrees.get(index)
{
if context.same_as_before
&& record.change == subtree.change()
&& record.transform == subtree.transform_to_parent()
{
let (end, stop) = (record.end, record.stop);
reuse.cursor = end;
reuse.copy_later(scene, index, end, stop);
return;
}
reuse.cursor = index + 1;
before = Some((record.end, record.passed_down));
}
flush_copies(reuse, scene);
let record = scene.open_subtree(subtree);
match subtree {
Subtree::Layer(layer) => push_layer_items(
layer,
scene,
context,
record,
before.and_then(|(_, passed_down)| passed_down),
reuse,
),
Subtree::Inline(inline) => push_inline_items(inline, scene, context),
}
flush_copies(reuse, scene);
scene.close_subtree(record);
if let (Some(reuse), Some((end, _))) = (reuse.as_mut(), before) {
reuse.cursor = end;
}
}
#[inline(never)]
fn push_layer_surface(
layer: &LayerNode,
scene: &mut RasterScene,
context: &RasterTraversalContext<'_>,
transform: &ProjectiveTransform,
) {
let Some(placement) = SurfacePlacement::of(layer.local_bounds, transform) else {
return;
};
let Some(parent_transform) = layer
.transform_to_parent
.inverse()
.map(|inverse| inverse.then(placement.to_surface))
else {
return;
};
let content_style = ContentStyle {
alpha: 1.0,
color_filter: context.parent_content_style.color_filter.clone(),
};
let mut items = RasterScene::new();
let record = items.open_subtree(Subtree::Layer(layer));
push_layer_items(
layer,
&mut items,
&RasterTraversalContext {
parent_transform: &parent_transform,
parent_content_style: &content_style,
parent_visual_clip: Some(Rect {
x: 0.0,
y: 0.0,
width: placement.width as f32,
height: placement.height as f32,
}),
inherited_translated_snap_anchor: None,
inherited_translated_content_context: false,
diagnostics: context.diagnostics,
light: context.light,
same_as_before: false,
text: context.text,
surface_root: true,
in_space: context.in_space,
},
record,
None,
&mut None,
);
items.close_subtree(record);
if let Some(image) = rasterize_surface(
&items,
placement.width,
placement.height,
context.diagnostics,
context.text,
) {
scene.push_surface_image(
image,
placement.rect,
placement.to_device,
context.parent_visual_clip,
context.parent_content_style.alpha,
);
}
}
fn push_layer_items(
layer: &LayerNode,
scene: &mut RasterScene,
context: &RasterTraversalContext<'_>,
record: usize,
passed_down_before: Option<usize>,
reuse: &mut Option<Reuse<'_>>,
) {
let diagnostics = context.diagnostics;
let transform = layer.transform_to_parent.then(*context.parent_transform);
let mapping = layer_raster_mapping(layer, &transform, context.parent_content_style);
let styled_content_layer;
let raster_content_layer = if mapping.draws_plain() {
&PLAIN_CONTENT_LAYER
} else {
styled_content_layer = mapping.content_layer();
&styled_content_layer
};
report_unsupported_effects(&layer.graphics_layer, diagnostics);
if mapping.transformed_bounds.width <= 0.0 || mapping.transformed_bounds.height <= 0.0 {
return;
}
let visual_clip = resolve_clip(
context.parent_visual_clip,
layer
.visual_clip_rect()
.map(|clip| transform.bounds_for_rect(clip)),
);
let effective_translated_content_context =
context.inherited_translated_content_context || layer.translated_content_context;
let (translated_snap_anchor, layer_snap_anchor) =
layer_snap_anchors(layer, context, &transform, &mapping, raster_content_layer);
if visual_clip.is_some_and(|clip| clip.is_empty()) {
return;
}
if !context.surface_root {
push_layer_shadow_of(
scene,
layer,
&mapping,
&transform,
context.parent_visual_clip,
context.light,
);
if layer.isolation.explicit_offscreen || !scales_and_moves(&transform) {
push_layer_surface(layer, scene, context, &transform);
return;
}
}
let primitive_context = PrimitiveRenderContext {
layer_bounds: mapping.content_bounds,
node_layer: raster_content_layer,
visual_clip,
motion_context_animated: layer.motion_context_animated,
content_offset_translation: effective_translated_content_context,
layer_snap_anchor,
};
let (mut seen, in_space) = layer_space(layer, &transform, context.in_space);
let mut children_same_as_before = None;
let mut push_child =
|child: &RenderNode, scene: &mut RasterScene, reuse: &mut Option<Reuse<'_>>| {
let Some(subtree) = Subtree::of_child(child) else {
flush_copies(reuse, scene);
return render_draw_child(scene, child, primitive_context);
};
if let Subtree::Layer(child_layer) = subtree {
if let Some(surfaces) = seen.take_if(|_| child_layer.has_space()) {
flush_copies(reuse, scene);
push_seen_surfaces(
surfaces,
scene,
context,
(&mapping.content_style, visual_clip),
);
}
if drawn_by_its_scene(child_layer, in_space) {
return;
}
}
let same_as_before = *children_same_as_before.get_or_insert_with(|| {
let passed_down = PassedDown {
transform,
alpha: mapping.content_style.alpha,
color_filter: mapping.content_style.color_filter.clone(),
clip: visual_clip,
snap_anchor: translated_snap_anchor,
translated: effective_translated_content_context,
};
note_passed_down(
scene,
record,
passed_down,
context,
passed_down_before,
reuse,
)
});
populate_draws_from_graph(
subtree,
scene,
&RasterTraversalContext {
parent_transform: &transform,
parent_content_style: &mapping.content_style,
parent_visual_clip: visual_clip,
inherited_translated_snap_anchor: translated_snap_anchor,
inherited_translated_content_context: effective_translated_content_context,
diagnostics,
light: context.light,
same_as_before,
text: context.text,
surface_root: false,
in_space,
},
reuse,
);
};
for_each_in_draw_order(&layer.children, |child| push_child(child, scene, reuse));
}
fn push_inline_items(
inline: &InlineNode,
scene: &mut RasterScene,
context: &RasterTraversalContext<'_>,
) {
let transform = inline.transform_to_parent().then(*context.parent_transform);
let node_rect = inline.node_rect();
let content_style = context.parent_content_style.under(&GraphicsLayer::DEFAULT);
let mapping = raster_layer_mapping(node_rect, None, &transform, content_style);
if mapping.transformed_bounds.width <= 0.0 || mapping.transformed_bounds.height <= 0.0 {
return;
}
let styled_content_layer;
let raster_content_layer = if mapping.draws_plain() {
&PLAIN_CONTENT_LAYER
} else {
styled_content_layer = mapping.content_layer();
&styled_content_layer
};
let translated = context.inherited_translated_content_context;
let layer_snap_anchor = context.inherited_translated_snap_anchor.or_else(|| {
((translated || !inline.motion_context_animated)
&& content_needs_rigid_snap(&inline.content, translated))
.then(|| rigid_snap_anchor(mapping.layer_bounds.raster_rect(), raster_content_layer))
.flatten()
});
let primitive_context = PrimitiveRenderContext {
layer_bounds: mapping.content_bounds,
node_layer: raster_content_layer,
visual_clip: context.parent_visual_clip,
motion_context_animated: inline.motion_context_animated,
content_offset_translation: translated,
layer_snap_anchor,
};
for_each_in_draw_order(&inline.content, |child| {
render_draw_child(scene, child, primitive_context);
});
}
fn layer_space<'a>(
layer: &'a LayerNode,
transform: &ProjectiveTransform,
in_space: bool,
) -> (Option<Vec<(&'a LayerNode, ProjectiveTransform)>>, bool) {
if layer.graphics_layer.space.is_none() {
return (None, in_space);
}
let seen = seen_surfaces(layer, transform);
let in_space = in_space || seen.is_some();
(seen, in_space)
}
fn drawn_by_its_scene(layer: &LayerNode, in_space: bool) -> bool {
in_space
&& layer.graphics_layer.space.is_some()
&& cranpose_render_common::space::scene_of(layer).is_none()
}
fn layer_snap_anchors(
layer: &LayerNode,
context: &RasterTraversalContext<'_>,
transform: &ProjectiveTransform,
mapping: &RasterLayerMapping,
content_layer: &GraphicsLayer,
) -> (Option<Point>, Option<Point>) {
let effective_translated_content_context =
context.inherited_translated_content_context || layer.translated_content_context;
let allow_rigid_snap = effective_translated_content_context || !layer.motion_context_animated;
let boundary_snap_anchor = if !context.inherited_translated_content_context
&& layer.translated_content_context
&& allow_rigid_snap
{
rigid_snap_anchor(
transform.bounds_for_rect(layer.local_bounds.translate(
layer.translated_content_offset.x,
layer.translated_content_offset.y,
)),
content_layer,
)
} else {
None
};
let translated_snap_anchor = context
.inherited_translated_snap_anchor
.or(boundary_snap_anchor);
let layer_snap_anchor = translated_snap_anchor.or_else(|| {
if allow_rigid_snap
&& content_needs_rigid_snap(&layer.children, effective_translated_content_context)
{
rigid_snap_anchor(mapping.layer_bounds.raster_rect(), content_layer)
} else {
None
}
});
(translated_snap_anchor, layer_snap_anchor)
}
fn note_passed_down(
scene: &mut RasterScene,
record: usize,
passed_down: PassedDown,
context: &RasterTraversalContext<'_>,
before: Option<usize>,
reuse: &Option<Reuse<'_>>,
) -> bool {
let same = context.same_as_before
&& before.and_then(|at| reuse.as_ref()?.old.passed_down.get(at)) == Some(&passed_down);
scene.note_passed_down(record, passed_down);
same
}
#[cfg(test)]
#[path = "tests/pipeline_tests.rs"]
mod tests;