/src/serenity/Userland/Libraries/LibWeb/Layout/GridFormattingContext.h
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com> |
3 | | * Copyright (c) 2022-2023, Martin Falisse <mfalisse@outlook.com> |
4 | | * |
5 | | * SPDX-License-Identifier: BSD-2-Clause |
6 | | */ |
7 | | |
8 | | #pragma once |
9 | | |
10 | | #include <LibWeb/CSS/Length.h> |
11 | | #include <LibWeb/Layout/FormattingContext.h> |
12 | | |
13 | | namespace Web::Layout { |
14 | | |
15 | | enum class GridDimension { |
16 | | Row, |
17 | | Column |
18 | | }; |
19 | | |
20 | | struct GridPosition { |
21 | | int row; |
22 | | int column; |
23 | 0 | inline bool operator==(GridPosition const&) const = default; |
24 | | }; |
25 | | |
26 | | struct GridItem { |
27 | | JS::NonnullGCPtr<Box const> box; |
28 | | |
29 | | int row; |
30 | | size_t row_span; |
31 | | int column; |
32 | | size_t column_span; |
33 | | |
34 | | [[nodiscard]] size_t span(GridDimension const dimension) const |
35 | 0 | { |
36 | 0 | return dimension == GridDimension::Column ? column_span : row_span; |
37 | 0 | } |
38 | | |
39 | | [[nodiscard]] int raw_position(GridDimension const dimension) const |
40 | 0 | { |
41 | 0 | return dimension == GridDimension::Column ? column : row; |
42 | 0 | } |
43 | | |
44 | | [[nodiscard]] CSSPixels add_margin_box_sizes(CSSPixels content_size, GridDimension dimension, LayoutState const& state) const |
45 | 0 | { |
46 | 0 | auto const& box_state = state.get(box); |
47 | 0 | if (dimension == GridDimension::Column) |
48 | 0 | return box_state.margin_box_left() + content_size + box_state.margin_box_right(); |
49 | 0 | return box_state.margin_box_top() + content_size + box_state.margin_box_bottom(); |
50 | 0 | } |
51 | | |
52 | | [[nodiscard]] int gap_adjusted_row() const; |
53 | | [[nodiscard]] int gap_adjusted_column() const; |
54 | | }; |
55 | | |
56 | | enum class FoundUnoccupiedPlace { |
57 | | No, |
58 | | Yes |
59 | | }; |
60 | | |
61 | | class OccupationGrid { |
62 | | public: |
63 | | OccupationGrid(size_t columns_count, size_t rows_count) |
64 | 0 | { |
65 | 0 | m_max_column_index = max(0, columns_count - 1); |
66 | 0 | m_max_row_index = max(0, rows_count - 1); |
67 | 0 | } |
68 | 0 | OccupationGrid() { } |
69 | | |
70 | | void set_occupied(int column_start, int column_end, int row_start, int row_end); |
71 | | |
72 | | size_t column_count() const |
73 | 0 | { |
74 | 0 | return abs(m_min_column_index) + m_max_column_index + 1; |
75 | 0 | } |
76 | | |
77 | | size_t row_count() const |
78 | 0 | { |
79 | 0 | return abs(m_min_row_index) + m_max_row_index + 1; |
80 | 0 | } |
81 | | |
82 | 0 | void set_max_column_index(size_t max_column_index) { m_max_column_index = max_column_index; } |
83 | | |
84 | 0 | int min_column_index() const { return m_min_column_index; } |
85 | 0 | int max_column_index() const { return m_max_column_index; } |
86 | 0 | int min_row_index() const { return m_min_row_index; } |
87 | 0 | int max_row_index() const { return m_max_row_index; } |
88 | | |
89 | | bool is_occupied(int column_index, int row_index) const; |
90 | | |
91 | | FoundUnoccupiedPlace find_unoccupied_place(GridDimension dimension, int& column_index, int& row_index, int column_span, int row_span) const; |
92 | | |
93 | | private: |
94 | | HashTable<GridPosition> m_occupation_grid; |
95 | | |
96 | | int m_min_column_index { 0 }; |
97 | | int m_max_column_index { 0 }; |
98 | | int m_min_row_index { 0 }; |
99 | | int m_max_row_index { 0 }; |
100 | | }; |
101 | | |
102 | | class GridFormattingContext final : public FormattingContext { |
103 | | public: |
104 | | explicit GridFormattingContext(LayoutState&, LayoutMode, Box const& grid_container, FormattingContext* parent); |
105 | | ~GridFormattingContext(); |
106 | | |
107 | 0 | virtual bool inhibits_floating() const override { return true; } |
108 | | |
109 | | virtual void run(AvailableSpace const& available_space) override; |
110 | | virtual CSSPixels automatic_content_width() const override; |
111 | | virtual CSSPixels automatic_content_height() const override; |
112 | | |
113 | 0 | Box const& grid_container() const { return context_box(); } |
114 | | |
115 | | private: |
116 | | CSS::JustifyItems justification_for_item(Box const& box) const; |
117 | | CSS::AlignItems alignment_for_item(Box const& box) const; |
118 | | |
119 | | void resolve_items_box_metrics(GridDimension const dimension); |
120 | | |
121 | | CSSPixels m_automatic_content_height { 0 }; |
122 | | |
123 | | bool is_auto_positioned_track(CSS::GridTrackPlacement const&, CSS::GridTrackPlacement const&) const; |
124 | | |
125 | | struct GridTrack { |
126 | | CSS::GridSize min_track_sizing_function; |
127 | | CSS::GridSize max_track_sizing_function; |
128 | | |
129 | | CSSPixels base_size { 0 }; |
130 | | bool base_size_frozen { false }; |
131 | | |
132 | | Optional<CSSPixels> growth_limit { 0 }; |
133 | | bool growth_limit_frozen { false }; |
134 | | bool infinitely_growable { false }; |
135 | | |
136 | | CSSPixels space_to_distribute { 0 }; |
137 | | CSSPixels planned_increase { 0 }; |
138 | | CSSPixels item_incurred_increase { 0 }; |
139 | | |
140 | | bool is_gap { false }; |
141 | | |
142 | | static GridTrack create_from_definition(CSS::ExplicitGridTrack const& definition); |
143 | | static GridTrack create_auto(); |
144 | | static GridTrack create_gap(CSSPixels size); |
145 | | }; |
146 | | |
147 | | struct GridArea { |
148 | | String name; |
149 | | size_t row_start { 0 }; |
150 | | size_t row_end { 1 }; |
151 | | size_t column_start { 0 }; |
152 | | size_t column_end { 1 }; |
153 | | bool invalid { false }; /* FIXME: Ignore ignore invalid areas during layout */ |
154 | | }; |
155 | | |
156 | | struct GridLine { |
157 | | Vector<String> names; |
158 | | }; |
159 | | Vector<GridLine> m_row_lines; |
160 | | Vector<GridLine> m_column_lines; |
161 | | |
162 | | void init_grid_lines(GridDimension); |
163 | | |
164 | | Vector<GridTrack> m_grid_rows; |
165 | | Vector<GridTrack> m_grid_columns; |
166 | | |
167 | | bool has_gaps(GridDimension const dimension) const |
168 | 0 | { |
169 | 0 | if (dimension == GridDimension::Column) { |
170 | 0 | return !grid_container().computed_values().column_gap().is_auto(); |
171 | 0 | } else { |
172 | 0 | return !grid_container().computed_values().row_gap().is_auto(); |
173 | 0 | } |
174 | 0 | } |
175 | | |
176 | | template<typename Callback> |
177 | | void for_each_spanned_track_by_item(GridItem const& item, GridDimension const dimension, Callback callback) |
178 | 0 | { |
179 | 0 | auto& tracks = dimension == GridDimension::Column ? m_grid_columns : m_grid_rows; |
180 | 0 | auto& gaps = dimension == GridDimension::Column ? m_column_gap_tracks : m_row_gap_tracks; |
181 | 0 | auto has_gaps = this->has_gaps(dimension); |
182 | 0 | auto item_span = item.span(dimension); |
183 | 0 | auto item_index = item.raw_position(dimension); |
184 | 0 | for (size_t span = 0; span < item_span; span++) { |
185 | 0 | auto track_index = item_index + span; |
186 | 0 | if (track_index >= tracks.size()) |
187 | 0 | break; |
188 | | |
189 | 0 | auto& track = tracks[track_index]; |
190 | 0 | callback(track); |
191 | |
|
192 | 0 | auto is_last_spanned_track = span == item_span - 1; |
193 | 0 | if (has_gaps && !is_last_spanned_track) { |
194 | 0 | auto& gap = gaps[track_index]; |
195 | 0 | callback(gap); |
196 | 0 | } |
197 | 0 | } |
198 | 0 | } Unexecuted instantiation: GridFormattingContext.cpp:void Web::Layout::GridFormattingContext::for_each_spanned_track_by_item<Web::Layout::GridFormattingContext::expand_flexible_tracks(Web::Layout::GridDimension)::$_1::operator()() const::{lambda(Web::Layout::GridFormattingContext::GridTrack&)#1}>(Web::Layout::GridItem const&, Web::Layout::GridDimension, Web::Layout::GridFormattingContext::expand_flexible_tracks(Web::Layout::GridDimension)::$_1::operator()() const::{lambda(Web::Layout::GridFormattingContext::GridTrack&)#1}) Unexecuted instantiation: GridFormattingContext.cpp:void Web::Layout::GridFormattingContext::for_each_spanned_track_by_item<Web::Layout::GridFormattingContext::increase_sizes_to_accommodate_spanning_items_crossing_content_sized_tracks(Web::Layout::GridDimension, unsigned long)::$_1>(Web::Layout::GridItem const&, Web::Layout::GridDimension, Web::Layout::GridFormattingContext::increase_sizes_to_accommodate_spanning_items_crossing_content_sized_tracks(Web::Layout::GridDimension, unsigned long)::$_1) Unexecuted instantiation: GridFormattingContext.cpp:void Web::Layout::GridFormattingContext::for_each_spanned_track_by_item<Web::Layout::GridFormattingContext::increase_sizes_to_accommodate_spanning_items_crossing_flexible_tracks(Web::Layout::GridDimension)::$_1>(Web::Layout::GridItem const&, Web::Layout::GridDimension, Web::Layout::GridFormattingContext::increase_sizes_to_accommodate_spanning_items_crossing_flexible_tracks(Web::Layout::GridDimension)::$_1) |
199 | | |
200 | | template<typename Callback> |
201 | | void for_each_spanned_track_by_item(GridItem const& item, GridDimension const dimension, Callback callback) const |
202 | 0 | { |
203 | 0 | auto& tracks = dimension == GridDimension::Column ? m_grid_columns : m_grid_rows; |
204 | 0 | auto& gaps = dimension == GridDimension::Column ? m_column_gap_tracks : m_row_gap_tracks; |
205 | 0 | auto has_gaps = this->has_gaps(dimension); |
206 | 0 | auto item_span = item.span(dimension); |
207 | 0 | auto item_index = item.raw_position(dimension); |
208 | 0 | for (size_t span = 0; span < item_span; span++) { |
209 | 0 | auto track_index = item_index + span; |
210 | 0 | if (track_index >= tracks.size()) |
211 | 0 | break; |
212 | | |
213 | 0 | auto& track = tracks[track_index]; |
214 | 0 | callback(track); |
215 | |
|
216 | 0 | auto is_last_spanned_track = span == item_span - 1; |
217 | 0 | if (has_gaps && !is_last_spanned_track) { |
218 | 0 | auto& gap = gaps[track_index]; |
219 | 0 | callback(gap); |
220 | 0 | } |
221 | 0 | } |
222 | 0 | } |
223 | | |
224 | | Vector<GridTrack> m_row_gap_tracks; |
225 | | Vector<GridTrack> m_column_gap_tracks; |
226 | | |
227 | | Vector<GridTrack&> m_grid_rows_and_gaps; |
228 | | Vector<GridTrack&> m_grid_columns_and_gaps; |
229 | | |
230 | | size_t m_explicit_rows_line_count { 0 }; |
231 | | size_t m_explicit_columns_line_count { 0 }; |
232 | | |
233 | | OccupationGrid m_occupation_grid; |
234 | | Vector<GridItem> m_grid_items; |
235 | | |
236 | | Optional<AvailableSpace> m_available_space; |
237 | | |
238 | | void determine_grid_container_height(); |
239 | | void determine_intrinsic_size_of_grid_container(AvailableSpace const& available_space); |
240 | | |
241 | | void layout_absolutely_positioned_element(Box const&, AvailableSpace const&); |
242 | | virtual void parent_context_did_dimension_child_root_box() override; |
243 | | |
244 | | void resolve_grid_item_widths(); |
245 | | void resolve_grid_item_heights(); |
246 | | |
247 | | void resolve_track_spacing(GridDimension const dimension); |
248 | | |
249 | | AvailableSize get_free_space(AvailableSpace const&, GridDimension const) const; |
250 | | |
251 | | Optional<int> get_line_index_by_line_name(GridDimension dimension, String const&); |
252 | | CSSPixels resolve_definite_track_size(CSS::GridSize const&, AvailableSpace const&); |
253 | | int count_of_repeated_auto_fill_or_fit_tracks(GridDimension, CSS::ExplicitGridTrack const& repeated_track); |
254 | | |
255 | | void build_grid_areas(); |
256 | | |
257 | | struct PlacementPosition { |
258 | | int start { 0 }; |
259 | | int end { 0 }; |
260 | | size_t span { 1 }; |
261 | | }; |
262 | | PlacementPosition resolve_grid_position(Box const& child_box, GridDimension const dimension); |
263 | | |
264 | | void place_grid_items(); |
265 | | void place_item_with_row_and_column_position(Box const& child_box); |
266 | | void place_item_with_row_position(Box const& child_box); |
267 | | void place_item_with_column_position(Box const& child_box, int& auto_placement_cursor_x, int& auto_placement_cursor_y); |
268 | | void place_item_with_no_declared_position(Box const& child_box, int& auto_placement_cursor_x, int& auto_placement_cursor_y); |
269 | | void record_grid_placement(GridItem); |
270 | | |
271 | | void initialize_grid_tracks_from_definition(GridDimension); |
272 | | void initialize_grid_tracks_for_columns_and_rows(); |
273 | | void initialize_gap_tracks(AvailableSpace const&); |
274 | | |
275 | | void collapse_auto_fit_tracks_if_needed(GridDimension const); |
276 | | |
277 | | enum class SpaceDistributionPhase { |
278 | | AccommodateMinimumContribution, |
279 | | AccommodateMinContentContribution, |
280 | | AccommodateMaxContentContribution |
281 | | }; |
282 | | |
283 | | template<typename Match> |
284 | | void distribute_extra_space_across_spanned_tracks_base_size(GridDimension dimension, CSSPixels item_size_contribution, SpaceDistributionPhase phase, Vector<GridTrack&>& spanned_tracks, Match matcher); |
285 | | |
286 | | template<typename Match> |
287 | | void distribute_extra_space_across_spanned_tracks_growth_limit(CSSPixels item_size_contribution, Vector<GridTrack&>& spanned_tracks, Match matcher); |
288 | | |
289 | | void initialize_track_sizes(GridDimension); |
290 | | void resolve_intrinsic_track_sizes(GridDimension); |
291 | | void increase_sizes_to_accommodate_spanning_items_crossing_content_sized_tracks(GridDimension, size_t span); |
292 | | void increase_sizes_to_accommodate_spanning_items_crossing_flexible_tracks(GridDimension); |
293 | | void maximize_tracks_using_available_size(AvailableSpace const& available_space, GridDimension dimension); |
294 | | void maximize_tracks(GridDimension); |
295 | | void expand_flexible_tracks(GridDimension); |
296 | | void stretch_auto_tracks(GridDimension); |
297 | | void run_track_sizing(GridDimension); |
298 | | |
299 | | CSSPixels calculate_grid_container_maximum_size(GridDimension const) const; |
300 | | |
301 | | CSS::Size const& get_item_preferred_size(GridItem const&, GridDimension const) const; |
302 | | |
303 | | CSSPixels calculate_min_content_size(GridItem const&, GridDimension const) const; |
304 | | CSSPixels calculate_max_content_size(GridItem const&, GridDimension const) const; |
305 | | |
306 | | CSSPixels calculate_min_content_contribution(GridItem const&, GridDimension const) const; |
307 | | CSSPixels calculate_max_content_contribution(GridItem const&, GridDimension const) const; |
308 | | |
309 | | CSSPixels calculate_limited_min_content_contribution(GridItem const&, GridDimension const) const; |
310 | | CSSPixels calculate_limited_max_content_contribution(GridItem const&, GridDimension const) const; |
311 | | |
312 | | CSSPixels containing_block_size_for_item(GridItem const&, GridDimension const) const; |
313 | | AvailableSpace get_available_space_for_item(GridItem const&) const; |
314 | | |
315 | | CSSPixelRect get_grid_area_rect(GridItem const&) const; |
316 | | |
317 | | CSSPixels content_size_suggestion(GridItem const&, GridDimension const) const; |
318 | | Optional<CSSPixels> specified_size_suggestion(GridItem const&, GridDimension const) const; |
319 | | CSSPixels content_based_minimum_size(GridItem const&, GridDimension const) const; |
320 | | CSSPixels automatic_minimum_size(GridItem const&, GridDimension const) const; |
321 | | CSSPixels calculate_minimum_contribution(GridItem const&, GridDimension const) const; |
322 | | }; |
323 | | |
324 | | } |