summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibWeb/Layout/Label.cpp
blob: fc8ef5664933ef5e5048ca93ecf5e1dccdd9a5d4 (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
152
/*
 * Copyright (c) 2021, Tim Flynn <trflynn89@serenityos.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <LibGUI/Event.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Element.h>
#include <LibWeb/Layout/InitialContainingBlock.h>
#include <LibWeb/Layout/Label.h>
#include <LibWeb/Layout/LabelableNode.h>
#include <LibWeb/Layout/TextNode.h>
#include <LibWeb/Painting/LabelablePaintable.h>

namespace Web::Layout {

Label::Label(DOM::Document& document, HTML::HTMLLabelElement* element, NonnullRefPtr<CSS::StyleProperties> style)
    : BlockContainer(document, element, move(style))
{
}

Label::~Label() = default;

void Label::handle_mousedown_on_label(Badge<Painting::TextPaintable>, Gfx::IntPoint const&, unsigned button)
{
    if (button != GUI::MouseButton::Primary)
        return;

    if (auto* control = labeled_control(); control)
        control->paintable()->handle_associated_label_mousedown({});

    m_tracking_mouse = true;
}

void Label::handle_mouseup_on_label(Badge<Painting::TextPaintable>, Gfx::IntPoint const& position, unsigned button)
{
    if (!m_tracking_mouse || button != GUI::MouseButton::Primary)
        return;

    // NOTE: Changing the checked state of the DOM node may run arbitrary JS, which could disappear this node.
    NonnullRefPtr protect = *this;

    if (auto* control = labeled_control(); control) {
        bool is_inside_control = enclosing_int_rect(control->paint_box()->absolute_rect()).contains(position);
        bool is_inside_label = enclosing_int_rect(paint_box()->absolute_rect()).contains(position);

        if (is_inside_control || is_inside_label)
            control->paintable()->handle_associated_label_mouseup({});
    }

    m_tracking_mouse = false;
}

void Label::handle_mousemove_on_label(Badge<Painting::TextPaintable>, Gfx::IntPoint const& position, unsigned)
{
    if (!m_tracking_mouse)
        return;

    if (auto* control = labeled_control(); control) {
        bool is_inside_control = enclosing_int_rect(control->paint_box()->absolute_rect()).contains(position);
        bool is_inside_label = enclosing_int_rect(paint_box()->absolute_rect()).contains(position);

        control->paintable()->handle_associated_label_mousemove({}, is_inside_control || is_inside_label);
    }
}

bool Label::is_inside_associated_label(LabelableNode const& control, Gfx::IntPoint const& position)
{
    if (auto* label = label_for_control_node(control); label)
        return enclosing_int_rect(label->paint_box()->absolute_rect()).contains(position);
    return false;
}

bool Label::is_associated_label_hovered(LabelableNode const& control)
{
    if (auto* label = label_for_control_node(control); label) {
        if (label->document().hovered_node() == &label->dom_node())
            return true;

        if (auto* child = label->first_child_of_type<TextNode>(); child)
            return label->document().hovered_node() == &child->dom_node();
    }

    return false;
}

// https://html.spec.whatwg.org/multipage/forms.html#labeled-control
Label const* Label::label_for_control_node(LabelableNode const& control)
{
    if (!control.document().layout_node())
        return nullptr;

    // The for attribute may be specified to indicate a form control with which the caption is to be associated.
    // If the attribute is specified, the attribute's value must be the ID of a labelable element in the
    // same tree as the label element. If the attribute is specified and there is an element in the tree
    // whose ID is equal to the value of the for attribute, and the first such element in tree order is
    // a labelable element, then that element is the label element's labeled control.
    if (auto id = control.dom_node().attribute(HTML::AttributeNames::id); !id.is_empty()) {
        Label const* label = nullptr;

        control.document().layout_node()->for_each_in_inclusive_subtree_of_type<Label>([&](auto& node) {
            if (node.dom_node().for_() == id) {
                label = &node;
                return IterationDecision::Break;
            }
            return IterationDecision::Continue;
        });

        if (label)
            return label;
    }

    // If the for attribute is not specified, but the label element has a labelable element descendant,
    // then the first such descendant in tree order is the label element's labeled control.
    return control.first_ancestor_of_type<Label>();
}

// https://html.spec.whatwg.org/multipage/forms.html#labeled-control
LabelableNode* Label::labeled_control()
{
    if (!document().layout_node())
        return nullptr;

    LabelableNode* control = nullptr;

    // The for attribute may be specified to indicate a form control with which the caption is to be associated.
    // If the attribute is specified, the attribute's value must be the ID of a labelable element in the
    // same tree as the label element. If the attribute is specified and there is an element in the tree
    // whose ID is equal to the value of the for attribute, and the first such element in tree order is
    // a labelable element, then that element is the label element's labeled control.
    if (auto for_ = dom_node().for_(); !for_.is_null()) {
        document().layout_node()->for_each_in_inclusive_subtree_of_type<LabelableNode>([&](auto& node) {
            if (node.dom_node().attribute(HTML::AttributeNames::id) == for_) {
                control = &node;
                return IterationDecision::Break;
            }
            return IterationDecision::Continue;
        });
        return control;
    }

    // If the for attribute is not specified, but the label element has a labelable element descendant,
    // then the first such descendant in tree order is the label element's labeled control.
    for_each_in_subtree_of_type<LabelableNode>([&](auto& labelable_node) {
        control = &labelable_node;
        return IterationDecision::Break;
    });
    return control;
}

}