diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index c3ad56f489..e8b928504b 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -132,6 +132,7 @@ set(SOURCES Layout/LayoutBlock.cpp Layout/LayoutBox.cpp Layout/LayoutBreak.cpp + Layout/LayoutButton.cpp Layout/LayoutCanvas.cpp Layout/LayoutCheckBox.cpp Layout/LayoutDocument.cpp diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index c162796c22..8df979d35f 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -136,6 +136,8 @@ namespace Web { class EventHandler; class Frame; class LayoutBlock; +class LayoutButton; +class LayoutCheckBox; class LayoutDocument; class LayoutNode; class LayoutNodeWithStyle; diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Libraries/LibWeb/HTML/HTMLInputElement.cpp index 1e89724343..249329e056 100644 --- a/Libraries/LibWeb/HTML/HTMLInputElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLInputElement.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,19 @@ HTMLInputElement::~HTMLInputElement() { } +void HTMLInputElement::did_click_button(Badge) +{ + dispatch_event(DOM::Event::create("click")); + + if (type().equals_ignoring_case("submit")) { + if (auto* form = first_ancestor_of_type()) { + // FIXME: Remove this const_cast once we have a non-const first_ancestor_of_type. + form->submit(this); + } + return; + } +} + RefPtr HTMLInputElement::create_layout_node(const CSS::StyleProperties* parent_style) { ASSERT(document().frame()); @@ -59,47 +73,27 @@ RefPtr HTMLInputElement::create_layout_node(const CSS::StyleProperti if (style->display() == CSS::Display::None) return nullptr; - RefPtr widget; - if (type() == "submit") { - auto& button = page_view.add(value()); - int text_width = Gfx::Font::default_font().width(value()); - button.set_relative_rect(0, 0, text_width + 20, 20); - button.on_click = [this](auto) { - if (auto* form = first_ancestor_of_type()) { - // FIXME: Remove this const_cast once we have a non-const first_ancestor_of_type. - const_cast(form)->submit(this); - } - }; - widget = button; - } else if (type() == "button") { - auto& button = page_view.add(value()); - int text_width = Gfx::Font::default_font().width(value()); - button.set_relative_rect(0, 0, text_width + 20, 20); - button.on_click = [this](auto) { - const_cast(this)->dispatch_event(DOM::Event::create("click")); - }; - widget = button; - } else if (type() == "checkbox") { - return adopt(*new LayoutCheckBox(document(), *this, move(style))); - } else { - auto& text_box = page_view.add(); - text_box.set_text(value()); - text_box.on_change = [this] { - auto& widget = downcast(layout_node())->widget(); - const_cast(this)->set_attribute(HTML::AttributeNames::value, static_cast(widget).text()); - }; - int text_width = Gfx::Font::default_font().width(value()); - auto size_value = attribute(HTML::AttributeNames::size); - if (!size_value.is_null()) { - auto size = size_value.to_uint(); - if (size.has_value()) - text_width = Gfx::Font::default_font().glyph_width('x') * size.value(); - } - text_box.set_relative_rect(0, 0, text_width + 20, 20); - widget = text_box; - } + if (type().equals_ignoring_case("submit") || type().equals_ignoring_case("button")) + return adopt(*new LayoutButton(document(), *this, move(style))); - return adopt(*new LayoutWidget(document(), *this, *widget)); + if (type() == "checkbox") + return adopt(*new LayoutCheckBox(document(), *this, move(style))); + + auto& text_box = page_view.add(); + text_box.set_text(value()); + text_box.on_change = [this] { + auto& widget = downcast(layout_node())->widget(); + const_cast(this)->set_attribute(HTML::AttributeNames::value, static_cast(widget).text()); + }; + int text_width = Gfx::Font::default_font().width(value()); + auto size_value = attribute(HTML::AttributeNames::size); + if (!size_value.is_null()) { + auto size = size_value.to_uint(); + if (size.has_value()) + text_width = Gfx::Font::default_font().glyph_width('x') * size.value(); + } + text_box.set_relative_rect(0, 0, text_width + 20, 20); + return adopt(*new LayoutWidget(document(), *this, text_box)); } void HTMLInputElement::set_checked(bool checked) diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.h b/Libraries/LibWeb/HTML/HTMLInputElement.h index a17417fc07..6b6405148c 100644 --- a/Libraries/LibWeb/HTML/HTMLInputElement.h +++ b/Libraries/LibWeb/HTML/HTMLInputElement.h @@ -48,6 +48,8 @@ public: bool enabled() const; + void did_click_button(Badge); + private: bool m_checked { false }; }; diff --git a/Libraries/LibWeb/Layout/LayoutButton.cpp b/Libraries/LibWeb/Layout/LayoutButton.cpp new file mode 100644 index 0000000000..e09b052fd0 --- /dev/null +++ b/Libraries/LibWeb/Layout/LayoutButton.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +namespace Web { + +LayoutButton::LayoutButton(DOM::Document& document, HTML::HTMLInputElement& element, NonnullRefPtr style) + : LayoutReplaced(document, element, move(style)) +{ +} + +LayoutButton::~LayoutButton() +{ +} + +void LayoutButton::layout(LayoutMode layout_mode) +{ + auto& font = specified_style().font(); + set_intrinsic_width(font.width(node().value()) + 20); + set_has_intrinsic_width(true); + + set_intrinsic_height(20); + set_has_intrinsic_height(true); + LayoutReplaced::layout(layout_mode); +} + +void LayoutButton::paint(PaintContext& context, PaintPhase phase) +{ + if (!is_visible()) + return; + + LayoutReplaced::paint(context, phase); + + if (phase == PaintPhase::Foreground) { + bool hovered = document().hovered_node() == &node(); + Gfx::StylePainter::paint_button(context.painter(), enclosing_int_rect(absolute_rect()), context.palette(), Gfx::ButtonStyle::Normal, m_being_pressed, hovered, node().checked(), node().enabled()); + + auto text_rect = enclosing_int_rect(absolute_rect()); + if (m_being_pressed) + text_rect.move_by(1, 1); + context.painter().draw_text(text_rect, node().value(), specified_style().font(), Gfx::TextAlignment::Center); + } +} + +void LayoutButton::handle_mousedown(Badge, const Gfx::IntPoint&, unsigned button, unsigned) +{ + if (button != GUI::MouseButton::Left || !node().enabled()) + return; + + m_being_pressed = true; + set_needs_display(); + + m_tracking_mouse = true; + frame().event_handler().set_mouse_event_tracking_layout_node(this); +} + +void LayoutButton::handle_mouseup(Badge, const Gfx::IntPoint& position, unsigned button, unsigned) +{ + if (!m_tracking_mouse || button != GUI::MouseButton::Left || !node().enabled()) + return; + + // NOTE: Handling the click may run arbitrary JS, which could disappear this node. + NonnullRefPtr protected_this = *this; + NonnullRefPtr protected_frame = frame(); + + bool is_inside = enclosing_int_rect(absolute_rect()).contains(position); + if (is_inside) + node().did_click_button({}); + + m_being_pressed = false; + m_tracking_mouse = false; + + protected_frame->event_handler().set_mouse_event_tracking_layout_node(nullptr); +} + +void LayoutButton::handle_mousemove(Badge, const Gfx::IntPoint& position, unsigned, unsigned) +{ + if (!m_tracking_mouse || !node().enabled()) + return; + + bool is_inside = enclosing_int_rect(absolute_rect()).contains(position); + if (m_being_pressed == is_inside) + return; + + m_being_pressed = is_inside; + set_needs_display(); +} + +} diff --git a/Libraries/LibWeb/Layout/LayoutButton.h b/Libraries/LibWeb/Layout/LayoutButton.h new file mode 100644 index 0000000000..29153eb054 --- /dev/null +++ b/Libraries/LibWeb/Layout/LayoutButton.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include +#include + +namespace Web { + +class LayoutButton : public LayoutReplaced { +public: + LayoutButton(DOM::Document&, HTML::HTMLInputElement&, NonnullRefPtr); + virtual ~LayoutButton() override; + + virtual void layout(LayoutMode = LayoutMode::Default) override; + virtual void paint(PaintContext&, PaintPhase) override; + + const HTML::HTMLInputElement& node() const { return static_cast(LayoutReplaced::node()); } + HTML::HTMLInputElement& node() { return static_cast(LayoutReplaced::node()); } + +private: + virtual const char* class_name() const override { return "LayoutButton"; } + virtual bool is_button() const override { return true; } + virtual bool wants_mouse_events() const override { return true; } + virtual void handle_mousedown(Badge, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override; + virtual void handle_mouseup(Badge, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override; + virtual void handle_mousemove(Badge, const Gfx::IntPoint&, unsigned buttons, unsigned modifiers) override; + + bool m_being_pressed { false }; + bool m_tracking_mouse { false }; +}; + +} + +AK_BEGIN_TYPE_TRAITS(Web::LayoutButton) +static bool is_type(const Web::LayoutNode& layout_node) { return layout_node.is_button(); } +AK_END_TYPE_TRAITS() diff --git a/Libraries/LibWeb/Layout/LayoutNode.h b/Libraries/LibWeb/Layout/LayoutNode.h index 42b2b094e8..066e485f83 100644 --- a/Libraries/LibWeb/Layout/LayoutNode.h +++ b/Libraries/LibWeb/Layout/LayoutNode.h @@ -93,6 +93,7 @@ public: virtual bool is_table_row_group() const { return false; } virtual bool is_break() const { return false; } virtual bool is_check_box() const { return false; } + virtual bool is_button() const { return false; } bool has_style() const { return m_has_style; } bool is_inline() const { return m_inline; }