|
| 1 | +use ie_css::resolve::ResolvedStyle; |
| 2 | +use ie_css::values::{CssValue, LengthUnit, PropertyId}; |
| 3 | + |
| 4 | +use crate::{LayoutTree, Rect}; |
| 5 | + |
| 6 | +/// Apply CSS positioned layout as a post-pass after normal flow. |
| 7 | +pub fn apply_positioned(tree: &mut LayoutTree, styles: &[ResolvedStyle], viewport: Rect) { |
| 8 | + let count = tree.boxes.len(); |
| 9 | + for i in 0..count { |
| 10 | + let node_id = match tree.boxes[i].node_id { |
| 11 | + Some(id) => id, |
| 12 | + None => continue, |
| 13 | + }; |
| 14 | + let style = match styles.get(node_id) { |
| 15 | + Some(s) => s, |
| 16 | + None => continue, |
| 17 | + }; |
| 18 | + let position = style |
| 19 | + .get(PropertyId::Position) |
| 20 | + .and_then(|v| match v { |
| 21 | + CssValue::Keyword(k) => Some(k.as_str()), |
| 22 | + _ => None, |
| 23 | + }) |
| 24 | + .unwrap_or("static"); |
| 25 | + |
| 26 | + match position { |
| 27 | + "relative" => apply_relative(i, tree, style), |
| 28 | + "absolute" => apply_absolute(i, tree, styles, viewport), |
| 29 | + "fixed" => apply_fixed(i, tree, style, viewport), |
| 30 | + _ => {} // static: nothing to do |
| 31 | + } |
| 32 | + } |
| 33 | +} |
| 34 | + |
| 35 | +fn apply_relative(box_idx: usize, tree: &mut LayoutTree, style: &ResolvedStyle) { |
| 36 | + let top = get_offset(style, PropertyId::Top); |
| 37 | + let left = get_offset(style, PropertyId::Left); |
| 38 | + let bottom = get_offset(style, PropertyId::Bottom); |
| 39 | + let right = get_offset(style, PropertyId::Right); |
| 40 | + |
| 41 | + // top takes precedence over bottom, left over right |
| 42 | + let dx = if left != 0.0 { left } else { -right }; |
| 43 | + let dy = if top != 0.0 { top } else { -bottom }; |
| 44 | + |
| 45 | + tree.boxes[box_idx].content_rect.x += dx; |
| 46 | + tree.boxes[box_idx].content_rect.y += dy; |
| 47 | +} |
| 48 | + |
| 49 | +fn apply_absolute(box_idx: usize, tree: &mut LayoutTree, styles: &[ResolvedStyle], viewport: Rect) { |
| 50 | + let node_id = match tree.boxes[box_idx].node_id { |
| 51 | + Some(id) => id, |
| 52 | + None => return, |
| 53 | + }; |
| 54 | + let style = match styles.get(node_id) { |
| 55 | + Some(s) => s, |
| 56 | + None => return, |
| 57 | + }; |
| 58 | + |
| 59 | + // Find containing block: nearest positioned ancestor, or viewport |
| 60 | + let containing = find_containing_block(box_idx, tree, styles).unwrap_or(viewport); |
| 61 | + |
| 62 | + let top = get_offset_option(style, PropertyId::Top); |
| 63 | + let left = get_offset_option(style, PropertyId::Left); |
| 64 | + let bottom = get_offset_option(style, PropertyId::Bottom); |
| 65 | + let right = get_offset_option(style, PropertyId::Right); |
| 66 | + |
| 67 | + if let Some(t) = top { |
| 68 | + tree.boxes[box_idx].content_rect.y = containing.y + t; |
| 69 | + } else if let Some(b) = bottom { |
| 70 | + tree.boxes[box_idx].content_rect.y = |
| 71 | + containing.y + containing.height - tree.boxes[box_idx].content_rect.height - b; |
| 72 | + } |
| 73 | + |
| 74 | + if let Some(l) = left { |
| 75 | + tree.boxes[box_idx].content_rect.x = containing.x + l; |
| 76 | + } else if let Some(r) = right { |
| 77 | + tree.boxes[box_idx].content_rect.x = |
| 78 | + containing.x + containing.width - tree.boxes[box_idx].content_rect.width - r; |
| 79 | + } |
| 80 | +} |
| 81 | + |
| 82 | +fn apply_fixed(box_idx: usize, tree: &mut LayoutTree, style: &ResolvedStyle, viewport: Rect) { |
| 83 | + let top = get_offset_option(style, PropertyId::Top); |
| 84 | + let left = get_offset_option(style, PropertyId::Left); |
| 85 | + let bottom = get_offset_option(style, PropertyId::Bottom); |
| 86 | + let right = get_offset_option(style, PropertyId::Right); |
| 87 | + |
| 88 | + if let Some(t) = top { |
| 89 | + tree.boxes[box_idx].content_rect.y = viewport.y + t; |
| 90 | + } else if let Some(b) = bottom { |
| 91 | + tree.boxes[box_idx].content_rect.y = |
| 92 | + viewport.y + viewport.height - tree.boxes[box_idx].content_rect.height - b; |
| 93 | + } |
| 94 | + |
| 95 | + if let Some(l) = left { |
| 96 | + tree.boxes[box_idx].content_rect.x = viewport.x + l; |
| 97 | + } else if let Some(r) = right { |
| 98 | + tree.boxes[box_idx].content_rect.x = |
| 99 | + viewport.x + viewport.width - tree.boxes[box_idx].content_rect.width - r; |
| 100 | + } |
| 101 | +} |
| 102 | + |
| 103 | +fn find_containing_block( |
| 104 | + box_idx: usize, |
| 105 | + tree: &LayoutTree, |
| 106 | + styles: &[ResolvedStyle], |
| 107 | +) -> Option<Rect> { |
| 108 | + // Walk up the tree to find nearest positioned ancestor |
| 109 | + for i in (0..box_idx).rev() { |
| 110 | + if tree.boxes[i].children.contains(&box_idx) { |
| 111 | + if let Some(node_id) = tree.boxes[i].node_id |
| 112 | + && let Some(style) = styles.get(node_id) |
| 113 | + { |
| 114 | + let pos = style |
| 115 | + .get(PropertyId::Position) |
| 116 | + .and_then(|v| match v { |
| 117 | + CssValue::Keyword(k) => Some(k.as_str()), |
| 118 | + _ => None, |
| 119 | + }) |
| 120 | + .unwrap_or("static"); |
| 121 | + if pos != "static" { |
| 122 | + return Some(tree.boxes[i].content_rect); |
| 123 | + } |
| 124 | + } |
| 125 | + // Continue searching upward from this parent |
| 126 | + return find_containing_block(i, tree, styles); |
| 127 | + } |
| 128 | + } |
| 129 | + None |
| 130 | +} |
| 131 | + |
| 132 | +fn get_offset(style: &ResolvedStyle, prop: PropertyId) -> f32 { |
| 133 | + match style.get(prop) { |
| 134 | + Some(CssValue::Length(v, LengthUnit::Px)) => *v as f32, |
| 135 | + Some(CssValue::Number(v)) => *v as f32, |
| 136 | + _ => 0.0, |
| 137 | + } |
| 138 | +} |
| 139 | + |
| 140 | +fn get_offset_option(style: &ResolvedStyle, prop: PropertyId) -> Option<f32> { |
| 141 | + match style.get(prop) { |
| 142 | + Some(CssValue::Length(v, LengthUnit::Px)) => Some(*v as f32), |
| 143 | + Some(CssValue::Number(v)) => Some(*v as f32), |
| 144 | + Some(CssValue::Auto) | None => None, |
| 145 | + _ => None, |
| 146 | + } |
| 147 | +} |
0 commit comments