mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 23:50:19 +00:00
LibWeb: Refactor all LabelableNode subclasses + input event handling :^)
This commit is messy due to the Paintable and Layout classes being tangled together. The RadioButton, CheckBox and ButtonBox classes are now subclasses of FormAssociatedLabelableNode. This subclass separates these layout nodes from LabelableNode, which is also the superclass of non-form associated labelable nodes (Progress). ButtonPaintable, CheckBoxPaintable and RadioButtonPaintable no longer call events on DOM nodes directly from their mouse event handlers; instead, all the functionality is now directly in EventHandler, which dispatches the related events. handle_mousedown and related methods return a bool indicating whether the event handling should proceed. Paintable classes can now return an alternative DOM::Node which should be the target of the mouse event. Labels use this to indicate that the labeled control should be the target of the mouse events. HTMLInputElement put its activation behavior on run_activation_behavior, which wasn't actually called anywhere and had to be manually called by other places. We now use activation_behavior which is used by EventDispatcher. This commit also brings HTMLInputElement closer to spec by removing the did_foo functions that did ad-hoc event dispatching and unifies the behavior under run_input_activation_behavior.
This commit is contained in:
parent
06ccc45157
commit
29583104d2
Notes:
sideshowbarker
2024-07-17 17:20:49 +09:00
Author: https://github.com/sin-ack Commit: https://github.com/SerenityOS/serenity/commit/29583104d2 Pull-request: https://github.com/SerenityOS/serenity/pull/13052 Reviewed-by: https://github.com/Lubrsi Reviewed-by: https://github.com/awesomekling Reviewed-by: https://github.com/linusg ✅ Reviewed-by: https://github.com/trflynn89
26 changed files with 409 additions and 466 deletions
|
@ -328,6 +328,7 @@ bool EventDispatcher::dispatch(NonnullRefPtr<EventTarget> target, NonnullRefPtr<
|
|||
if (!event->cancelled()) {
|
||||
// NOTE: Since activation_target is set, it will have activation behavior.
|
||||
activation_target->activation_behavior(event);
|
||||
activation_target->legacy_cancelled_activation_behavior_was_not_called();
|
||||
} else {
|
||||
activation_target->legacy_cancelled_activation_behavior();
|
||||
}
|
||||
|
|
|
@ -55,13 +55,11 @@ public:
|
|||
// NOTE: These only exist for checkbox and radio input elements.
|
||||
virtual void legacy_pre_activation_behavior() { }
|
||||
virtual void legacy_cancelled_activation_behavior() { }
|
||||
virtual void legacy_cancelled_activation_behavior_was_not_called() { }
|
||||
|
||||
Bindings::CallbackType* event_handler_attribute(FlyString const& name);
|
||||
void set_event_handler_attribute(FlyString const& name, Optional<Bindings::CallbackType>);
|
||||
|
||||
// https://dom.spec.whatwg.org/#eventtarget-activation-behavior
|
||||
virtual void run_activation_behavior() { }
|
||||
|
||||
protected:
|
||||
EventTarget();
|
||||
|
||||
|
|
|
@ -23,33 +23,20 @@ namespace Web::HTML {
|
|||
HTMLInputElement::HTMLInputElement(DOM::Document& document, DOM::QualifiedName qualified_name)
|
||||
: FormAssociatedElement(document, move(qualified_name))
|
||||
{
|
||||
activation_behavior = [this](auto&) {
|
||||
// The activation behavior for input elements are these steps:
|
||||
|
||||
// FIXME: 1. If this element is not mutable and is not in the Checkbox state and is not in the Radio state, then return.
|
||||
|
||||
// 2. Run this element's input activation behavior, if any, and do nothing otherwise.
|
||||
run_input_activation_behavior();
|
||||
};
|
||||
}
|
||||
|
||||
HTMLInputElement::~HTMLInputElement()
|
||||
{
|
||||
}
|
||||
|
||||
void HTMLInputElement::did_click_button(Badge<Painting::ButtonPaintable>)
|
||||
{
|
||||
// FIXME: This should be a PointerEvent.
|
||||
dispatch_event(DOM::Event::create(EventNames::click));
|
||||
|
||||
if (type() == "submit") {
|
||||
if (auto* form = first_ancestor_of_type<HTMLFormElement>()) {
|
||||
form->submit_form(this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void HTMLInputElement::did_click_checkbox(Badge<Painting::CheckBoxPaintable>)
|
||||
{
|
||||
// FIXME: This should be a PointerEvent.
|
||||
auto click_event = DOM::Event::create(EventNames::click);
|
||||
click_event->set_bubbles(true);
|
||||
dispatch_event(move(click_event));
|
||||
}
|
||||
|
||||
RefPtr<Layout::Node> HTMLInputElement::create_layout_node(NonnullRefPtr<CSS::StyleProperties> style)
|
||||
{
|
||||
if (type() == "hidden")
|
||||
|
@ -69,7 +56,7 @@ RefPtr<Layout::Node> HTMLInputElement::create_layout_node(NonnullRefPtr<CSS::Sty
|
|||
return layout_node;
|
||||
}
|
||||
|
||||
void HTMLInputElement::set_checked(bool checked, ChangeSource change_source, ShouldRunActivationBehavior should_run_activation_behavior)
|
||||
void HTMLInputElement::set_checked(bool checked, ChangeSource change_source)
|
||||
{
|
||||
if (m_checked == checked)
|
||||
return;
|
||||
|
@ -81,25 +68,12 @@ void HTMLInputElement::set_checked(bool checked, ChangeSource change_source, Sho
|
|||
|
||||
m_checked = checked;
|
||||
set_needs_style_update(true);
|
||||
|
||||
if (should_run_activation_behavior == ShouldRunActivationBehavior::Yes)
|
||||
run_activation_behavior();
|
||||
}
|
||||
|
||||
void HTMLInputElement::run_activation_behavior()
|
||||
{
|
||||
// The activation behavior for input elements are these steps:
|
||||
|
||||
// FIXME: 1. If this element is not mutable and is not in the Checkbox state and is not in the Radio state, then return.
|
||||
|
||||
// 2. Run this element's input activation behavior, if any, and do nothing otherwise.
|
||||
run_input_activation_behavior();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/input.html#input-activation-behavior
|
||||
void HTMLInputElement::run_input_activation_behavior()
|
||||
{
|
||||
if (type() == "checkbox") {
|
||||
if (type() == "checkbox" || type() == "radio") {
|
||||
// 1. If the element is not connected, then return.
|
||||
if (!is_connected())
|
||||
return;
|
||||
|
@ -114,6 +88,18 @@ void HTMLInputElement::run_input_activation_behavior()
|
|||
auto change_event = DOM::Event::create(HTML::EventNames::change);
|
||||
change_event->set_bubbles(true);
|
||||
dispatch_event(move(change_event));
|
||||
} else if (type() == "submit") {
|
||||
RefPtr<HTMLFormElement> form;
|
||||
// 1. If the element does not have a form owner, then return.
|
||||
if (!(form = this->form()))
|
||||
return;
|
||||
|
||||
// 2. If the element's node document is not fully active, then return.
|
||||
if (!document().is_fully_active())
|
||||
return;
|
||||
|
||||
// 3. Submit the form owner from the element.
|
||||
form->submit_form(this);
|
||||
} else {
|
||||
dispatch_event(DOM::Event::create(EventNames::change));
|
||||
}
|
||||
|
@ -208,7 +194,7 @@ void HTMLInputElement::parse_attribute(FlyString const& name, String const& valu
|
|||
// When the checked content attribute is added, if the control does not have dirty checkedness,
|
||||
// the user agent must set the checkedness of the element to true
|
||||
if (!m_dirty_checkedness)
|
||||
set_checked(true, ChangeSource::Programmatic, ShouldRunActivationBehavior::No);
|
||||
set_checked(true, ChangeSource::Programmatic);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -219,7 +205,7 @@ void HTMLInputElement::did_remove_attribute(FlyString const& name)
|
|||
// When the checked content attribute is removed, if the control does not have dirty checkedness,
|
||||
// the user agent must set the checkedness of the element to false.
|
||||
if (!m_dirty_checkedness)
|
||||
set_checked(false, ChangeSource::Programmatic, ShouldRunActivationBehavior::No);
|
||||
set_checked(false, ChangeSource::Programmatic);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -300,4 +286,93 @@ void HTMLInputElement::inserted()
|
|||
create_shadow_tree_if_needed();
|
||||
}
|
||||
|
||||
void HTMLInputElement::set_checked_within_group()
|
||||
{
|
||||
if (checked())
|
||||
return;
|
||||
|
||||
set_checked(true, ChangeSource::User);
|
||||
String name = this->name();
|
||||
|
||||
document().for_each_in_inclusive_subtree_of_type<HTML::HTMLInputElement>([&](auto& element) {
|
||||
if (element.checked() && &element != this && element.name() == name)
|
||||
element.set_checked(false, ChangeSource::User);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/input.html#the-input-element:legacy-pre-activation-behavior
|
||||
void HTMLInputElement::legacy_pre_activation_behavior()
|
||||
{
|
||||
m_before_legacy_pre_activation_behavior_checked = checked();
|
||||
|
||||
// 1. If this element's type attribute is in the Checkbox state, then set
|
||||
// this element's checkedness to its opposite value (i.e. true if it is
|
||||
// false, false if it is true) and set this element's indeterminate IDL
|
||||
// attribute to false.
|
||||
// FIXME: Set indeterminate to false when that exists.
|
||||
if (type_state() == TypeAttributeState::Checkbox) {
|
||||
set_checked(!checked(), ChangeSource::User);
|
||||
}
|
||||
|
||||
// 2. If this element's type attribute is in the Radio Button state, then
|
||||
// get a reference to the element in this element's radio button group that
|
||||
// has its checkedness set to true, if any, and then set this element's
|
||||
// checkedness to true.
|
||||
if (type_state() == TypeAttributeState::RadioButton) {
|
||||
String name = this->name();
|
||||
|
||||
document().for_each_in_inclusive_subtree_of_type<HTML::HTMLInputElement>([&](auto& element) {
|
||||
if (element.checked() && element.name() == name) {
|
||||
m_legacy_pre_activation_behavior_checked_element_in_group = element;
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
set_checked_within_group();
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/input.html#the-input-element:legacy-canceled-activation-behavior
|
||||
void HTMLInputElement::legacy_cancelled_activation_behavior()
|
||||
{
|
||||
// 1. If the element's type attribute is in the Checkbox state, then set the
|
||||
// element's checkedness and the element's indeterminate IDL attribute back
|
||||
// to the values they had before the legacy-pre-activation behavior was run.
|
||||
if (type_state() == TypeAttributeState::Checkbox) {
|
||||
set_checked(m_before_legacy_pre_activation_behavior_checked, ChangeSource::Programmatic);
|
||||
}
|
||||
|
||||
// 2. If this element 's type attribute is in the Radio Button state, then
|
||||
// if the element to which a reference was obtained in the
|
||||
// legacy-pre-activation behavior, if any, is still in what is now this
|
||||
// element' s radio button group, if it still has one, and if so, setting
|
||||
// that element 's checkedness to true; or else, if there was no such
|
||||
// element, or that element is no longer in this element' s radio button
|
||||
// group, or if this element no longer has a radio button group, setting
|
||||
// this element's checkedness to false.
|
||||
if (type_state() == TypeAttributeState::RadioButton) {
|
||||
String name = this->name();
|
||||
bool did_reselect_previous_element = false;
|
||||
if (m_legacy_pre_activation_behavior_checked_element_in_group) {
|
||||
auto& element_in_group = *m_legacy_pre_activation_behavior_checked_element_in_group;
|
||||
if (name == element_in_group.name()) {
|
||||
element_in_group.set_checked_within_group();
|
||||
did_reselect_previous_element = true;
|
||||
}
|
||||
|
||||
m_legacy_pre_activation_behavior_checked_element_in_group = nullptr;
|
||||
}
|
||||
|
||||
if (!did_reselect_previous_element)
|
||||
set_checked(false, ChangeSource::User);
|
||||
}
|
||||
}
|
||||
|
||||
void HTMLInputElement::legacy_cancelled_activation_behavior_was_not_called()
|
||||
{
|
||||
m_legacy_pre_activation_behavior_checked_element_in_group = nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -67,14 +67,7 @@ public:
|
|||
Programmatic,
|
||||
User,
|
||||
};
|
||||
enum class ShouldRunActivationBehavior {
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
void set_checked(bool, ChangeSource = ChangeSource::Programmatic, ShouldRunActivationBehavior = ShouldRunActivationBehavior::Yes);
|
||||
|
||||
void did_click_button(Badge<Painting::ButtonPaintable>);
|
||||
void did_click_checkbox(Badge<Painting::CheckBoxPaintable>);
|
||||
void set_checked(bool, ChangeSource = ChangeSource::Programmatic);
|
||||
|
||||
void did_edit_text_node(Badge<BrowsingContext>);
|
||||
|
||||
|
@ -105,10 +98,13 @@ public:
|
|||
private:
|
||||
// ^DOM::EventTarget
|
||||
virtual void did_receive_focus() override;
|
||||
virtual void run_activation_behavior() override;
|
||||
virtual void legacy_pre_activation_behavior() override;
|
||||
virtual void legacy_cancelled_activation_behavior() override;
|
||||
virtual void legacy_cancelled_activation_behavior_was_not_called() override;
|
||||
|
||||
void create_shadow_tree_if_needed();
|
||||
void run_input_activation_behavior();
|
||||
void set_checked_within_group();
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/input.html#value-sanitization-algorithm
|
||||
String value_sanitization_algorithm(String) const;
|
||||
|
@ -118,6 +114,10 @@ private:
|
|||
|
||||
// https://html.spec.whatwg.org/multipage/input.html#concept-input-checked-dirty-flag
|
||||
bool m_dirty_checkedness { false };
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/input.html#the-input-element:legacy-pre-activation-behavior
|
||||
bool m_before_legacy_pre_activation_behavior_checked { false };
|
||||
RefPtr<HTMLInputElement> m_legacy_pre_activation_behavior_checked_element_in_group;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -7,13 +7,12 @@
|
|||
#include <LibGfx/Font.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/Layout/ButtonBox.h>
|
||||
#include <LibWeb/Layout/Label.h>
|
||||
#include <LibWeb/Painting/ButtonPaintable.h>
|
||||
|
||||
namespace Web::Layout {
|
||||
|
||||
ButtonBox::ButtonBox(DOM::Document& document, HTML::HTMLInputElement& element, NonnullRefPtr<CSS::StyleProperties> style)
|
||||
: LabelableNode(document, element, move(style))
|
||||
: FormAssociatedLabelableNode(document, element, move(style))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -23,8 +22,14 @@ ButtonBox::~ButtonBox()
|
|||
|
||||
void ButtonBox::prepare_for_replaced_layout()
|
||||
{
|
||||
set_intrinsic_width(font().width(dom_node().value()));
|
||||
set_intrinsic_height(font().glyph_height());
|
||||
// For <input type="submit" /> and <input type="button" />, the contents of
|
||||
// the button does not appear as the contents of the element but as the
|
||||
// value attribute. This is not the case with <button />, which contains
|
||||
// its contents normally.
|
||||
if (is<HTML::HTMLInputElement>(dom_node())) {
|
||||
set_intrinsic_width(font().width(static_cast<HTML::HTMLInputElement&>(dom_node()).value()));
|
||||
set_intrinsic_height(font().glyph_height());
|
||||
}
|
||||
}
|
||||
|
||||
RefPtr<Painting::Paintable> ButtonBox::create_paintable() const
|
||||
|
|
|
@ -7,20 +7,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <LibWeb/HTML/HTMLInputElement.h>
|
||||
#include <LibWeb/Layout/LabelableNode.h>
|
||||
#include <LibWeb/Layout/FormAssociatedLabelableNode.h>
|
||||
|
||||
namespace Web::Layout {
|
||||
|
||||
class ButtonBox : public LabelableNode {
|
||||
class ButtonBox : public FormAssociatedLabelableNode {
|
||||
public:
|
||||
ButtonBox(DOM::Document&, HTML::HTMLInputElement&, NonnullRefPtr<CSS::StyleProperties>);
|
||||
virtual ~ButtonBox() override;
|
||||
|
||||
virtual void prepare_for_replaced_layout() override;
|
||||
|
||||
const HTML::HTMLInputElement& dom_node() const { return static_cast<const HTML::HTMLInputElement&>(LabelableNode::dom_node()); }
|
||||
HTML::HTMLInputElement& dom_node() { return static_cast<HTML::HTMLInputElement&>(LabelableNode::dom_node()); }
|
||||
|
||||
private:
|
||||
virtual RefPtr<Painting::Paintable> create_paintable() const override;
|
||||
};
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
namespace Web::Layout {
|
||||
|
||||
CheckBox::CheckBox(DOM::Document& document, HTML::HTMLInputElement& element, NonnullRefPtr<CSS::StyleProperties> style)
|
||||
: LabelableNode(document, element, move(style))
|
||||
: FormAssociatedLabelableNode(document, element, move(style))
|
||||
{
|
||||
set_intrinsic_width(13);
|
||||
set_intrinsic_height(13);
|
||||
|
|
|
@ -7,18 +7,15 @@
|
|||
#pragma once
|
||||
|
||||
#include <LibWeb/HTML/HTMLInputElement.h>
|
||||
#include <LibWeb/Layout/LabelableNode.h>
|
||||
#include <LibWeb/Layout/FormAssociatedLabelableNode.h>
|
||||
|
||||
namespace Web::Layout {
|
||||
|
||||
class CheckBox : public LabelableNode {
|
||||
class CheckBox : public FormAssociatedLabelableNode {
|
||||
public:
|
||||
CheckBox(DOM::Document&, HTML::HTMLInputElement&, NonnullRefPtr<CSS::StyleProperties>);
|
||||
virtual ~CheckBox() override;
|
||||
|
||||
const HTML::HTMLInputElement& dom_node() const { return static_cast<const HTML::HTMLInputElement&>(LabelableNode::dom_node()); }
|
||||
HTML::HTMLInputElement& dom_node() { return static_cast<HTML::HTMLInputElement&>(LabelableNode::dom_node()); }
|
||||
|
||||
private:
|
||||
virtual RefPtr<Painting::Paintable> create_paintable() const override;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2022, sin-ack <sin-ack@protonmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/HTML/FormAssociatedElement.h>
|
||||
#include <LibWeb/Layout/LabelableNode.h>
|
||||
|
||||
namespace Web::Layout {
|
||||
|
||||
class FormAssociatedLabelableNode : public LabelableNode {
|
||||
public:
|
||||
const HTML::FormAssociatedElement& dom_node() const { return static_cast<const HTML::FormAssociatedElement&>(LabelableNode::dom_node()); }
|
||||
HTML::FormAssociatedElement& dom_node() { return static_cast<HTML::FormAssociatedElement&>(LabelableNode::dom_node()); }
|
||||
|
||||
protected:
|
||||
FormAssociatedLabelableNode(DOM::Document& document, HTML::FormAssociatedElement& element, NonnullRefPtr<CSS::StyleProperties> style)
|
||||
: LabelableNode(document, element, move(style))
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~FormAssociatedLabelableNode() = default;
|
||||
};
|
||||
|
||||
}
|
|
@ -26,11 +26,12 @@ public:
|
|||
void handle_mouseup_on_label(Badge<Painting::TextPaintable>, const Gfx::IntPoint&, unsigned button);
|
||||
void handle_mousemove_on_label(Badge<Painting::TextPaintable>, const Gfx::IntPoint&, unsigned button);
|
||||
|
||||
LabelableNode* labeled_control();
|
||||
|
||||
private:
|
||||
virtual bool is_label() const override { return true; }
|
||||
|
||||
static Label const* label_for_control_node(LabelableNode const&);
|
||||
LabelableNode* labeled_control();
|
||||
|
||||
bool m_tracking_mouse { false };
|
||||
};
|
||||
|
|
|
@ -7,14 +7,12 @@
|
|||
|
||||
#include <LibGUI/Event.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/Layout/Label.h>
|
||||
#include <LibWeb/Layout/RadioButton.h>
|
||||
#include <LibWeb/Painting/RadioButtonPaintable.h>
|
||||
|
||||
namespace Web::Layout {
|
||||
|
||||
RadioButton::RadioButton(DOM::Document& document, HTML::HTMLInputElement& element, NonnullRefPtr<CSS::StyleProperties> style)
|
||||
: LabelableNode(document, element, move(style))
|
||||
: FormAssociatedLabelableNode(document, element, move(style))
|
||||
{
|
||||
set_intrinsic_width(12);
|
||||
set_intrinsic_height(12);
|
||||
|
|
|
@ -7,18 +7,15 @@
|
|||
#pragma once
|
||||
|
||||
#include <LibWeb/HTML/HTMLInputElement.h>
|
||||
#include <LibWeb/Layout/LabelableNode.h>
|
||||
#include <LibWeb/Layout/FormAssociatedLabelableNode.h>
|
||||
|
||||
namespace Web::Layout {
|
||||
|
||||
class RadioButton final : public LabelableNode {
|
||||
class RadioButton final : public FormAssociatedLabelableNode {
|
||||
public:
|
||||
RadioButton(DOM::Document&, HTML::HTMLInputElement&, NonnullRefPtr<CSS::StyleProperties>);
|
||||
virtual ~RadioButton() override;
|
||||
|
||||
const HTML::HTMLInputElement& dom_node() const { return static_cast<const HTML::HTMLInputElement&>(LabelableNode::dom_node()); }
|
||||
HTML::HTMLInputElement& dom_node() { return static_cast<HTML::HTMLInputElement&>(LabelableNode::dom_node()); }
|
||||
|
||||
private:
|
||||
virtual RefPtr<Painting::Paintable> create_paintable() const override;
|
||||
};
|
||||
|
|
|
@ -154,37 +154,45 @@ bool EventHandler::handle_mouseup(const Gfx::IntPoint& position, unsigned button
|
|||
if (!paint_root())
|
||||
return false;
|
||||
|
||||
if (m_mouse_event_tracking_layout_node) {
|
||||
m_mouse_event_tracking_layout_node->paintable()->handle_mouseup({}, position, button, modifiers);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool handled_event = false;
|
||||
|
||||
auto result = paint_root()->hit_test(position, Painting::HitTestType::Exact);
|
||||
RefPtr<Painting::Paintable> paintable;
|
||||
if (m_mouse_event_tracking_layout_node) {
|
||||
paintable = m_mouse_event_tracking_layout_node->paintable();
|
||||
} else {
|
||||
auto result = paint_root()->hit_test(position, Painting::HitTestType::Exact);
|
||||
paintable = result.paintable;
|
||||
}
|
||||
|
||||
if (result.paintable && result.paintable->wants_mouse_events()) {
|
||||
result.paintable->handle_mouseup({}, position, button, modifiers);
|
||||
if (paintable && paintable->wants_mouse_events()) {
|
||||
if (paintable->handle_mouseup({}, position, button, modifiers) == Painting::Paintable::DispatchEventOfSameName::No)
|
||||
return false;
|
||||
|
||||
// Things may have changed as a consequence of Layout::Node::handle_mouseup(). Hit test again.
|
||||
if (!paint_root())
|
||||
return true;
|
||||
result = paint_root()->hit_test(position, Painting::HitTestType::Exact);
|
||||
auto result = paint_root()->hit_test(position, Painting::HitTestType::Exact);
|
||||
paintable = result.paintable;
|
||||
}
|
||||
|
||||
if (result.paintable && result.paintable->layout_node().dom_node()) {
|
||||
RefPtr<DOM::Node> node = result.paintable->layout_node().dom_node();
|
||||
if (is<HTML::HTMLIFrameElement>(*node)) {
|
||||
if (auto* nested_browsing_context = static_cast<HTML::HTMLIFrameElement&>(*node).nested_browsing_context())
|
||||
return nested_browsing_context->event_handler().handle_mouseup(position.translated(compute_mouse_event_offset({}, result.paintable->layout_node())), button, modifiers);
|
||||
return false;
|
||||
}
|
||||
auto offset = compute_mouse_event_offset(position, result.paintable->layout_node());
|
||||
node->dispatch_event(UIEvents::MouseEvent::create(UIEvents::EventNames::mouseup, offset.x(), offset.y(), position.x(), position.y()));
|
||||
handled_event = true;
|
||||
if (paintable) {
|
||||
RefPtr<DOM::Node> node = paintable->mouse_event_target();
|
||||
if (!node)
|
||||
node = paintable->layout_node().dom_node();
|
||||
|
||||
if (node.ptr() == m_mousedown_target) {
|
||||
node->dispatch_event(UIEvents::MouseEvent::create(UIEvents::EventNames::click, offset.x(), offset.y(), position.x(), position.y()));
|
||||
if (node) {
|
||||
if (is<HTML::HTMLIFrameElement>(*node)) {
|
||||
if (auto* nested_browsing_context = static_cast<HTML::HTMLIFrameElement&>(*node).nested_browsing_context())
|
||||
return nested_browsing_context->event_handler().handle_mouseup(position.translated(compute_mouse_event_offset({}, paintable->layout_node())), button, modifiers);
|
||||
return false;
|
||||
}
|
||||
auto offset = compute_mouse_event_offset(position, paintable->layout_node());
|
||||
node->dispatch_event(UIEvents::MouseEvent::create(UIEvents::EventNames::mouseup, offset.x(), offset.y(), position.x(), position.y()));
|
||||
handled_event = true;
|
||||
|
||||
if (node.ptr() == m_mousedown_target) {
|
||||
node->dispatch_event(UIEvents::MouseEvent::create(UIEvents::EventNames::click, offset.x(), offset.y(), position.x(), position.y()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,31 +206,34 @@ bool EventHandler::handle_mousedown(const Gfx::IntPoint& position, unsigned butt
|
|||
if (!paint_root())
|
||||
return false;
|
||||
|
||||
if (m_mouse_event_tracking_layout_node) {
|
||||
m_mouse_event_tracking_layout_node->paintable()->handle_mousedown({}, position, button, modifiers);
|
||||
return true;
|
||||
}
|
||||
|
||||
NonnullRefPtr document = *m_browsing_context.active_document();
|
||||
RefPtr<DOM::Node> node;
|
||||
|
||||
{
|
||||
// TODO: Allow selecting element behind if one on top has pointer-events set to none.
|
||||
auto result = paint_root()->hit_test(position, Painting::HitTestType::Exact);
|
||||
if (!result.paintable)
|
||||
return false;
|
||||
RefPtr<Painting::Paintable> paintable;
|
||||
if (m_mouse_event_tracking_layout_node) {
|
||||
paintable = m_mouse_event_tracking_layout_node->paintable();
|
||||
} else {
|
||||
auto result = paint_root()->hit_test(position, Painting::HitTestType::Exact);
|
||||
if (!result.paintable)
|
||||
return false;
|
||||
paintable = result.paintable;
|
||||
}
|
||||
|
||||
auto pointer_events = result.paintable->computed_values().pointer_events();
|
||||
auto pointer_events = paintable->computed_values().pointer_events();
|
||||
// FIXME: Handle other values for pointer-events.
|
||||
if (pointer_events == CSS::PointerEvents::None)
|
||||
return false;
|
||||
|
||||
node = result.paintable->layout_node().dom_node();
|
||||
node = paintable->mouse_event_target();
|
||||
if (!node)
|
||||
node = paintable->layout_node().dom_node();
|
||||
document->set_hovered_node(node);
|
||||
|
||||
if (result.paintable->wants_mouse_events()) {
|
||||
result.paintable->handle_mousedown({}, position, button, modifiers);
|
||||
return true;
|
||||
if (paintable->wants_mouse_events()) {
|
||||
if (paintable->handle_mousedown({}, position, button, modifiers) == Painting::Paintable::DispatchEventOfSameName::No)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!node)
|
||||
|
@ -230,15 +241,15 @@ bool EventHandler::handle_mousedown(const Gfx::IntPoint& position, unsigned butt
|
|||
|
||||
if (is<HTML::HTMLIFrameElement>(*node)) {
|
||||
if (auto* nested_browsing_context = static_cast<HTML::HTMLIFrameElement&>(*node).nested_browsing_context())
|
||||
return nested_browsing_context->event_handler().handle_mousedown(position.translated(compute_mouse_event_offset({}, result.paintable->layout_node())), button, modifiers);
|
||||
return nested_browsing_context->event_handler().handle_mousedown(position.translated(compute_mouse_event_offset({}, paintable->layout_node())), button, modifiers);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (auto* page = m_browsing_context.page())
|
||||
page->set_focused_browsing_context({}, m_browsing_context);
|
||||
|
||||
auto offset = compute_mouse_event_offset(position, result.paintable->layout_node());
|
||||
m_mousedown_target = node;
|
||||
auto offset = compute_mouse_event_offset(position, paintable->layout_node());
|
||||
node->dispatch_event(UIEvents::MouseEvent::create(UIEvents::EventNames::mousedown, offset.x(), offset.y(), position.x(), position.y()));
|
||||
}
|
||||
|
||||
|
@ -316,37 +327,45 @@ bool EventHandler::handle_mousemove(const Gfx::IntPoint& position, unsigned butt
|
|||
if (!paint_root())
|
||||
return false;
|
||||
|
||||
if (m_mouse_event_tracking_layout_node) {
|
||||
m_mouse_event_tracking_layout_node->paintable()->handle_mousemove({}, position, buttons, modifiers);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto& document = *m_browsing_context.active_document();
|
||||
|
||||
bool hovered_node_changed = false;
|
||||
bool is_hovering_link = false;
|
||||
Gfx::StandardCursor hovered_node_cursor = Gfx::StandardCursor::None;
|
||||
auto result = paint_root()->hit_test(position, Painting::HitTestType::Exact);
|
||||
|
||||
RefPtr<Painting::Paintable> paintable;
|
||||
Optional<int> start_index;
|
||||
if (m_mouse_event_tracking_layout_node) {
|
||||
paintable = m_mouse_event_tracking_layout_node->paintable();
|
||||
} else {
|
||||
auto result = paint_root()->hit_test(position, Painting::HitTestType::Exact);
|
||||
paintable = result.paintable;
|
||||
start_index = result.index_in_node;
|
||||
}
|
||||
|
||||
const HTML::HTMLAnchorElement* hovered_link_element = nullptr;
|
||||
if (result.paintable) {
|
||||
if (result.paintable->wants_mouse_events()) {
|
||||
document.set_hovered_node(result.paintable->layout_node().dom_node());
|
||||
result.paintable->handle_mousemove({}, position, buttons, modifiers);
|
||||
if (paintable) {
|
||||
if (paintable->wants_mouse_events()) {
|
||||
document.set_hovered_node(paintable->layout_node().dom_node());
|
||||
if (paintable->handle_mousemove({}, position, buttons, modifiers) == Painting::Paintable::DispatchEventOfSameName::No)
|
||||
return false;
|
||||
|
||||
// FIXME: It feels a bit aggressive to always update the cursor like this.
|
||||
if (auto* page = m_browsing_context.page())
|
||||
page->client().page_did_request_cursor_change(Gfx::StandardCursor::None);
|
||||
return true;
|
||||
}
|
||||
|
||||
RefPtr<DOM::Node> node = result.paintable->layout_node().dom_node();
|
||||
RefPtr<DOM::Node> node = paintable->mouse_event_target();
|
||||
if (!node)
|
||||
node = paintable->layout_node().dom_node();
|
||||
|
||||
if (node && is<HTML::HTMLIFrameElement>(*node)) {
|
||||
if (auto* nested_browsing_context = static_cast<HTML::HTMLIFrameElement&>(*node).nested_browsing_context())
|
||||
return nested_browsing_context->event_handler().handle_mousemove(position.translated(compute_mouse_event_offset({}, result.paintable->layout_node())), buttons, modifiers);
|
||||
return nested_browsing_context->event_handler().handle_mousemove(position.translated(compute_mouse_event_offset({}, paintable->layout_node())), buttons, modifiers);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto pointer_events = result.paintable->computed_values().pointer_events();
|
||||
auto pointer_events = paintable->computed_values().pointer_events();
|
||||
// FIXME: Handle other values for pointer-events.
|
||||
if (pointer_events == CSS::PointerEvents::None)
|
||||
return false;
|
||||
|
@ -359,20 +378,20 @@ bool EventHandler::handle_mousemove(const Gfx::IntPoint& position, unsigned butt
|
|||
is_hovering_link = true;
|
||||
|
||||
if (node->is_text()) {
|
||||
auto cursor = result.paintable->computed_values().cursor();
|
||||
auto cursor = paintable->computed_values().cursor();
|
||||
if (cursor == CSS::Cursor::Auto)
|
||||
hovered_node_cursor = Gfx::StandardCursor::IBeam;
|
||||
else
|
||||
hovered_node_cursor = cursor_css_to_gfx(cursor);
|
||||
} else if (node->is_element()) {
|
||||
auto cursor = result.paintable->computed_values().cursor();
|
||||
auto cursor = paintable->computed_values().cursor();
|
||||
if (cursor == CSS::Cursor::Auto)
|
||||
hovered_node_cursor = Gfx::StandardCursor::Arrow;
|
||||
else
|
||||
hovered_node_cursor = cursor_css_to_gfx(cursor);
|
||||
}
|
||||
|
||||
auto offset = compute_mouse_event_offset(position, result.paintable->layout_node());
|
||||
auto offset = compute_mouse_event_offset(position, paintable->layout_node());
|
||||
node->dispatch_event(UIEvents::MouseEvent::create(UIEvents::EventNames::mousemove, offset.x(), offset.y(), position.x(), position.y()));
|
||||
// NOTE: Dispatching an event may have disturbed the world.
|
||||
if (!paint_root() || paint_root() != node->document().paint_box())
|
||||
|
@ -380,8 +399,8 @@ bool EventHandler::handle_mousemove(const Gfx::IntPoint& position, unsigned butt
|
|||
}
|
||||
if (m_in_mouse_selection) {
|
||||
auto hit = paint_root()->hit_test(position, Painting::HitTestType::TextCursor);
|
||||
if (hit.paintable && hit.paintable->layout_node().dom_node()) {
|
||||
m_browsing_context.set_cursor_position(DOM::Position(*hit.paintable->layout_node().dom_node(), result.index_in_node));
|
||||
if (start_index.has_value() && hit.paintable && hit.paintable->layout_node().dom_node()) {
|
||||
m_browsing_context.set_cursor_position(DOM::Position(*hit.paintable->layout_node().dom_node(), *start_index));
|
||||
layout_root()->set_selection_end({ hit.paintable->layout_node(), hit.index_in_node });
|
||||
}
|
||||
if (auto* page = m_browsing_context.page())
|
||||
|
|
|
@ -40,87 +40,13 @@ void ButtonPaintable::paint(PaintContext& context, PaintPhase phase) const
|
|||
|
||||
PaintableBox::paint(context, phase);
|
||||
|
||||
if (phase == PaintPhase::Foreground) {
|
||||
auto const& dom_node = layout_box().dom_node();
|
||||
if (is<HTML::HTMLInputElement>(dom_node) && phase == PaintPhase::Foreground) {
|
||||
auto text_rect = enclosing_int_rect(absolute_rect());
|
||||
if (m_being_pressed)
|
||||
if (being_pressed())
|
||||
text_rect.translate_by(1, 1);
|
||||
context.painter().draw_text(text_rect, layout_box().dom_node().value(), layout_box().font(), Gfx::TextAlignment::Center, computed_values().color());
|
||||
context.painter().draw_text(text_rect, static_cast<HTML::HTMLInputElement const&>(dom_node).value(), layout_box().font(), Gfx::TextAlignment::Center, computed_values().color());
|
||||
}
|
||||
}
|
||||
|
||||
void ButtonPaintable::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 ButtonPaintable::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: Handling the click may run arbitrary JS, which could disappear this node.
|
||||
NonnullRefPtr protected_this = *this;
|
||||
NonnullRefPtr protected_browsing_context = browsing_context();
|
||||
|
||||
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)
|
||||
const_cast<Layout::ButtonBox&>(layout_box()).dom_node().did_click_button({});
|
||||
|
||||
m_being_pressed = false;
|
||||
m_tracking_mouse = false;
|
||||
|
||||
protected_browsing_context->event_handler().set_mouse_event_tracking_layout_node(nullptr);
|
||||
}
|
||||
|
||||
void ButtonPaintable::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 ButtonPaintable::handle_associated_label_mousedown(Badge<Layout::Label>)
|
||||
{
|
||||
m_being_pressed = true;
|
||||
set_needs_display();
|
||||
}
|
||||
|
||||
void ButtonPaintable::handle_associated_label_mouseup(Badge<Layout::Label>)
|
||||
{
|
||||
// NOTE: Handling the click may run arbitrary JS, which could disappear this node.
|
||||
NonnullRefPtr protected_this = *this;
|
||||
NonnullRefPtr protected_browsing_context = browsing_context();
|
||||
|
||||
layout_box().dom_node().did_click_button({});
|
||||
m_being_pressed = false;
|
||||
}
|
||||
|
||||
void ButtonPaintable::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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,20 +20,8 @@ public:
|
|||
Layout::ButtonBox const& layout_box() const;
|
||||
Layout::ButtonBox& layout_box();
|
||||
|
||||
virtual bool wants_mouse_events() const override { return true; }
|
||||
virtual void handle_mousedown(Badge<EventHandler>, Gfx::IntPoint const&, unsigned button, unsigned modifiers) override;
|
||||
virtual void handle_mouseup(Badge<EventHandler>, Gfx::IntPoint const&, unsigned button, unsigned modifiers) override;
|
||||
virtual void handle_mousemove(Badge<EventHandler>, Gfx::IntPoint const&, unsigned buttons, unsigned modifiers) override;
|
||||
|
||||
private:
|
||||
ButtonPaintable(Layout::ButtonBox const&);
|
||||
|
||||
virtual void handle_associated_label_mousedown(Badge<Layout::Label>) override;
|
||||
virtual void handle_associated_label_mouseup(Badge<Layout::Label>) override;
|
||||
virtual void handle_associated_label_mousemove(Badge<Layout::Label>, bool is_inside_node_or_label) override;
|
||||
|
||||
bool m_being_pressed { false };
|
||||
bool m_tracking_mouse { false };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -41,89 +41,9 @@ void CheckBoxPaintable::paint(PaintContext& context, PaintPhase phase) const
|
|||
|
||||
PaintableBox::paint(context, phase);
|
||||
|
||||
auto const& checkbox = static_cast<HTML::HTMLInputElement const&>(layout_box().dom_node());
|
||||
if (phase == PaintPhase::Foreground)
|
||||
Gfx::StylePainter::paint_check_box(context.painter(), enclosing_int_rect(absolute_rect()), context.palette(), layout_box().dom_node().enabled(), layout_box().dom_node().checked(), m_being_pressed);
|
||||
}
|
||||
|
||||
void CheckBoxPaintable::handle_mousedown(Badge<EventHandler>, Gfx::IntPoint const&, 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 CheckBoxPaintable::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) {
|
||||
layout_box().dom_node().did_click_checkbox({});
|
||||
layout_box().dom_node().set_checked(!layout_box().dom_node().checked(), HTML::HTMLInputElement::ChangeSource::User);
|
||||
}
|
||||
|
||||
m_being_pressed = false;
|
||||
m_tracking_mouse = false;
|
||||
browsing_context().event_handler().set_mouse_event_tracking_layout_node(nullptr);
|
||||
}
|
||||
|
||||
void CheckBoxPaintable::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 CheckBoxPaintable::handle_associated_label_mousedown(Badge<Layout::Label>)
|
||||
{
|
||||
if (!layout_box().dom_node().enabled())
|
||||
return;
|
||||
|
||||
m_being_pressed = true;
|
||||
set_needs_display();
|
||||
}
|
||||
|
||||
void CheckBoxPaintable::handle_associated_label_mouseup(Badge<Layout::Label>)
|
||||
{
|
||||
if (!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;
|
||||
|
||||
layout_box().dom_node().did_click_checkbox({});
|
||||
layout_box().dom_node().set_checked(!layout_box().dom_node().checked(), HTML::HTMLInputElement::ChangeSource::User);
|
||||
m_being_pressed = false;
|
||||
}
|
||||
|
||||
void CheckBoxPaintable::handle_associated_label_mousemove(Badge<Layout::Label>, bool is_inside_node_or_label)
|
||||
{
|
||||
if (m_being_pressed == is_inside_node_or_label || !layout_box().dom_node().enabled())
|
||||
return;
|
||||
|
||||
m_being_pressed = is_inside_node_or_label;
|
||||
set_needs_display();
|
||||
Gfx::StylePainter::paint_check_box(context.painter(), enclosing_int_rect(absolute_rect()), context.palette(), layout_box().dom_node().enabled(), checkbox.checked(), being_pressed());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,20 +20,8 @@ public:
|
|||
Layout::CheckBox const& layout_box() const;
|
||||
Layout::CheckBox& layout_box();
|
||||
|
||||
virtual bool wants_mouse_events() const override { return true; }
|
||||
virtual void handle_mousedown(Badge<EventHandler>, Gfx::IntPoint const&, unsigned button, unsigned modifiers) override;
|
||||
virtual void handle_mouseup(Badge<EventHandler>, Gfx::IntPoint const&, unsigned button, unsigned modifiers) override;
|
||||
virtual void handle_mousemove(Badge<EventHandler>, Gfx::IntPoint const&, unsigned buttons, unsigned modifiers) override;
|
||||
|
||||
private:
|
||||
CheckBoxPaintable(Layout::CheckBox const&);
|
||||
|
||||
virtual void handle_associated_label_mousedown(Badge<Layout::Label>) override;
|
||||
virtual void handle_associated_label_mouseup(Badge<Layout::Label>) override;
|
||||
virtual void handle_associated_label_mousemove(Badge<Layout::Label>, bool is_inside_node_or_label) override;
|
||||
|
||||
bool m_being_pressed { false };
|
||||
bool m_tracking_mouse { false };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -1,26 +1,99 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2022, sin-ack <sin-ack@protonmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibGUI/Event.h>
|
||||
#include <LibWeb/HTML/BrowsingContext.h>
|
||||
#include <LibWeb/Layout/Label.h>
|
||||
#include <LibWeb/Painting/LabelablePaintable.h>
|
||||
|
||||
namespace Web::Painting {
|
||||
|
||||
Layout::LabelableNode const& LabelablePaintable::layout_box() const
|
||||
{
|
||||
return static_cast<Layout::LabelableNode const&>(PaintableBox::layout_box());
|
||||
}
|
||||
|
||||
Layout::LabelableNode& LabelablePaintable::layout_box()
|
||||
{
|
||||
return static_cast<Layout::LabelableNode&>(PaintableBox::layout_box());
|
||||
}
|
||||
|
||||
LabelablePaintable::LabelablePaintable(Layout::LabelableNode const& layout_node)
|
||||
: PaintableBox(layout_node)
|
||||
{
|
||||
}
|
||||
|
||||
void LabelablePaintable::set_being_pressed(bool being_pressed)
|
||||
{
|
||||
if (m_being_pressed == being_pressed)
|
||||
return;
|
||||
m_being_pressed = being_pressed;
|
||||
set_needs_display();
|
||||
}
|
||||
|
||||
Layout::FormAssociatedLabelableNode const& LabelablePaintable::layout_box() const
|
||||
{
|
||||
return static_cast<Layout::FormAssociatedLabelableNode const&>(PaintableBox::layout_box());
|
||||
}
|
||||
|
||||
Layout::FormAssociatedLabelableNode& LabelablePaintable::layout_box()
|
||||
{
|
||||
return static_cast<Layout::FormAssociatedLabelableNode&>(PaintableBox::layout_box());
|
||||
}
|
||||
|
||||
LabelablePaintable::DispatchEventOfSameName LabelablePaintable::handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned)
|
||||
{
|
||||
if (button != GUI::MouseButton::Primary || !layout_box().dom_node().enabled())
|
||||
return DispatchEventOfSameName::No;
|
||||
|
||||
set_being_pressed(true);
|
||||
m_tracking_mouse = true;
|
||||
browsing_context().event_handler().set_mouse_event_tracking_layout_node(&layout_box());
|
||||
return DispatchEventOfSameName::Yes;
|
||||
}
|
||||
|
||||
LabelablePaintable::DispatchEventOfSameName LabelablePaintable::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 DispatchEventOfSameName::No;
|
||||
|
||||
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);
|
||||
|
||||
set_being_pressed(false);
|
||||
m_tracking_mouse = false;
|
||||
browsing_context().event_handler().set_mouse_event_tracking_layout_node(nullptr);
|
||||
return DispatchEventOfSameName::Yes;
|
||||
}
|
||||
|
||||
LabelablePaintable::DispatchEventOfSameName LabelablePaintable::handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned, unsigned)
|
||||
{
|
||||
if (!m_tracking_mouse || !layout_box().dom_node().enabled())
|
||||
return DispatchEventOfSameName::No;
|
||||
|
||||
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);
|
||||
|
||||
set_being_pressed(is_inside_node_or_label);
|
||||
return DispatchEventOfSameName::Yes;
|
||||
}
|
||||
|
||||
void LabelablePaintable::handle_associated_label_mousedown(Badge<Layout::Label>)
|
||||
{
|
||||
set_being_pressed(true);
|
||||
}
|
||||
|
||||
void LabelablePaintable::handle_associated_label_mouseup(Badge<Layout::Label>)
|
||||
{
|
||||
// NOTE: Handling the click may run arbitrary JS, which could disappear this node.
|
||||
NonnullRefPtr protected_this = *this;
|
||||
NonnullRefPtr protected_browsing_context = browsing_context();
|
||||
|
||||
set_being_pressed(false);
|
||||
}
|
||||
|
||||
void LabelablePaintable::handle_associated_label_mousemove(Badge<Layout::Label>, bool is_inside_node_or_label)
|
||||
{
|
||||
if (being_pressed() == is_inside_node_or_label)
|
||||
return;
|
||||
|
||||
set_being_pressed(is_inside_node_or_label);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,27 +1,44 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2022, sin-ack <sin-ack@protonmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/Layout/LabelableNode.h>
|
||||
#include <LibWeb/HTML/FormAssociatedElement.h>
|
||||
#include <LibWeb/Layout/FormAssociatedLabelableNode.h>
|
||||
#include <LibWeb/Painting/PaintableBox.h>
|
||||
|
||||
namespace Web::Painting {
|
||||
|
||||
// FIXME: Splinter this into FormAssociatedLabelablePaintable once
|
||||
// ProgressPaintable switches over to this.
|
||||
class LabelablePaintable : public PaintableBox {
|
||||
public:
|
||||
Layout::LabelableNode const& layout_box() const;
|
||||
Layout::LabelableNode& layout_box();
|
||||
Layout::FormAssociatedLabelableNode const& layout_box() const;
|
||||
Layout::FormAssociatedLabelableNode& layout_box();
|
||||
|
||||
virtual void handle_associated_label_mousedown(Badge<Layout::Label>) { }
|
||||
virtual void handle_associated_label_mouseup(Badge<Layout::Label>) { }
|
||||
virtual void handle_associated_label_mousemove(Badge<Layout::Label>, [[maybe_unused]] bool is_inside_node_or_label) { }
|
||||
virtual bool wants_mouse_events() const override { return true; }
|
||||
virtual DispatchEventOfSameName handle_mousedown(Badge<EventHandler>, Gfx::IntPoint const&, unsigned button, unsigned modifiers) override;
|
||||
virtual DispatchEventOfSameName handle_mouseup(Badge<EventHandler>, Gfx::IntPoint const&, unsigned button, unsigned modifiers) override;
|
||||
virtual DispatchEventOfSameName handle_mousemove(Badge<EventHandler>, Gfx::IntPoint const&, unsigned buttons, unsigned modifiers) override;
|
||||
|
||||
void handle_associated_label_mousedown(Badge<Layout::Label>);
|
||||
void handle_associated_label_mouseup(Badge<Layout::Label>);
|
||||
void handle_associated_label_mousemove(Badge<Layout::Label>, bool is_inside_node_or_label);
|
||||
|
||||
bool being_pressed() const { return m_being_pressed; }
|
||||
// NOTE: Only the HTML node associated with this paintable should call this!
|
||||
void set_being_pressed(bool being_pressed);
|
||||
|
||||
protected:
|
||||
LabelablePaintable(Layout::LabelableNode const&);
|
||||
|
||||
private:
|
||||
bool m_being_pressed { false };
|
||||
bool m_tracking_mouse { false };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -10,16 +10,19 @@
|
|||
|
||||
namespace Web::Painting {
|
||||
|
||||
void Paintable::handle_mousedown(Badge<EventHandler>, Gfx::IntPoint const&, unsigned, unsigned)
|
||||
Paintable::DispatchEventOfSameName Paintable::handle_mousedown(Badge<EventHandler>, Gfx::IntPoint const&, unsigned, unsigned)
|
||||
{
|
||||
return DispatchEventOfSameName::Yes;
|
||||
}
|
||||
|
||||
void Paintable::handle_mouseup(Badge<EventHandler>, Gfx::IntPoint const&, unsigned, unsigned)
|
||||
Paintable::DispatchEventOfSameName Paintable::handle_mouseup(Badge<EventHandler>, Gfx::IntPoint const&, unsigned, unsigned)
|
||||
{
|
||||
return DispatchEventOfSameName::Yes;
|
||||
}
|
||||
|
||||
void Paintable::handle_mousemove(Badge<EventHandler>, Gfx::IntPoint const&, unsigned, unsigned)
|
||||
Paintable::DispatchEventOfSameName Paintable::handle_mousemove(Badge<EventHandler>, Gfx::IntPoint const&, unsigned, unsigned)
|
||||
{
|
||||
return DispatchEventOfSameName::Yes;
|
||||
}
|
||||
|
||||
bool Paintable::handle_mousewheel(Badge<EventHandler>, Gfx::IntPoint const&, unsigned, unsigned, int wheel_delta_x, int wheel_delta_y)
|
||||
|
|
|
@ -53,9 +53,19 @@ public:
|
|||
virtual HitTestResult hit_test(Gfx::IntPoint const&, HitTestType) const;
|
||||
|
||||
virtual bool wants_mouse_events() const { return false; }
|
||||
virtual void handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers);
|
||||
virtual void handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers);
|
||||
virtual void handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint&, unsigned buttons, unsigned modifiers);
|
||||
|
||||
enum class DispatchEventOfSameName {
|
||||
Yes,
|
||||
No,
|
||||
};
|
||||
// When these methods return true, the DOM event with the same name will be
|
||||
// dispatch at the mouse_event_target if it returns a valid DOM::Node, or
|
||||
// the layout node's associated DOM node if it doesn't.
|
||||
virtual DispatchEventOfSameName handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers);
|
||||
virtual DispatchEventOfSameName handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers);
|
||||
virtual DispatchEventOfSameName handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint&, unsigned buttons, unsigned modifiers);
|
||||
virtual DOM::Node* mouse_event_target() const { return nullptr; }
|
||||
|
||||
virtual bool handle_mousewheel(Badge<EventHandler>, const Gfx::IntPoint&, unsigned buttons, unsigned modifiers, int wheel_delta_x, int wheel_delta_y);
|
||||
|
||||
Layout::Node const& layout_node() const { return m_layout_node; }
|
||||
|
|
|
@ -11,6 +11,9 @@
|
|||
|
||||
namespace Web::Painting {
|
||||
|
||||
// FIXME: ProgressPaintable should inherit from LabelablePaintable, as it is a LabelableNode.
|
||||
// LabelablePaintable should be split into FormAssociatedLabelablePaintable once this
|
||||
// happens.
|
||||
class ProgressPaintable final : public PaintableBox {
|
||||
public:
|
||||
static NonnullRefPtr<ProgressPaintable> create(Layout::Progress const&);
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
#include <LibGfx/StylePainter.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/HTML/BrowsingContext.h>
|
||||
#include <LibWeb/HTML/HTMLImageElement.h>
|
||||
#include <LibWeb/HTML/HTMLInputElement.h>
|
||||
#include <LibWeb/Layout/Label.h>
|
||||
#include <LibWeb/Layout/RadioButton.h>
|
||||
#include <LibWeb/Painting/RadioButtonPaintable.h>
|
||||
|
@ -25,16 +25,6 @@ RadioButtonPaintable::RadioButtonPaintable(Layout::RadioButton const& layout_box
|
|||
{
|
||||
}
|
||||
|
||||
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())
|
||||
|
@ -42,95 +32,9 @@ void RadioButtonPaintable::paint(PaintContext& context, PaintPhase phase) const
|
|||
|
||||
PaintableBox::paint(context, phase);
|
||||
|
||||
auto const& radio_box = static_cast<HTML::HTMLInputElement const&>(layout_box().dom_node());
|
||||
if (phase == PaintPhase::Foreground)
|
||||
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;
|
||||
});
|
||||
Gfx::StylePainter::paint_radio_button(context.painter(), enclosing_int_rect(absolute_rect()), context.palette(), radio_box.checked(), being_pressed());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,25 +17,8 @@ public:
|
|||
|
||||
virtual void paint(PaintContext&, PaintPhase) const override;
|
||||
|
||||
Layout::RadioButton const& layout_box() const;
|
||||
Layout::RadioButton& layout_box();
|
||||
|
||||
virtual bool wants_mouse_events() const override { return true; }
|
||||
virtual void handle_mousedown(Badge<EventHandler>, Gfx::IntPoint const&, unsigned button, unsigned modifiers) override;
|
||||
virtual void handle_mouseup(Badge<EventHandler>, Gfx::IntPoint const&, unsigned button, unsigned modifiers) override;
|
||||
virtual void handle_mousemove(Badge<EventHandler>, Gfx::IntPoint const&, unsigned buttons, unsigned modifiers) override;
|
||||
|
||||
private:
|
||||
RadioButtonPaintable(Layout::RadioButton const&);
|
||||
|
||||
virtual void handle_associated_label_mousedown(Badge<Layout::Label>) override;
|
||||
virtual void handle_associated_label_mouseup(Badge<Layout::Label>) override;
|
||||
virtual void handle_associated_label_mousemove(Badge<Layout::Label>, bool is_inside_node_or_label) override;
|
||||
|
||||
void set_checked_within_group();
|
||||
|
||||
bool m_being_pressed { false };
|
||||
bool m_tracking_mouse { false };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <LibWeb/HTML/BrowsingContext.h>
|
||||
#include <LibWeb/Layout/Label.h>
|
||||
#include <LibWeb/Layout/LabelableNode.h>
|
||||
#include <LibWeb/Page/EventHandler.h>
|
||||
#include <LibWeb/Painting/TextPaintable.h>
|
||||
|
||||
|
@ -26,34 +27,43 @@ bool TextPaintable::wants_mouse_events() const
|
|||
return layout_node().first_ancestor_of_type<Layout::Label>();
|
||||
}
|
||||
|
||||
void TextPaintable::handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned button, unsigned)
|
||||
DOM::Node* TextPaintable::mouse_event_target() const
|
||||
{
|
||||
auto* label = layout_node().first_ancestor_of_type<Layout::Label>();
|
||||
if (!label)
|
||||
return;
|
||||
const_cast<Layout::Label*>(label)->handle_mousedown_on_label({}, position, button);
|
||||
const_cast<HTML::BrowsingContext&>(browsing_context()).event_handler().set_mouse_event_tracking_layout_node(&const_cast<Layout::TextNode&>(layout_node()));
|
||||
if (auto* label = layout_node().first_ancestor_of_type<Layout::Label>()) {
|
||||
if (auto* control = const_cast<Layout::Label*>(label)->labeled_control())
|
||||
return &control->dom_node();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void TextPaintable::handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned button, unsigned)
|
||||
TextPaintable::DispatchEventOfSameName TextPaintable::handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned button, unsigned)
|
||||
{
|
||||
auto* label = layout_node().first_ancestor_of_type<Layout::Label>();
|
||||
if (!label)
|
||||
return;
|
||||
return DispatchEventOfSameName::No;
|
||||
const_cast<Layout::Label*>(label)->handle_mousedown_on_label({}, position, button);
|
||||
const_cast<HTML::BrowsingContext&>(browsing_context()).event_handler().set_mouse_event_tracking_layout_node(&const_cast<Layout::TextNode&>(layout_node()));
|
||||
return DispatchEventOfSameName::Yes;
|
||||
}
|
||||
|
||||
// NOTE: Changing the state of the DOM node may run arbitrary JS, which could disappear this node.
|
||||
NonnullRefPtr protect = *this;
|
||||
TextPaintable::DispatchEventOfSameName TextPaintable::handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned button, unsigned)
|
||||
{
|
||||
auto* label = layout_node().first_ancestor_of_type<Layout::Label>();
|
||||
if (!label)
|
||||
return DispatchEventOfSameName::No;
|
||||
|
||||
const_cast<Layout::Label*>(label)->handle_mouseup_on_label({}, position, button);
|
||||
const_cast<HTML::BrowsingContext&>(browsing_context()).event_handler().set_mouse_event_tracking_layout_node(nullptr);
|
||||
return DispatchEventOfSameName::Yes;
|
||||
}
|
||||
|
||||
void TextPaintable::handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned button, unsigned)
|
||||
TextPaintable::DispatchEventOfSameName TextPaintable::handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned button, unsigned)
|
||||
{
|
||||
auto* label = layout_node().first_ancestor_of_type<Layout::Label>();
|
||||
if (!label)
|
||||
return;
|
||||
return DispatchEventOfSameName::No;
|
||||
const_cast<Layout::Label*>(label)->handle_mousemove_on_label({}, position, button);
|
||||
return DispatchEventOfSameName::Yes;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,9 +17,10 @@ public:
|
|||
Layout::TextNode const& layout_node() const { return static_cast<Layout::TextNode const&>(Paintable::layout_node()); }
|
||||
|
||||
virtual bool wants_mouse_events() const override;
|
||||
virtual void handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override;
|
||||
virtual void handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override;
|
||||
virtual void handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override;
|
||||
virtual DOM::Node* mouse_event_target() const override;
|
||||
virtual DispatchEventOfSameName handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override;
|
||||
virtual DispatchEventOfSameName handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override;
|
||||
virtual DispatchEventOfSameName handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override;
|
||||
|
||||
private:
|
||||
explicit TextPaintable(Layout::TextNode const&);
|
||||
|
|
Loading…
Reference in a new issue