diff options
author | bors-servo <release+servo@mozilla.com> | 2013-06-25 16:18:31 -0700 |
---|---|---|
committer | bors-servo <release+servo@mozilla.com> | 2013-06-25 16:18:31 -0700 |
commit | a01f6b97f23b06dda6587f9b1c3d4e2ec3cf1f82 (patch) | |
tree | 68ab58e7491044088ced5be647547d53db2a7364 /src | |
parent | 033af01283234dd2cc423f2018d0d348849f4720 (diff) | |
parent | 427328e8e4851aaf2f2c0ceeb0fef85bf8b43306 (diff) | |
download | servo-a01f6b97f23b06dda6587f9b1c3d4e2ec3cf1f82.tar.gz servo-a01f6b97f23b06dda6587f9b1c3d4e2ec3cf1f82.zip |
auto merge of #542 : eric93/servo/floats, r=pcwalton
I added the minimal amount of code needed to place left-floats on the screen (right floats should also be possible soon). Text does not wrap around floats yet.
One thing I'm curious about is whether some existing abstractions (like Cell) can be used instead of this weird overwriting thing done in float_context.rs.
Diffstat (limited to 'src')
-rw-r--r-- | src/components/main/layout/block.rs | 40 | ||||
-rw-r--r-- | src/components/main/layout/box.rs | 36 | ||||
-rw-r--r-- | src/components/main/layout/box_builder.rs | 48 | ||||
-rw-r--r-- | src/components/main/layout/float.rs | 241 | ||||
-rw-r--r-- | src/components/main/layout/float_context.rs | 258 | ||||
-rw-r--r-- | src/components/main/layout/flow.rs | 28 | ||||
-rw-r--r-- | src/components/main/layout/inline.rs | 55 | ||||
-rw-r--r-- | src/components/main/layout/layout_task.rs | 6 | ||||
-rw-r--r-- | src/components/main/layout/model.rs | 16 | ||||
-rwxr-xr-x | src/components/main/servo.rc | 2 |
10 files changed, 694 insertions, 36 deletions
diff --git a/src/components/main/layout/block.rs b/src/components/main/layout/block.rs index f7f07b14df2..3fa9906423a 100644 --- a/src/components/main/layout/block.rs +++ b/src/components/main/layout/block.rs @@ -8,9 +8,10 @@ use layout::box::{RenderBox}; use layout::context::LayoutContext; use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; use layout::display_list_builder::{FlowDisplayListBuilderMethods}; -use layout::flow::{BlockFlow, FlowContext, FlowData, InlineBlockFlow}; +use layout::flow::{BlockFlow, FlowContext, FlowData, InlineBlockFlow, FloatFlow}; use layout::inline::InlineLayout; use layout::model::{MaybeAuto, Specified, Auto}; +use layout::float_context::FloatContext; use core::cell::Cell; use geom::point::Point2D; @@ -72,7 +73,7 @@ impl BlockLayout for FlowContext { fn starts_block_flow(&self) -> bool { match *self { - BlockFlow(*) | InlineBlockFlow(*) => true, + BlockFlow(*) | InlineBlockFlow(*) | FloatFlow(*) => true, _ => false } } @@ -91,14 +92,17 @@ impl BlockFlowData { pub fn bubble_widths_block(@mut self, ctx: &LayoutContext) { let mut min_width = Au(0); let mut pref_width = Au(0); + let mut num_floats = 0; /* find max width from child block contexts */ for BlockFlow(self).each_child |child_ctx| { assert!(child_ctx.starts_block_flow() || child_ctx.starts_inline_flow()); - do child_ctx.with_base |child_node| { + do child_ctx.with_mut_base |child_node| { min_width = geometry::max(min_width, child_node.min_width); pref_width = geometry::max(pref_width, child_node.pref_width); + + num_floats = num_floats + child_node.num_floats; } } @@ -116,6 +120,7 @@ impl BlockFlowData { self.common.min_width = min_width; self.common.pref_width = pref_width; + self.common.num_floats = num_floats; } /// Computes left and right margins and width based on CSS 2.1 secion 10.3.3. @@ -180,6 +185,7 @@ impl BlockFlowData { debug!("Setting root position"); self.common.position.origin = Au::zero_point(); self.common.position.size.width = ctx.screen_size.size.width; + self.common.floats_in = FloatContext::new(self.common.num_floats); } //position was set to the containing block by the flow's parent @@ -240,17 +246,41 @@ impl BlockFlowData { } } - pub fn assign_height_block(@mut self, ctx: &LayoutContext) { + pub fn assign_height_block(@mut self, ctx: &mut LayoutContext) { let mut cur_y = Au(0); let mut top_offset = Au(0); + let mut left_offset = Au(0); for self.box.each |&box| { do box.with_model |model| { top_offset = model.margin.top + model.border.top + model.padding.top; cur_y += top_offset; + left_offset = model.offset(); } } + // TODO(eatkinson): the translation here is probably + // totally wrong. We need to do it right or pages + // with floats will look very strange. + + // Floats for blocks work like this: + // self.floats_in -> child[0].floats_in + // visit child[0] + // child[i-1].floats_out -> child[i].floats_in + // visit child[i] + // repeat until all children are visited. + // last_child.floats_out -> self.floats_out (done at the end of this method) + let mut float_ctx = self.common.floats_in.translate(Point2D(-left_offset, -top_offset)); + for BlockFlow(self).each_child |kid| { + do kid.with_mut_base |child_node| { + child_node.floats_in = float_ctx.clone(); + } + kid.assign_height(ctx); + do kid.with_mut_base |child_node| { + float_ctx = child_node.floats_out.translate(Point2D(Au(0), -child_node.position.size.height)); + } + + } for BlockFlow(self).each_child |kid| { do kid.with_mut_base |child_node| { child_node.position.origin.y = cur_y; @@ -281,6 +311,8 @@ impl BlockFlowData { //TODO(eatkinson): compute heights using the 'height' property. self.common.position.size.height = height + noncontent_height; + + self.common.floats_out = float_ctx.translate(Point2D(left_offset, self.common.position.size.height)); } pub fn build_display_list_block<E:ExtraDisplayListData>(@mut self, diff --git a/src/components/main/layout/box.rs b/src/components/main/layout/box.rs index 141425c30ed..7d7db5410bc 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; @@ -370,11 +370,41 @@ pub impl RenderBox { } } + /// Guess the intrinsic width of this box for + /// computation of min and preferred widths. + // + // TODO(eatkinson): this is unspecified in + // CSS 2.1, but we need to do something reasonable + // here. What this function does currently is + // NOT reasonable. + // + // TODO(eatkinson): integrate with + // get_min_width and get_pref_width? + priv fn guess_width (&self) -> Au { + do self.with_base |base| { + if(!base.node.is_element()) { + Au(0) + } else { + + let w = MaybeAuto::from_width(self.style().width(), Au(0)).spec_or_default(Au(0)); + let ml = MaybeAuto::from_margin(self.style().margin_left(), Au(0)).spec_or_default(Au(0)); + let mr = MaybeAuto::from_margin(self.style().margin_right(), Au(0)).spec_or_default(Au(0)); + let pl = base.model.compute_padding_length(self.style().padding_left(), Au(0)); + let pr = base.model.compute_padding_length(self.style().padding_right(), Au(0)); + let bl = base.model.compute_border_width(self.style().border_left_width()); + let br = base.model.compute_border_width(self.style().border_right_width()); + + w + ml + mr + pl + pr + bl + br + } + } + } + /// Returns the *minimum width* of this render box as defined by the CSS specification. 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. - match *self { + + self.guess_width() + 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 // `FlowContext` will combine the width of this element and that of its children to @@ -397,7 +427,7 @@ pub impl RenderBox { /// Returns the *preferred width* of this render box as defined by the CSS specification. fn get_pref_width(&self, _: &LayoutContext) -> Au { - match *self { + self.guess_width() + match *self { // TODO: This should account for the preferred width of the box element in isolation. // That includes borders, margins, and padding, but not child widths. The block // `FlowContext` will combine the width of this element and that of its children to diff --git a/src/components/main/layout/box_builder.rs b/src/components/main/layout/box_builder.rs index b446f8a7172..8d5134b7c4e 100644 --- a/src/components/main/layout/box_builder.rs +++ b/src/components/main/layout/box_builder.rs @@ -6,6 +6,7 @@ use layout::aux::LayoutAuxMethods; use layout::block::BlockFlowData; +use layout::float::FloatFlowData; use layout::box::{GenericRenderBoxClass, ImageRenderBox, ImageRenderBoxClass, RenderBox}; use layout::box::{RenderBoxBase, RenderBoxType, RenderBox_Generic, RenderBox_Image}; use layout::box::{RenderBox_Text, UnscannedTextRenderBox, UnscannedTextRenderBoxClass}; @@ -22,6 +23,7 @@ use newcss::values::{CSSDisplayTableRowGroup, CSSDisplayTableHeaderGroup, CSSDis use newcss::values::{CSSDisplayTableRow, CSSDisplayTableColumnGroup, CSSDisplayTableColumn}; use newcss::values::{CSSDisplayTableCell, CSSDisplayTableCaption}; use newcss::values::{CSSDisplayNone}; +use newcss::values::{CSSFloatNone}; use script::dom::element::*; use script::dom::node::{AbstractNode, CommentNodeTypeId, DoctypeNodeTypeId}; use script::dom::node::{ElementNodeTypeId, LayoutView, TextNodeTypeId}; @@ -159,6 +161,18 @@ impl BoxGenerator { assert!(block.box.is_none()); block.box = Some(new_box); }, + FloatFlow(float) => { + debug!("BoxGenerator[f%d]: point b", float.common.id); + let new_box = self.make_box(ctx, box_type, node, self.flow, builder); + + debug!("BoxGenerator[f%d]: attaching box[b%d] to float flow (node: %s)", + float.common.id, + new_box.id(), + node.debug_str()); + + assert!(float.box.is_none()); + float.box = Some(new_box); + }, _ => warn!("push_node() not implemented for flow f%d", self.flow.id()), } } @@ -349,8 +363,24 @@ pub impl LayoutTreeBuilder { None => None, Some(gen) => Some(gen.flow) }; + + // TODO(eatkinson): use the value of the float property to + // determine whether to float left or right. + let is_float = if (node.is_element()) { + match node.style().float() { + CSSFloatNone => false, + _ => true + } + } else { + false + }; + let new_generator = match (display, parent_generator.flow, sibling_flow) { + (CSSDisplayBlock, BlockFlow(_), _) if is_float => { + self.create_child_generator(node, parent_generator, Flow_Float) + } + (CSSDisplayBlock, BlockFlow(info), _) => match (info.is_root, node.parent_node()) { // If this is the root node, then use the root flow's // context. Otherwise, make a child block context. @@ -360,6 +390,11 @@ pub impl LayoutTreeBuilder { self.create_child_generator(node, parent_generator, Flow_Block) } }, + + (CSSDisplayBlock, FloatFlow(*), _) => { + self.create_child_generator(node, parent_generator, Flow_Block) + } + // Inlines that are children of inlines are part of the same flow (CSSDisplayInline, InlineFlow(*), _) => parent_generator, (CSSDisplayInlineBlock, InlineFlow(*), _) => parent_generator, @@ -371,6 +406,15 @@ pub impl LayoutTreeBuilder { self.create_child_generator(node, parent_generator, Flow_Inline) } + // FIXME(eatkinson): this is bogus. Floats should not be able to split + // inlines. They should be appended as children of the inline flow. + (CSSDisplayInline, _, Some(FloatFlow(*))) | + (CSSDisplayInlineBlock, _, Some(FloatFlow(*))) | + (CSSDisplayInline, FloatFlow(*), _) | + (CSSDisplayInlineBlock, FloatFlow(*), _) => { + self.create_child_generator(node, parent_generator, Flow_Inline) + } + // Inlines whose previous sibling was not a block try to use their // sibling's flow context. (CSSDisplayInline, BlockFlow(*), _) | @@ -383,8 +427,6 @@ pub impl LayoutTreeBuilder { // TODO(eatkinson): blocks that are children of inlines need // to split their parent flows. - // - // TODO(eatkinson): floats and positioned elements. _ => parent_generator }; @@ -520,7 +562,7 @@ pub impl LayoutTreeBuilder { let result = match ty { Flow_Absolute => AbsoluteFlow(@mut info), Flow_Block => BlockFlow(@mut BlockFlowData::new(info)), - Flow_Float => FloatFlow(@mut info), + Flow_Float => FloatFlow(@mut FloatFlowData::new(info)), Flow_InlineBlock => InlineBlockFlow(@mut info), Flow_Inline => InlineFlow(@mut InlineFlowData::new(info)), Flow_Root => BlockFlow(@mut BlockFlowData::new_root(info)), diff --git a/src/components/main/layout/float.rs b/src/components/main/layout/float.rs new file mode 100644 index 00000000000..d1ea3c3fdc8 --- /dev/null +++ b/src/components/main/layout/float.rs @@ -0,0 +1,241 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use layout::box::{RenderBox}; +use layout::context::LayoutContext; +use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; +use layout::display_list_builder::{FlowDisplayListBuilderMethods}; +use layout::flow::{FloatFlow, FlowData}; +use layout::model::{MaybeAuto}; +use layout::float_context::{FloatContext, PlacementInfo, FloatLeft}; + +use core::cell::Cell; +use geom::point::Point2D; +use geom::rect::Rect; +use gfx::display_list::DisplayList; +use gfx::geometry::Au; +use gfx::geometry; +use servo_util::tree::{TreeNodeRef, TreeUtils}; + +pub struct FloatFlowData { + /// Data common to all flows. + common: FlowData, + + /// The associated render box. + box: Option<RenderBox>, + + containing_width: Au, + + /// Parent clobbers our position, so store it separately + rel_pos: Point2D<Au>, + + /// Index into the box list for inline floats + index: Option<uint>, + +} + +impl FloatFlowData { + pub fn new(common: FlowData) -> FloatFlowData { + FloatFlowData { + common: common, + containing_width: Au(0), + box: None, + index: None, + rel_pos: Point2D(Au(0), Au(0)), + } + } + + pub fn teardown(&mut self) { + self.common.teardown(); + for self.box.each |box| { + box.teardown(); + } + self.box = None; + self.index = None; + } +} + +impl FloatFlowData { + pub fn bubble_widths_float(@mut self, ctx: &LayoutContext) { + let mut min_width = Au(0); + let mut pref_width = Au(0); + + self.common.num_floats = 1; + + for FloatFlow(self).each_child |child_ctx| { + //assert!(child_ctx.starts_block_flow() || child_ctx.starts_inline_flow()); + + do child_ctx.with_mut_base |child_node| { + min_width = geometry::max(min_width, child_node.min_width); + pref_width = geometry::max(pref_width, child_node.pref_width); + child_node.floats_in = FloatContext::new(child_node.num_floats); + } + } + + self.box.map(|&box| { + let style = box.style(); + do box.with_model |model| { + model.compute_borders(style) + } + + min_width = min_width.add(&box.get_min_width(ctx)); + pref_width = pref_width.add(&box.get_pref_width(ctx)); + }); + + self.common.min_width = min_width; + self.common.pref_width = pref_width; + } + + pub fn assign_widths_float(@mut self, _: &LayoutContext) { + debug!("assign_widths_float: assigning width for flow %?", self.common.id); + // position.size.width is set by parent even though we don't know + // position.origin yet. + let mut remaining_width = self.common.position.size.width; + self.containing_width = remaining_width; + let mut x_offset = Au(0); + + for self.box.each |&box| { + let style = box.style(); + do box.with_model |model| { + // Can compute padding here since we know containing block width. + model.compute_padding(style, remaining_width); + + // Margins for floats are 0 if auto. + let margin_top = MaybeAuto::from_margin(style.margin_top(), + remaining_width).spec_or_default(Au(0)); + let margin_bottom = MaybeAuto::from_margin(style.margin_bottom(), + remaining_width).spec_or_default(Au(0)); + let margin_left = MaybeAuto::from_margin(style.margin_left(), + remaining_width).spec_or_default(Au(0)); + let margin_right = MaybeAuto::from_margin(style.margin_right(), + remaining_width).spec_or_default(Au(0)); + + + + let shrink_to_fit = geometry::min(self.common.pref_width, + geometry::max(self.common.min_width, + remaining_width)); + + + let width = MaybeAuto::from_width(style.width(), + remaining_width).spec_or_default(shrink_to_fit); + debug!("assign_widths_float -- width: %?", width); + + model.margin.top = margin_top; + model.margin.right = margin_right; + model.margin.bottom = margin_bottom; + model.margin.left = margin_left; + + x_offset = model.offset(); + remaining_width = width; + } + + do box.with_mut_base |base| { + //The associated box is the border box of this flow + base.position.origin.x = base.model.margin.left; + + let pb = base.model.padding.left + base.model.padding.right + + base.model.border.left + base.model.border.right; + base.position.size.width = remaining_width + pb; + } + } + + self.common.position.size.width = remaining_width; + + for FloatFlow(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 = x_offset; + child_node.position.size.width = remaining_width; + } + } + } + + pub fn assign_height_float(@mut self, ctx: &mut LayoutContext) { + for FloatFlow(self).each_child |kid| { + kid.assign_height(ctx); + } + + let mut cur_y = Au(0); + let mut top_offset = Au(0); + + for self.box.each |&box| { + do box.with_model |model| { + top_offset = model.margin.top + model.border.top + model.padding.top; + cur_y += top_offset; + } + } + + for FloatFlow(self).each_child |kid| { + do kid.with_mut_base |child_node| { + child_node.position.origin.y = cur_y; + cur_y += child_node.position.size.height; + } + } + + let mut height = cur_y - top_offset; + + let mut noncontent_height = Au(0); + self.box.map(|&box| { + do box.with_mut_base |base| { + //The associated box is the border box of this flow + base.position.origin.y = base.model.margin.top; + + noncontent_height = base.model.padding.top + base.model.padding.bottom + + base.model.border.top + base.model.border.bottom; + base.position.size.height = height + noncontent_height; + + noncontent_height += base.model.margin.top + base.model.margin.bottom; + } + }); + + + //TODO(eatkinson): compute heights properly using the 'height' property. + for self.box.each |&box| { + + let height_prop = + MaybeAuto::from_height(box.style().height(), Au(0)).spec_or_default(Au(0)); + + height = geometry::max(height, height_prop) + noncontent_height; + debug!("assign_height_float -- height: %?", height); + do box.with_mut_base |base| { + base.position.size.height = height; + } + } + + let info = PlacementInfo { + width: self.common.position.size.width, + height: height, + ceiling: Au(0), + max_width: self.containing_width, + f_type: FloatLeft, + }; + + // Place the float and return the FloatContext back to the parent flow. + // After, grab the position and use that to set our position. + self.common.floats_out = self.common.floats_in.add_float(&info); + self.rel_pos = self.common.floats_out.last_float_pos(); + } + + pub fn build_display_list_float<E:ExtraDisplayListData>(@mut self, + builder: &DisplayListBuilder, + dirty: &Rect<Au>, + offset: &Point2D<Au>, + list: &Cell<DisplayList<E>>) { + + let offset = *offset + self.rel_pos; + self.box.map(|&box| { + box.build_display_list(builder, dirty, &offset, list) + }); + + + // go deeper into the flow tree + let flow = FloatFlow(self); + for flow.each_child |child| { + flow.build_display_list_for_child(builder, child, dirty, &offset, list) + } + } +} + diff --git a/src/components/main/layout/float_context.rs b/src/components/main/layout/float_context.rs new file mode 100644 index 00000000000..50fbb5a452b --- /dev/null +++ b/src/components/main/layout/float_context.rs @@ -0,0 +1,258 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use geom::point::Point2D; +use geom::size::Size2D; +use geom::rect::Rect; +use gfx::geometry::{Au, max, min}; +use core::util::replace; + +pub enum FloatType{ + FloatLeft, + FloatRight +} + +struct FloatContextBase{ + float_data: ~[Option<FloatData>], + floats_used: uint, + max_y : Au, + offset: Point2D<Au> +} + +struct FloatData{ + bounds: Rect<Au>, + f_type: FloatType +} + +/// All information necessary to place a float +pub struct PlacementInfo{ + width: Au, // The dimensions of the float + height: Au, + ceiling: Au, // The minimum top of the float, as determined by earlier elements + max_width: Au, // The maximum right of the float, generally determined by the contining block + f_type: FloatType // left or right +} + +/// Wrappers around float methods. To avoid allocating data we'll never use, +/// destroy the context on modification. +pub enum FloatContext { + Invalid, + Valid(FloatContextBase) +} + +impl FloatContext { + pub fn new(num_floats: uint) -> FloatContext { + Valid(FloatContextBase::new(num_floats)) + } + + #[inline(always)] + pub fn clone(&mut self) -> FloatContext { + match *self { + Invalid => fail!("Can't clone an invalid float context"), + Valid(_) => replace(self, Invalid) + } + } + + #[inline(always)] + fn with_mut_base<R>(&mut self, callback: &fn(&mut FloatContextBase) -> R) -> R { + match *self { + Invalid => fail!("Float context no longer available"), + Valid(ref mut base) => callback(base) + } + } + + #[inline(always)] + pub fn with_base<R>(&self, callback: &fn(&FloatContextBase) -> R) -> R { + match *self { + Invalid => fail!("Float context no longer available"), + Valid(ref base) => callback(base) + } + } + + #[inline(always)] + pub fn translate(&mut self, trans: Point2D<Au>) -> FloatContext { + do self.with_mut_base |base| { + base.translate(trans); + } + replace(self, Invalid) + } + + #[inline(always)] + pub fn available_rect(&mut self, top: Au, height: Au, max_x: Au) -> Option<Rect<Au>> { + do self.with_base |base| { + base.available_rect(top, height, max_x) + } + } + + #[inline(always)] + pub fn add_float(&mut self, info: &PlacementInfo) -> FloatContext{ + do self.with_mut_base |base| { + base.add_float(info); + } + replace(self, Invalid) + } + + #[inline(always)] + pub fn last_float_pos(&mut self) -> Point2D<Au> { + do self.with_base |base| { + base.last_float_pos() + } + } +} + +impl FloatContextBase{ + fn new(num_floats: uint) -> FloatContextBase { + debug!("Creating float context of size %?", num_floats); + let new_data = vec::from_elem(num_floats, None); + FloatContextBase { + float_data: new_data, + floats_used: 0, + max_y: Au(0), + offset: Point2D(Au(0), Au(0)) + } + } + + fn translate(&mut self, trans: Point2D<Au>) { + self.offset += trans; + } + + fn last_float_pos(&self) -> Point2D<Au> { + assert!(self.floats_used > 0, "Error: tried to access FloatContext with no floats in it"); + + match self.float_data[self.floats_used - 1] { + None => fail!("FloatContext error: floats should never be None here"), + Some(float) => { + debug!("Returning float position: %?", float.bounds.origin + self.offset); + float.bounds.origin + self.offset + } + } + } + + /// Returns a rectangle that encloses the region from top to top + height, + /// with width small enough that it doesn't collide with any floats. max_x + /// is the x-coordinate beyond which floats have no effect (generally + /// this is the containing block width). + fn available_rect(&self, top: Au, height: Au, max_x: Au) -> Option<Rect<Au>> { + fn range_intersect(top_1: Au, bottom_1: Au, top_2: Au, bottom_2: Au) -> (Au, Au) { + (max(top_1, top_2), min(bottom_1, bottom_2)) + } + + debug!("available_rect: trying to find space at %?", top); + + let top = top - self.offset.y; + + // Relevant dimensions for the right-most left float + let mut (max_left, l_top, l_bottom) = (Au(0) - self.offset.x, None, None); + // Relevant dimensions for the left-most right float + let mut (min_right, r_top, r_bottom) = (max_x - self.offset.x, None, None); + + // Find the float collisions for the given vertical range. + for self.float_data.each |float| { + match *float{ + None => (), + Some(data) => { + let float_pos = data.bounds.origin; + let float_size = data.bounds.size; + match data.f_type { + FloatLeft => { + if(float_pos.x + float_size.width > max_left && + float_pos.y + float_size.height > top && float_pos.y < top + height) { + max_left = float_pos.x + float_size.width; + + l_top = Some(float_pos.y); + l_bottom = Some(float_pos.y + float_size.height); + } + } + FloatRight => { + if(float_pos.x < min_right && + float_pos.y + float_size.height > top && float_pos.y < top + height) { + min_right = float_pos.x; + + r_top = Some(float_pos.y); + r_bottom = Some(float_pos.y + float_size.height); + } + } + } + } + }; + } + + // Extend the vertical range of the rectangle to the closest floats. + // If there are floats on both sides, take the intersection of the + // two areas. + let (top, bottom) = match (r_top, r_bottom, l_top, l_bottom) { + (Some(r_top), Some(r_bottom), Some(l_top), Some(l_bottom)) => + range_intersect(r_top, r_bottom, l_top, l_bottom), + + (None, None, Some(l_top), Some(l_bottom)) => (l_top, l_bottom), + (Some(r_top), Some(r_bottom), None, None) => (r_top, r_bottom), + (None, None, None, None) => return None, + _ => fail!("Reached unreachable state when computing float area") + }; + + // When the window is smaller than the float, we will return a rect + // with negative width. + assert!(max_left < min_right + || max_left > max_x - self.offset.x + || min_right < Au(0) - self.offset.x + ,"Float position error"); + + //TODO(eatkinson): do we need to do something similar for heights? + assert!(top < bottom, "Float position error"); + + Some(Rect{ + origin: Point2D(max_left, top) + self.offset, + size: Size2D(min_right - max_left, bottom - top) + }) + } + + fn add_float(&mut self, info: &PlacementInfo) { + debug!("Floats_used: %?, Floats available: %?", self.floats_used, self.float_data.len()); + assert!(self.floats_used < self.float_data.len() && + self.float_data[self.floats_used].is_none()); + + let new_float = FloatData { + bounds: Rect { + origin: self.place_float(info) - self.offset, + size: Size2D(info.width, info.height) + }, + f_type: info.f_type + }; + self.float_data[self.floats_used] = Some(new_float); + self.floats_used += 1; + } + + /// Given necessary info, finds the position of the float in + /// LOCAL COORDINATES. i.e. must be translated before placed + /// in the float list + fn place_float(&self, info: &PlacementInfo) -> Point2D<Au>{ + debug!("place_float: Placing float with width %? and height %?", info.width, info.height); + // Can't go any higher than previous floats or + // previous elements in the document. + let mut float_y = max(info.ceiling, self.max_y + self.offset.y); + loop { + let maybe_location = self.available_rect(float_y, info.height, info.max_width); + debug!("place_float: Got available rect: %? for y-pos: %?", maybe_location, float_y); + match maybe_location { + // If there are no floats blocking us, return the current location + // TODO(eatknson): integrate with overflow + None => return Point2D(Au(0), float_y), + Some(rect) => { + assert!(rect.origin.y + rect.size.height != float_y, + "Non-terminating float placement"); + + // Place here if there is enough room + if (rect.size.width >= info.width) { + return Point2D(rect.origin.x, float_y); + } + + // Try to place at the next-lowest location. + // Need to be careful of fencepost errors. + float_y = rect.origin.y + rect.size.height; + } + } + } + } +} + diff --git a/src/components/main/layout/flow.rs b/src/components/main/layout/flow.rs index 900a7b0a130..e2c14ce22e9 100644 --- a/src/components/main/layout/flow.rs +++ b/src/components/main/layout/flow.rs @@ -26,10 +26,12 @@ /// similar methods. use layout::block::BlockFlowData; +use layout::float::FloatFlowData; use layout::box::RenderBox; use layout::context::LayoutContext; use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; use layout::inline::{InlineFlowData}; +use layout::float_context::{FloatContext, Invalid}; use core::cell::Cell; use geom::point::Point2D; @@ -44,7 +46,7 @@ use servo_util::tree::{TreeNode, TreeNodeRef, TreeUtils}; pub enum FlowContext { AbsoluteFlow(@mut FlowData), BlockFlow(@mut BlockFlowData), - FloatFlow(@mut FlowData), + FloatFlow(@mut FloatFlowData), InlineBlockFlow(@mut FlowData), InlineFlow(@mut InlineFlowData), TableFlow(@mut FlowData), @@ -70,10 +72,10 @@ impl FlowContext { pub fn teardown(&self) { match *self { AbsoluteFlow(data) | - FloatFlow(data) | InlineBlockFlow(data) | TableFlow(data) => data.teardown(), BlockFlow(data) => data.teardown(), + FloatFlow(data) => data.teardown(), InlineFlow(data) => data.teardown() } } @@ -110,7 +112,7 @@ impl TreeNodeRef<FlowData> for FlowContext { BlockFlow(info) => { callback(&info.common) } - FloatFlow(info) => callback(info), + FloatFlow(info) => callback(&info.common), InlineBlockFlow(info) => callback(info), InlineFlow(info) => { callback(&info.common) @@ -124,7 +126,7 @@ impl TreeNodeRef<FlowData> for FlowContext { BlockFlow(info) => { callback(&mut info.common) } - FloatFlow(info) => callback(info), + FloatFlow(info) => callback(&mut info.common), InlineBlockFlow(info) => callback(info), InlineFlow(info) => { callback(&mut info.common) @@ -156,6 +158,9 @@ pub struct FlowData { min_width: Au, pref_width: Au, position: Rect<Au>, + floats_in: FloatContext, + floats_out: FloatContext, + num_floats: uint, } impl TreeNode<FlowContext> for FlowData { @@ -216,6 +221,9 @@ impl FlowData { min_width: Au(0), pref_width: Au(0), position: Au::zero_rect(), + floats_in: Invalid, + floats_out: Invalid, + num_floats: 0, } } } @@ -264,6 +272,7 @@ impl<'self> FlowContext { match *self { BlockFlow(info) => info.bubble_widths_block(ctx), InlineFlow(info) => info.bubble_widths_inline(ctx), + FloatFlow(info) => info.bubble_widths_float(ctx), _ => fail!(fmt!("Tried to bubble_widths of flow: f%d", self.id())) } } @@ -272,6 +281,7 @@ impl<'self> FlowContext { match *self { BlockFlow(info) => info.assign_widths_block(ctx), InlineFlow(info) => info.assign_widths_inline(ctx), + FloatFlow(info) => info.assign_widths_float(ctx), _ => fail!(fmt!("Tried to assign_widths of flow: f%d", self.id())) } } @@ -280,6 +290,7 @@ impl<'self> FlowContext { match *self { BlockFlow(info) => info.assign_height_block(ctx), InlineFlow(info) => info.assign_height_inline(ctx), + FloatFlow(info) => info.assign_height_float(ctx), _ => fail!(fmt!("Tried to assign_height of flow: f%d", self.id())) } } @@ -296,6 +307,7 @@ impl<'self> FlowContext { match *self { BlockFlow(info) => info.build_display_list_block(builder, dirty, offset, list), InlineFlow(info) => info.build_display_list_inline(builder, dirty, offset, list), + FloatFlow(info) => info.build_display_list_float(builder, dirty, offset, list), _ => fail!(fmt!("Tried to build_display_list_recurse of flow: %?", self)) } } @@ -408,11 +420,17 @@ impl<'self> FlowContext { None => ~"BlockFlow", } }, + FloatFlow(float) => { + match float.box { + Some(box) => fmt!("FloatFlow(box=b%d)", box.id()), + None => ~"FloatFlow", + } + }, _ => ~"(Unknown flow)" }; do self.with_base |base| { - fmt!("f%? %? size %?", base.id, repr, base.position) + fmt!("f%? %? floats %? size %?", base.id, repr, base.num_floats, base.position) } } } diff --git a/src/components/main/layout/inline.rs b/src/components/main/layout/inline.rs index 1e14c36dec9..4894da2fb1c 100644 --- a/src/components/main/layout/inline.rs +++ b/src/components/main/layout/inline.rs @@ -10,6 +10,7 @@ use layout::context::LayoutContext; use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; use layout::flow::{FlowContext, FlowData, InlineFlow}; use layout::text::{UnscannedMethods, adapt_textbox_with_range}; +use layout::float_context::FloatContext; use core::util; use geom::{Point2D, Rect, Size2D}; @@ -25,6 +26,7 @@ use newcss::values::{CSSLineHeightNormal, CSSLineHeightNumber, CSSLineHeightLeng use servo_util::range::Range; use std::deque::Deque; +use servo_util::tree::{TreeNodeRef, TreeUtils}; /* Lineboxes are represented as offsets into the child list, rather than @@ -394,7 +396,7 @@ impl TextRunScanner { struct PendingLine { range: Range, - width: Au + bounds: Rect<Au> } struct LineboxScanner { @@ -413,8 +415,8 @@ impl LineboxScanner { flow: inline, new_boxes: ~[], work_list: @mut Deque::new(), - pending_line: PendingLine {mut range: Range::empty(), mut width: Au(0)}, - line_spans: ~[] + pending_line: PendingLine {mut range: Range::empty(), mut bounds: Rect(Point2D(Au(0), Au(0)), Size2D(Au(0), Au(0)))}, + line_spans: ~[], } } @@ -427,7 +429,7 @@ impl LineboxScanner { fn reset_linebox(&mut self) { self.pending_line.range.reset(0,0); - self.pending_line.width = Au(0); + self.pending_line.bounds = Rect(Point2D(Au(0), Au(0)), Size2D(Au(0), Au(0))); } pub fn scan_for_lines(&mut self, ctx: &LayoutContext) { @@ -508,7 +510,7 @@ impl LineboxScanner { linebox_align = CSSTextAlignLeft; } - let slack_width = self.flow.position().size.width - self.pending_line.width; + let slack_width = self.flow.position().size.width - self.pending_line.bounds.size.width; match linebox_align { // So sorry, but justified text is more complicated than shuffling linebox coordinates. // TODO(Issue #213): implement `text-align: justify` @@ -548,7 +550,7 @@ impl LineboxScanner { // return value: whether any box was appended. fn try_append_to_line(&mut self, ctx: &LayoutContext, in_box: RenderBox) -> bool { - let remaining_width = self.flow.position().size.width - self.pending_line.width; + let remaining_width = self.flow.position().size.width - self.pending_line.bounds.size.width; let in_box_width = in_box.position().size.width; let line_is_empty: bool = self.pending_line.range.length() == 0; @@ -639,7 +641,7 @@ impl LineboxScanner { self.pending_line.range.reset(self.new_boxes.len(), 0); } self.pending_line.range.extend_by(1); - self.pending_line.width += box.position().size.width; + self.pending_line.bounds.size.width += box.position().size.width; self.new_boxes.push(box); } } @@ -696,6 +698,15 @@ impl InlineFlowData { pub fn bubble_widths_inline(@mut self, ctx: &mut LayoutContext) { let mut scanner = TextRunScanner::new(); scanner.scan_for_runs(ctx, InlineFlow(self)); + let mut num_floats = 0; + + for InlineFlow(self).each_child |kid| { + do kid.with_mut_base |base| { + num_floats += base.num_floats; + base.floats_in = FloatContext::new(base.num_floats); + } + } + { let this = &mut *self; @@ -711,12 +722,13 @@ impl InlineFlowData { this.common.min_width = min_width; this.common.pref_width = pref_width; + this.common.num_floats = num_floats; } } /// 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. - pub fn assign_widths_inline(@mut self, ctx: &mut LayoutContext) { + pub fn assign_widths_inline(@mut self, _: &mut LayoutContext) { // Initialize content box widths if they haven't been initialized already. // // TODO: Combine this with `LineboxScanner`'s walk in the box list, or put this into @@ -745,9 +757,11 @@ impl InlineFlowData { } // End of for loop. } - let mut scanner = LineboxScanner::new(InlineFlow(self)); - scanner.scan_for_lines(ctx); - + for InlineFlow(self).each_child |kid| { + do kid.with_mut_base |base| { + base.position.size.width = self.common.position.size.width; + } + } // There are no child contexts, so stop here. // TODO(Issue #225): once there are 'inline-block' elements, this won't be @@ -757,7 +771,18 @@ impl InlineFlowData { // 'inline-block' box that created this flow before recursing. } - pub fn assign_height_inline(&mut self, _: &mut LayoutContext) { + pub fn assign_height_inline(@mut self, ctx: &mut LayoutContext) { + + for InlineFlow(self).each_child |kid| { + kid.assign_height(ctx); + } + + + // TODO(eatkinson): line boxes need to shrink if there are floats + let mut scanner = LineboxScanner::new(InlineFlow(self)); + scanner.scan_for_lines(ctx); + self.common.floats_out = self.common.floats_in.clone(); + // TODO(#226): Get the CSS `line-height` property from the containing block's style to // determine minimum linebox height. // @@ -774,10 +799,8 @@ impl InlineFlowData { let mut linebox_height = Au(0); let mut baseline_offset = Au(0); - let boxes = &mut self.boxes; - for line_span.eachi |box_i| { - let cur_box = boxes[box_i]; // FIXME: borrow checker workaround + let cur_box = self.boxes[box_i]; // Compute the height and bounding box of each box. let bounding_box = match cur_box { @@ -849,7 +872,7 @@ impl InlineFlowData { // Now go back and adjust the Y coordinates to match the baseline we determined. for line_span.eachi |box_i| { - let cur_box = boxes[box_i]; + let cur_box = self.boxes[box_i]; // TODO(#226): This is completely wrong. We need to use the element's `line-height` // when calculating line box height. Then we should go back over and set Y offsets diff --git a/src/components/main/layout/layout_task.rs b/src/components/main/layout/layout_task.rs index 0389f7135ce..0d683ec25f4 100644 --- a/src/components/main/layout/layout_task.rs +++ b/src/components/main/layout/layout_task.rs @@ -225,9 +225,9 @@ impl Layout { for layout_root.traverse_preorder |flow| { flow.assign_widths(&mut layout_ctx); }; - for layout_root.traverse_postorder |flow| { - flow.assign_height(&mut layout_ctx); - }; + + // For now, this is an inorder traversal + layout_root.assign_height(&mut layout_ctx); } // Build the display list if necessary, and send it to the renderer. diff --git a/src/components/main/layout/model.rs b/src/components/main/layout/model.rs index 37195aa757e..bd465db83b9 100644 --- a/src/components/main/layout/model.rs +++ b/src/components/main/layout/model.rs @@ -20,6 +20,7 @@ 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::{CSSHeight, CSSHeightLength, CSSHeightPercentage, CSSHeightAuto}; 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". @@ -61,6 +62,17 @@ impl MaybeAuto{ } } + pub fn from_height(height: CSSHeight, cb_height: Au) -> MaybeAuto{ + match height { + CSSHeightAuto => Auto, + CSSHeightPercentage(percent) => Specified(cb_height.scale_by(percent/100.0)), + //FIXME(eatkinson): Compute pt and em values properly + CSSHeightLength(Px(v)) | + CSSHeightLength(Pt(v)) | + CSSHeightLength(Em(v)) => Specified(Au::from_frac_px(v)), + } + } + pub fn spec_or_default(&self, default: Au) -> Au{ match *self{ Auto => default, @@ -112,7 +124,7 @@ impl BoxModel { } /// Helper function to compute the border width in app units from the CSS border width. - priv fn compute_border_width(&self, width: CSSBorderWidth) -> Au { + pub fn compute_border_width(&self, width: CSSBorderWidth) -> Au { match width { CSSBorderWidthLength(Px(v)) | CSSBorderWidthLength(Em(v)) | @@ -126,7 +138,7 @@ impl BoxModel { } } - fn compute_padding_length(&self, padding: CSSPadding, content_box_width: Au) -> Au { + pub fn compute_padding_length(&self, padding: CSSPadding, content_box_width: Au) -> Au { match padding { CSSPaddingLength(Px(v)) | CSSPaddingLength(Pt(v)) | diff --git a/src/components/main/servo.rc b/src/components/main/servo.rc index 99073d701a6..4cdfb710ee5 100755 --- a/src/components/main/servo.rc +++ b/src/components/main/servo.rc @@ -73,6 +73,8 @@ pub mod layout { pub mod inline; pub mod model; pub mod text; + pub mod float_context; + pub mod float; mod aux; } |