use std::collections::BTreeMap;
use azul_core::{
dom::{NodeId, NodeType},
geom::{LogicalPosition, LogicalRect, LogicalSize},
hit_test::ScrollPosition,
resources::RendererResources,
styled_dom::StyledDom,
};
use azul_css::{
corety::LayoutDebugMessage,
css::CssPropertyValue,
props::{
basic::pixel::PixelValue,
layout::{LayoutPosition, LayoutWritingMode},
property::{CssProperty, CssPropertyType},
},
};
use crate::{
font_traits::{FontLoaderTrait, ParsedFontTrait},
solver3::{
fc::{layout_formatting_context, LayoutConstraints, TextAlign},
getters::{
get_aspect_ratio_property, get_direction_property, get_display_property, get_writing_mode, get_position, MultiValue,
get_css_top, get_css_bottom, get_css_left, get_css_right,
get_css_height, get_css_width,
},
layout_tree::LayoutTree,
LayoutContext, LayoutError, Result,
},
};
#[derive(Debug, Default)]
struct PositionOffsets {
top: Option<f32>,
right: Option<f32>,
bottom: Option<f32>,
left: Option<f32>,
}
pub fn get_position_type(styled_dom: &StyledDom, dom_id: Option<NodeId>) -> LayoutPosition {
let Some(id) = dom_id else {
return LayoutPosition::Static;
};
let node_state = &styled_dom.styled_nodes.as_container()[id].styled_node_state;
get_position(styled_dom, id, node_state).unwrap_or_default()
}
fn resolve_position_offsets(
styled_dom: &StyledDom,
dom_id: Option<NodeId>,
cb_size: LogicalSize,
) -> PositionOffsets {
use azul_css::props::basic::pixel::{PhysicalSize, PropertyContext, ResolutionContext};
use crate::solver3::getters::{
get_element_font_size, get_parent_font_size, get_root_font_size,
};
let Some(id) = dom_id else {
return PositionOffsets::default();
};
let node_state = &styled_dom.styled_nodes.as_container()[id].styled_node_state;
let element_font_size = get_element_font_size(styled_dom, id, node_state);
let parent_font_size = get_parent_font_size(styled_dom, id, node_state);
let root_font_size = get_root_font_size(styled_dom, node_state);
let containing_block_size = PhysicalSize::new(cb_size.width, cb_size.height);
let resolution_context = ResolutionContext {
element_font_size,
parent_font_size,
root_font_size,
containing_block_size,
element_size: None, viewport_size: PhysicalSize::new(0.0, 0.0),
};
let mut offsets = PositionOffsets::default();
offsets.top = match get_css_top(styled_dom, id, node_state) {
MultiValue::Exact(pv) => Some(pv.resolve_with_context(&resolution_context, PropertyContext::Height)),
_ => None,
};
offsets.bottom = match get_css_bottom(styled_dom, id, node_state) {
MultiValue::Exact(pv) => Some(pv.resolve_with_context(&resolution_context, PropertyContext::Height)),
_ => None,
};
offsets.left = match get_css_left(styled_dom, id, node_state) {
MultiValue::Exact(pv) => Some(pv.resolve_with_context(&resolution_context, PropertyContext::Width)),
_ => None,
};
offsets.right = match get_css_right(styled_dom, id, node_state) {
MultiValue::Exact(pv) => Some(pv.resolve_with_context(&resolution_context, PropertyContext::Width)),
_ => None,
};
offsets
}
pub fn position_out_of_flow_elements<T: ParsedFontTrait>(
ctx: &mut LayoutContext<'_, T>,
tree: &mut LayoutTree,
calculated_positions: &mut super::PositionVec,
viewport: LogicalRect,
) -> Result<()> {
for node_index in 0..tree.nodes.len() {
let node = &tree.nodes[node_index];
let dom_id = match node.dom_node_id {
Some(id) => id,
None => continue,
};
let position_type = get_position_type(ctx.styled_dom, Some(dom_id));
if position_type == LayoutPosition::Absolute || position_type == LayoutPosition::Fixed {
{
use azul_core::dom::FormattingContext;
let parent_is_flex_or_grid = node.parent.and_then(|p| tree.get(p)).map_or(false, |pn| {
matches!(pn.formatting_context, FormattingContext::Flex | FormattingContext::Grid)
});
if parent_is_flex_or_grid {
continue;
}
}
let parent_info: Option<(usize, LogicalPosition, f32, f32, f32, f32)> = {
let node = &tree.nodes[node_index];
node.parent.and_then(|parent_idx| {
let parent_node = tree.get(parent_idx)?;
let parent_dom_id = parent_node.dom_node_id?;
let parent_position = get_position_type(ctx.styled_dom, Some(parent_dom_id));
if parent_position == LayoutPosition::Absolute
|| parent_position == LayoutPosition::Fixed
{
calculated_positions.get(parent_idx).map(|parent_pos| {
let pbp = parent_node.box_props.unpack();
(
parent_idx,
*parent_pos,
pbp.border.left,
pbp.border.top,
pbp.padding.left,
pbp.padding.top,
)
})
} else {
None
}
})
};
let containing_block_rect = if position_type == LayoutPosition::Fixed {
viewport
} else {
find_absolute_containing_block_rect(
tree,
node_index,
ctx.styled_dom,
calculated_positions,
viewport,
)?
};
let node = &tree.nodes[node_index];
let element_size = if let Some(size) = node.used_size {
size
} else {
let intrinsic = tree.warm(node_index).and_then(|w| w.intrinsic_sizes).unwrap_or_default();
let size = crate::solver3::sizing::calculate_used_size_for_node(
ctx.styled_dom,
Some(dom_id),
containing_block_rect.size,
intrinsic,
&node.box_props.unpack(),
ctx.viewport_size,
)?;
if let Some(node_mut) = tree.get_mut(node_index) {
node_mut.used_size = Some(size);
}
size
};
let offsets =
resolve_position_offsets(ctx.styled_dom, Some(dom_id), containing_block_rect.size);
let mut static_pos = calculated_positions
.get(node_index)
.copied()
.unwrap_or_default();
if position_type == LayoutPosition::Fixed {
if let Some((_, parent_pos, border_left, border_top, padding_left, padding_top)) =
parent_info
{
static_pos = LogicalPosition::new(
parent_pos.x + border_left + padding_left,
parent_pos.y + border_top + padding_top,
);
}
}
let mut final_pos = LogicalPosition::zero();
let node_state = &ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
let (margin_top_val, margin_bottom_val, margin_auto,
margin_left_val, margin_right_val, margin_left_auto_flag, margin_right_auto_flag) = {
let node = &tree.nodes[node_index];
let nbp = node.box_props.unpack();
(nbp.margin.top, nbp.margin.bottom,
nbp.margin_auto,
nbp.margin.left, nbp.margin.right,
nbp.margin_auto.left, nbp.margin_auto.right)
};
let cb_height = containing_block_rect.size.height;
let css_height = get_css_height(ctx.styled_dom, dom_id, node_state);
let node_data = &ctx.styled_dom.node_data.as_container()[dom_id];
let is_replaced = matches!(node_data.node_type, NodeType::Image(_))
|| node_data.is_virtual_view_node();
let height_is_auto = css_height.is_auto() && !is_replaced;
let top_is_auto = offsets.top.is_none();
let bottom_is_auto = offsets.bottom.is_none();
let mut used_height = element_size.height;
let mut used_margin_top = if margin_auto.top { 0.0 } else { margin_top_val };
let mut used_margin_bottom = if margin_auto.bottom { 0.0 } else { margin_bottom_val };
if top_is_auto && height_is_auto && bottom_is_auto {
final_pos.y = static_pos.y;
} else if !top_is_auto && !height_is_auto && !bottom_is_auto {
let top_val = offsets.top.unwrap();
let bottom_val = offsets.bottom.unwrap();
if margin_auto.top && margin_auto.bottom {
let available = cb_height - top_val - used_height - bottom_val;
let each = available / 2.0;
used_margin_top = each;
used_margin_bottom = each;
} else if margin_auto.top {
used_margin_top = cb_height - top_val - used_height - used_margin_bottom - bottom_val;
} else if margin_auto.bottom {
used_margin_bottom = cb_height - top_val - used_height - used_margin_top - bottom_val;
}
final_pos.y = containing_block_rect.origin.y + top_val + used_margin_top;
} else if top_is_auto && height_is_auto && !bottom_is_auto {
let bottom_val = offsets.bottom.unwrap();
let top_val = cb_height - used_margin_top - used_height - used_margin_bottom - bottom_val;
final_pos.y = containing_block_rect.origin.y + top_val + used_margin_top;
} else if top_is_auto && bottom_is_auto && !height_is_auto {
final_pos.y = static_pos.y;
} else if height_is_auto && bottom_is_auto && !top_is_auto {
let top_val = offsets.top.unwrap();
final_pos.y = containing_block_rect.origin.y + top_val + used_margin_top;
} else if top_is_auto && !height_is_auto && !bottom_is_auto {
let bottom_val = offsets.bottom.unwrap();
let top_val = cb_height - used_margin_top - used_height - used_margin_bottom - bottom_val;
final_pos.y = containing_block_rect.origin.y + top_val + used_margin_top;
} else if height_is_auto && !top_is_auto && !bottom_is_auto {
let has_aspect_ratio = matches!(
get_aspect_ratio_property(ctx.styled_dom, dom_id, node_state),
MultiValue::Exact(azul_css::props::style::effects::StyleAspectRatio::Ratio(_))
);
let top_val = offsets.top.unwrap();
let bottom_val = offsets.bottom.unwrap();
if !has_aspect_ratio {
used_height = (cb_height - top_val - used_margin_top - used_margin_bottom - bottom_val).max(0.0);
}
final_pos.y = containing_block_rect.origin.y + top_val + used_margin_top;
if let Some(node_mut) = tree.get_mut(node_index) {
if let Some(ref mut size) = node_mut.used_size {
size.height = used_height;
}
}
} else if bottom_is_auto && !top_is_auto && !height_is_auto {
let top_val = offsets.top.unwrap();
final_pos.y = containing_block_rect.origin.y + top_val + used_margin_top;
} else {
final_pos.y = static_pos.y;
}
{
let margin_left = margin_left_val;
let margin_right = margin_right_val;
let margin_left_auto = margin_left_auto_flag;
let margin_right_auto = margin_right_auto_flag;
let cb_width = containing_block_rect.size.width;
let border_box_width = element_size.width;
let left_val = offsets.left;
let right_val = offsets.right;
let left_is_auto = left_val.is_none();
let right_is_auto = right_val.is_none();
use azul_css::props::style::StyleDirection;
let cb_direction = {
let cb_dom_id = if position_type == LayoutPosition::Fixed {
None } else {
let mut parent = tree.nodes[node_index].parent;
let mut found = None;
while let Some(pidx) = parent {
if let Some(pnode) = tree.get(pidx) {
if get_position_type(ctx.styled_dom, pnode.dom_node_id).is_positioned() {
found = pnode.dom_node_id;
break;
}
parent = pnode.parent;
} else {
break;
}
}
found
};
match cb_dom_id {
Some(cb_id) => {
let cb_ns = &ctx.styled_dom.styled_nodes.as_container()[cb_id].styled_node_state;
match get_direction_property(ctx.styled_dom, cb_id, cb_ns) {
MultiValue::Exact(v) => v,
_ => StyleDirection::Ltr,
}
}
None => StyleDirection::Ltr,
}
};
let width_is_auto = get_css_width(ctx.styled_dom, dom_id, node_state).is_auto() && !is_replaced;
if !left_is_auto && !width_is_auto && !right_is_auto {
let left = left_val.unwrap();
let right = right_val.unwrap();
let remaining = cb_width - left - border_box_width - right;
if margin_left_auto && margin_right_auto {
let each_margin = remaining / 2.0;
if each_margin < 0.0 {
match cb_direction {
StyleDirection::Ltr => {
final_pos.x = containing_block_rect.origin.x + left;
}
StyleDirection::Rtl => {
final_pos.x = containing_block_rect.origin.x + left + remaining;
}
}
} else {
final_pos.x = containing_block_rect.origin.x + left + each_margin;
}
} else if margin_left_auto {
let solved_margin_left = remaining - margin_right;
final_pos.x = containing_block_rect.origin.x + left + solved_margin_left;
} else if margin_right_auto {
final_pos.x = containing_block_rect.origin.x + left + margin_left;
} else {
match cb_direction {
StyleDirection::Ltr => {
final_pos.x = containing_block_rect.origin.x + left + margin_left;
}
StyleDirection::Rtl => {
let solved_left = cb_width - margin_left - border_box_width - margin_right - right;
final_pos.x = containing_block_rect.origin.x + solved_left + margin_left;
}
}
}
} else {
let m_left = if margin_left_auto { 0.0 } else { margin_left };
let m_right = if margin_right_auto { 0.0 } else { margin_right };
if left_is_auto && width_is_auto && right_is_auto {
match cb_direction {
StyleDirection::Ltr => {
final_pos.x = static_pos.x;
}
StyleDirection::Rtl => {
let static_offset = static_pos.x - containing_block_rect.origin.x;
let right_static = (cb_width - static_offset - border_box_width).max(0.0);
let solved_left = cb_width - m_left - border_box_width - m_right - right_static;
final_pos.x = containing_block_rect.origin.x + solved_left + m_left;
}
}
} else if left_is_auto && width_is_auto && !right_is_auto {
let right = right_val.unwrap();
let solved_left = cb_width - m_left - border_box_width - m_right - right;
final_pos.x = containing_block_rect.origin.x + solved_left + m_left;
} else if left_is_auto && !width_is_auto && right_is_auto {
final_pos.x = static_pos.x;
} else if !left_is_auto && width_is_auto && right_is_auto {
let left = left_val.unwrap();
final_pos.x = containing_block_rect.origin.x + left + m_left;
} else if left_is_auto && !width_is_auto && !right_is_auto {
let right = right_val.unwrap();
let solved_left = cb_width - m_left - border_box_width - m_right - right;
final_pos.x = containing_block_rect.origin.x + solved_left + m_left;
} else if !left_is_auto && width_is_auto && !right_is_auto {
let has_aspect_ratio = matches!(
get_aspect_ratio_property(ctx.styled_dom, dom_id, node_state),
MultiValue::Exact(azul_css::props::style::effects::StyleAspectRatio::Ratio(_))
);
let left = left_val.unwrap();
let right = right_val.unwrap();
if !has_aspect_ratio {
let used_width = (cb_width - left - m_left - m_right - right).max(0.0);
if let Some(node_mut) = tree.get_mut(node_index) {
if let Some(ref mut size) = node_mut.used_size {
size.width = used_width;
}
}
}
final_pos.x = containing_block_rect.origin.x + left + m_left;
} else if !left_is_auto && !width_is_auto && right_is_auto {
let left = left_val.unwrap();
final_pos.x = containing_block_rect.origin.x + left + m_left;
} else {
final_pos.x = static_pos.x;
}
}
}
super::pos_set(calculated_positions, node_index, final_pos);
}
}
Ok(())
}
pub fn adjust_relative_positions<T: ParsedFontTrait>(
ctx: &mut LayoutContext<'_, T>,
tree: &LayoutTree,
calculated_positions: &mut super::PositionVec,
viewport: LogicalRect, ) -> Result<()> {
for node_index in 0..tree.nodes.len() {
let node = &tree.nodes[node_index];
let position_type = get_position_type(ctx.styled_dom, node.dom_node_id);
if position_type != LayoutPosition::Relative && position_type != LayoutPosition::Sticky {
continue;
}
{
use azul_css::props::layout::LayoutDisplay;
let display = get_display_property(ctx.styled_dom, node.dom_node_id);
if let MultiValue::Exact(d) = display {
if matches!(
d,
LayoutDisplay::TableColumnGroup
| LayoutDisplay::TableColumn
| LayoutDisplay::TableCell
| LayoutDisplay::TableCaption
) {
continue;
}
}
}
let containing_block_size = node.parent
.and_then(|parent_idx| tree.get(parent_idx))
.map(|parent_node| {
let parent_wm = parent_node.dom_node_id
.map(|pid| {
let ps = &ctx.styled_dom.styled_nodes.as_container()[pid].styled_node_state;
get_writing_mode(ctx.styled_dom, pid, ps).unwrap_or_default()
})
.unwrap_or_default();
let parent_used_size = parent_node.used_size.unwrap_or_default();
parent_node.box_props.inner_size(parent_used_size, parent_wm)
})
.unwrap_or(viewport.size);
let offsets =
resolve_position_offsets(ctx.styled_dom, node.dom_node_id, containing_block_size);
let Some(current_pos) = calculated_positions.get_mut(node_index) else {
continue;
};
let initial_pos = *current_pos;
let mut delta_x = 0.0;
let mut delta_y = 0.0;
if let Some(top) = offsets.top {
delta_y = top;
} else if let Some(bottom) = offsets.bottom {
delta_y = -bottom;
}
use azul_css::props::style::StyleDirection;
let cb_direction = node.parent
.and_then(|parent_idx| tree.get(parent_idx))
.and_then(|parent_node| {
let parent_dom_id = parent_node.dom_node_id?;
let parent_state =
&ctx.styled_dom.styled_nodes.as_container()[parent_dom_id].styled_node_state;
match get_direction_property(ctx.styled_dom, parent_dom_id, parent_state) {
MultiValue::Exact(v) => Some(v),
_ => None,
}
})
.unwrap_or(StyleDirection::Ltr);
match cb_direction {
StyleDirection::Ltr => {
if let Some(left) = offsets.left {
delta_x = left;
} else if let Some(right) = offsets.right {
delta_x = -right;
}
}
StyleDirection::Rtl => {
if let Some(right) = offsets.right {
delta_x = -right;
} else if let Some(left) = offsets.left {
delta_x = left;
}
}
}
if delta_x != 0.0 || delta_y != 0.0 {
current_pos.x += delta_x;
current_pos.y += delta_y;
ctx.debug_log(&format!(
"Adjusted relative element #{} from {:?} to {:?} (delta: {}, {})",
node_index, initial_pos, *current_pos, delta_x, delta_y
));
{
use azul_css::props::layout::LayoutDisplay;
let display = get_display_property(ctx.styled_dom, node.dom_node_id);
let is_table_row_like = matches!(
display,
MultiValue::Exact(
LayoutDisplay::TableRowGroup
| LayoutDisplay::TableHeaderGroup
| LayoutDisplay::TableFooterGroup
| LayoutDisplay::TableRow
)
);
if is_table_row_like {
let mut stack = tree.children(node_index).to_vec();
while let Some(child_idx) = stack.pop() {
if let Some(child_pos) = calculated_positions.get_mut(child_idx) {
child_pos.x += delta_x;
child_pos.y += delta_y;
}
stack.extend_from_slice(tree.children(child_idx));
}
}
}
}
}
Ok(())
}
#[derive(Debug, Clone)]
pub struct StickyConstraints {
pub top_inset: f32,
pub right_inset: f32,
pub bottom_inset: f32,
pub left_inset: f32,
pub normal_flow_position: LogicalPosition,
pub border_box_size: LogicalSize,
pub scrollport: LogicalRect,
}
fn find_nearest_scrollport(
tree: &LayoutTree,
node_index: usize,
styled_dom: &StyledDom,
calculated_positions: &super::PositionVec,
viewport: LogicalRect,
) -> LogicalRect {
use crate::solver3::getters::{get_overflow_x, get_overflow_y};
use azul_css::props::layout::LayoutOverflow;
let mut current_parent_idx = tree.get(node_index).and_then(|n| n.parent);
while let Some(parent_index) = current_parent_idx {
let parent_node = match tree.get(parent_index) {
Some(n) => n,
None => break,
};
let parent_dom_id = match parent_node.dom_node_id {
Some(id) => id,
None => {
current_parent_idx = parent_node.parent;
continue;
}
};
let node_state = &styled_dom.styled_nodes.as_container()[parent_dom_id].styled_node_state;
let ox = get_overflow_x(styled_dom, parent_dom_id, node_state);
let oy = get_overflow_y(styled_dom, parent_dom_id, node_state);
let is_scrollport = matches!(
ox,
MultiValue::Exact(LayoutOverflow::Scroll | LayoutOverflow::Auto)
) || matches!(
oy,
MultiValue::Exact(LayoutOverflow::Scroll | LayoutOverflow::Auto)
);
if is_scrollport {
let margin_box_pos = calculated_positions
.get(parent_index)
.copied()
.unwrap_or_default();
let border_box_size = parent_node.used_size.unwrap_or_default();
let pbp = parent_node.box_props.unpack();
let content_pos = LogicalPosition::new(
margin_box_pos.x
+ pbp.border.left
+ pbp.padding.left,
margin_box_pos.y
+ pbp.border.top
+ pbp.padding.top,
);
let content_size = LogicalSize::new(
(border_box_size.width
- pbp.border.left
- pbp.border.right
- pbp.padding.left
- pbp.padding.right)
.max(0.0),
(border_box_size.height
- pbp.border.top
- pbp.border.bottom
- pbp.padding.top
- pbp.padding.bottom)
.max(0.0),
);
return LogicalRect::new(content_pos, content_size);
}
current_parent_idx = parent_node.parent;
}
viewport
}
fn find_nearest_scroll_offset(
tree: &LayoutTree,
node_index: usize,
scroll_offsets: &BTreeMap<NodeId, ScrollPosition>,
) -> LogicalPosition {
let mut parent = tree.get(node_index).and_then(|n| n.parent);
while let Some(pidx) = parent {
if let Some(pnode) = tree.get(pidx) {
if let Some(dom_id) = pnode.dom_node_id {
if let Some(scroll_pos) = scroll_offsets.get(&dom_id) {
let offset_x = scroll_pos.children_rect.origin.x - scroll_pos.parent_rect.origin.x;
let offset_y = scroll_pos.children_rect.origin.y - scroll_pos.parent_rect.origin.y;
return LogicalPosition::new(offset_x, offset_y);
}
}
parent = pnode.parent;
} else {
break;
}
}
LogicalPosition::zero()
}
pub fn adjust_sticky_positions<T: ParsedFontTrait>(
ctx: &mut LayoutContext<'_, T>,
tree: &LayoutTree,
calculated_positions: &mut super::PositionVec,
scroll_offsets: &BTreeMap<NodeId, ScrollPosition>,
viewport: LogicalRect,
) -> Result<()> {
for node_index in 0..tree.nodes.len() {
let node = &tree.nodes[node_index];
let position_type = get_position_type(ctx.styled_dom, node.dom_node_id);
if position_type != LayoutPosition::Sticky {
continue;
}
let dom_id = match node.dom_node_id {
Some(id) => id,
None => continue,
};
let scrollport = find_nearest_scrollport(
tree,
node_index,
ctx.styled_dom,
calculated_positions,
viewport,
);
let containing_block = node.parent
.and_then(|parent_idx| {
let parent_node = tree.get(parent_idx)?;
let parent_pos = calculated_positions.get(parent_idx).copied().unwrap_or_default();
let parent_size = parent_node.used_size.unwrap_or_default();
let parent_wm = parent_node.dom_node_id
.map(|pid| {
let ps = &ctx.styled_dom.styled_nodes.as_container()[pid].styled_node_state;
get_writing_mode(ctx.styled_dom, pid, ps).unwrap_or_default()
})
.unwrap_or_default();
let pbp = parent_node.box_props.unpack();
let content_size = pbp.inner_size(parent_size, parent_wm);
let content_origin = LogicalPosition::new(
parent_pos.x + pbp.border.left + pbp.padding.left,
parent_pos.y + pbp.border.top + pbp.padding.top,
);
Some(LogicalRect::new(content_origin, content_size))
})
.unwrap_or(viewport);
let offsets = resolve_position_offsets(ctx.styled_dom, Some(dom_id), scrollport.size);
let scroll_offset = find_nearest_scroll_offset(tree, node_index, scroll_offsets);
let Some(current_pos) = calculated_positions.get_mut(node_index) else {
continue;
};
let static_pos = *current_pos;
let element_size = node.used_size.unwrap_or_default();
let nbp = node.box_props.unpack();
let margin = &nbp.margin;
let mut shift_x = 0.0f32;
let mut shift_y = 0.0f32;
if let Some(top_inset) = offsets.top {
let sticky_edge = scrollport.origin.y + scroll_offset.y + top_inset;
let border_top = current_pos.y;
if border_top < sticky_edge {
shift_y = shift_y.max(sticky_edge - border_top);
}
}
if let Some(bottom_inset) = offsets.bottom {
let sticky_edge = scrollport.origin.y + scroll_offset.y + scrollport.size.height - bottom_inset;
let border_bottom = current_pos.y + element_size.height;
if border_bottom > sticky_edge {
shift_y = shift_y.min(sticky_edge - border_bottom);
}
}
if let Some(left_inset) = offsets.left {
let sticky_edge = scrollport.origin.x + scroll_offset.x + left_inset;
let border_left = current_pos.x;
if border_left < sticky_edge {
shift_x = shift_x.max(sticky_edge - border_left);
}
}
if let Some(right_inset) = offsets.right {
let sticky_edge = scrollport.origin.x + scroll_offset.x + scrollport.size.width - right_inset;
let border_right = current_pos.x + element_size.width;
if border_right > sticky_edge {
shift_x = shift_x.min(sticky_edge - border_right);
}
}
if shift_y != 0.0 {
let margin_box_top = current_pos.y - margin.top + shift_y;
let margin_box_bottom = current_pos.y + element_size.height + margin.bottom + shift_y;
if margin_box_top < containing_block.origin.y {
shift_y += containing_block.origin.y - margin_box_top;
}
let cb_bottom = containing_block.origin.y + containing_block.size.height;
if margin_box_bottom > cb_bottom {
shift_y -= margin_box_bottom - cb_bottom;
}
}
if shift_x != 0.0 {
let margin_box_left = current_pos.x - margin.left + shift_x;
let margin_box_right = current_pos.x + element_size.width + margin.right + shift_x;
if margin_box_left < containing_block.origin.x {
shift_x += containing_block.origin.x - margin_box_left;
}
let cb_right = containing_block.origin.x + containing_block.size.width;
if margin_box_right > cb_right {
shift_x -= margin_box_right - cb_right;
}
}
if shift_x != 0.0 || shift_y != 0.0 {
current_pos.x += shift_x;
current_pos.y += shift_y;
ctx.debug_log(&format!(
"Adjusted sticky element #{} from {:?} to {:?}",
node_index, static_pos, *current_pos
));
}
}
Ok(())
}
pub fn find_absolute_containing_block_rect(
tree: &LayoutTree,
node_index: usize,
styled_dom: &StyledDom,
calculated_positions: &super::PositionVec,
viewport: LogicalRect,
) -> Result<LogicalRect> {
let mut current_parent_idx = tree.get(node_index).and_then(|n| n.parent);
while let Some(parent_index) = current_parent_idx {
let parent_node = tree.get(parent_index).ok_or(LayoutError::InvalidTree)?;
if get_position_type(styled_dom, parent_node.dom_node_id).is_positioned() {
let margin_box_pos = calculated_positions
.get(parent_index)
.copied()
.unwrap_or_default();
let border_box_size = parent_node.used_size.unwrap_or_default();
let pbp = parent_node.box_props.unpack();
let padding_box_pos = LogicalPosition::new(
margin_box_pos.x + pbp.border.left,
margin_box_pos.y + pbp.border.top,
);
let padding_box_size = LogicalSize::new(
border_box_size.width
- pbp.border.left
- pbp.border.right,
border_box_size.height
- pbp.border.top
- pbp.border.bottom,
);
return Ok(LogicalRect::new(padding_box_pos, padding_box_size));
}
current_parent_idx = parent_node.parent;
}
Ok(viewport)
}