mirror of
https://github.com/emilk/egui.git
synced 2026-09-03 07:10:04 -04:00
Add AsId and IdSalt (#8184)
## Related * https://github.com/emilk/egui/pull/5851 * https://github.com/emilk/egui/pull/7988 ## What We want to make it easier to understand the lineage of a `Id` (which is the parent `Id`, and the parent of that, etc?) As a first step of that, we want to clarify the different between a globally unique `Id`, and an `IdSalt` I also introduced the `AsId` and `AsIdSalt` traits, which are implemented of anything that implements `Hash` and `Debug`. The `Debug` half of that is unused here, but will later be used in `Debug` builds to produce a proper tree.
This commit is contained in:
@@ -1,9 +1,7 @@
|
|||||||
use std::hash::Hash;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Context, Id, InnerResponse, NumExt as _, Rect, Response, Sense, Stroke, TextStyle,
|
AsIdSalt, Context, Id, IdSalt, InnerResponse, NumExt as _, Rect, Response, Sense, Stroke,
|
||||||
TextWrapMode, Ui, UiBuilder, UiKind, UiStackInfo, WidgetInfo, WidgetText, WidgetType, emath,
|
TextStyle, TextWrapMode, Ui, UiBuilder, UiKind, UiStackInfo, WidgetInfo, WidgetText,
|
||||||
epaint, pos2, remap, remap_clamp, vec2,
|
WidgetType, emath, epaint, pos2, remap, remap_clamp, vec2,
|
||||||
};
|
};
|
||||||
use emath::GuiRounding as _;
|
use emath::GuiRounding as _;
|
||||||
use epaint::{Shape, StrokeKind};
|
use epaint::{Shape, StrokeKind};
|
||||||
@@ -371,7 +369,7 @@ pub struct CollapsingHeader {
|
|||||||
text: WidgetText,
|
text: WidgetText,
|
||||||
default_open: bool,
|
default_open: bool,
|
||||||
open: Option<bool>,
|
open: Option<bool>,
|
||||||
id_salt: Id,
|
id_salt: IdSalt,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
selectable: bool,
|
selectable: bool,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
@@ -388,7 +386,7 @@ impl CollapsingHeader {
|
|||||||
/// you need to provide a unique id source with [`Self::id_salt`].
|
/// you need to provide a unique id source with [`Self::id_salt`].
|
||||||
pub fn new(text: impl Into<WidgetText>) -> Self {
|
pub fn new(text: impl Into<WidgetText>) -> Self {
|
||||||
let text = text.into();
|
let text = text.into();
|
||||||
let id_salt = Id::new(text.text());
|
let id_salt = IdSalt::new(text.text());
|
||||||
Self {
|
Self {
|
||||||
text,
|
text,
|
||||||
default_open: false,
|
default_open: false,
|
||||||
@@ -424,8 +422,8 @@ impl CollapsingHeader {
|
|||||||
/// Explicitly set the source of the [`Id`] of this widget, instead of using title label.
|
/// Explicitly set the source of the [`Id`] of this widget, instead of using title label.
|
||||||
/// This is useful if the title label is dynamic or not unique.
|
/// This is useful if the title label is dynamic or not unique.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn id_salt(mut self, id_salt: impl Hash) -> Self {
|
pub fn id_salt(mut self, id_salt: impl AsIdSalt) -> Self {
|
||||||
self.id_salt = Id::new(id_salt);
|
self.id_salt = IdSalt::new(id_salt);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
use epaint::Shape;
|
use epaint::Shape;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Align2, Context, Id, InnerResponse, NumExt as _, Painter, Popup, PopupCloseBehavior, Rect,
|
Align2, AsIdSalt, Context, Id, IdSalt, InnerResponse, NumExt as _, Painter, Popup,
|
||||||
Response, ScrollArea, Sense, Stroke, TextStyle, TextWrapMode, Ui, UiBuilder, Vec2, WidgetInfo,
|
PopupCloseBehavior, Rect, Response, ScrollArea, Sense, Stroke, TextStyle, TextWrapMode, Ui,
|
||||||
WidgetText, WidgetType, epaint, style::StyleModifier, style::WidgetVisuals, vec2,
|
UiBuilder, Vec2, WidgetInfo, WidgetText, WidgetType, epaint,
|
||||||
|
style::{StyleModifier, WidgetVisuals},
|
||||||
|
vec2,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[expect(unused_imports)] // Documentation
|
#[expect(unused_imports)] // Documentation
|
||||||
@@ -36,7 +38,7 @@ pub type IconPainter = Box<dyn FnOnce(&Ui, Rect, &WidgetVisuals, bool)>;
|
|||||||
/// ```
|
/// ```
|
||||||
#[must_use = "You should call .show*"]
|
#[must_use = "You should call .show*"]
|
||||||
pub struct ComboBox {
|
pub struct ComboBox {
|
||||||
id_salt: Id,
|
id_salt: IdSalt,
|
||||||
label: Option<WidgetText>,
|
label: Option<WidgetText>,
|
||||||
selected_text: WidgetText,
|
selected_text: WidgetText,
|
||||||
width: Option<f32>,
|
width: Option<f32>,
|
||||||
@@ -49,9 +51,9 @@ pub struct ComboBox {
|
|||||||
|
|
||||||
impl ComboBox {
|
impl ComboBox {
|
||||||
/// Create new [`ComboBox`] with id and label
|
/// Create new [`ComboBox`] with id and label
|
||||||
pub fn new(id_salt: impl std::hash::Hash, label: impl Into<WidgetText>) -> Self {
|
pub fn new(id_salt: impl AsIdSalt, label: impl Into<WidgetText>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id_salt: Id::new(id_salt),
|
id_salt: IdSalt::new(id_salt),
|
||||||
label: Some(label.into()),
|
label: Some(label.into()),
|
||||||
selected_text: Default::default(),
|
selected_text: Default::default(),
|
||||||
width: None,
|
width: None,
|
||||||
@@ -67,7 +69,7 @@ impl ComboBox {
|
|||||||
pub fn from_label(label: impl Into<WidgetText>) -> Self {
|
pub fn from_label(label: impl Into<WidgetText>) -> Self {
|
||||||
let label = label.into();
|
let label = label.into();
|
||||||
Self {
|
Self {
|
||||||
id_salt: Id::new(label.text()),
|
id_salt: IdSalt::new(label.text()),
|
||||||
label: Some(label),
|
label: Some(label),
|
||||||
selected_text: Default::default(),
|
selected_text: Default::default(),
|
||||||
width: None,
|
width: None,
|
||||||
@@ -80,9 +82,9 @@ impl ComboBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Without label.
|
/// Without label.
|
||||||
pub fn from_id_salt(id_salt: impl std::hash::Hash) -> Self {
|
pub fn from_id_salt(id_salt: impl AsIdSalt) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id_salt: Id::new(id_salt),
|
id_salt: IdSalt::new(id_salt),
|
||||||
label: Default::default(),
|
label: Default::default(),
|
||||||
selected_text: Default::default(),
|
selected_text: Default::default(),
|
||||||
width: None,
|
width: None,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
Align2, Color32, Context, CursorIcon, Id, NumExt as _, Rect, Response, Sense, Shape, Ui,
|
Align2, AsIdSalt, Color32, Context, CursorIcon, Id, IdSalt, NumExt as _, Rect, Response, Sense,
|
||||||
UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b, pos2, vec2,
|
Shape, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b, pos2, vec2,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
@@ -41,7 +41,7 @@ impl State {
|
|||||||
#[must_use = "You should call .show()"]
|
#[must_use = "You should call .show()"]
|
||||||
pub struct Resize {
|
pub struct Resize {
|
||||||
id: Option<Id>,
|
id: Option<Id>,
|
||||||
id_salt: Option<Id>,
|
id_salt: Option<IdSalt>,
|
||||||
|
|
||||||
/// If false, we are no enabled
|
/// If false, we are no enabled
|
||||||
resizable: Vec2b,
|
resizable: Vec2b,
|
||||||
@@ -78,8 +78,8 @@ impl Resize {
|
|||||||
|
|
||||||
/// A source for the unique [`Id`], e.g. `.id_salt("second_resize_area")` or `.id_salt(loop_index)`.
|
/// A source for the unique [`Id`], e.g. `.id_salt("second_resize_area")` or `.id_salt(loop_index)`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn id_salt(mut self, id_salt: impl std::hash::Hash) -> Self {
|
pub fn id_salt(mut self, id_salt: impl AsIdSalt) -> Self {
|
||||||
self.id_salt = Some(Id::new(id_salt));
|
self.id_salt = Some(IdSalt::new(id_salt));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,7 +209,7 @@ impl Resize {
|
|||||||
fn begin(&self, ui: &mut Ui) -> Prepared {
|
fn begin(&self, ui: &mut Ui) -> Prepared {
|
||||||
let position = ui.available_rect_before_wrap().min;
|
let position = ui.available_rect_before_wrap().min;
|
||||||
let id = self.id.unwrap_or_else(|| {
|
let id = self.id.unwrap_or_else(|| {
|
||||||
let id_salt = self.id_salt.unwrap_or_else(|| Id::new("resize"));
|
let id_salt = self.id_salt.unwrap_or_else(|| IdSalt::new("resize"));
|
||||||
ui.make_persistent_id(id_salt)
|
ui.make_persistent_id(id_salt)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ use emath::GuiRounding as _;
|
|||||||
use epaint::{Color32, Direction, Margin, Shape};
|
use epaint::{Color32, Direction, Margin, Shape};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Context, CursorIcon, Id, NumExt as _, Pos2, Rangef, Rect, Response, Sense, Ui, UiBuilder,
|
AsIdSalt, Context, CursorIcon, Id, IdSalt, NumExt as _, Pos2, Rangef, Rect, Response, Sense,
|
||||||
UiKind, UiStackInfo, Vec2, Vec2b, WidgetInfo, emath, epaint, lerp, pass_state, pos2, remap,
|
Ui, UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b, WidgetInfo, emath, epaint, lerp, pass_state,
|
||||||
remap_clamp,
|
pos2, remap, remap_clamp,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
@@ -344,7 +344,7 @@ pub struct ScrollArea {
|
|||||||
min_scrolled_size: Vec2,
|
min_scrolled_size: Vec2,
|
||||||
scroll_bar_visibility: ScrollBarVisibility,
|
scroll_bar_visibility: ScrollBarVisibility,
|
||||||
scroll_bar_rect: Option<Rect>,
|
scroll_bar_rect: Option<Rect>,
|
||||||
id_salt: Option<Id>,
|
id_salt: Option<IdSalt>,
|
||||||
offset_x: Option<f32>,
|
offset_x: Option<f32>,
|
||||||
offset_y: Option<f32>,
|
offset_y: Option<f32>,
|
||||||
on_hover_cursor: Option<CursorIcon>,
|
on_hover_cursor: Option<CursorIcon>,
|
||||||
@@ -479,8 +479,8 @@ impl ScrollArea {
|
|||||||
|
|
||||||
/// A source for the unique [`Id`], e.g. `.id_salt("second_scroll_area")` or `.id_salt(loop_index)`.
|
/// A source for the unique [`Id`], e.g. `.id_salt("second_scroll_area")` or `.id_salt(loop_index)`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn id_salt(mut self, id_salt: impl std::hash::Hash) -> Self {
|
pub fn id_salt(mut self, id_salt: impl AsIdSalt) -> Self {
|
||||||
self.id_salt = Some(Id::new(id_salt));
|
self.id_salt = Some(IdSalt::new(id_salt));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -730,7 +730,7 @@ impl ScrollArea {
|
|||||||
|
|
||||||
let ctx = ui.ctx().clone();
|
let ctx = ui.ctx().clone();
|
||||||
|
|
||||||
let id_salt = id_salt.unwrap_or_else(|| Id::new("scroll_area"));
|
let id_salt = id_salt.unwrap_or_else(|| IdSalt::new("scroll_area"));
|
||||||
let id = ui.make_persistent_id(id_salt);
|
let id = ui.make_persistent_id(id_salt);
|
||||||
ctx.check_for_id_clash(
|
ctx.check_for_id_clash(
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ use std::sync::Arc;
|
|||||||
use emath::GuiRounding as _;
|
use emath::GuiRounding as _;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Align2, Color32, Context, Id, InnerResponse, NumExt as _, Painter, Rect, Region, Style, Ui,
|
Align2, AsIdSalt, Color32, Context, Id, IdSalt, InnerResponse, NumExt as _, Painter, Rect,
|
||||||
UiBuilder, Vec2, vec2,
|
Region, Style, Ui, UiBuilder, Vec2, vec2,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
@@ -312,7 +312,7 @@ impl GridLayout {
|
|||||||
/// ```
|
/// ```
|
||||||
#[must_use = "You should call .show()"]
|
#[must_use = "You should call .show()"]
|
||||||
pub struct Grid {
|
pub struct Grid {
|
||||||
id_salt: Id,
|
id_salt: IdSalt,
|
||||||
num_columns: Option<usize>,
|
num_columns: Option<usize>,
|
||||||
min_col_width: Option<f32>,
|
min_col_width: Option<f32>,
|
||||||
min_row_height: Option<f32>,
|
min_row_height: Option<f32>,
|
||||||
@@ -324,9 +324,9 @@ pub struct Grid {
|
|||||||
|
|
||||||
impl Grid {
|
impl Grid {
|
||||||
/// Create a new [`Grid`] with a locally unique identifier.
|
/// Create a new [`Grid`] with a locally unique identifier.
|
||||||
pub fn new(id_salt: impl std::hash::Hash) -> Self {
|
pub fn new(id_salt: impl AsIdSalt) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id_salt: Id::new(id_salt),
|
id_salt: IdSalt::new(id_salt),
|
||||||
num_columns: None,
|
num_columns: None,
|
||||||
min_col_width: None,
|
min_col_width: None,
|
||||||
min_row_height: None,
|
min_row_height: None,
|
||||||
|
|||||||
@@ -2,6 +2,16 @@
|
|||||||
|
|
||||||
use std::num::NonZeroU64;
|
use std::num::NonZeroU64;
|
||||||
|
|
||||||
|
use crate::{AsIdSalt, IdSalt};
|
||||||
|
|
||||||
|
/// Types that can be converted to an [`Id`].
|
||||||
|
///
|
||||||
|
/// This is all types implementing `Hash` and `Debug`,
|
||||||
|
/// which includes things like string, integers, tuples of those, etc.
|
||||||
|
pub trait AsId: std::hash::Hash + std::fmt::Debug {}
|
||||||
|
|
||||||
|
impl<T: std::hash::Hash + std::fmt::Debug> AsId for T {}
|
||||||
|
|
||||||
/// egui tracks widgets frame-to-frame using [`Id`]s.
|
/// egui tracks widgets frame-to-frame using [`Id`]s.
|
||||||
///
|
///
|
||||||
/// For instance, if you start dragging a slider one frame, egui stores
|
/// For instance, if you start dragging a slider one frame, egui stores
|
||||||
@@ -43,6 +53,7 @@ impl Id {
|
|||||||
/// though obviously it will lead to a lot of collisions if you do use it!
|
/// though obviously it will lead to a lot of collisions if you do use it!
|
||||||
pub const NULL: Self = Self(NonZeroU64::MAX);
|
pub const NULL: Self = Self(NonZeroU64::MAX);
|
||||||
|
|
||||||
|
/// Create a new root [`Id`] from a high-entropy hash.
|
||||||
#[inline]
|
#[inline]
|
||||||
const fn from_hash(hash: u64) -> Self {
|
const fn from_hash(hash: u64) -> Self {
|
||||||
if let Some(nonzero) = NonZeroU64::new(hash) {
|
if let Some(nonzero) = NonZeroU64::new(hash) {
|
||||||
@@ -52,17 +63,17 @@ impl Id {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a new [`Id`] by hashing some source (e.g. a string or integer).
|
/// Generate a new root [`Id`] by hashing some source (e.g. a string or integer).
|
||||||
pub fn new(source: impl std::hash::Hash) -> Self {
|
pub fn new(source: impl AsId) -> Self {
|
||||||
Self::from_hash(ahash::RandomState::with_seeds(1, 2, 3, 4).hash_one(source))
|
Self::from_hash(ahash::RandomState::with_seeds(1, 2, 3, 4).hash_one(source))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a new [`Id`] by hashing the parent [`Id`] and the given argument.
|
/// Generate a child [`Id`] by salting the parent [`Id`] with the given argument.
|
||||||
pub fn with(self, child: impl std::hash::Hash) -> Self {
|
pub fn with(self, salt: impl AsIdSalt) -> Self {
|
||||||
use std::hash::{BuildHasher as _, Hasher as _};
|
use std::hash::{BuildHasher as _, Hasher as _};
|
||||||
let mut hasher = ahash::RandomState::with_seeds(1, 2, 3, 4).build_hasher();
|
let mut hasher = ahash::RandomState::with_seeds(1, 2, 3, 4).build_hasher();
|
||||||
hasher.write_u64(self.0.get());
|
hasher.write_u64(self.value());
|
||||||
child.hash(&mut hasher);
|
hasher.write_u64(IdSalt::new(salt).value());
|
||||||
Self::from_hash(hasher.finish())
|
Self::from_hash(hasher.finish())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
59
crates/egui/src/id_salt.rs
Normal file
59
crates/egui/src/id_salt.rs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
use std::num::NonZeroU64;
|
||||||
|
|
||||||
|
/// Types that can be converted to an [`IdSalt`].
|
||||||
|
///
|
||||||
|
/// This is all types implementing `Hash` and `Debug`,
|
||||||
|
/// which includes things like string, integers, tuples of those, etc.
|
||||||
|
pub trait AsIdSalt: std::hash::Hash + std::fmt::Debug {}
|
||||||
|
|
||||||
|
impl<T: std::hash::Hash + std::fmt::Debug> AsIdSalt for T {}
|
||||||
|
|
||||||
|
/// Uniquely identifies a child widget within a parent widget.
|
||||||
|
///
|
||||||
|
/// An [`IdSalt`] is only unique within a parent [`crate::Id`].
|
||||||
|
/// An [`IdSalt`] is NOT globally unique.
|
||||||
|
///
|
||||||
|
/// You combine a parent [`crate::Id`] with an [`IdSalt`] to get a child [`crate::Id`],
|
||||||
|
/// using [`crate::Id::with`].
|
||||||
|
///
|
||||||
|
/// An [`IdSalt`] is usually a string, an integer, or similar.
|
||||||
|
///
|
||||||
|
/// An [`IdSalt`] should NOT be produced from an [`crate::Id`].
|
||||||
|
///
|
||||||
|
/// This is niche-optimized to that `Option<IdSalt>` is the same size as `IdSalt`.
|
||||||
|
#[derive(Clone, Copy, Hash, Eq, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
pub struct IdSalt(NonZeroU64);
|
||||||
|
|
||||||
|
impl nohash_hasher::IsEnabled for IdSalt {}
|
||||||
|
|
||||||
|
impl IdSalt {
|
||||||
|
/// Create a new [`IdSalt`] by hashing some source (e.g. a string or integer).
|
||||||
|
pub fn new(source: impl AsIdSalt) -> Self {
|
||||||
|
Self::from_hash(ahash::RandomState::with_seeds(1, 2, 3, 4).hash_one(source))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new root [`IdSalt`] from a high-entropy hash.
|
||||||
|
#[inline]
|
||||||
|
const fn from_hash(hash: u64) -> Self {
|
||||||
|
if let Some(nonzero) = NonZeroU64::new(hash) {
|
||||||
|
Self(nonzero)
|
||||||
|
} else {
|
||||||
|
Self(NonZeroU64::MIN) // The hash was exactly zero
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The inner value of the [`IdSalt`].
|
||||||
|
///
|
||||||
|
/// This is a high-entropy hash.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn value(&self) -> u64 {
|
||||||
|
self.0.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for IdSalt {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "salt_{:04X}", self.value() as u16)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -400,6 +400,7 @@ pub(crate) mod grid;
|
|||||||
pub mod gui_zoom;
|
pub mod gui_zoom;
|
||||||
mod hit_test;
|
mod hit_test;
|
||||||
mod id;
|
mod id;
|
||||||
|
mod id_salt;
|
||||||
mod input_state;
|
mod input_state;
|
||||||
mod interaction;
|
mod interaction;
|
||||||
pub mod introspection;
|
pub mod introspection;
|
||||||
@@ -473,7 +474,8 @@ pub use self::{
|
|||||||
drag_and_drop::DragAndDrop,
|
drag_and_drop::DragAndDrop,
|
||||||
epaint::text::TextWrapMode,
|
epaint::text::TextWrapMode,
|
||||||
grid::Grid,
|
grid::Grid,
|
||||||
id::{Id, IdMap, IdSet},
|
id::{AsId, Id, IdMap, IdSet},
|
||||||
|
id_salt::{AsIdSalt, IdSalt},
|
||||||
input_state::{InputOptions, InputState, MultiTouchInfo, PointerState, SurrenderFocusOn},
|
input_state::{InputOptions, InputState, MultiTouchInfo, PointerState, SurrenderFocusOn},
|
||||||
layers::{LayerId, Order},
|
layers::{LayerId, Order},
|
||||||
layout::*,
|
layout::*,
|
||||||
@@ -486,7 +488,7 @@ pub use self::{
|
|||||||
style::{FontSelection, Spacing, Style, TextStyle, Visuals},
|
style::{FontSelection, Spacing, Style, TextStyle, Visuals},
|
||||||
text::{Galley, TextFormat},
|
text::{Galley, TextFormat},
|
||||||
ui::Ui,
|
ui::Ui,
|
||||||
ui_builder::UiBuilder,
|
ui_builder::{IdSource, UiBuilder},
|
||||||
ui_stack::*,
|
ui_stack::*,
|
||||||
viewport::*,
|
viewport::*,
|
||||||
widget_rect::{InteractOptions, WidgetRect, WidgetRects},
|
widget_rect::{InteractOptions, WidgetRect, WidgetRects},
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
#![warn(missing_docs)] // Let's keep `Ui` well-documented.
|
#![warn(missing_docs)] // Let's keep `Ui` well-documented.
|
||||||
#![expect(clippy::use_self)]
|
#![expect(clippy::use_self)]
|
||||||
|
|
||||||
use std::{any::Any, hash::Hash, ops::Deref, sync::Arc};
|
use std::{any::Any, ops::Deref, sync::Arc};
|
||||||
|
|
||||||
use crate::containers::menu;
|
use crate::containers::menu;
|
||||||
use crate::widget_style::{HasClasses as _, ROOT_CLASS};
|
use crate::widget_style::{HasClasses as _, ROOT_CLASS};
|
||||||
use crate::{containers::*, ecolor::*, layout::*, placer::Placer, widgets::*, *};
|
use crate::{IdSource, containers::*, ecolor::*, layout::*, placer::Placer, widgets::*, *};
|
||||||
use emath::GuiRounding as _;
|
use emath::GuiRounding as _;
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/// This is what you use to place widgets.
|
/// This is what you use to place widgets.
|
||||||
@@ -106,8 +107,7 @@ impl Ui {
|
|||||||
/// [`crate::Panel`], [`crate::CentralPanel`], [`crate::Window`] or [`crate::Area`].
|
/// [`crate::Panel`], [`crate::CentralPanel`], [`crate::Window`] or [`crate::Area`].
|
||||||
pub fn new(ctx: Context, id: Id, ui_builder: UiBuilder) -> Self {
|
pub fn new(ctx: Context, id: Id, ui_builder: UiBuilder) -> Self {
|
||||||
let UiBuilder {
|
let UiBuilder {
|
||||||
id_salt,
|
id_source,
|
||||||
global_scope: _,
|
|
||||||
ui_stack_info,
|
ui_stack_info,
|
||||||
layer_id,
|
layer_id,
|
||||||
max_rect,
|
max_rect,
|
||||||
@@ -124,8 +124,8 @@ impl Ui {
|
|||||||
let layer_id = layer_id.unwrap_or_else(LayerId::background);
|
let layer_id = layer_id.unwrap_or_else(LayerId::background);
|
||||||
|
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
id_salt.is_none(),
|
id_source.is_none(),
|
||||||
"Top-level Ui:s should not have an id_salt"
|
"Top-level Ui:s should not have an UiBuilder::id_source"
|
||||||
);
|
);
|
||||||
|
|
||||||
let max_rect = max_rect.unwrap_or_else(|| ctx.content_rect());
|
let max_rect = max_rect.unwrap_or_else(|| ctx.content_rect());
|
||||||
@@ -207,8 +207,7 @@ impl Ui {
|
|||||||
/// [`Ui::advance_cursor_after_rect`].
|
/// [`Ui::advance_cursor_after_rect`].
|
||||||
pub fn new_child(&mut self, ui_builder: UiBuilder) -> Self {
|
pub fn new_child(&mut self, ui_builder: UiBuilder) -> Self {
|
||||||
let UiBuilder {
|
let UiBuilder {
|
||||||
id_salt,
|
id_source,
|
||||||
global_scope,
|
|
||||||
ui_stack_info,
|
ui_stack_info,
|
||||||
layer_id,
|
layer_id,
|
||||||
max_rect,
|
max_rect,
|
||||||
@@ -224,7 +223,6 @@ impl Ui {
|
|||||||
|
|
||||||
let mut painter = self.painter.clone();
|
let mut painter = self.painter.clone();
|
||||||
|
|
||||||
let id_salt = id_salt.unwrap_or_else(|| Id::from("child"));
|
|
||||||
let max_rect = max_rect.unwrap_or_else(|| self.available_rect_before_wrap());
|
let max_rect = max_rect.unwrap_or_else(|| self.available_rect_before_wrap());
|
||||||
let mut layout = layout.unwrap_or_else(|| *self.layout());
|
let mut layout = layout.unwrap_or_else(|| *self.layout());
|
||||||
let enabled = self.enabled && !disabled && !invisible;
|
let enabled = self.enabled && !disabled && !invisible;
|
||||||
@@ -248,13 +246,15 @@ impl Ui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
debug_assert!(!max_rect.any_nan(), "max_rect is NaN: {max_rect:?}");
|
debug_assert!(!max_rect.any_nan(), "max_rect is NaN: {max_rect:?}");
|
||||||
let (stable_id, unique_id) = if global_scope {
|
|
||||||
(id_salt, id_salt)
|
|
||||||
} else {
|
|
||||||
let stable_id = self.id.with(id_salt);
|
|
||||||
let unique_id = stable_id.with(self.next_auto_id_salt);
|
|
||||||
|
|
||||||
(stable_id, unique_id)
|
let id_source = id_source.unwrap_or_else(|| IdSource::Child(IdSalt::new("child")));
|
||||||
|
let (stable_id, unique_id) = match id_source {
|
||||||
|
IdSource::Explicit(id) => (id, id),
|
||||||
|
IdSource::Child(id_salt) => {
|
||||||
|
let stable_id = self.id.with(id_salt);
|
||||||
|
let unique_id = stable_id.with(self.next_auto_id_salt);
|
||||||
|
(stable_id, unique_id)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let next_auto_id_salt = unique_id.value().wrapping_add(1);
|
let next_auto_id_salt = unique_id.value().wrapping_add(1);
|
||||||
|
|
||||||
@@ -880,11 +880,8 @@ impl Ui {
|
|||||||
/// # [`Id`] creation
|
/// # [`Id`] creation
|
||||||
impl Ui {
|
impl Ui {
|
||||||
/// Use this to generate widget ids for widgets that have persistent state in [`Memory`].
|
/// Use this to generate widget ids for widgets that have persistent state in [`Memory`].
|
||||||
pub fn make_persistent_id<IdSource>(&self, id_salt: IdSource) -> Id
|
pub fn make_persistent_id(&self, id_salt: impl AsIdSalt) -> Id {
|
||||||
where
|
self.id.with(id_salt)
|
||||||
IdSource: Hash,
|
|
||||||
{
|
|
||||||
self.id.with(&id_salt)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This is the `Id` that will be assigned to the next widget added to this `Ui`.
|
/// This is the `Id` that will be assigned to the next widget added to this `Ui`.
|
||||||
@@ -893,10 +890,7 @@ impl Ui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Same as `ui.next_auto_id().with(id_salt)`
|
/// Same as `ui.next_auto_id().with(id_salt)`
|
||||||
pub fn auto_id_with<IdSource>(&self, id_salt: IdSource) -> Id
|
pub fn auto_id_with(&self, id_salt: impl AsIdSalt) -> Id {
|
||||||
where
|
|
||||||
IdSource: Hash,
|
|
||||||
{
|
|
||||||
Id::new(self.next_auto_id_salt).with(id_salt)
|
Id::new(self.next_auto_id_salt).with(id_salt)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2168,7 +2162,7 @@ impl Ui {
|
|||||||
/// ```
|
/// ```
|
||||||
pub fn push_id<R>(
|
pub fn push_id<R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
id_salt: impl Hash,
|
id_salt: impl AsIdSalt,
|
||||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||||
) -> InnerResponse<R> {
|
) -> InnerResponse<R> {
|
||||||
self.scope_dyn(UiBuilder::new().id_salt(id_salt), Box::new(add_contents))
|
self.scope_dyn(UiBuilder::new().id_salt(id_salt), Box::new(add_contents))
|
||||||
@@ -2233,7 +2227,7 @@ impl Ui {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn indent<R>(
|
pub fn indent<R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
id_salt: impl Hash,
|
id_salt: impl AsIdSalt,
|
||||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||||
) -> InnerResponse<R> {
|
) -> InnerResponse<R> {
|
||||||
self.indent_dyn(id_salt, Box::new(add_contents))
|
self.indent_dyn(id_salt, Box::new(add_contents))
|
||||||
@@ -2241,7 +2235,7 @@ impl Ui {
|
|||||||
|
|
||||||
fn indent_dyn<'c, R>(
|
fn indent_dyn<'c, R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
id_salt: impl Hash,
|
id_salt: impl AsIdSalt,
|
||||||
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||||
) -> InnerResponse<R> {
|
) -> InnerResponse<R> {
|
||||||
assert!(
|
assert!(
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
use std::{hash::Hash, sync::Arc};
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[expect(unused_imports)] // Used for doclinks
|
#[expect(unused_imports)] // Used for doclinks
|
||||||
use crate::Ui;
|
use crate::Ui;
|
||||||
use crate::widget_style::HasClasses;
|
use crate::{
|
||||||
use crate::{ClosableTag, widget_style::Classes};
|
AsIdSalt, ClosableTag, Id, IdSalt, LayerId, Layout, Rect, Sense, Style, UiStackInfo,
|
||||||
use crate::{Id, LayerId, Layout, Rect, Sense, Style, UiStackInfo};
|
widget_style::{Classes, HasClasses},
|
||||||
|
};
|
||||||
|
|
||||||
/// Build a [`Ui`] as the child of another [`Ui`].
|
/// Build a [`Ui`] as the child of another [`Ui`].
|
||||||
///
|
///
|
||||||
@@ -14,8 +15,7 @@ use crate::{Id, LayerId, Layout, Rect, Sense, Style, UiStackInfo};
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct UiBuilder {
|
pub struct UiBuilder {
|
||||||
pub id_salt: Option<Id>,
|
pub id_source: Option<IdSource>,
|
||||||
pub global_scope: bool,
|
|
||||||
pub ui_stack_info: UiStackInfo,
|
pub ui_stack_info: UiStackInfo,
|
||||||
pub layer_id: Option<LayerId>,
|
pub layer_id: Option<LayerId>,
|
||||||
pub max_rect: Option<Rect>,
|
pub max_rect: Option<Rect>,
|
||||||
@@ -29,6 +29,16 @@ pub struct UiBuilder {
|
|||||||
pub classes: Classes,
|
pub classes: Classes,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Is this [`Ui`] a root or a child of another [`Ui`]?
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum IdSource {
|
||||||
|
/// Explicitly use this [`Id`]
|
||||||
|
Explicit(Id),
|
||||||
|
|
||||||
|
/// Salt the parent [`Id`] with this.
|
||||||
|
Child(IdSalt),
|
||||||
|
}
|
||||||
|
|
||||||
impl UiBuilder {
|
impl UiBuilder {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
@@ -41,8 +51,8 @@ impl UiBuilder {
|
|||||||
/// You should give each [`Ui`] an `id_salt` that is unique
|
/// You should give each [`Ui`] an `id_salt` that is unique
|
||||||
/// within the parent, or give it none at all.
|
/// within the parent, or give it none at all.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn id_salt(mut self, id_salt: impl Hash) -> Self {
|
pub fn id_salt(mut self, id_salt: impl AsIdSalt) -> Self {
|
||||||
self.id_salt = Some(Id::new(id_salt));
|
self.id_source = Some(IdSource::Child(IdSalt::new(id_salt)));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,20 +67,7 @@ impl UiBuilder {
|
|||||||
/// This is a shortcut for `.id_salt(my_id).global_scope(true)`.
|
/// This is a shortcut for `.id_salt(my_id).global_scope(true)`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn id(mut self, id: Id) -> Self {
|
pub fn id(mut self, id: Id) -> Self {
|
||||||
self.id_salt = Some(id);
|
self.id_source = Some(IdSource::Explicit(id));
|
||||||
self.global_scope = true;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Make the new `Ui` child ids independent of the parent `Ui`.
|
|
||||||
/// This way child widgets can be moved in the ui tree without losing state.
|
|
||||||
/// You have to ensure that in a frame the child widgets do not get rendered in multiple places.
|
|
||||||
///
|
|
||||||
/// You should set the same globally unique `id_salt` at every place in the ui tree where you want the
|
|
||||||
/// child widgets to share state.
|
|
||||||
#[inline]
|
|
||||||
pub fn global_scope(mut self, global_scope: bool) -> Self {
|
|
||||||
self.global_scope = global_scope;
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use epaint::{Pos2, Vec2};
|
use epaint::{Pos2, Vec2};
|
||||||
|
|
||||||
use crate::{Context, Id, Ui};
|
use crate::{AsId, Context, Id, Ui};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@ impl ViewportId {
|
|||||||
pub const ROOT: Self = Self(Id::NULL);
|
pub const ROOT: Self = Self(Id::NULL);
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn from_hash_of(source: impl std::hash::Hash) -> Self {
|
pub fn from_hash_of(source: impl AsId) -> Self {
|
||||||
Self(Id::new(source))
|
Self(Id::new(source))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ use emath::{Rect, TSTransform};
|
|||||||
use epaint::text::{Galley, LayoutJob, TextWrapMode, cursor::CCursor};
|
use epaint::text::{Galley, LayoutJob, TextWrapMode, cursor::CCursor};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Align, Align2, AtomExt as _, AtomKind, AtomLayout, Atoms, Color32, Context, CursorIcon, Event,
|
Align, Align2, AsIdSalt, AtomExt as _, AtomKind, AtomLayout, Atoms, Color32, Context,
|
||||||
EventFilter, FontSelection, Frame, Id, ImeEvent, IntoAtoms, IntoSizedResult, Key,
|
CursorIcon, Event, EventFilter, FontSelection, Frame, Id, IdSalt, ImeEvent, IntoAtoms,
|
||||||
KeyboardShortcut, Margin, Modifiers, NumExt as _, Response, Sense, SizedAtomKind, TextBuffer,
|
IntoSizedResult, Key, KeyboardShortcut, Margin, Modifiers, NumExt as _, Response, Sense,
|
||||||
TextStyle, Ui, Vec2, Widget, WidgetInfo, WidgetWithState, epaint,
|
SizedAtomKind, TextBuffer, TextStyle, Ui, Vec2, Widget, WidgetInfo, WidgetWithState, epaint,
|
||||||
os::OperatingSystem,
|
os::OperatingSystem,
|
||||||
output::OutputEvent,
|
output::OutputEvent,
|
||||||
response,
|
response,
|
||||||
@@ -72,7 +72,7 @@ pub struct TextEdit<'t> {
|
|||||||
suffix: Atoms<'static>,
|
suffix: Atoms<'static>,
|
||||||
hint_text: Atoms<'static>,
|
hint_text: Atoms<'static>,
|
||||||
id: Option<Id>,
|
id: Option<Id>,
|
||||||
id_salt: Option<Id>,
|
id_salt: Option<IdSalt>,
|
||||||
font_selection: FontSelection,
|
font_selection: FontSelection,
|
||||||
text_color: Option<Color32>,
|
text_color: Option<Color32>,
|
||||||
layouter: Option<LayouterFn<'t>>,
|
layouter: Option<LayouterFn<'t>>,
|
||||||
@@ -171,14 +171,14 @@ impl<'t> TextEdit<'t> {
|
|||||||
|
|
||||||
/// A source for the unique [`Id`], e.g. `.id_source("second_text_edit_field")` or `.id_source(loop_index)`.
|
/// A source for the unique [`Id`], e.g. `.id_source("second_text_edit_field")` or `.id_source(loop_index)`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn id_source(self, id_salt: impl std::hash::Hash) -> Self {
|
pub fn id_source(self, id_salt: impl AsIdSalt) -> Self {
|
||||||
self.id_salt(id_salt)
|
self.id_salt(id_salt)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A source for the unique [`Id`], e.g. `.id_salt("second_text_edit_field")` or `.id_salt(loop_index)`.
|
/// A source for the unique [`Id`], e.g. `.id_salt("second_text_edit_field")` or `.id_salt(loop_index)`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn id_salt(mut self, id_salt: impl std::hash::Hash) -> Self {
|
pub fn id_salt(mut self, id_salt: impl AsIdSalt) -> Self {
|
||||||
self.id_salt = Some(Id::new(id_salt));
|
self.id_salt = Some(IdSalt::new(id_salt));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d14571fdd7602ea4924d04725008433e1bb53596d8a279f310ddc6c3231b6d32
|
oid sha256:c9b497aa9b2b92843937c84a6ff8901f248501d5d4a89c2fb1237008a44fcad1
|
||||||
size 114106
|
size 114038
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
//! Takes all available height, so if you want something below the table, put it in a strip.
|
//! Takes all available height, so if you want something below the table, put it in a strip.
|
||||||
|
|
||||||
use egui::{
|
use egui::{
|
||||||
Align, Id, NumExt as _, Rangef, Rect, Response, ScrollArea, Ui, Vec2, Vec2b,
|
Align, AsIdSalt, IdSalt, NumExt as _, Rangef, Rect, Response, ScrollArea, Ui, Vec2, Vec2b,
|
||||||
scroll_area::{DragScroll, ScrollAreaOutput, ScrollBarVisibility, ScrollSource},
|
scroll_area::{DragScroll, ScrollAreaOutput, ScrollBarVisibility, ScrollSource},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -246,7 +246,7 @@ impl Default for TableScrollOptions {
|
|||||||
/// ```
|
/// ```
|
||||||
pub struct TableBuilder<'a> {
|
pub struct TableBuilder<'a> {
|
||||||
ui: &'a mut Ui,
|
ui: &'a mut Ui,
|
||||||
id_salt: Id,
|
id_salt: IdSalt,
|
||||||
columns: Vec<Column>,
|
columns: Vec<Column>,
|
||||||
striped: Option<bool>,
|
striped: Option<bool>,
|
||||||
resizable: bool,
|
resizable: bool,
|
||||||
@@ -260,7 +260,7 @@ impl<'a> TableBuilder<'a> {
|
|||||||
let cell_layout = *ui.layout();
|
let cell_layout = *ui.layout();
|
||||||
Self {
|
Self {
|
||||||
ui,
|
ui,
|
||||||
id_salt: Id::new("__table_state"),
|
id_salt: IdSalt::new("__table_state"),
|
||||||
columns: Default::default(),
|
columns: Default::default(),
|
||||||
striped: None,
|
striped: None,
|
||||||
resizable: false,
|
resizable: false,
|
||||||
@@ -270,12 +270,12 @@ impl<'a> TableBuilder<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Give this table a unique id within the parent [`Ui`].
|
/// Give this table a unique salt within the parent [`Ui`].
|
||||||
///
|
///
|
||||||
/// This is required if you have multiple tables in the same [`Ui`].
|
/// This is required if you have multiple tables in the same [`Ui`].
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn id_salt(mut self, id_salt: impl std::hash::Hash) -> Self {
|
pub fn id_salt(mut self, id_salt: impl AsIdSalt) -> Self {
|
||||||
self.id_salt = Id::new(id_salt);
|
self.id_salt = IdSalt::new(id_salt);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user