From d5e47933abd2a9df10dd7ea0c2010124f0948f0b Mon Sep 17 00:00:00 2001 From: Eric Atkinson Date: Thu, 30 May 2013 10:55:42 -0700 Subject: [PATCH] Add horizontal borders, margins and padding. Broken until rust-css supports padding --- src/components/main/layout/block.rs | 72 +++++++++++++++++++++++--- src/components/main/layout/box.rs | 80 ++++++++++++++++------------- src/components/main/layout/model.rs | 71 +++++++++++++++++++++++-- 3 files changed, 174 insertions(+), 49 deletions(-) diff --git a/src/components/main/layout/block.rs b/src/components/main/layout/block.rs index 95872a3273c..3e26acf669b 100644 --- a/src/components/main/layout/block.rs +++ b/src/components/main/layout/block.rs @@ -10,6 +10,7 @@ use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; use layout::display_list_builder::{FlowDisplayListBuilderMethods}; use layout::flow::{BlockFlow, FlowContext, FlowData, InlineBlockFlow}; use layout::inline::InlineLayout; +use layout::model::{MaybeAuto, Specified, Auto}; use core::cell::Cell; use geom::point::Point2D; @@ -104,6 +105,8 @@ impl BlockFlowData { /* if not an anonymous block context, add in block box's widths. these widths will not include child elements, just padding etc. */ self.box.map(|&box| { + //Can compute border width here since it doesn't depend on anything + box.compute_borders(); min_width = min_width.add(&box.get_min_width(ctx)); pref_width = pref_width.add(&box.get_pref_width(ctx)); }); @@ -112,6 +115,57 @@ impl BlockFlowData { self.common.pref_width = pref_width; } + /// Computes left and right margins and width based on CSS 2.1 secion 10.3.3. + /// Requires borders and padding to already be computed + priv fn compute_horiz( &self, + width: MaybeAuto, + left_margin: MaybeAuto, + right_margin: MaybeAuto, + available_width: Au) -> (Au, Au, Au) { + + //If width is not 'auto', and width + margins > available_width, all 'auto' margins are treated as '0' + let (left_margin, right_margin) = match width{ + Auto => (left_margin, right_margin), + Specified(width) => { + let left = left_margin.spec_or_default(Au(0)); + let right = right_margin.spec_or_default(Au(0)); + + if((left + right + width) > available_width) { + (Specified(left), Specified(right)) + } else { + (left_margin, right_margin) + } + } + }; + + //Invariant: left_margin_Au + width_Au + right_margin_Au == available_width + let (left_margin_Au, width_Au, right_margin_Au) = match (left_margin, width, right_margin) { + //If all have a computed value other than 'auto', the system is over-constrained and we need to discard a margin. + //if direction is ltr, ignore the specified right margin and solve for it. If it is rtl, ignore the specified + //left margin. FIXME(eatkinson): this assumes the direction is ltr + (Specified(margin_l), Specified(width), Specified(margin_r)) => (margin_l, width, available_width - (margin_l + width )), + + //If exactly one value is 'auto', solve for it + (Auto, Specified(width), Specified(margin_r)) => (available_width - (width + margin_r), width, margin_r), + (Specified(margin_l), Auto, Specified(margin_r)) => (margin_l, available_width - (margin_l + margin_r), margin_r), + (Specified(margin_l), Specified(width), Auto) => (margin_l, width, available_width - (margin_l + width)), + + //If width is set to 'auto', any other 'auto' value becomes '0', and width is solved for + (Auto, Auto, Specified(margin_r)) => (Au(0), available_width - margin_r, margin_r), + (Specified(margin_l), Auto, Auto) => (margin_l, available_width - margin_l, Au(0)), + (Auto, Auto, Auto) => (Au(0), available_width, Au(0)), + + //If left and right margins are auto, they become equal + (Auto, Specified(width), Auto) => { + let margin = (available_width - width).scale_by(0.5); + (margin, width, margin) + } + + }; + //return values in same order as params + (width_Au, left_margin_Au, right_margin_Au) + } + /// Recursively (top-down) determines the actual width of child contexts and boxes. When called /// on this context, the context has had its width set by the parent context. /// @@ -123,24 +177,28 @@ impl BlockFlowData { self.common.position.size.width = ctx.screen_size.size.width; } + //position was set to the containing block by the flow's parent let mut remaining_width = self.common.position.size.width; - let left_used = Au(0); + let mut x_offset = Au(0); - // Let the box consume some width. It will return the amount remaining for its children. self.box.map(|&box| { - do box.with_mut_base |base| { - base.position.size.width = remaining_width; + box.compute_padding(remaining_width); + let available_width = remaining_width - box.get_noncontent_width(); - let (left_used, right_used) = box.get_used_width(); - remaining_width -= left_used.add(&right_used); + do box.compute_width(remaining_width) |width, left_margin, right_margin| { + self.compute_horiz(width, left_margin, right_margin, available_width) } + + let content_box = box.content_box(); + x_offset = content_box.origin.x; + remaining_width = content_box.size.width; }); for BlockFlow(self).each_child |kid| { assert!(kid.starts_block_flow() || kid.starts_inline_flow()); do kid.with_mut_base |child_node| { - child_node.position.origin.x = left_used; + child_node.position.origin.x = x_offset; child_node.position.size.width = remaining_width; } } diff --git a/src/components/main/layout/box.rs b/src/components/main/layout/box.rs index 53b8826657b..6033a924bfc 100644 --- a/src/components/main/layout/box.rs +++ b/src/components/main/layout/box.rs @@ -8,7 +8,7 @@ use css::node_style::StyledNode; use layout::context::LayoutContext; use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData, ToGfxColor}; use layout::flow::FlowContext; -use layout::model::BoxModel; +use layout::model::{BoxModel,MaybeAuto}; use layout::text; use core::cell::Cell; @@ -374,14 +374,6 @@ pub impl RenderBox { fn get_min_width(&self, _: &LayoutContext) -> Au { // FIXME(pcwalton): I think we only need to calculate this if the damage says that CSS // needs to be restyled. - do self.with_mut_base |base| { - // TODO(pcwalton): Hmm, it seems wasteful to have the box model stuff inside every - // render box if they can only be nonzero if the box is an element. - if base.node.is_element() { - base.model.populate(base.node.style()) - } - } - match *self { // TODO: This should account for the minimum width of the box element in isolation. // That includes borders, margins, and padding, but not child widths. The block @@ -456,37 +448,51 @@ pub impl RenderBox { (Au(0), Au(0)) } + fn compute_padding(&self, cb_width: Au) { + do self.with_imm_base |base| { + base.model.compute_padding(self.style(), cb_width); + } + } + + fn compute_borders(&self){ + do self.with_imm_base |base| { + base.model.compute_borders(self.style()); + } + } + + fn get_noncontent_width(&self) -> Au { + do self.with_imm_base |base| { + base.model.border.left + base.model.padding.left + base.model.border.right + + base.model.padding.right + } + } + + fn compute_width (&self, cb_width: Au, + callback: &fn(MaybeAuto, MaybeAuto, MaybeAuto) -> (Au, Au, Au)) { + let computed_width = MaybeAuto::from_width(self.style().width()); + let computed_margin_left = MaybeAuto::from_margin(self.style().margin_left()); + let computed_margin_right = MaybeAuto::from_margin(self.style().margin_right()); + + let (used_width, used_margin_left, used_margin_right) = + callback(computed_width, computed_margin_left, computed_margin_right); + + do self.with_mut_base |base| { + base.model.margin.left = used_margin_left; + base.model.margin.right = used_margin_right; + base.position.size.width = used_width + self.get_noncontent_width(); + base.position.origin.x = used_margin_left; + } + } + /// The box formed by the content edge as defined in CSS 2.1 ยง 8.1. Coordinates are relative to /// the owning flow. fn content_box(&self) -> Rect { - let origin = self.position().origin; - match *self { - ImageRenderBoxClass(image_box) => { - Rect { - origin: origin, - size: image_box.base.position.size, - } - }, - GenericRenderBoxClass(*) => { - self.position() - - // FIXME: The following hits an ICE for whatever reason. - - /* - let origin = self.d().position.origin; - let size = self.d().position.size; - let (offset_left, offset_right) = self.get_used_width(); - let (offset_top, offset_bottom) = self.get_used_height(); - - Rect { - origin: Point2D(origin.x + offset_left, origin.y + offset_top), - size: Size2D(size.width - (offset_left + offset_right), - size.height - (offset_top + offset_bottom)) - } - */ - }, - TextRenderBoxClass(*) => self.position(), - UnscannedTextRenderBoxClass(*) => fail!(~"Shouldn't see unscanned boxes here.") + do self.with_imm_base |base| { + let origin = Point2D(base.position.origin.x + base.model.border.left + base.model.padding.left, + base.position.origin.y); + let size = Size2D(base.position.size.width - self.get_noncontent_width(), + base.position.size.height); + Rect(origin, size) } } diff --git a/src/components/main/layout/model.rs b/src/components/main/layout/model.rs index da92788a338..d0fb4ec083d 100644 --- a/src/components/main/layout/model.rs +++ b/src/components/main/layout/model.rs @@ -19,12 +19,54 @@ use newcss::complete::CompleteStyle; use newcss::units::{Em, Pt, Px}; use newcss::values::{CSSBorderWidth, CSSBorderWidthLength, CSSBorderWidthMedium}; use newcss::values::{CSSBorderWidthThick, CSSBorderWidthThin}; - +use newcss::values::{CSSWidth, CSSWidthLength, CSSWidthPercentage, CSSWidthAuto}; +use newcss::values::{CSSMargin, CSSMarginLength, CSSMarginPercentage, CSSMarginAuto}; +use newcss::values::{CSSPadding, CSSPaddingLength, CSSPaddingPercentage}; /// Encapsulates the borders, padding, and margins, which we collectively call the "box model". pub struct BoxModel { border: SideOffsets2D, padding: SideOffsets2D, margin: SideOffsets2D, + content_width: Au, +} + +/// Useful helper data type when computing values for blocks and positioned elements. +pub enum MaybeAuto{ + Auto, + Specified(Au), +} + +impl MaybeAuto{ + pub fn from_margin(margin: CSSMargin) -> MaybeAuto{ + match margin { + CSSMarginAuto => Auto, + //FIXME(eatkinson): Compute percents properly + CSSMarginPercentage(_) => Specified(Au(0)), + //FIXME(eatkinson): Compute pt and em values properly + CSSMarginLength(Px(v)) | + CSSMarginLength(Pt(v)) | + CSSMarginLength(Em(v)) => Specified(Au::from_frac_px(v)), + } + } + + pub fn from_width(width: CSSWidth) -> MaybeAuto{ + match width{ + CSSWidthAuto => Auto, + //FIXME(eatkinson): Compute percents properly + CSSWidthPercentage(_) => Specified(Au(0)), + //FIXME(eatkinson): Compute pt and em values properly + CSSWidthLength(Px(v)) | + CSSWidthLength(Pt(v)) | + CSSWidthLength(Em(v)) => Specified(Au::from_frac_px(v)), + } + } + + pub fn spec_or_default(&self, default: Au) -> Au{ + match *self{ + Auto => default, + Specified(value) => value + } + } } impl Zero for BoxModel { @@ -33,24 +75,31 @@ impl Zero for BoxModel { border: Zero::zero(), padding: Zero::zero(), margin: Zero::zero(), + content_width: Zero::zero(), } } fn is_zero(&self) -> bool { - self.padding.is_zero() && self.border.is_zero() && self.margin.is_zero() + self.padding.is_zero() && self.border.is_zero() && self.margin.is_zero() && + self.content_width.is_zero() } } impl BoxModel { /// Populates the box model parameters from the given computed style. - pub fn populate(&mut self, style: CompleteStyle) { - // Populate the borders. + pub fn compute_borders(&mut self, style: CompleteStyle) { + // Compute the borders. self.border.top = self.compute_border_width(style.border_top_width()); self.border.right = self.compute_border_width(style.border_right_width()); self.border.bottom = self.compute_border_width(style.border_bottom_width()); self.border.left = self.compute_border_width(style.border_left_width()); + } - // TODO(pcwalton): Padding, margins. + pub fn compute_padding(&mut self, style: CompleteStyle, cb_width: Au){ + self.padding.top = self.compute_padding(style.padding_top(), cb_width); + self.padding.right = self.compute_padding(style.padding_right(), cb_width); + self.padding.bottom = self.compute_padding(style.padding_bottom(), cb_width); + self.padding.left = self.compute_padding(style.padding_left(), cb_width); } /// Helper function to compute the border width in app units from the CSS border width. @@ -67,6 +116,18 @@ impl BoxModel { CSSBorderWidthThick => Au::from_px(10), } } + + fn compute_padding(&self, padding: CSSPadding, cb_width: Au) -> Au{ + match padding { + CSSPaddingLength(Px(v)) | + CSSPaddingLength(Pt(v)) | + CSSPaddingLength(Em(v)) => { + // FIXME(eatkinson): Handle 'em' and 'pt' correctly + Au::from_frac_px(v) + } + CSSPaddingPercentage(p) => cb_width.scale_by(p) + } + } } //