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::{
28 get_direction_property, get_writing_mode, get_position, MultiValue,
29 get_css_top, get_css_bottom, get_css_left, get_css_right,
30 },
31 layout_tree::LayoutTree,
32 LayoutContext, LayoutError, Result,
33 },
34};
35
36#[derive(Debug, Default)]
37struct PositionOffsets {
38 top: Option<f32>,
39 right: Option<f32>,
40 bottom: Option<f32>,
41 left: Option<f32>,
42}
43
44pub 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_state = &styled_dom.styled_nodes.as_container()[id].styled_node_state;
50 get_position(styled_dom, id, node_state).unwrap_or_default()
51}
52
53fn get_position_property(styled_dom: &StyledDom, node_id: NodeId) -> LayoutPosition {
55 let node_state = &styled_dom.styled_nodes.as_container()[node_id].styled_node_state;
56 get_position(styled_dom, node_id, node_state).unwrap_or(LayoutPosition::Static)
57}
58
59fn resolve_position_offsets(
63 styled_dom: &StyledDom,
64 dom_id: Option<NodeId>,
65 cb_size: LogicalSize,
66) -> PositionOffsets {
67 use azul_css::props::basic::pixel::{PhysicalSize, PropertyContext, ResolutionContext};
68
69 use crate::solver3::getters::{
70 get_element_font_size, get_parent_font_size, get_root_font_size,
71 };
72
73 let Some(id) = dom_id else {
74 return PositionOffsets::default();
75 };
76 let node_state = &styled_dom.styled_nodes.as_container()[id].styled_node_state;
77
78 let element_font_size = get_element_font_size(styled_dom, id, node_state);
80 let parent_font_size = get_parent_font_size(styled_dom, id, node_state);
81 let root_font_size = get_root_font_size(styled_dom, node_state);
82
83 let containing_block_size = PhysicalSize::new(cb_size.width, cb_size.height);
84
85 let resolution_context = ResolutionContext {
86 element_font_size,
87 parent_font_size,
88 root_font_size,
89 containing_block_size,
90 element_size: None, viewport_size: PhysicalSize::new(0.0, 0.0),
92 };
93
94 let mut offsets = PositionOffsets::default();
95
96 offsets.top = match get_css_top(styled_dom, id, node_state) {
99 MultiValue::Exact(pv) => Some(pv.resolve_with_context(&resolution_context, PropertyContext::Height)),
100 _ => None,
101 };
102
103 offsets.bottom = match get_css_bottom(styled_dom, id, node_state) {
104 MultiValue::Exact(pv) => Some(pv.resolve_with_context(&resolution_context, PropertyContext::Height)),
105 _ => None,
106 };
107
108 offsets.left = match get_css_left(styled_dom, id, node_state) {
110 MultiValue::Exact(pv) => Some(pv.resolve_with_context(&resolution_context, PropertyContext::Width)),
111 _ => None,
112 };
113
114 offsets.right = match get_css_right(styled_dom, id, node_state) {
115 MultiValue::Exact(pv) => Some(pv.resolve_with_context(&resolution_context, PropertyContext::Width)),
116 _ => None,
117 };
118
119 offsets
120}
121
122pub fn position_out_of_flow_elements<T: ParsedFontTrait>(
125 ctx: &mut LayoutContext<'_, T>,
126 tree: &mut LayoutTree,
127 calculated_positions: &mut super::PositionVec,
128 viewport: LogicalRect,
129) -> Result<()> {
130 for node_index in 0..tree.nodes.len() {
131 let node = &tree.nodes[node_index];
132 let dom_id = match node.dom_node_id {
133 Some(id) => id,
134 None => continue,
135 };
136
137 let position_type = get_position_type(ctx.styled_dom, Some(dom_id));
138
139 if position_type == LayoutPosition::Absolute || position_type == LayoutPosition::Fixed {
140 let parent_info: Option<(usize, LogicalPosition, f32, f32, f32, f32)> = {
142 let node = &tree.nodes[node_index];
143 node.parent.and_then(|parent_idx| {
144 let parent_node = tree.get(parent_idx)?;
145 let parent_dom_id = parent_node.dom_node_id?;
146 let parent_position = get_position_type(ctx.styled_dom, Some(parent_dom_id));
147 if parent_position == LayoutPosition::Absolute
148 || parent_position == LayoutPosition::Fixed
149 {
150 calculated_positions.get(parent_idx).map(|parent_pos| {
151 (
152 parent_idx,
153 *parent_pos,
154 parent_node.box_props.border.left,
155 parent_node.box_props.border.top,
156 parent_node.box_props.padding.left,
157 parent_node.box_props.padding.top,
158 )
159 })
160 } else {
161 None
162 }
163 })
164 };
165
166 let containing_block_rect = if position_type == LayoutPosition::Fixed {
168 viewport
169 } else {
170 find_absolute_containing_block_rect(
171 tree,
172 node_index,
173 ctx.styled_dom,
174 calculated_positions,
175 viewport,
176 )?
177 };
178
179 let node = &tree.nodes[node_index];
181
182 let element_size = if let Some(size) = node.used_size {
185 size
186 } else {
187 let intrinsic = node.intrinsic_sizes.unwrap_or_default();
189 let size = crate::solver3::sizing::calculate_used_size_for_node(
190 ctx.styled_dom,
191 Some(dom_id),
192 containing_block_rect.size,
193 intrinsic,
194 &node.box_props,
195 ctx.viewport_size,
196 )?;
197
198 if let Some(node_mut) = tree.get_mut(node_index) {
200 node_mut.used_size = Some(size);
201 }
202
203 size
204 };
205
206 let offsets =
208 resolve_position_offsets(ctx.styled_dom, Some(dom_id), containing_block_rect.size);
209
210 let mut static_pos = calculated_positions
211 .get(node_index)
212 .copied()
213 .unwrap_or_default();
214
215 if position_type == LayoutPosition::Fixed {
220 if let Some((_, parent_pos, border_left, border_top, padding_left, padding_top)) =
221 parent_info
222 {
223 static_pos = LogicalPosition::new(
225 parent_pos.x + border_left + padding_left,
226 parent_pos.y + border_top + padding_top,
227 );
228 }
229 }
230
231 let mut final_pos = LogicalPosition::zero();
232
233 if let Some(top) = offsets.top {
235 final_pos.y = containing_block_rect.origin.y + top;
236 } else if let Some(bottom) = offsets.bottom {
237 final_pos.y = containing_block_rect.origin.y + containing_block_rect.size.height
238 - element_size.height
239 - bottom;
240 } else {
241 final_pos.y = static_pos.y;
242 }
243
244 if let Some(left) = offsets.left {
246 final_pos.x = containing_block_rect.origin.x + left;
247 } else if let Some(right) = offsets.right {
248 final_pos.x = containing_block_rect.origin.x + containing_block_rect.size.width
249 - element_size.width
250 - right;
251 } else {
252 final_pos.x = static_pos.x;
253 }
254
255 calculated_positions.insert(node_index, final_pos);
256 }
257 }
258 Ok(())
259}
260
261pub fn adjust_relative_positions<T: ParsedFontTrait>(
267 ctx: &mut LayoutContext<'_, T>,
268 tree: &LayoutTree,
269 calculated_positions: &mut super::PositionVec,
270 viewport: LogicalRect, ) -> Result<()> {
272 for node_index in 0..tree.nodes.len() {
274 let node = &tree.nodes[node_index];
275 let position_type = get_position_type(ctx.styled_dom, node.dom_node_id);
276
277 if position_type != LayoutPosition::Relative {
279 continue;
280 }
281
282 let containing_block_size = node.parent
285 .and_then(|parent_idx| tree.get(parent_idx))
286 .map(|parent_node| {
287 let parent_dom_id = parent_node.dom_node_id.unwrap_or(NodeId::ZERO);
289 let parent_node_state =
290 &ctx.styled_dom.styled_nodes.as_container()[parent_dom_id].styled_node_state;
291 let parent_wm =
292 get_writing_mode(ctx.styled_dom, parent_dom_id, parent_node_state)
293 .unwrap_or_default();
294 let parent_used_size = parent_node.used_size.unwrap_or_default();
295 parent_node.box_props.inner_size(parent_used_size, parent_wm)
296 })
297 .unwrap_or(viewport.size);
299
300 let offsets =
302 resolve_position_offsets(ctx.styled_dom, node.dom_node_id, containing_block_size);
303
304 let Some(current_pos) = calculated_positions.get_mut(node_index) else {
306 continue;
307 };
308
309 let initial_pos = *current_pos;
310
311 let mut delta_x = 0.0;
313 let mut delta_y = 0.0;
314
315 if let Some(top) = offsets.top {
323 delta_y = top;
324 } else if let Some(bottom) = offsets.bottom {
325 delta_y = -bottom;
326 }
327
328 let node_dom_id = node.dom_node_id.unwrap_or(NodeId::ZERO);
331 let node_state = &ctx.styled_dom.styled_nodes.as_container()[node_dom_id].styled_node_state;
332
333 use azul_css::props::style::StyleDirection;
334 let direction = match get_direction_property(ctx.styled_dom, node_dom_id, node_state) {
335 MultiValue::Exact(v) => v,
336 _ => StyleDirection::Ltr,
337 };
338 match direction {
339 StyleDirection::Ltr => {
340 if let Some(left) = offsets.left {
342 delta_x = left;
343 } else if let Some(right) = offsets.right {
344 delta_x = -right;
345 }
346 }
347 StyleDirection::Rtl => {
348 if let Some(right) = offsets.right {
350 delta_x = -right;
351 } else if let Some(left) = offsets.left {
352 delta_x = left;
353 }
354 }
355 }
356
357 if delta_x != 0.0 || delta_y != 0.0 {
359 current_pos.x += delta_x;
360 current_pos.y += delta_y;
361
362 ctx.debug_log(&format!(
363 "Adjusted relative element #{} from {:?} to {:?} (delta: {}, {})",
364 node_index, initial_pos, *current_pos, delta_x, delta_y
365 ));
366 }
367 }
368 Ok(())
369}
370
371fn find_absolute_containing_block_rect(
375 tree: &LayoutTree,
376 node_index: usize,
377 styled_dom: &StyledDom,
378 calculated_positions: &super::PositionVec,
379 viewport: LogicalRect,
380) -> Result<LogicalRect> {
381 let mut current_parent_idx = tree.get(node_index).and_then(|n| n.parent);
382
383 while let Some(parent_index) = current_parent_idx {
384 let parent_node = tree.get(parent_index).ok_or(LayoutError::InvalidTree)?;
385
386 if get_position_type(styled_dom, parent_node.dom_node_id) != LayoutPosition::Static {
387 let margin_box_pos = calculated_positions
389 .get(parent_index)
390 .copied()
391 .unwrap_or_default();
392 let border_box_size = parent_node.used_size.unwrap_or_default();
394
395 let padding_box_pos = LogicalPosition::new(
398 margin_box_pos.x + parent_node.box_props.border.left,
399 margin_box_pos.y + parent_node.box_props.border.top,
400 );
401
402 let padding_box_size = LogicalSize::new(
404 border_box_size.width
405 - parent_node.box_props.border.left
406 - parent_node.box_props.border.right,
407 border_box_size.height
408 - parent_node.box_props.border.top
409 - parent_node.box_props.border.bottom,
410 );
411
412 return Ok(LogicalRect::new(padding_box_pos, padding_box_size));
413 }
414 current_parent_idx = parent_node.parent;
415 }
416
417 Ok(viewport) }