Serenity Operating System
at hosted 244 lines 9.9 kB view raw
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 27#include <LibGUI/Painter.h> 28#include <LibWeb/DOM/Document.h> 29#include <LibWeb/DOM/HTMLBodyElement.h> 30#include <LibWeb/Frame.h> 31#include <LibWeb/Layout/LayoutBlock.h> 32#include <LibWeb/Layout/LayoutBox.h> 33 34//#define DRAW_BOXES_AROUND_LAYOUT_NODES 35//#define DRAW_BOXES_AROUND_HOVERED_NODES 36 37namespace Web { 38 39void LayoutBox::paint_border(RenderingContext& context, Edge edge, const Gfx::FloatRect& rect, CSS::PropertyID style_property_id, CSS::PropertyID color_property_id, CSS::PropertyID width_property_id) 40{ 41 auto border_width = style().property(width_property_id); 42 if (!border_width.has_value()) 43 return; 44 45 auto border_style = style().property(style_property_id); 46 float width = border_width.value()->to_length().to_px(); 47 48 int int_width = max((int)width, 1); 49 50 Color color; 51 auto border_color = style().property(color_property_id); 52 if (border_color.has_value()) { 53 color = border_color.value()->to_color(document()); 54 } else { 55 // FIXME: This is basically CSS "currentColor" which should be handled elsewhere 56 // in a much more reusable way. 57 auto current_color = style().property(CSS::PropertyID::Color); 58 if (current_color.has_value()) 59 color = current_color.value()->to_color(document()); 60 else 61 color = Color::Black; 62 } 63 64 auto first_point_for_edge = [](Edge edge, const Gfx::FloatRect& rect) { 65 switch (edge) { 66 case Edge::Top: 67 return rect.top_left(); 68 case Edge::Right: 69 return rect.top_right(); 70 case Edge::Bottom: 71 return rect.bottom_left(); 72 case Edge::Left: 73 default: 74 return rect.top_left(); 75 } 76 }; 77 78 auto second_point_for_edge = [](Edge edge, const Gfx::FloatRect& rect) { 79 switch (edge) { 80 case Edge::Top: 81 return rect.top_right(); 82 case Edge::Right: 83 return rect.bottom_right(); 84 case Edge::Bottom: 85 return rect.bottom_right(); 86 case Edge::Left: 87 default: 88 return rect.bottom_left(); 89 } 90 }; 91 92 auto p1 = first_point_for_edge(edge, rect); 93 auto p2 = second_point_for_edge(edge, rect); 94 95 if (border_style.has_value() && border_style.value()->to_string() == "inset") { 96 auto top_left_color = Color::from_rgb(0x5a5a5a); 97 auto bottom_right_color = Color::from_rgb(0x888888); 98 color = (edge == Edge::Left || edge == Edge::Top) ? top_left_color : bottom_right_color; 99 } else if (border_style.has_value() && border_style.value()->to_string() == "outset") { 100 auto top_left_color = Color::from_rgb(0x888888); 101 auto bottom_right_color = Color::from_rgb(0x5a5a5a); 102 color = (edge == Edge::Left || edge == Edge::Top) ? top_left_color : bottom_right_color; 103 } 104 105 bool dotted = border_style.has_value() && border_style.value()->to_string() == "dotted"; 106 107 auto draw_line = [&](auto& p1, auto& p2) { 108 context.painter().draw_line({ (int)p1.x(), (int)p1.y() }, { (int)p2.x(), (int)p2.y() }, color, 1, dotted); 109 }; 110 111 auto width_for = [&](CSS::PropertyID property_id) -> float { 112 auto width = style().property(property_id); 113 if (!width.has_value()) 114 return 0; 115 return width.value()->to_length().to_px(); 116 }; 117 118 float p1_step = 0; 119 float p2_step = 0; 120 121 switch (edge) { 122 case Edge::Top: 123 p1_step = width_for(CSS::PropertyID::BorderLeftWidth) / (float)int_width; 124 p2_step = width_for(CSS::PropertyID::BorderRightWidth) / (float)int_width; 125 for (int i = 0; i < int_width; ++i) { 126 draw_line(p1, p2); 127 p1.move_by(p1_step, 1); 128 p2.move_by(-p2_step, 1); 129 } 130 break; 131 case Edge::Right: 132 p1_step = width_for(CSS::PropertyID::BorderTopWidth) / (float)int_width; 133 p2_step = width_for(CSS::PropertyID::BorderBottomWidth) / (float)int_width; 134 for (int i = int_width - 1; i >= 0; --i) { 135 draw_line(p1, p2); 136 p1.move_by(-1, p1_step); 137 p2.move_by(-1, -p2_step); 138 } 139 break; 140 case Edge::Bottom: 141 p1_step = width_for(CSS::PropertyID::BorderLeftWidth) / (float)int_width; 142 p2_step = width_for(CSS::PropertyID::BorderRightWidth) / (float)int_width; 143 for (int i = int_width - 1; i >= 0; --i) { 144 draw_line(p1, p2); 145 p1.move_by(p1_step, -1); 146 p2.move_by(-p2_step, -1); 147 } 148 break; 149 case Edge::Left: 150 p1_step = width_for(CSS::PropertyID::BorderTopWidth) / (float)int_width; 151 p2_step = width_for(CSS::PropertyID::BorderBottomWidth) / (float)int_width; 152 for (int i = 0; i < int_width; ++i) { 153 draw_line(p1, p2); 154 p1.move_by(1, p1_step); 155 p2.move_by(1, -p2_step); 156 } 157 break; 158 } 159} 160 161void LayoutBox::render(RenderingContext& context) 162{ 163 if (!is_visible()) 164 return; 165 166#ifdef DRAW_BOXES_AROUND_LAYOUT_NODES 167 context.painter().draw_rect(m_rect, Color::Blue); 168#endif 169#ifdef DRAW_BOXES_AROUND_HOVERED_NODES 170 if (!is_anonymous() && node() == document().hovered_node()) 171 context.painter().draw_rect(m_rect, Color::Red); 172#endif 173 174 if (node() && document().inspected_node() == node()) 175 context.painter().draw_rect(enclosing_int_rect(m_rect), Color::Magenta); 176 177 Gfx::FloatRect padded_rect; 178 padded_rect.set_x(x() - box_model().padding().left.to_px()); 179 padded_rect.set_width(width() + box_model().padding().left.to_px() + box_model().padding().right.to_px()); 180 padded_rect.set_y(y() - box_model().padding().top.to_px()); 181 padded_rect.set_height(height() + box_model().padding().top.to_px() + box_model().padding().bottom.to_px()); 182 183 if (!is_body()) { 184 auto bgcolor = style().property(CSS::PropertyID::BackgroundColor); 185 if (bgcolor.has_value() && bgcolor.value()->is_color()) { 186 context.painter().fill_rect(enclosing_int_rect(padded_rect), bgcolor.value()->to_color(document())); 187 } 188 189 auto bgimage = style().property(CSS::PropertyID::BackgroundImage); 190 if (bgimage.has_value() && bgimage.value()->is_image()) { 191 auto& image_value = static_cast<const ImageStyleValue&>(*bgimage.value()); 192 if (image_value.bitmap()) { 193 context.painter().draw_tiled_bitmap(enclosing_int_rect(padded_rect), *image_value.bitmap()); 194 } 195 } 196 } 197 198 Gfx::FloatRect bordered_rect; 199 bordered_rect.set_x(padded_rect.x() - box_model().border().left.to_px()); 200 bordered_rect.set_width(padded_rect.width() + box_model().border().left.to_px() + box_model().border().right.to_px()); 201 bordered_rect.set_y(padded_rect.y() - box_model().border().top.to_px()); 202 bordered_rect.set_height(padded_rect.height() + box_model().border().top.to_px() + box_model().border().bottom.to_px()); 203 204 paint_border(context, Edge::Left, bordered_rect, CSS::PropertyID::BorderLeftStyle, CSS::PropertyID::BorderLeftColor, CSS::PropertyID::BorderLeftWidth); 205 paint_border(context, Edge::Right, bordered_rect, CSS::PropertyID::BorderRightStyle, CSS::PropertyID::BorderRightColor, CSS::PropertyID::BorderRightWidth); 206 paint_border(context, Edge::Top, bordered_rect, CSS::PropertyID::BorderTopStyle, CSS::PropertyID::BorderTopColor, CSS::PropertyID::BorderTopWidth); 207 paint_border(context, Edge::Bottom, bordered_rect, CSS::PropertyID::BorderBottomStyle, CSS::PropertyID::BorderBottomColor, CSS::PropertyID::BorderBottomWidth); 208 209 LayoutNodeWithStyleAndBoxModelMetrics::render(context); 210} 211 212HitTestResult LayoutBox::hit_test(const Gfx::Point& position) const 213{ 214 // FIXME: It would be nice if we could confidently skip over hit testing 215 // parts of the layout tree, but currently we can't just check 216 // m_rect.contains() since inline text rects can't be trusted.. 217 HitTestResult result { m_rect.contains(position.x(), position.y()) ? this : nullptr }; 218 for_each_child([&](auto& child) { 219 auto child_result = child.hit_test(position); 220 if (child_result.layout_node) 221 result = child_result; 222 }); 223 return result; 224} 225 226void LayoutBox::set_needs_display() 227{ 228 auto* frame = document().frame(); 229 ASSERT(frame); 230 231 if (!is_inline()) { 232 const_cast<Frame*>(frame)->set_needs_display(enclosing_int_rect(rect())); 233 return; 234 } 235 236 LayoutNode::set_needs_display(); 237} 238 239bool LayoutBox::is_body() const 240{ 241 return node() && node() == document().body(); 242} 243 244}