Add tags to `UiStack` (#4617)
You can now set custom tags on the `UiStack`. This allows you to write code that is situationally aware at runtime. For instance, you could decide wether or not a label should truncate its text depending on what part of your ui it is in, without having to pass that info down via the callstack.
This commit is contained in:
parent
321d2441c1
commit
bb8400853f
|
|
@ -253,10 +253,7 @@ impl Frame {
|
|||
let content_ui = ui.child_ui(
|
||||
inner_rect,
|
||||
*ui.layout(),
|
||||
Some(UiStackInfo {
|
||||
frame: self,
|
||||
kind: Some(UiKind::Frame),
|
||||
}),
|
||||
Some(UiStackInfo::new(UiKind::Frame).with_frame(self)),
|
||||
);
|
||||
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -362,10 +362,7 @@ impl SidePanel {
|
|||
self.id,
|
||||
available_rect,
|
||||
clip_rect,
|
||||
UiStackInfo {
|
||||
kind: None, // set by show_inside_dyn
|
||||
frame: Frame::default(),
|
||||
},
|
||||
UiStackInfo::default(),
|
||||
);
|
||||
|
||||
let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents);
|
||||
|
|
@ -848,10 +845,7 @@ impl TopBottomPanel {
|
|||
self.id,
|
||||
available_rect,
|
||||
clip_rect,
|
||||
UiStackInfo {
|
||||
kind: None, // set by show_inside_dyn
|
||||
frame: Frame::default(),
|
||||
},
|
||||
UiStackInfo::default(), // set by show_inside_dyn
|
||||
);
|
||||
|
||||
let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents);
|
||||
|
|
@ -1120,10 +1114,7 @@ impl CentralPanel {
|
|||
id,
|
||||
available_rect,
|
||||
clip_rect,
|
||||
UiStackInfo {
|
||||
kind: None, // set by show_inside_dyn
|
||||
frame: Frame::default(),
|
||||
},
|
||||
UiStackInfo::default(), // set by show_inside_dyn
|
||||
);
|
||||
|
||||
let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents);
|
||||
|
|
|
|||
|
|
@ -94,8 +94,7 @@ impl Ui {
|
|||
let ui_stack = UiStack {
|
||||
id,
|
||||
layout_direction: layout.main_dir,
|
||||
kind: ui_stack_info.kind,
|
||||
frame: ui_stack_info.frame,
|
||||
info: ui_stack_info,
|
||||
parent: None,
|
||||
min_rect: placer.min_rect(),
|
||||
max_rect: placer.max_rect(),
|
||||
|
|
@ -130,6 +129,8 @@ impl Ui {
|
|||
///
|
||||
/// Note: calling this function twice from the same [`Ui`] will create a conflict of id. Use
|
||||
/// [`Self::scope`] if needed.
|
||||
///
|
||||
/// When in doubt, use `None` for the `UiStackInfo` argument.
|
||||
pub fn child_ui(
|
||||
&mut self,
|
||||
max_rect: Rect,
|
||||
|
|
@ -140,6 +141,8 @@ impl Ui {
|
|||
}
|
||||
|
||||
/// Create a new [`Ui`] at a specific region with a specific id.
|
||||
///
|
||||
/// When in doubt, use `None` for the `UiStackInfo` argument.
|
||||
pub fn child_ui_with_id_source(
|
||||
&mut self,
|
||||
max_rect: Rect,
|
||||
|
|
@ -162,12 +165,10 @@ impl Ui {
|
|||
|
||||
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,
|
||||
info: ui_stack_info.unwrap_or_default(),
|
||||
parent: Some(self.stack.clone()),
|
||||
min_rect: placer.min_rect(),
|
||||
max_rect: placer.max_rect(),
|
||||
|
|
@ -1956,7 +1957,22 @@ impl Ui {
|
|||
id_source: impl Hash,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> InnerResponse<R> {
|
||||
self.scope_dyn(Box::new(add_contents), Id::new(id_source))
|
||||
self.scope_dyn(Box::new(add_contents), Id::new(id_source), None)
|
||||
}
|
||||
|
||||
/// Push another level onto the [`UiStack`].
|
||||
///
|
||||
/// You can use this, for instance, to tag a group of widgets.
|
||||
pub fn push_stack_info<R>(
|
||||
&mut self,
|
||||
ui_stack_info: UiStackInfo,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> InnerResponse<R> {
|
||||
self.scope_dyn(
|
||||
Box::new(add_contents),
|
||||
Id::new("child"),
|
||||
Some(ui_stack_info),
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a scoped child ui.
|
||||
|
|
@ -1972,18 +1988,19 @@ impl Ui {
|
|||
/// # });
|
||||
/// ```
|
||||
pub fn scope<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
|
||||
self.scope_dyn(Box::new(add_contents), Id::new("child"))
|
||||
self.scope_dyn(Box::new(add_contents), Id::new("child"), None)
|
||||
}
|
||||
|
||||
fn scope_dyn<'c, R>(
|
||||
&mut self,
|
||||
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||
id_source: Id,
|
||||
ui_stack_info: Option<UiStackInfo>,
|
||||
) -> 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, None);
|
||||
self.child_ui_with_id_source(child_rect, *self.layout(), id_source, ui_stack_info);
|
||||
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());
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use std::iter::FusedIterator;
|
||||
use std::sync::Arc;
|
||||
use std::{any::Any, iter::FusedIterator};
|
||||
|
||||
use crate::{Direction, Frame, Id, Rect};
|
||||
|
||||
|
|
@ -54,6 +54,7 @@ pub enum UiKind {
|
|||
|
||||
impl UiKind {
|
||||
/// Is this any kind of panel?
|
||||
#[inline]
|
||||
pub fn is_panel(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
|
|
@ -64,25 +65,121 @@ impl UiKind {
|
|||
| Self::BottomPanel
|
||||
)
|
||||
}
|
||||
|
||||
/// Is this any kind of [`crate::Area`]?
|
||||
#[inline]
|
||||
pub fn is_area(&self) -> bool {
|
||||
match self {
|
||||
Self::CentralPanel
|
||||
| Self::LeftPanel
|
||||
| Self::RightPanel
|
||||
| Self::TopPanel
|
||||
| Self::BottomPanel
|
||||
| Self::Frame
|
||||
| Self::ScrollArea
|
||||
| Self::Resize
|
||||
| Self::TableCell => false,
|
||||
|
||||
Self::Window
|
||||
| Self::Menu
|
||||
| Self::Popup
|
||||
| Self::Tooltip
|
||||
| Self::Picker
|
||||
| Self::GenericArea => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Information about a [`crate::Ui`] to be included in the corresponding [`UiStack`].
|
||||
#[derive(Default, Copy, Clone, Debug)]
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct UiStackInfo {
|
||||
pub kind: Option<UiKind>,
|
||||
pub frame: Frame,
|
||||
pub tags: UiTags,
|
||||
}
|
||||
|
||||
impl UiStackInfo {
|
||||
/// Create a new [`UiStackInfo`] with the given kind and an empty frame.
|
||||
#[inline]
|
||||
pub fn new(kind: UiKind) -> Self {
|
||||
Self {
|
||||
kind: Some(kind),
|
||||
frame: Default::default(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn with_frame(mut self, frame: Frame) -> Self {
|
||||
self.frame = frame;
|
||||
self
|
||||
}
|
||||
|
||||
/// Insert a tag with no value.
|
||||
#[inline]
|
||||
pub fn with_tag(mut self, key: impl Into<String>) -> Self {
|
||||
self.tags.insert(key, None);
|
||||
self
|
||||
}
|
||||
|
||||
/// Insert a tag with some value.
|
||||
#[inline]
|
||||
pub fn with_tag_value(
|
||||
mut self,
|
||||
key: impl Into<String>,
|
||||
value: impl Any + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
self.tags.insert(key, Some(Arc::new(value)));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// User-chosen tags.
|
||||
///
|
||||
/// You can use this in any way you want,
|
||||
/// i.e. to set some tag on a [`crate::Ui`] and then in your own widget check
|
||||
/// for the existence of this tag up the [`UiStack`].
|
||||
///
|
||||
/// Note that egui never sets any tags itself, so this is purely for user code.
|
||||
///
|
||||
/// All tagging is transient, and will only live as long as the parent [`crate::Ui`], i.e. within a single render frame.
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct UiTags(pub ahash::HashMap<String, Option<Arc<dyn Any + Send + Sync + 'static>>>);
|
||||
|
||||
impl UiTags {
|
||||
#[inline]
|
||||
pub fn insert(
|
||||
&mut self,
|
||||
key: impl Into<String>,
|
||||
value: Option<Arc<dyn Any + Send + Sync + 'static>>,
|
||||
) {
|
||||
self.0.insert(key.into(), value);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn contains(&self, key: &str) -> bool {
|
||||
self.0.contains_key(key)
|
||||
}
|
||||
|
||||
/// Get the value of a tag.
|
||||
///
|
||||
/// Note that `None` is returned both if the key is set to the value `None`,
|
||||
/// and if the key is not set at all.
|
||||
#[inline]
|
||||
pub fn get_any(&self, key: &str) -> Option<&Arc<dyn Any + Send + Sync + 'static>> {
|
||||
self.0.get(key)?.as_ref()
|
||||
}
|
||||
|
||||
/// Get the value of a tag.
|
||||
///
|
||||
/// Note that `None` is returned both if the key is set to the value `None`,
|
||||
/// and if the key is not set at all.
|
||||
pub fn get_downcast<T: Any + Send + Sync + 'static>(&self, key: &str) -> Option<&T> {
|
||||
self.0.get(key)?.as_ref().and_then(|any| any.downcast_ref())
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
@ -96,12 +193,11 @@ impl UiStackInfo {
|
|||
/// 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)]
|
||||
#[derive(Debug)]
|
||||
pub struct UiStack {
|
||||
// stuff that `Ui::child_ui` can deal with directly
|
||||
pub id: Id,
|
||||
pub kind: Option<UiKind>,
|
||||
pub frame: Frame,
|
||||
pub info: UiStackInfo,
|
||||
pub layout_direction: Direction,
|
||||
pub min_rect: Rect,
|
||||
pub max_rect: Rect,
|
||||
|
|
@ -110,10 +206,26 @@ pub struct UiStack {
|
|||
|
||||
// these methods act on this specific node
|
||||
impl UiStack {
|
||||
#[inline]
|
||||
pub fn kind(&self) -> Option<UiKind> {
|
||||
self.info.kind
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn frame(&self) -> &Frame {
|
||||
&self.info.frame
|
||||
}
|
||||
|
||||
/// User tags.
|
||||
#[inline]
|
||||
pub fn tags(&self) -> &UiTags {
|
||||
&self.info.tags
|
||||
}
|
||||
|
||||
/// Is this [`crate::Ui`] a panel?
|
||||
#[inline]
|
||||
pub fn is_panel_ui(&self) -> bool {
|
||||
self.kind.map_or(false, |kind| kind.is_panel())
|
||||
self.kind().map_or(false, |kind| kind.is_panel())
|
||||
}
|
||||
|
||||
/// Is this a root [`crate::Ui`], i.e. created with [`crate::Ui::new()`]?
|
||||
|
|
@ -125,7 +237,7 @@ impl UiStack {
|
|||
/// This this [`crate::Ui`] a [`crate::Frame`] with a visible stroke?
|
||||
#[inline]
|
||||
pub fn has_visible_frame(&self) -> bool {
|
||||
!self.frame.stroke.is_empty()
|
||||
!self.info.frame.stroke.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -139,7 +251,7 @@ impl UiStack {
|
|||
|
||||
/// 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))
|
||||
self.iter().any(|frame| frame.kind() == Some(kind))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -547,7 +547,7 @@ fn ui_stack_demo(ui: &mut Ui) {
|
|||
});
|
||||
|
||||
row.col(|ui| {
|
||||
ui.label(if let Some(kind) = node.kind {
|
||||
ui.label(if let Some(kind) = node.kind() {
|
||||
format!("{kind:?}")
|
||||
} else {
|
||||
"-".to_owned()
|
||||
|
|
|
|||
|
|
@ -209,9 +209,9 @@ fn full_span_horizontal_range(ui_stack: &egui::UiStack) -> Rangef {
|
|||
if node.has_visible_frame()
|
||||
|| node.is_panel_ui()
|
||||
|| node.is_root_ui()
|
||||
|| node.kind == Some(UiKind::TableCell)
|
||||
|| node.kind() == Some(UiKind::TableCell)
|
||||
{
|
||||
return (node.max_rect + node.frame.inner_margin).x_range();
|
||||
return (node.max_rect + node.frame().inner_margin).x_range();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -280,7 +280,7 @@ fn stack_ui_impl(ui: &mut egui::Ui, stack: &egui::UiStack) {
|
|||
}
|
||||
});
|
||||
row.col(|ui| {
|
||||
let s = if let Some(kind) = node.kind {
|
||||
let s = if let Some(kind) = node.kind() {
|
||||
format!("{kind:?}")
|
||||
} else {
|
||||
"-".to_owned()
|
||||
|
|
@ -289,7 +289,8 @@ fn stack_ui_impl(ui: &mut egui::Ui, stack: &egui::UiStack) {
|
|||
ui.label(s);
|
||||
});
|
||||
row.col(|ui| {
|
||||
if node.frame.stroke == egui::Stroke::NONE {
|
||||
let frame = node.frame();
|
||||
if frame.stroke == egui::Stroke::NONE {
|
||||
ui.label("-");
|
||||
} else {
|
||||
let mut layout_job = egui::text::LayoutJob::default();
|
||||
|
|
@ -298,11 +299,11 @@ fn stack_ui_impl(ui: &mut egui::Ui, stack: &egui::UiStack) {
|
|||
0.0,
|
||||
egui::TextFormat::simple(
|
||||
egui::TextStyle::Body.resolve(ui.style()),
|
||||
node.frame.stroke.color,
|
||||
frame.stroke.color,
|
||||
),
|
||||
);
|
||||
layout_job.append(
|
||||
format!("{}px", node.frame.stroke.width).as_str(),
|
||||
format!("{}px", frame.stroke.width).as_str(),
|
||||
0.0,
|
||||
egui::TextFormat::simple(
|
||||
egui::TextStyle::Body.resolve(ui.style()),
|
||||
|
|
@ -314,10 +315,10 @@ fn stack_ui_impl(ui: &mut egui::Ui, stack: &egui::UiStack) {
|
|||
}
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.label(print_margin(&node.frame.inner_margin));
|
||||
ui.label(print_margin(&node.frame().inner_margin));
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.label(print_margin(&node.frame.outer_margin));
|
||||
ui.label(print_margin(&node.frame().outer_margin));
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.label(format!("{:?}", node.layout_direction));
|
||||
|
|
|
|||
Loading…
Reference in New Issue