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:
parent
06f88e12b0
commit
5a196f6604
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
|
|
|
|||
|
|
@ -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`].
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
Loading…
Reference in New Issue