2020-01-18 09:38:21 +01:00
|
|
|
/*
|
2023-02-25 11:04:29 +01:00
|
|
|
* Copyright (c) 2018-2023, Andreas Kling <kling@serenityos.org>
|
2020-01-18 09:38:21 +01:00
|
|
|
*
|
2021-04-22 01:24:48 -07:00
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
2020-01-18 09:38:21 +01:00
|
|
|
*/
|
|
|
|
|
|
2023-01-11 19:48:53 +01:00
|
|
|
#include <LibWeb/DOM/Range.h>
|
2020-03-07 10:32:51 +01:00
|
|
|
#include <LibWeb/Dump.h>
|
2023-02-25 11:04:29 +01:00
|
|
|
#include <LibWeb/Layout/Viewport.h>
|
2022-03-10 23:13:37 +01:00
|
|
|
#include <LibWeb/Painting/PaintableBox.h>
|
2020-06-18 21:39:27 +02:00
|
|
|
#include <LibWeb/Painting/StackingContext.h>
|
2019-06-15 22:49:44 +02:00
|
|
|
|
2020-11-22 15:53:01 +01:00
|
|
|
namespace Web::Layout {
|
2020-03-07 10:27:02 +01:00
|
|
|
|
2023-02-25 11:04:29 +01:00
|
|
|
Viewport::Viewport(DOM::Document& document, NonnullRefPtr<CSS::StyleProperties> style)
|
2021-10-06 20:02:41 +02:00
|
|
|
: BlockContainer(document, &document, move(style))
|
2019-06-15 22:49:44 +02:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-25 11:04:29 +01:00
|
|
|
Viewport::~Viewport() = default;
|
2019-06-16 21:35:03 +02:00
|
|
|
|
2023-02-25 11:04:29 +01:00
|
|
|
JS::GCPtr<Selection::Selection> Viewport::selection() const
|
2023-01-11 19:48:53 +01:00
|
|
|
{
|
|
|
|
|
return const_cast<DOM::Document&>(document()).get_selection();
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-25 11:04:29 +01:00
|
|
|
void Viewport::build_stacking_context_tree_if_needed()
|
2022-03-21 10:56:02 +01:00
|
|
|
{
|
2023-04-20 16:01:38 +01:00
|
|
|
if (paintable_box()->stacking_context())
|
2022-03-21 10:56:02 +01:00
|
|
|
return;
|
|
|
|
|
build_stacking_context_tree();
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-25 11:04:29 +01:00
|
|
|
void Viewport::build_stacking_context_tree()
|
2020-06-15 17:29:35 +02:00
|
|
|
{
|
2023-08-07 00:51:05 +02:00
|
|
|
paintable_box()->set_stacking_context(make<Painting::StackingContext>(*this, nullptr, 0));
|
2020-06-15 17:29:35 +02:00
|
|
|
|
2023-06-02 12:01:14 +02:00
|
|
|
size_t index_in_tree_order = 1;
|
2022-03-21 10:56:02 +01:00
|
|
|
for_each_in_subtree_of_type<Box>([&](Box& box) {
|
2023-04-20 16:00:42 +01:00
|
|
|
if (!box.paintable_box())
|
2022-04-07 15:29:21 +02:00
|
|
|
return IterationDecision::Continue;
|
2023-08-07 00:51:05 +02:00
|
|
|
box.paintable_box()->invalidate_stacking_context();
|
2020-06-15 17:29:35 +02:00
|
|
|
if (!box.establishes_stacking_context()) {
|
2023-04-20 16:00:42 +01:00
|
|
|
VERIFY(!box.paintable_box()->stacking_context());
|
2020-06-15 17:29:35 +02:00
|
|
|
return IterationDecision::Continue;
|
|
|
|
|
}
|
2023-08-07 00:51:05 +02:00
|
|
|
auto* parent_context = box.paintable_box()->enclosing_stacking_context();
|
2021-02-23 20:42:32 +01:00
|
|
|
VERIFY(parent_context);
|
2023-08-07 00:51:05 +02:00
|
|
|
box.paintable_box()->set_stacking_context(make<Painting::StackingContext>(box, parent_context, index_in_tree_order++));
|
2020-06-15 17:29:35 +02:00
|
|
|
return IterationDecision::Continue;
|
|
|
|
|
});
|
2022-03-13 16:19:54 +01:00
|
|
|
|
2023-08-07 00:51:05 +02:00
|
|
|
paintable_box()->stacking_context()->sort();
|
2020-06-15 17:29:35 +02:00
|
|
|
}
|
|
|
|
|
|
2023-02-25 11:04:29 +01:00
|
|
|
void Viewport::paint_all_phases(PaintContext& context)
|
2020-06-15 17:29:35 +02:00
|
|
|
{
|
2022-03-21 10:56:02 +01:00
|
|
|
build_stacking_context_tree_if_needed();
|
2022-10-27 14:37:05 +01:00
|
|
|
context.painter().translate(-context.device_viewport_rect().location().to_type<int>());
|
2023-04-20 16:01:38 +01:00
|
|
|
paintable_box()->stacking_context()->paint(context);
|
2020-06-15 17:29:35 +02:00
|
|
|
}
|
|
|
|
|
|
2023-02-25 11:04:29 +01:00
|
|
|
void Viewport::recompute_selection_states()
|
2020-08-21 17:50:41 +02:00
|
|
|
{
|
2023-01-11 19:48:53 +01:00
|
|
|
// 1. Start by resetting the selection state of all layout nodes to None.
|
2021-04-06 18:38:10 +01:00
|
|
|
for_each_in_inclusive_subtree([&](auto& layout_node) {
|
2023-01-11 19:48:53 +01:00
|
|
|
layout_node.set_selection_state(SelectionState::None);
|
2020-08-21 17:50:41 +02:00
|
|
|
return IterationDecision::Continue;
|
|
|
|
|
});
|
|
|
|
|
|
2023-01-11 19:48:53 +01:00
|
|
|
// 2. If there is no active Selection or selected Range, return.
|
|
|
|
|
auto selection = document().get_selection();
|
|
|
|
|
if (!selection)
|
|
|
|
|
return;
|
|
|
|
|
auto range = selection->range();
|
|
|
|
|
if (!range)
|
|
|
|
|
return;
|
2020-08-21 17:54:44 +02:00
|
|
|
|
2023-01-11 19:48:53 +01:00
|
|
|
auto* start_container = range->start_container();
|
|
|
|
|
auto* end_container = range->end_container();
|
|
|
|
|
|
2023-01-12 20:27:16 +01:00
|
|
|
// 3. If the selection starts and ends in the same node:
|
|
|
|
|
if (start_container == end_container) {
|
|
|
|
|
// 1. If the selection starts and ends at the same offset, return.
|
|
|
|
|
if (range->start_offset() == range->end_offset()) {
|
|
|
|
|
// NOTE: A zero-length selection should not be visible.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. If it's a text node, mark it as StartAndEnd and return.
|
|
|
|
|
if (is<DOM::Text>(*start_container)) {
|
|
|
|
|
if (auto* layout_node = start_container->layout_node()) {
|
|
|
|
|
layout_node->set_selection_state(SelectionState::StartAndEnd);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-11 19:48:53 +01:00
|
|
|
if (start_container == end_container && is<DOM::Text>(*start_container)) {
|
|
|
|
|
if (auto* layout_node = start_container->layout_node()) {
|
|
|
|
|
layout_node->set_selection_state(SelectionState::StartAndEnd);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. Mark the selection start node as Start (if text) or Full (if anything else).
|
|
|
|
|
if (auto* layout_node = start_container->layout_node()) {
|
|
|
|
|
if (is<DOM::Text>(*start_container))
|
|
|
|
|
layout_node->set_selection_state(SelectionState::Start);
|
|
|
|
|
else
|
|
|
|
|
layout_node->set_selection_state(SelectionState::Full);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 5. Mark the selection end node as End (if text) or Full (if anything else).
|
|
|
|
|
if (auto* layout_node = end_container->layout_node()) {
|
|
|
|
|
if (is<DOM::Text>(*end_container))
|
|
|
|
|
layout_node->set_selection_state(SelectionState::End);
|
|
|
|
|
else
|
|
|
|
|
layout_node->set_selection_state(SelectionState::Full);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 6. Mark the nodes between start node and end node (in tree order) as Full.
|
|
|
|
|
for (auto* node = start_container->next_in_pre_order(); node && node != end_container; node = node->next_in_pre_order()) {
|
|
|
|
|
if (auto* layout_node = node->layout_node())
|
|
|
|
|
layout_node->set_selection_state(SelectionState::Full);
|
|
|
|
|
}
|
2020-08-21 17:54:44 +02:00
|
|
|
}
|
|
|
|
|
|
2020-03-07 10:27:02 +01:00
|
|
|
}
|