1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 21:37:35 +00:00

LibWeb: Move mouse event and label logic from layout to painting tree

Input events have nothing to do with layout, so let's not send them to
layout nodes.

The job of Paintable starts to become clear. It represents a paintable
item that can be rendered into the viewport, which means it can also
be targeted by the mouse cursor.
This commit is contained in:
Andreas Kling 2022-03-10 22:46:35 +01:00
parent ed84fbce47
commit cb0c5390ff
35 changed files with 560 additions and 429 deletions

View file

@ -4,8 +4,12 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGUI/Event.h>
#include <LibGfx/StylePainter.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/HTML/BrowsingContext.h>
#include <LibWeb/HTML/HTMLImageElement.h>
#include <LibWeb/Layout/Label.h>
#include <LibWeb/Layout/RadioButton.h>
#include <LibWeb/Painting/RadioButtonPaintable.h>
@ -17,7 +21,7 @@ NonnullRefPtr<RadioButtonPaintable> RadioButtonPaintable::create(Layout::RadioBu
}
RadioButtonPaintable::RadioButtonPaintable(Layout::RadioButton const& layout_box)
: PaintableBox(layout_box)
: LabelablePaintable(layout_box)
{
}
@ -26,6 +30,11 @@ Layout::RadioButton const& RadioButtonPaintable::layout_box() const
return static_cast<Layout::RadioButton const&>(layout_node());
}
Layout::RadioButton& RadioButtonPaintable::layout_box()
{
return static_cast<Layout::RadioButton&>(layout_node());
}
void RadioButtonPaintable::paint(PaintContext& context, PaintPhase phase) const
{
if (!is_visible())
@ -34,7 +43,94 @@ void RadioButtonPaintable::paint(PaintContext& context, PaintPhase phase) const
PaintableBox::paint(context, phase);
if (phase == PaintPhase::Foreground)
Gfx::StylePainter::paint_radio_button(context.painter(), enclosing_int_rect(absolute_rect()), context.palette(), layout_box().dom_node().checked(), layout_box().being_pressed());
Gfx::StylePainter::paint_radio_button(context.painter(), enclosing_int_rect(absolute_rect()), context.palette(), layout_box().dom_node().checked(), m_being_pressed);
}
void RadioButtonPaintable::handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned)
{
if (button != GUI::MouseButton::Primary || !layout_box().dom_node().enabled())
return;
m_being_pressed = true;
set_needs_display();
m_tracking_mouse = true;
browsing_context().event_handler().set_mouse_event_tracking_layout_node(&layout_box());
}
void RadioButtonPaintable::handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned button, unsigned)
{
if (!m_tracking_mouse || button != GUI::MouseButton::Primary || !layout_box().dom_node().enabled())
return;
// NOTE: Changing the checked state of the DOM node may run arbitrary JS, which could disappear this node.
NonnullRefPtr protect = *this;
bool is_inside_node_or_label = enclosing_int_rect(absolute_rect()).contains(position);
if (!is_inside_node_or_label)
is_inside_node_or_label = Layout::Label::is_inside_associated_label(layout_box(), position);
if (is_inside_node_or_label)
set_checked_within_group();
m_being_pressed = false;
m_tracking_mouse = false;
browsing_context().event_handler().set_mouse_event_tracking_layout_node(nullptr);
}
void RadioButtonPaintable::handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned, unsigned)
{
if (!m_tracking_mouse || !layout_box().dom_node().enabled())
return;
bool is_inside_node_or_label = enclosing_int_rect(absolute_rect()).contains(position);
if (!is_inside_node_or_label)
is_inside_node_or_label = Layout::Label::is_inside_associated_label(layout_box(), position);
if (m_being_pressed == is_inside_node_or_label)
return;
m_being_pressed = is_inside_node_or_label;
set_needs_display();
}
void RadioButtonPaintable::handle_associated_label_mousedown(Badge<Layout::Label>)
{
m_being_pressed = true;
set_needs_display();
}
void RadioButtonPaintable::handle_associated_label_mouseup(Badge<Layout::Label>)
{
// NOTE: Changing the checked state of the DOM node may run arbitrary JS, which could disappear this node.
NonnullRefPtr protect = *this;
set_checked_within_group();
m_being_pressed = false;
}
void RadioButtonPaintable::handle_associated_label_mousemove(Badge<Layout::Label>, bool is_inside_node_or_label)
{
if (m_being_pressed == is_inside_node_or_label)
return;
m_being_pressed = is_inside_node_or_label;
set_needs_display();
}
void RadioButtonPaintable::set_checked_within_group()
{
if (layout_box().dom_node().checked())
return;
layout_box().dom_node().set_checked(true, HTML::HTMLInputElement::ChangeSource::User);
String name = layout_box().dom_node().name();
document().for_each_in_inclusive_subtree_of_type<HTML::HTMLInputElement>([&](auto& element) {
if (element.checked() && (element.paintable() != this) && (element.name() == name))
element.set_checked(false, HTML::HTMLInputElement::ChangeSource::User);
return IterationDecision::Continue;
});
}
}