diff --git a/src/css.rs b/src/css.rs index 6b7f11d..97e1815 100644 --- a/src/css.rs +++ b/src/css.rs @@ -81,6 +81,16 @@ pub enum Value { ColorValue(Color), } +impl Value { + pub fn to_px(&self) -> f32 { + match *self { + Value::Length(f, Unit::Px) => f, + // TODO: other lengths + _ => 0.0 + } + } +} + impl std::fmt::Display for Value { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match *self { diff --git a/src/layout.rs b/src/layout.rs new file mode 100644 index 0000000..d84939e --- /dev/null +++ b/src/layout.rs @@ -0,0 +1,215 @@ +struct Rect { + x: f32, + y: f32, + width: f32, + height: f32, +} + +impl Rect { + fn expanded_by(self, edge: Edge) -> Rect { + Rect { + x: self.x - edge.left, + y: self.y - edge.top, + width: self.width + edge.left + edge.right, + height: self.height + edge.top + edge.bottom, + } + } +} + +struct Edge { + left: f32, + right: f32, + top: f32, + bottom: f32, +} + +impl Dimensions { + fn padding_box(self) -> Rect { + self.content.expanded_by(self.padding) + } + fn border_box(self) -> Rect { + self.padding_box().expanded_by(self.border) + } + fn margin_box(self) -> Rect { + self.border_box().expanded_by(self.margin) + } +} + +struct Dim { + content: Rect, + + padding: Edge, + border: Edge, + margin: Edge, +} + +enum BoxType<'a> { + Block(&'a StyledNode<'a>), + Inline(&'a StyledNode<'a>), + Anonymous +} + +struct LayoutBox<'a> { + dimensions: Dim, + btype: BoxType<'a>, + children: Vec>, +} + +impl LayoutBox { + fn new(btype: BoxType) -> LayoutBox { + LayoutBox { + btype: btype, + dimensions: Default::default(), + children: Vec::new(), + } + } + + fn get_inline_container(&mut self) -> &mut LayoutBox { + match self.btype { + Inline(_) | Anonymous => self, + Block(_) => { + match self.children.last() { + Some(&LayoutBox { btype: Anonymous,..}) => {} + _ => self.children.push(LayoutBox::new(Anonymous)) + } + self.children.last_mut().unwrap() + } + } + } + + fn layout(&mut self, containing: Dim) { + match self.btype { + Block(_) => self.layout_block(containing), + Inline(_) => {} // TODO + Anonymous => {} // TODO + } + } + + fn layout_block(&mut self, containing: Dim) { + self.calculate_block_width(containing); + + self.calculate_block_position(containing); + + self.layout_block_children(); + + self.calculate_block_height(); + } + + fn calculate_block_width(&mut self, containing: Dim) { + let style = self.get_style_node(); + + let auto = Keyword("auto".to_string()); + let mut width = style.value("width").unwrap_or(auto.clone()); + + let zero = Length(0.0, Px); + + let mut margin_left = style.lookup("margin-left", "margin", &zero); + let mut margin_right = style.lookup("margin-right", "margin", &zero); + + let border_left = style.lookup("border-left-width", "border-width", &zero); + let border_right = style.lookup("border-right-width", "border-width", &zero); + + let padding_left = style.lookup("padding-left", "padding", &zero); + let padding_right = style.lookup("padding-right", "padding", &zero); + + let total = [&margin_left, &margin_right, &border_left, &border_right, + &padding_left, &padding_right, &width + ].iter().map(|v| v.to_px()).sum(); + + if width != auto && total > containing.content.width { + if margin_left == auto { + margin_left = Length(0.0, Px); + } + if margin_right == auto { + margin_right = Length(0.0, Px); + } + } + + let underflow = containing.content.width - total; + + match (width == auto, margin_left == auto, margin_right == auto) { + (false, false, false) => { + margin_right = Length(margin_right.to_px() + underflow, Px); + } + + (false, false, true) => { margin_right = Length(underflow, Px); } + (false, true, false) => { margin_left = Length(underflow, Px); } + + (true, _, _) => { + if margin_left == auto { margin_left = Length(0.0, Px); } + if margin_right == auto { margin_right = Length(0.0, Px); } + + if underflow >= 0.0 { + width = Length(underflow, Px); + } else { + width = Length(0.0, Px); + margin_right = Length(margin_right.to_px() + underflow, Px); + } + } + + (false, true, true) => { + margin_left = Length(underflow / 2.0, Px); + margin_right = Length(underflow / 2.0, Px); + } + } + + let d = &mut self.dimensions; + d.content.width = width.to_px(); + + d.padding.left = padding_left.to_px(); + d.padding.right = padding_right.to_px(); + + d.border.left = border_left.to_px(); + d.border.right = border_right.to_px(); + + d.margin.left = margin_left.to_px(); + d.margin.right = margin_right.to_px(); + } + + fn calculate_block_position(&mut self, containing: Dim) { + let style = self.get_style_node(); + let d = &mut self.dimensions; + + let zero = Length(0.0, Px); + + d.margin.top = style.lookup("margin-top", "margin", &zero).to_px(); + d.margin.bottom = style.lookup("margin-bottom", "margin", &zero).to_px(); + + d.border.top = style.lookup("border-top-width", "border-width", &zero).to_px(); + d.border.bottom = style.lookup("border-bottom-width", "border-width", &zero).to_px(); + + d.padding.top = style.lookup("padding-top", "padding", &zero).to_px(); + d.padding.bottom = style.lookup("padding-bottom", "padding", &zero).to_px(); + + d.content.x = containing.content.x + + d.margin.left + d.border.left + d.padding.left; + + d.content.y = containing.content.height + containing.content.y + + d.margin.top + d.border.top + d.padding.top; + } + + fn layout_block_children(&mut self) { + let d = &mut self.dimensions; + for child in &mut self.children { + child.layout(*d); + d.content.height = d.content.height + child.dimensions.margin_box().height; + } + } +} + +fn build_layout_tree<'a>(style_node: &'a StyledNode<'a>) -> LayoutBox<'a> { + let mut root = LayoutBox::new(match style_node.display() { + css::Display::Block => Block(style_node), + css::Display::Inline => Inline(style_node), + css::Display::DisplayNone => panic!("Root node has display: none.") + }); + + for child in &style_node.children { + match child.display() { + css::Display::Block => root.children.push(build_layout_tree(child)), + css::Display::Inline => root.get_inline_container().children.push(build_layout_tree(child)), + css::Display::DisplayNone => {} + } + } + return root; +} diff --git a/src/styling.rs b/src/styling.rs index 7241677..e122b49 100644 --- a/src/styling.rs +++ b/src/styling.rs @@ -11,6 +11,29 @@ pub struct Node<'a> { children: Vec>, } +enum Display { + Inline, + Block, + None, +} + +impl<'a> Node<'a> { + fn value(&self, name: &str) -> Option { + self.values.get(name).map(|v| v.clone()) + } + + fn display(&self) -> Display { + match self.value("display") { + Some(css::Value::Keyword(s)) => match &*s { + "block" => Display::Block, + "none" => Display::None, + _ => Display::Inline + }, + _ => Display::Inline + } + } +} + fn matches(elem: &dom::EData, selector: &css::Selector) -> bool { match *selector { css::Selector::Simple(ref s) => matches_simple(elem, s)