1use std::collections::BTreeMap;
5
6use azul_core::{
7 dom::NodeId,
8 geom::{LogicalPosition, LogicalRect, LogicalSize},
9 hit_test::ScrollPosition,
10 resources::RendererResources,
11 styled_dom::StyledDom,
12};
13use azul_css::{
14 corety::LayoutDebugMessage,
15 css::CssPropertyValue,
16 props::{
17 basic::pixel::PixelValue,
18 layout::{LayoutPosition, LayoutWritingMode},
19 property::{CssProperty, CssPropertyType},
20 },
21};
22
23use crate::{
24 font_traits::{FontLoaderTrait, ParsedFontTrait},
25 solver3::{
26 fc::{layout_formatting_context, LayoutConstraints, TextAlign},
27 getters::get_writing_mode,
28 layout_tree::LayoutTree,
29 LayoutContext, LayoutError, Result,
30 },
31};
32
33#[derive(Debug, Default)]
34struct PositionOffsets {
35 top: Option<f32>,
36 right: Option<f32>,
37 bottom: Option<f32>,
38 left: Option<f32>,
39}
40
41pub fn get_position_type(styled_dom: &StyledDom, dom_id: Option<NodeId>) -> LayoutPosition {
46 let Some(id) = dom_id else {
47 return LayoutPosition::Static;
48 };
49 let node_data = &styled_dom.node_data.as_container()[id];
50 let node_state = &styled_dom.styled_nodes.as_container()[id].styled_node_state;
51 styled_dom
52 .css_property_cache
53 .ptr
54 .get_position(node_data, &id, node_state)
55 .and_then(|w| w.get_property().cloned())
56 .unwrap_or_default()
57}
58
59fn get_position_property(styled_dom: &StyledDom, node_id: NodeId) -> LayoutPosition {
61 let node_data = &styled_dom.node_data.as_container()[node_id];
62 let node_state = &styled_dom.styled_nodes.as_container()[node_id].styled_node_state;
63 styled_dom
64 .css_property_cache
65 .ptr
66 .get_position(node_data, &node_id, node_state)
67 .and_then(|p| p.get_property().copied())
68 .unwrap_or(LayoutPosition::Static)
69}
70
71fn resolve_position_offsets(
75 styled_dom: &StyledDom,
76 dom_id: Option<NodeId>,
77 cb_size: LogicalSize,
78) -> PositionOffsets {
79 use azul_css::props::basic::pixel::{PhysicalSize, PropertyContext, ResolutionContext};
80
81 use crate::solver3::getters::{
82 get_element_font_size, get_parent_font_size, get_root_font_size,
83 };
84
85 let Some(id) = dom_id else {
86 return PositionOffsets::default();
87 };
88 let node_data = &styled_dom.node_data.as_container()[id];
89 let node_state = &styled_dom.styled_nodes.as_container()[id].styled_node_state;
90
91 let element_font_size = get_element_font_size(styled_dom, id, node_state);
93 let parent_font_size = get_parent_font_size(styled_dom, id, node_state);
94 let root_font_size = get_root_font_size(styled_dom, node_state);
95
96 let containing_block_size = PhysicalSize::new(cb_size.width, cb_size.height);
97
98 let resolution_context = ResolutionContext {
99 element_font_size,
100 parent_font_size,
101 root_font_size,
102 containing_block_size,
103 element_size: None, viewport_size: PhysicalSize::new(0.0, 0.0),
105 };
106
107 let mut offsets = PositionOffsets::default();
108
109 offsets.top = styled_dom
112 .css_property_cache
113 .ptr
114 .get_top(node_data, &id, node_state)
115 .and_then(|t| t.get_property())
116 .map(|v| {
117 v.inner
118 .resolve_with_context(&resolution_context, PropertyContext::Height)
119 });
120
121 offsets.bottom = styled_dom
122 .css_property_cache
123 .ptr
124 .get_bottom(node_data, &id, node_state)
125 .and_then(|b| b.get_property())
126 .map(|v| {
127 v.inner
128 .resolve_with_context(&resolution_context, PropertyContext::Height)
129 });
130
131 offsets.left = styled_dom
133 .css_property_cache
134 .ptr
135 .get_left(node_data, &id, node_state)
136 .and_then(|l| l.get_property())
137 .map(|v| {
138 v.inner
139 .resolve_with_context(&resolution_context, PropertyContext::Width)
140 });
141
142 offsets.right = styled_dom
143 .css_property_cache
144 .ptr
145 .get_right(node_data, &id, node_state)
146 .and_then(|r| r.get_property())
147 .map(|v| {
148 v.inner
149 .resolve_with_context(&resolution_context, PropertyContext::Width)
150 });
151
152 offsets
153}
154
155pub fn position_out_of_flow_elements<T: ParsedFontTrait>(
158 ctx: &mut LayoutContext<'_, T>,
159 tree: &mut LayoutTree,
160 calculated_positions: &mut BTreeMap<usize, LogicalPosition>,
161 viewport: LogicalRect,
162) -> Result<()> {
163 for node_index in 0..tree.nodes.len() {
164 let node = &tree.nodes[node_index];
165 let dom_id = match node.dom_node_id {
166 Some(id) => id,
167 None => continue,
168 };
169
170 let position_type = get_position_type(ctx.styled_dom, Some(dom_id));
171
172 if position_type == LayoutPosition::Absolute || position_type == LayoutPosition::Fixed {
173 let parent_info: Option<(usize, LogicalPosition, f32, f32, f32, f32)> = {
175 let node = &tree.nodes[node_index];
176 node.parent.and_then(|parent_idx| {
177 let parent_node = tree.get(parent_idx)?;
178 let parent_dom_id = parent_node.dom_node_id?;
179 let parent_position = get_position_type(ctx.styled_dom, Some(parent_dom_id));
180 if parent_position == LayoutPosition::Absolute
181 || parent_position == LayoutPosition::Fixed
182 {
183 calculated_positions.get(&parent_idx).map(|parent_pos| {
184 (
185 parent_idx,
186 *parent_pos,
187 parent_node.box_props.border.left,
188 parent_node.box_props.border.top,
189 parent_node.box_props.padding.left,
190 parent_node.box_props.padding.top,
191 )
192 })
193 } else {
194 None
195 }
196 })
197 };
198
199 let containing_block_rect = if position_type == LayoutPosition::Fixed {
201 viewport
202 } else {
203 find_absolute_containing_block_rect(
204 tree,
205 node_index,
206 ctx.styled_dom,
207 calculated_positions,
208 viewport,
209 )?
210 };
211
212 let node = &tree.nodes[node_index];
214
215 let element_size = if let Some(size) = node.used_size {
218 size
219 } else {
220 let intrinsic = node.intrinsic_sizes.unwrap_or_default();
222 let size = crate::solver3::sizing::calculate_used_size_for_node(
223 ctx.styled_dom,
224 Some(dom_id),
225 containing_block_rect.size,
226 intrinsic,
227 &node.box_props,
228 )?;
229
230 if let Some(node_mut) = tree.get_mut(node_index) {
232 node_mut.used_size = Some(size);
233 }
234
235 size
236 };
237
238 let offsets =
240 resolve_position_offsets(ctx.styled_dom, Some(dom_id), containing_block_rect.size);
241
242 let mut static_pos = calculated_positions
243 .get(&node_index)
244 .copied()
245 .unwrap_or_default();
246
247 if position_type == LayoutPosition::Fixed && static_pos == LogicalPosition::zero() {
250 if let Some((_, parent_pos, border_left, border_top, padding_left, padding_top)) =
251 parent_info
252 {
253 static_pos = LogicalPosition::new(
255 parent_pos.x + border_left + padding_left,
256 parent_pos.y + border_top + padding_top,
257 );
258 }
259 }
260
261 let mut final_pos = LogicalPosition::zero();
262
263 if let Some(top) = offsets.top {
265 final_pos.y = containing_block_rect.origin.y + top;
266 } else if let Some(bottom) = offsets.bottom {
267 final_pos.y = containing_block_rect.origin.y + containing_block_rect.size.height
268 - element_size.height
269 - bottom;
270 } else {
271 final_pos.y = static_pos.y;
272 }
273
274 if let Some(left) = offsets.left {
276 final_pos.x = containing_block_rect.origin.x + left;
277 } else if let Some(right) = offsets.right {
278 final_pos.x = containing_block_rect.origin.x + containing_block_rect.size.width
279 - element_size.width
280 - right;
281 } else {
282 final_pos.x = static_pos.x;
283 }
284
285 calculated_positions.insert(node_index, final_pos);
286 }
287 }
288 Ok(())
289}
290
291pub fn adjust_relative_positions<T: ParsedFontTrait>(
297 ctx: &mut LayoutContext<'_, T>,
298 tree: &LayoutTree,
299 calculated_positions: &mut BTreeMap<usize, LogicalPosition>,
300 viewport: LogicalRect, ) -> Result<()> {
302 for node_index in 0..tree.nodes.len() {
304 let node = &tree.nodes[node_index];
305 let position_type = get_position_type(ctx.styled_dom, node.dom_node_id);
306
307 if position_type != LayoutPosition::Relative {
309 continue;
310 }
311
312 let containing_block_size = node.parent
315 .and_then(|parent_idx| tree.get(parent_idx))
316 .map(|parent_node| {
317 let parent_dom_id = parent_node.dom_node_id.unwrap_or(NodeId::ZERO);
319 let parent_node_state =
320 &ctx.styled_dom.styled_nodes.as_container()[parent_dom_id].styled_node_state;
321 let parent_wm =
322 get_writing_mode(ctx.styled_dom, parent_dom_id, parent_node_state)
323 .unwrap_or_default();
324 let parent_used_size = parent_node.used_size.unwrap_or_default();
325 parent_node.box_props.inner_size(parent_used_size, parent_wm)
326 })
327 .unwrap_or(viewport.size);
329
330 let offsets =
332 resolve_position_offsets(ctx.styled_dom, node.dom_node_id, containing_block_size);
333
334 let Some(current_pos) = calculated_positions.get_mut(&node_index) else {
336 continue;
337 };
338
339 let initial_pos = *current_pos;
340
341 let mut delta_x = 0.0;
343 let mut delta_y = 0.0;
344
345 if let Some(top) = offsets.top {
353 delta_y = top;
354 } else if let Some(bottom) = offsets.bottom {
355 delta_y = -bottom;
356 }
357
358 let node_dom_id = node.dom_node_id.unwrap_or(NodeId::ZERO);
361 let node_data = &ctx.styled_dom.node_data.as_container()[node_dom_id];
362 let node_state = &ctx.styled_dom.styled_nodes.as_container()[node_dom_id].styled_node_state;
363 let direction = ctx
364 .styled_dom
365 .css_property_cache
366 .ptr
367 .get_direction(node_data, &node_dom_id, node_state)
368 .and_then(|s| s.get_property().copied())
369 .unwrap_or(azul_css::props::style::StyleDirection::Ltr);
370
371 use azul_css::props::style::StyleDirection;
372 match direction {
373 StyleDirection::Ltr => {
374 if let Some(left) = offsets.left {
376 delta_x = left;
377 } else if let Some(right) = offsets.right {
378 delta_x = -right;
379 }
380 }
381 StyleDirection::Rtl => {
382 if let Some(right) = offsets.right {
384 delta_x = -right;
385 } else if let Some(left) = offsets.left {
386 delta_x = left;
387 }
388 }
389 }
390
391 if delta_x != 0.0 || delta_y != 0.0 {
393 current_pos.x += delta_x;
394 current_pos.y += delta_y;
395
396 ctx.debug_log(&format!(
397 "Adjusted relative element #{} from {:?} to {:?} (delta: {}, {})",
398 node_index, initial_pos, *current_pos, delta_x, delta_y
399 ));
400 }
401 }
402 Ok(())
403}
404
405fn find_absolute_containing_block_rect(
409 tree: &LayoutTree,
410 node_index: usize,
411 styled_dom: &StyledDom,
412 calculated_positions: &BTreeMap<usize, LogicalPosition>,
413 viewport: LogicalRect,
414) -> Result<LogicalRect> {
415 let mut current_parent_idx = tree.get(node_index).and_then(|n| n.parent);
416
417 while let Some(parent_index) = current_parent_idx {
418 let parent_node = tree.get(parent_index).ok_or(LayoutError::InvalidTree)?;
419
420 if get_position_type(styled_dom, parent_node.dom_node_id) != LayoutPosition::Static {
421 let margin_box_pos = calculated_positions
423 .get(&parent_index)
424 .copied()
425 .unwrap_or_default();
426 let border_box_size = parent_node.used_size.unwrap_or_default();
428
429 let padding_box_pos = LogicalPosition::new(
432 margin_box_pos.x + parent_node.box_props.border.left,
433 margin_box_pos.y + parent_node.box_props.border.top,
434 );
435
436 let padding_box_size = LogicalSize::new(
438 border_box_size.width
439 - parent_node.box_props.border.left
440 - parent_node.box_props.border.right,
441 border_box_size.height
442 - parent_node.box_props.border.top
443 - parent_node.box_props.border.bottom,
444 );
445
446 return Ok(LogicalRect::new(padding_box_pos, padding_box_size));
447 }
448 current_parent_idx = parent_node.parent;
449 }
450
451 Ok(viewport) }