/
float_context.rs
313 lines (269 loc) · 11.3 KB
/
float_context.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
/* 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 std::util::replace;
use std::vec;
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 place_between_floats(&self, info: &PlacementInfo) -> Rect<Au> {
do self.with_base |base| {
base.place_between_floats(info)
}
}
#[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 = 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 = Au(0) - self.offset.x;
let mut l_top = None;
let mut l_bottom = None;
// Relevant dimensions for the left-most right float
let mut min_right = max_x - self.offset.x;
let mut r_top = None;
let mut r_bottom = None;
// Find the float collisions for the given vertical range.
for self.float_data.iter().advance |float| {
debug!("available_rect: Checking for collision against 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);
debug!("available_rect: collision with left float: new max_left is %?",
max_left);
}
}
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);
debug!("available_rect: collision with right float: new max_left is %?",
max_left);
}
}
}
}
};
}
// 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")
};
// This assertion is too strong and fails in some cases. It is OK to
// return negative widths since we check against that right away, but
// we should still undersrtand why they occur and add a stronger
// assertion here.
//assert!(max_left < min_right);
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_info = PlacementInfo {
width: info.width,
height: info.height,
ceiling: max(info.ceiling, self.max_y + self.offset.y),
max_width: info.max_width,
f_type: info.f_type
};
let new_float = FloatData {
bounds: Rect {
origin: self.place_between_floats(&new_info).origin - self.offset,
size: Size2D(info.width, info.height)
},
f_type: info.f_type
};
self.float_data[self.floats_used] = Some(new_float);
self.max_y = max(self.max_y, new_float.bounds.origin.y);
self.floats_used += 1;
}
/// Returns true if the given rect overlaps with any floats.
fn collides_with_float(&self, bounds: &Rect<Au>) -> bool {
for self.float_data.each |float| {
match *float{
None => (),
Some(data) => {
if data.bounds.translate(&self.offset).intersects(bounds) {
return true;
}
}
};
}
return false;
}
/// Given necessary info, finds the closest place a box can be positioned
/// without colliding with any floats.
fn place_between_floats(&self, info: &PlacementInfo) -> Rect<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 = info.ceiling;
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 match info.f_type {
FloatLeft => Rect(Point2D(Au(0), float_y),
Size2D(info.max_width, info.height)),
FloatRight => Rect(Point2D(info.max_width - info.width, float_y),
Size2D(info.max_width, info.height))
},
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 match info.f_type {
FloatLeft => Rect(Point2D(rect.origin.x, float_y),
Size2D(rect.size.width, info.height)),
FloatRight => {
Rect(Point2D(rect.origin.x + rect.size.width - info.width, float_y),
Size2D(rect.size.width, info.height))
}
};
}
// Try to place at the next-lowest location.
// Need to be careful of fencepost errors.
float_y = rect.origin.y + rect.size.height;
}
}
}
}
}