use std::collections::BTreeMap;
use azul_core::{
dom::NodeId,
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_direction_property, get_writing_mode, get_position, MultiValue,
get_css_top, get_css_bottom, get_css_left, get_css_right,
},
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 get_position_property(styled_dom: &StyledDom, node_id: NodeId) -> LayoutPosition {
let node_state = &styled_dom.styled_nodes.as_container()[node_id].styled_node_state;
get_position(styled_dom, node_id, node_state).unwrap_or(LayoutPosition::Static)
}
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 {
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| {
(
parent_idx,
*parent_pos,
parent_node.box_props.border.left,
parent_node.box_props.border.top,
parent_node.box_props.padding.left,
parent_node.box_props.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 = node.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,
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();
if let Some(top) = offsets.top {
final_pos.y = containing_block_rect.origin.y + top;
} else if let Some(bottom) = offsets.bottom {
final_pos.y = containing_block_rect.origin.y + containing_block_rect.size.height
- element_size.height
- bottom;
} else {
final_pos.y = static_pos.y;
}
if let Some(left) = offsets.left {
final_pos.x = containing_block_rect.origin.x + left;
} else if let Some(right) = offsets.right {
final_pos.x = containing_block_rect.origin.x + containing_block_rect.size.width
- element_size.width
- right;
} else {
final_pos.x = static_pos.x;
}
calculated_positions.insert(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 {
continue;
}
let containing_block_size = node.parent
.and_then(|parent_idx| tree.get(parent_idx))
.map(|parent_node| {
let parent_dom_id = parent_node.dom_node_id.unwrap_or(NodeId::ZERO);
let parent_node_state =
&ctx.styled_dom.styled_nodes.as_container()[parent_dom_id].styled_node_state;
let parent_wm =
get_writing_mode(ctx.styled_dom, parent_dom_id, parent_node_state)
.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;
}
let node_dom_id = node.dom_node_id.unwrap_or(NodeId::ZERO);
let node_state = &ctx.styled_dom.styled_nodes.as_container()[node_dom_id].styled_node_state;
use azul_css::props::style::StyleDirection;
let direction = match get_direction_property(ctx.styled_dom, node_dom_id, node_state) {
MultiValue::Exact(v) => v,
_ => StyleDirection::Ltr,
};
match 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
));
}
}
Ok(())
}
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) != LayoutPosition::Static {
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 padding_box_pos = LogicalPosition::new(
margin_box_pos.x + parent_node.box_props.border.left,
margin_box_pos.y + parent_node.box_props.border.top,
);
let padding_box_size = LogicalSize::new(
border_box_size.width
- parent_node.box_props.border.left
- parent_node.box_props.border.right,
border_box_size.height
- parent_node.box_props.border.top
- parent_node.box_props.border.bottom,
);
return Ok(LogicalRect::new(padding_box_pos, padding_box_size));
}
current_parent_idx = parent_node.parent;
}
Ok(viewport) }