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
This commit is contained in:
Emil Ernerfeldt 2021-11-01 22:08:23 +01:00
parent 461f380a24
commit eda1d91654
17 changed files with 525 additions and 462 deletions

View file

@ -295,6 +295,7 @@ impl CollapsingHeader {
header_response header_response
.widget_info(|| WidgetInfo::labeled(WidgetType::CollapsingHeader, text.text())); .widget_info(|| WidgetInfo::labeled(WidgetType::CollapsingHeader, text.text()));
if ui.is_rect_visible(rect) {
let visuals = ui let visuals = ui
.style() .style()
.interact_selectable(&header_response, self.selected); .interact_selectable(&header_response, self.selected);
@ -334,6 +335,7 @@ impl CollapsingHeader {
} }
text.paint_with_visuals(ui.painter(), text_pos, &visuals); text.paint_with_visuals(ui.painter(), text_pos, &visuals);
}
Prepared { Prepared {
id, id,

View file

@ -173,6 +173,7 @@ fn combo_box_dyn<'c, R>(
let response = ui.interact(button_rect, button_id, Sense::click()); let response = ui.interact(button_rect, button_id, Sense::click());
// response.active |= is_popup_open; // response.active |= is_popup_open;
if ui.is_rect_visible(rect) {
let icon_rect = Align2::RIGHT_CENTER.align_size_within_rect(icon_size, rect); let icon_rect = Align2::RIGHT_CENTER.align_size_within_rect(icon_size, rect);
let visuals = if is_popup_open { let visuals = if is_popup_open {
&ui.visuals().widgets.open &ui.visuals().widgets.open
@ -183,6 +184,7 @@ fn combo_box_dyn<'c, R>(
let text_rect = Align2::LEFT_CENTER.align_size_within_rect(galley.size(), rect); let text_rect = Align2::LEFT_CENTER.align_size_within_rect(galley.size(), rect);
galley.paint_with_visuals(ui.painter(), text_rect.min, visuals); galley.paint_with_visuals(ui.painter(), text_rect.min, visuals);
}
}); });
if button_response.clicked() { if button_response.clicked() {
@ -223,6 +225,8 @@ fn button_frame(
outer_rect.set_height(outer_rect.height().at_least(interact_size.y)); outer_rect.set_height(outer_rect.height().at_least(interact_size.y));
let response = ui.interact(outer_rect, id, sense); let response = ui.interact(outer_rect, id, sense);
if ui.is_rect_visible(outer_rect) {
let visuals = if is_popup_open { let visuals = if is_popup_open {
&ui.visuals().widgets.open &ui.visuals().widgets.open
} else { } else {
@ -238,6 +242,7 @@ fn button_frame(
stroke: visuals.bg_stroke, stroke: visuals.bg_stroke,
}, },
); );
}
ui.advance_cursor_after_rect(outer_rect); ui.advance_cursor_after_rect(outer_rect);

View file

@ -209,8 +209,11 @@ impl Prepared {
.. ..
} = self; } = self;
if ui.is_rect_visible(outer_rect) {
let shape = frame.paint(outer_rect); let shape = frame.paint(outer_rect);
ui.painter().set(where_to_put_background, shape); ui.painter().set(where_to_put_background, shape);
}
ui.allocate_rect(outer_rect, Sense::hover()) ui.allocate_rect(outer_rect, Sense::hover())
} }
} }

View file

@ -671,6 +671,7 @@ impl Prepared {
state.vel[d] = 0.0; state.vel[d] = 0.0;
} }
if ui.is_rect_visible(outer_scroll_rect) {
// Avoid frame-delay by calculating a new handle rect: // Avoid frame-delay by calculating a new handle rect:
let mut handle_rect = if d == 0 { let mut handle_rect = if d == 0 {
Rect::from_min_max( Rect::from_min_max(
@ -716,6 +717,7 @@ impl Prepared {
visuals.bg_fill, visuals.bg_fill,
)); ));
} }
}
ui.advance_cursor_after_rect(outer_rect); ui.advance_cursor_after_rect(outer_rect);

View file

@ -426,7 +426,7 @@ impl SubMenuButton {
crate::WidgetInfo::labeled(crate::WidgetType::Button, &text_galley.text()) 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 visuals = Self::visuals(ui, &response, menu_state, sub_id);
let text_pos = Align2::LEFT_CENTER let text_pos = Align2::LEFT_CENTER
.align_size_within_rect(text_galley.size(), rect.shrink2(button_padding)) .align_size_within_rect(text_galley.size(), rect.shrink2(button_padding))

View file

@ -122,13 +122,13 @@ impl Ui {
// ------------------------------------------------- // -------------------------------------------------
/// A unique identity of this `Ui`. /// A unique identity of this `Ui`.
#[inline(always)] #[inline]
pub fn id(&self) -> Id { pub fn id(&self) -> Id {
self.id self.id
} }
/// Style options for this `Ui` and its children. /// Style options for this `Ui` and its children.
#[inline(always)] #[inline]
pub fn style(&self) -> &std::sync::Arc<Style> { pub fn style(&self) -> &std::sync::Arc<Style> {
&self.style &self.style
} }
@ -161,7 +161,7 @@ impl Ui {
/// The current spacing options for this `Ui`. /// The current spacing options for this `Ui`.
/// Short for `ui.style().spacing`. /// Short for `ui.style().spacing`.
#[inline(always)] #[inline]
pub fn spacing(&self) -> &crate::style::Spacing { pub fn spacing(&self) -> &crate::style::Spacing {
&self.style.spacing &self.style.spacing
} }
@ -180,7 +180,7 @@ impl Ui {
/// The current visuals settings of this `Ui`. /// The current visuals settings of this `Ui`.
/// Short for `ui.style().visuals`. /// Short for `ui.style().visuals`.
#[inline(always)] #[inline]
pub fn visuals(&self) -> &crate::Visuals { pub fn visuals(&self) -> &crate::Visuals {
&self.style.visuals &self.style.visuals
} }
@ -200,20 +200,20 @@ impl Ui {
} }
/// Get a reference to the parent [`CtxRef`]. /// Get a reference to the parent [`CtxRef`].
#[inline(always)] #[inline]
pub fn ctx(&self) -> &CtxRef { pub fn ctx(&self) -> &CtxRef {
self.painter.ctx() self.painter.ctx()
} }
/// Use this to paint stuff within this `Ui`. /// Use this to paint stuff within this `Ui`.
#[inline(always)] #[inline]
pub fn painter(&self) -> &Painter { pub fn painter(&self) -> &Painter {
&self.painter &self.painter
} }
/// If `false`, the `Ui` does not allow any interaction and /// If `false`, the `Ui` does not allow any interaction and
/// the widgets in it will draw with a gray look. /// the widgets in it will draw with a gray look.
#[inline(always)] #[inline]
pub fn is_enabled(&self) -> bool { pub fn is_enabled(&self) -> bool {
self.enabled self.enabled
} }
@ -246,7 +246,7 @@ impl Ui {
} }
/// If `false`, any widgets added to the `Ui` will be invisible and non-interactive. /// If `false`, any widgets added to the `Ui` will be invisible and non-interactive.
#[inline(always)] #[inline]
pub fn visible(&self) -> bool { pub fn visible(&self) -> bool {
self.painter.visible() self.painter.visible()
} }
@ -277,7 +277,7 @@ impl Ui {
} }
} }
#[inline(always)] #[inline]
pub fn layout(&self) -> &Layout { pub fn layout(&self) -> &Layout {
self.placer.layout() self.placer.layout()
} }
@ -305,37 +305,42 @@ impl Ui {
} }
/// Use this to paint stuff within this `Ui`. /// Use this to paint stuff within this `Ui`.
#[inline]
pub fn layer_id(&self) -> LayerId { pub fn layer_id(&self) -> LayerId {
self.painter().layer_id() self.painter().layer_id()
} }
/// The `Input` of the `Context` associated with the `Ui`. /// The `Input` of the `Context` associated with the `Ui`.
/// Equivalent to `.ctx().input()`. /// Equivalent to `.ctx().input()`.
#[inline(always)] #[inline]
pub fn input(&self) -> &InputState { pub fn input(&self) -> &InputState {
self.ctx().input() self.ctx().input()
} }
/// The `Memory` of the `Context` associated with the `Ui`. /// The `Memory` of the `Context` associated with the `Ui`.
/// Equivalent to `.ctx().memory()`. /// Equivalent to `.ctx().memory()`.
#[inline]
pub fn memory(&self) -> MutexGuard<'_, Memory> { pub fn memory(&self) -> MutexGuard<'_, Memory> {
self.ctx().memory() self.ctx().memory()
} }
/// The `Output` of the `Context` associated with the `Ui`. /// The `Output` of the `Context` associated with the `Ui`.
/// Equivalent to `.ctx().output()`. /// Equivalent to `.ctx().output()`.
#[inline]
pub fn output(&self) -> MutexGuard<'_, Output> { pub fn output(&self) -> MutexGuard<'_, Output> {
self.ctx().output() self.ctx().output()
} }
/// The `Fonts` of the `Context` associated with the `Ui`. /// The `Fonts` of the `Context` associated with the `Ui`.
/// Equivalent to `.ctx().fonts()`. /// Equivalent to `.ctx().fonts()`.
#[inline]
pub fn fonts(&self) -> &Fonts { pub fn fonts(&self) -> &Fonts {
self.ctx().fonts() self.ctx().fonts()
} }
/// Screen-space rectangle for clipping what we paint in this ui. /// Screen-space rectangle for clipping what we paint in this ui.
/// This is used, for instance, to avoid painting outside a window that is smaller than its contents. /// This is used, for instance, to avoid painting outside a window that is smaller than its contents.
#[inline]
pub fn clip_rect(&self) -> Rect { pub fn clip_rect(&self) -> Rect {
self.painter.clip_rect() self.painter.clip_rect()
} }
@ -345,6 +350,11 @@ impl Ui {
pub fn set_clip_rect(&mut self, clip_rect: Rect) { pub fn set_clip_rect(&mut self, clip_rect: Rect) {
self.painter.set_clip_rect(clip_rect); self.painter.set_clip_rect(clip_rect);
} }
/// Can be used for culling: if `false`, then no part of `rect` will be visible on screen.
pub fn is_rect_visible(&self, rect: Rect) -> bool {
self.visible() && rect.intersects(self.clip_rect())
}
} }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
@ -741,7 +751,7 @@ impl Ui {
/// If the contents overflow, more space will be allocated. /// If the contents overflow, more space will be allocated.
/// When finished, the amount of space actually used (`min_rect`) will be allocated. /// When finished, the amount of space actually used (`min_rect`) will be allocated.
/// So you can request a lot of space and then use less. /// So you can request a lot of space and then use less.
#[inline(always)] #[inline]
pub fn allocate_ui<R>( pub fn allocate_ui<R>(
&mut self, &mut self,
desired_size: Vec2, desired_size: Vec2,
@ -754,7 +764,7 @@ impl Ui {
/// If the contents overflow, more space will be allocated. /// If the contents overflow, more space will be allocated.
/// When finished, the amount of space actually used (`min_rect`) will be allocated. /// When finished, the amount of space actually used (`min_rect`) will be allocated.
/// So you can request a lot of space and then use less. /// So you can request a lot of space and then use less.
#[inline(always)] #[inline]
pub fn allocate_ui_with_layout<R>( pub fn allocate_ui_with_layout<R>(
&mut self, &mut self,
desired_size: Vec2, desired_size: Vec2,
@ -883,7 +893,7 @@ impl Ui {
/// let response = ui.add(egui::Slider::new(&mut my_value, 0..=100)); /// let response = ui.add(egui::Slider::new(&mut my_value, 0..=100));
/// response.on_hover_text("Drag me!"); /// response.on_hover_text("Drag me!");
/// ``` /// ```
#[inline(always)] #[inline]
pub fn add(&mut self, widget: impl Widget) -> Response { pub fn add(&mut self, widget: impl Widget) -> Response {
widget.ui(self) widget.ui(self)
} }
@ -980,7 +990,7 @@ impl Ui {
/// This will be in addition to the [`Spacing::item_spacing`}. /// This will be in addition to the [`Spacing::item_spacing`}.
/// ///
/// [`Self::min_rect`] will expand to contain the space. /// [`Self::min_rect`] will expand to contain the space.
#[inline(always)] #[inline]
pub fn add_space(&mut self, amount: f32) { pub fn add_space(&mut self, amount: f32) {
self.placer.advance_cursor(amount); self.placer.advance_cursor(amount);
} }
@ -990,7 +1000,7 @@ impl Ui {
/// Shortcut for `add(Label::new(text))` /// Shortcut for `add(Label::new(text))`
/// ///
/// See also [`Label`]. /// See also [`Label`].
#[inline(always)] #[inline]
pub fn label(&mut self, text: impl Into<WidgetText>) -> Response { pub fn label(&mut self, text: impl Into<WidgetText>) -> Response {
Label::new(text).ui(self) Label::new(text).ui(self)
} }
@ -1197,7 +1207,7 @@ impl Ui {
} }
/// Shortcut for `add(Separator::default())` (see [`Separator`]). /// Shortcut for `add(Separator::default())` (see [`Separator`]).
#[inline(always)] #[inline]
pub fn separator(&mut self) -> Response { pub fn separator(&mut self) -> Response {
Separator::default().ui(self) Separator::default().ui(self)
} }
@ -1243,7 +1253,7 @@ impl Ui {
/// Show an image here with the given size. /// Show an image here with the given size.
/// ///
/// See also [`Image`]. /// See also [`Image`].
#[inline(always)] #[inline]
pub fn image(&mut self, texture_id: TextureId, size: impl Into<Vec2>) -> Response { pub fn image(&mut self, texture_id: TextureId, size: impl Into<Vec2>) -> Response {
Image::new(texture_id, size).ui(self) Image::new(texture_id, size).ui(self)
} }
@ -1403,7 +1413,7 @@ impl Ui {
/// ///
/// The `id_source` here be anything at all. /// The `id_source` here be anything at all.
// TODO: remove `id_source` argument? // TODO: remove `id_source` argument?
#[inline(always)] #[inline]
pub fn indent<R>( pub fn indent<R>(
&mut self, &mut self,
id_source: impl Hash, id_source: impl Hash,
@ -1555,7 +1565,7 @@ impl Ui {
/// ``` /// ```
/// ///
/// See also [`Self::with_layout`] for more options. /// See also [`Self::with_layout`] for more options.
#[inline(always)] #[inline]
pub fn vertical<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> { pub fn vertical<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
self.with_layout_dyn(Layout::top_down(Align::Min), Box::new(add_contents)) self.with_layout_dyn(Layout::top_down(Align::Min), Box::new(add_contents))
} }
@ -1750,7 +1760,7 @@ impl Ui {
self.menu_state = menu_state; self.menu_state = menu_state;
} }
#[inline(always)] #[inline]
/// Create a menu button. Creates a button for a sub-menu when the `Ui` is inside a menu. /// Create a menu button. Creates a button for a sub-menu when the `Ui` is inside a menu.
/// ///
/// ``` /// ```

View file

@ -144,7 +144,7 @@ impl Widget for Button {
let (rect, response) = ui.allocate_at_least(desired_size, sense); let (rect, response) = ui.allocate_at_least(desired_size, sense);
response.widget_info(|| WidgetInfo::labeled(WidgetType::Button, text.text())); response.widget_info(|| WidgetInfo::labeled(WidgetType::Button, text.text()));
if ui.clip_rect().intersects(rect) { if ui.is_rect_visible(rect) {
let visuals = ui.style().interact(&response); let visuals = ui.style().interact(&response);
let text_pos = ui let text_pos = ui
.layout() .layout()
@ -234,6 +234,7 @@ impl<'a> Widget for Checkbox<'a> {
} }
response.widget_info(|| WidgetInfo::selected(WidgetType::Checkbox, *checked, text.text())); response.widget_info(|| WidgetInfo::selected(WidgetType::Checkbox, *checked, text.text()));
if ui.is_rect_visible(rect) {
// let visuals = ui.style().interact_selectable(&response, *checked); // too colorful // let visuals = ui.style().interact_selectable(&response, *checked); // too colorful
let visuals = ui.style().interact(&response); let visuals = ui.style().interact(&response);
let text_pos = pos2( let text_pos = pos2(
@ -261,6 +262,8 @@ impl<'a> Widget for Checkbox<'a> {
} }
text.paint_with_visuals(ui.painter(), text_pos, visuals); text.paint_with_visuals(ui.painter(), text_pos, visuals);
}
response response
} }
} }
@ -331,6 +334,7 @@ impl Widget for RadioButton {
response response
.widget_info(|| WidgetInfo::selected(WidgetType::RadioButton, checked, text.text())); .widget_info(|| WidgetInfo::selected(WidgetType::RadioButton, checked, text.text()));
if ui.is_rect_visible(rect) {
let text_pos = pos2( let text_pos = pos2(
rect.min.x + button_padding.x + icon_width + icon_spacing, rect.min.x + button_padding.x + icon_width + icon_spacing,
rect.center().y - 0.5 * text.size().y, rect.center().y - 0.5 * text.size().y,
@ -361,6 +365,8 @@ impl Widget for RadioButton {
} }
text.paint_with_visuals(ui.painter(), text_pos, visuals); text.paint_with_visuals(ui.painter(), text_pos, visuals);
}
response response
} }
} }
@ -438,7 +444,7 @@ impl Widget for ImageButton {
let (rect, response) = ui.allocate_exact_size(padded_size, sense); let (rect, response) = ui.allocate_exact_size(padded_size, sense);
response.widget_info(|| WidgetInfo::new(WidgetType::ImageButton)); response.widget_info(|| WidgetInfo::new(WidgetType::ImageButton));
if ui.clip_rect().intersects(rect) { if ui.is_rect_visible(rect) {
let (expansion, corner_radius, fill, stroke) = if selected { let (expansion, corner_radius, fill, stroke) = if selected {
let selection = ui.visuals().selection; let selection = ui.visuals().selection;
(-padding, 0.0, selection.bg_fill, selection.stroke) (-padding, 0.0, selection.bg_fill, selection.stroke)

View file

@ -50,6 +50,7 @@ pub fn show_color(ui: &mut Ui, color: impl Into<Hsva>, desired_size: Vec2) -> Re
fn show_hsva(ui: &mut Ui, color: Hsva, desired_size: Vec2) -> Response { fn show_hsva(ui: &mut Ui, color: Hsva, desired_size: Vec2) -> Response {
let (rect, response) = ui.allocate_at_least(desired_size, Sense::hover()); let (rect, response) = ui.allocate_at_least(desired_size, Sense::hover());
if ui.is_rect_visible(rect) {
background_checkers(ui.painter(), rect); background_checkers(ui.painter(), rect);
if true { if true {
let left = Rect::from_min_max(rect.left_top(), rect.center_bottom()); let left = Rect::from_min_max(rect.left_top(), rect.center_bottom());
@ -64,6 +65,7 @@ fn show_hsva(ui: &mut Ui, color: Hsva, desired_size: Vec2) -> Response {
stroke: Stroke::new(3.0, color.to_opaque()), stroke: Stroke::new(3.0, color.to_opaque()),
}); });
} }
}
response response
} }
@ -71,6 +73,8 @@ fn color_button(ui: &mut Ui, color: Color32, open: bool) -> Response {
let size = ui.spacing().interact_size; let size = ui.spacing().interact_size;
let (rect, response) = ui.allocate_exact_size(size, Sense::click()); let (rect, response) = ui.allocate_exact_size(size, Sense::click());
response.widget_info(|| WidgetInfo::new(WidgetType::ColorButton)); response.widget_info(|| WidgetInfo::new(WidgetType::ColorButton));
if ui.is_rect_visible(rect) {
let visuals = if open { let visuals = if open {
&ui.visuals().widgets.open &ui.visuals().widgets.open
} else { } else {
@ -88,6 +92,7 @@ fn color_button(ui: &mut Ui, color: Color32, open: bool) -> Response {
let corner_radius = visuals.corner_radius.at_most(2.0); let corner_radius = visuals.corner_radius.at_most(2.0);
ui.painter() ui.painter()
.rect_stroke(rect, corner_radius, (2.0, visuals.bg_fill)); // fill is intentional, because default style has no border .rect_stroke(rect, corner_radius, (2.0, visuals.bg_fill)); // fill is intentional, because default style has no border
}
response response
} }
@ -102,6 +107,7 @@ fn color_slider_1d(ui: &mut Ui, value: &mut f32, color_at: impl Fn(f32) -> Color
*value = remap_clamp(mpos.x, rect.left()..=rect.right(), 0.0..=1.0); *value = remap_clamp(mpos.x, rect.left()..=rect.right(), 0.0..=1.0);
} }
if ui.is_rect_visible(rect) {
let visuals = ui.style().interact(&response); let visuals = ui.style().interact(&response);
background_checkers(ui.painter(), rect); // for alpha: background_checkers(ui.painter(), rect); // for alpha:
@ -140,6 +146,7 @@ fn color_slider_1d(ui: &mut Ui, value: &mut f32, color_at: impl Fn(f32) -> Color
Stroke::new(visuals.fg_stroke.width, contrast_color(picked_color)), Stroke::new(visuals.fg_stroke.width, contrast_color(picked_color)),
)); ));
} }
}
response response
} }
@ -158,6 +165,7 @@ fn color_slider_2d(
*y_value = remap_clamp(mpos.y, rect.bottom()..=rect.top(), 0.0..=1.0); *y_value = remap_clamp(mpos.y, rect.bottom()..=rect.top(), 0.0..=1.0);
} }
if ui.is_rect_visible(rect) {
let visuals = ui.style().interact(&response); let visuals = ui.style().interact(&response);
let mut mesh = Mesh::default(); let mut mesh = Mesh::default();
@ -193,6 +201,7 @@ fn color_slider_2d(
fill: picked_color, fill: picked_color,
stroke: Stroke::new(visuals.fg_stroke.width, contrast_color(picked_color)), stroke: Stroke::new(visuals.fg_stroke.width, contrast_color(picked_color)),
}); });
}
response response
} }

View file

@ -78,6 +78,7 @@ impl Widget for Hyperlink {
}); });
} }
if ui.is_rect_visible(response.rect) {
let color = ui.visuals().hyperlink_color; let color = ui.visuals().hyperlink_color;
let visuals = ui.style().interact(&response); let visuals = ui.style().interact(&response);
@ -94,6 +95,7 @@ impl Widget for Hyperlink {
underline, underline,
angle: 0.0, angle: 0.0,
}); });
}
response.on_hover_text(url) response.on_hover_text(url)
} }

View file

@ -68,6 +68,7 @@ impl Image {
} }
pub fn paint_at(&self, ui: &mut Ui, rect: Rect) { pub fn paint_at(&self, ui: &mut Ui, rect: Rect) {
if ui.is_rect_visible(rect) {
use epaint::*; use epaint::*;
let Self { let Self {
texture_id, texture_id,
@ -91,6 +92,7 @@ impl Image {
ui.painter().add(Shape::mesh(mesh)); ui.painter().add(Shape::mesh(mesh));
} }
} }
}
} }
impl Widget for Image { impl Widget for Image {

View file

@ -237,6 +237,8 @@ impl Widget for Label {
fn ui(self, ui: &mut Ui) -> Response { fn ui(self, ui: &mut Ui) -> Response {
let (pos, text_galley, response) = self.layout_in_ui(ui); let (pos, text_galley, response) = self.layout_in_ui(ui);
response.widget_info(|| WidgetInfo::labeled(WidgetType::Label, text_galley.text())); response.widget_info(|| WidgetInfo::labeled(WidgetType::Label, text_galley.text()));
if ui.is_rect_visible(response.rect) {
let response_color = ui.style().interact(&response).text_color(); let response_color = ui.style().interact(&response).text_color();
let underline = if response.has_focus() { let underline = if response.has_focus() {
@ -258,6 +260,7 @@ impl Widget for Label {
underline, underline,
angle: 0.0, angle: 0.0,
}); });
}
response response
} }

View file

@ -71,6 +71,8 @@ impl Widget for ProgressBar {
let height = ui.spacing().interact_size.y; let height = ui.spacing().interact_size.y;
let (outer_rect, response) = let (outer_rect, response) =
ui.allocate_exact_size(vec2(desired_width, height), Sense::hover()); ui.allocate_exact_size(vec2(desired_width, height), Sense::hover());
if ui.is_rect_visible(response.rect) {
let visuals = ui.style().visuals.clone(); let visuals = ui.style().visuals.clone();
let corner_radius = outer_rect.height() / 2.0; let corner_radius = outer_rect.height() / 2.0;
ui.painter().rect( ui.painter().rect(
@ -124,7 +126,9 @@ impl Widget for ProgressBar {
if let Some(text_kind) = text { if let Some(text_kind) = text {
let text = match text_kind { let text = match text_kind {
ProgressBarText::Custom(text) => text, ProgressBarText::Custom(text) => text,
ProgressBarText::Percentage => format!("{}%", (progress * 100.0) as usize).into(), ProgressBarText::Percentage => {
format!("{}%", (progress * 100.0) as usize).into()
}
}; };
let galley = text.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Button); let galley = text.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Button);
let text_pos = outer_rect.left_center() - Vec2::new(0.0, galley.size().y / 2.0) let text_pos = outer_rect.left_center() - Vec2::new(0.0, galley.size().y / 2.0)
@ -138,6 +142,7 @@ impl Widget for ProgressBar {
text_color, text_color,
); );
} }
}
response response
} }

View file

@ -58,6 +58,7 @@ impl Widget for SelectableLabel {
WidgetInfo::selected(WidgetType::SelectableLabel, selected, text.text()) WidgetInfo::selected(WidgetType::SelectableLabel, selected, text.text())
}); });
if ui.is_rect_visible(response.rect) {
let text_pos = ui let text_pos = ui
.layout() .layout()
.align_size_within_rect(text.size(), rect.shrink2(button_padding)) .align_size_within_rect(text.size(), rect.shrink2(button_padding))
@ -74,6 +75,8 @@ impl Widget for SelectableLabel {
} }
text.paint_with_visuals(ui.painter(), text_pos, &visuals); text.paint_with_visuals(ui.painter(), text_pos, &visuals);
}
response response
} }
} }

View file

@ -68,6 +68,8 @@ impl Widget for Separator {
}; };
let (rect, response) = ui.allocate_at_least(size, Sense::hover()); let (rect, response) = ui.allocate_at_least(size, Sense::hover());
if ui.is_rect_visible(response.rect) {
let points = if is_horizontal_line { let points = if is_horizontal_line {
[ [
pos2(rect.left(), rect.center().y), pos2(rect.left(), rect.center().y),
@ -81,6 +83,8 @@ impl Widget for Separator {
}; };
let stroke = ui.visuals().widgets.noninteractive.bg_stroke; let stroke = ui.visuals().widgets.noninteractive.bg_stroke;
ui.painter().line_segment(points, stroke); ui.painter().line_segment(points, stroke);
}
response response
} }
} }

View file

@ -320,7 +320,7 @@ impl<'a> Slider<'a> {
} }
// Paint it: // Paint it:
{ if ui.is_rect_visible(response.rect) {
let value = self.get_value(); let value = self.get_value();
let rail_radius = ui let rail_radius = ui

View file

@ -507,6 +507,7 @@ impl<'t> TextEdit<'t> {
text_draw_pos -= vec2(offset_x, 0.0); text_draw_pos -= vec2(offset_x, 0.0);
} }
if ui.is_rect_visible(rect) {
painter.galley(text_draw_pos, galley.clone()); painter.galley(text_draw_pos, galley.clone());
if text.as_ref().is_empty() && !hint_text.is_empty() { if text.as_ref().is_empty() && !hint_text.is_empty() {
@ -535,7 +536,7 @@ impl<'t> TextEdit<'t> {
if interactive && text.is_mutable() { if interactive && text.is_mutable() {
// egui_web uses `text_cursor_pos` when showing IME, // egui_web uses `text_cursor_pos` when showing IME,
// so only set it when text is editable! // so only set it when text is editable and visible!
ui.ctx().output().text_cursor_pos = Some( ui.ctx().output().text_cursor_pos = Some(
galley galley
.pos_from_cursor(&cursor_range.primary) .pos_from_cursor(&cursor_range.primary)
@ -545,6 +546,7 @@ impl<'t> TextEdit<'t> {
} }
} }
} }
}
state.clone().store(ui.ctx(), id); state.clone().store(ui.ctx(), id);

View file

@ -41,7 +41,9 @@ pub fn toggle_ui(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
response.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::Checkbox, *on, "")); response.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::Checkbox, *on, ""));
// 4. Paint! // 4. Paint!
// First let's ask for a simple animation from egui. // Make sure we need to paint:
if ui.is_rect_visible(rect) {
// Let's ask for a simple animation from egui.
// egui keeps track of changes in the boolean associated with the id and // egui keeps track of changes in the boolean associated with the id and
// returns an animated value in the 0-1 range for how much "on" we are. // returns an animated value in the 0-1 range for how much "on" we are.
let how_on = ui.ctx().animate_bool(response.id, *on); let how_on = ui.ctx().animate_bool(response.id, *on);
@ -59,6 +61,7 @@ pub fn toggle_ui(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
let center = egui::pos2(circle_x, rect.center().y); let center = egui::pos2(circle_x, rect.center().y);
ui.painter() ui.painter()
.circle(center, 0.75 * radius, visuals.bg_fill, visuals.fg_stroke); .circle(center, 0.75 * radius, visuals.bg_fill, visuals.fg_stroke);
}
// All done! Return the interaction response so the user can check what happened // All done! Return the interaction response so the user can check what happened
// (hovered, clicked, ...) and maybe show a tooltip: // (hovered, clicked, ...) and maybe show a tooltip:
@ -76,6 +79,7 @@ fn toggle_ui_compact(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
} }
response.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::Checkbox, *on, "")); response.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::Checkbox, *on, ""));
if ui.is_rect_visible(rect) {
let how_on = ui.ctx().animate_bool(response.id, *on); let how_on = ui.ctx().animate_bool(response.id, *on);
let visuals = ui.style().interact_selectable(&response, *on); let visuals = ui.style().interact_selectable(&response, *on);
let rect = rect.expand(visuals.expansion); let rect = rect.expand(visuals.expansion);
@ -86,6 +90,7 @@ fn toggle_ui_compact(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
let center = egui::pos2(circle_x, rect.center().y); let center = egui::pos2(circle_x, rect.center().y);
ui.painter() ui.painter()
.circle(center, 0.75 * radius, visuals.bg_fill, visuals.fg_stroke); .circle(center, 0.75 * radius, visuals.bg_fill, visuals.fg_stroke);
}
response response
} }