Create a `UiBuilder` for building `Ui`s (#4969)

* Part of https://github.com/emilk/egui/issues/4634

The goals is to create fewer, more powerful entry points.


### Added
* `egui::UiBuilder`
* `Ui::allocate_new_ui`
* `Ui::new_child`

### Breaking changes
* `Ui::new` now takes a `UiBuilder`
* Deprecated
	* `ui.add_visible_ui`
	* `ui.allocate_ui_at_rect`
	* `ui.child_ui`
	* `ui.child_ui_with_id_source`
	* `ui.push_stack_info`
This commit is contained in:
Emil Ernerfeldt 2024-08-26 08:51:18 +02:00 committed by GitHub
parent 06f88e12b0
commit 5a196f6604
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 392 additions and 172 deletions

View File

@ -532,16 +532,19 @@ impl Prepared {
pub(crate) fn content_ui(&self, ctx: &Context) -> Ui {
let max_rect = self.state.rect();
let clip_rect = self.constrain_rect; // Don't paint outside our bounds
let mut ui_builder = UiBuilder::new()
.ui_stack_info(UiStackInfo::new(self.kind))
.max_rect(max_rect);
let mut ui = Ui::new(
ctx.clone(),
self.layer_id,
self.layer_id.id,
max_rect,
clip_rect,
UiStackInfo::new(self.kind),
);
if !self.enabled {
ui_builder = ui_builder.disabled();
}
if self.sizing_pass {
ui_builder = ui_builder.sizing_pass();
}
let mut ui = Ui::new(ctx.clone(), self.layer_id, self.layer_id.id, ui_builder);
ui.set_clip_rect(self.constrain_rect); // Don't paint outside our bounds
if self.fade_in {
if let Some(last_became_visible_at) = self.state.last_became_visible_at {
@ -556,12 +559,6 @@ impl Prepared {
}
}
if !self.enabled {
ui.disable();
}
if self.sizing_pass {
ui.set_sizing_pass();
}
ui
}

View File

@ -425,7 +425,7 @@ fn button_frame(
outer_rect.set_height(outer_rect.height().at_least(interact_size.y));
let inner_rect = outer_rect.shrink2(margin);
let mut content_ui = ui.child_ui(inner_rect, *ui.layout(), None);
let mut content_ui = ui.new_child(UiBuilder::new().max_rect(inner_rect));
add_contents(&mut content_ui);
let mut outer_rect = content_ui.min_rect().expand2(margin);

View File

@ -250,10 +250,10 @@ impl Frame {
inner_rect.max.x = inner_rect.max.x.max(inner_rect.min.x);
inner_rect.max.y = inner_rect.max.y.max(inner_rect.min.y);
let content_ui = ui.child_ui(
inner_rect,
*ui.layout(),
Some(UiStackInfo::new(UiKind::Frame).with_frame(self)),
let content_ui = ui.new_child(
UiBuilder::new()
.ui_stack_info(UiStackInfo::new(UiKind::Frame).with_frame(self))
.max_rect(inner_rect),
);
// content_ui.set_clip_rect(outer_rect_bounds.shrink(self.stroke.width * 0.5)); // Can't do this since we don't know final size yet

View File

@ -261,14 +261,15 @@ impl SidePanel {
}
}
let mut panel_ui = ui.child_ui_with_id_source(
panel_rect,
Layout::top_down(Align::Min),
id,
Some(UiStackInfo::new(match side {
Side::Left => UiKind::LeftPanel,
Side::Right => UiKind::RightPanel,
})),
let mut panel_ui = ui.new_child(
UiBuilder::new()
.id_source(id)
.ui_stack_info(UiStackInfo::new(match side {
Side::Left => UiKind::LeftPanel,
Side::Right => UiKind::RightPanel,
}))
.max_rect(panel_rect)
.layout(Layout::top_down(Align::Min)),
);
panel_ui.expand_to_include_rect(panel_rect);
panel_ui.set_clip_rect(panel_rect); // If we overflow, don't do so visibly (#4475)
@ -367,15 +368,13 @@ impl SidePanel {
let layer_id = LayerId::background();
let side = self.side;
let available_rect = ctx.available_rect();
let clip_rect = ctx.screen_rect();
let mut panel_ui = Ui::new(
ctx.clone(),
layer_id,
self.id,
available_rect,
clip_rect,
UiStackInfo::default(),
UiBuilder::new().max_rect(available_rect),
);
panel_ui.set_clip_rect(ctx.screen_rect());
let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents);
let rect = inner_response.response.rect;
@ -750,14 +749,15 @@ impl TopBottomPanel {
}
}
let mut panel_ui = ui.child_ui_with_id_source(
panel_rect,
Layout::top_down(Align::Min),
id,
Some(UiStackInfo::new(match side {
TopBottomSide::Top => UiKind::TopPanel,
TopBottomSide::Bottom => UiKind::BottomPanel,
})),
let mut panel_ui = ui.new_child(
UiBuilder::new()
.id_source(id)
.ui_stack_info(UiStackInfo::new(match side {
TopBottomSide::Top => UiKind::TopPanel,
TopBottomSide::Bottom => UiKind::BottomPanel,
}))
.max_rect(panel_rect)
.layout(Layout::top_down(Align::Min)),
);
panel_ui.expand_to_include_rect(panel_rect);
panel_ui.set_clip_rect(panel_rect); // If we overflow, don't do so visibly (#4475)
@ -857,15 +857,13 @@ impl TopBottomPanel {
let available_rect = ctx.available_rect();
let side = self.side;
let clip_rect = ctx.screen_rect();
let mut panel_ui = Ui::new(
ctx.clone(),
layer_id,
self.id,
available_rect,
clip_rect,
UiStackInfo::default(), // set by show_inside_dyn
UiBuilder::new().max_rect(available_rect),
);
panel_ui.set_clip_rect(ctx.screen_rect());
let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents);
let rect = inner_response.response.rect;
@ -1091,10 +1089,11 @@ impl CentralPanel {
let Self { frame } = self;
let panel_rect = ui.available_rect_before_wrap();
let mut panel_ui = ui.child_ui(
panel_rect,
Layout::top_down(Align::Min),
Some(UiStackInfo::new(UiKind::CentralPanel)),
let mut panel_ui = ui.new_child(
UiBuilder::new()
.ui_stack_info(UiStackInfo::new(UiKind::CentralPanel))
.max_rect(panel_rect)
.layout(Layout::top_down(Align::Min)),
);
panel_ui.set_clip_rect(panel_rect); // If we overflow, don't do so visibly (#4475)
@ -1124,15 +1123,13 @@ impl CentralPanel {
let layer_id = LayerId::background();
let id = Id::new((ctx.viewport_id(), "central_panel"));
let clip_rect = ctx.screen_rect();
let mut panel_ui = Ui::new(
ctx.clone(),
layer_id,
id,
available_rect,
clip_rect,
UiStackInfo::default(), // set by show_inside_dyn
UiBuilder::new().max_rect(available_rect),
);
panel_ui.set_clip_rect(ctx.screen_rect());
let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents);

View File

@ -270,10 +270,10 @@ impl Resize {
content_clip_rect = content_clip_rect.intersect(ui.clip_rect()); // Respect parent region
let mut content_ui = ui.child_ui(
inner_rect,
*ui.layout(),
Some(UiStackInfo::new(UiKind::Resize)),
let mut content_ui = ui.new_child(
UiBuilder::new()
.ui_stack_info(UiStackInfo::new(UiKind::Resize))
.max_rect(inner_rect),
);
content_ui.set_clip_rect(content_clip_rect);

View File

@ -560,10 +560,10 @@ impl ScrollArea {
}
let content_max_rect = Rect::from_min_size(inner_rect.min - state.offset, content_max_size);
let mut content_ui = ui.child_ui(
content_max_rect,
*ui.layout(),
Some(UiStackInfo::new(UiKind::ScrollArea)),
let mut content_ui = ui.new_child(
UiBuilder::new()
.ui_stack_info(UiStackInfo::new(UiKind::ScrollArea))
.max_rect(content_max_rect),
);
{
@ -732,7 +732,7 @@ impl ScrollArea {
let rect = Rect::from_x_y_ranges(ui.max_rect().x_range(), y_min..=y_max);
ui.allocate_ui_at_rect(rect, |viewport_ui| {
ui.allocate_new_ui(UiBuilder::new().max_rect(rect), |viewport_ui| {
viewport_ui.skip_ahead_auto_ids(min_row); // Make sure we get consistent IDs.
add_contents(viewport_ui, min_row..max_row)
})

View File

@ -425,11 +425,14 @@ impl Grid {
// then we should pick a default layout that matches that alignment,
// which we do here:
let max_rect = ui.cursor().intersect(ui.max_rect());
ui.allocate_ui_at_rect(max_rect, |ui| {
if prev_state.is_none() {
// Hide the ui this frame, and make things as narrow as possible.
ui.set_sizing_pass();
}
let mut ui_builder = UiBuilder::new().max_rect(max_rect);
if prev_state.is_none() {
// Hide the ui this frame, and make things as narrow as possible.
ui_builder = ui_builder.sizing_pass();
}
ui.allocate_new_ui(ui_builder, |ui| {
ui.horizontal(|ui| {
let is_color = color_picker.is_some();
let mut grid = GridLayout {

View File

@ -400,6 +400,7 @@ mod sense;
pub mod style;
pub mod text_selection;
mod ui;
mod ui_builder;
mod ui_stack;
pub mod util;
pub mod viewport;
@ -442,7 +443,7 @@ pub mod text {
};
}
pub use {
pub use self::{
containers::*,
context::{Context, RepaintCause, RequestRepaintInfo},
data::{
@ -467,6 +468,7 @@ pub use {
style::{FontSelection, Style, TextStyle, Visuals},
text::{Galley, TextFormat},
ui::Ui,
ui_builder::UiBuilder,
ui_stack::*,
viewport::*,
widget_rect::{WidgetRect, WidgetRects},

View File

@ -75,20 +75,34 @@ impl Ui {
// ------------------------------------------------------------------------
// Creation:
/// Create a new [`Ui`].
/// Create a new top-level [`Ui`].
///
/// Normally you would not use this directly, but instead use
/// [`SidePanel`], [`TopBottomPanel`], [`CentralPanel`], [`Window`] or [`Area`].
pub fn new(
ctx: Context,
layer_id: LayerId,
id: Id,
max_rect: Rect,
clip_rect: Rect,
ui_stack_info: UiStackInfo,
) -> Self {
let style = ctx.style();
let layout = Layout::default();
pub fn new(ctx: Context, layer_id: LayerId, id: Id, ui_builder: UiBuilder) -> Self {
let UiBuilder {
id_source,
ui_stack_info,
max_rect,
layout,
disabled,
invisible,
sizing_pass,
style,
} = ui_builder;
debug_assert!(
id_source.is_none(),
"Top-level Ui:s should not have an id_source"
);
let max_rect = max_rect.unwrap_or_else(|| ctx.screen_rect());
let clip_rect = max_rect;
let layout = layout.unwrap_or_default();
let invisible = invisible || sizing_pass;
let disabled = disabled || invisible || sizing_pass;
let style = style.unwrap_or_else(|| ctx.style());
let placer = Placer::new(max_rect, layout);
let ui_stack = UiStack {
id,
@ -98,7 +112,7 @@ impl Ui {
min_rect: placer.min_rect(),
max_rect: placer.max_rect(),
};
let ui = Ui {
let mut ui = Ui {
id,
next_auto_id_source: id.with("auto").value(),
painter: Painter::new(ctx, layer_id, clip_rect),
@ -121,6 +135,16 @@ impl Ui {
enabled: ui.enabled,
});
if disabled {
ui.disable();
}
if invisible {
ui.set_invisible();
}
if sizing_pass {
ui.set_sizing_pass();
}
ui
}
@ -130,25 +154,67 @@ impl Ui {
/// [`Self::scope`] if needed.
///
/// When in doubt, use `None` for the `UiStackInfo` argument.
#[deprecated = "Use ui.new_child() instead"]
pub fn child_ui(
&mut self,
max_rect: Rect,
layout: Layout,
ui_stack_info: Option<UiStackInfo>,
) -> Self {
self.child_ui_with_id_source(max_rect, layout, "child", ui_stack_info)
self.new_child(
UiBuilder::new()
.max_rect(max_rect)
.layout(layout)
.ui_stack_info(ui_stack_info.unwrap_or_default()),
)
}
/// Create a new [`Ui`] at a specific region with a specific id.
///
/// When in doubt, use `None` for the `UiStackInfo` argument.
#[deprecated = "Use ui.new_child() instead"]
pub fn child_ui_with_id_source(
&mut self,
max_rect: Rect,
mut layout: Layout,
layout: Layout,
id_source: impl Hash,
ui_stack_info: Option<UiStackInfo>,
) -> Self {
self.new_child(
UiBuilder::new()
.id_source(id_source)
.max_rect(max_rect)
.layout(layout)
.ui_stack_info(ui_stack_info.unwrap_or_default()),
)
}
/// Create a child `Ui` with the properties of the given builder.
pub fn new_child(&mut self, ui_builder: UiBuilder) -> Self {
let UiBuilder {
id_source,
ui_stack_info,
max_rect,
layout,
disabled,
invisible,
sizing_pass,
style,
} = ui_builder;
let mut painter = self.painter.clone();
let id_source = id_source.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 invisible = invisible || sizing_pass;
let enabled = self.enabled && !disabled && !invisible && !sizing_pass;
if invisible {
painter.set_invisible();
}
let sizing_pass = self.sizing_pass || sizing_pass;
let style = style.unwrap_or_else(|| self.style.clone());
if self.sizing_pass {
// During the sizing pass we want widgets to use up as little space as possible,
// so that we measure the only the space we _need_.
@ -167,7 +233,7 @@ impl Ui {
let ui_stack = UiStack {
id: new_id,
layout_direction: layout.main_dir,
info: ui_stack_info.unwrap_or_default(),
info: ui_stack_info,
parent: Some(self.stack.clone()),
min_rect: placer.min_rect(),
max_rect: placer.max_rect(),
@ -175,11 +241,11 @@ impl Ui {
let child_ui = Ui {
id: new_id,
next_auto_id_source,
painter: self.painter.clone(),
style: self.style.clone(),
painter,
style,
placer,
enabled: self.enabled,
sizing_pass: self.sizing_pass,
enabled,
sizing_pass,
menu_state: self.menu_state.clone(),
stack: Arc::new(ui_stack),
};
@ -1130,16 +1196,10 @@ impl Ui {
let item_spacing = self.spacing().item_spacing;
let frame_rect = self.placer.next_space(desired_size, item_spacing);
let child_rect = self.placer.justify_and_align(frame_rect, desired_size);
let mut child_ui = self.child_ui(child_rect, layout, None);
let ret = add_contents(&mut child_ui);
let final_child_rect = child_ui.min_rect();
self.placer
.advance_after_rects(final_child_rect, final_child_rect, item_spacing);
let response = self.interact(final_child_rect, child_ui.id, Sense::hover());
InnerResponse::new(ret, response)
self.allocate_new_ui(
UiBuilder::new().max_rect(child_rect).layout(layout),
add_contents,
)
}
/// Allocated the given rectangle and then adds content to that rectangle.
@ -1147,24 +1207,40 @@ impl Ui {
/// If the contents overflow, more space 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.
#[deprecated = "Use `allocate_new_ui` instead"]
pub fn allocate_ui_at_rect<R>(
&mut self,
max_rect: Rect,
add_contents: impl FnOnce(&mut Self) -> R,
) -> InnerResponse<R> {
debug_assert!(max_rect.is_finite());
let mut child_ui = self.child_ui(max_rect, *self.layout(), None);
let ret = add_contents(&mut child_ui);
let final_child_rect = child_ui.min_rect();
self.allocate_new_ui(UiBuilder::new().max_rect(max_rect), add_contents)
}
self.placer.advance_after_rects(
final_child_rect,
final_child_rect,
self.spacing().item_spacing,
);
/// Allocated space (`UiBuilder::max_rect`) and then add content to it.
///
/// If the contents overflow, more space will be allocated.
/// When finished, the amount of space actually used (`min_rect`) will be allocated in the parent.
/// So you can request a lot of space and then use less.
pub fn allocate_new_ui<R>(
&mut self,
ui_builder: UiBuilder,
add_contents: impl FnOnce(&mut Self) -> R,
) -> InnerResponse<R> {
self.allocate_new_ui_dyn(ui_builder, Box::new(add_contents))
}
let response = self.interact(final_child_rect, child_ui.id, Sense::hover());
InnerResponse::new(ret, response)
fn allocate_new_ui_dyn<'c, R>(
&mut self,
ui_builder: UiBuilder,
add_contents: Box<dyn FnOnce(&mut Self) -> R + 'c>,
) -> InnerResponse<R> {
let mut child_ui = self.new_child(ui_builder);
let inner = add_contents(&mut child_ui);
let rect = child_ui.min_rect();
let item_spacing = self.spacing().item_spacing;
self.placer.advance_after_rects(rect, rect, item_spacing);
let response = self.interact(rect, child_ui.id, Sense::hover());
InnerResponse::new(inner, response)
}
/// Convenience function to get a region to paint on.
@ -1194,7 +1270,10 @@ impl Ui {
let painter = self.painter().with_clip_rect(clip_rect);
(response, painter)
}
}
/// # Scrolling
impl Ui {
/// Adjust the scroll position of any parent [`ScrollArea`] so that the given [`Rect`] becomes visible.
///
/// If `align` is [`Align::TOP`] it means "put the top of the rect at the top of the scroll area", etc.
@ -1367,9 +1446,12 @@ impl Ui {
///
/// See also [`Self::add`] and [`Self::add_sized`].
pub fn put(&mut self, max_rect: Rect, widget: impl Widget) -> Response {
self.allocate_ui_at_rect(max_rect, |ui| {
ui.centered_and_justified(|ui| ui.add(widget)).inner
})
self.allocate_new_ui(
UiBuilder::new()
.max_rect(max_rect)
.layout(Layout::centered_and_justified(Direction::TopDown)),
|ui| ui.add(widget),
)
.inner
}
@ -1481,17 +1563,17 @@ impl Ui {
/// });
/// # });
/// ```
#[deprecated = "Use 'ui.scope_builder' instead"]
pub fn add_visible_ui<R>(
&mut self,
visible: bool,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
self.scope(|ui| {
if !visible {
ui.set_invisible();
}
add_contents(ui)
})
let mut ui_builder = UiBuilder::new();
if !visible {
ui_builder = ui_builder.invisible();
}
self.scope_builder(ui_builder, add_contents)
}
/// Add extra space before the next widget.
@ -1986,21 +2068,24 @@ impl Ui {
id_source: impl Hash,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
self.scope_dyn(Box::new(add_contents), Id::new(id_source), None)
self.scope_dyn(
UiBuilder::new().id_source(id_source),
Box::new(add_contents),
)
}
/// Push another level onto the [`UiStack`].
///
/// You can use this, for instance, to tag a group of widgets.
#[deprecated = "Use 'ui.scope_builder' instead"]
pub fn push_stack_info<R>(
&mut self,
ui_stack_info: UiStackInfo,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
self.scope_dyn(
UiBuilder::new().ui_stack_info(ui_stack_info),
Box::new(add_contents),
Id::new("child"),
Some(ui_stack_info),
)
}
@ -2017,19 +2102,26 @@ impl Ui {
/// # });
/// ```
pub fn scope<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
self.scope_dyn(Box::new(add_contents), Id::new("child"), None)
self.scope_dyn(UiBuilder::new(), Box::new(add_contents))
}
fn scope_dyn<'c, R>(
/// Create a child, add content to it, and then allocate only what was used in the parent `Ui`.
pub fn scope_builder<R>(
&mut self,
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
id_source: Id,
ui_stack_info: Option<UiStackInfo>,
ui_builder: UiBuilder,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
self.scope_dyn(ui_builder, Box::new(add_contents))
}
/// Create a child, add content to it, and then allocate only what was used in the parent `Ui`.
pub fn scope_dyn<'c, R>(
&mut self,
ui_builder: UiBuilder,
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
) -> InnerResponse<R> {
let child_rect = self.available_rect_before_wrap();
let next_auto_id_source = self.next_auto_id_source;
let mut child_ui =
self.child_ui_with_id_source(child_rect, *self.layout(), id_source, ui_stack_info);
let mut child_ui = self.new_child(ui_builder);
self.next_auto_id_source = next_auto_id_source; // HACK: we want `scope` to only increment this once, so that `ui.scope` is equivalent to `ui.allocate_space`.
let ret = add_contents(&mut child_ui);
let response = self.allocate_rect(child_ui.min_rect(), Sense::hover());
@ -2089,7 +2181,7 @@ impl Ui {
child_rect.min.x += indent;
let mut child_ui =
self.child_ui_with_id_source(child_rect, *self.layout(), id_source, None);
self.new_child(UiBuilder::new().id_source(id_source).max_rect(child_rect));
let ret = add_contents(&mut child_ui);
let left_vline = self.visuals().indent_has_left_vline;
@ -2240,7 +2332,10 @@ impl Ui {
/// See also [`Self::with_layout`] for more options.
#[inline]
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.allocate_new_ui(
UiBuilder::new().layout(Layout::top_down(Align::Min)),
add_contents,
)
}
/// Start a ui with vertical layout.
@ -2259,7 +2354,10 @@ impl Ui {
&mut self,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
self.with_layout_dyn(Layout::top_down(Align::Center), Box::new(add_contents))
self.allocate_new_ui(
UiBuilder::new().layout(Layout::top_down(Align::Center)),
add_contents,
)
}
/// Start a ui with vertical layout.
@ -2277,9 +2375,9 @@ impl Ui {
&mut self,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
self.with_layout_dyn(
Layout::top_down(Align::Center).with_cross_justify(true),
Box::new(add_contents),
self.allocate_new_ui(
UiBuilder::new().layout(Layout::top_down(Align::Center).with_cross_justify(true)),
add_contents,
)
}
@ -2303,21 +2401,7 @@ impl Ui {
layout: Layout,
add_contents: impl FnOnce(&mut Self) -> R,
) -> InnerResponse<R> {
self.with_layout_dyn(layout, Box::new(add_contents))
}
fn with_layout_dyn<'c, R>(
&mut self,
layout: Layout,
add_contents: Box<dyn FnOnce(&mut Self) -> R + 'c>,
) -> InnerResponse<R> {
let mut child_ui = self.child_ui(self.available_rect_before_wrap(), layout, None);
let inner = add_contents(&mut child_ui);
let rect = child_ui.min_rect();
let item_spacing = self.spacing().item_spacing;
self.placer.advance_after_rects(rect, rect, item_spacing);
InnerResponse::new(inner, self.interact(rect, child_ui.id, Sense::hover()))
self.allocate_new_ui(UiBuilder::new().layout(layout), add_contents)
}
/// This will make the next added widget centered and justified in the available space.
@ -2327,9 +2411,9 @@ impl Ui {
&mut self,
add_contents: impl FnOnce(&mut Self) -> R,
) -> InnerResponse<R> {
self.with_layout_dyn(
Layout::centered_and_justified(Direction::TopDown),
Box::new(add_contents),
self.allocate_new_ui(
UiBuilder::new().layout(Layout::centered_and_justified(Direction::TopDown)),
add_contents,
)
}
@ -2394,8 +2478,11 @@ impl Ui {
pos,
pos2(pos.x + column_width, self.max_rect().right_bottom().y),
);
let mut column_ui =
self.child_ui(child_rect, Layout::top_down_justified(Align::LEFT), None);
let mut column_ui = self.new_child(
UiBuilder::new()
.max_rect(child_rect)
.layout(Layout::top_down_justified(Align::LEFT)),
);
column_ui.set_width(column_width);
column_ui
})
@ -2448,8 +2535,11 @@ impl Ui {
pos,
pos2(pos.x + column_width, self.max_rect().right_bottom().y),
);
let mut column_ui =
self.child_ui(child_rect, Layout::top_down_justified(Align::LEFT), None);
let mut column_ui = self.new_child(
UiBuilder::new()
.max_rect(child_rect)
.layout(Layout::top_down_justified(Align::LEFT)),
);
column_ui.set_width(column_width);
column_ui
});
@ -2574,7 +2664,10 @@ impl Ui {
(InnerResponse { inner, response }, payload)
}
}
/// # Menus
impl Ui {
/// Close the menu we are in (including submenus), if any.
///
/// See also: [`Self::menu_button`] and [`Response::context_menu`].

View File

@ -0,0 +1,119 @@
use std::{hash::Hash, sync::Arc};
use crate::{Id, Layout, Rect, Style, UiStackInfo};
#[allow(unused_imports)] // Used for doclinks
use crate::Ui;
/// Build a [`Ui`] as the chlild of another [`Ui`].
///
/// By default, everything is inherited from the parent,
/// except for `max_rect` which by default is set to
/// the parent [`Ui::available_rect_before_wrap`].
#[must_use]
#[derive(Default)]
pub struct UiBuilder {
pub id_source: Option<Id>,
pub ui_stack_info: UiStackInfo,
pub max_rect: Option<Rect>,
pub layout: Option<Layout>,
pub disabled: bool,
pub invisible: bool,
pub sizing_pass: bool,
pub style: Option<Arc<Style>>,
}
impl UiBuilder {
#[inline]
pub fn new() -> Self {
Self::default()
}
/// Seed the child `Ui` with this `id_source`, which will be mixed
/// with the [`Ui::id`] of the parent.
///
/// You should give each [`Ui`] an `id_source` that is unique
/// within the parent, or give it none at all.
#[inline]
pub fn id_source(mut self, id_source: impl Hash) -> Self {
self.id_source = Some(Id::new(id_source));
self
}
/// Provide some information about the new `Ui` being built.
#[inline]
pub fn ui_stack_info(mut self, ui_stack_info: UiStackInfo) -> Self {
self.ui_stack_info = ui_stack_info;
self
}
/// Set the max rectangle, within which widgets will go.
///
/// New widgets will *try* to fit within this rectangle.
///
/// Text labels will wrap to fit within `max_rect`.
/// Separator lines will span the `max_rect`.
///
/// If a new widget doesn't fit within the `max_rect` then the
/// [`Ui`] will make room for it by expanding both `min_rect` and
///
/// If not set, this will be set to the parent
/// [`Ui::available_rect_before_wrap`].
#[inline]
pub fn max_rect(mut self, max_rect: Rect) -> Self {
self.max_rect = Some(max_rect);
self
}
/// Override the layout.
///
/// Will otherwise be inherited from the parent.
#[inline]
pub fn layout(mut self, layout: Layout) -> Self {
self.layout = Some(layout);
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
}
/// Make the contents invisible.
///
/// Will also disable the `Ui` (see [`Self::disabled`]).
///
/// If the parent `Ui` is invisible, the child will always be invisible.
#[inline]
pub fn invisible(mut self) -> Self {
self.invisible = true;
self.disabled = true;
self
}
/// Set to true in special cases where we do one frame
/// 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.
#[inline]
pub fn sizing_pass(mut self) -> Self {
self.sizing_pass = true;
self.invisible = true;
self.disabled = true;
self
}
/// Override the style.
///
/// Otherwise will inherit the style of the parent.
#[inline]
pub fn style(mut self, style: impl Into<Arc<Style>>) -> Self {
self.style = Some(style.into());
self
}
}

View File

@ -61,10 +61,15 @@ impl crate::Demo for WidgetGallery {
impl crate::View for WidgetGallery {
fn ui(&mut self, ui: &mut egui::Ui) {
ui.add_enabled_ui(self.enabled, |ui| {
if !self.visible {
ui.set_invisible();
}
let mut ui_builder = egui::UiBuilder::new();
if !self.enabled {
ui_builder = ui_builder.disabled();
}
if !self.visible {
ui_builder = ui_builder.invisible();
}
ui.scope_builder(ui_builder, |ui| {
ui.multiply_opacity(self.opacity);
egui::Grid::new("my_grid")

View File

@ -1,4 +1,4 @@
use egui::{Id, Pos2, Rect, Response, Sense, Ui};
use egui::{Id, Pos2, Rect, Response, Sense, Ui, UiBuilder};
#[derive(Clone, Copy)]
pub(crate) enum CellSize {
@ -197,11 +197,12 @@ impl<'l> StripLayout<'l> {
child_ui_id_source: egui::Id,
add_cell_contents: impl FnOnce(&mut Ui),
) -> Ui {
let mut child_ui = self.ui.child_ui_with_id_source(
max_rect,
self.cell_layout,
child_ui_id_source,
Some(egui::UiStackInfo::new(egui::UiKind::TableCell)),
let mut child_ui = self.ui.new_child(
UiBuilder::new()
.id_source(child_ui_id_source)
.ui_stack_info(egui::UiStackInfo::new(egui::UiKind::TableCell))
.max_rect(max_rect)
.layout(self.cell_layout),
);
if flags.clip {

View File

@ -71,7 +71,7 @@ fn custom_window_frame(ctx: &egui::Context, title: &str, add_contents: impl FnOn
rect
}
.shrink(4.0);
let mut content_ui = ui.child_ui(content_rect, *ui.layout(), None);
let mut content_ui = ui.new_child(UiBuilder::new().max_rect(content_rect));
add_contents(&mut content_ui);
});
}
@ -116,14 +116,17 @@ fn title_bar_ui(ui: &mut egui::Ui, title_bar_rect: eframe::epaint::Rect, title:
ui.ctx().send_viewport_cmd(ViewportCommand::StartDrag);
}
ui.allocate_ui_at_rect(title_bar_rect, |ui| {
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
ui.allocate_new_ui(
UiBuilder::new()
.max_rect(title_bar_rect)
.layout(egui::Layout::right_to_left(egui::Align::Center)),
|ui| {
ui.spacing_mut().item_spacing.x = 0.0;
ui.visuals_mut().button_frame = false;
ui.add_space(8.0);
close_maximize_minimize(ui);
});
});
},
);
}
/// Show some close/maximize/minimize buttons for the native window.

View File

@ -4,7 +4,7 @@
use std::sync::Arc;
use eframe::egui;
use egui::{mutex::RwLock, Id, InnerResponse, ViewportBuilder, ViewportId};
use egui::{mutex::RwLock, Id, InnerResponse, UiBuilder, ViewportBuilder, ViewportId};
// Drag-and-drop between windows is not yet implemented, but if you wanna work on it, enable this:
pub const DRAG_AND_DROP_TEST: bool = false;
@ -460,7 +460,7 @@ fn drop_target<R>(
let available_rect = ui.available_rect_before_wrap();
let inner_rect = available_rect.shrink2(margin);
let mut content_ui = ui.child_ui(inner_rect, *ui.layout(), None);
let mut content_ui = ui.new_child(UiBuilder::new().max_rect(inner_rect));
let ret = body(&mut content_ui);
let outer_rect =