Box.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. *
  8. * 1. Redistributions of source code must retain the above copyright notice, this
  9. * list of conditions and the following disclaimer.
  10. *
  11. * 2. Redistributions in binary form must reproduce the above copyright notice,
  12. * this list of conditions and the following disclaimer in the documentation
  13. * and/or other materials provided with the distribution.
  14. *
  15. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  16. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  17. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  18. * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  19. * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  20. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  21. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  22. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  23. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  24. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25. */
  26. #include <LibGUI/Painter.h>
  27. #include <LibWeb/DOM/Document.h>
  28. #include <LibWeb/HTML/HTMLBodyElement.h>
  29. #include <LibWeb/Layout/BlockBox.h>
  30. #include <LibWeb/Layout/Box.h>
  31. #include <LibWeb/Page/Frame.h>
  32. namespace Web::Layout {
  33. void Box::paint_border(PaintContext& context, Edge edge, const Gfx::FloatRect& rect, const BorderData& border_data)
  34. {
  35. float width = border_data.width;
  36. if (width <= 0)
  37. return;
  38. auto color = border_data.color;
  39. auto border_style = border_data.line_style;
  40. int int_width = max((int)width, 1);
  41. struct Points {
  42. Gfx::FloatPoint p1;
  43. Gfx::FloatPoint p2;
  44. };
  45. auto points_for_edge = [](Edge edge, const Gfx::FloatRect& rect) -> Points {
  46. switch (edge) {
  47. case Edge::Top:
  48. return { rect.top_left(), rect.top_right() };
  49. case Edge::Right:
  50. return { rect.top_right(), rect.bottom_right() };
  51. case Edge::Bottom:
  52. return { rect.bottom_left(), rect.bottom_right() };
  53. default: // Edge::Left
  54. return { rect.top_left(), rect.bottom_left() };
  55. }
  56. };
  57. auto [p1, p2] = points_for_edge(edge, rect);
  58. if (border_style == CSS::LineStyle::Inset) {
  59. auto top_left_color = Color::from_rgb(0x5a5a5a);
  60. auto bottom_right_color = Color::from_rgb(0x888888);
  61. color = (edge == Edge::Left || edge == Edge::Top) ? top_left_color : bottom_right_color;
  62. } else if (border_style == CSS::LineStyle::Outset) {
  63. auto top_left_color = Color::from_rgb(0x888888);
  64. auto bottom_right_color = Color::from_rgb(0x5a5a5a);
  65. color = (edge == Edge::Left || edge == Edge::Top) ? top_left_color : bottom_right_color;
  66. }
  67. auto gfx_line_style = Gfx::Painter::LineStyle::Solid;
  68. if (border_style == CSS::LineStyle::Dotted)
  69. gfx_line_style = Gfx::Painter::LineStyle::Dotted;
  70. if (border_style == CSS::LineStyle::Dashed)
  71. gfx_line_style = Gfx::Painter::LineStyle::Dashed;
  72. if (gfx_line_style != Gfx::Painter::LineStyle::Solid) {
  73. switch (edge) {
  74. case Edge::Top:
  75. p1.move_by(int_width / 2, int_width / 2);
  76. p2.move_by(-int_width / 2, int_width / 2);
  77. break;
  78. case Edge::Right:
  79. p1.move_by(-int_width / 2, int_width / 2);
  80. p2.move_by(-int_width / 2, -int_width / 2);
  81. break;
  82. case Edge::Bottom:
  83. p1.move_by(int_width / 2, -int_width / 2);
  84. p2.move_by(-int_width / 2, -int_width / 2);
  85. break;
  86. case Edge::Left:
  87. p1.move_by(int_width / 2, int_width / 2);
  88. p2.move_by(int_width / 2, -int_width / 2);
  89. break;
  90. }
  91. context.painter().draw_line({ (int)p1.x(), (int)p1.y() }, { (int)p2.x(), (int)p2.y() }, color, int_width, gfx_line_style);
  92. return;
  93. }
  94. auto draw_line = [&](auto& p1, auto& p2) {
  95. context.painter().draw_line({ (int)p1.x(), (int)p1.y() }, { (int)p2.x(), (int)p2.y() }, color, 1, gfx_line_style);
  96. };
  97. float p1_step = 0;
  98. float p2_step = 0;
  99. switch (edge) {
  100. case Edge::Top:
  101. p1_step = style().border_left().width / (float)int_width;
  102. p2_step = style().border_right().width / (float)int_width;
  103. for (int i = 0; i < int_width; ++i) {
  104. draw_line(p1, p2);
  105. p1.move_by(p1_step, 1);
  106. p2.move_by(-p2_step, 1);
  107. }
  108. break;
  109. case Edge::Right:
  110. p1_step = style().border_top().width / (float)int_width;
  111. p2_step = style().border_bottom().width / (float)int_width;
  112. for (int i = int_width - 1; i >= 0; --i) {
  113. draw_line(p1, p2);
  114. p1.move_by(-1, p1_step);
  115. p2.move_by(-1, -p2_step);
  116. }
  117. break;
  118. case Edge::Bottom:
  119. p1_step = style().border_left().width / (float)int_width;
  120. p2_step = style().border_right().width / (float)int_width;
  121. for (int i = int_width - 1; i >= 0; --i) {
  122. draw_line(p1, p2);
  123. p1.move_by(p1_step, -1);
  124. p2.move_by(-p2_step, -1);
  125. }
  126. break;
  127. case Edge::Left:
  128. p1_step = style().border_top().width / (float)int_width;
  129. p2_step = style().border_bottom().width / (float)int_width;
  130. for (int i = 0; i < int_width; ++i) {
  131. draw_line(p1, p2);
  132. p1.move_by(1, p1_step);
  133. p2.move_by(1, -p2_step);
  134. }
  135. break;
  136. }
  137. }
  138. void Box::paint(PaintContext& context, PaintPhase phase)
  139. {
  140. if (!is_visible())
  141. return;
  142. Gfx::PainterStateSaver saver(context.painter());
  143. if (is_fixed_position())
  144. context.painter().translate(context.scroll_offset());
  145. Gfx::FloatRect padded_rect;
  146. padded_rect.set_x(absolute_x() - box_model().padding.left.to_px(*this));
  147. padded_rect.set_width(width() + box_model().padding.left.to_px(*this) + box_model().padding.right.to_px(*this));
  148. padded_rect.set_y(absolute_y() - box_model().padding.top.to_px(*this));
  149. padded_rect.set_height(height() + box_model().padding.top.to_px(*this) + box_model().padding.bottom.to_px(*this));
  150. if (phase == PaintPhase::Background && !is_body()) {
  151. // FIXME: We should paint the body here too, but that currently happens at the view layer.
  152. auto bgcolor = specified_style().property(CSS::PropertyID::BackgroundColor);
  153. if (bgcolor.has_value() && bgcolor.value()->is_color()) {
  154. context.painter().fill_rect(enclosing_int_rect(padded_rect), bgcolor.value()->to_color(document()));
  155. }
  156. auto bgimage = specified_style().property(CSS::PropertyID::BackgroundImage);
  157. if (bgimage.has_value() && bgimage.value()->is_image()) {
  158. auto& image_value = static_cast<const CSS::ImageStyleValue&>(*bgimage.value());
  159. if (image_value.bitmap()) {
  160. context.painter().draw_tiled_bitmap(enclosing_int_rect(padded_rect), *image_value.bitmap());
  161. }
  162. }
  163. }
  164. if (phase == PaintPhase::Border) {
  165. Gfx::FloatRect bordered_rect;
  166. bordered_rect.set_x(padded_rect.x() - box_model().border.left.to_px(*this));
  167. bordered_rect.set_width(padded_rect.width() + box_model().border.left.to_px(*this) + box_model().border.right.to_px(*this));
  168. bordered_rect.set_y(padded_rect.y() - box_model().border.top.to_px(*this));
  169. bordered_rect.set_height(padded_rect.height() + box_model().border.top.to_px(*this) + box_model().border.bottom.to_px(*this));
  170. paint_border(context, Edge::Left, bordered_rect, style().border_left());
  171. paint_border(context, Edge::Right, bordered_rect, style().border_right());
  172. paint_border(context, Edge::Top, bordered_rect, style().border_top());
  173. paint_border(context, Edge::Bottom, bordered_rect, style().border_bottom());
  174. }
  175. Layout::NodeWithStyleAndBoxModelMetrics::paint(context, phase);
  176. if (phase == PaintPhase::Overlay && dom_node() && document().inspected_node() == dom_node()) {
  177. auto content_rect = absolute_rect();
  178. auto margin_box = box_model().margin_box(*this);
  179. Gfx::FloatRect margin_rect;
  180. margin_rect.set_x(absolute_x() - margin_box.left);
  181. margin_rect.set_width(width() + margin_box.left + margin_box.right);
  182. margin_rect.set_y(absolute_y() - margin_box.top);
  183. margin_rect.set_height(height() + margin_box.top + margin_box.bottom);
  184. context.painter().draw_rect(enclosing_int_rect(margin_rect), Color::Yellow);
  185. context.painter().draw_rect(enclosing_int_rect(padded_rect), Color::Cyan);
  186. context.painter().draw_rect(enclosing_int_rect(content_rect), Color::Magenta);
  187. }
  188. if (phase == PaintPhase::FocusOutline && dom_node() && dom_node()->is_element() && downcast<DOM::Element>(*dom_node()).is_focused()) {
  189. context.painter().draw_rect(enclosing_int_rect(absolute_rect()), context.palette().focus_outline());
  190. }
  191. }
  192. HitTestResult Box::hit_test(const Gfx::IntPoint& position, HitTestType type) const
  193. {
  194. // FIXME: It would be nice if we could confidently skip over hit testing
  195. // parts of the layout tree, but currently we can't just check
  196. // m_rect.contains() since inline text rects can't be trusted..
  197. HitTestResult result { absolute_rect().contains(position.x(), position.y()) ? this : nullptr };
  198. for_each_child([&](auto& child) {
  199. if (is<Box>(child) && downcast<Box>(child).stacking_context())
  200. return;
  201. auto child_result = child.hit_test(position, type);
  202. if (child_result.layout_node)
  203. result = child_result;
  204. });
  205. return result;
  206. }
  207. void Box::set_needs_display()
  208. {
  209. if (!is_inline()) {
  210. frame().set_needs_display(enclosing_int_rect(absolute_rect()));
  211. return;
  212. }
  213. Node::set_needs_display();
  214. }
  215. bool Box::is_body() const
  216. {
  217. return dom_node() && dom_node() == document().body();
  218. }
  219. void Box::set_offset(const Gfx::FloatPoint& offset)
  220. {
  221. if (m_offset == offset)
  222. return;
  223. m_offset = offset;
  224. did_set_rect();
  225. }
  226. void Box::set_size(const Gfx::FloatSize& size)
  227. {
  228. if (m_size == size)
  229. return;
  230. m_size = size;
  231. did_set_rect();
  232. }
  233. Gfx::FloatPoint Box::effective_offset() const
  234. {
  235. if (m_containing_line_box_fragment)
  236. return m_containing_line_box_fragment->offset();
  237. return m_offset;
  238. }
  239. const Gfx::FloatRect Box::absolute_rect() const
  240. {
  241. Gfx::FloatRect rect { effective_offset(), size() };
  242. for (auto* block = containing_block(); block; block = block->containing_block()) {
  243. rect.move_by(block->effective_offset());
  244. }
  245. return rect;
  246. }
  247. void Box::set_containing_line_box_fragment(LineBoxFragment& fragment)
  248. {
  249. m_containing_line_box_fragment = fragment.make_weak_ptr();
  250. }
  251. StackingContext* Box::enclosing_stacking_context()
  252. {
  253. for (auto* ancestor = parent(); ancestor; ancestor = ancestor->parent()) {
  254. if (!ancestor->is_box())
  255. continue;
  256. auto& ancestor_box = downcast<Box>(*ancestor);
  257. if (!ancestor_box.establishes_stacking_context())
  258. continue;
  259. ASSERT(ancestor_box.stacking_context());
  260. return ancestor_box.stacking_context();
  261. }
  262. // We should always reach the Layout::InitialContainingBlockBox stacking context.
  263. ASSERT_NOT_REACHED();
  264. }
  265. bool Box::establishes_stacking_context() const
  266. {
  267. if (!has_style())
  268. return false;
  269. if (dom_node() == document().root())
  270. return true;
  271. auto position = style().position();
  272. auto z_index = style().z_index();
  273. if (position == CSS::Position::Absolute || position == CSS::Position::Relative) {
  274. if (z_index.has_value())
  275. return true;
  276. }
  277. if (position == CSS::Position::Fixed || position == CSS::Position::Sticky)
  278. return true;
  279. return false;
  280. }
  281. LineBox& Box::ensure_last_line_box()
  282. {
  283. if (m_line_boxes.is_empty())
  284. return add_line_box();
  285. return m_line_boxes.last();
  286. }
  287. LineBox& Box::add_line_box()
  288. {
  289. m_line_boxes.append(LineBox());
  290. return m_line_boxes.last();
  291. }
  292. float Box::width_of_logical_containing_block() const
  293. {
  294. auto* containing_block = this->containing_block();
  295. ASSERT(containing_block);
  296. return containing_block->width();
  297. }
  298. }