diff options
Diffstat (limited to 'src/components/main/layout/float_context.rs')
-rw-r--r-- | src/components/main/layout/float_context.rs | 225 |
1 files changed, 225 insertions, 0 deletions
diff --git a/src/components/main/layout/float_context.rs b/src/components/main/layout/float_context.rs new file mode 100644 index 00000000000..dd35b4f91cb --- /dev/null +++ b/src/components/main/layout/float_context.rs @@ -0,0 +1,225 @@ +/* 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_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_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_base |base| { + base.add_float(info); + } + replace(self, Invalid) + } +} + +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; + } + + /// 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)) + } + + // 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") + }; + assert!(max_left < min_right, "Float position error"); + 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>{ + // Can't go any higher than previous floats or + // previous elements in the document. + let mut float_y = max(info.ceiling, self.max_y); + loop { + let maybe_location = self.available_rect(float_y, info.height, info.max_width); + 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; + } + } + } + } +} + |