Add horizontal borders, margins and padding. Broken until rust-css supports padding

This commit is contained in:
Eric Atkinson 2013-05-30 10:55:42 -07:00 committed by Patrick Walton
parent facb70756c
commit d5e47933ab
3 changed files with 174 additions and 49 deletions

View file

@ -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;
}
}

View file

@ -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<Au> {
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)
}
}

View file

@ -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<Au>,
padding: SideOffsets2D<Au>,
margin: SideOffsets2D<Au>,
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)
}
}
}
//