Merge lp:~widelands-dev/widelands/bug-1751620-floating-images into lp:widelands
- bug-1751620-floating-images
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 8620 | ||||
Proposed branch: | lp:~widelands-dev/widelands/bug-1751620-floating-images | ||||
Merge into: | lp:widelands | ||||
Diff against target: |
431 lines (+174/-75) 3 files modified
data/scripting/richtext.lua (+2/-3) src/graphic/text/rt_parse.cc (+4/-2) src/graphic/text/rt_render.cc (+168/-70) |
||||
To merge this branch: | bzr merge lp:~widelands-dev/widelands/bug-1751620-floating-images | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
GunChleoc | Approve | ||
Review via email: mp+341286@code.launchpad.net |
Commit message
Implementing text floating around images.
Description of the change
Implements a richtext attribute to allow text to flow around images.
bunnybot (widelandsofficial) wrote : | # |
GunChleoc (gunchleoc) wrote : | # |
This is working great for float::left, thank you so much! Float::right doesn't do anything yet though. You can test by hacking li_image like this:
function li_image(imagepath, text)
return
div(
p(text) .. div("float=right padding_l=6", p(img(imagepath)))
)
end
I leave it up to you if you want to do that in this branch or a follow-up branch.
GunChleoc (gunchleoc) wrote : | # |
Never mind, the code below works :)
@bunnybot merge
div(
p(text)
)
Notabilis (notabilis27) wrote : | # |
Thanks for the review and the documentation.
The problem is that the floating divs have to be in the "line" before the text, otherwise the layout class does not know about the required space. I guess it could be fixed, but would probably make two layout runs necessary. Simply documenting it should be fine in my opinion.
GunChleoc (gunchleoc) wrote : | # |
Agreed. I have updated the documentation a bit in rt_parse, that should be sufficient for usage purpose. And what we lose in syntax flexibility here, we are gaining in speed :)
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 3292. State: passed. Details: https:/
Appveyor build 3100. State: failed. Details: https:/
Preview Diff
1 | === modified file 'data/scripting/richtext.lua' |
2 | --- data/scripting/richtext.lua 2018-02-24 15:31:26 +0000 |
3 | +++ data/scripting/richtext.lua 2018-03-15 05:52:38 +0000 |
4 | @@ -419,9 +419,8 @@ |
5 | function li_image(imagepath, text) |
6 | return |
7 | div("width=100%", |
8 | - div(p(vspace(6) .. img(imagepath) .. space(6))) .. |
9 | - div(p(space(6))) .. |
10 | - div("width=*", p(vspace(6) .. text .. vspace(12))) |
11 | + div("float=left padding_r=6", p(img(imagepath))) .. |
12 | + p(text) |
13 | ) |
14 | end |
15 | |
16 | |
17 | === modified file 'src/graphic/text/rt_parse.cc' |
18 | --- src/graphic/text/rt_parse.cc 2018-02-25 11:51:29 +0000 |
19 | +++ src/graphic/text/rt_parse.cc 2018-03-15 05:52:38 +0000 |
20 | @@ -261,8 +261,10 @@ |
21 | |
22 | The same attributes as :ref:`rt_tags_rt`, plus the following: |
23 | |
24 | -* **margin**: Shrink all contents to leave a margin towards the outer edge of this tag's rectangle |
25 | -* **float**: To be implemented. Allowed values are ``left``, ``right`` |
26 | +* **margin**: Shrink all contents to leave a margin towards the outer edge of this tag's rectangle. |
27 | +* **float**: Make text float around this div. Allowed values are ``left``, ``right``. |
28 | + The structure has to be something like: ``div("width=100%", div("float=left padding_r=6", p(img(imagepath))) .. p(text))``, |
29 | + with the first embedded div being the floating one. |
30 | * **valign**: Align the contents vertically. Allowed values are ``top`` (default), ``center`` or ``middle``, ``bottom``. |
31 | * **width**: The width of this element, as a pixel amount, or as a percentage. |
32 | The last ``div`` in a row can be expanded automatically by using ``*``. |
33 | |
34 | === modified file 'src/graphic/text/rt_render.cc' |
35 | --- src/graphic/text/rt_render.cc 2018-02-25 18:20:58 +0000 |
36 | +++ src/graphic/text/rt_render.cc 2018-03-15 05:52:38 +0000 |
37 | @@ -198,13 +198,13 @@ |
38 | |
39 | class RenderNode { |
40 | public: |
41 | - enum Floating { |
42 | - NO_FLOAT = 0, |
43 | - FLOAT_RIGHT, |
44 | - FLOAT_LEFT, |
45 | + enum class Floating { |
46 | + kNone, |
47 | + kRight, |
48 | + kLeft, |
49 | }; |
50 | explicit RenderNode(NodeStyle& ns) |
51 | - : floating_(NO_FLOAT), halign_(ns.halign), valign_(ns.valign), x_(0), y_(0) { |
52 | + : floating_(Floating::kNone), halign_(ns.halign), valign_(ns.valign), x_(0), y_(0) { |
53 | } |
54 | virtual ~RenderNode() { |
55 | } |
56 | @@ -292,6 +292,9 @@ |
57 | int32_t x_, y_; |
58 | }; |
59 | |
60 | +/* |
61 | + * Class to calculate positions of nodes within a div tag. |
62 | + */ |
63 | class Layout { |
64 | public: |
65 | explicit Layout(std::vector<std::shared_ptr<RenderNode>>& all) |
66 | @@ -307,19 +310,7 @@ |
67 | fit_nodes(std::vector<std::shared_ptr<RenderNode>>* rv, uint16_t w, Borders p, bool trim_spaces); |
68 | |
69 | private: |
70 | - // Represents a change in the rendering constraints. For example when an |
71 | - // Image is inserted, the width will become wider after it. This is a |
72 | - // constraint change. |
73 | - struct ConstraintChange { |
74 | - int at_y; |
75 | - int32_t delta_w; |
76 | - int32_t delta_offset_x; |
77 | - |
78 | - bool operator<(const ConstraintChange& o) const { |
79 | - return at_y > o.at_y || (at_y == o.at_y && delta_w > o.delta_w); |
80 | - } |
81 | - }; |
82 | - |
83 | + bool calculate_line_width(uint16_t *x, uint16_t *w, uint16_t lineheight); |
84 | uint16_t fit_line(uint16_t w, |
85 | const Borders&, |
86 | std::vector<std::shared_ptr<RenderNode>>* rv, |
87 | @@ -328,13 +319,55 @@ |
88 | uint16_t h_; |
89 | size_t idx_; |
90 | std::vector<std::shared_ptr<RenderNode>>& all_nodes_; |
91 | - std::priority_queue<ConstraintChange> constraint_changes_; |
92 | + std::queue<std::shared_ptr<RenderNode>> floats_; |
93 | }; |
94 | |
95 | -uint16_t Layout::fit_line(uint16_t w, |
96 | - const Borders& p, |
97 | - std::vector<std::shared_ptr<RenderNode>>* rv, |
98 | - bool trim_spaces) { |
99 | +/** |
100 | + * Calculate the width of a line at h_ taking into account floating elements. |
101 | + * @param x The first useable x position in the line. Has to be set by the caller, |
102 | + * might be modified inside this function. |
103 | + * @param w The useable width of the line. Has to be set by the caller, |
104 | + * might be modified inside this function. |
105 | + * @param lineheight The height of the line the maximum width should be calculated of. |
106 | + * @return Whether less than the full width can be used (i.e., there is a float). |
107 | + */ |
108 | +bool Layout::calculate_line_width(uint16_t *x, uint16_t *w, uint16_t lineheight) { |
109 | + // Drop elements we already passed |
110 | + while (!floats_.empty() && h_ >= floats_.front()->y() + floats_.front()->height()) { |
111 | + floats_.pop(); |
112 | + } |
113 | + if (floats_.empty()) { |
114 | + return false; |
115 | + } |
116 | + // Check whether there is an element at the current height |
117 | + std::shared_ptr<RenderNode> n = floats_.front(); |
118 | + if (h_ + lineheight < n->y()) { |
119 | + // Nope, nothing in the current line |
120 | + // Since the elements are ordered, no further element can match |
121 | + return false; |
122 | + } |
123 | + |
124 | + if (n->get_floating() == RenderNode::Floating::kLeft) { |
125 | + *x += n->width(); |
126 | + } else { |
127 | + assert(n->get_floating() == RenderNode::Floating::kRight); |
128 | + *w -= n->width(); |
129 | + } |
130 | + return true; |
131 | +} |
132 | + |
133 | +/* |
134 | + * Calculate x positions of nodes in one line and return them in rv. |
135 | + * As many nodes of all_nodes_ are added to the line as there is space. |
136 | + * Remove leading/trailing spaces and assign x positions to all elements. |
137 | + * Use remaining space to distribute expanding elements a bit further. |
138 | + * Returns hotspot of the line. |
139 | + * Method is called from within Layout::fit_nodes(). |
140 | + */ |
141 | +uint16_t Layout::fit_line(const uint16_t w_max, // Maximum width of line |
142 | + const Borders& p, // Left/right border. Is left empty |
143 | + std::vector<std::shared_ptr<RenderNode>>* rv, // Output: Nodes to render |
144 | + bool trim_spaces) { // Whether leading/trailing space should be removed |
145 | assert(rv->empty()); |
146 | |
147 | // Remove leading spaces |
148 | @@ -342,17 +375,79 @@ |
149 | all_nodes_[idx_++].reset(); |
150 | } |
151 | |
152 | - uint16_t x = p.left; |
153 | + uint16_t w; |
154 | + uint16_t x; |
155 | std::size_t first_idx = idx_; |
156 | + uint16_t lineheight = 0; |
157 | + |
158 | + // Pass 1: Run through all nodes who *might* end up in this line and check for floats |
159 | + w = w_max - p.right; |
160 | + x = p.left; |
161 | + bool width_was_reduced = calculate_line_width(&x, &w, lineheight); |
162 | + lineheight = 0; |
163 | + uint16_t w_used = 0; |
164 | + for (; idx_ < all_nodes_.size(); ++idx_) { |
165 | + if (w_used + all_nodes_[idx_]->width() > w) { |
166 | + // Line is full |
167 | + break; |
168 | + } |
169 | + if (all_nodes_[idx_]->get_floating() == RenderNode::Floating::kNone) { |
170 | + // Normal, non-floating node |
171 | + w_used += all_nodes_[idx_]->width(); |
172 | + assert(all_nodes_[idx_]->height() >= all_nodes_[idx_]->hotspot_y()); |
173 | + lineheight = std::max(lineheight, all_nodes_[idx_]->height()); |
174 | + continue; |
175 | + } |
176 | + // Found a float. Add it to list |
177 | + // New float start directly below lowest flow in list |
178 | + if (!floats_.empty()) { |
179 | + all_nodes_[idx_]->set_y(floats_.back()->y() + floats_.back()->height()); |
180 | + } else { |
181 | + all_nodes_[idx_]->set_y(h_); |
182 | + } |
183 | + // Set x position of the float based on its desired orientation |
184 | + if (all_nodes_[idx_]->get_floating() == RenderNode::Floating::kLeft) { |
185 | + all_nodes_[idx_]->set_x(p.left); |
186 | + } else { |
187 | + assert(all_nodes_[idx_]->get_floating() == RenderNode::Floating::kRight); |
188 | + all_nodes_[idx_]->set_x(w_max - all_nodes_[idx_]->width() - p.right); |
189 | + } |
190 | + floats_.push(all_nodes_[idx_]); |
191 | + // When the line width hasn't been reduced by a float yet, do so now. |
192 | + // If it already has been reduced than the new float will be placed somewhere below |
193 | + // the current line so no need to adapt the line width |
194 | + if (!width_was_reduced) { |
195 | + // Don't need to reset x and w since they haven't been modified on last call |
196 | + width_was_reduced = calculate_line_width(&x, &w, lineheight); |
197 | + assert(width_was_reduced); |
198 | + } |
199 | + } |
200 | + |
201 | + idx_ = first_idx; |
202 | + // w and x now contain the width of the line and the x position of the first element in it |
203 | |
204 | // Calc fitting nodes |
205 | while (idx_ < all_nodes_.size()) { |
206 | std::shared_ptr<RenderNode> n = all_nodes_[idx_]; |
207 | + if (n->get_floating() != RenderNode::Floating::kNone) { |
208 | + // Don't handle floaters here |
209 | + rv->push_back(n); |
210 | + if (idx_ == first_idx) { |
211 | + first_idx++; |
212 | + } |
213 | + ++idx_; |
214 | + continue; |
215 | + } |
216 | uint16_t nw = n->width(); |
217 | - if (x + nw + p.right > w || n->get_floating() != RenderNode::NO_FLOAT) { |
218 | + // Check whether the element is too big for the current line |
219 | + // (position + width-of-element + border) > width-of-line |
220 | + if (x + nw + p.right > w) { |
221 | + // Its too big |
222 | if (idx_ == first_idx) { |
223 | + // If it is the first element in the line, add it anyway and pretend that it matches exactly |
224 | nw = w - p.right - x; |
225 | } else { |
226 | + // Too wide and not the first element: We are done with the line |
227 | break; |
228 | } |
229 | } |
230 | @@ -401,9 +496,12 @@ |
231 | } |
232 | } |
233 | |
234 | - // Find the biggest hotspot of the truly remaining items. |
235 | + // Find the biggest hotspot of the truly remaining non-floating items. |
236 | uint16_t cur_line_hotspot = 0; |
237 | for (std::shared_ptr<RenderNode> node : *rv) { |
238 | + if (node->get_floating() != RenderNode::Floating::kNone) { |
239 | + continue; |
240 | + } |
241 | cur_line_hotspot = std::max(cur_line_hotspot, node->hotspot_y()); |
242 | } |
243 | return cur_line_hotspot; |
244 | @@ -412,6 +510,9 @@ |
245 | /* |
246 | * Take ownership of all nodes, delete those that we do not render anyways (for |
247 | * example unneeded spaces), append the rest to the vector we got. |
248 | + * Also, calculate positions for all nodes based on the given width w |
249 | + * and the widths of the nodes. |
250 | + * Method is called from within DivTagHandler::enter(). |
251 | */ |
252 | uint16_t Layout::fit_nodes(std::vector<std::shared_ptr<RenderNode>>* rv, |
253 | uint16_t w, |
254 | @@ -428,10 +529,12 @@ |
255 | |
256 | int line_height = 0; |
257 | int line_start = INFINITE_WIDTH; |
258 | - // Compute real line height and width, taking into account alignement |
259 | + // Compute real line height and width, taking into account alignment |
260 | for (std::shared_ptr<RenderNode> n : nodes_in_line) { |
261 | - line_height = std::max(line_height, biggest_hotspot - n->hotspot_y() + n->height()); |
262 | - n->set_y(h_ + biggest_hotspot - n->hotspot_y()); |
263 | + if (n->get_floating() == RenderNode::Floating::kNone) { |
264 | + line_height = std::max(line_height, biggest_hotspot - n->hotspot_y() + n->height()); |
265 | + n->set_y(h_ + biggest_hotspot - n->hotspot_y()); |
266 | + } |
267 | if (line_start >= INFINITE_WIDTH || n->x() < line_start) { |
268 | line_start = n->x() - p.left; |
269 | } |
270 | @@ -440,7 +543,7 @@ |
271 | |
272 | // Go over again and adjust position for VALIGN |
273 | for (std::shared_ptr<RenderNode> n : nodes_in_line) { |
274 | - uint16_t space = line_height - n->height(); |
275 | + int space = line_height - n->height(); |
276 | if (!space || n->valign() == UI::Align::kBottom) { |
277 | continue; |
278 | } |
279 | @@ -450,43 +553,25 @@ |
280 | // Space can become negative, for example when we have mixed fontsets on the same line |
281 | // (e.g. "default" and "arabic"), due to differing font heights and hotspots. |
282 | // So, we fix the sign. |
283 | - n->set_y(std::abs(n->y() - space)); |
284 | + if (n->get_floating() == RenderNode::Floating::kNone) { |
285 | + n->set_y(std::abs(n->y() - space)); |
286 | + } |
287 | } |
288 | rv->insert(rv->end(), nodes_in_line.begin(), nodes_in_line.end()); |
289 | |
290 | h_ += line_height; |
291 | - while (!constraint_changes_.empty() && constraint_changes_.top().at_y <= h_) { |
292 | - const ConstraintChange& top = constraint_changes_.top(); |
293 | - w += top.delta_w; |
294 | - p.left += top.delta_offset_x; |
295 | - constraint_changes_.pop(); |
296 | - } |
297 | |
298 | - if ((idx_ < all_nodes_.size()) && all_nodes_[idx_]->get_floating()) { |
299 | - std::shared_ptr<RenderNode> n = all_nodes_[idx_]; |
300 | - n->set_y(h_); |
301 | - ConstraintChange cc = {h_ + n->height(), 0, 0}; |
302 | - if (n->get_floating() == RenderNode::FLOAT_LEFT) { |
303 | - n->set_x(p.left); |
304 | - p.left += n->width(); |
305 | - cc.delta_offset_x = -n->width(); |
306 | - max_line_width = std::max<int>(max_line_width, n->x() + n->width() + p.right); |
307 | - } else { |
308 | - n->set_x(w - n->width() - p.right); |
309 | - w -= n->width(); |
310 | - cc.delta_w = n->width(); |
311 | - max_line_width = std::max(max_line_width, w); |
312 | - } |
313 | - constraint_changes_.push(cc); |
314 | - rv->push_back(n); |
315 | - ++idx_; |
316 | - } |
317 | if (idx_ == idx_before_iteration_) { |
318 | throw WidthTooSmall( |
319 | "Could not fit a single render node in line. Width of an Element is too small!"); |
320 | } |
321 | } |
322 | |
323 | + if (!floats_.empty()) { |
324 | + // If there is a float left this means the floats go down further than the text. |
325 | + // If this is the case, reset the height of the div |
326 | + h_ = std::max<uint16_t>(h_, floats_.back()->y() + floats_.back()->height()); |
327 | + } |
328 | h_ += p.bottom; |
329 | return max_line_width; |
330 | } |
331 | @@ -800,6 +885,7 @@ |
332 | |
333 | // Draw Solid background Color |
334 | if (is_background_color_set_) { |
335 | + // TODO(Notabilis): I think margin_.right and .bottom are missing in next line |
336 | UI::RenderedRect* bg_rect = |
337 | new UI::RenderedRect(Recti(margin_.left, margin_.top, w_, h_), background_color_); |
338 | // Size is automatically adjusted in RenderedText while blitting, so no need to call |
339 | @@ -1336,6 +1422,13 @@ |
340 | render_node_(new DivTagRenderNode(ns)) { |
341 | } |
342 | |
343 | + /* |
344 | + * Calculate width of all children of this div. |
345 | + * Width is either fixed, a percentage of the parent or fill. If the width should fill |
346 | + * the line, it is set to the remaining width of the current line. New lines aren't really |
347 | + * started here but percent/filling elements are made so big that they won't fit into the |
348 | + * previous line when their final position is calculated in Layout::fit_nodes(). |
349 | + */ |
350 | void enter() override { |
351 | Borders padding, margin; |
352 | |
353 | @@ -1368,26 +1461,24 @@ |
354 | } |
355 | |
356 | std::vector<std::shared_ptr<RenderNode>> subnodes; |
357 | - // If a percentage width is used, temporarily set it as the overall width. This way, divs with |
358 | - // width "fill" only use the width of their parent node. Also, percentages given in child |
359 | - // nodes |
360 | - // are relative to the width of their parent node. |
361 | - const uint16_t old_overall_width = renderer_style_.overall_width; |
362 | + // If a percentage width is used, temporarily set it as the overall width. This way, |
363 | + // divs with width "fill" only use the width of their parent node. Also, percentages |
364 | + // given in child nodes are relative to the width of their parent node. |
365 | + const uint16_t old_line_width = renderer_style_.overall_width; |
366 | if (render_node_->desired_width().unit == WidthUnit::kPercent) { |
367 | - renderer_style_.overall_width = |
368 | - render_node_->desired_width().width * renderer_style_.overall_width / 100; |
369 | + renderer_style_.overall_width = render_node_->desired_width().width * renderer_style_.overall_width / 100; |
370 | } |
371 | TagHandler::emit_nodes(subnodes); |
372 | - renderer_style_.overall_width = old_overall_width; |
373 | + renderer_style_.overall_width = old_line_width; |
374 | |
375 | - // Determine the width by the width of the widest subnode |
376 | + // Determine the required width by the width of the widest subnode |
377 | uint16_t width_first_subnode = INFINITE_WIDTH; |
378 | uint16_t widest_subnode = 0; |
379 | for (std::shared_ptr<RenderNode> n : subnodes) { |
380 | if (n->width() >= INFINITE_WIDTH) { |
381 | continue; |
382 | } |
383 | - if (width_first_subnode >= INFINITE_WIDTH && n->width() > 0) { |
384 | + if (width_first_subnode >= INFINITE_WIDTH && n->width()) { |
385 | width_first_subnode = n->width() + padding.left + padding.right; |
386 | } |
387 | widest_subnode = std::max<int>(widest_subnode, n->width() + padding.left + padding.right); |
388 | @@ -1401,6 +1492,9 @@ |
389 | case WidthUnit::kPercent: |
390 | w_ = render_node_->desired_width().width * renderer_style_.overall_width / 100; |
391 | |
392 | + if (render_node_->get_floating() != RenderNode::Floating::kNone) { |
393 | + break; |
394 | + } |
395 | // Reduce remaining width |
396 | if (renderer_style_.remaining_width <= w_) { |
397 | // Not enough space. Div will be placed in the next line, calculate the remaining space |
398 | @@ -1412,7 +1506,9 @@ |
399 | break; |
400 | case WidthUnit::kFill: |
401 | w_ = renderer_style_.remaining_width; |
402 | - renderer_style_.remaining_width = 0; |
403 | + if (render_node_->get_floating() == RenderNode::Floating::kNone) { |
404 | + renderer_style_.remaining_width = 0; |
405 | + } |
406 | break; |
407 | default: |
408 | if (!w_) { |
409 | @@ -1430,7 +1526,9 @@ |
410 | extra_width = w_ - max_line_width; |
411 | } else if (render_node_->desired_width().unit == WidthUnit::kShrink) { |
412 | w_ = max_line_width; |
413 | - renderer_style_.remaining_width -= w_; |
414 | + if (render_node_->get_floating() == RenderNode::Floating::kNone) { |
415 | + renderer_style_.remaining_width -= w_; |
416 | + } |
417 | } |
418 | |
419 | // Collect all tags from children |
420 | @@ -1494,9 +1592,9 @@ |
421 | if (a.has("float")) { |
422 | const std::string s = a["float"].get_string(); |
423 | if (s == "right") |
424 | - render_node_->set_floating(RenderNode::FLOAT_RIGHT); |
425 | + render_node_->set_floating(RenderNode::Floating::kRight); |
426 | else if (s == "left") |
427 | - render_node_->set_floating(RenderNode::FLOAT_LEFT); |
428 | + render_node_->set_floating(RenderNode::Floating::kLeft); |
429 | } |
430 | if (a.has("valign")) { |
431 | const std::string align = a["valign"].get_string(); |
Continuous integration builds have changed state:
Travis build 3288. State: passed. Details: https:/ /travis- ci.org/ widelands/ widelands/ builds/ 352028977. /ci.appveyor. com/project/ widelands- dev/widelands/ build/_ widelands_ dev_widelands_ bug_1751620_ floating_ images- 3096.
Appveyor build 3096. State: success. Details: https:/