LibGUI: Improve IconView rubberband performance

Rather than invalidating the entire window, which is very expensive on
the transparent desktop widget, just invalidate the areas that actually
need updating.
This commit is contained in:
Tom 2021-02-15 19:30:47 -07:00 committed by Andreas Kling
parent cd0a1fa5b0
commit 6cdb657493
Notes: sideshowbarker 2024-07-18 21:55:49 +09:00
5 changed files with 30 additions and 6 deletions

View file

@ -208,7 +208,8 @@ void AbstractView::notify_selection_changed(Badge<ModelSelection>)
did_update_selection(); did_update_selection();
if (on_selection_change) if (on_selection_change)
on_selection_change(); on_selection_change();
update(); if (!m_suppress_update_on_selection_change)
update();
} }
NonnullRefPtr<Gfx::Font> AbstractView::font_for_index(const ModelIndex& index) const NonnullRefPtr<Gfx::Font> AbstractView::font_for_index(const ModelIndex& index) const

View file

@ -171,6 +171,8 @@ protected:
void draw_item_text(Gfx::Painter&, const ModelIndex&, bool, const Gfx::IntRect&, const StringView&, const Gfx::Font&, Gfx::TextAlignment, Gfx::TextElision); void draw_item_text(Gfx::Painter&, const ModelIndex&, bool, const Gfx::IntRect&, const StringView&, const Gfx::Font&, Gfx::TextAlignment, Gfx::TextElision);
void set_suppress_update_on_selection_change(bool value) { m_suppress_update_on_selection_change = value; }
virtual void did_scroll() override; virtual void did_scroll() override;
void set_hovered_index(const ModelIndex&); void set_hovered_index(const ModelIndex&);
void activate(const ModelIndex&); void activate(const ModelIndex&);
@ -215,6 +217,7 @@ private:
bool m_tab_key_navigation_enabled { false }; bool m_tab_key_navigation_enabled { false };
bool m_is_dragging { false }; bool m_is_dragging { false };
bool m_draw_item_text_with_shadow { false }; bool m_draw_item_text_with_shadow { false };
bool m_suppress_update_on_selection_change { false };
}; };
} }

View file

@ -258,7 +258,7 @@ void IconView::mouseup_event(MouseEvent& event)
m_rubber_banding = false; m_rubber_banding = false;
if (m_out_of_view_timer) if (m_out_of_view_timer)
m_out_of_view_timer->stop(); m_out_of_view_timer->stop();
update(); update(to_widget_rect(Gfx::IntRect::from_two_points(m_rubber_band_origin, m_rubber_band_current)));
} }
AbstractView::mouseup_event(event); AbstractView::mouseup_event(event);
} }
@ -268,8 +268,15 @@ bool IconView::update_rubber_banding(const Gfx::IntPoint& position)
auto adjusted_position = to_content_position(position); auto adjusted_position = to_content_position(position);
if (m_rubber_band_current != adjusted_position) { if (m_rubber_band_current != adjusted_position) {
auto prev_rect = Gfx::IntRect::from_two_points(m_rubber_band_origin, m_rubber_band_current); auto prev_rect = Gfx::IntRect::from_two_points(m_rubber_band_origin, m_rubber_band_current);
auto prev_rubber_band_fill_rect = prev_rect.shrunken(1, 1);
m_rubber_band_current = adjusted_position; m_rubber_band_current = adjusted_position;
auto rubber_band_rect = Gfx::IntRect::from_two_points(m_rubber_band_origin, m_rubber_band_current); auto rubber_band_rect = Gfx::IntRect::from_two_points(m_rubber_band_origin, m_rubber_band_current);
auto rubber_band_fill_rect = rubber_band_rect.shrunken(1, 1);
for (auto& rect : prev_rubber_band_fill_rect.shatter(rubber_band_fill_rect))
update(to_widget_rect(rect.inflated(1, 1)));
for (auto& rect : rubber_band_fill_rect.shatter(prev_rubber_band_fill_rect))
update(to_widget_rect(rect.inflated(1, 1)));
// If the rectangle width or height is 0, we still want to be able // If the rectangle width or height is 0, we still want to be able
// to match the items in the path. An easy work-around for this // to match the items in the path. An easy work-around for this
@ -302,11 +309,16 @@ bool IconView::update_rubber_banding(const Gfx::IntPoint& position)
return IterationDecision::Continue; return IterationDecision::Continue;
}); });
// We're changing the selection and invalidating those items, so
// no need to trigger a full re-render for each item
set_suppress_update_on_selection_change(true);
// Now toggle all items that are no longer in the selected area, once only // Now toggle all items that are no longer in the selected area, once only
for_each_item_intersecting_rects(deselect_area, [&](ItemData& item_data) -> IterationDecision { for_each_item_intersecting_rects(deselect_area, [&](ItemData& item_data) -> IterationDecision {
if (!item_data.selection_toggled && item_data.is_intersecting(prev_rect) && !item_data.is_intersecting(rubber_band_rect)) { if (!item_data.selection_toggled && item_data.is_intersecting(prev_rect) && !item_data.is_intersecting(rubber_band_rect)) {
item_data.selection_toggled = true; item_data.selection_toggled = true;
toggle_selection(item_data); toggle_selection(item_data);
update(to_widget_rect(item_data.rect()));
} }
return IterationDecision::Continue; return IterationDecision::Continue;
}); });
@ -315,11 +327,13 @@ bool IconView::update_rubber_banding(const Gfx::IntPoint& position)
if (!item_data.selection_toggled && !item_data.is_intersecting(prev_rect) && item_data.is_intersecting(rubber_band_rect)) { if (!item_data.selection_toggled && !item_data.is_intersecting(prev_rect) && item_data.is_intersecting(rubber_band_rect)) {
item_data.selection_toggled = true; item_data.selection_toggled = true;
toggle_selection(item_data); toggle_selection(item_data);
update(to_widget_rect(item_data.rect()));
} }
return IterationDecision::Continue; return IterationDecision::Continue;
}); });
update(); set_suppress_update_on_selection_change(false);
return true; return true;
} }
return false; return false;
@ -491,8 +505,8 @@ void IconView::paint_event(PaintEvent& event)
painter.add_clip_rect(widget_inner_rect()); painter.add_clip_rect(widget_inner_rect());
painter.add_clip_rect(event.rect()); painter.add_clip_rect(event.rect());
if (fill_with_background_color()) painter.fill_rect(event.rect(), fill_with_background_color() ? widget_background_color : Color::Transparent);
painter.fill_rect(event.rect(), widget_background_color);
painter.translate(frame_thickness(), frame_thickness()); painter.translate(frame_thickness(), frame_thickness());
painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value()); painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value());

View file

@ -109,6 +109,11 @@ private:
VERIFY(valid); VERIFY(valid);
return icon_rect.contains(point) || text_rect.contains(point); return icon_rect.contains(point) || text_rect.contains(point);
} }
Gfx::IntRect rect() const
{
return text_rect.united(icon_rect);
}
}; };
template<typename Function> template<typename Function>

View file

@ -129,6 +129,8 @@ public:
m_state_stack.take_last(); m_state_stack.take_last();
} }
IntRect clip_rect() const { return state().clip_rect; }
protected: protected:
IntPoint translation() const { return state().translation; } IntPoint translation() const { return state().translation; }
IntRect to_physical(const IntRect& r) const { return r.translated(translation()) * scale(); } IntRect to_physical(const IntRect& r) const { return r.translated(translation()) * scale(); }
@ -139,7 +141,6 @@ protected:
void fill_rect_with_draw_op(const IntRect&, Color); void fill_rect_with_draw_op(const IntRect&, Color);
void blit_with_opacity(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect, float opacity, bool apply_alpha = true); void blit_with_opacity(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect, float opacity, bool apply_alpha = true);
void draw_physical_pixel(const IntPoint&, Color, int thickness = 1); void draw_physical_pixel(const IntPoint&, Color, int thickness = 1);
IntRect clip_rect() const { return state().clip_rect; }
struct State { struct State {
const Font* font; const Font* font;