diff --git a/Libraries/LibWeb/Frame.cpp b/Libraries/LibWeb/Frame.cpp index 0a9182ebf2..e1d93354e5 100644 --- a/Libraries/LibWeb/Frame.cpp +++ b/Libraries/LibWeb/Frame.cpp @@ -26,8 +26,9 @@ #include #include -#include #include +#include +#include namespace Web { @@ -87,10 +88,8 @@ void Frame::did_scroll(Badge) return; if (!m_document->layout_node()) return; - m_document->layout_node()->for_each_in_subtree([&](LayoutNode& layout_node) { - if (layout_node.is_widget()) { - layout_node.layout(LayoutNode::LayoutMode::Default); - } + m_document->layout_node()->for_each_in_subtree_of_type([&](auto& layout_widget) { + layout_widget.update_widget(); return IterationDecision::Continue; }); } diff --git a/Libraries/LibWeb/Layout/LayoutBlock.cpp b/Libraries/LibWeb/Layout/LayoutBlock.cpp index 6886fb3671..39e3fd17c9 100644 --- a/Libraries/LibWeb/Layout/LayoutBlock.cpp +++ b/Libraries/LibWeb/Layout/LayoutBlock.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include namespace Web { @@ -83,12 +84,14 @@ void LayoutBlock::layout_block_children(LayoutMode layout_mode) return; auto& child_block = static_cast(child); child_block.layout(layout_mode); - content_height = child_block.rect().bottom() + child_block.box_model().full_margin().bottom - rect().top(); + + if (!child_block.is_absolutely_positioned()) + content_height = child_block.rect().bottom() + child_block.box_model().full_margin().bottom - rect().top(); }); if (layout_mode != LayoutMode::Default) { float max_width = 0; for_each_child([&](auto& child) { - if (child.is_box()) + if (child.is_box() && !child.is_absolutely_positioned()) max_width = max(max_width, to(child).width()); }); rect().set_width(max_width); @@ -164,6 +167,10 @@ void LayoutBlock::layout_inline_children(LayoutMode layout_mode) for (size_t i = 0; i < line_box.fragments().size(); ++i) { auto& fragment = line_box.fragments()[i]; + + if (fragment.layout_node().is_absolutely_positioned()) + continue; + // Vertically align everyone's bottom to the line. // FIXME: Support other kinds of vertical alignment. fragment.rect().set_x(roundf(x_offset + fragment.rect().x())); diff --git a/Libraries/LibWeb/Layout/LayoutBox.cpp b/Libraries/LibWeb/Layout/LayoutBox.cpp index 3de1fa3584..1095a94ff9 100644 --- a/Libraries/LibWeb/Layout/LayoutBox.cpp +++ b/Libraries/LibWeb/Layout/LayoutBox.cpp @@ -272,4 +272,12 @@ bool LayoutBox::is_body() const return node() && node() == document().body(); } +void LayoutBox::set_rect(const Gfx::FloatRect& rect) +{ + if (m_rect == rect) + return; + m_rect = rect; + did_set_rect(); +} + } diff --git a/Libraries/LibWeb/Layout/LayoutBox.h b/Libraries/LibWeb/Layout/LayoutBox.h index ced083e3dc..6101d29077 100644 --- a/Libraries/LibWeb/Layout/LayoutBox.h +++ b/Libraries/LibWeb/Layout/LayoutBox.h @@ -35,7 +35,7 @@ class LayoutBox : public LayoutNodeWithStyleAndBoxModelMetrics { public: const Gfx::FloatRect& rect() const { return m_rect; } Gfx::FloatRect& rect() { return m_rect; } - void set_rect(const Gfx::FloatRect& rect) { m_rect = rect; } + void set_rect(const Gfx::FloatRect&); float x() const { return rect().x(); } float y() const { return rect().y(); } @@ -57,6 +57,8 @@ protected: virtual void render(RenderingContext&) override; + virtual void did_set_rect() {} + private: virtual bool is_box() const override { return true; } diff --git a/Libraries/LibWeb/Layout/LayoutImage.cpp b/Libraries/LibWeb/Layout/LayoutImage.cpp index f386372d80..5e5109233d 100644 --- a/Libraries/LibWeb/Layout/LayoutImage.cpp +++ b/Libraries/LibWeb/Layout/LayoutImage.cpp @@ -43,8 +43,10 @@ LayoutImage::~LayoutImage() void LayoutImage::layout(LayoutMode layout_mode) { if (node().preferred_width() && node().preferred_height()) { - rect().set_width(node().preferred_width()); - rect().set_height(node().preferred_height()); + set_has_intrinsic_width(true); + set_has_intrinsic_height(true); + set_intrinsic_width(node().preferred_width()); + set_intrinsic_height(node().preferred_height()); } else if (renders_as_alt_text()) { auto& font = Gfx::Font::default_font(); auto alt = node().alt(); diff --git a/Libraries/LibWeb/Layout/LayoutNode.cpp b/Libraries/LibWeb/Layout/LayoutNode.cpp index cbcfd4d7f6..e838162a4b 100644 --- a/Libraries/LibWeb/Layout/LayoutNode.cpp +++ b/Libraries/LibWeb/Layout/LayoutNode.cpp @@ -62,12 +62,15 @@ bool LayoutNode::can_contain_boxes_with_position_absolute() const const LayoutBlock* LayoutNode::containing_block() const { - if (is_text()) { + auto nearest_block_ancestor = [this] { auto* ancestor = parent(); - while (ancestor && ((ancestor->is_inline() && !is(*ancestor)) || !is(*ancestor))) + while (ancestor && !is(*ancestor)) ancestor = ancestor->parent(); return to(ancestor); - } + }; + + if (is_text()) + return nearest_block_ancestor(); if (is_absolutely_positioned()) { auto* ancestor = parent(); @@ -81,10 +84,7 @@ const LayoutBlock* LayoutNode::containing_block() const if (style().position() == CSS::Position::Fixed) return &root(); - auto* ancestor = parent(); - while (ancestor && ((ancestor->is_inline() && !is(*ancestor)) || !is(*ancestor))) - ancestor = ancestor->parent(); - return to(ancestor); + return nearest_block_ancestor(); } void LayoutNode::render(RenderingContext& context) @@ -185,5 +185,4 @@ bool LayoutNode::is_absolutely_positioned() const return style().position() == CSS::Position::Absolute; } - } diff --git a/Libraries/LibWeb/Layout/LayoutReplaced.cpp b/Libraries/LibWeb/Layout/LayoutReplaced.cpp index f4cddfcb86..1698697b5f 100644 --- a/Libraries/LibWeb/Layout/LayoutReplaced.cpp +++ b/Libraries/LibWeb/Layout/LayoutReplaced.cpp @@ -41,6 +41,136 @@ LayoutReplaced::~LayoutReplaced() { } +float LayoutReplaced::calculate_width() const +{ + // 10.3.2 [Inline,] replaced elements + + auto& style = this->style(); + auto auto_value = Length(); + auto zero_value = Length(0, Length::Type::Absolute); + auto& containing_block = *this->containing_block(); + + auto margin_left = style.length_or_fallback(CSS::PropertyID::MarginLeft, zero_value, containing_block.width()); + auto margin_right = style.length_or_fallback(CSS::PropertyID::MarginRight, zero_value, containing_block.width()); + + // A computed value of 'auto' for 'margin-left' or 'margin-right' becomes a used value of '0'. + if (margin_left.is_auto()) + margin_left = zero_value; + if (margin_right.is_auto()) + margin_right = zero_value; + + auto specified_width = style.length_or_fallback(CSS::PropertyID::Width, auto_value, containing_block.width()); + auto specified_height = style.length_or_fallback(CSS::PropertyID::Height, auto_value, containing_block.height()); + + // FIXME: Actually compute 'width' + auto computed_width = specified_width; + + float used_width = specified_width.to_px(); + + // If 'height' and 'width' both have computed values of 'auto' and the element also has an intrinsic width, + // then that intrinsic width is the used value of 'width'. + if (specified_height.is_auto() && specified_width.is_auto() && has_intrinsic_width()) { + used_width = intrinsic_width(); + } + + // If 'height' and 'width' both have computed values of 'auto' and the element has no intrinsic width, + // but does have an intrinsic height and intrinsic ratio; + // or if 'width' has a computed value of 'auto', + // 'height' has some other computed value, and the element does have an intrinsic ratio; then the used value of 'width' is: + // + // (used height) * (intrinsic ratio) + else if ((specified_height.is_auto() && specified_width.is_auto() && !has_intrinsic_width() && has_intrinsic_height() && has_intrinsic_ratio()) || computed_width.is_auto()) { + used_width = calculate_height() * intrinsic_ratio(); + } + + else if (computed_width.is_auto() && has_intrinsic_width()) { + used_width = intrinsic_width(); + } + + else if (computed_width.is_auto()) { + used_width = 300; + } + + return used_width; +} + +float LayoutReplaced::calculate_height() const +{ + // 10.6.2 Inline replaced elements, block-level replaced elements in normal flow, + // 'inline-block' replaced elements in normal flow and floating replaced elements + auto& style = this->style(); + auto auto_value = Length(); + auto zero_value = Length(0, Length::Type::Absolute); + auto& containing_block = *this->containing_block(); + + auto margin_top = style.length_or_fallback(CSS::PropertyID::MarginTop, zero_value, containing_block.width()); + auto margin_bottom = style.length_or_fallback(CSS::PropertyID::MarginBottom, zero_value, containing_block.width()); + + auto specified_width = style.length_or_fallback(CSS::PropertyID::Width, auto_value, containing_block.width()); + auto specified_height = style.length_or_fallback(CSS::PropertyID::Height, auto_value, containing_block.height()); + + float used_height = specified_height.to_px(); + + // If 'height' and 'width' both have computed values of 'auto' and the element also has + // an intrinsic height, then that intrinsic height is the used value of 'height'. + if (specified_width.is_auto() && specified_height.is_auto() && has_intrinsic_height()) + used_height = intrinsic_height(); + else if (specified_height.is_auto() && has_intrinsic_ratio()) + used_height = calculate_width() * intrinsic_ratio(); + else if (specified_height.is_auto() && has_intrinsic_height()) + used_height = intrinsic_height(); + else if (specified_height.is_auto()) + used_height = 150; + + return used_height; +} + +Gfx::FloatPoint LayoutReplaced::calculate_position() +{ + auto& style = this->style(); + auto auto_value = Length(); + auto zero_value = Length(0, Length::Type::Absolute); + auto& containing_block = *this->containing_block(); + + auto width = style.length_or_fallback(CSS::PropertyID::Width, auto_value, containing_block.width()); + + if (style.position() == CSS::Position::Absolute) { + box_model().offset().top = style.length_or_fallback(CSS::PropertyID::Top, zero_value, containing_block.height()); + box_model().offset().right = style.length_or_fallback(CSS::PropertyID::Right, zero_value, containing_block.width()); + box_model().offset().bottom = style.length_or_fallback(CSS::PropertyID::Bottom, zero_value, containing_block.height()); + box_model().offset().left = style.length_or_fallback(CSS::PropertyID::Left, zero_value, containing_block.width()); + } + + box_model().margin().top = style.length_or_fallback(CSS::PropertyID::MarginTop, zero_value, containing_block.width()); + box_model().margin().bottom = style.length_or_fallback(CSS::PropertyID::MarginBottom, zero_value, containing_block.width()); + box_model().border().top = style.length_or_fallback(CSS::PropertyID::BorderTopWidth, zero_value); + box_model().border().bottom = style.length_or_fallback(CSS::PropertyID::BorderBottomWidth, zero_value); + box_model().padding().top = style.length_or_fallback(CSS::PropertyID::PaddingTop, zero_value, containing_block.width()); + box_model().padding().bottom = style.length_or_fallback(CSS::PropertyID::PaddingBottom, zero_value, containing_block.width()); + + float position_x = box_model().margin().left.to_px() + + box_model().border().left.to_px() + + box_model().padding().left.to_px() + + box_model().offset().left.to_px(); + + if (style.position() != CSS::Position::Absolute || containing_block.style().position() == CSS::Position::Absolute) + position_x += containing_block.x(); + + float position_y = box_model().full_margin().top + box_model().offset().top.to_px(); + + return { position_x, position_y }; +} + +void LayoutReplaced::layout(LayoutMode layout_mode) +{ + rect().set_width(calculate_width()); + rect().set_height(calculate_height()); + + LayoutBox::layout(layout_mode); + + rect().set_location(calculate_position()); +} + void LayoutReplaced::split_into_lines(LayoutBlock& container, LayoutMode layout_mode) { layout(layout_mode); diff --git a/Libraries/LibWeb/Layout/LayoutReplaced.h b/Libraries/LibWeb/Layout/LayoutReplaced.h index 2959c3bc36..99450b3a79 100644 --- a/Libraries/LibWeb/Layout/LayoutReplaced.h +++ b/Libraries/LibWeb/Layout/LayoutReplaced.h @@ -40,10 +40,39 @@ public: virtual bool is_replaced() const final { return true; } + bool has_intrinsic_width() const { return m_has_intrinsic_width; } + bool has_intrinsic_height() const { return m_has_intrinsic_height; } + bool has_intrinsic_ratio() const { return m_has_intrinsic_ratio; } + + float intrinsic_width() const { return m_intrinsic_width; } + float intrinsic_height() const { return m_intrinsic_height; } + float intrinsic_ratio() const { return m_intrinsic_ratio; } + + void set_has_intrinsic_width(bool has) { m_has_intrinsic_width = has; } + void set_has_intrinsic_height(bool has) { m_has_intrinsic_height = has; } + void set_has_intrinsic_ratio(bool has) { m_has_intrinsic_ratio = has; } + + void set_intrinsic_width(float width) { m_intrinsic_width = width; } + void set_intrinsic_height(float height) { m_intrinsic_height = height; } + void set_intrinsic_ratio(float ratio) { m_intrinsic_ratio = ratio; } + +protected: + virtual void layout(LayoutMode) override; + virtual void split_into_lines(LayoutBlock& container, LayoutMode) override; + private: + Gfx::FloatPoint calculate_position(); + float calculate_width() const; + float calculate_height() const; + virtual const char* class_name() const override { return "LayoutReplaced"; } - virtual void split_into_lines(LayoutBlock& container, LayoutMode) override; + bool m_has_intrinsic_width { false }; + bool m_has_intrinsic_height { false }; + bool m_has_intrinsic_ratio { false }; + float m_intrinsic_width { 0 }; + float m_intrinsic_height { 0 }; + float m_intrinsic_ratio { 0 }; }; template<> diff --git a/Libraries/LibWeb/Layout/LayoutWidget.cpp b/Libraries/LibWeb/Layout/LayoutWidget.cpp index 2d612d7843..f97f519c2a 100644 --- a/Libraries/LibWeb/Layout/LayoutWidget.cpp +++ b/Libraries/LibWeb/Layout/LayoutWidget.cpp @@ -49,9 +49,22 @@ LayoutWidget::~LayoutWidget() void LayoutWidget::layout(LayoutMode layout_mode) { - rect().set_size(widget().width(), widget().height()); - LayoutReplaced::layout(layout_mode); + set_has_intrinsic_width(true); + set_has_intrinsic_height(true); + set_intrinsic_width(widget().width()); + set_intrinsic_height(widget().height()); + LayoutReplaced::layout(layout_mode); +} + +void LayoutWidget::did_set_rect() +{ + LayoutReplaced::did_set_rect(); + update_widget(); +} + +void LayoutWidget::update_widget() +{ auto adjusted_widget_position = rect().location().to_int_point(); if (auto* page_view = document().frame()->page_view()) adjusted_widget_position.move_by(-page_view->horizontal_scrollbar().value(), -page_view->vertical_scrollbar().value()); diff --git a/Libraries/LibWeb/Layout/LayoutWidget.h b/Libraries/LibWeb/Layout/LayoutWidget.h index e11c31f5cd..6e3f3d0f09 100644 --- a/Libraries/LibWeb/Layout/LayoutWidget.h +++ b/Libraries/LibWeb/Layout/LayoutWidget.h @@ -30,7 +30,7 @@ namespace Web { -class LayoutWidget : public LayoutReplaced { +class LayoutWidget final : public LayoutReplaced { public: LayoutWidget(const Element&, GUI::Widget&); virtual ~LayoutWidget() override; @@ -43,9 +43,13 @@ public: virtual bool is_widget() const final { return true; } + void update_widget(); + private: virtual const char* class_name() const override { return "LayoutWidget"; } + virtual void did_set_rect() override; + NonnullRefPtr m_widget; };