summaryrefslogtreecommitdiff
path: root/Libraries/LibHTML/Layout/LayoutNode.cpp
blob: 031d8a454c5acb5984d476f6b8a663729e2da465 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#include <LibGUI/GPainter.h>
#include <LibHTML/DOM/Document.h>
#include <LibHTML/DOM/Element.h>
#include <LibHTML/Frame.h>
#include <LibHTML/Layout/LayoutBlock.h>
#include <LibHTML/Layout/LayoutNode.h>

//#define DRAW_BOXES_AROUND_LAYOUT_NODES
//#define DRAW_BOXES_AROUND_HOVERED_NODES

LayoutNode::LayoutNode(const Node* node)
    : m_node(node)
{
    if (m_node)
        m_node->set_layout_node({}, this);
}

LayoutNode::~LayoutNode()
{
    if (m_node)
        m_node->set_layout_node({}, nullptr);
}

void LayoutNode::layout()
{
    for_each_child([](auto& child) {
        child.layout();
    });
}

const LayoutBlock* LayoutNode::containing_block() const
{
    for (auto* ancestor = parent(); ancestor; ancestor = ancestor->parent()) {
        if (ancestor->is_block())
            return static_cast<const LayoutBlock*>(ancestor);
    }
    return nullptr;
}

void LayoutNode::render(RenderingContext& context)
{
    if (!is_visible())
        return;

#ifdef DRAW_BOXES_AROUND_LAYOUT_NODES
    context.painter().draw_rect(m_rect, Color::Blue);
#endif
#ifdef DRAW_BOXES_AROUND_HOVERED_NODES
    if (!is_anonymous() && node() == document().hovered_node())
        context.painter().draw_rect(m_rect, Color::Red);
#endif

    Rect padded_rect;
    padded_rect.set_x(rect().x() - box_model().padding().left.to_px());
    padded_rect.set_width(rect().width() + box_model().padding().left.to_px() + box_model().padding().right.to_px());
    padded_rect.set_y(rect().y() - box_model().padding().top.to_px());
    padded_rect.set_height(rect().height() + box_model().padding().top.to_px() + box_model().padding().bottom.to_px());

    auto bgcolor = style().property(CSS::PropertyID::BackgroundColor);
    if (bgcolor.has_value() && bgcolor.value()->is_color()) {
        context.painter().fill_rect(padded_rect, bgcolor.value()->to_color(document()));
    }

    // FIXME: Respect all individual border sides
    auto border_width_value = style().property(CSS::PropertyID::BorderTopWidth);
    auto border_color_value = style().property(CSS::PropertyID::BorderTopColor);
    auto border_style_value = style().property(CSS::PropertyID::BorderTopStyle);
    if (border_width_value.has_value() && border_color_value.has_value()) {
        int border_width = border_width_value.value()->to_length().to_px();
        Color border_color = border_color_value.value()->to_color(document());

        if (border_style_value.has_value() && border_style_value.value()->to_string() == "inset") {
            // border-style: inset
            auto shadow_color = Color::from_rgb(0x888888);
            auto highlight_color = Color::from_rgb(0x5a5a5a);
            context.painter().draw_line(padded_rect.top_left(), padded_rect.top_right(), highlight_color, border_width);
            context.painter().draw_line(padded_rect.top_right(), padded_rect.bottom_right(), shadow_color, border_width);
            context.painter().draw_line(padded_rect.bottom_right(), padded_rect.bottom_left(), shadow_color, border_width);
            context.painter().draw_line(padded_rect.bottom_left(), padded_rect.top_left(), highlight_color, border_width);
        } else if (border_style_value.has_value() && border_style_value.value()->to_string() == "outset") {
            // border-style: outset
            auto highlight_color = Color::from_rgb(0x888888);
            auto shadow_color = Color::from_rgb(0x5a5a5a);
            context.painter().draw_line(padded_rect.top_left(), padded_rect.top_right(), highlight_color, border_width);
            context.painter().draw_line(padded_rect.top_right(), padded_rect.bottom_right(), shadow_color, border_width);
            context.painter().draw_line(padded_rect.bottom_right(), padded_rect.bottom_left(), shadow_color, border_width);
            context.painter().draw_line(padded_rect.bottom_left(), padded_rect.top_left(), highlight_color, border_width);
        } else {
            // border-style: solid
            context.painter().draw_line(padded_rect.top_left(), padded_rect.top_right(), border_color, border_width);
            context.painter().draw_line(padded_rect.top_right(), padded_rect.bottom_right(), border_color, border_width);
            context.painter().draw_line(padded_rect.bottom_right(), padded_rect.bottom_left(), border_color, border_width);
            context.painter().draw_line(padded_rect.bottom_left(), padded_rect.top_left(), border_color, border_width);
        }
    }

    // TODO: render our border
    for_each_child([&](auto& child) {
        child.render(context);
    });
}

HitTestResult LayoutNode::hit_test(const Point& position) const
{
    // FIXME: It would be nice if we could confidently skip over hit testing
    //        parts of the layout tree, but currently we can't just check
    //        m_rect.contains() since inline text rects can't be trusted..
    HitTestResult result { m_rect.contains(position) ? this : nullptr };
    for_each_child([&](auto& child) {
        auto child_result = child.hit_test(position);
        if (child_result.layout_node)
            result = child_result;
    });
    return result;
}

const Document& LayoutNode::document() const
{
    if (is_anonymous())
        return parent()->document();
    return node()->document();
}

void LayoutNode::split_into_lines(LayoutBlock& container)
{
    for_each_child([&](auto& child) {
        if (child.is_inline()) {
            child.split_into_lines(container);
        } else {
            // FIXME: Support block children of inlines.
        }
    });
}

void LayoutNode::set_needs_display()
{
    auto* frame = document().frame();
    ASSERT(frame);

    if (!is_inline()) {
        const_cast<Frame*>(frame)->set_needs_display(rect());
        return;
    }

    for_each_fragment_of_this([&](auto& fragment) {
        if (&fragment.layout_node() == this || is_ancestor_of(fragment.layout_node())) {
            const_cast<Frame*>(frame)->set_needs_display(fragment.rect());
        }
        return IterationDecision::Continue;
    });
}