LayoutBlockBoxSpace.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. /*
  2. * This source file is part of RmlUi, the HTML/CSS Interface Middleware
  3. *
  4. * For the latest information, see http://github.com/mikke89/RmlUi
  5. *
  6. * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
  7. * Copyright (c) 2019 The RmlUi Team, and contributors
  8. *
  9. * Permission is hereby granted, free of charge, to any person obtaining a copy
  10. * of this software and associated documentation files (the "Software"), to deal
  11. * in the Software without restriction, including without limitation the rights
  12. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. * copies of the Software, and to permit persons to whom the Software is
  14. * furnished to do so, subject to the following conditions:
  15. *
  16. * The above copyright notice and this permission notice shall be included in
  17. * all copies or substantial portions of the Software.
  18. *
  19. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. * THE SOFTWARE.
  26. *
  27. */
  28. #include "LayoutBlockBoxSpace.h"
  29. #include "../../Include/RmlUi/Core/ComputedValues.h"
  30. #include "../../Include/RmlUi/Core/Element.h"
  31. #include "../../Include/RmlUi/Core/ElementScroll.h"
  32. #include "LayoutBlockBox.h"
  33. #include "LayoutEngine.h"
  34. #include <float.h>
  35. namespace Rml {
  36. LayoutBlockBoxSpace::LayoutBlockBoxSpace(LayoutBlockBox* _parent) : offset(0, 0), dimensions(0, 0)
  37. {
  38. parent = _parent;
  39. }
  40. LayoutBlockBoxSpace::~LayoutBlockBoxSpace()
  41. {
  42. }
  43. // Imports boxes from another block into this space.
  44. void LayoutBlockBoxSpace::ImportSpace(const LayoutBlockBoxSpace& space)
  45. {
  46. // Copy all the boxes from the parent into this space. Could do some optimisation here!
  47. for (int i = 0; i < NUM_ANCHOR_EDGES; ++i)
  48. {
  49. for (size_t j = 0; j < space.boxes[i].size(); ++j)
  50. boxes[i].push_back(space.boxes[i][j]);
  51. }
  52. }
  53. // Generates the position for a box of a given size within a containing block box.
  54. void LayoutBlockBoxSpace::PositionBox(Vector2f& box_position, float& box_width, float cursor, const Vector2f dimensions) const
  55. {
  56. box_width = PositionBox(box_position, cursor, dimensions);
  57. }
  58. // Generates and sets the position for a floating box of a given size within our block box.
  59. float LayoutBlockBoxSpace::PositionBox(float cursor, Element* element)
  60. {
  61. Vector2f element_size = element->GetBox().GetSize(Box::MARGIN);
  62. Style::Float float_property = element->GetComputedValues().float_();
  63. // Shift the cursor down (if necessary) so it isn't placed any higher than a previously-floated box.
  64. for (int i = 0; i < NUM_ANCHOR_EDGES; ++i)
  65. {
  66. if (!boxes[i].empty())
  67. cursor = Math::Max(cursor, boxes[i].back().offset.y);
  68. }
  69. // Shift the cursor down past to clear boxes, if necessary.
  70. cursor = ClearBoxes(cursor, element->GetComputedValues().clear());
  71. // Find a place to put this box.
  72. Vector2f element_offset;
  73. PositionBox(element_offset, cursor, element_size, float_property);
  74. // It's been placed, so we can now add it to our list of floating boxes.
  75. boxes[float_property == Style::Float::Left ? LEFT : RIGHT].push_back(SpaceBox(element_offset, element_size));
  76. // Set our offset and dimensions (if necessary) so they enclose the new box.
  77. Vector2f normalised_offset = element_offset - (parent->GetPosition() + parent->GetBox().GetPosition());
  78. offset.x = Math::Min(offset.x, normalised_offset.x);
  79. offset.y = Math::Min(offset.y, normalised_offset.y);
  80. dimensions.x = Math::Max(dimensions.x, normalised_offset.x + element_size.x);
  81. dimensions.y = Math::Max(dimensions.y, normalised_offset.y + element_size.y);
  82. // Shift the offset into the correct space relative to the element's offset parent.
  83. element_offset += Vector2f(element->GetBox().GetEdge(Box::MARGIN, Box::LEFT), element->GetBox().GetEdge(Box::MARGIN, Box::TOP));
  84. element->SetOffset(element_offset - parent->GetOffsetParent()->GetPosition(), parent->GetOffsetParent()->GetElement());
  85. return element_offset.y + element_size.y;
  86. }
  87. // Determines the appropriate vertical position for an object that is choosing to clear floating elements to the left
  88. // or right (or both).
  89. float LayoutBlockBoxSpace::ClearBoxes(float cursor, Style::Clear clear_property) const
  90. {
  91. using namespace Style;
  92. // Clear left boxes.
  93. if (clear_property == Clear::Left ||
  94. clear_property == Clear::Both)
  95. {
  96. for (size_t i = 0; i < boxes[LEFT].size(); ++i)
  97. cursor = Math::Max(cursor, boxes[LEFT][i].offset.y + boxes[LEFT][i].dimensions.y);
  98. }
  99. // Clear right boxes.
  100. if (clear_property == Clear::Right ||
  101. clear_property == Clear::Both)
  102. {
  103. for (size_t i = 0; i < boxes[RIGHT].size(); ++i)
  104. cursor = Math::Max(cursor, boxes[RIGHT][i].offset.y + boxes[RIGHT][i].dimensions.y);
  105. }
  106. return cursor;
  107. }
  108. // Generates the position for an arbitrary box within our space layout, floated against either the left or right edge.
  109. float LayoutBlockBoxSpace::PositionBox(Vector2f& box_position, float cursor, const Vector2f dimensions, Style::Float float_property) const
  110. {
  111. float parent_scrollbar_width = parent->GetElement()->GetElementScroll()->GetScrollbarSize(ElementScroll::VERTICAL);
  112. float parent_origin = parent->GetPosition().x + parent->GetBox().GetPosition(Box::CONTENT).x;
  113. float parent_edge = parent->GetBox().GetSize().x + parent_origin - parent_scrollbar_width;
  114. AnchorEdge box_edge = float_property == Style::Float::Right ? RIGHT : LEFT;
  115. box_position.y = cursor;
  116. box_position.x = parent_origin;
  117. if (box_edge == RIGHT)
  118. box_position.x += parent->GetBox().GetSize().x - dimensions.x - parent_scrollbar_width;
  119. float next_cursor = FLT_MAX;
  120. // First up; we iterate through all boxes that share our edge, pushing ourself to the side of them if we intersect
  121. // them. We record the height of the lowest box that gets in our way; in the event we can't be positioned at this
  122. // height, we'll reposition ourselves at that height for the next iteration.
  123. for (size_t i = 0; i < boxes[box_edge].size(); ++i)
  124. {
  125. const SpaceBox& fixed_box = boxes[box_edge][i];
  126. // If the fixed box's bottom edge is above our top edge, then we can safely skip it.
  127. if (fixed_box.offset.y + fixed_box.dimensions.y <= box_position.y)
  128. continue;
  129. // If the fixed box's top edge is below our bottom edge, then we can safely skip it.
  130. if (fixed_box.offset.y >= box_position.y + dimensions.y)
  131. continue;
  132. // We're intersecting this box vertically, so the box is pushed to the side if necessary.
  133. bool collision = false;
  134. if (box_edge == LEFT)
  135. {
  136. float right_edge = fixed_box.offset.x + fixed_box.dimensions.x;
  137. collision = box_position.x < right_edge;
  138. if (collision)
  139. box_position.x = right_edge;
  140. }
  141. else
  142. {
  143. collision = box_position.x + dimensions.x > fixed_box.offset.x;
  144. if (collision)
  145. box_position.x = fixed_box.offset.x - dimensions.x;
  146. }
  147. // If there was a collision, then we *might* want to remember the height of this box if it is the earliest-
  148. // terminating box we've collided with so far.
  149. if (collision)
  150. {
  151. next_cursor = Math::Min(next_cursor, fixed_box.offset.y + fixed_box.dimensions.y);
  152. // Were we pushed out of our containing box? If so, try again at the next cursor position.
  153. float normalised_position = box_position.x - parent_origin;
  154. if (normalised_position < 0 ||
  155. normalised_position + dimensions.x > parent->GetBox().GetSize().x)
  156. return PositionBox(box_position, next_cursor + 0.01f, dimensions, float_property);
  157. }
  158. }
  159. // Second; we go through all of the boxes on the other edge, checking for horizontal collisions and determining the
  160. // maximum width the box can stretch to, if it is placed at this location.
  161. float maximum_box_width = box_edge == LEFT ? parent_edge - box_position.x : box_position.x + dimensions.x;
  162. for (size_t i = 0; i < boxes[1 - box_edge].size(); ++i)
  163. {
  164. const SpaceBox& fixed_box = boxes[1 - box_edge][i];
  165. // If the fixed box's bottom edge is above our top edge, then we can safely skip it.
  166. if (fixed_box.offset.y + fixed_box.dimensions.y <= box_position.y)
  167. continue;
  168. // If the fixed box's top edge is below our bottom edge, then we can safely skip it.
  169. if (fixed_box.offset.y >= box_position.y + dimensions.y)
  170. continue;
  171. // We intersect this box vertically, so check if it intersects horizontally.
  172. bool collision = false;
  173. if (box_edge == LEFT)
  174. {
  175. maximum_box_width = Math::Min(maximum_box_width, fixed_box.offset.x - box_position.x);
  176. collision = box_position.x + dimensions.x > fixed_box.offset.x;
  177. }
  178. else
  179. {
  180. maximum_box_width = Math::Min(maximum_box_width, (box_position.x + dimensions.x) - (fixed_box.offset.x + fixed_box.dimensions.x));
  181. collision = box_position.x < fixed_box.offset.x + fixed_box.dimensions.x;
  182. }
  183. // If we collided with this box ... d'oh! We'll try again lower down the page, at the highest bottom-edge of
  184. // any of the boxes we've been pushed around by so far.
  185. if (collision)
  186. {
  187. next_cursor = Math::Min(next_cursor, fixed_box.offset.y + fixed_box.dimensions.y);
  188. return PositionBox(box_position, next_cursor + 0.01f, dimensions, float_property);
  189. }
  190. }
  191. // Third; we go through all of the boxes (on both sides), checking for vertical collisions.
  192. for (int i = 0; i < 2; ++i)
  193. {
  194. for (size_t j = 0; j < boxes[i].size(); ++j)
  195. {
  196. const SpaceBox& fixed_box = boxes[i][j];
  197. // If the fixed box's bottom edge is above our top edge, then we can safely skip it.
  198. if (fixed_box.offset.y + fixed_box.dimensions.y <= box_position.y)
  199. continue;
  200. // If the fixed box's top edge is below our bottom edge, then we can safely skip it.
  201. if (fixed_box.offset.y >= box_position.y + dimensions.y)
  202. continue;
  203. // We collide vertically; if we also collide horizontally, then we have to try again further down the
  204. // layout. If the fixed box's left edge is to right of our right edge, then we can safely skip it.
  205. if (fixed_box.offset.x >= box_position.x + dimensions.x)
  206. continue;
  207. // If the fixed box's right edge is to the left of our left edge, then we can safely skip it.
  208. if (fixed_box.offset.x + fixed_box.dimensions.x <= box_position.x)
  209. continue;
  210. // D'oh! We hit this box. Ah well; we'll try again lower down the page, at the highest bottom-edge of any
  211. // of the boxes we've been pushed around by so far.
  212. next_cursor = Math::Min(next_cursor, fixed_box.offset.y + fixed_box.dimensions.y);
  213. return PositionBox(box_position, next_cursor + 0.01f, dimensions, float_property);
  214. }
  215. }
  216. // Looks like we've found a winner!
  217. return maximum_box_width;
  218. }
  219. // Returns the top-left offset of the boxes within the space.
  220. Vector2f LayoutBlockBoxSpace::GetOffset() const
  221. {
  222. return offset;
  223. }
  224. // Returns the dimensions of the boxes within the space.
  225. Vector2f LayoutBlockBoxSpace::GetDimensions() const
  226. {
  227. return dimensions - offset;
  228. }
  229. void* LayoutBlockBoxSpace::operator new(size_t size)
  230. {
  231. return LayoutEngine::AllocateLayoutChunk(size);
  232. }
  233. void LayoutBlockBoxSpace::operator delete(void* chunk, size_t size)
  234. {
  235. LayoutEngine::DeallocateLayoutChunk(chunk, size);
  236. }
  237. LayoutBlockBoxSpace::SpaceBox::SpaceBox() : offset(0, 0), dimensions(0, 0)
  238. {
  239. }
  240. LayoutBlockBoxSpace::SpaceBox::SpaceBox(const Vector2f offset, const Vector2f dimensions) : offset(offset), dimensions(dimensions)
  241. {
  242. }
  243. } // namespace Rml