BlockContainer.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  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 "BlockContainer.h"
  29. #include "../../../Include/RmlUi/Core/ComputedValues.h"
  30. #include "../../../Include/RmlUi/Core/Element.h"
  31. #include "../../../Include/RmlUi/Core/ElementScroll.h"
  32. #include "../../../Include/RmlUi/Core/Profiling.h"
  33. #include "FloatedBoxSpace.h"
  34. #include "InlineContainer.h"
  35. #include "LayoutDetails.h"
  36. #include "LineBox.h"
  37. namespace Rml {
  38. BlockContainer::BlockContainer(ContainerBox* _parent_container, FloatedBoxSpace* _space, Element* _element, const Box& _box, float _min_height,
  39. float _max_height) :
  40. ContainerBox(Type::BlockContainer, _element, _parent_container), box(_box), min_height(_min_height), max_height(_max_height), space(_space)
  41. {
  42. RMLUI_ASSERT(element);
  43. RMLUI_ASSERT(box.GetSize().x >= 0.f);
  44. if (!space)
  45. {
  46. // We are the root of the formatting context, establish a new space for floated boxes.
  47. root_space = MakeUnique<FloatedBoxSpace>();
  48. space = root_space.get();
  49. }
  50. }
  51. BlockContainer::~BlockContainer() {}
  52. bool BlockContainer::Close(BlockContainer* parent_block_container)
  53. {
  54. // If the last child of this block box is an inline box, then we haven't closed it; close it now!
  55. if (!CloseOpenInlineContainer())
  56. return false;
  57. // Set this box's height, if necessary.
  58. if (box.GetSize().y < 0)
  59. {
  60. float content_height = box_cursor;
  61. if (!parent_block_container)
  62. content_height = Math::Max(content_height, space->GetDimensions(FloatedBoxEdge::Margin).y - (position.y + box.GetPosition().y));
  63. content_height = Math::Clamp(content_height, min_height, max_height);
  64. box.SetContent({box.GetSize().x, content_height});
  65. }
  66. // Find the size of our content.
  67. const Vector2f space_box = space->GetDimensions(FloatedBoxEdge::Overflow) - (position + box.GetPosition());
  68. Vector2f content_box = Math::Max(inner_content_size, space_box);
  69. content_box.y = Math::Max(content_box.y, box_cursor);
  70. if (!SubmitBox(content_box, box, max_height))
  71. return false;
  72. // If we are the root of our block formatting context, this will be null. Otherwise increment our parent's cursor to account for this box.
  73. if (parent_block_container)
  74. {
  75. RMLUI_ASSERTMSG(GetParent() == parent_block_container, "Mismatched parent box.");
  76. // If this close fails, it means this block box has caused our parent box to generate an automatic vertical scrollbar.
  77. if (!parent_block_container->EncloseChildBox(this, position, box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Border),
  78. box.GetEdge(BoxArea::Margin, BoxEdge::Bottom)))
  79. return false;
  80. }
  81. // Now that we have been sized, we can proceed with formatting and placing positioned elements that this container
  82. // acts as a containing block for.
  83. ClosePositionedElements();
  84. // Find the element baseline which is the distance from the margin bottom of the element to its baseline.
  85. float element_baseline = 0;
  86. // For inline-blocks with visible overflow, this is the baseline of the last line of the element (see CSS2 10.8.1).
  87. if (element->GetDisplay() == Style::Display::InlineBlock && !IsScrollContainer())
  88. {
  89. float baseline = 0;
  90. bool found_baseline = GetBaselineOfLastLine(baseline);
  91. // The retrieved baseline is the vertical distance from the top of our root space (the coordinate system of our
  92. // local block formatting context), convert it to the element's local coordinates.
  93. if (found_baseline)
  94. {
  95. const float bottom_position =
  96. position.y + box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Border) + box.GetEdge(BoxArea::Margin, BoxEdge::Bottom);
  97. element_baseline = bottom_position - baseline;
  98. }
  99. }
  100. SetElementBaseline(element_baseline);
  101. EnsureEmptyInterruptedLineBox();
  102. SubmitElementLayout();
  103. return true;
  104. }
  105. bool BlockContainer::EncloseChildBox(LayoutBox* child, Vector2f child_position, float child_height, float child_margin_bottom)
  106. {
  107. child_position -= (box.GetPosition() + position);
  108. box_cursor = child_position.y + child_height + child_margin_bottom;
  109. // Extend the inner content size. The vertical size can be larger than the box_cursor due to overflow.
  110. inner_content_size = Math::Max(inner_content_size, child_position + child->GetVisibleOverflowSize());
  111. const Vector2f content_size = Math::Max(Vector2f{box.GetSize().x, box_cursor}, inner_content_size);
  112. const bool result = CatchOverflow(content_size, box, max_height);
  113. return result;
  114. }
  115. BlockContainer* BlockContainer::OpenBlockBox(Element* child_element, const Box& child_box, float min_height, float max_height)
  116. {
  117. if (!CloseOpenInlineContainer())
  118. return nullptr;
  119. auto child_container_ptr = MakeUnique<BlockContainer>(this, space, child_element, child_box, min_height, max_height);
  120. BlockContainer* child_container = child_container_ptr.get();
  121. child_container->position = NextBoxPosition(child_box, child_element->GetComputedValues().clear());
  122. child_element->SetOffset(child_container->position - position, element);
  123. child_container->ResetScrollbars(child_box);
  124. // Store relatively positioned elements with their containing block so that their offset can be updated after
  125. // their containing block has been sized.
  126. if (child_element->GetPosition() == Style::Position::Relative)
  127. AddRelativeElement(child_element);
  128. child_boxes.push_back(std::move(child_container_ptr));
  129. return child_container;
  130. }
  131. LayoutBox* BlockContainer::AddBlockLevelBox(UniquePtr<LayoutBox> block_level_box_ptr, Element* child_element, const Box& child_box)
  132. {
  133. RMLUI_ASSERT(child_box.GetSize().y >= 0.f); // Assumes child element already formatted and sized.
  134. if (!CloseOpenInlineContainer())
  135. return nullptr;
  136. // Clear any floats to avoid overlapping them. In CSS, it is allowed to instead shrink the box and place it next to
  137. // any floats, but we keep it simple here for now and just clear them.
  138. Vector2f child_position = NextBoxPosition(child_box, Style::Clear::Both);
  139. child_element->SetOffset(child_position - position, element);
  140. if (child_element->GetPosition() == Style::Position::Relative)
  141. AddRelativeElement(child_element);
  142. LayoutBox* block_level_box = block_level_box_ptr.get();
  143. child_boxes.push_back(std::move(block_level_box_ptr));
  144. if (!EncloseChildBox(block_level_box, child_position, child_box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Border),
  145. child_box.GetEdge(BoxArea::Margin, BoxEdge::Bottom)))
  146. return nullptr;
  147. return block_level_box;
  148. }
  149. InlineBoxHandle BlockContainer::AddInlineNode(Node* node, const Box& child_box)
  150. {
  151. RMLUI_ZoneScoped;
  152. // Inline-level elements need to be added to an inline container, open one if needed.
  153. InlineContainer* inline_container = EnsureOpenInlineContainer();
  154. InlineBox* inline_box = inline_container->AddInlineNode(node, child_box);
  155. if (Element* element = AsIf<Element*>(node))
  156. {
  157. if (element->GetPosition() == Style::Position::Relative)
  158. AddRelativeElement(element);
  159. }
  160. return {inline_box};
  161. }
  162. void BlockContainer::CloseInlineNode(InlineBoxHandle handle)
  163. {
  164. // If the inline-level element did not generate an inline box, then there is no need to close anything.
  165. if (!handle.inline_box)
  166. return;
  167. // Usually the inline container the box was placed in is still the open box, and we can just close the inline
  168. // element in it. However, it is possible that an intermediary block-level element was placed, thereby splitting the
  169. // inline element into multiple inline containers around the block-level box. If we don't have an open inline
  170. // container at all, open a new one, even if the sole purpose of the new line is to close this inline element.
  171. EnsureOpenInlineContainer()->CloseInlineNode(handle.inline_box);
  172. }
  173. void BlockContainer::AddBreak()
  174. {
  175. const float line_height = element->GetLineHeight();
  176. // Check for an inline box as our last child; if so, we can simply end its line and bail.
  177. if (InlineContainer* inline_container = GetOpenInlineContainer())
  178. {
  179. inline_container->AddBreak(line_height);
  180. return;
  181. }
  182. // No inline box as our last child; no problem, just increment the cursor by the line height of this element.
  183. box_cursor += line_height;
  184. }
  185. void BlockContainer::AddFloatElement(Element* element, Vector2f visible_overflow_size)
  186. {
  187. if (InlineContainer* inline_container = GetOpenInlineContainer())
  188. {
  189. // Try to add the float to our inline container, placing it next to any open line if possible. Otherwise, queue it for later.
  190. bool float_placed = false;
  191. float line_position_top = 0.f;
  192. Vector2f line_size;
  193. if (queued_float_elements.empty() && inline_container->GetOpenLineBoxDimensions(line_position_top, line_size))
  194. {
  195. const Vector2f margin_size = element->GetBox().GetSize(BoxArea::Margin);
  196. const Style::Float float_property = element->GetComputedValues().float_();
  197. const Style::Clear clear_property = element->GetComputedValues().clear();
  198. float available_width = 0.f;
  199. const Vector2f float_position =
  200. space->NextFloatPosition(this, available_width, line_position_top, margin_size, float_property, clear_property);
  201. const float line_position_bottom = line_position_top + line_size.y;
  202. const float line_and_element_width = margin_size.x + line_size.x;
  203. // If the float can be positioned on the open line, and it can fit next to the line's contents, place it now.
  204. if (float_position.y < line_position_bottom && line_and_element_width <= available_width)
  205. {
  206. PlaceFloat(element, line_position_top, visible_overflow_size);
  207. inline_container->UpdateOpenLineBoxPlacement();
  208. float_placed = true;
  209. }
  210. }
  211. if (!float_placed)
  212. queued_float_elements.push_back({element, visible_overflow_size});
  213. }
  214. else
  215. {
  216. // There is no inline container, so just place it!
  217. const Vector2f box_position = NextBoxPosition();
  218. PlaceFloat(element, box_position.y, visible_overflow_size);
  219. }
  220. if (element->GetPosition() == Style::Position::Relative)
  221. AddRelativeElement(element);
  222. }
  223. Vector2f BlockContainer::GetOpenStaticPosition(Style::Display display) const
  224. {
  225. // Estimate the next box as if it had static position (10.6.4). If the element is inline-level, position it on the
  226. // open line if we have one. Otherwise, block-level elements are positioned on a hypothetical next line.
  227. Vector2f static_position = NextBoxPosition();
  228. if (const InlineContainer* inline_container = GetOpenInlineContainer())
  229. {
  230. const bool inline_level_element = (display == Style::Display::Inline || display == Style::Display::InlineBlock);
  231. static_position += inline_container->GetStaticPositionEstimate(inline_level_element);
  232. }
  233. return static_position;
  234. }
  235. Vector2f BlockContainer::NextBoxPosition() const
  236. {
  237. Vector2f box_position = position + box.GetPosition();
  238. box_position.y += box_cursor;
  239. return box_position;
  240. }
  241. Vector2f BlockContainer::NextBoxPosition(const Box& child_box, Style::Clear clear_property) const
  242. {
  243. const float child_top_margin = child_box.GetEdge(BoxArea::Margin, BoxEdge::Top);
  244. Vector2f box_position = NextBoxPosition();
  245. box_position.x += child_box.GetEdge(BoxArea::Margin, BoxEdge::Left);
  246. box_position.y += child_top_margin;
  247. float clear_margin = space->DetermineClearPosition(box_position.y, clear_property) - box_position.y;
  248. if (clear_margin > 0.f)
  249. {
  250. box_position.y += clear_margin;
  251. }
  252. else if (const LayoutBox* block_box = GetOpenLayoutBox())
  253. {
  254. // Check for a collapsing vertical margin with our last child, which will be vertically adjacent to the new box.
  255. if (const Box* open_box = block_box->GetIfBox())
  256. {
  257. const float open_bottom_margin = open_box->GetEdge(BoxArea::Margin, BoxEdge::Bottom);
  258. const float margin_sum = open_bottom_margin + child_top_margin;
  259. // The collapsed margin size depends on the sign of each margin, according to CSS behavior. The margins have
  260. // already been added to the 'box_position', so subtract their sum as needed.
  261. const int num_negative_margins = int(child_top_margin < 0.f) + int(open_bottom_margin < 0.f);
  262. switch (num_negative_margins)
  263. {
  264. case 0:
  265. // Use the largest margin.
  266. box_position.y += Math::Max(child_top_margin, open_bottom_margin) - margin_sum;
  267. break;
  268. case 1:
  269. // Use the sum of the positive and negative margin. These are already added to the position, so do nothing.
  270. break;
  271. case 2:
  272. // Use the most negative margin.
  273. box_position.y += Math::Min(child_top_margin, open_bottom_margin) - margin_sum;
  274. break;
  275. }
  276. }
  277. }
  278. return box_position;
  279. }
  280. void BlockContainer::PlaceQueuedFloats(float vertical_position)
  281. {
  282. if (!queued_float_elements.empty())
  283. {
  284. for (QueuedFloat entry : queued_float_elements)
  285. PlaceFloat(entry.element, vertical_position, entry.visible_overflow_size);
  286. queued_float_elements.clear();
  287. }
  288. }
  289. float BlockContainer::GetShrinkToFitWidth() const
  290. {
  291. auto& computed = element->GetComputedValues();
  292. float content_width = 0.0f;
  293. if (computed.width().type == Style::Width::Length)
  294. {
  295. // We have a definite width, so use that size.
  296. content_width = box.GetSize().x;
  297. }
  298. else
  299. {
  300. // Nope, then use the largest outer shrink-to-fit width of our children. Percentage sizing would be relative to
  301. // our containing block width and is treated just like 'auto' in this context.
  302. for (const auto& block_box : child_boxes)
  303. {
  304. const float child_inner_width = block_box->GetShrinkToFitWidth();
  305. float child_edges_width = 0.f;
  306. if (const Box* child_box = block_box->GetIfBox())
  307. child_edges_width = child_box->GetSizeAcross(BoxDirection::Horizontal, BoxArea::Margin, BoxArea::Padding);
  308. content_width = Math::Max(content_width, child_edges_width + child_inner_width);
  309. }
  310. }
  311. if (root_space)
  312. {
  313. // Since we are the root of the block formatting context, add the width contributions of the floated boxes in
  314. // our context. The basic algorithm used can produce overestimates, since floats may not be located next to the
  315. // rest of the content.
  316. const float edge_left = box.GetPosition().x;
  317. const float edge_right = edge_left + box.GetSize().x;
  318. content_width += space->GetShrinkToFitWidth(edge_left, edge_right);
  319. }
  320. float min_width, max_width;
  321. LayoutDetails::GetMinMaxWidth(min_width, max_width, computed, box, 0.f);
  322. content_width = Math::Clamp(content_width, min_width, max_width);
  323. return content_width;
  324. }
  325. const Box* BlockContainer::GetIfBox() const
  326. {
  327. return &box;
  328. }
  329. Element* BlockContainer::GetElement() const
  330. {
  331. return element;
  332. }
  333. const FloatedBoxSpace* BlockContainer::GetBlockBoxSpace() const
  334. {
  335. return space;
  336. }
  337. Vector2f BlockContainer::GetPosition() const
  338. {
  339. return position;
  340. }
  341. Box& BlockContainer::GetBox()
  342. {
  343. return box;
  344. }
  345. const Box& BlockContainer::GetBox() const
  346. {
  347. return box;
  348. }
  349. void BlockContainer::ResetContents()
  350. {
  351. RMLUI_ZoneScopedC(0xDD3322);
  352. if (root_space)
  353. root_space->Reset();
  354. child_boxes.clear();
  355. queued_float_elements.clear();
  356. box_cursor = 0;
  357. interrupted_line_box.reset();
  358. inner_content_size = {};
  359. }
  360. String BlockContainer::DebugDumpTree(int depth) const
  361. {
  362. String value = String(depth * 2, ' ') + "BlockContainer" + " | " + LayoutDetails::GetDebugElementName(element) + '\n';
  363. for (auto&& block_box : child_boxes)
  364. value += block_box->DumpLayoutTree(depth + 1);
  365. return value;
  366. }
  367. InlineContainer* BlockContainer::GetOpenInlineContainer()
  368. {
  369. return const_cast<InlineContainer*>(static_cast<const BlockContainer&>(*this).GetOpenInlineContainer());
  370. }
  371. const InlineContainer* BlockContainer::GetOpenInlineContainer() const
  372. {
  373. if (!child_boxes.empty() && child_boxes.back()->GetType() == Type::InlineContainer)
  374. return rmlui_static_cast<InlineContainer*>(child_boxes.back().get());
  375. return nullptr;
  376. }
  377. InlineContainer* BlockContainer::EnsureOpenInlineContainer()
  378. {
  379. // First check to see if we already have an open inline container.
  380. InlineContainer* inline_container = GetOpenInlineContainer();
  381. // Otherwise, we open a new one.
  382. if (!inline_container)
  383. {
  384. const float scrollbar_width = (IsScrollContainer() ? element->GetElementScroll()->GetScrollbarSize(ElementScroll::VERTICAL) : 0.f);
  385. const float available_width = box.GetSize().x - scrollbar_width;
  386. auto inline_container_ptr = MakeUnique<InlineContainer>(this, available_width);
  387. inline_container = inline_container_ptr.get();
  388. child_boxes.push_back(std::move(inline_container_ptr));
  389. if (interrupted_line_box)
  390. {
  391. inline_container->AddChainedBox(std::move(interrupted_line_box));
  392. interrupted_line_box.reset();
  393. }
  394. }
  395. return inline_container;
  396. }
  397. const LayoutBox* BlockContainer::GetOpenLayoutBox() const
  398. {
  399. if (!child_boxes.empty())
  400. return child_boxes.back().get();
  401. return nullptr;
  402. }
  403. bool BlockContainer::CloseOpenInlineContainer()
  404. {
  405. if (InlineContainer* inline_container = GetOpenInlineContainer())
  406. {
  407. EnsureEmptyInterruptedLineBox();
  408. Vector2f child_position;
  409. float child_height = 0.f;
  410. inline_container->Close(&interrupted_line_box, child_position, child_height);
  411. // Increment our cursor. If this close fails, it means this block container generated an automatic scrollbar.
  412. if (!EncloseChildBox(inline_container, child_position, child_height, 0.f))
  413. return false;
  414. }
  415. return true;
  416. }
  417. void BlockContainer::EnsureEmptyInterruptedLineBox()
  418. {
  419. if (interrupted_line_box)
  420. {
  421. RMLUI_ERROR; // Internal error: Interrupted line box leaked.
  422. interrupted_line_box.reset();
  423. }
  424. }
  425. void BlockContainer::PlaceFloat(Element* element, float vertical_position, Vector2f visible_overflow_size)
  426. {
  427. const Box& element_box = element->GetBox();
  428. const Vector2f border_size = element_box.GetSize(BoxArea::Border);
  429. visible_overflow_size = Math::Max(border_size, visible_overflow_size);
  430. const Vector2f margin_top_left = {element_box.GetEdge(BoxArea::Margin, BoxEdge::Left), element_box.GetEdge(BoxArea::Margin, BoxEdge::Top)};
  431. const Vector2f margin_bottom_right = {element_box.GetEdge(BoxArea::Margin, BoxEdge::Right),
  432. element_box.GetEdge(BoxArea::Margin, BoxEdge::Bottom)};
  433. const Vector2f margin_size = border_size + margin_top_left + margin_bottom_right;
  434. Style::Float float_property = element->GetComputedValues().float_();
  435. Style::Clear clear_property = element->GetComputedValues().clear();
  436. float unused_box_width = 0.f;
  437. const Vector2f margin_position = space->NextFloatPosition(this, unused_box_width, vertical_position, margin_size, float_property, clear_property);
  438. const Vector2f border_position = margin_position + margin_top_left;
  439. space->PlaceFloat(float_property, margin_position, margin_size, border_position, visible_overflow_size);
  440. // Shift the offset into this container's space, which acts as the float element's containing block.
  441. element->SetOffset(border_position - position, GetElement());
  442. }
  443. bool BlockContainer::GetBaselineOfLastLine(float& out_baseline) const
  444. {
  445. // Return the baseline of our last child that itself has a baseline.
  446. for (int i = (int)child_boxes.size() - 1; i >= 0; i--)
  447. {
  448. if (child_boxes[i]->GetBaselineOfLastLine(out_baseline))
  449. return true;
  450. }
  451. return false;
  452. }
  453. } // namespace Rml