LibGUI: Support top-to-bottom flow in IconView

Sometimes you might want your icons to flow from top-to-bottom instead
of from left-to-right. :^)
This commit is contained in:
Andreas Kling 2020-12-27 14:44:49 +01:00
parent 0117c57418
commit 8b31833650
Notes: sideshowbarker 2024-07-19 00:34:00 +09:00
2 changed files with 104 additions and 31 deletions

View file

@ -129,7 +129,9 @@ auto IconView::item_data_from_content_position(const Gfx::IntPoint& content_posi
return nullptr;
int row, column;
column_row_from_content_position(content_position, row, column);
int item_index = row * m_visual_column_count + column;
int item_index = (m_flow_direction == FlowDirection::LeftToRight)
? row * m_visual_column_count + column
: column * m_visual_row_count + row;
if (item_index < 0 || item_index >= item_count())
return nullptr;
return &get_item_data(item_index);
@ -154,14 +156,26 @@ void IconView::update_content_size()
if (!model())
return set_content_size({});
m_visual_column_count = max(1, available_size().width() / effective_item_size().width());
if (m_visual_column_count)
m_visual_row_count = ceil_div(model()->row_count(), m_visual_column_count);
else
m_visual_row_count = 0;
int content_width;
int content_height;
int content_width = available_size().width();
int content_height = m_visual_row_count * effective_item_size().height();
if (m_flow_direction == FlowDirection::LeftToRight) {
m_visual_column_count = max(1, available_size().width() / effective_item_size().width());
if (m_visual_column_count)
m_visual_row_count = ceil_div(model()->row_count(), m_visual_column_count);
else
m_visual_row_count = 0;
content_width = available_size().width();
content_height = m_visual_row_count * effective_item_size().height();
} else {
m_visual_row_count = max(1, available_size().height() / effective_item_size().height());
if (m_visual_row_count)
m_visual_column_count = ceil_div(model()->row_count(), m_visual_row_count);
else
m_visual_column_count = 0;
content_width = m_visual_column_count * effective_item_size().width();
content_height = available_size().height();
}
set_content_size({ content_width, content_height });
@ -179,8 +193,17 @@ Gfx::IntRect IconView::item_rect(int item_index) const
{
if (!m_visual_row_count || !m_visual_column_count)
return {};
int visual_row_index = item_index / m_visual_column_count;
int visual_column_index = item_index % m_visual_column_count;
int visual_row_index;
int visual_column_index;
if (m_flow_direction == FlowDirection::LeftToRight) {
visual_row_index = item_index / m_visual_column_count;
visual_column_index = item_index % m_visual_column_count;
} else {
visual_row_index = item_index % m_visual_row_count;
visual_column_index = item_index / m_visual_row_count;
}
return {
visual_column_index * effective_item_size().width(),
visual_row_index * effective_item_size().height(),
@ -686,6 +709,13 @@ void IconView::set_selection(const ModelIndex& new_index)
AbstractView::set_selection(new_index);
}
int IconView::items_per_page() const
{
if (m_flow_direction == FlowDirection::LeftToRight)
return (visible_content_rect().height() / effective_item_size().height()) * m_visual_column_count;
return (visible_content_rect().width() / effective_item_size().width()) * m_visual_row_count;
}
void IconView::move_cursor(CursorMovement movement, SelectionUpdate selection_update)
{
if (!model())
@ -697,44 +727,65 @@ void IconView::move_cursor(CursorMovement movement, SelectionUpdate selection_up
return;
}
ModelIndex new_index;
auto new_row = cursor_index().row();
switch (movement) {
case CursorMovement::Right:
new_index = model.index(cursor_index().row() + 1, cursor_index().column());
if (m_flow_direction == FlowDirection::LeftToRight)
new_row += 1;
else
new_row += m_visual_row_count;
break;
case CursorMovement::Left:
new_index = model.index(cursor_index().row() - 1, cursor_index().column());
if (m_flow_direction == FlowDirection::LeftToRight)
new_row -= 1;
else
new_row -= m_visual_row_count;
break;
case CursorMovement::Up:
new_index = model.index(cursor_index().row() - m_visual_column_count, cursor_index().column());
if (m_flow_direction == FlowDirection::LeftToRight)
new_row -= m_visual_column_count;
else
new_row -= 1;
break;
case CursorMovement::Down:
new_index = model.index(cursor_index().row() + m_visual_column_count, cursor_index().column());
if (m_flow_direction == FlowDirection::LeftToRight)
new_row += m_visual_column_count;
else
new_row += 1;
break;
case CursorMovement::PageUp: {
int items_per_page = (visible_content_rect().height() / effective_item_size().height()) * m_visual_column_count;
new_index = model.index(max(0, cursor_index().row() - items_per_page), cursor_index().column());
case CursorMovement::PageUp:
new_row = max(0, cursor_index().row() - items_per_page());
break;
}
case CursorMovement::PageDown: {
int items_per_page = (visible_content_rect().height() / effective_item_size().height()) * m_visual_column_count;
new_index = model.index(min(model.row_count() - 1, cursor_index().row() + items_per_page), cursor_index().column());
case CursorMovement::PageDown:
new_row = min(model.row_count() - 1, cursor_index().row() + items_per_page());
break;
}
case CursorMovement::Home:
new_index = model.index(0, model_column());
new_row = 0;
break;
case CursorMovement::End:
new_index = model.index(model.row_count() - 1, model_column());
new_row = model.row_count() - 1;
break;
default:
break;
return;
}
auto new_index = model.index(new_row, cursor_index().column());
if (new_index.is_valid())
set_cursor(new_index, selection_update);
}
void IconView::set_flow_direction(FlowDirection flow_direction)
{
if (m_flow_direction == flow_direction)
return;
m_flow_direction = flow_direction;
m_item_data_cache.clear();
m_item_data_cache_valid = false;
update();
}
template<typename Function>
inline IterationDecision IconView::for_each_item_intersecting_rect(const Gfx::IntRect& rect, Function f) const
{
@ -745,11 +796,22 @@ inline IterationDecision IconView::for_each_item_intersecting_rect(const Gfx::In
column_row_from_content_position(rect.top_left(), begin_row, begin_column);
int end_row, end_column;
column_row_from_content_position(rect.bottom_right(), end_row, end_column);
int items_per_column = end_column - begin_column + 1;
int item_index = max(0, begin_row * m_visual_column_count + begin_column);
int last_index = min(item_count(), end_row * m_visual_column_count + end_column + 1);
int items_per_flow_axis_step;
int item_index;
int last_index;
if (m_flow_direction == FlowDirection::LeftToRight) {
items_per_flow_axis_step = end_column - begin_column + 1;
item_index = max(0, begin_row * m_visual_column_count + begin_column);
last_index = min(item_count(), end_row * m_visual_column_count + end_column + 1);
} else {
items_per_flow_axis_step = end_row - begin_row + 1;
item_index = max(0, begin_column * m_visual_row_count + begin_row);
last_index = min(item_count(), end_column * m_visual_row_count + end_row + 1);
}
while (item_index < last_index) {
for (int i = item_index; i < min(item_index + items_per_column, last_index); i++) {
for (int i = item_index; i < min(item_index + items_per_flow_axis_step, last_index); i++) {
auto& item_data = get_item_data(i);
if (item_data.is_intersecting(rect)) {
auto decision = f(item_data);
@ -759,6 +821,7 @@ inline IterationDecision IconView::for_each_item_intersecting_rect(const Gfx::In
}
item_index += m_visual_column_count;
};
return IterationDecision::Continue;
}
@ -772,5 +835,4 @@ inline IterationDecision IconView::for_each_item_intersecting_rects(const Vector
}
return IterationDecision::Continue;
}
}

View file

@ -38,6 +38,14 @@ class IconView : public AbstractView {
public:
virtual ~IconView() override;
enum class FlowDirection {
LeftToRight,
TopToBottom,
};
FlowDirection flow_direction() const { return m_flow_direction; }
void set_flow_direction(FlowDirection);
int content_width() const;
int horizontal_padding() const { return m_horizontal_padding; }
@ -120,6 +128,7 @@ private:
void get_item_rects(int item_index, ItemData& item_data, const Gfx::Font&) const;
bool update_rubber_banding(const Gfx::IntPoint&);
void scroll_out_of_view_timer_fired();
int items_per_page() const;
void reinit_item_cache() const;
int model_index_to_item_index(const ModelIndex& model_index) const
@ -158,6 +167,8 @@ private:
ModelIndex m_drop_candidate_index;
FlowDirection m_flow_direction { FlowDirection::LeftToRight };
mutable Vector<ItemData> m_item_data_cache;
mutable int m_selected_count_cache { 0 };
mutable int m_first_selected_hint { 0 };