/src/serenity/Userland/Libraries/LibWeb/Layout/Label.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2021, Tim Flynn <trflynn89@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <LibWeb/DOM/Document.h> |
8 | | #include <LibWeb/DOM/Element.h> |
9 | | #include <LibWeb/Layout/Label.h> |
10 | | #include <LibWeb/Layout/LabelableNode.h> |
11 | | #include <LibWeb/Layout/TextNode.h> |
12 | | #include <LibWeb/Layout/Viewport.h> |
13 | | #include <LibWeb/Painting/LabelablePaintable.h> |
14 | | #include <LibWeb/UIEvents/MouseButton.h> |
15 | | |
16 | | namespace Web::Layout { |
17 | | |
18 | | JS_DEFINE_ALLOCATOR(Label); |
19 | | |
20 | | Label::Label(DOM::Document& document, HTML::HTMLLabelElement* element, NonnullRefPtr<CSS::StyleProperties> style) |
21 | 0 | : BlockContainer(document, element, move(style)) |
22 | 0 | { |
23 | 0 | } |
24 | | |
25 | 0 | Label::~Label() = default; |
26 | | |
27 | | void Label::handle_mousedown_on_label(Badge<Painting::TextPaintable>, CSSPixelPoint, unsigned button) |
28 | 0 | { |
29 | 0 | if (button != UIEvents::MouseButton::Primary) |
30 | 0 | return; |
31 | | |
32 | 0 | if (auto control = dom_node().control(); control && is<Painting::LabelablePaintable>(control->paintable())) { |
33 | 0 | auto& labelable_paintable = verify_cast<Painting::LabelablePaintable>(*control->paintable()); |
34 | 0 | labelable_paintable.handle_associated_label_mousedown({}); |
35 | 0 | } |
36 | |
|
37 | 0 | m_tracking_mouse = true; |
38 | 0 | } |
39 | | |
40 | | void Label::handle_mouseup_on_label(Badge<Painting::TextPaintable>, CSSPixelPoint position, unsigned button) |
41 | 0 | { |
42 | 0 | if (!m_tracking_mouse || button != UIEvents::MouseButton::Primary) |
43 | 0 | return; |
44 | | |
45 | 0 | if (auto control = dom_node().control(); control && is<Painting::LabelablePaintable>(control->paintable())) { |
46 | 0 | bool is_inside_control = control->paintable_box()->absolute_rect().contains(position); |
47 | 0 | bool is_inside_label = paintable_box()->absolute_rect().contains(position); |
48 | 0 | if (is_inside_control || is_inside_label) { |
49 | 0 | auto& labelable_paintable = verify_cast<Painting::LabelablePaintable>(*control->paintable()); |
50 | 0 | labelable_paintable.handle_associated_label_mouseup({}); |
51 | 0 | } |
52 | 0 | } |
53 | |
|
54 | 0 | m_tracking_mouse = false; |
55 | 0 | } |
56 | | |
57 | | void Label::handle_mousemove_on_label(Badge<Painting::TextPaintable>, CSSPixelPoint position, unsigned) |
58 | 0 | { |
59 | 0 | if (!m_tracking_mouse) |
60 | 0 | return; |
61 | | |
62 | 0 | if (auto control = dom_node().control(); control && is<Painting::LabelablePaintable>(control->paintable())) { |
63 | 0 | bool is_inside_control = control->paintable_box()->absolute_rect().contains(position); |
64 | 0 | bool is_inside_label = paintable_box()->absolute_rect().contains(position); |
65 | 0 | auto& labelable_paintable = verify_cast<Painting::LabelablePaintable>(*control->paintable()); |
66 | 0 | labelable_paintable.handle_associated_label_mousemove({}, is_inside_control || is_inside_label); |
67 | 0 | } |
68 | 0 | } |
69 | | |
70 | | bool Label::is_inside_associated_label(LabelableNode const& control, CSSPixelPoint position) |
71 | 0 | { |
72 | 0 | if (auto* label = label_for_control_node(control); label) |
73 | 0 | return label->paintable_box()->absolute_rect().contains(position); |
74 | 0 | return false; |
75 | 0 | } |
76 | | |
77 | | bool Label::is_associated_label_hovered(LabelableNode const& control) |
78 | 0 | { |
79 | 0 | if (auto* label = label_for_control_node(control); label) { |
80 | 0 | if (label->document().hovered_node() == &label->dom_node()) |
81 | 0 | return true; |
82 | | |
83 | 0 | if (auto* child = label->first_child_of_type<TextNode>(); child) |
84 | 0 | return label->document().hovered_node() == &child->dom_node(); |
85 | 0 | } |
86 | | |
87 | 0 | return false; |
88 | 0 | } |
89 | | |
90 | | // https://html.spec.whatwg.org/multipage/forms.html#labeled-control |
91 | | Label const* Label::label_for_control_node(LabelableNode const& control) |
92 | 0 | { |
93 | 0 | if (!control.document().layout_node()) |
94 | 0 | return nullptr; |
95 | | |
96 | | // The for attribute may be specified to indicate a form control with which the caption is to be associated. |
97 | | // If the attribute is specified, the attribute's value must be the ID of a labelable element in the |
98 | | // same tree as the label element. If the attribute is specified and there is an element in the tree |
99 | | // whose ID is equal to the value of the for attribute, and the first such element in tree order is |
100 | | // a labelable element, then that element is the label element's labeled control. |
101 | 0 | if (auto const& id = control.dom_node().id(); id.has_value() && !id->is_empty()) { |
102 | 0 | Label const* label = nullptr; |
103 | |
|
104 | 0 | control.document().layout_node()->for_each_in_inclusive_subtree_of_type<Label>([&](auto& node) { |
105 | 0 | if (node.dom_node().for_() == id) { |
106 | 0 | label = &node; |
107 | 0 | return TraversalDecision::Break; |
108 | 0 | } |
109 | 0 | return TraversalDecision::Continue; |
110 | 0 | }); |
111 | |
|
112 | 0 | if (label) |
113 | 0 | return label; |
114 | 0 | } |
115 | | |
116 | | // If the for attribute is not specified, but the label element has a labelable element descendant, |
117 | | // then the first such descendant in tree order is the label element's labeled control. |
118 | 0 | return control.first_ancestor_of_type<Label>(); |
119 | 0 | } |
120 | | |
121 | | } |