Skip to content

Commit

Permalink
Introduce UiStack (#4588)
Browse files Browse the repository at this point in the history
* Closes #4534

This PR:
- Introduces `Ui::stack()`, which returns the `UiStack` structure
providing information on the current `Ui` hierarchy.
- **BREAKING**: `Ui::new()` now takes a `UiStackInfo` argument, which is
used to populate some of this `Ui`'s `UiStack`'s fields.
- **BREAKING**: `Ui::child_ui()` and `Ui::child_ui_with_id_source()` now
take an `Option<UiStackInfo>` argument, which is used to populate some
of the children `Ui`'s `UiStack`'s fields.
- New `Area::kind()` builder function, to set the `UiStackKind` value of
the `Area`'s `Ui`.
- Adds a (minimalistic) demo to egui demo (in the "Misc Demos" window).
- Adds a more thorough `test_ui_stack` test/playground demo.

TODO:
- [x] benchmarks
- [x] add example to demo

Future work:
- Add `UiStackKind` and related support for more container (e.g.
`CollapsingHeader`, etc.)
- Add a tag/property system that would allow adding arbitrary data to a
stack node. This data could then be queried by nested `Ui`s. Probably
needed for #3284.
- Add support to track columnar layouts.

---------

Co-authored-by: Emil Ernerfeldt <[email protected]>
  • Loading branch information
abey79 and emilk authored Jun 4, 2024
1 parent c0a9800 commit a287921
Show file tree
Hide file tree
Showing 23 changed files with 794 additions and 30 deletions.
9 changes: 9 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3636,6 +3636,15 @@ dependencies = [
"env_logger",
]

[[package]]
name = "test_ui_stack"
version = "0.1.0"
dependencies = [
"eframe",
"egui_extras",
"env_logger",
]

[[package]]
name = "test_viewports"
version = "0.1.0"
Expand Down
16 changes: 16 additions & 0 deletions crates/egui/src/containers/area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ impl AreaState {
#[derive(Clone, Copy, Debug)]
pub struct Area {
pub(crate) id: Id,
kind: UiKind,
sense: Option<Sense>,
movable: bool,
interactable: bool,
Expand All @@ -105,6 +106,7 @@ impl Area {
pub fn new(id: Id) -> Self {
Self {
id,
kind: UiKind::GenericArea,
sense: None,
movable: true,
interactable: true,
Expand All @@ -130,6 +132,15 @@ impl Area {
self
}

/// Change the [`UiKind`] of the arena.
///
/// Default to [`UiKind::GenericArea`].
#[inline]
pub fn kind(mut self, kind: UiKind) -> Self {
self.kind = kind;
self
}

pub fn layer(&self) -> LayerId {
LayerId::new(self.order, self.id)
}
Expand Down Expand Up @@ -303,6 +314,7 @@ impl Area {
}

pub(crate) struct Prepared {
kind: UiKind,
layer_id: LayerId,
state: AreaState,
move_response: Response,
Expand Down Expand Up @@ -336,6 +348,7 @@ impl Area {
pub(crate) fn begin(self, ctx: &Context) -> Prepared {
let Self {
id,
kind,
sense,
movable,
order,
Expand Down Expand Up @@ -458,6 +471,7 @@ impl Area {
move_response.interact_rect = state.rect();

Prepared {
kind,
layer_id,
state,
move_response,
Expand Down Expand Up @@ -498,6 +512,7 @@ impl Prepared {
self.layer_id.id,
max_rect,
clip_rect,
UiStackInfo::new(self.kind),
);

if self.fade_in {
Expand All @@ -520,6 +535,7 @@ impl Prepared {
#[allow(clippy::needless_pass_by_value)] // intentional to swallow up `content_ui`.
pub(crate) fn end(self, ctx: &Context, content_ui: Ui) -> Response {
let Self {
kind: _,
layer_id,
mut state,
move_response,
Expand Down
2 changes: 1 addition & 1 deletion crates/egui/src/containers/combo_box.rs
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,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());
let mut content_ui = ui.child_ui(inner_rect, *ui.layout(), None);
add_contents(&mut content_ui);

let mut outer_rect = content_ui.min_rect().expand2(margin);
Expand Down
9 changes: 8 additions & 1 deletion crates/egui/src/containers/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,14 @@ 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());
let content_ui = ui.child_ui(
inner_rect,
*ui.layout(),
Some(UiStackInfo {
frame: self,
kind: Some(UiKind::Frame),
}),
);

// 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

Expand Down
62 changes: 56 additions & 6 deletions crates/egui/src/containers/panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,15 @@ impl SidePanel {
}
}

let mut panel_ui = ui.child_ui_with_id_source(panel_rect, Layout::top_down(Align::Min), id);
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,
})),
);
panel_ui.expand_to_include_rect(panel_rect);
let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style()));
let inner_response = frame.show(&mut panel_ui, |ui| {
Expand Down Expand Up @@ -348,7 +356,17 @@ impl SidePanel {
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);
let mut panel_ui = Ui::new(
ctx.clone(),
layer_id,
self.id,
available_rect,
clip_rect,
UiStackInfo {
kind: None, // set by show_inside_dyn
frame: Frame::default(),
},
);

let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents);
let rect = inner_response.response.rect;
Expand Down Expand Up @@ -723,7 +741,15 @@ impl TopBottomPanel {
}
}

let mut panel_ui = ui.child_ui_with_id_source(panel_rect, Layout::top_down(Align::Min), id);
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,
})),
);
panel_ui.expand_to_include_rect(panel_rect);
let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style()));
let inner_response = frame.show(&mut panel_ui, |ui| {
Expand Down Expand Up @@ -816,7 +842,17 @@ impl TopBottomPanel {
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);
let mut panel_ui = Ui::new(
ctx.clone(),
layer_id,
self.id,
available_rect,
clip_rect,
UiStackInfo {
kind: None, // set by show_inside_dyn
frame: Frame::default(),
},
);

let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents);
let rect = inner_response.response.rect;
Expand Down Expand Up @@ -1045,7 +1081,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));
let mut panel_ui = ui.child_ui(
panel_rect,
Layout::top_down(Align::Min),
Some(UiStackInfo::new(UiKind::CentralPanel)),
);

let frame = frame.unwrap_or_else(|| Frame::central_panel(ui.style()));
frame.show(&mut panel_ui, |ui| {
Expand Down Expand Up @@ -1074,7 +1114,17 @@ impl CentralPanel {
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);
let mut panel_ui = Ui::new(
ctx.clone(),
layer_id,
id,
available_rect,
clip_rect,
UiStackInfo {
kind: None, // set by show_inside_dyn
frame: Frame::default(),
},
);

let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents);

Expand Down
2 changes: 2 additions & 0 deletions crates/egui/src/containers/popup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ fn show_tooltip_at_avoid_dyn<'c, R>(
);

let InnerResponse { inner, response } = Area::new(tooltip_area_id)
.kind(UiKind::Popup)
.order(Order::Tooltip)
.pivot(pivot)
.fixed_pos(anchor)
Expand Down Expand Up @@ -311,6 +312,7 @@ pub fn popup_above_or_below_widget<R>(
let inner_width = widget_response.rect.width() - frame_margin.sum().x;

let inner = Area::new(popup_id)
.kind(UiKind::Popup)
.order(Order::Foreground)
.fixed_pos(pos)
.default_width(inner_width)
Expand Down
6 changes: 5 additions & 1 deletion crates/egui/src/containers/resize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,11 @@ 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());
let mut content_ui = ui.child_ui(
inner_rect,
*ui.layout(),
Some(UiStackInfo::new(UiKind::Resize)),
);
content_ui.set_clip_rect(content_clip_rect);

Prepared {
Expand Down
6 changes: 5 additions & 1 deletion crates/egui/src/containers/scroll_area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,11 @@ 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());
let mut content_ui = ui.child_ui(
content_max_rect,
*ui.layout(),
Some(UiStackInfo::new(UiKind::ScrollArea)),
);

{
// Clip the content, but only when we really need to:
Expand Down
2 changes: 1 addition & 1 deletion crates/egui/src/containers/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ impl<'open> Window<'open> {
/// If you need a changing title, you must call `window.id(…)` with a fixed id.
pub fn new(title: impl Into<WidgetText>) -> Self {
let title = title.into().fallback_text_style(TextStyle::Heading);
let area = Area::new(Id::new(title.text()));
let area = Area::new(Id::new(title.text())).kind(UiKind::Window);
Self {
title,
open: None,
Expand Down
2 changes: 2 additions & 0 deletions crates/egui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ mod sense;
pub mod style;
pub mod text_selection;
mod ui;
mod ui_stack;
pub mod util;
pub mod viewport;
mod widget_rect;
Expand Down Expand Up @@ -466,6 +467,7 @@ pub use {
style::{FontSelection, Style, TextStyle, Visuals},
text::{Galley, TextFormat},
ui::Ui,
ui_stack::*,
viewport::*,
widget_rect::{WidgetRect, WidgetRects},
widget_text::{RichText, WidgetText},
Expand Down
1 change: 1 addition & 0 deletions crates/egui/src/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ fn menu_popup<'c, R>(
};

let area = Area::new(menu_id.with("__menu"))
.kind(UiKind::Menu)
.order(Order::Foreground)
.fixed_pos(pos)
.interactable(true)
Expand Down
Loading

0 comments on commit a287921

Please sign in to comment.