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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "test_ui_stack"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"eframe",
|
||||
"egui_extras",
|
||||
"env_logger",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "test_viewports"
|
||||
version = "0.1.0"
|
||||
|
|
|
@ -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,
|
||||
|
@ -105,6 +106,7 @@ impl Area {
|
|||
pub fn new(id: Id) -> Self {
|
||||
Self {
|
||||
id,
|
||||
kind: UiKind::GenericArea,
|
||||
sense: None,
|
||||
movable: true,
|
||||
interactable: true,
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -303,6 +314,7 @@ impl Area {
|
|||
}
|
||||
|
||||
pub(crate) struct Prepared {
|
||||
kind: UiKind,
|
||||
layer_id: LayerId,
|
||||
state: AreaState,
|
||||
move_response: Response,
|
||||
|
@ -336,6 +348,7 @@ impl Area {
|
|||
pub(crate) fn begin(self, ctx: &Context) -> Prepared {
|
||||
let Self {
|
||||
id,
|
||||
kind,
|
||||
sense,
|
||||
movable,
|
||||
order,
|
||||
|
@ -458,6 +471,7 @@ impl Area {
|
|||
move_response.interact_rect = state.rect();
|
||||
|
||||
Prepared {
|
||||
kind,
|
||||
layer_id,
|
||||
state,
|
||||
move_response,
|
||||
|
@ -498,6 +512,7 @@ impl Prepared {
|
|||
self.layer_id.id,
|
||||
max_rect,
|
||||
clip_rect,
|
||||
UiStackInfo::new(self.kind),
|
||||
);
|
||||
|
||||
if self.fade_in {
|
||||
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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| {
|
||||
|
@ -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;
|
||||
|
@ -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| {
|
||||
|
@ -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;
|
||||
|
@ -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| {
|
||||
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
@ -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},
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -67,6 +67,9 @@ pub struct Ui {
|
|||
|
||||
/// Indicates whether this Ui belongs to a Menu.
|
||||
menu_state: Option<Arc<RwLock<MenuState>>>,
|
||||
|
||||
/// The [`UiStack`] for this [`Ui`].
|
||||
stack: Arc<UiStack>,
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
|
@ -77,17 +80,36 @@ impl 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) -> 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 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 {
|
||||
id,
|
||||
next_auto_id_source: id.with("auto").value(),
|
||||
painter: Painter::new(ctx, layer_id, clip_rect),
|
||||
style,
|
||||
placer: Placer::new(max_rect, Layout::default()),
|
||||
placer,
|
||||
enabled: true,
|
||||
sizing_pass: false,
|
||||
menu_state: None,
|
||||
stack: Arc::new(ui_stack),
|
||||
};
|
||||
|
||||
// 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.
|
||||
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.
|
||||
|
@ -115,6 +145,7 @@ impl Ui {
|
|||
max_rect: Rect,
|
||||
mut layout: Layout,
|
||||
id_source: impl Hash,
|
||||
ui_stack_info: Option<UiStackInfo>,
|
||||
) -> Self {
|
||||
if self.sizing_pass {
|
||||
// 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());
|
||||
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);
|
||||
|
||||
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 {
|
||||
id: self.id.with(id_source),
|
||||
id: new_id,
|
||||
next_auto_id_source,
|
||||
painter: self.painter.clone(),
|
||||
style: self.style.clone(),
|
||||
placer: Placer::new(max_rect, layout),
|
||||
placer,
|
||||
enabled: self.enabled,
|
||||
sizing_pass: self.sizing_pass,
|
||||
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:
|
||||
|
@ -258,6 +303,12 @@ impl Ui {
|
|||
&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`].
|
||||
#[inline]
|
||||
pub fn ctx(&self) -> &Context {
|
||||
|
@ -1021,7 +1072,7 @@ impl Ui {
|
|||
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);
|
||||
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();
|
||||
|
||||
|
@ -1042,7 +1093,7 @@ impl Ui {
|
|||
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());
|
||||
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();
|
||||
|
||||
|
@ -1868,7 +1919,8 @@ impl Ui {
|
|||
) -> 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);
|
||||
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`.
|
||||
let ret = add_contents(&mut child_ui);
|
||||
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();
|
||||
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 left_vline = self.visuals().indent_has_left_vline;
|
||||
|
@ -2149,7 +2202,7 @@ impl Ui {
|
|||
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);
|
||||
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;
|
||||
|
@ -2233,7 +2286,7 @@ impl Ui {
|
|||
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));
|
||||
self.child_ui(child_rect, Layout::top_down_justified(Align::LEFT), None);
|
||||
column_ui.set_width(column_width);
|
||||
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
|
||||
if ui.memory(|mem| mem.is_popup_open(popup_id)) {
|
||||
let area_response = Area::new(popup_id)
|
||||
.kind(UiKind::Picker)
|
||||
.order(Order::Foreground)
|
||||
.fixed_pos(button_response.rect.max)
|
||||
.show(ui.ctx(), |ui| {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use super::*;
|
||||
|
||||
use egui::*;
|
||||
|
||||
/// 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")
|
||||
.default_open(false)
|
||||
.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) {
|
||||
use egui::text::LayoutJob;
|
||||
|
||||
|
|
|
@ -140,6 +140,7 @@ impl<'a> Widget for DatePickerButton<'a> {
|
|||
inner: saved,
|
||||
response: area_response,
|
||||
} = Area::new(ui.make_persistent_id(self.id_source))
|
||||
.kind(egui::UiKind::Picker)
|
||||
.order(Order::Foreground)
|
||||
.fixed_pos(pos)
|
||||
.show(ui.ctx(), |ui| {
|
||||
|
|
|
@ -195,9 +195,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(rect, self.cell_layout, child_ui_id_source);
|
||||
let mut child_ui = self.ui.child_ui_with_id_source(
|
||||
rect,
|
||||
self.cell_layout,
|
||||
child_ui_id_source,
|
||||
Some(egui::UiStackInfo::new(egui::UiKind::TableCell)),
|
||||
);
|
||||
|
||||
if flags.clip {
|
||||
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 legend_pad = 4.0;
|
||||
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
|
||||
.scope(|ui| {
|
||||
let background_frame = Frame {
|
||||
|
|
|
@ -1488,7 +1488,7 @@ impl<'a> PreparedPlot<'a> {
|
|||
|
||||
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()));
|
||||
for item in &self.items {
|
||||
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
|
||||
}
|
||||
.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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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 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 outer_rect =
|
||||
|
|
Loading…
Reference in New Issue