From eda1d916546ada6a9b8d5e8a96f5033e871bfeb3 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 1 Nov 2021 22:08:23 +0100 Subject: [PATCH] Add culling of the painting for most widgets This is a good early-out for widgets in `ScrollAreas`, but also prepares for speeding up the first pass of a possible two-pass version of egui: https://github.com/emilk/egui/issues/843 --- egui/src/containers/collapsing_header.rs | 72 +++---- egui/src/containers/combo_box.rs | 53 +++--- egui/src/containers/frame.rs | 7 +- egui/src/containers/scroll_area.rs | 86 ++++----- egui/src/menu.rs | 2 +- egui/src/ui.rs | 50 +++-- egui/src/widgets/button.rs | 104 ++++++----- egui/src/widgets/color_picker.rs | 187 ++++++++++--------- egui/src/widgets/hyperlink.rs | 30 +-- egui/src/widgets/image.rs | 40 ++-- egui/src/widgets/label.rs | 41 ++-- egui/src/widgets/progress_bar.rs | 129 +++++++------ egui/src/widgets/selected_label.rs | 25 +-- egui/src/widgets/separator.rs | 30 +-- egui/src/widgets/slider.rs | 2 +- egui/src/widgets/text_edit/builder.rs | 66 +++---- egui_demo_lib/src/apps/demo/toggle_switch.rs | 61 +++--- 17 files changed, 524 insertions(+), 461 deletions(-) diff --git a/egui/src/containers/collapsing_header.rs b/egui/src/containers/collapsing_header.rs index 6a912782007..97e04e20088 100644 --- a/egui/src/containers/collapsing_header.rs +++ b/egui/src/containers/collapsing_header.rs @@ -295,45 +295,47 @@ impl CollapsingHeader { header_response .widget_info(|| WidgetInfo::labeled(WidgetType::CollapsingHeader, text.text())); - let visuals = ui - .style() - .interact_selectable(&header_response, self.selected); - - if ui.visuals().collapsing_header_frame || self.show_background { - ui.painter().add(epaint::RectShape { - rect: header_response.rect.expand(visuals.expansion), - corner_radius: visuals.corner_radius, - fill: visuals.bg_fill, - stroke: visuals.bg_stroke, - // stroke: Default::default(), - }); - } + if ui.is_rect_visible(rect) { + let visuals = ui + .style() + .interact_selectable(&header_response, self.selected); + + if ui.visuals().collapsing_header_frame || self.show_background { + ui.painter().add(epaint::RectShape { + rect: header_response.rect.expand(visuals.expansion), + corner_radius: visuals.corner_radius, + fill: visuals.bg_fill, + stroke: visuals.bg_stroke, + // stroke: Default::default(), + }); + } - if self.selected - || self.selectable && (header_response.hovered() || header_response.has_focus()) - { - let rect = rect.expand(visuals.expansion); + if self.selected + || self.selectable && (header_response.hovered() || header_response.has_focus()) + { + let rect = rect.expand(visuals.expansion); - let corner_radius = 2.0; - ui.painter() - .rect(rect, corner_radius, visuals.bg_fill, visuals.bg_stroke); - } + let corner_radius = 2.0; + ui.painter() + .rect(rect, corner_radius, visuals.bg_fill, visuals.bg_stroke); + } - { - let (mut icon_rect, _) = ui.spacing().icon_rectangles(header_response.rect); - icon_rect.set_center(pos2( - header_response.rect.left() + ui.spacing().indent / 2.0, - header_response.rect.center().y, - )); - let icon_response = Response { - rect: icon_rect, - ..header_response.clone() - }; - let openness = state.openness(ui.ctx(), id); - paint_icon(ui, openness, &icon_response); - } + { + let (mut icon_rect, _) = ui.spacing().icon_rectangles(header_response.rect); + icon_rect.set_center(pos2( + header_response.rect.left() + ui.spacing().indent / 2.0, + header_response.rect.center().y, + )); + let icon_response = Response { + rect: icon_rect, + ..header_response.clone() + }; + let openness = state.openness(ui.ctx(), id); + paint_icon(ui, openness, &icon_response); + } - text.paint_with_visuals(ui.painter(), text_pos, &visuals); + text.paint_with_visuals(ui.painter(), text_pos, &visuals); + } Prepared { id, diff --git a/egui/src/containers/combo_box.rs b/egui/src/containers/combo_box.rs index 873e4cd9472..3589fa12584 100644 --- a/egui/src/containers/combo_box.rs +++ b/egui/src/containers/combo_box.rs @@ -173,16 +173,18 @@ fn combo_box_dyn<'c, R>( let response = ui.interact(button_rect, button_id, Sense::click()); // response.active |= is_popup_open; - let icon_rect = Align2::RIGHT_CENTER.align_size_within_rect(icon_size, rect); - let visuals = if is_popup_open { - &ui.visuals().widgets.open - } else { - ui.style().interact(&response) - }; - paint_icon(ui.painter(), icon_rect.expand(visuals.expansion), visuals); + if ui.is_rect_visible(rect) { + let icon_rect = Align2::RIGHT_CENTER.align_size_within_rect(icon_size, rect); + let visuals = if is_popup_open { + &ui.visuals().widgets.open + } else { + ui.style().interact(&response) + }; + paint_icon(ui.painter(), icon_rect.expand(visuals.expansion), visuals); - let text_rect = Align2::LEFT_CENTER.align_size_within_rect(galley.size(), rect); - galley.paint_with_visuals(ui.painter(), text_rect.min, visuals); + let text_rect = Align2::LEFT_CENTER.align_size_within_rect(galley.size(), rect); + galley.paint_with_visuals(ui.painter(), text_rect.min, visuals); + } }); if button_response.clicked() { @@ -223,21 +225,24 @@ fn button_frame( outer_rect.set_height(outer_rect.height().at_least(interact_size.y)); let response = ui.interact(outer_rect, id, sense); - let visuals = if is_popup_open { - &ui.visuals().widgets.open - } else { - ui.style().interact(&response) - }; - - ui.painter().set( - where_to_put_background, - epaint::RectShape { - rect: outer_rect.expand(visuals.expansion), - corner_radius: visuals.corner_radius, - fill: visuals.bg_fill, - stroke: visuals.bg_stroke, - }, - ); + + if ui.is_rect_visible(outer_rect) { + let visuals = if is_popup_open { + &ui.visuals().widgets.open + } else { + ui.style().interact(&response) + }; + + ui.painter().set( + where_to_put_background, + epaint::RectShape { + rect: outer_rect.expand(visuals.expansion), + corner_radius: visuals.corner_radius, + fill: visuals.bg_fill, + stroke: visuals.bg_stroke, + }, + ); + } ui.advance_cursor_after_rect(outer_rect); diff --git a/egui/src/containers/frame.rs b/egui/src/containers/frame.rs index 4214f991b2a..fd85819d89b 100644 --- a/egui/src/containers/frame.rs +++ b/egui/src/containers/frame.rs @@ -209,8 +209,11 @@ impl Prepared { .. } = self; - let shape = frame.paint(outer_rect); - ui.painter().set(where_to_put_background, shape); + if ui.is_rect_visible(outer_rect) { + let shape = frame.paint(outer_rect); + ui.painter().set(where_to_put_background, shape); + } + ui.allocate_rect(outer_rect, Sense::hover()) } } diff --git a/egui/src/containers/scroll_area.rs b/egui/src/containers/scroll_area.rs index 985a31eab3d..cc1251d8ad9 100644 --- a/egui/src/containers/scroll_area.rs +++ b/egui/src/containers/scroll_area.rs @@ -671,50 +671,52 @@ impl Prepared { state.vel[d] = 0.0; } - // Avoid frame-delay by calculating a new handle rect: - let mut handle_rect = if d == 0 { - Rect::from_min_max( - pos2(from_content(state.offset.x), min_cross), - pos2(from_content(state.offset.x + inner_rect.width()), max_cross), - ) - } else { - Rect::from_min_max( - pos2(min_cross, from_content(state.offset.y)), - pos2( - max_cross, - from_content(state.offset.y + inner_rect.height()), - ), - ) - }; - let min_handle_size = ui.spacing().scroll_bar_width; - if handle_rect.size()[d] < min_handle_size { - handle_rect = Rect::from_center_size( - handle_rect.center(), - if d == 0 { - vec2(min_handle_size, handle_rect.size().y) - } else { - vec2(handle_rect.size().x, min_handle_size) - }, - ); - } + if ui.is_rect_visible(outer_scroll_rect) { + // Avoid frame-delay by calculating a new handle rect: + let mut handle_rect = if d == 0 { + Rect::from_min_max( + pos2(from_content(state.offset.x), min_cross), + pos2(from_content(state.offset.x + inner_rect.width()), max_cross), + ) + } else { + Rect::from_min_max( + pos2(min_cross, from_content(state.offset.y)), + pos2( + max_cross, + from_content(state.offset.y + inner_rect.height()), + ), + ) + }; + let min_handle_size = ui.spacing().scroll_bar_width; + if handle_rect.size()[d] < min_handle_size { + handle_rect = Rect::from_center_size( + handle_rect.center(), + if d == 0 { + vec2(min_handle_size, handle_rect.size().y) + } else { + vec2(handle_rect.size().x, min_handle_size) + }, + ); + } - let visuals = if scrolling_enabled { - ui.style().interact(&response) - } else { - &ui.style().visuals.widgets.inactive - }; + let visuals = if scrolling_enabled { + ui.style().interact(&response) + } else { + &ui.style().visuals.widgets.inactive + }; - ui.painter().add(epaint::Shape::rect_filled( - outer_scroll_rect, - visuals.corner_radius, - ui.visuals().extreme_bg_color, - )); - - ui.painter().add(epaint::Shape::rect_filled( - handle_rect, - visuals.corner_radius, - visuals.bg_fill, - )); + ui.painter().add(epaint::Shape::rect_filled( + outer_scroll_rect, + visuals.corner_radius, + ui.visuals().extreme_bg_color, + )); + + ui.painter().add(epaint::Shape::rect_filled( + handle_rect, + visuals.corner_radius, + visuals.bg_fill, + )); + } } ui.advance_cursor_after_rect(outer_rect); diff --git a/egui/src/menu.rs b/egui/src/menu.rs index 0c39ec3bda1..18231460116 100644 --- a/egui/src/menu.rs +++ b/egui/src/menu.rs @@ -426,7 +426,7 @@ impl SubMenuButton { crate::WidgetInfo::labeled(crate::WidgetType::Button, &text_galley.text()) }); - if ui.clip_rect().intersects(rect) { + if ui.is_rect_visible(rect) { let visuals = Self::visuals(ui, &response, menu_state, sub_id); let text_pos = Align2::LEFT_CENTER .align_size_within_rect(text_galley.size(), rect.shrink2(button_padding)) diff --git a/egui/src/ui.rs b/egui/src/ui.rs index 35474f153c5..fe798d0a6fc 100644 --- a/egui/src/ui.rs +++ b/egui/src/ui.rs @@ -122,13 +122,13 @@ impl Ui { // ------------------------------------------------- /// A unique identity of this `Ui`. - #[inline(always)] + #[inline] pub fn id(&self) -> Id { self.id } /// Style options for this `Ui` and its children. - #[inline(always)] + #[inline] pub fn style(&self) -> &std::sync::Arc