Introduce `UiStack` (#4588)
* 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 <emil.ernerfeldt@gmail.com>
This commit is contained in:
parent
c0a9800d05
commit
a28792194d
|
@ -3636,6 +3636,15 @@ dependencies = [
|
||||||
"env_logger",
|
"env_logger",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "test_ui_stack"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"eframe",
|
||||||
|
"egui_extras",
|
||||||
|
"env_logger",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "test_viewports"
|
name = "test_viewports"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
@ -81,6 +81,7 @@ impl AreaState {
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct Area {
|
pub struct Area {
|
||||||
pub(crate) id: Id,
|
pub(crate) id: Id,
|
||||||
|
kind: UiKind,
|
||||||
sense: Option<Sense>,
|
sense: Option<Sense>,
|
||||||
movable: bool,
|
movable: bool,
|
||||||
interactable: bool,
|
interactable: bool,
|
||||||
|
@ -105,6 +106,7 @@ impl Area {
|
||||||
pub fn new(id: Id) -> Self {
|
pub fn new(id: Id) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
|
kind: UiKind::GenericArea,
|
||||||
sense: None,
|
sense: None,
|
||||||
movable: true,
|
movable: true,
|
||||||
interactable: true,
|
interactable: true,
|
||||||
|
@ -130,6 +132,15 @@ impl Area {
|
||||||
self
|
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 {
|
pub fn layer(&self) -> LayerId {
|
||||||
LayerId::new(self.order, self.id)
|
LayerId::new(self.order, self.id)
|
||||||
}
|
}
|
||||||
|
@ -303,6 +314,7 @@ impl Area {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct Prepared {
|
pub(crate) struct Prepared {
|
||||||
|
kind: UiKind,
|
||||||
layer_id: LayerId,
|
layer_id: LayerId,
|
||||||
state: AreaState,
|
state: AreaState,
|
||||||
move_response: Response,
|
move_response: Response,
|
||||||
|
@ -336,6 +348,7 @@ impl Area {
|
||||||
pub(crate) fn begin(self, ctx: &Context) -> Prepared {
|
pub(crate) fn begin(self, ctx: &Context) -> Prepared {
|
||||||
let Self {
|
let Self {
|
||||||
id,
|
id,
|
||||||
|
kind,
|
||||||
sense,
|
sense,
|
||||||
movable,
|
movable,
|
||||||
order,
|
order,
|
||||||
|
@ -458,6 +471,7 @@ impl Area {
|
||||||
move_response.interact_rect = state.rect();
|
move_response.interact_rect = state.rect();
|
||||||
|
|
||||||
Prepared {
|
Prepared {
|
||||||
|
kind,
|
||||||
layer_id,
|
layer_id,
|
||||||
state,
|
state,
|
||||||
move_response,
|
move_response,
|
||||||
|
@ -498,6 +512,7 @@ impl Prepared {
|
||||||
self.layer_id.id,
|
self.layer_id.id,
|
||||||
max_rect,
|
max_rect,
|
||||||
clip_rect,
|
clip_rect,
|
||||||
|
UiStackInfo::new(self.kind),
|
||||||
);
|
);
|
||||||
|
|
||||||
if self.fade_in {
|
if self.fade_in {
|
||||||
|
@ -520,6 +535,7 @@ impl Prepared {
|
||||||
#[allow(clippy::needless_pass_by_value)] // intentional to swallow up `content_ui`.
|
#[allow(clippy::needless_pass_by_value)] // intentional to swallow up `content_ui`.
|
||||||
pub(crate) fn end(self, ctx: &Context, content_ui: Ui) -> Response {
|
pub(crate) fn end(self, ctx: &Context, content_ui: Ui) -> Response {
|
||||||
let Self {
|
let Self {
|
||||||
|
kind: _,
|
||||||
layer_id,
|
layer_id,
|
||||||
mut state,
|
mut state,
|
||||||
move_response,
|
move_response,
|
||||||
|
|
|
@ -418,7 +418,7 @@ fn button_frame(
|
||||||
outer_rect.set_height(outer_rect.height().at_least(interact_size.y));
|
outer_rect.set_height(outer_rect.height().at_least(interact_size.y));
|
||||||
|
|
||||||
let inner_rect = outer_rect.shrink2(margin);
|
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);
|
add_contents(&mut content_ui);
|
||||||
|
|
||||||
let mut outer_rect = content_ui.min_rect().expand2(margin);
|
let mut outer_rect = content_ui.min_rect().expand2(margin);
|
||||||
|
|
|
@ -250,7 +250,14 @@ impl Frame {
|
||||||
inner_rect.max.x = inner_rect.max.x.max(inner_rect.min.x);
|
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);
|
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
|
// 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
|
||||||
|
|
||||||
|
|
|
@ -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);
|
panel_ui.expand_to_include_rect(panel_rect);
|
||||||
let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style()));
|
let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style()));
|
||||||
let inner_response = frame.show(&mut panel_ui, |ui| {
|
let inner_response = frame.show(&mut panel_ui, |ui| {
|
||||||
|
@ -348,7 +356,17 @@ impl SidePanel {
|
||||||
let side = self.side;
|
let side = self.side;
|
||||||
let available_rect = ctx.available_rect();
|
let available_rect = ctx.available_rect();
|
||||||
let clip_rect = ctx.screen_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 inner_response = self.show_inside_dyn(&mut panel_ui, add_contents);
|
||||||
let rect = inner_response.response.rect;
|
let rect = inner_response.response.rect;
|
||||||
|
@ -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);
|
panel_ui.expand_to_include_rect(panel_rect);
|
||||||
let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style()));
|
let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style()));
|
||||||
let inner_response = frame.show(&mut panel_ui, |ui| {
|
let inner_response = frame.show(&mut panel_ui, |ui| {
|
||||||
|
@ -816,7 +842,17 @@ impl TopBottomPanel {
|
||||||
let side = self.side;
|
let side = self.side;
|
||||||
|
|
||||||
let clip_rect = ctx.screen_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 inner_response = self.show_inside_dyn(&mut panel_ui, add_contents);
|
||||||
let rect = inner_response.response.rect;
|
let rect = inner_response.response.rect;
|
||||||
|
@ -1045,7 +1081,11 @@ impl CentralPanel {
|
||||||
let Self { frame } = self;
|
let Self { frame } = self;
|
||||||
|
|
||||||
let panel_rect = ui.available_rect_before_wrap();
|
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()));
|
let frame = frame.unwrap_or_else(|| Frame::central_panel(ui.style()));
|
||||||
frame.show(&mut panel_ui, |ui| {
|
frame.show(&mut panel_ui, |ui| {
|
||||||
|
@ -1074,7 +1114,17 @@ impl CentralPanel {
|
||||||
let id = Id::new((ctx.viewport_id(), "central_panel"));
|
let id = Id::new((ctx.viewport_id(), "central_panel"));
|
||||||
|
|
||||||
let clip_rect = ctx.screen_rect();
|
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);
|
let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents);
|
||||||
|
|
||||||
|
|
|
@ -131,6 +131,7 @@ fn show_tooltip_at_avoid_dyn<'c, R>(
|
||||||
);
|
);
|
||||||
|
|
||||||
let InnerResponse { inner, response } = Area::new(tooltip_area_id)
|
let InnerResponse { inner, response } = Area::new(tooltip_area_id)
|
||||||
|
.kind(UiKind::Popup)
|
||||||
.order(Order::Tooltip)
|
.order(Order::Tooltip)
|
||||||
.pivot(pivot)
|
.pivot(pivot)
|
||||||
.fixed_pos(anchor)
|
.fixed_pos(anchor)
|
||||||
|
@ -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_width = widget_response.rect.width() - frame_margin.sum().x;
|
||||||
|
|
||||||
let inner = Area::new(popup_id)
|
let inner = Area::new(popup_id)
|
||||||
|
.kind(UiKind::Popup)
|
||||||
.order(Order::Foreground)
|
.order(Order::Foreground)
|
||||||
.fixed_pos(pos)
|
.fixed_pos(pos)
|
||||||
.default_width(inner_width)
|
.default_width(inner_width)
|
||||||
|
|
|
@ -270,7 +270,11 @@ impl Resize {
|
||||||
|
|
||||||
content_clip_rect = content_clip_rect.intersect(ui.clip_rect()); // Respect parent region
|
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);
|
content_ui.set_clip_rect(content_clip_rect);
|
||||||
|
|
||||||
Prepared {
|
Prepared {
|
||||||
|
|
|
@ -556,7 +556,11 @@ impl ScrollArea {
|
||||||
}
|
}
|
||||||
|
|
||||||
let content_max_rect = Rect::from_min_size(inner_rect.min - state.offset, content_max_size);
|
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:
|
// Clip the content, but only when we really need to:
|
||||||
|
|
|
@ -49,7 +49,7 @@ impl<'open> Window<'open> {
|
||||||
/// If you need a changing title, you must call `window.id(…)` with a fixed id.
|
/// If you need a changing title, you must call `window.id(…)` with a fixed id.
|
||||||
pub fn new(title: impl Into<WidgetText>) -> Self {
|
pub fn new(title: impl Into<WidgetText>) -> Self {
|
||||||
let title = title.into().fallback_text_style(TextStyle::Heading);
|
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 {
|
Self {
|
||||||
title,
|
title,
|
||||||
open: None,
|
open: None,
|
||||||
|
|
|
@ -400,6 +400,7 @@ mod sense;
|
||||||
pub mod style;
|
pub mod style;
|
||||||
pub mod text_selection;
|
pub mod text_selection;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
mod ui_stack;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
pub mod viewport;
|
pub mod viewport;
|
||||||
mod widget_rect;
|
mod widget_rect;
|
||||||
|
@ -466,6 +467,7 @@ pub use {
|
||||||
style::{FontSelection, Style, TextStyle, Visuals},
|
style::{FontSelection, Style, TextStyle, Visuals},
|
||||||
text::{Galley, TextFormat},
|
text::{Galley, TextFormat},
|
||||||
ui::Ui,
|
ui::Ui,
|
||||||
|
ui_stack::*,
|
||||||
viewport::*,
|
viewport::*,
|
||||||
widget_rect::{WidgetRect, WidgetRects},
|
widget_rect::{WidgetRect, WidgetRects},
|
||||||
widget_text::{RichText, WidgetText},
|
widget_text::{RichText, WidgetText},
|
||||||
|
|
|
@ -146,6 +146,7 @@ fn menu_popup<'c, R>(
|
||||||
};
|
};
|
||||||
|
|
||||||
let area = Area::new(menu_id.with("__menu"))
|
let area = Area::new(menu_id.with("__menu"))
|
||||||
|
.kind(UiKind::Menu)
|
||||||
.order(Order::Foreground)
|
.order(Order::Foreground)
|
||||||
.fixed_pos(pos)
|
.fixed_pos(pos)
|
||||||
.interactable(true)
|
.interactable(true)
|
||||||
|
|
|
@ -67,6 +67,9 @@ pub struct Ui {
|
||||||
|
|
||||||
/// Indicates whether this Ui belongs to a Menu.
|
/// Indicates whether this Ui belongs to a Menu.
|
||||||
menu_state: Option<Arc<RwLock<MenuState>>>,
|
menu_state: Option<Arc<RwLock<MenuState>>>,
|
||||||
|
|
||||||
|
/// The [`UiStack`] for this [`Ui`].
|
||||||
|
stack: Arc<UiStack>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ui {
|
impl Ui {
|
||||||
|
@ -77,17 +80,36 @@ impl Ui {
|
||||||
///
|
///
|
||||||
/// Normally you would not use this directly, but instead use
|
/// Normally you would not use this directly, but instead use
|
||||||
/// [`SidePanel`], [`TopBottomPanel`], [`CentralPanel`], [`Window`] or [`Area`].
|
/// [`SidePanel`], [`TopBottomPanel`], [`CentralPanel`], [`Window`] or [`Area`].
|
||||||
pub fn new(ctx: Context, layer_id: LayerId, id: Id, max_rect: Rect, clip_rect: Rect) -> Self {
|
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 style = ctx.style();
|
||||||
|
let layout = Layout::default();
|
||||||
|
let placer = Placer::new(max_rect, layout);
|
||||||
|
let ui_stack = UiStack {
|
||||||
|
id,
|
||||||
|
layout_direction: layout.main_dir,
|
||||||
|
kind: ui_stack_info.kind,
|
||||||
|
frame: ui_stack_info.frame,
|
||||||
|
parent: None,
|
||||||
|
min_rect: placer.min_rect(),
|
||||||
|
max_rect: placer.max_rect(),
|
||||||
|
};
|
||||||
let ui = Ui {
|
let ui = Ui {
|
||||||
id,
|
id,
|
||||||
next_auto_id_source: id.with("auto").value(),
|
next_auto_id_source: id.with("auto").value(),
|
||||||
painter: Painter::new(ctx, layer_id, clip_rect),
|
painter: Painter::new(ctx, layer_id, clip_rect),
|
||||||
style,
|
style,
|
||||||
placer: Placer::new(max_rect, Layout::default()),
|
placer,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
sizing_pass: false,
|
sizing_pass: false,
|
||||||
menu_state: None,
|
menu_state: None,
|
||||||
|
stack: Arc::new(ui_stack),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Register in the widget stack early, to ensure we are behind all widgets we contain:
|
// Register in the widget stack early, to ensure we are behind all widgets we contain:
|
||||||
|
@ -105,8 +127,16 @@ impl Ui {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new [`Ui`] at a specific region.
|
/// Create a new [`Ui`] at a specific region.
|
||||||
pub fn child_ui(&mut self, max_rect: Rect, layout: Layout) -> Self {
|
///
|
||||||
self.child_ui_with_id_source(max_rect, layout, "child")
|
/// Note: calling this function twice from the same [`Ui`] will create a conflict of id. Use
|
||||||
|
/// [`Self::scope`] if needed.
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new [`Ui`] at a specific region with a specific id.
|
/// Create a new [`Ui`] at a specific region with a specific id.
|
||||||
|
@ -115,6 +145,7 @@ impl Ui {
|
||||||
max_rect: Rect,
|
max_rect: Rect,
|
||||||
mut layout: Layout,
|
mut layout: Layout,
|
||||||
id_source: impl Hash,
|
id_source: impl Hash,
|
||||||
|
ui_stack_info: Option<UiStackInfo>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
if self.sizing_pass {
|
if self.sizing_pass {
|
||||||
// During the sizing pass we want widgets to use up as little space as possible,
|
// During the sizing pass we want widgets to use up as little space as possible,
|
||||||
|
@ -128,15 +159,29 @@ impl Ui {
|
||||||
debug_assert!(!max_rect.any_nan());
|
debug_assert!(!max_rect.any_nan());
|
||||||
let next_auto_id_source = Id::new(self.next_auto_id_source).with("child").value();
|
let next_auto_id_source = Id::new(self.next_auto_id_source).with("child").value();
|
||||||
self.next_auto_id_source = self.next_auto_id_source.wrapping_add(1);
|
self.next_auto_id_source = self.next_auto_id_source.wrapping_add(1);
|
||||||
|
|
||||||
|
let new_id = self.id.with(id_source);
|
||||||
|
let placer = Placer::new(max_rect, layout);
|
||||||
|
let ui_stack_info = ui_stack_info.unwrap_or_default();
|
||||||
|
let ui_stack = UiStack {
|
||||||
|
id: new_id,
|
||||||
|
layout_direction: layout.main_dir,
|
||||||
|
kind: ui_stack_info.kind,
|
||||||
|
frame: ui_stack_info.frame,
|
||||||
|
parent: Some(self.stack.clone()),
|
||||||
|
min_rect: placer.min_rect(),
|
||||||
|
max_rect: placer.max_rect(),
|
||||||
|
};
|
||||||
let child_ui = Ui {
|
let child_ui = Ui {
|
||||||
id: self.id.with(id_source),
|
id: new_id,
|
||||||
next_auto_id_source,
|
next_auto_id_source,
|
||||||
painter: self.painter.clone(),
|
painter: self.painter.clone(),
|
||||||
style: self.style.clone(),
|
style: self.style.clone(),
|
||||||
placer: Placer::new(max_rect, layout),
|
placer,
|
||||||
enabled: self.enabled,
|
enabled: self.enabled,
|
||||||
sizing_pass: self.sizing_pass,
|
sizing_pass: self.sizing_pass,
|
||||||
menu_state: self.menu_state.clone(),
|
menu_state: self.menu_state.clone(),
|
||||||
|
stack: Arc::new(ui_stack),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Register in the widget stack early, to ensure we are behind all widgets we contain:
|
// Register in the widget stack early, to ensure we are behind all widgets we contain:
|
||||||
|
@ -258,6 +303,12 @@ impl Ui {
|
||||||
&mut self.style_mut().visuals
|
&mut self.style_mut().visuals
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a reference to this [`Ui`]'s [`UiStack`].
|
||||||
|
#[inline]
|
||||||
|
pub fn stack(&self) -> &Arc<UiStack> {
|
||||||
|
&self.stack
|
||||||
|
}
|
||||||
|
|
||||||
/// Get a reference to the parent [`Context`].
|
/// Get a reference to the parent [`Context`].
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn ctx(&self) -> &Context {
|
pub fn ctx(&self) -> &Context {
|
||||||
|
@ -1021,7 +1072,7 @@ impl Ui {
|
||||||
let frame_rect = self.placer.next_space(desired_size, 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 child_rect = self.placer.justify_and_align(frame_rect, desired_size);
|
||||||
|
|
||||||
let mut child_ui = self.child_ui(child_rect, layout);
|
let mut child_ui = self.child_ui(child_rect, layout, None);
|
||||||
let ret = add_contents(&mut child_ui);
|
let ret = add_contents(&mut child_ui);
|
||||||
let final_child_rect = child_ui.min_rect();
|
let final_child_rect = child_ui.min_rect();
|
||||||
|
|
||||||
|
@ -1042,7 +1093,7 @@ impl Ui {
|
||||||
add_contents: impl FnOnce(&mut Self) -> R,
|
add_contents: impl FnOnce(&mut Self) -> R,
|
||||||
) -> InnerResponse<R> {
|
) -> InnerResponse<R> {
|
||||||
debug_assert!(max_rect.is_finite());
|
debug_assert!(max_rect.is_finite());
|
||||||
let mut child_ui = self.child_ui(max_rect, *self.layout());
|
let mut child_ui = self.child_ui(max_rect, *self.layout(), None);
|
||||||
let ret = add_contents(&mut child_ui);
|
let ret = add_contents(&mut child_ui);
|
||||||
let final_child_rect = child_ui.min_rect();
|
let final_child_rect = child_ui.min_rect();
|
||||||
|
|
||||||
|
@ -1868,7 +1919,8 @@ impl Ui {
|
||||||
) -> InnerResponse<R> {
|
) -> InnerResponse<R> {
|
||||||
let child_rect = self.available_rect_before_wrap();
|
let child_rect = self.available_rect_before_wrap();
|
||||||
let next_auto_id_source = self.next_auto_id_source;
|
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);
|
let mut child_ui =
|
||||||
|
self.child_ui_with_id_source(child_rect, *self.layout(), id_source, None);
|
||||||
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`.
|
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 ret = add_contents(&mut child_ui);
|
||||||
let response = self.allocate_rect(child_ui.min_rect(), Sense::hover());
|
let response = self.allocate_rect(child_ui.min_rect(), Sense::hover());
|
||||||
|
@ -1927,7 +1979,8 @@ impl Ui {
|
||||||
let mut child_rect = self.placer.available_rect_before_wrap();
|
let mut child_rect = self.placer.available_rect_before_wrap();
|
||||||
child_rect.min.x += indent;
|
child_rect.min.x += indent;
|
||||||
|
|
||||||
let mut child_ui = self.child_ui_with_id_source(child_rect, *self.layout(), id_source);
|
let mut child_ui =
|
||||||
|
self.child_ui_with_id_source(child_rect, *self.layout(), id_source, None);
|
||||||
let ret = add_contents(&mut child_ui);
|
let ret = add_contents(&mut child_ui);
|
||||||
|
|
||||||
let left_vline = self.visuals().indent_has_left_vline;
|
let left_vline = self.visuals().indent_has_left_vline;
|
||||||
|
@ -2149,7 +2202,7 @@ impl Ui {
|
||||||
layout: Layout,
|
layout: Layout,
|
||||||
add_contents: Box<dyn FnOnce(&mut Self) -> R + 'c>,
|
add_contents: Box<dyn FnOnce(&mut Self) -> R + 'c>,
|
||||||
) -> InnerResponse<R> {
|
) -> InnerResponse<R> {
|
||||||
let mut child_ui = self.child_ui(self.available_rect_before_wrap(), layout);
|
let mut child_ui = self.child_ui(self.available_rect_before_wrap(), layout, None);
|
||||||
let inner = add_contents(&mut child_ui);
|
let inner = add_contents(&mut child_ui);
|
||||||
let rect = child_ui.min_rect();
|
let rect = child_ui.min_rect();
|
||||||
let item_spacing = self.spacing().item_spacing;
|
let item_spacing = self.spacing().item_spacing;
|
||||||
|
@ -2233,7 +2286,7 @@ impl Ui {
|
||||||
pos2(pos.x + column_width, self.max_rect().right_bottom().y),
|
pos2(pos.x + column_width, self.max_rect().right_bottom().y),
|
||||||
);
|
);
|
||||||
let mut column_ui =
|
let mut column_ui =
|
||||||
self.child_ui(child_rect, Layout::top_down_justified(Align::LEFT));
|
self.child_ui(child_rect, Layout::top_down_justified(Align::LEFT), None);
|
||||||
column_ui.set_width(column_width);
|
column_ui.set_width(column_width);
|
||||||
column_ui
|
column_ui
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,166 @@
|
||||||
|
use std::iter::FusedIterator;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::{Direction, Frame, Id, Rect};
|
||||||
|
|
||||||
|
/// What kind is this [`crate::Ui`]?
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum UiKind {
|
||||||
|
/// A [`crate::Window`].
|
||||||
|
Window,
|
||||||
|
|
||||||
|
/// A [`crate::CentralPanel`].
|
||||||
|
CentralPanel,
|
||||||
|
|
||||||
|
/// A left [`crate::SidePanel`].
|
||||||
|
LeftPanel,
|
||||||
|
|
||||||
|
/// A right [`crate::SidePanel`].
|
||||||
|
RightPanel,
|
||||||
|
|
||||||
|
/// A top [`crate::TopBottomPanel`].
|
||||||
|
TopPanel,
|
||||||
|
|
||||||
|
/// A bottom [`crate::TopBottomPanel`].
|
||||||
|
BottomPanel,
|
||||||
|
|
||||||
|
/// A [`crate::Frame`].
|
||||||
|
Frame,
|
||||||
|
|
||||||
|
/// A [`crate::ScrollArea`].
|
||||||
|
ScrollArea,
|
||||||
|
|
||||||
|
/// A [`crate::Resize`].
|
||||||
|
Resize,
|
||||||
|
|
||||||
|
/// The content of a regular menu.
|
||||||
|
Menu,
|
||||||
|
|
||||||
|
/// The content of a popup menu.
|
||||||
|
Popup,
|
||||||
|
|
||||||
|
/// A tooltip, as shown by e.g. [`crate::Response::on_hover_ui`].
|
||||||
|
Tooltip,
|
||||||
|
|
||||||
|
/// A picker, such as color picker.
|
||||||
|
Picker,
|
||||||
|
|
||||||
|
/// A table cell (from the `egui_extras` crate).
|
||||||
|
TableCell,
|
||||||
|
|
||||||
|
/// An [`crate::Area`] that is not of any other kind.
|
||||||
|
GenericArea,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiKind {
|
||||||
|
/// Is this any kind of panel?
|
||||||
|
pub fn is_panel(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self,
|
||||||
|
Self::CentralPanel
|
||||||
|
| Self::LeftPanel
|
||||||
|
| Self::RightPanel
|
||||||
|
| Self::TopPanel
|
||||||
|
| Self::BottomPanel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Information about a [`crate::Ui`] to be included in the corresponding [`UiStack`].
|
||||||
|
#[derive(Default, Copy, Clone, Debug)]
|
||||||
|
pub struct UiStackInfo {
|
||||||
|
pub kind: Option<UiKind>,
|
||||||
|
pub frame: Frame,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiStackInfo {
|
||||||
|
/// Create a new [`UiStackInfo`] with the given kind and an empty frame.
|
||||||
|
pub fn new(kind: UiKind) -> Self {
|
||||||
|
Self {
|
||||||
|
kind: Some(kind),
|
||||||
|
frame: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Information about a [`crate::Ui`] and its parents.
|
||||||
|
///
|
||||||
|
/// [`UiStack`] serves to keep track of the current hierarchy of [`crate::Ui`]s, such
|
||||||
|
/// that nested widgets or user code may adapt to the surrounding context or obtain layout information
|
||||||
|
/// from a [`crate::Ui`] that might be several steps higher in the hierarchy.
|
||||||
|
///
|
||||||
|
/// Note: since [`UiStack`] contains a reference to its parent, it is both a stack, and a node within
|
||||||
|
/// that stack. Most of its methods are about the specific node, but some methods walk up the
|
||||||
|
/// hierarchy to provide information about the entire stack.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct UiStack {
|
||||||
|
// stuff that `Ui::child_ui` can deal with directly
|
||||||
|
pub id: Id,
|
||||||
|
pub kind: Option<UiKind>,
|
||||||
|
pub frame: Frame,
|
||||||
|
pub layout_direction: Direction,
|
||||||
|
pub min_rect: Rect,
|
||||||
|
pub max_rect: Rect,
|
||||||
|
pub parent: Option<Arc<UiStack>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// these methods act on this specific node
|
||||||
|
impl UiStack {
|
||||||
|
/// Is this [`crate::Ui`] a panel?
|
||||||
|
#[inline]
|
||||||
|
pub fn is_panel_ui(&self) -> bool {
|
||||||
|
self.kind.map_or(false, |kind| kind.is_panel())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is this a root [`crate::Ui`], i.e. created with [`crate::Ui::new()`]?
|
||||||
|
#[inline]
|
||||||
|
pub fn is_root_ui(&self) -> bool {
|
||||||
|
self.parent.is_none()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This this [`crate::Ui`] a [`crate::Frame`] with a visible stroke?
|
||||||
|
#[inline]
|
||||||
|
pub fn has_visible_frame(&self) -> bool {
|
||||||
|
!self.frame.stroke.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// these methods act on the entire stack
|
||||||
|
impl UiStack {
|
||||||
|
/// Return an iterator that walks the stack from this node to the root.
|
||||||
|
#[allow(clippy::iter_without_into_iter)]
|
||||||
|
pub fn iter(&self) -> UiStackIterator<'_> {
|
||||||
|
UiStackIterator { next: Some(self) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if this node is or is contained in a [`crate::Ui`] of a specific kind.
|
||||||
|
pub fn contained_in(&self, kind: UiKind) -> bool {
|
||||||
|
self.iter().any(|frame| frame.kind == Some(kind))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Iterator that walks up a stack of `StackFrame`s.
|
||||||
|
///
|
||||||
|
/// See [`UiStack::iter`].
|
||||||
|
pub struct UiStackIterator<'a> {
|
||||||
|
next: Option<&'a UiStack>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for UiStackIterator<'a> {
|
||||||
|
type Item = &'a UiStack;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let current = self.next;
|
||||||
|
self.next = current.and_then(|frame| frame.parent.as_deref());
|
||||||
|
current
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FusedIterator for UiStackIterator<'a> {}
|
|
@ -489,6 +489,7 @@ pub fn color_edit_button_hsva(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) -> Res
|
||||||
// TODO(emilk): make it easier to show a temporary popup that closes when you click outside it
|
// TODO(emilk): make it easier to show a temporary popup that closes when you click outside it
|
||||||
if ui.memory(|mem| mem.is_popup_open(popup_id)) {
|
if ui.memory(|mem| mem.is_popup_open(popup_id)) {
|
||||||
let area_response = Area::new(popup_id)
|
let area_response = Area::new(popup_id)
|
||||||
|
.kind(UiKind::Picker)
|
||||||
.order(Order::Foreground)
|
.order(Order::Foreground)
|
||||||
.fixed_pos(button_response.rect.max)
|
.fixed_pos(button_response.rect.max)
|
||||||
.show(ui.ctx(), |ui| {
|
.show(ui.ctx(), |ui| {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use egui::*;
|
use egui::*;
|
||||||
|
|
||||||
/// Showcase some ui code
|
/// Showcase some ui code
|
||||||
|
@ -154,6 +155,10 @@ impl View for MiscDemoWindow {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
CollapsingHeader::new("Ui Stack")
|
||||||
|
.default_open(false)
|
||||||
|
.show(ui, ui_stack_demo);
|
||||||
|
|
||||||
CollapsingHeader::new("Misc")
|
CollapsingHeader::new("Misc")
|
||||||
.default_open(false)
|
.default_open(false)
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
|
@ -492,6 +497,72 @@ impl Tree {
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fn ui_stack_demo(ui: &mut Ui) {
|
||||||
|
ui.horizontal_wrapped(|ui| {
|
||||||
|
ui.label("The");
|
||||||
|
ui.code("egui::Ui");
|
||||||
|
ui.label("core type is typically deeply nested in");
|
||||||
|
ui.code("egui");
|
||||||
|
ui.label(
|
||||||
|
"applications. To provide context to nested code, it maintains a stack \
|
||||||
|
with various information.\n\nThis is how the stack looks like here:",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
let stack = ui.stack().clone();
|
||||||
|
Frame {
|
||||||
|
inner_margin: ui.spacing().menu_margin,
|
||||||
|
stroke: ui.visuals().widgets.noninteractive.bg_stroke,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.show(ui, |ui| {
|
||||||
|
egui_extras::TableBuilder::new(ui)
|
||||||
|
.column(egui_extras::Column::auto())
|
||||||
|
.column(egui_extras::Column::auto())
|
||||||
|
.header(18.0, |mut header| {
|
||||||
|
header.col(|ui| {
|
||||||
|
ui.strong("id");
|
||||||
|
});
|
||||||
|
header.col(|ui| {
|
||||||
|
ui.strong("kind");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.body(|mut body| {
|
||||||
|
for node in stack.iter() {
|
||||||
|
body.row(18.0, |mut row| {
|
||||||
|
row.col(|ui| {
|
||||||
|
let response = ui.label(format!("{:?}", node.id));
|
||||||
|
|
||||||
|
if response.hovered() {
|
||||||
|
ui.ctx().debug_painter().debug_rect(
|
||||||
|
node.max_rect,
|
||||||
|
Color32::GREEN,
|
||||||
|
"max_rect",
|
||||||
|
);
|
||||||
|
ui.ctx().debug_painter().circle_filled(
|
||||||
|
node.min_rect.min,
|
||||||
|
2.0,
|
||||||
|
Color32::RED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.label(if let Some(kind) = node.kind {
|
||||||
|
format!("{kind:?}")
|
||||||
|
} else {
|
||||||
|
"-".to_owned()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.small("Hover on UI's ids to display their origin and max rect.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
fn text_layout_demo(ui: &mut Ui) {
|
fn text_layout_demo(ui: &mut Ui) {
|
||||||
use egui::text::LayoutJob;
|
use egui::text::LayoutJob;
|
||||||
|
|
||||||
|
|
|
@ -140,6 +140,7 @@ impl<'a> Widget for DatePickerButton<'a> {
|
||||||
inner: saved,
|
inner: saved,
|
||||||
response: area_response,
|
response: area_response,
|
||||||
} = Area::new(ui.make_persistent_id(self.id_source))
|
} = Area::new(ui.make_persistent_id(self.id_source))
|
||||||
|
.kind(egui::UiKind::Picker)
|
||||||
.order(Order::Foreground)
|
.order(Order::Foreground)
|
||||||
.fixed_pos(pos)
|
.fixed_pos(pos)
|
||||||
.show(ui.ctx(), |ui| {
|
.show(ui.ctx(), |ui| {
|
||||||
|
|
|
@ -195,9 +195,12 @@ impl<'l> StripLayout<'l> {
|
||||||
child_ui_id_source: egui::Id,
|
child_ui_id_source: egui::Id,
|
||||||
add_cell_contents: impl FnOnce(&mut Ui),
|
add_cell_contents: impl FnOnce(&mut Ui),
|
||||||
) -> Ui {
|
) -> Ui {
|
||||||
let mut child_ui =
|
let mut child_ui = self.ui.child_ui_with_id_source(
|
||||||
self.ui
|
rect,
|
||||||
.child_ui_with_id_source(rect, self.cell_layout, child_ui_id_source);
|
self.cell_layout,
|
||||||
|
child_ui_id_source,
|
||||||
|
Some(egui::UiStackInfo::new(egui::UiKind::TableCell)),
|
||||||
|
);
|
||||||
|
|
||||||
if flags.clip {
|
if flags.clip {
|
||||||
let margin = egui::Vec2::splat(self.ui.visuals().clip_rect_margin);
|
let margin = egui::Vec2::splat(self.ui.visuals().clip_rect_margin);
|
||||||
|
|
|
@ -250,7 +250,7 @@ impl Widget for &mut LegendWidget {
|
||||||
let layout = Layout::from_main_dir_and_cross_align(main_dir, cross_align);
|
let layout = Layout::from_main_dir_and_cross_align(main_dir, cross_align);
|
||||||
let legend_pad = 4.0;
|
let legend_pad = 4.0;
|
||||||
let legend_rect = rect.shrink(legend_pad);
|
let legend_rect = rect.shrink(legend_pad);
|
||||||
let mut legend_ui = ui.child_ui(legend_rect, layout);
|
let mut legend_ui = ui.child_ui(legend_rect, layout, None);
|
||||||
legend_ui
|
legend_ui
|
||||||
.scope(|ui| {
|
.scope(|ui| {
|
||||||
let background_frame = Frame {
|
let background_frame = Frame {
|
||||||
|
|
|
@ -1488,7 +1488,7 @@ impl<'a> PreparedPlot<'a> {
|
||||||
|
|
||||||
let transform = &self.transform;
|
let transform = &self.transform;
|
||||||
|
|
||||||
let mut plot_ui = ui.child_ui(*transform.frame(), Layout::default());
|
let mut plot_ui = ui.child_ui(*transform.frame(), Layout::default(), None);
|
||||||
plot_ui.set_clip_rect(transform.frame().intersect(ui.clip_rect()));
|
plot_ui.set_clip_rect(transform.frame().intersect(ui.clip_rect()));
|
||||||
for item in &self.items {
|
for item in &self.items {
|
||||||
item.shapes(&plot_ui, transform, &mut shapes);
|
item.shapes(&plot_ui, transform, &mut shapes);
|
||||||
|
|
|
@ -71,7 +71,7 @@ fn custom_window_frame(ctx: &egui::Context, title: &str, add_contents: impl FnOn
|
||||||
rect
|
rect
|
||||||
}
|
}
|
||||||
.shrink(4.0);
|
.shrink(4.0);
|
||||||
let mut content_ui = ui.child_ui(content_rect, *ui.layout());
|
let mut content_ui = ui.child_ui(content_rect, *ui.layout(), None);
|
||||||
add_contents(&mut content_ui);
|
add_contents(&mut content_ui);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
[package]
|
||||||
|
name = "test_ui_stack"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Antoine Beyeler <abeyeler@gmail.com>"]
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.76"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
eframe = { workspace = true, features = [
|
||||||
|
"default",
|
||||||
|
"persistence",
|
||||||
|
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
|
||||||
|
] }
|
||||||
|
|
||||||
|
# For image support:
|
||||||
|
egui_extras = { workspace = true, features = ["default", "image"] }
|
||||||
|
|
||||||
|
env_logger = { version = "0.10", default-features = false, features = [
|
||||||
|
"auto-color",
|
||||||
|
"humantime",
|
||||||
|
] }
|
|
@ -0,0 +1,347 @@
|
||||||
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||||
|
#![allow(rustdoc::missing_crate_level_docs)] // it's an example
|
||||||
|
|
||||||
|
use eframe::egui;
|
||||||
|
use eframe::egui::{Rangef, Shape, UiKind};
|
||||||
|
use egui_extras::Column;
|
||||||
|
|
||||||
|
fn main() -> Result<(), eframe::Error> {
|
||||||
|
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
|
||||||
|
let options = eframe::NativeOptions {
|
||||||
|
viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 240.0]),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
eframe::run_native(
|
||||||
|
"Stack Frame Demo",
|
||||||
|
options,
|
||||||
|
Box::new(|cc| {
|
||||||
|
// This gives us image support:
|
||||||
|
egui_extras::install_image_loaders(&cc.egui_ctx);
|
||||||
|
|
||||||
|
Ok(Box::<MyApp>::default())
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct MyApp {}
|
||||||
|
|
||||||
|
impl eframe::App for MyApp {
|
||||||
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
|
ctx.style_mut(|style| style.interaction.tooltip_delay = 0.0);
|
||||||
|
egui::SidePanel::right("side_panel").show(ctx, |ui| {
|
||||||
|
ui.heading("Information");
|
||||||
|
ui.label(
|
||||||
|
"This is a demo/test environment of the `UiStack` feature. The tables display \
|
||||||
|
the UI stack in various contexts. You can hover on the IDs to display the \
|
||||||
|
corresponding origin/`max_rect`.\n\n\
|
||||||
|
The \"Full span test\" labels showcase an implementation of full-span \
|
||||||
|
highlighting. Hover to see them in action!",
|
||||||
|
);
|
||||||
|
ui.add_space(10.0);
|
||||||
|
if ui.button("Reset egui memory").clicked() {
|
||||||
|
ctx.memory_mut(|mem| *mem = Default::default());
|
||||||
|
}
|
||||||
|
ui.add_space(20.0);
|
||||||
|
|
||||||
|
egui::ScrollArea::both().auto_shrink(false).show(ui, |ui| {
|
||||||
|
stack_ui(ui);
|
||||||
|
|
||||||
|
// full span test
|
||||||
|
ui.add_space(20.0);
|
||||||
|
full_span_widget(ui, false);
|
||||||
|
|
||||||
|
// nested frames test
|
||||||
|
ui.add_space(20.0);
|
||||||
|
egui::Frame {
|
||||||
|
stroke: ui.visuals().noninteractive().bg_stroke,
|
||||||
|
inner_margin: egui::Margin::same(4.0),
|
||||||
|
outer_margin: egui::Margin::same(4.0),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.show(ui, |ui| {
|
||||||
|
full_span_widget(ui, false);
|
||||||
|
stack_ui(ui);
|
||||||
|
|
||||||
|
egui::Frame {
|
||||||
|
stroke: ui.visuals().noninteractive().bg_stroke,
|
||||||
|
inner_margin: egui::Margin::same(8.0),
|
||||||
|
outer_margin: egui::Margin::same(6.0),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.show(ui, |ui| {
|
||||||
|
full_span_widget(ui, false);
|
||||||
|
stack_ui(ui);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
egui::TopBottomPanel::bottom("bottom_panel")
|
||||||
|
.resizable(true)
|
||||||
|
.show(ctx, |ui| {
|
||||||
|
egui::ScrollArea::vertical()
|
||||||
|
.auto_shrink(false)
|
||||||
|
.show(ui, |ui| {
|
||||||
|
stack_ui(ui);
|
||||||
|
|
||||||
|
// full span test
|
||||||
|
ui.add_space(20.0);
|
||||||
|
full_span_widget(ui, false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
egui::ScrollArea::vertical()
|
||||||
|
.auto_shrink(false)
|
||||||
|
.show(ui, |ui| {
|
||||||
|
ui.label("stack here:");
|
||||||
|
stack_ui(ui);
|
||||||
|
|
||||||
|
// full span test
|
||||||
|
ui.add_space(20.0);
|
||||||
|
full_span_widget(ui, false);
|
||||||
|
|
||||||
|
// tooltip test
|
||||||
|
ui.add_space(20.0);
|
||||||
|
ui.label("Hover me").on_hover_ui(|ui| {
|
||||||
|
full_span_widget(ui, true);
|
||||||
|
ui.add_space(20.0);
|
||||||
|
stack_ui(ui);
|
||||||
|
});
|
||||||
|
|
||||||
|
// combobox test
|
||||||
|
ui.add_space(20.0);
|
||||||
|
egui::ComboBox::from_id_source("combo_box")
|
||||||
|
.selected_text("click me")
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
full_span_widget(ui, true);
|
||||||
|
ui.add_space(20.0);
|
||||||
|
stack_ui(ui);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ui nesting test
|
||||||
|
ui.add_space(20.0);
|
||||||
|
ui.label("UI nesting test:");
|
||||||
|
egui::Frame {
|
||||||
|
stroke: ui.visuals().noninteractive().bg_stroke,
|
||||||
|
inner_margin: egui::Margin::same(4.0),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.show(ui, |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.scope(stack_ui);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// table test
|
||||||
|
let mut cell_stack = None;
|
||||||
|
ui.add_space(20.0);
|
||||||
|
ui.label("Table test:");
|
||||||
|
|
||||||
|
egui_extras::TableBuilder::new(ui)
|
||||||
|
.vscroll(false)
|
||||||
|
.column(Column::auto())
|
||||||
|
.column(Column::auto())
|
||||||
|
.header(20.0, |mut header| {
|
||||||
|
header.col(|ui| {
|
||||||
|
ui.strong("column 1");
|
||||||
|
});
|
||||||
|
header.col(|ui| {
|
||||||
|
ui.strong("column 2");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.body(|mut body| {
|
||||||
|
body.row(20.0, |mut row| {
|
||||||
|
row.col(|ui| {
|
||||||
|
full_span_widget(ui, false);
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
||||||
|
ui.label("See stack below");
|
||||||
|
cell_stack = Some(ui.stack().clone());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(cell_stack) = cell_stack {
|
||||||
|
ui.label("Cell's stack:");
|
||||||
|
stack_ui_impl(ui, &cell_stack);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
egui::Window::new("Window")
|
||||||
|
.pivot(egui::Align2::RIGHT_TOP)
|
||||||
|
.show(ctx, |ui| {
|
||||||
|
full_span_widget(ui, false);
|
||||||
|
ui.add_space(20.0);
|
||||||
|
stack_ui(ui);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Demo of a widget that highlights its background all the way to the edge of its container when
|
||||||
|
/// hovered.
|
||||||
|
fn full_span_widget(ui: &mut egui::Ui, permanent: bool) {
|
||||||
|
let bg_shape_idx = ui.painter().add(Shape::Noop);
|
||||||
|
let response = ui.label("Full span test");
|
||||||
|
let ui_stack = ui.stack();
|
||||||
|
|
||||||
|
let rect = egui::Rect::from_x_y_ranges(
|
||||||
|
full_span_horizontal_range(ui_stack),
|
||||||
|
response.rect.y_range(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if permanent || response.hovered() {
|
||||||
|
ui.painter().set(
|
||||||
|
bg_shape_idx,
|
||||||
|
Shape::rect_filled(rect, 0.0, ui.visuals().selection.bg_fill),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the horizontal range of the enclosing container.
|
||||||
|
fn full_span_horizontal_range(ui_stack: &egui::UiStack) -> Rangef {
|
||||||
|
for node in ui_stack.iter() {
|
||||||
|
if node.has_visible_frame()
|
||||||
|
|| node.is_panel_ui()
|
||||||
|
|| node.is_root_ui()
|
||||||
|
|| node.kind == Some(UiKind::TableCell)
|
||||||
|
{
|
||||||
|
return (node.max_rect + node.frame.inner_margin).x_range();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// should never happen
|
||||||
|
Rangef::EVERYTHING
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stack_ui(ui: &mut egui::Ui) {
|
||||||
|
let ui_stack = ui.stack().clone();
|
||||||
|
ui.scope(|ui| {
|
||||||
|
stack_ui_impl(ui, &ui_stack);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stack_ui_impl(ui: &mut egui::Ui, stack: &egui::UiStack) {
|
||||||
|
egui::Frame {
|
||||||
|
stroke: ui.style().noninteractive().fg_stroke,
|
||||||
|
inner_margin: egui::Margin::same(4.0),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.show(ui, |ui| {
|
||||||
|
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
||||||
|
|
||||||
|
egui_extras::TableBuilder::new(ui)
|
||||||
|
.column(Column::auto())
|
||||||
|
.column(Column::auto())
|
||||||
|
.column(Column::auto())
|
||||||
|
.column(Column::auto())
|
||||||
|
.column(Column::auto())
|
||||||
|
.column(Column::auto())
|
||||||
|
.header(20.0, |mut header| {
|
||||||
|
header.col(|ui| {
|
||||||
|
ui.strong("id");
|
||||||
|
});
|
||||||
|
header.col(|ui| {
|
||||||
|
ui.strong("kind");
|
||||||
|
});
|
||||||
|
header.col(|ui| {
|
||||||
|
ui.strong("stroke");
|
||||||
|
});
|
||||||
|
header.col(|ui| {
|
||||||
|
ui.strong("inner");
|
||||||
|
});
|
||||||
|
header.col(|ui| {
|
||||||
|
ui.strong("outer");
|
||||||
|
});
|
||||||
|
header.col(|ui| {
|
||||||
|
ui.strong("direction");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.body(|mut body| {
|
||||||
|
for node in stack.iter() {
|
||||||
|
body.row(20.0, |mut row| {
|
||||||
|
row.col(|ui| {
|
||||||
|
if ui.label(format!("{:?}", node.id)).hovered() {
|
||||||
|
ui.ctx().debug_painter().debug_rect(
|
||||||
|
node.max_rect,
|
||||||
|
egui::Color32::GREEN,
|
||||||
|
"max",
|
||||||
|
);
|
||||||
|
ui.ctx().debug_painter().circle_filled(
|
||||||
|
node.min_rect.min,
|
||||||
|
2.0,
|
||||||
|
egui::Color32::RED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
let s = if let Some(kind) = node.kind {
|
||||||
|
format!("{kind:?}")
|
||||||
|
} else {
|
||||||
|
"-".to_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.label(s);
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
if node.frame.stroke == egui::Stroke::NONE {
|
||||||
|
ui.label("-");
|
||||||
|
} else {
|
||||||
|
let mut layout_job = egui::text::LayoutJob::default();
|
||||||
|
layout_job.append(
|
||||||
|
"⬛ ",
|
||||||
|
0.0,
|
||||||
|
egui::TextFormat::simple(
|
||||||
|
egui::TextStyle::Body.resolve(ui.style()),
|
||||||
|
node.frame.stroke.color,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
layout_job.append(
|
||||||
|
format!("{}px", node.frame.stroke.width).as_str(),
|
||||||
|
0.0,
|
||||||
|
egui::TextFormat::simple(
|
||||||
|
egui::TextStyle::Body.resolve(ui.style()),
|
||||||
|
ui.style().visuals.text_color(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
||||||
|
ui.label(layout_job);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.label(print_margin(&node.frame.inner_margin));
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.label(print_margin(&node.frame.outer_margin));
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.label(format!("{:?}", node.layout_direction));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_margin(margin: &egui::Margin) -> String {
|
||||||
|
if margin.is_same() {
|
||||||
|
format!("{}px", margin.left)
|
||||||
|
} else {
|
||||||
|
let s1 = if margin.left == margin.right {
|
||||||
|
format!("H: {}px", margin.left)
|
||||||
|
} else {
|
||||||
|
format!("L: {}px R: {}px", margin.left, margin.right)
|
||||||
|
};
|
||||||
|
let s2 = if margin.top == margin.bottom {
|
||||||
|
format!("V: {}px", margin.top)
|
||||||
|
} else {
|
||||||
|
format!("T: {}px B: {}px", margin.top, margin.bottom)
|
||||||
|
};
|
||||||
|
format!("{s1} / {s2}")
|
||||||
|
}
|
||||||
|
}
|
|
@ -457,7 +457,7 @@ fn drop_target<R>(
|
||||||
|
|
||||||
let available_rect = ui.available_rect_before_wrap();
|
let available_rect = ui.available_rect_before_wrap();
|
||||||
let inner_rect = available_rect.shrink2(margin);
|
let inner_rect = available_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);
|
||||||
let ret = body(&mut content_ui);
|
let ret = body(&mut content_ui);
|
||||||
|
|
||||||
let outer_rect =
|
let outer_rect =
|
||||||
|
|
Loading…
Reference in New Issue