Serenity Operating System
at master 832 lines 31 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2022, the SerenityOS developers. 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include <AK/Utf8View.h> 9#include <LibCore/Timer.h> 10#include <LibGUI/IconView.h> 11#include <LibGUI/Model.h> 12#include <LibGUI/ModelEditingDelegate.h> 13#include <LibGUI/Painter.h> 14#include <LibGUI/Scrollbar.h> 15 16#include <LibGfx/Palette.h> 17 18REGISTER_WIDGET(GUI, IconView); 19 20namespace GUI { 21 22IconView::IconView() 23{ 24 set_fill_with_background_color(true); 25 set_background_role(ColorRole::Base); 26 set_foreground_role(ColorRole::BaseText); 27 horizontal_scrollbar().set_visible(false); 28} 29 30void IconView::select_all() 31{ 32 for (int item_index = 0; item_index < item_count(); ++item_index) { 33 auto& item_data = m_item_data_cache[item_index]; 34 if (!item_data.selected) { 35 if (item_data.is_valid()) 36 add_selection(item_data); 37 else 38 add_selection(model()->index(item_index, model_column())); 39 } 40 } 41} 42 43void IconView::scroll_into_view(ModelIndex const& index, bool scroll_horizontally, bool scroll_vertically) 44{ 45 if (!index.is_valid()) 46 return; 47 AbstractScrollableWidget::scroll_into_view(item_rect(index.row()), scroll_horizontally, scroll_vertically); 48} 49 50void IconView::resize_event(ResizeEvent& event) 51{ 52 AbstractView::resize_event(event); 53 update_content_size(); 54 55 if (!m_had_valid_size) { 56 m_had_valid_size = true; 57 if (!selection().is_empty()) 58 scroll_into_view(selection().first()); 59 } 60} 61 62void IconView::did_change_font() 63{ 64 AbstractView::did_change_font(); 65 rebuild_item_cache(); 66} 67 68void IconView::rebuild_item_cache() const 69{ 70 auto prev_item_count = m_item_data_cache.size(); 71 size_t new_item_count = item_count(); 72 auto items_to_invalidate = min(prev_item_count, new_item_count); 73 74 // if the new number of items is less, check if any of the 75 // ones not in the list anymore was selected 76 for (size_t i = new_item_count; i < m_item_data_cache.size(); i++) { 77 auto& item_data = m_item_data_cache[i]; 78 if (item_data.selected) { 79 VERIFY(m_selected_count_cache > 0); 80 m_selected_count_cache--; 81 } 82 } 83 if ((size_t)m_first_selected_hint >= new_item_count) 84 m_first_selected_hint = 0; 85 m_item_data_cache.resize(new_item_count); 86 for (size_t i = 0; i < items_to_invalidate; i++) { 87 auto& item_data = m_item_data_cache[i]; 88 // TODO: It's unfortunate that we have no way to know whether any 89 // data actually changed, so we have to invalidate *everyone* 90 if (item_data.is_valid() /* && !model()->is_valid(item_data.index)*/) 91 item_data.invalidate(); 92 if (item_data.selected && i < (size_t)m_first_selected_hint) 93 m_first_selected_hint = (int)i; 94 } 95 96 m_item_data_cache_valid = true; 97} 98 99auto IconView::get_item_data(int item_index) const -> ItemData& 100{ 101 if (!m_item_data_cache_valid) 102 rebuild_item_cache(); 103 104 auto& item_data = m_item_data_cache[item_index]; 105 if (item_data.is_valid()) 106 return item_data; 107 108 item_data.index = model()->index(item_index, model_column()); 109 item_data.text = item_data.index.data().to_deprecated_string(); 110 get_item_rects(item_index, item_data, font_for_index(item_data.index)); 111 item_data.valid = true; 112 return item_data; 113} 114 115auto IconView::item_data_from_content_position(Gfx::IntPoint content_position) const -> ItemData* 116{ 117 if (!m_visual_row_count || !m_visual_column_count) 118 return nullptr; 119 int row, column; 120 column_row_from_content_position(content_position, row, column); 121 int item_index = (m_flow_direction == FlowDirection::LeftToRight) 122 ? row * m_visual_column_count + column 123 : column * m_visual_row_count + row; 124 if (item_index < 0 || item_index >= item_count()) 125 return nullptr; 126 return &get_item_data(item_index); 127} 128 129void IconView::model_did_update(unsigned flags) 130{ 131 AbstractView::model_did_update(flags); 132 if (!model() || (flags & GUI::Model::InvalidateAllIndices)) { 133 m_item_data_cache.clear(); 134 AbstractView::clear_selection(); 135 m_selected_count_cache = 0; 136 m_first_selected_hint = 0; 137 } 138 m_item_data_cache_valid = false; 139 update_content_size(); 140 update(); 141} 142 143void IconView::update_content_size() 144{ 145 if (!model()) 146 return set_content_size({}); 147 148 int content_width; 149 int content_height; 150 151 if (m_flow_direction == FlowDirection::LeftToRight) { 152 m_visual_column_count = max(1, available_size().width() / effective_item_size().width()); 153 if (m_visual_column_count) 154 m_visual_row_count = ceil_div(model()->row_count(), m_visual_column_count); 155 else 156 m_visual_row_count = 0; 157 content_width = m_visual_column_count * effective_item_size().width(); 158 content_height = m_visual_row_count * effective_item_size().height(); 159 } else { 160 m_visual_row_count = max(1, available_size().height() / effective_item_size().height()); 161 if (m_visual_row_count) 162 m_visual_column_count = ceil_div(model()->row_count(), m_visual_row_count); 163 else 164 m_visual_column_count = 0; 165 content_width = m_visual_column_count * effective_item_size().width(); 166 content_height = available_size().height(); 167 } 168 169 set_content_size({ content_width, content_height }); 170 171 if (!m_item_data_cache_valid) 172 rebuild_item_cache(); 173 174 for (int item_index = 0; item_index < item_count(); item_index++) { 175 auto& item_data = m_item_data_cache[item_index]; 176 if (item_data.is_valid()) 177 update_item_rects(item_index, item_data); 178 } 179} 180 181Gfx::IntRect IconView::item_rect(int item_index) const 182{ 183 if (!m_visual_row_count || !m_visual_column_count) 184 return {}; 185 int visual_row_index; 186 int visual_column_index; 187 188 if (m_flow_direction == FlowDirection::LeftToRight) { 189 visual_row_index = item_index / m_visual_column_count; 190 visual_column_index = item_index % m_visual_column_count; 191 } else { 192 visual_row_index = item_index % m_visual_row_count; 193 visual_column_index = item_index / m_visual_row_count; 194 } 195 196 return { 197 visual_column_index * effective_item_size().width(), 198 visual_row_index * effective_item_size().height(), 199 effective_item_size().width(), 200 effective_item_size().height() 201 }; 202} 203 204ModelIndex IconView::index_at_event_position(Gfx::IntPoint position) const 205{ 206 VERIFY(model()); 207 auto adjusted_position = to_content_position(position); 208 if (auto item_data = item_data_from_content_position(adjusted_position)) { 209 if (item_data->is_containing(adjusted_position)) 210 return item_data->index; 211 } 212 return {}; 213} 214 215void IconView::mousedown_event(MouseEvent& event) 216{ 217 if (!model()) 218 return AbstractView::mousedown_event(event); 219 220 if (event.button() != MouseButton::Primary) 221 return AbstractView::mousedown_event(event); 222 223 auto index = index_at_event_position(event.position()); 224 if (index.is_valid()) { 225 // We might start dragging this item, but not rubber-banding. 226 return AbstractView::mousedown_event(event); 227 } 228 229 if (!(event.modifiers() & Mod_Ctrl)) { 230 clear_selection(); 231 } 232 233 auto adjusted_position = to_content_position(event.position()); 234 235 m_might_drag = false; 236 if (selection_mode() == SelectionMode::MultiSelection) { 237 m_rubber_banding = true; 238 m_rubber_band_origin = adjusted_position; 239 m_rubber_band_current = adjusted_position; 240 } 241} 242 243void IconView::mouseup_event(MouseEvent& event) 244{ 245 if (m_rubber_banding && event.button() == MouseButton::Primary) { 246 m_rubber_banding = false; 247 set_automatic_scrolling_timer_active(false); 248 update(to_widget_rect(Gfx::IntRect::from_two_points(m_rubber_band_origin, m_rubber_band_current))); 249 } 250 AbstractView::mouseup_event(event); 251} 252 253bool IconView::update_rubber_banding(Gfx::IntPoint input_position) 254{ 255 auto adjusted_position = to_content_position(input_position.constrained(widget_inner_rect().inflated(1, 1))); 256 if (m_rubber_band_current != adjusted_position) { 257 auto prev_rect = Gfx::IntRect::from_two_points(m_rubber_band_origin, m_rubber_band_current); 258 auto prev_rubber_band_fill_rect = prev_rect.shrunken(1, 1); 259 m_rubber_band_current = adjusted_position; 260 auto rubber_band_rect = Gfx::IntRect::from_two_points(m_rubber_band_origin, m_rubber_band_current); 261 auto rubber_band_fill_rect = rubber_band_rect.shrunken(1, 1); 262 263 for (auto& rect : prev_rubber_band_fill_rect.shatter(rubber_band_fill_rect)) 264 update(to_widget_rect(rect.inflated(1, 1))); 265 for (auto& rect : rubber_band_fill_rect.shatter(prev_rubber_band_fill_rect)) 266 update(to_widget_rect(rect.inflated(1, 1))); 267 268 // If the rectangle width or height is 0, we still want to be able 269 // to match the items in the path. An easy work-around for this 270 // is to simply set the width or height to 1 271 auto ensure_rect = [](Gfx::IntRect& rect) { 272 if (rect.width() <= 0) 273 rect.set_width(1); 274 if (rect.height() <= 0) 275 rect.set_height(1); 276 }; 277 ensure_rect(prev_rect); 278 ensure_rect(rubber_band_rect); 279 280 // Clearing the entire selection every time is very expensive, 281 // determine what items may need to be deselected and what new 282 // items may need to be selected. Avoid a ton of allocations. 283 284 auto deselect_area = prev_rect.shatter(rubber_band_rect); 285 auto select_area = rubber_band_rect.shatter(prev_rect); 286 287 // Initialize all candidate's toggle flag. We need to know which 288 // items we touched because the various rectangles likely will 289 // contain the same item more than once 290 for_each_item_intersecting_rects(deselect_area, [](ItemData& item_data) -> IterationDecision { 291 item_data.selection_toggled = false; 292 return IterationDecision::Continue; 293 }); 294 for_each_item_intersecting_rects(select_area, [](ItemData& item_data) -> IterationDecision { 295 item_data.selection_toggled = false; 296 return IterationDecision::Continue; 297 }); 298 299 // We're changing the selection and invalidating those items, so 300 // no need to trigger a full re-render for each item 301 set_suppress_update_on_selection_change(true); 302 303 // Now toggle all items that are no longer in the selected area, once only 304 for_each_item_intersecting_rects(deselect_area, [&](ItemData& item_data) -> IterationDecision { 305 if (!item_data.selection_toggled && item_data.is_intersecting(prev_rect) && !item_data.is_intersecting(rubber_band_rect)) { 306 item_data.selection_toggled = true; 307 toggle_selection(item_data); 308 update(to_widget_rect(item_data.rect())); 309 } 310 return IterationDecision::Continue; 311 }); 312 // Now toggle all items that are in the new selected area, once only 313 for_each_item_intersecting_rects(select_area, [&](ItemData& item_data) -> IterationDecision { 314 if (!item_data.selection_toggled && !item_data.is_intersecting(prev_rect) && item_data.is_intersecting(rubber_band_rect)) { 315 item_data.selection_toggled = true; 316 toggle_selection(item_data); 317 update(to_widget_rect(item_data.rect())); 318 } 319 return IterationDecision::Continue; 320 }); 321 322 set_suppress_update_on_selection_change(false); 323 324 return true; 325 } 326 return false; 327} 328 329void IconView::mousemove_event(MouseEvent& event) 330{ 331 if (!model()) 332 return AbstractView::mousemove_event(event); 333 334 m_rubber_band_scroll_delta = automatic_scroll_delta_from_position(event.position()); 335 336 if (m_rubber_banding) { 337 m_out_of_view_position = event.position(); 338 set_automatic_scrolling_timer_active(!m_rubber_band_scroll_delta.is_zero()); 339 340 if (update_rubber_banding(event.position())) 341 return; 342 } 343 344 AbstractView::mousemove_event(event); 345} 346 347void IconView::automatic_scrolling_timer_did_fire() 348{ 349 AbstractView::automatic_scrolling_timer_did_fire(); 350 351 if (m_rubber_band_scroll_delta.is_zero()) 352 return; 353 354 vertical_scrollbar().increase_slider_by(m_rubber_band_scroll_delta.y()); 355 horizontal_scrollbar().increase_slider_by(m_rubber_band_scroll_delta.x()); 356 update_rubber_banding(m_out_of_view_position); 357} 358 359void IconView::update_item_rects(int item_index, ItemData& item_data) const 360{ 361 auto item_rect = this->item_rect(item_index); 362 item_data.icon_rect.center_within(item_rect); 363 item_data.icon_rect.translate_by(0, item_data.icon_offset_y); 364 item_data.text_rect.center_horizontally_within(item_rect); 365 item_data.text_rect.set_top(item_rect.y() + item_data.text_offset_y); 366} 367 368Gfx::IntRect IconView::content_rect(ModelIndex const& index) const 369{ 370 if (!index.is_valid()) 371 return {}; 372 auto& item_data = get_item_data(index.row()); 373 return item_data.rect(); 374} 375 376Gfx::IntRect IconView::editing_rect(ModelIndex const& index) const 377{ 378 if (!index.is_valid()) 379 return {}; 380 auto& item_data = get_item_data(index.row()); 381 auto editing_rect = item_data.text_rect; 382 editing_rect.set_height(font_for_index(index)->pixel_size_rounded_up() + 8); 383 editing_rect.set_y(item_data.text_rect.y() - 2); 384 return editing_rect; 385} 386 387void IconView::editing_widget_did_change(ModelIndex const& index) 388{ 389 if (m_editing_delegate->value().is_string()) { 390 auto text_width = font_for_index(index)->width(m_editing_delegate->value().as_string()); 391 m_edit_widget_content_rect.set_width(min(text_width + 8, effective_item_size().width())); 392 m_edit_widget_content_rect.center_horizontally_within(editing_rect(index).translated(frame_thickness(), frame_thickness())); 393 update_edit_widget_position(); 394 } 395} 396 397Gfx::IntRect 398IconView::paint_invalidation_rect(ModelIndex const& index) const 399{ 400 if (!index.is_valid()) 401 return {}; 402 auto& item_data = get_item_data(index.row()); 403 return item_data.rect(true); 404} 405 406void IconView::did_change_hovered_index(ModelIndex const& old_index, ModelIndex const& new_index) 407{ 408 AbstractView::did_change_hovered_index(old_index, new_index); 409 if (old_index.is_valid()) 410 get_item_rects(old_index.row(), get_item_data(old_index.row()), font_for_index(old_index)); 411 if (new_index.is_valid()) 412 get_item_rects(new_index.row(), get_item_data(new_index.row()), font_for_index(new_index)); 413} 414 415void IconView::did_change_cursor_index(ModelIndex const& old_index, ModelIndex const& new_index) 416{ 417 AbstractView::did_change_cursor_index(old_index, new_index); 418 if (old_index.is_valid()) 419 get_item_rects(old_index.row(), get_item_data(old_index.row()), font_for_index(old_index)); 420 if (new_index.is_valid()) 421 get_item_rects(new_index.row(), get_item_data(new_index.row()), font_for_index(new_index)); 422} 423 424void IconView::get_item_rects(int item_index, ItemData& item_data, Gfx::Font const& font) const 425{ 426 auto item_rect = this->item_rect(item_index); 427 item_data.icon_rect = Gfx::IntRect(0, 0, 32, 32).centered_within(item_rect); 428 item_data.icon_offset_y = -font.pixel_size_rounded_up() - 6; 429 item_data.icon_rect.translate_by(0, item_data.icon_offset_y); 430 431 int unwrapped_text_width = static_cast<int>(ceilf(font.width(item_data.text))); 432 int available_width = item_rect.width() - 6; 433 434 item_data.text_rect = { 0, item_data.icon_rect.bottom() + 6 + 1, 0, font.pixel_size_rounded_up() }; 435 item_data.wrapped_text_lines.clear(); 436 437 if ((unwrapped_text_width > available_width) && (item_data.selected || m_hovered_index == item_data.index || cursor_index() == item_data.index || m_always_wrap_item_labels)) { 438 int current_line_width = 0; 439 int current_line_start = 0; 440 int widest_line_width = 0; 441 Utf8View utf8_view(item_data.text); 442 auto it = utf8_view.begin(); 443 for (; it != utf8_view.end(); ++it) { 444 auto code_point = *it; 445 auto glyph_width = font.glyph_width(code_point); 446 if ((current_line_width + glyph_width + font.glyph_spacing()) > available_width) { 447 item_data.wrapped_text_lines.append(item_data.text.substring_view(current_line_start, utf8_view.byte_offset_of(it) - current_line_start)); 448 current_line_start = utf8_view.byte_offset_of(it); 449 current_line_width = glyph_width; 450 } else { 451 current_line_width += glyph_width + font.glyph_spacing(); 452 } 453 widest_line_width = max(widest_line_width, current_line_width); 454 } 455 if (current_line_width > 0) { 456 item_data.wrapped_text_lines.append(item_data.text.substring_view(current_line_start, utf8_view.byte_offset_of(it) - current_line_start)); 457 } 458 item_data.text_rect.set_width(widest_line_width); 459 item_data.text_rect.center_horizontally_within(item_rect); 460 item_data.text_rect.intersect(item_rect); 461 item_data.text_rect.set_height(font.pixel_size_rounded_up() * item_data.wrapped_text_lines.size()); 462 item_data.text_rect.inflate(6, 6); 463 item_data.text_rect_wrapped = item_data.text_rect; 464 } else { 465 item_data.text_rect.set_width(unwrapped_text_width); 466 item_data.text_rect.inflate(6, 6); 467 if (item_data.text_rect.width() > available_width) 468 item_data.text_rect.set_width(available_width); 469 item_data.text_rect.center_horizontally_within(item_rect); 470 } 471 item_data.text_rect.intersect(item_rect); 472 item_data.text_offset_y = item_data.text_rect.y() - item_rect.y(); 473} 474 475void IconView::second_paint_event(PaintEvent& event) 476{ 477 if (!m_rubber_banding) 478 return; 479 480 Painter painter(*this); 481 painter.add_clip_rect(event.rect()); 482 painter.add_clip_rect(widget_inner_rect()); 483 painter.translate(frame_thickness(), frame_thickness()); 484 painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value()); 485 486 auto rubber_band_rect = Gfx::IntRect::from_two_points(m_rubber_band_origin, m_rubber_band_current); 487 painter.fill_rect(rubber_band_rect, palette().rubber_band_fill()); 488 painter.draw_rect(rubber_band_rect, palette().rubber_band_border()); 489} 490 491void IconView::paint_event(PaintEvent& event) 492{ 493 Color widget_background_color = palette().color(background_role()); 494 Frame::paint_event(event); 495 496 Painter painter(*this); 497 painter.add_clip_rect(widget_inner_rect()); 498 painter.add_clip_rect(event.rect()); 499 500 painter.fill_rect(event.rect(), fill_with_background_color() ? widget_background_color : Color::Transparent); 501 502 if (!model()) 503 return; 504 505 painter.translate(frame_thickness(), frame_thickness()); 506 painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value()); 507 508 auto selection_color = is_focused() ? palette().selection() : palette().inactive_selection(); 509 510 for_each_item_intersecting_rect(to_content_rect(event.rect()), [&](auto& item_data) -> IterationDecision { 511 Color background_color; 512 if (item_data.selected) { 513 background_color = selection_color; 514 } else { 515 if (fill_with_background_color()) 516 background_color = widget_background_color; 517 } 518 519 auto icon = item_data.index.data(ModelRole::Icon); 520 521 if (icon.is_icon()) { 522 if (auto bitmap = icon.as_icon().bitmap_for_size(item_data.icon_rect.width())) { 523 Gfx::IntRect destination = bitmap->rect(); 524 destination.center_within(item_data.icon_rect); 525 526 if (item_data.selected) { 527 auto tint = selection_color.with_alpha(100); 528 painter.blit_filtered(destination.location(), *bitmap, bitmap->rect(), [&](auto src) { return src.blend(tint); }); 529 } else if (m_hovered_index.is_valid() && m_hovered_index == item_data.index) { 530 painter.blit_brightened(destination.location(), *bitmap, bitmap->rect()); 531 } else { 532 auto opacity = item_data.index.data(ModelRole::IconOpacity).as_float_or(1.0f); 533 painter.blit(destination.location(), *bitmap, bitmap->rect(), opacity); 534 } 535 } 536 } 537 538 auto font = font_for_index(item_data.index); 539 540 const auto& text_rect = item_data.text_rect; 541 542 if (m_edit_index != item_data.index) 543 painter.fill_rect(text_rect, background_color); 544 545 if (is_focused() && item_data.index == cursor_index()) { 546 painter.draw_rect(text_rect, widget_background_color); 547 painter.draw_focus_rect(text_rect, palette().focus_outline()); 548 } 549 550 if (!item_data.wrapped_text_lines.is_empty()) { 551 // Item text would not fit in the item text rect, let's break it up into lines.. 552 553 const auto& lines = item_data.wrapped_text_lines; 554 size_t number_of_text_lines = min((size_t)text_rect.height() / font->pixel_size_rounded_up(), lines.size()); 555 size_t previous_line_lengths = 0; 556 for (size_t line_index = 0; line_index < number_of_text_lines; ++line_index) { 557 Gfx::IntRect line_rect; 558 line_rect.set_width(text_rect.width()); 559 line_rect.set_height(font->pixel_size_rounded_up()); 560 line_rect.center_horizontally_within(item_data.text_rect); 561 line_rect.set_y(3 + item_data.text_rect.y() + line_index * font->pixel_size_rounded_up()); 562 line_rect.inflate(6, 0); 563 564 // Shrink the line_rect on the last line to apply elision if there are more lines. 565 if (number_of_text_lines - 1 == line_index && lines.size() > number_of_text_lines) 566 line_rect.inflate(-(6 + 2 * font->max_glyph_width()), 0); 567 568 draw_item_text(painter, item_data.index, item_data.selected, line_rect, lines[line_index], font, Gfx::TextAlignment::Center, Gfx::TextElision::Right, previous_line_lengths); 569 previous_line_lengths += lines[line_index].length(); 570 } 571 } else { 572 draw_item_text(painter, item_data.index, item_data.selected, item_data.text_rect, item_data.text, font, Gfx::TextAlignment::Center, Gfx::TextElision::Right); 573 } 574 575 if (has_pending_drop() && item_data.index == drop_candidate_index()) { 576 // FIXME: This visualization is not great, as it's also possible to drop things on the text label.. 577 painter.draw_rect(item_data.icon_rect.inflated(8, 8), palette().selection(), true); 578 } 579 return IterationDecision::Continue; 580 }); 581} 582 583int IconView::item_count() const 584{ 585 if (!model()) 586 return 0; 587 return model()->row_count(); 588} 589 590void IconView::did_update_selection() 591{ 592 AbstractView::did_update_selection(); 593 if (m_changing_selection) 594 return; 595 596 // Selection was modified externally, we need to synchronize our cache 597 do_clear_selection(); 598 selection().for_each_index([&](ModelIndex const& index) { 599 if (index.is_valid()) { 600 auto item_index = model_index_to_item_index(index); 601 if ((size_t)item_index < m_item_data_cache.size()) 602 do_add_selection(get_item_data(item_index)); 603 } 604 }); 605} 606 607void IconView::do_clear_selection() 608{ 609 for (size_t item_index = m_first_selected_hint; item_index < m_item_data_cache.size(); item_index++) { 610 if (m_selected_count_cache == 0) 611 break; 612 auto& item_data = m_item_data_cache[item_index]; 613 if (!item_data.selected) 614 continue; 615 item_data.selected = false; 616 m_selected_count_cache--; 617 } 618 m_first_selected_hint = 0; 619 VERIFY(m_selected_count_cache == 0); 620} 621 622void IconView::clear_selection() 623{ 624 TemporaryChange change(m_changing_selection, true); 625 AbstractView::clear_selection(); 626 do_clear_selection(); 627} 628 629bool IconView::do_add_selection(ItemData& item_data) 630{ 631 if (!item_data.selected) { 632 item_data.selected = true; 633 m_selected_count_cache++; 634 int item_index = &item_data - &m_item_data_cache[0]; 635 if (m_first_selected_hint > item_index) 636 m_first_selected_hint = item_index; 637 return true; 638 } 639 return false; 640} 641 642void IconView::add_selection(ItemData& item_data) 643{ 644 if (do_add_selection(item_data)) 645 AbstractView::add_selection(item_data.index); 646} 647 648void IconView::add_selection(ModelIndex const& new_index) 649{ 650 TemporaryChange change(m_changing_selection, true); 651 auto item_index = model_index_to_item_index(new_index); 652 add_selection(get_item_data(item_index)); 653} 654 655void IconView::toggle_selection(ItemData& item_data) 656{ 657 if (!item_data.selected) 658 add_selection(item_data); 659 else 660 remove_item_selection(item_data); 661} 662 663void IconView::toggle_selection(ModelIndex const& new_index) 664{ 665 TemporaryChange change(m_changing_selection, true); 666 auto item_index = model_index_to_item_index(new_index); 667 toggle_selection(get_item_data(item_index)); 668} 669 670void IconView::remove_item_selection(ItemData& item_data) 671{ 672 if (!item_data.selected) 673 return; 674 675 TemporaryChange change(m_changing_selection, true); 676 item_data.selected = false; 677 VERIFY(m_selected_count_cache > 0); 678 m_selected_count_cache--; 679 int item_index = &item_data - &m_item_data_cache[0]; 680 if (m_first_selected_hint == item_index) { 681 m_first_selected_hint = 0; 682 while ((size_t)item_index < m_item_data_cache.size()) { 683 if (m_item_data_cache[item_index].selected) { 684 m_first_selected_hint = item_index; 685 break; 686 } 687 item_index++; 688 } 689 } 690 AbstractView::remove_selection(item_data.index); 691} 692 693void IconView::set_selection(ModelIndex const& new_index) 694{ 695 TemporaryChange change(m_changing_selection, true); 696 do_clear_selection(); 697 auto item_index = model_index_to_item_index(new_index); 698 auto& item_data = get_item_data(item_index); 699 item_data.selected = true; 700 m_selected_count_cache = 1; 701 if (item_index < m_first_selected_hint) 702 m_first_selected_hint = item_index; 703 AbstractView::set_selection(new_index); 704} 705 706int IconView::items_per_page() const 707{ 708 if (m_flow_direction == FlowDirection::LeftToRight) 709 return (visible_content_rect().height() / effective_item_size().height()) * m_visual_column_count; 710 return (visible_content_rect().width() / effective_item_size().width()) * m_visual_row_count; 711} 712 713void IconView::move_cursor(CursorMovement movement, SelectionUpdate selection_update) 714{ 715 if (!model()) 716 return; 717 auto& model = *this->model(); 718 719 if (!cursor_index().is_valid()) { 720 set_cursor(model.index(0, model_column()), SelectionUpdate::Set); 721 return; 722 } 723 724 auto new_row = cursor_index().row(); 725 726 switch (movement) { 727 case CursorMovement::Right: 728 if (m_flow_direction == FlowDirection::LeftToRight) 729 new_row += 1; 730 else 731 new_row += m_visual_row_count; 732 break; 733 case CursorMovement::Left: 734 if (m_flow_direction == FlowDirection::LeftToRight) 735 new_row -= 1; 736 else 737 new_row -= m_visual_row_count; 738 break; 739 case CursorMovement::Up: 740 if (m_flow_direction == FlowDirection::LeftToRight) 741 new_row -= m_visual_column_count; 742 else 743 new_row -= 1; 744 break; 745 case CursorMovement::Down: 746 if (m_flow_direction == FlowDirection::LeftToRight) 747 new_row += m_visual_column_count; 748 else 749 new_row += 1; 750 break; 751 case CursorMovement::PageUp: 752 new_row = max(0, cursor_index().row() - items_per_page()); 753 break; 754 755 case CursorMovement::PageDown: 756 new_row = min(model.row_count() - 1, cursor_index().row() + items_per_page()); 757 break; 758 759 case CursorMovement::Home: 760 new_row = 0; 761 break; 762 case CursorMovement::End: 763 new_row = model.row_count() - 1; 764 break; 765 default: 766 return; 767 } 768 auto new_index = model.index(new_row, cursor_index().column()); 769 if (new_index.is_valid()) 770 set_cursor(new_index, selection_update); 771} 772 773void IconView::set_flow_direction(FlowDirection flow_direction) 774{ 775 if (m_flow_direction == flow_direction) 776 return; 777 m_flow_direction = flow_direction; 778 m_item_data_cache.clear(); 779 m_item_data_cache_valid = false; 780 update(); 781} 782 783template<typename Function> 784inline IterationDecision IconView::for_each_item_intersecting_rect(Gfx::IntRect const& rect, Function f) const 785{ 786 VERIFY(model()); 787 if (rect.is_empty()) 788 return IterationDecision::Continue; 789 int begin_row, begin_column; 790 column_row_from_content_position(rect.top_left(), begin_row, begin_column); 791 int end_row, end_column; 792 column_row_from_content_position(rect.bottom_right(), end_row, end_column); 793 794 int items_per_flow_axis_step; 795 int item_index; 796 int last_index; 797 if (m_flow_direction == FlowDirection::LeftToRight) { 798 items_per_flow_axis_step = end_column - begin_column + 1; 799 item_index = max(0, begin_row * m_visual_column_count + begin_column); 800 last_index = min(item_count(), end_row * m_visual_column_count + end_column + 1); 801 } else { 802 items_per_flow_axis_step = end_row - begin_row + 1; 803 item_index = max(0, begin_column * m_visual_row_count + begin_row); 804 last_index = min(item_count(), end_column * m_visual_row_count + end_row + 1); 805 } 806 807 while (item_index < last_index) { 808 for (int i = item_index; i < min(item_index + items_per_flow_axis_step, last_index); i++) { 809 auto& item_data = get_item_data(i); 810 if (item_data.is_intersecting(rect)) { 811 auto decision = f(item_data); 812 if (decision != IterationDecision::Continue) 813 return decision; 814 } 815 } 816 item_index += (m_flow_direction == FlowDirection::LeftToRight) ? m_visual_column_count : m_visual_row_count; 817 }; 818 819 return IterationDecision::Continue; 820} 821 822template<typename Function> 823inline IterationDecision IconView::for_each_item_intersecting_rects(Vector<Gfx::IntRect> const& rects, Function f) const 824{ 825 for (auto& rect : rects) { 826 auto decision = for_each_item_intersecting_rect(rect, f); 827 if (decision != IterationDecision::Continue) 828 return decision; 829 } 830 return IterationDecision::Continue; 831} 832}