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();
if (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

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 set_suppress_update_on_selection_change(bool value) { m_suppress_update_on_selection_change = value; }
virtual void did_scroll() override;
void set_hovered_index(const ModelIndex&);
void activate(const ModelIndex&);
@ -215,6 +217,7 @@ private:
bool m_tab_key_navigation_enabled { false };
bool m_is_dragging { 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;
if (m_out_of_view_timer)
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);
}
@ -268,8 +268,15 @@ bool IconView::update_rubber_banding(const Gfx::IntPoint& position)
auto adjusted_position = to_content_position(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_rubber_band_fill_rect = prev_rect.shrunken(1, 1);
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_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
// 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;
});
// 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
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)) {
item_data.selection_toggled = true;
toggle_selection(item_data);
update(to_widget_rect(item_data.rect()));
}
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)) {
item_data.selection_toggled = true;
toggle_selection(item_data);
update(to_widget_rect(item_data.rect()));
}
return IterationDecision::Continue;
});
update();
set_suppress_update_on_selection_change(false);
return true;
}
return false;
@ -491,8 +505,8 @@ void IconView::paint_event(PaintEvent& event)
painter.add_clip_rect(widget_inner_rect());
painter.add_clip_rect(event.rect());
if (fill_with_background_color())
painter.fill_rect(event.rect(), widget_background_color);
painter.fill_rect(event.rect(), fill_with_background_color() ? widget_background_color : Color::Transparent);
painter.translate(frame_thickness(), frame_thickness());
painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value());

View file

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

View file

@ -129,6 +129,8 @@ public:
m_state_stack.take_last();
}
IntRect clip_rect() const { return state().clip_rect; }
protected:
IntPoint translation() const { return state().translation; }
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 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);
IntRect clip_rect() const { return state().clip_rect; }
struct State {
const Font* font;