mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-01-08 04:15:23 +03:00
LibWeb: Respect CSS z-index property while painting
To support z-ordering when painting, the layout tree now has a parallel sparse tree of stacking contexts. The rules for which layout boxes establish a stacking context are a bit complex, but the intent is to encapsulate the decision making into establishes_stacking_context(). When we paint, we start from the ICB (LayoutDocument) who always has a StackingContext and then paint the tree of StackingContexts where each node has its children sorted by z-index. This is pretty crude, but gets the basic job done. Note that this does not yet support hit testing; hit testing is still done using a naive treewalk from the root.
This commit is contained in:
parent
ce3260c6bf
commit
96da15a8a4
Notes:
sideshowbarker
2024-07-19 05:38:33 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/96da15a8a44
@ -94,6 +94,7 @@ set(SOURCES
|
||||
Layout/LayoutWidget.cpp
|
||||
Layout/LineBox.cpp
|
||||
Layout/LineBoxFragment.cpp
|
||||
Layout/StackingContext.cpp
|
||||
LayoutTreeModel.cpp
|
||||
Loader/FrameLoader.cpp
|
||||
Loader/ImageLoader.cpp
|
||||
|
@ -185,6 +185,14 @@ float StyleProperties::line_height(const LayoutNode& layout_node) const
|
||||
return (float)font().glyph_height() * 1.4f;
|
||||
}
|
||||
|
||||
Optional<int> StyleProperties::z_index() const
|
||||
{
|
||||
auto value = property(CSS::PropertyID::ZIndex);
|
||||
if (!value.has_value())
|
||||
return {};
|
||||
return static_cast<int>(value.value()->to_length().raw_value());
|
||||
}
|
||||
|
||||
CSS::Position StyleProperties::position() const
|
||||
{
|
||||
if (property(CSS::PropertyID::Position).has_value()) {
|
||||
|
@ -74,6 +74,7 @@ public:
|
||||
bool operator!=(const StyleProperties& other) const { return !(*this == other); }
|
||||
|
||||
CSS::Position position() const;
|
||||
Optional<int> z_index() const;
|
||||
|
||||
private:
|
||||
HashMap<unsigned, NonnullRefPtr<StyleValue>> m_property_values;
|
||||
|
@ -64,6 +64,7 @@ class RenderingContext;
|
||||
class Resource;
|
||||
class ResourceLoader;
|
||||
class Selector;
|
||||
class StackingContext;
|
||||
class StyleResolver;
|
||||
class StyleRule;
|
||||
class StyleSheet;
|
||||
|
@ -310,4 +310,36 @@ void LayoutBox::set_containing_line_box_fragment(LineBoxFragment& fragment)
|
||||
m_containing_line_box_fragment = fragment.make_weak_ptr();
|
||||
}
|
||||
|
||||
StackingContext* LayoutBox::enclosing_stacking_context()
|
||||
{
|
||||
for (auto* ancestor = parent(); ancestor; ancestor = ancestor->parent()) {
|
||||
if (!ancestor->is_box())
|
||||
continue;
|
||||
auto& ancestor_box = to<LayoutBox>(*ancestor);
|
||||
if (!ancestor_box.establishes_stacking_context())
|
||||
continue;
|
||||
ASSERT(ancestor_box.stacking_context());
|
||||
return ancestor_box.stacking_context();
|
||||
}
|
||||
// We should always reach the LayoutDocument stacking context.
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
bool LayoutBox::establishes_stacking_context() const
|
||||
{
|
||||
if (!has_style())
|
||||
return false;
|
||||
if (node() == document().root())
|
||||
return true;
|
||||
auto position = style().position();
|
||||
auto z_index = style().z_index();
|
||||
if (position == CSS::Position::Absolute || position == CSS::Position::Relative) {
|
||||
if (z_index.has_value())
|
||||
return true;
|
||||
}
|
||||
if (position == CSS::Position::Fixed || position == CSS::Position::Sticky)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,8 +26,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <LibGfx/FloatRect.h>
|
||||
#include <LibWeb/Layout/LayoutNode.h>
|
||||
#include <LibWeb/Layout/StackingContext.h>
|
||||
|
||||
namespace Web {
|
||||
|
||||
@ -60,14 +62,19 @@ public:
|
||||
|
||||
void set_containing_line_box_fragment(LineBoxFragment&);
|
||||
|
||||
bool establishes_stacking_context() const;
|
||||
StackingContext* stacking_context() { return m_stacking_context; }
|
||||
void set_stacking_context(NonnullOwnPtr<StackingContext> context) { m_stacking_context = move(context); }
|
||||
StackingContext* enclosing_stacking_context();
|
||||
|
||||
virtual void render(RenderingContext&) override;
|
||||
|
||||
protected:
|
||||
LayoutBox(const Node* node, NonnullRefPtr<StyleProperties> style)
|
||||
: LayoutNodeWithStyleAndBoxModelMetrics(node, move(style))
|
||||
{
|
||||
}
|
||||
|
||||
virtual void render(RenderingContext&) override;
|
||||
|
||||
virtual void did_set_rect() { }
|
||||
|
||||
private:
|
||||
@ -86,6 +93,8 @@ private:
|
||||
|
||||
// Some boxes hang off of line box fragments. (inline-block, inline-table, replaced, etc)
|
||||
WeakPtr<LineBoxFragment> m_containing_line_box_fragment;
|
||||
|
||||
OwnPtr<StackingContext> m_stacking_context;
|
||||
};
|
||||
|
||||
template<>
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include <LibWeb/Layout/LayoutDocument.h>
|
||||
#include <LibWeb/Layout/LayoutImage.h>
|
||||
#include <LibWeb/Layout/LayoutWidget.h>
|
||||
#include <LibWeb/Layout/StackingContext.h>
|
||||
|
||||
namespace Web {
|
||||
|
||||
@ -41,8 +42,31 @@ LayoutDocument::~LayoutDocument()
|
||||
{
|
||||
}
|
||||
|
||||
void LayoutDocument::build_stacking_context_tree()
|
||||
{
|
||||
if (stacking_context())
|
||||
return;
|
||||
|
||||
set_stacking_context(make<StackingContext>(*this, nullptr));
|
||||
|
||||
for_each_in_subtree_of_type<LayoutBox>([&](LayoutBox& box) {
|
||||
if (&box == this)
|
||||
return IterationDecision::Continue;
|
||||
if (!box.establishes_stacking_context()) {
|
||||
ASSERT(!box.stacking_context());
|
||||
return IterationDecision::Continue;
|
||||
}
|
||||
auto* parent_context = box.enclosing_stacking_context();
|
||||
ASSERT(parent_context);
|
||||
box.set_stacking_context(make<StackingContext>(box, parent_context));
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
void LayoutDocument::layout(LayoutMode layout_mode)
|
||||
{
|
||||
build_stacking_context_tree();
|
||||
|
||||
set_width(frame().size().width());
|
||||
|
||||
LayoutNode::layout(layout_mode);
|
||||
@ -76,4 +100,9 @@ void LayoutDocument::did_set_viewport_rect(Badge<Frame>, const Gfx::IntRect& a_v
|
||||
});
|
||||
}
|
||||
|
||||
void LayoutDocument::render(RenderingContext& context)
|
||||
{
|
||||
stacking_context()->render(context);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -40,6 +40,8 @@ public:
|
||||
virtual const char* class_name() const override { return "LayoutDocument"; }
|
||||
virtual void layout(LayoutMode = LayoutMode::Default) override;
|
||||
|
||||
virtual void render(RenderingContext&) override;
|
||||
|
||||
const LayoutRange& selection() const { return m_selection; }
|
||||
LayoutRange& selection() { return m_selection; }
|
||||
|
||||
@ -47,8 +49,17 @@ public:
|
||||
|
||||
virtual bool is_root() const override { return true; }
|
||||
|
||||
void build_stacking_context_tree();
|
||||
|
||||
private:
|
||||
LayoutRange m_selection;
|
||||
};
|
||||
|
||||
template<>
|
||||
inline bool is<LayoutDocument>(const LayoutNode& node)
|
||||
{
|
||||
return node.is_root();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -94,8 +94,9 @@ void LayoutNode::render(RenderingContext& context)
|
||||
if (!is_visible())
|
||||
return;
|
||||
|
||||
// TODO: render our border
|
||||
for_each_child([&](auto& child) {
|
||||
if (child.is_box() && to<LayoutBox>(child).stacking_context())
|
||||
return;
|
||||
child.render(context);
|
||||
});
|
||||
}
|
||||
|
73
Libraries/LibWeb/Layout/StackingContext.cpp
Normal file
73
Libraries/LibWeb/Layout/StackingContext.cpp
Normal file
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/QuickSort.h>
|
||||
#include <LibWeb/DOM/Node.h>
|
||||
#include <LibWeb/Layout/LayoutBox.h>
|
||||
#include <LibWeb/Layout/LayoutDocument.h>
|
||||
#include <LibWeb/Layout/StackingContext.h>
|
||||
|
||||
namespace Web {
|
||||
|
||||
StackingContext::StackingContext(LayoutBox& box, StackingContext* parent)
|
||||
: m_box(box)
|
||||
, m_parent(parent)
|
||||
{
|
||||
ASSERT(m_parent != this);
|
||||
if (m_parent) {
|
||||
m_parent->m_children.append(this);
|
||||
|
||||
// FIXME: Don't sort on every append..
|
||||
quick_sort(m_children, [](auto& a, auto& b) {
|
||||
return a->m_box.style().z_index().value_or(0) < b->m_box.style().z_index().value_or(0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void StackingContext::render(RenderingContext& context)
|
||||
{
|
||||
if (!m_box.is_root()) {
|
||||
m_box.render(context);
|
||||
} else {
|
||||
// NOTE: LayoutDocument::render() merely calls StackingContext::render()
|
||||
// so we call its base class instead.
|
||||
to<LayoutDocument>(m_box).LayoutBlock::render(context);
|
||||
}
|
||||
for (auto* child : m_children) {
|
||||
child->render(context);
|
||||
}
|
||||
}
|
||||
|
||||
void StackingContext::dump(int indent) const
|
||||
{
|
||||
for (int i = 0; i < indent; ++i)
|
||||
dbgprintf(" ");
|
||||
dbgprintf("SC for %s{%s} %s [children: %zu]\n", m_box.class_name(), m_box.node() ? m_box.node()->tag_name().characters() : "(anonymous)", m_box.absolute_rect().to_string().characters(), m_children.size());
|
||||
for (auto& child : m_children)
|
||||
child->dump(indent + 1);
|
||||
}
|
||||
|
||||
}
|
53
Libraries/LibWeb/Layout/StackingContext.h
Normal file
53
Libraries/LibWeb/Layout/StackingContext.h
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Vector.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
|
||||
namespace Web {
|
||||
|
||||
class LayoutBox;
|
||||
|
||||
class StackingContext {
|
||||
public:
|
||||
StackingContext(LayoutBox&, StackingContext* parent);
|
||||
|
||||
StackingContext* parent() { return m_parent; }
|
||||
const StackingContext* parent() const { return m_parent; }
|
||||
|
||||
void render(RenderingContext&);
|
||||
|
||||
void dump(int indent = 0) const;
|
||||
|
||||
private:
|
||||
LayoutBox& m_box;
|
||||
StackingContext* const m_parent { nullptr };
|
||||
Vector<StackingContext*> m_children;
|
||||
};
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user