mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 01:07:35 +00:00
LibWeb: Start implementing proper layout of replaced elements
LayoutReplaced now has intrinsic width, height and ratio. Only some of the values may be present. The layout algorithm takes the various configurations into account per the CSS specification. This is still pretty immature but at least we're moving forward. :^)
This commit is contained in:
parent
7fcf61be35
commit
4d5ecf6e32
10 changed files with 215 additions and 22 deletions
|
@ -26,8 +26,9 @@
|
|||
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/Frame.h>
|
||||
#include <LibWeb/PageView.h>
|
||||
#include <LibWeb/Layout/LayoutDocument.h>
|
||||
#include <LibWeb/Layout/LayoutWidget.h>
|
||||
#include <LibWeb/PageView.h>
|
||||
|
||||
namespace Web {
|
||||
|
||||
|
@ -87,10 +88,8 @@ void Frame::did_scroll(Badge<PageView>)
|
|||
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<LayoutWidget>([&](auto& layout_widget) {
|
||||
layout_widget.update_widget();
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include <LibWeb/Layout/LayoutInline.h>
|
||||
#include <LibWeb/Layout/LayoutReplaced.h>
|
||||
#include <LibWeb/Layout/LayoutText.h>
|
||||
#include <LibWeb/Layout/LayoutWidget.h>
|
||||
#include <math.h>
|
||||
|
||||
namespace Web {
|
||||
|
@ -83,12 +84,14 @@ void LayoutBlock::layout_block_children(LayoutMode layout_mode)
|
|||
return;
|
||||
auto& child_block = static_cast<LayoutBlock&>(child);
|
||||
child_block.layout(layout_mode);
|
||||
|
||||
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<LayoutBox>(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()));
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<LayoutReplaced>(*ancestor)) || !is<LayoutBlock>(*ancestor)))
|
||||
while (ancestor && !is<LayoutBlock>(*ancestor))
|
||||
ancestor = ancestor->parent();
|
||||
return to<LayoutBlock>(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<LayoutReplaced>(*ancestor)) || !is<LayoutBlock>(*ancestor)))
|
||||
ancestor = ancestor->parent();
|
||||
return to<LayoutBlock>(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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<>
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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<GUI::Widget> m_widget;
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue