diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index d3d2a722821..dd8076579fe 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -512,6 +512,8 @@ impl Area { Sense::hover() } }); + + let enabled = enabled && interactable; let move_response = ctx.create_widget( WidgetRect { @@ -595,7 +597,11 @@ impl Prepared { .max_rect(max_rect) .layout(self.layout) .closable(); - + + if !self.state.interactable { + ui_builder = ui_builder + .noninteractive(); + } if !self.enabled { ui_builder = ui_builder.disabled(); } diff --git a/crates/egui/src/hit_test.rs b/crates/egui/src/hit_test.rs index d1122350ef9..b55bd1f4726 100644 --- a/crates/egui/src/hit_test.rs +++ b/crates/egui/src/hit_test.rs @@ -2,7 +2,7 @@ use ahash::HashMap; use emath::TSTransform; -use crate::{LayerId, Pos2, Rect, Sense, WidgetRect, WidgetRects, ahash, emath, id::IdSet}; +use crate::{LayerId, Pos2, Rect, WidgetRect, WidgetRects, ahash, emath, id::IdSet}; /// Result of a hit-test against [`WidgetRects`]. /// @@ -65,6 +65,14 @@ pub fn hit_test( .filter(|layer| layer.order.allow_interaction()) .flat_map(|&layer_id| widgets.get_layer(layer_id)) .filter(|&w| { + + // Exclude non-interactive, disabled and invisible widgets. + // This simplifies the code in `hit_test_on_close` so it doesn't have to check + // the `enabled` flag everywhere: + if w.interact_rect.is_negative() || w.interact_rect.any_nan() || !w.enabled { + return false; + } + if w.interact_rect.is_negative() || w.interact_rect.any_nan() { return false; } @@ -125,16 +133,6 @@ pub fn hit_test( close.retain(|hit| included_layers.contains(&hit.layer_id)); - // If a widget is disabled, treat it as if it isn't sensing anything. - // This simplifies the code in `hit_test_on_close` so it doesn't have to check - // the `enabled` flag everywhere: - for w in &mut close { - if !w.enabled { - w.sense -= Sense::CLICK; - w.sense -= Sense::DRAG; - } - } - // Find widgets which are hidden behind another widget and discard them. // This is the case when a widget fully contains another widget and is on a different layer. // It prevents "hovering through" widgets when there is a clickable widget behind. diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 1371f1a7c67..c341ba43108 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -129,6 +129,7 @@ impl Ui { layer_id, max_rect, layout, + noninteractive, disabled, invisible, sizing_pass, @@ -146,6 +147,7 @@ impl Ui { let max_rect = max_rect.unwrap_or_else(|| ctx.screen_rect()); let clip_rect = max_rect; let layout = layout.unwrap_or_default(); + let enabled = !disabled && !invisible && !noninteractive; let disabled = disabled || invisible; let style = style.unwrap_or_else(|| ctx.style()); let sense = sense.unwrap_or(Sense::hover()); @@ -166,7 +168,7 @@ impl Ui { painter: Painter::new(ctx, layer_id, clip_rect), style, placer, - enabled: true, + enabled, sizing_pass, menu_state: None, stack: Arc::new(ui_stack), @@ -256,6 +258,7 @@ impl Ui { layer_id, max_rect, layout, + noninteractive, disabled, invisible, sizing_pass, @@ -268,7 +271,7 @@ impl Ui { let id_salt = id_salt.unwrap_or_else(|| Id::from("child")); let max_rect = max_rect.unwrap_or_else(|| self.available_rect_before_wrap()); let mut layout = layout.unwrap_or(*self.layout()); - let enabled = self.enabled && !disabled && !invisible; + let enabled = self.enabled && !disabled && !invisible && !noninteractive; if let Some(layer_id) = layer_id { painter.set_layer_id(layer_id); } diff --git a/crates/egui/src/ui_builder.rs b/crates/egui/src/ui_builder.rs index e83148da6e2..4171ba4433e 100644 --- a/crates/egui/src/ui_builder.rs +++ b/crates/egui/src/ui_builder.rs @@ -19,6 +19,7 @@ pub struct UiBuilder { pub layer_id: Option, pub max_rect: Option, pub layout: Option, + pub noninteractive: bool, pub disabled: bool, pub invisible: bool, pub sizing_pass: bool, @@ -111,13 +112,23 @@ impl UiBuilder { self.layout = Some(layout); self } - + + /// Make the new `Ui` non-interactive, i.e. passthrough. no visual changes. + /// + /// Note that if the parent `Ui` is non-interactive, the child will always be non-interactive. + #[inline] + pub fn noninteractive(mut self) -> Self { + self.noninteractive = true; + self + } + /// Make the new `Ui` disabled, i.e. grayed-out and non-interactive. /// /// Note that if the parent `Ui` is disabled, the child will always be disabled. #[inline] pub fn disabled(mut self) -> Self { self.disabled = true; + self.noninteractive = true; self } @@ -130,6 +141,7 @@ impl UiBuilder { pub fn invisible(mut self) -> Self { self.invisible = true; self.disabled = true; + self.noninteractive = true; self }