FloatedBoxSpace.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  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-2023 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 "FloatedBoxSpace.h"
  29. #include "../../../Include/RmlUi/Core/ComputedValues.h"
  30. #include "../../../Include/RmlUi/Core/Element.h"
  31. #include "../../../Include/RmlUi/Core/ElementScroll.h"
  32. #include "BlockContainer.h"
  33. #include "LayoutPools.h"
  34. #include <float.h>
  35. namespace Rml {
  36. FloatedBoxSpace::FloatedBoxSpace() {}
  37. FloatedBoxSpace::~FloatedBoxSpace() {}
  38. Vector2f FloatedBoxSpace::NextBoxPosition(const BlockContainer* parent, float& box_width, float cursor, const Vector2f dimensions, bool nowrap) const
  39. {
  40. return NextBoxPosition(parent, box_width, cursor, dimensions, nowrap, Style::Float::None);
  41. }
  42. Vector2f FloatedBoxSpace::NextFloatPosition(const BlockContainer* parent, float& out_box_width, float cursor, Vector2f dimensions,
  43. Style::Float float_property, Style::Clear clear_property) const
  44. {
  45. // Shift the cursor down (if necessary) so it isn't placed any higher than a previously-floated box.
  46. for (int i = 0; i < NUM_ANCHOR_EDGES; ++i)
  47. {
  48. if (!boxes[i].empty())
  49. cursor = Math::Max(cursor, boxes[i].back().offset.y);
  50. }
  51. // Shift the cursor down past to clear boxes, if necessary.
  52. cursor = DetermineClearPosition(cursor, clear_property);
  53. // Find a place to put this box.
  54. const bool nowrap = false;
  55. const Vector2f margin_offset = NextBoxPosition(parent, out_box_width, cursor, dimensions, nowrap, float_property);
  56. return margin_offset;
  57. }
  58. void FloatedBoxSpace::PlaceFloat(Style::Float float_property, Vector2f margin_position, Vector2f margin_size, Vector2f overflow_position,
  59. Vector2f overflow_size)
  60. {
  61. boxes[float_property == Style::Float::Left ? LEFT : RIGHT].push_back(FloatedBox{margin_position, margin_size});
  62. // Set our extents so they enclose the new box.
  63. extent_top_left_overflow = Math::Min(extent_top_left_overflow, overflow_position);
  64. extent_bottom_right_overflow = Math::Max(extent_bottom_right_overflow, overflow_position + overflow_size);
  65. extent_bottom_right_margin = Math::Max(extent_bottom_right_margin, margin_position + margin_size);
  66. }
  67. float FloatedBoxSpace::DetermineClearPosition(float cursor, Style::Clear clear_property) const
  68. {
  69. using namespace Style;
  70. // Clear left boxes.
  71. if (clear_property == Clear::Left || clear_property == Clear::Both)
  72. {
  73. for (size_t i = 0; i < boxes[LEFT].size(); ++i)
  74. cursor = Math::Max(cursor, boxes[LEFT][i].offset.y + boxes[LEFT][i].dimensions.y);
  75. }
  76. // Clear right boxes.
  77. if (clear_property == Clear::Right || clear_property == Clear::Both)
  78. {
  79. for (size_t i = 0; i < boxes[RIGHT].size(); ++i)
  80. cursor = Math::Max(cursor, boxes[RIGHT][i].offset.y + boxes[RIGHT][i].dimensions.y);
  81. }
  82. return cursor;
  83. }
  84. Vector2f FloatedBoxSpace::NextBoxPosition(const BlockContainer* parent, float& maximum_box_width, const float cursor, const Vector2f dimensions,
  85. const bool nowrap, const Style::Float float_property) const
  86. {
  87. const float parent_scrollbar_width = parent->GetElement()->GetElementScroll()->GetScrollbarSize(ElementScroll::VERTICAL);
  88. const float parent_edge_left = parent->GetPosition().x + parent->GetBox().GetPosition().x;
  89. const float parent_edge_right = (parent->GetBox().GetSize().x < 0.f ? FloatedBoxSpace::edge_right_position_for_indefinite_size
  90. : parent_edge_left + parent->GetBox().GetSize().x - parent_scrollbar_width);
  91. const AnchorEdge box_edge = (float_property == Style::Float::Right ? RIGHT : LEFT);
  92. Vector2f box_position = {parent_edge_left, cursor};
  93. if (box_edge == RIGHT)
  94. box_position.x = parent_edge_right - dimensions.x;
  95. float next_cursor = FLT_MAX;
  96. // First up; we iterate through all boxes that share our edge, pushing ourself to the side of them if we intersect
  97. // them. We record the height of the lowest box that gets in our way; in the event we can't be positioned at this
  98. // height, we'll reposition ourselves at that height for the next iteration.
  99. for (const FloatedBox& fixed_box : boxes[box_edge])
  100. {
  101. // If the fixed box's bottom edge is above our top edge, then we can safely skip it.
  102. if (fixed_box.offset.y + fixed_box.dimensions.y <= box_position.y)
  103. continue;
  104. // If the fixed box's top edge is below our bottom edge, then we can safely skip it.
  105. if (fixed_box.offset.y >= box_position.y + dimensions.y)
  106. continue;
  107. // We're intersecting this box vertically, so the box is pushed to the side if necessary.
  108. bool collision = false;
  109. if (box_edge == LEFT)
  110. {
  111. float right_edge = fixed_box.offset.x + fixed_box.dimensions.x;
  112. collision = box_position.x < right_edge;
  113. if (collision)
  114. box_position.x = right_edge;
  115. }
  116. else
  117. {
  118. collision = box_position.x + dimensions.x > fixed_box.offset.x;
  119. if (collision)
  120. box_position.x = fixed_box.offset.x - dimensions.x;
  121. }
  122. // If there was a collision, then we *might* want to remember the height of this box if it is the earliest-
  123. // terminating box we've collided with so far.
  124. if (collision && !nowrap)
  125. {
  126. next_cursor = Math::Min(next_cursor, fixed_box.offset.y + fixed_box.dimensions.y);
  127. // Were we pushed out of our containing box? If so, try again at the next cursor position.
  128. if (box_position.x < parent_edge_left || box_position.x + dimensions.x > parent_edge_right)
  129. return NextBoxPosition(parent, maximum_box_width, next_cursor, dimensions, nowrap, float_property);
  130. }
  131. }
  132. // Second; we go through all of the boxes on the other edge, checking for horizontal collisions and determining the
  133. // maximum width the box can stretch to, if it is placed at this location.
  134. maximum_box_width = (box_edge == LEFT ? parent_edge_right - box_position.x : box_position.x + dimensions.x);
  135. for (const FloatedBox& fixed_box : boxes[1 - box_edge])
  136. {
  137. // If the fixed box's bottom edge is above our top edge, then we can safely skip it.
  138. if (fixed_box.offset.y + fixed_box.dimensions.y <= box_position.y)
  139. continue;
  140. // If the fixed box's top edge is below our bottom edge, then we can safely skip it.
  141. if (fixed_box.offset.y >= box_position.y + dimensions.y)
  142. continue;
  143. // We intersect this box vertically, so check if it intersects horizontally.
  144. bool collision = false;
  145. if (box_edge == LEFT)
  146. {
  147. maximum_box_width = Math::Min(maximum_box_width, fixed_box.offset.x - box_position.x);
  148. collision = box_position.x + dimensions.x > fixed_box.offset.x;
  149. }
  150. else
  151. {
  152. maximum_box_width = Math::Min(maximum_box_width, (box_position.x + dimensions.x) - (fixed_box.offset.x + fixed_box.dimensions.x));
  153. collision = box_position.x < fixed_box.offset.x + fixed_box.dimensions.x;
  154. }
  155. // If we collided with this box ... d'oh! We'll try again lower down the page, at the highest bottom-edge of
  156. // any of the boxes we've been pushed around by so far.
  157. if (collision && !nowrap)
  158. {
  159. next_cursor = Math::Min(next_cursor, fixed_box.offset.y + fixed_box.dimensions.y);
  160. return NextBoxPosition(parent, maximum_box_width, next_cursor, dimensions, nowrap, float_property);
  161. }
  162. }
  163. // If we are restricted from wrapping the position down, then we are already done now that we've shifted horizontally.
  164. if (nowrap)
  165. return box_position;
  166. // Third; we go through all of the boxes (on both sides), checking for vertical collisions.
  167. for (int i = 0; i < 2; ++i)
  168. {
  169. for (const FloatedBox& fixed_box : boxes[i])
  170. {
  171. // If the fixed box's bottom edge is above our top edge, then we can safely skip it.
  172. if (fixed_box.offset.y + fixed_box.dimensions.y <= box_position.y)
  173. continue;
  174. // If the fixed box's top edge is below our bottom edge, then we can safely skip it.
  175. if (fixed_box.offset.y >= box_position.y + dimensions.y)
  176. continue;
  177. // We collide vertically; if we also collide horizontally, then we have to try again further down the
  178. // layout. If the fixed box's left edge is to right of our right edge, then we can safely skip it.
  179. if (fixed_box.offset.x >= box_position.x + dimensions.x)
  180. continue;
  181. // If the fixed box's right edge is to the left of our left edge, then we can safely skip it.
  182. if (fixed_box.offset.x + fixed_box.dimensions.x <= box_position.x)
  183. continue;
  184. // D'oh! We hit this box. Ah well; we'll try again lower down the page, at the highest bottom-edge of any
  185. // of the boxes we've been pushed around by so far.
  186. next_cursor = Math::Min(next_cursor, fixed_box.offset.y + fixed_box.dimensions.y);
  187. return NextBoxPosition(parent, maximum_box_width, next_cursor, dimensions, nowrap, float_property);
  188. }
  189. }
  190. // Looks like we've found a winner!
  191. return box_position;
  192. }
  193. Vector2f FloatedBoxSpace::GetDimensions(FloatedBoxEdge edge) const
  194. {
  195. // For now, we don't really use the top-left extent, because it is not allowed in CSS to scroll to content located
  196. // to the top or left, and thus we have no use for it currently. We could use it later to help detect overflow on
  197. // the top-left sides. For example so we can hide parts of floats pushing outside the top-left sides of its parent
  198. // which is set to 'overflow: auto'.
  199. return edge == FloatedBoxEdge::Margin ? extent_bottom_right_margin : extent_bottom_right_overflow;
  200. }
  201. float FloatedBoxSpace::GetShrinkToFitWidth(float edge_left, float edge_right) const
  202. {
  203. // For the left-anchored boxes: Find the right-most edge of the boxes, relative to our parent's left edge.
  204. float left_shrink_width = 0.f;
  205. for (const FloatedBox& box : boxes[LEFT])
  206. left_shrink_width = Math::Max(left_shrink_width, box.offset.x - edge_left + box.dimensions.x);
  207. // Conversely, for the right-anchored boxes: Find the left-most edge, relative to our parent's right edge.
  208. float right_shrink_width = 0.f;
  209. for (const FloatedBox& box : boxes[RIGHT])
  210. right_shrink_width = Math::Max(right_shrink_width, edge_right - box.offset.x);
  211. return left_shrink_width + right_shrink_width;
  212. }
  213. void* FloatedBoxSpace::operator new(size_t size)
  214. {
  215. return LayoutPools::AllocateLayoutChunk(size);
  216. }
  217. void FloatedBoxSpace::operator delete(void* chunk, size_t size)
  218. {
  219. LayoutPools::DeallocateLayoutChunk(chunk, size);
  220. }
  221. } // namespace Rml