Serenity Operating System
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#pragma once
9
10#include <AK/IterationDecision.h>
11#include <LibGUI/AbstractView.h>
12#include <LibGUI/Forward.h>
13#include <LibGUI/Variant.h>
14
15namespace GUI {
16
17class IconView : public AbstractView {
18 C_OBJECT(IconView)
19public:
20 virtual ~IconView() override = default;
21
22 enum class FlowDirection {
23 LeftToRight,
24 TopToBottom,
25 };
26
27 FlowDirection flow_direction() const { return m_flow_direction; }
28 void set_flow_direction(FlowDirection);
29
30 int horizontal_padding() const { return m_horizontal_padding; }
31
32 virtual void scroll_into_view(ModelIndex const&, bool scroll_horizontally = true, bool scroll_vertically = true) override;
33
34 Gfx::IntSize effective_item_size() const { return m_effective_item_size; }
35
36 bool always_wrap_item_labels() const { return m_always_wrap_item_labels; }
37 void set_always_wrap_item_labels(bool value) { m_always_wrap_item_labels = value; }
38
39 int model_column() const { return m_model_column; }
40 void set_model_column(int column) { m_model_column = column; }
41
42 virtual ModelIndex index_at_event_position(Gfx::IntPoint) const override;
43 virtual Gfx::IntRect content_rect(ModelIndex const&) const override;
44 virtual Gfx::IntRect editing_rect(ModelIndex const&) const override;
45 virtual Gfx::IntRect paint_invalidation_rect(ModelIndex const&) const override;
46
47 virtual void select_all() override;
48
49protected:
50 virtual void did_change_font() override;
51
52private:
53 IconView();
54
55 virtual void model_did_update(unsigned flags) override;
56 virtual void paint_event(PaintEvent&) override;
57 virtual void second_paint_event(PaintEvent&) override;
58 virtual void resize_event(ResizeEvent&) override;
59 virtual void mousedown_event(MouseEvent&) override;
60 virtual void mousemove_event(MouseEvent&) override;
61 virtual void mouseup_event(MouseEvent&) override;
62 virtual void did_change_hovered_index(ModelIndex const& old_index, ModelIndex const& new_index) override;
63 virtual void did_change_cursor_index(ModelIndex const& old_index, ModelIndex const& new_index) override;
64 virtual void editing_widget_did_change(ModelIndex const& index) override;
65
66 virtual void move_cursor(CursorMovement, SelectionUpdate) override;
67
68 virtual void automatic_scrolling_timer_did_fire() override;
69
70 struct ItemData {
71 Gfx::IntRect text_rect;
72 Optional<Gfx::IntRect> text_rect_wrapped;
73 Gfx::IntRect icon_rect;
74 int icon_offset_y;
75 int text_offset_y;
76 DeprecatedString text;
77 Vector<StringView> wrapped_text_lines;
78 ModelIndex index;
79 bool valid { false };
80 bool selected { false }; // always valid
81 bool selection_toggled; // only used as a temporary marker
82
83 bool is_valid() const { return valid; }
84 void invalidate()
85 {
86 valid = false;
87 text = {};
88 }
89
90 Gfx::IntRect hot_icon_rect() const { return icon_rect.inflated(10, 10); }
91 Gfx::IntRect hot_text_rect() const { return text_rect.inflated(2, 2); }
92
93 bool is_intersecting(Gfx::IntRect const& rect) const
94 {
95 VERIFY(valid);
96 return hot_icon_rect().intersects(rect) || hot_text_rect().intersects(rect);
97 }
98
99 bool is_containing(Gfx::IntPoint point) const
100 {
101 VERIFY(valid);
102 return hot_icon_rect().contains(point) || hot_text_rect().contains(point);
103 }
104
105 Gfx::IntRect rect(bool wrapped = false) const
106 {
107 if (wrapped && text_rect_wrapped.has_value())
108 return text_rect_wrapped->united(icon_rect);
109 return text_rect.united(icon_rect);
110 }
111 };
112
113 template<typename Function>
114 IterationDecision for_each_item_intersecting_rect(Gfx::IntRect const&, Function) const;
115
116 template<typename Function>
117 IterationDecision for_each_item_intersecting_rects(Vector<Gfx::IntRect> const&, Function) const;
118
119 void column_row_from_content_position(Gfx::IntPoint content_position, int& row, int& column) const
120 {
121 row = max(0, min(m_visual_row_count - 1, content_position.y() / effective_item_size().height()));
122 column = max(0, min(m_visual_column_count - 1, content_position.x() / effective_item_size().width()));
123 }
124
125 int item_count() const;
126 Gfx::IntRect item_rect(int item_index) const;
127 void update_content_size();
128 void update_item_rects(int item_index, ItemData& item_data) const;
129 void get_item_rects(int item_index, ItemData& item_data, Gfx::Font const&) const;
130 bool update_rubber_banding(Gfx::IntPoint);
131 int items_per_page() const;
132
133 void rebuild_item_cache() const;
134 int model_index_to_item_index(ModelIndex const& model_index) const
135 {
136 VERIFY(model_index.row() < item_count());
137 return model_index.row();
138 }
139
140 virtual void did_update_selection() override;
141 virtual void clear_selection() override;
142 virtual void add_selection(ModelIndex const& new_index) override;
143 virtual void set_selection(ModelIndex const& new_index) override;
144 virtual void toggle_selection(ModelIndex const& new_index) override;
145
146 ItemData& get_item_data(int) const;
147 ItemData* item_data_from_content_position(Gfx::IntPoint) const;
148 void do_clear_selection();
149 bool do_add_selection(ItemData&);
150 void add_selection(ItemData&);
151 void remove_item_selection(ItemData&);
152 void toggle_selection(ItemData&);
153
154 int m_horizontal_padding { 5 };
155 int m_model_column { 0 };
156 int m_visual_column_count { 0 };
157 int m_visual_row_count { 0 };
158
159 Gfx::IntSize m_effective_item_size { 80, 80 };
160
161 bool m_always_wrap_item_labels { false };
162
163 bool m_rubber_banding { false };
164 Gfx::IntPoint m_out_of_view_position;
165 Gfx::IntPoint m_rubber_band_origin;
166 Gfx::IntPoint m_rubber_band_current;
167 Gfx::IntPoint m_rubber_band_scroll_delta;
168
169 FlowDirection m_flow_direction { FlowDirection::LeftToRight };
170
171 mutable Vector<ItemData> m_item_data_cache;
172 mutable int m_selected_count_cache { 0 };
173 mutable int m_first_selected_hint { 0 };
174 mutable bool m_item_data_cache_valid { false };
175
176 bool m_changing_selection { false };
177
178 bool m_had_valid_size { false };
179};
180
181}