Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion crates/egui/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ impl Ui {
let clip_rect = max_rect;
let layout = layout.unwrap_or_default();
let disabled = disabled || invisible;
let sizing_pass = sizing_pass.unwrap_or(false);
let style = style.unwrap_or_else(|| ctx.global_style());
let sense = sense.unwrap_or_else(Sense::hover);
let classes = classes.with_class(class::ROOT);
Expand Down Expand Up @@ -236,7 +237,7 @@ impl Ui {
if invisible {
painter.set_invisible();
}
let sizing_pass = self.sizing_pass || sizing_pass;
let sizing_pass = sizing_pass.unwrap_or(self.sizing_pass);
let style = style.unwrap_or_else(|| Arc::clone(&self.style));
let sense = sense.unwrap_or_else(Sense::hover);

Expand Down
25 changes: 22 additions & 3 deletions crates/egui/src/ui_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ pub struct UiBuilder {
pub layout: Option<Layout>,
pub disabled: bool,
pub invisible: bool,
pub sizing_pass: bool,

/// `None` inherits from the parent; a root defaults to `false`.
pub sizing_pass: Option<bool>,
pub style: Option<Arc<Style>>,
pub sense: Option<Sense>,
pub accessibility_parent: Option<Id>,
Expand Down Expand Up @@ -150,10 +152,27 @@ impl UiBuilder {
/// where we size up the contents of the Ui, without actually showing it.
///
/// If the `sizing_pass` flag is set on the parent,
/// the child will inherit it automatically.
/// the child will inherit it automatically unless explicitly overridden
/// with [`Self::no_sizing_pass`].
#[inline]
pub fn sizing_pass(mut self) -> Self {
self.sizing_pass = true;
self.sizing_pass = Some(true);
self
}

/// Disable sizing mode for the new [`Ui`], even if the parent is sizing.
///
/// Use this for contents whose layout should not participate in sizing,
/// such as widgets clipped inside a fixed viewport. Descendants inherit
/// normal mode unless they explicitly enable [`Self::sizing_pass`].
/// The new [`Ui`] retains its parent chain, visibility, enabled state, and
/// input behavior. This does not change whether the current pass is discarded.
///
/// To use a centered or justified layout, also specify [`Self::layout`]:
/// the parent's inherited layout may already have been adjusted for sizing.
#[inline]
pub fn no_sizing_pass(mut self) -> Self {
self.sizing_pass = Some(false);
self
}

Expand Down
67 changes: 67 additions & 0 deletions crates/egui/tests/sizing_pass.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//! Sizing mode defaults, inheritance, and layout behavior.

use egui::{Align, Context, Id, Layout, Rect, Ui, UiBuilder, Vec2};

#[test]
fn root_sizing_mode_defaults_to_false() {
let ctx = Context::default();
let mut output = ctx.run_ui(Default::default(), |_| {
for (name, builder, expected) in [
("default", UiBuilder::new(), false),
("opt_out", UiBuilder::new().no_sizing_pass(), false),
("sizing", UiBuilder::new().sizing_pass(), true),
] {
let root = Ui::new(ctx.clone(), Id::unique(name), builder);
assert_eq!(root.is_sizing_pass(), expected, "{name}");
}
});
output.textures_delta.clear();
}

#[test]
fn children_inherit_and_override_sizing_mode() {
let ctx = Context::default();
let mut output = ctx.run_ui(Default::default(), |ui| {
assert!(!ui.new_child(UiBuilder::new()).is_sizing_pass());

let mut sizing = ui.new_child(UiBuilder::new().sizing_pass());
assert!(sizing.is_sizing_pass());
assert!(sizing.new_child(UiBuilder::new()).is_sizing_pass());

let mut normal = sizing.new_child(UiBuilder::new().no_sizing_pass());
assert!(!normal.is_sizing_pass());
assert!(!normal.new_child(UiBuilder::new()).is_sizing_pass());

// A descendant can enable sizing again after an ancestor opted out.
assert!(
normal
.new_child(UiBuilder::new().sizing_pass())
.is_sizing_pass()
);
});
output.textures_delta.clear();
}

#[test]
fn sizing_opt_out_preserves_requested_layout_and_widget_widths() {
let ctx = Context::default();
let mut output = ctx.run_ui(Default::default(), |ui| {
let rect = Rect::from_min_size(ui.cursor().min, Vec2::new(248.0, 90.0));
let mut parent = ui.new_child(UiBuilder::new().sizing_pass().max_rect(rect));
let builder = UiBuilder::new()
.max_rect(rect)
.layout(Layout::top_down(Align::Center).with_cross_justify(true));
let mut inherited = parent.new_child(builder.clone());
assert!(!inherited.layout().cross_justify);
assert_eq!(inherited.layout().cross_align, Align::Min);
assert_eq!(inherited.separator().rect.width(), 0.0);
let mut child = parent.new_child(builder.no_sizing_pass());
assert!(child.layout().cross_justify);
assert_eq!(child.layout().cross_align, Align::Center);
assert_eq!(child.separator().rect.width(), 248.0);
// Opting out does not reconstruct a layout already adjusted in an ancestor.
let descendant = inherited.new_child(UiBuilder::new().no_sizing_pass());
assert_eq!(descendant.layout(), inherited.layout());
});
output.textures_delta.clear();
}
2 changes: 1 addition & 1 deletion crates/egui_kittest/src/app_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ impl<State> AppKind<'_, State> {
fn run_ui(&mut self, ui: &mut egui::Ui, state: &mut State, sizing_pass: bool) -> UiRunOutput {
let mut builder = egui::UiBuilder::new();
if sizing_pass {
builder.sizing_pass = true;
builder = builder.sizing_pass();
}
let egui::InnerResponse {
inner: ui_id,
Expand Down
Loading