mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 02:07:34 +00:00
LibWeb: Some improvements to absolute positioning
Absolutely positioned blocks now register themselves with their containing block (and note that the containing block of an absolutely positioned box is the nearest non-statically positioned block ancestor or the ICB as fallback.) Containing blocks then drive the layout of their tracked absolutely positioned descendants as a separate layout pass. This is very far from perfect but the general direction seems good.
This commit is contained in:
parent
ff2c949d70
commit
260427f0ad
8 changed files with 166 additions and 24 deletions
48
Base/home/anon/www/position-absolute-from-edges.html
Normal file
48
Base/home/anon/www/position-absolute-from-edges.html
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<style>
|
||||||
|
#container {
|
||||||
|
position: absolute;
|
||||||
|
width: 500px;
|
||||||
|
height: 400px;
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
#red {
|
||||||
|
position: absolute;
|
||||||
|
left: 20px;
|
||||||
|
top: 20px;
|
||||||
|
background: red;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
border: 10px solid gray;
|
||||||
|
}
|
||||||
|
#green {
|
||||||
|
position: absolute;
|
||||||
|
right: 20px;
|
||||||
|
top: 20px;
|
||||||
|
background: green;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
margin: 50px;
|
||||||
|
}
|
||||||
|
#blue {
|
||||||
|
position: absolute;
|
||||||
|
left: 20px;
|
||||||
|
bottom: 20px;
|
||||||
|
background: blue;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
#yellow {
|
||||||
|
position: absolute;
|
||||||
|
right: 20px;
|
||||||
|
bottom: 20px;
|
||||||
|
background: yellow;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div id=container>
|
||||||
|
<div id=red></div>
|
||||||
|
<div id=green></div>
|
||||||
|
<div id=blue></div>
|
||||||
|
<div id=yellow></div>
|
||||||
|
</div>
|
|
@ -28,6 +28,7 @@ span#ua {
|
||||||
<p>Your user agent is: <b><span id="ua"></span></b></p>
|
<p>Your user agent is: <b><span id="ua"></span></b></p>
|
||||||
<p>Some small test pages:</p>
|
<p>Some small test pages:</p>
|
||||||
<ul>
|
<ul>
|
||||||
|
<li><a href="position-absolute-from-edges.html">position: absolute, offset from edges</a></li>
|
||||||
<li><a href="iframe.html">iframe</a></li>
|
<li><a href="iframe.html">iframe</a></li>
|
||||||
<li><a href="many-buggies.html">many buggies</a></li>
|
<li><a href="many-buggies.html">many buggies</a></li>
|
||||||
<li><a href="palette.html">system palette color css extension</a></li>
|
<li><a href="palette.html">system palette color css extension</a></li>
|
||||||
|
|
|
@ -145,6 +145,11 @@ void dump_tree(const LayoutNode& layout_node)
|
||||||
|
|
||||||
if (layout_node.is_block() && static_cast<const LayoutBlock&>(layout_node).children_are_inline()) {
|
if (layout_node.is_block() && static_cast<const LayoutBlock&>(layout_node).children_are_inline()) {
|
||||||
auto& block = static_cast<const LayoutBlock&>(layout_node);
|
auto& block = static_cast<const LayoutBlock&>(layout_node);
|
||||||
|
if (block.absolutely_positioned_descendant_count()) {
|
||||||
|
for (size_t i = 0; i < indent; ++i)
|
||||||
|
dbgprintf(" ");
|
||||||
|
dbgprintf(" %zu absolutely positioned descendant(s) tracked here\n", block.absolutely_positioned_descendant_count());
|
||||||
|
}
|
||||||
for (size_t i = 0; i < indent; ++i)
|
for (size_t i = 0; i < indent; ++i)
|
||||||
dbgprintf(" ");
|
dbgprintf(" ");
|
||||||
dbgprintf(" Line boxes (%d):\n", block.line_boxes().size());
|
dbgprintf(" Line boxes (%d):\n", block.line_boxes().size());
|
||||||
|
|
|
@ -46,4 +46,24 @@ BoxModelMetrics::PixelBox BoxModelMetrics::full_margin(const LayoutNode& layout_
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BoxModelMetrics::PixelBox BoxModelMetrics::padding_box(const LayoutNode& layout_node) const
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
m_padding.top.to_px(layout_node),
|
||||||
|
m_padding.right.to_px(layout_node),
|
||||||
|
m_padding.bottom.to_px(layout_node),
|
||||||
|
m_padding.left.to_px(layout_node),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
BoxModelMetrics::PixelBox BoxModelMetrics::border_box(const LayoutNode& layout_node) const
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
m_border.top.to_px(layout_node) + m_padding.top.to_px(layout_node),
|
||||||
|
m_border.right.to_px(layout_node) + m_padding.right.to_px(layout_node),
|
||||||
|
m_border.bottom.to_px(layout_node) + m_padding.bottom.to_px(layout_node),
|
||||||
|
m_border.left.to_px(layout_node) + m_padding.left.to_px(layout_node),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,8 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
PixelBox full_margin(const LayoutNode&) const;
|
PixelBox full_margin(const LayoutNode&) const;
|
||||||
|
PixelBox padding_box(const LayoutNode&) const;
|
||||||
|
PixelBox border_box(const LayoutNode&) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LengthBox m_margin;
|
LengthBox m_margin;
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#include <LibGUI/Painter.h>
|
#include <LibGUI/Painter.h>
|
||||||
#include <LibWeb/CSS/StyleResolver.h>
|
#include <LibWeb/CSS/StyleResolver.h>
|
||||||
#include <LibWeb/DOM/Element.h>
|
#include <LibWeb/DOM/Element.h>
|
||||||
|
#include <LibWeb/Dump.h>
|
||||||
#include <LibWeb/Layout/LayoutBlock.h>
|
#include <LibWeb/Layout/LayoutBlock.h>
|
||||||
#include <LibWeb/Layout/LayoutInline.h>
|
#include <LibWeb/Layout/LayoutInline.h>
|
||||||
#include <LibWeb/Layout/LayoutReplaced.h>
|
#include <LibWeb/Layout/LayoutReplaced.h>
|
||||||
|
@ -64,6 +65,69 @@ void LayoutBlock::layout(LayoutMode layout_mode)
|
||||||
layout_children(layout_mode);
|
layout_children(layout_mode);
|
||||||
|
|
||||||
compute_height();
|
compute_height();
|
||||||
|
|
||||||
|
if (layout_mode == LayoutMode::Default)
|
||||||
|
layout_absolute_descendants();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutBlock::layout_absolute_descendants()
|
||||||
|
{
|
||||||
|
for (auto& box : m_absolutely_positioned_descendants) {
|
||||||
|
box->layout(LayoutMode::Default);
|
||||||
|
auto& box_model = box->box_model();
|
||||||
|
auto& style = box->style();
|
||||||
|
auto zero_value = Length(0, Length::Type::Px);
|
||||||
|
|
||||||
|
auto specified_width = style.length_or_fallback(CSS::PropertyID::Width, Length(), width());
|
||||||
|
|
||||||
|
box_model.margin().top = style.length_or_fallback(CSS::PropertyID::MarginTop, {}, height());
|
||||||
|
box_model.margin().right = style.length_or_fallback(CSS::PropertyID::MarginRight, {}, width());
|
||||||
|
box_model.margin().bottom = style.length_or_fallback(CSS::PropertyID::MarginBottom, {}, height());
|
||||||
|
box_model.margin().left = style.length_or_fallback(CSS::PropertyID::MarginLeft, {}, width());
|
||||||
|
|
||||||
|
box_model.offset().top = style.length_or_fallback(CSS::PropertyID::Top, {}, height());
|
||||||
|
box_model.offset().right = style.length_or_fallback(CSS::PropertyID::Right, {}, width());
|
||||||
|
box_model.offset().bottom = style.length_or_fallback(CSS::PropertyID::Bottom, {}, height());
|
||||||
|
box_model.offset().left = style.length_or_fallback(CSS::PropertyID::Left, {}, width());
|
||||||
|
|
||||||
|
if (box_model.offset().left.is_auto() && specified_width.is_auto() && box_model.offset().right.is_auto()) {
|
||||||
|
if (box_model.margin().left.is_auto())
|
||||||
|
box_model.margin().left = zero_value;
|
||||||
|
if (box_model.margin().right.is_auto())
|
||||||
|
box_model.margin().right = zero_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gfx::FloatPoint used_offset;
|
||||||
|
|
||||||
|
float x_offset = box_model.offset().left.to_px(*box)
|
||||||
|
+ box_model.border_box(*box).left
|
||||||
|
- box_model.offset().right.to_px(*box)
|
||||||
|
- box_model.border_box(*box).right;
|
||||||
|
|
||||||
|
float y_offset = box_model.offset().top.to_px(*box)
|
||||||
|
+ box_model.border_box(*box).top
|
||||||
|
- box_model.offset().bottom.to_px(*box)
|
||||||
|
- box_model.border_box(*box).bottom;
|
||||||
|
|
||||||
|
if (!box_model.offset().left.is_auto()) {
|
||||||
|
used_offset.set_x(x_offset + box_model.margin().left.to_px(*box));
|
||||||
|
} else if (!box_model.offset().right.is_auto()) {
|
||||||
|
used_offset.set_x(width() + x_offset - box->width() - box_model.margin().right.to_px(*box));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!box_model.offset().top.is_auto()) {
|
||||||
|
used_offset.set_y(y_offset + box_model.margin().top.to_px(*box));
|
||||||
|
} else if (!box_model.offset().bottom.is_auto()) {
|
||||||
|
used_offset.set_y(height() + y_offset - box->height() - box_model.margin().bottom.to_px(*box));
|
||||||
|
}
|
||||||
|
|
||||||
|
box->set_offset(used_offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutBlock::add_absolutely_positioned_descendant(LayoutBox& box)
|
||||||
|
{
|
||||||
|
m_absolutely_positioned_descendants.set(box);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LayoutBlock::layout_children(LayoutMode layout_mode)
|
void LayoutBlock::layout_children(LayoutMode layout_mode)
|
||||||
|
@ -377,19 +441,17 @@ void LayoutBlock::compute_width()
|
||||||
|
|
||||||
void LayoutBlock::compute_position()
|
void LayoutBlock::compute_position()
|
||||||
{
|
{
|
||||||
auto& style = this->style();
|
// Absolutely positioned blocks are positioned by position_absolute_boxes()
|
||||||
|
if (is_absolutely_positioned()) {
|
||||||
auto zero_value = Length(0, Length::Type::Px);
|
dbg() << "Is abspos, adding to containing block " << containing_block()->node()->tag_name();
|
||||||
|
const_cast<LayoutBlock*>(containing_block())->add_absolutely_positioned_descendant(*this);
|
||||||
auto& containing_block = *this->containing_block();
|
return;
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto& style = this->style();
|
||||||
|
auto zero_value = Length(0, Length::Type::Px);
|
||||||
|
auto& containing_block = *this->containing_block();
|
||||||
|
|
||||||
box_model().margin().top = style.length_or_fallback(CSS::PropertyID::MarginTop, 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().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().top = style.length_or_fallback(CSS::PropertyID::BorderTopWidth, zero_value);
|
||||||
|
@ -405,19 +467,17 @@ void LayoutBlock::compute_position()
|
||||||
float position_y = box_model().full_margin(*this).top
|
float position_y = box_model().full_margin(*this).top
|
||||||
+ box_model().offset().top.to_px(*this);
|
+ box_model().offset().top.to_px(*this);
|
||||||
|
|
||||||
if (style.position() != CSS::Position::Absolute || containing_block.style().position() == CSS::Position::Absolute) {
|
LayoutBlock* relevant_sibling = previous_sibling();
|
||||||
LayoutBlock* relevant_sibling = previous_sibling();
|
while (relevant_sibling != nullptr) {
|
||||||
while (relevant_sibling != nullptr) {
|
if (relevant_sibling->style().position() != CSS::Position::Absolute)
|
||||||
if (relevant_sibling->style().position() != CSS::Position::Absolute)
|
break;
|
||||||
break;
|
relevant_sibling = relevant_sibling->previous_sibling();
|
||||||
relevant_sibling = relevant_sibling->previous_sibling();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (relevant_sibling) {
|
if (relevant_sibling) {
|
||||||
auto& previous_sibling_style = relevant_sibling->box_model();
|
auto& previous_sibling_style = relevant_sibling->box_model();
|
||||||
position_y += relevant_sibling->effective_offset().y() + relevant_sibling->height();
|
position_y += relevant_sibling->effective_offset().y() + relevant_sibling->height();
|
||||||
position_y += previous_sibling_style.full_margin(*this).bottom;
|
position_y += previous_sibling_style.full_margin(*this).bottom;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set_offset({ position_x, position_y });
|
set_offset({ position_x, position_y });
|
||||||
|
@ -430,7 +490,6 @@ void LayoutBlock::compute_height()
|
||||||
auto specified_height = style.length_or_fallback(CSS::PropertyID::Height, Length(), containing_block()->height());
|
auto specified_height = style.length_or_fallback(CSS::PropertyID::Height, Length(), containing_block()->height());
|
||||||
auto specified_max_height = style.length_or_fallback(CSS::PropertyID::MaxHeight, Length(), containing_block()->height());
|
auto specified_max_height = style.length_or_fallback(CSS::PropertyID::MaxHeight, Length(), containing_block()->height());
|
||||||
|
|
||||||
|
|
||||||
if (!specified_height.is_auto()) {
|
if (!specified_height.is_auto()) {
|
||||||
float used_height = specified_height.to_px(*this);
|
float used_height = specified_height.to_px(*this);
|
||||||
if (!specified_max_height.is_auto())
|
if (!specified_max_height.is_auto())
|
||||||
|
|
|
@ -65,10 +65,14 @@ public:
|
||||||
|
|
||||||
virtual void split_into_lines(LayoutBlock& container, LayoutMode) override;
|
virtual void split_into_lines(LayoutBlock& container, LayoutMode) override;
|
||||||
|
|
||||||
|
void add_absolutely_positioned_descendant(LayoutBox&);
|
||||||
|
size_t absolutely_positioned_descendant_count() const { return m_absolutely_positioned_descendants.size(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void compute_width();
|
void compute_width();
|
||||||
void compute_position();
|
void compute_position();
|
||||||
void compute_height();
|
void compute_height();
|
||||||
|
void layout_absolute_descendants();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
virtual bool is_block() const override { return true; }
|
virtual bool is_block() const override { return true; }
|
||||||
|
@ -80,6 +84,7 @@ private:
|
||||||
void layout_block_children(LayoutMode);
|
void layout_block_children(LayoutMode);
|
||||||
|
|
||||||
Vector<LineBox> m_line_boxes;
|
Vector<LineBox> m_line_boxes;
|
||||||
|
HashTable<RefPtr<LayoutBox>> m_absolutely_positioned_descendants;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename Callback>
|
template<typename Callback>
|
||||||
|
|
|
@ -58,6 +58,8 @@ void LayoutDocument::layout(LayoutMode layout_mode)
|
||||||
});
|
});
|
||||||
set_height(lowest_bottom);
|
set_height(lowest_bottom);
|
||||||
|
|
||||||
|
layout_absolute_descendants();
|
||||||
|
|
||||||
// FIXME: This is a total hack. Make sure any GUI::Widgets are moved into place after layout.
|
// FIXME: This is a total hack. Make sure any GUI::Widgets are moved into place after layout.
|
||||||
// We should stop embedding GUI::Widgets entirely, since that won't work out-of-process.
|
// We should stop embedding GUI::Widgets entirely, since that won't work out-of-process.
|
||||||
for_each_in_subtree_of_type<LayoutWidget>([&](auto& widget) {
|
for_each_in_subtree_of_type<LayoutWidget>([&](auto& widget) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue