Serenity Operating System
at master 129 lines 4.8 kB view raw
1/* 2 * Copyright (c) 2018-2023, Andreas Kling <kling@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <LibWeb/DOM/Range.h> 8#include <LibWeb/Dump.h> 9#include <LibWeb/Layout/Viewport.h> 10#include <LibWeb/Painting/PaintableBox.h> 11#include <LibWeb/Painting/StackingContext.h> 12 13namespace Web::Layout { 14 15Viewport::Viewport(DOM::Document& document, NonnullRefPtr<CSS::StyleProperties> style) 16 : BlockContainer(document, &document, move(style)) 17{ 18} 19 20Viewport::~Viewport() = default; 21 22JS::GCPtr<Selection::Selection> Viewport::selection() const 23{ 24 return const_cast<DOM::Document&>(document()).get_selection(); 25} 26 27void Viewport::build_stacking_context_tree_if_needed() 28{ 29 if (paint_box()->stacking_context()) 30 return; 31 build_stacking_context_tree(); 32} 33 34void Viewport::build_stacking_context_tree() 35{ 36 const_cast<Painting::PaintableWithLines*>(paint_box())->set_stacking_context(make<Painting::StackingContext>(*this, nullptr)); 37 38 for_each_in_subtree_of_type<Box>([&](Box& box) { 39 if (!box.paint_box()) 40 return IterationDecision::Continue; 41 const_cast<Painting::PaintableBox*>(box.paint_box())->invalidate_stacking_context(); 42 if (!box.establishes_stacking_context()) { 43 VERIFY(!box.paint_box()->stacking_context()); 44 return IterationDecision::Continue; 45 } 46 auto* parent_context = const_cast<Painting::PaintableBox*>(box.paint_box())->enclosing_stacking_context(); 47 VERIFY(parent_context); 48 const_cast<Painting::PaintableBox*>(box.paint_box())->set_stacking_context(make<Painting::StackingContext>(box, parent_context)); 49 return IterationDecision::Continue; 50 }); 51 52 const_cast<Painting::PaintableWithLines*>(paint_box())->stacking_context()->sort(); 53} 54 55void Viewport::paint_all_phases(PaintContext& context) 56{ 57 build_stacking_context_tree_if_needed(); 58 context.painter().fill_rect(context.enclosing_device_rect(paint_box()->absolute_rect()).to_type<int>(), document().background_color(context.palette())); 59 context.painter().translate(-context.device_viewport_rect().location().to_type<int>()); 60 paint_box()->stacking_context()->paint(context); 61} 62 63void Viewport::recompute_selection_states() 64{ 65 // 1. Start by resetting the selection state of all layout nodes to None. 66 for_each_in_inclusive_subtree([&](auto& layout_node) { 67 layout_node.set_selection_state(SelectionState::None); 68 return IterationDecision::Continue; 69 }); 70 71 // 2. If there is no active Selection or selected Range, return. 72 auto selection = document().get_selection(); 73 if (!selection) 74 return; 75 auto range = selection->range(); 76 if (!range) 77 return; 78 79 auto* start_container = range->start_container(); 80 auto* end_container = range->end_container(); 81 82 // 3. If the selection starts and ends in the same node: 83 if (start_container == end_container) { 84 // 1. If the selection starts and ends at the same offset, return. 85 if (range->start_offset() == range->end_offset()) { 86 // NOTE: A zero-length selection should not be visible. 87 return; 88 } 89 90 // 2. If it's a text node, mark it as StartAndEnd and return. 91 if (is<DOM::Text>(*start_container)) { 92 if (auto* layout_node = start_container->layout_node()) { 93 layout_node->set_selection_state(SelectionState::StartAndEnd); 94 } 95 return; 96 } 97 } 98 99 if (start_container == end_container && is<DOM::Text>(*start_container)) { 100 if (auto* layout_node = start_container->layout_node()) { 101 layout_node->set_selection_state(SelectionState::StartAndEnd); 102 } 103 return; 104 } 105 106 // 4. Mark the selection start node as Start (if text) or Full (if anything else). 107 if (auto* layout_node = start_container->layout_node()) { 108 if (is<DOM::Text>(*start_container)) 109 layout_node->set_selection_state(SelectionState::Start); 110 else 111 layout_node->set_selection_state(SelectionState::Full); 112 } 113 114 // 5. Mark the selection end node as End (if text) or Full (if anything else). 115 if (auto* layout_node = end_container->layout_node()) { 116 if (is<DOM::Text>(*end_container)) 117 layout_node->set_selection_state(SelectionState::End); 118 else 119 layout_node->set_selection_state(SelectionState::Full); 120 } 121 122 // 6. Mark the nodes between start node and end node (in tree order) as Full. 123 for (auto* node = start_container->next_in_pre_order(); node && node != end_container; node = node->next_in_pre_order()) { 124 if (auto* layout_node = node->layout_node()) 125 layout_node->set_selection_state(SelectionState::Full); 126 } 127} 128 129}