1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-26 14:49:06 -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:
Emil Ernerfeldt
2026-05-22 15:19:55 +02:00
committed by GitHub
parent bea47a2ce7
commit 3888087dc5
14 changed files with 174 additions and 111 deletions

View File

@@ -1,9 +1,7 @@
use std::hash::Hash;
use crate::{
Context, Id, InnerResponse, NumExt as _, Rect, Response, Sense, Stroke, TextStyle,
TextWrapMode, Ui, UiBuilder, UiKind, UiStackInfo, WidgetInfo, WidgetText, WidgetType, emath,
epaint, pos2, remap, remap_clamp, vec2,
AsIdSalt, Context, Id, IdSalt, InnerResponse, NumExt as _, Rect, Response, Sense, Stroke,
TextStyle, TextWrapMode, Ui, UiBuilder, UiKind, UiStackInfo, WidgetInfo, WidgetText,
WidgetType, emath, epaint, pos2, remap, remap_clamp, vec2,
};
use emath::GuiRounding as _;
use epaint::{Shape, StrokeKind};
@@ -371,7 +369,7 @@ pub struct CollapsingHeader {
text: WidgetText,
default_open: bool,
open: Option<bool>,
id_salt: Id,
id_salt: IdSalt,
enabled: bool,
selectable: bool,
selected: bool,
@@ -388,7 +386,7 @@ impl CollapsingHeader {
/// you need to provide a unique id source with [`Self::id_salt`].
pub fn new(text: impl Into<WidgetText>) -> Self {
let text = text.into();
let id_salt = Id::new(text.text());
let id_salt = IdSalt::new(text.text());
Self {
text,
default_open: false,
@@ -424,8 +422,8 @@ impl CollapsingHeader {
/// 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.
#[inline]
pub fn id_salt(mut self, id_salt: impl Hash) -> Self {
self.id_salt = Id::new(id_salt);
pub fn id_salt(mut self, id_salt: impl AsIdSalt) -> Self {
self.id_salt = IdSalt::new(id_salt);
self
}

View File

@@ -1,9 +1,11 @@
use epaint::Shape;
use crate::{
Align2, Context, Id, InnerResponse, NumExt as _, Painter, Popup, PopupCloseBehavior, Rect,
Response, ScrollArea, Sense, Stroke, TextStyle, TextWrapMode, Ui, UiBuilder, Vec2, WidgetInfo,
WidgetText, WidgetType, epaint, style::StyleModifier, style::WidgetVisuals, vec2,
Align2, AsIdSalt, Context, Id, IdSalt, InnerResponse, NumExt as _, Painter, Popup,
PopupCloseBehavior, Rect, Response, ScrollArea, Sense, Stroke, TextStyle, TextWrapMode, Ui,
UiBuilder, Vec2, WidgetInfo, WidgetText, WidgetType, epaint,
style::{StyleModifier, WidgetVisuals},
vec2,
};
#[expect(unused_imports)] // Documentation
@@ -36,7 +38,7 @@ pub type IconPainter = Box<dyn FnOnce(&Ui, Rect, &WidgetVisuals, bool)>;
/// ```
#[must_use = "You should call .show*"]
pub struct ComboBox {
id_salt: Id,
id_salt: IdSalt,
label: Option<WidgetText>,
selected_text: WidgetText,
width: Option<f32>,
@@ -49,9 +51,9 @@ pub struct ComboBox {
impl ComboBox {
/// 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 {
id_salt: Id::new(id_salt),
id_salt: IdSalt::new(id_salt),
label: Some(label.into()),
selected_text: Default::default(),
width: None,
@@ -67,7 +69,7 @@ impl ComboBox {
pub fn from_label(label: impl Into<WidgetText>) -> Self {
let label = label.into();
Self {
id_salt: Id::new(label.text()),
id_salt: IdSalt::new(label.text()),
label: Some(label),
selected_text: Default::default(),
width: None,
@@ -80,9 +82,9 @@ impl ComboBox {
}
/// 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 {
id_salt: Id::new(id_salt),
id_salt: IdSalt::new(id_salt),
label: Default::default(),
selected_text: Default::default(),
width: None,

View File

@@ -1,6 +1,6 @@
use crate::{
Align2, Color32, Context, CursorIcon, Id, NumExt as _, Rect, Response, Sense, Shape, Ui,
UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b, pos2, vec2,
Align2, AsIdSalt, Color32, Context, CursorIcon, Id, IdSalt, NumExt as _, Rect, Response, Sense,
Shape, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b, pos2, vec2,
};
#[derive(Clone, Copy, Debug)]
@@ -41,7 +41,7 @@ impl State {
#[must_use = "You should call .show()"]
pub struct Resize {
id: Option<Id>,
id_salt: Option<Id>,
id_salt: Option<IdSalt>,
/// If false, we are no enabled
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)`.
#[inline]
pub fn id_salt(mut self, id_salt: impl std::hash::Hash) -> Self {
self.id_salt = Some(Id::new(id_salt));
pub fn id_salt(mut self, id_salt: impl AsIdSalt) -> Self {
self.id_salt = Some(IdSalt::new(id_salt));
self
}
@@ -209,7 +209,7 @@ impl Resize {
fn begin(&self, ui: &mut Ui) -> Prepared {
let position = ui.available_rect_before_wrap().min;
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)
});

View File

@@ -8,9 +8,9 @@ use emath::GuiRounding as _;
use epaint::{Color32, Direction, Margin, Shape};
use crate::{
Context, CursorIcon, Id, NumExt as _, Pos2, Rangef, Rect, Response, Sense, Ui, UiBuilder,
UiKind, UiStackInfo, Vec2, Vec2b, WidgetInfo, emath, epaint, lerp, pass_state, pos2, remap,
remap_clamp,
AsIdSalt, Context, CursorIcon, Id, IdSalt, NumExt as _, Pos2, Rangef, Rect, Response, Sense,
Ui, UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b, WidgetInfo, emath, epaint, lerp, pass_state,
pos2, remap, remap_clamp,
};
#[derive(Clone, Copy, Debug)]
@@ -344,7 +344,7 @@ pub struct ScrollArea {
min_scrolled_size: Vec2,
scroll_bar_visibility: ScrollBarVisibility,
scroll_bar_rect: Option<Rect>,
id_salt: Option<Id>,
id_salt: Option<IdSalt>,
offset_x: Option<f32>,
offset_y: Option<f32>,
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)`.
#[inline]
pub fn id_salt(mut self, id_salt: impl std::hash::Hash) -> Self {
self.id_salt = Some(Id::new(id_salt));
pub fn id_salt(mut self, id_salt: impl AsIdSalt) -> Self {
self.id_salt = Some(IdSalt::new(id_salt));
self
}
@@ -730,7 +730,7 @@ impl ScrollArea {
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);
ctx.check_for_id_clash(
id,

View File

@@ -3,8 +3,8 @@ use std::sync::Arc;
use emath::GuiRounding as _;
use crate::{
Align2, Color32, Context, Id, InnerResponse, NumExt as _, Painter, Rect, Region, Style, Ui,
UiBuilder, Vec2, vec2,
Align2, AsIdSalt, Color32, Context, Id, IdSalt, InnerResponse, NumExt as _, Painter, Rect,
Region, Style, Ui, UiBuilder, Vec2, vec2,
};
#[cfg(debug_assertions)]
@@ -312,7 +312,7 @@ impl GridLayout {
/// ```
#[must_use = "You should call .show()"]
pub struct Grid {
id_salt: Id,
id_salt: IdSalt,
num_columns: Option<usize>,
min_col_width: Option<f32>,
min_row_height: Option<f32>,
@@ -324,9 +324,9 @@ pub struct Grid {
impl Grid {
/// 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 {
id_salt: Id::new(id_salt),
id_salt: IdSalt::new(id_salt),
num_columns: None,
min_col_width: None,
min_row_height: None,

View File

@@ -2,6 +2,16 @@
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.
///
/// 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!
pub const NULL: Self = Self(NonZeroU64::MAX);
/// Create a new root [`Id`] from a high-entropy hash.
#[inline]
const fn from_hash(hash: u64) -> Self {
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).
pub fn new(source: impl std::hash::Hash) -> Self {
/// Generate a new root [`Id`] by hashing some source (e.g. a string or integer).
pub fn new(source: impl AsId) -> Self {
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.
pub fn with(self, child: impl std::hash::Hash) -> Self {
/// Generate a child [`Id`] by salting the parent [`Id`] with the given argument.
pub fn with(self, salt: impl AsIdSalt) -> Self {
use std::hash::{BuildHasher as _, Hasher as _};
let mut hasher = ahash::RandomState::with_seeds(1, 2, 3, 4).build_hasher();
hasher.write_u64(self.0.get());
child.hash(&mut hasher);
hasher.write_u64(self.value());
hasher.write_u64(IdSalt::new(salt).value());
Self::from_hash(hasher.finish())
}

View 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)
}
}

View File

@@ -400,6 +400,7 @@ pub(crate) mod grid;
pub mod gui_zoom;
mod hit_test;
mod id;
mod id_salt;
mod input_state;
mod interaction;
pub mod introspection;
@@ -473,7 +474,8 @@ pub use self::{
drag_and_drop::DragAndDrop,
epaint::text::TextWrapMode,
grid::Grid,
id::{Id, IdMap, IdSet},
id::{AsId, Id, IdMap, IdSet},
id_salt::{AsIdSalt, IdSalt},
input_state::{InputOptions, InputState, MultiTouchInfo, PointerState, SurrenderFocusOn},
layers::{LayerId, Order},
layout::*,
@@ -486,7 +488,7 @@ pub use self::{
style::{FontSelection, Spacing, Style, TextStyle, Visuals},
text::{Galley, TextFormat},
ui::Ui,
ui_builder::UiBuilder,
ui_builder::{IdSource, UiBuilder},
ui_stack::*,
viewport::*,
widget_rect::{InteractOptions, WidgetRect, WidgetRects},

View File

@@ -1,12 +1,13 @@
#![warn(missing_docs)] // Let's keep `Ui` well-documented.
#![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::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 _;
// ----------------------------------------------------------------------------
/// This is what you use to place widgets.
@@ -106,8 +107,7 @@ impl Ui {
/// [`crate::Panel`], [`crate::CentralPanel`], [`crate::Window`] or [`crate::Area`].
pub fn new(ctx: Context, id: Id, ui_builder: UiBuilder) -> Self {
let UiBuilder {
id_salt,
global_scope: _,
id_source,
ui_stack_info,
layer_id,
max_rect,
@@ -124,8 +124,8 @@ impl Ui {
let layer_id = layer_id.unwrap_or_else(LayerId::background);
debug_assert!(
id_salt.is_none(),
"Top-level Ui:s should not have an id_salt"
id_source.is_none(),
"Top-level Ui:s should not have an UiBuilder::id_source"
);
let max_rect = max_rect.unwrap_or_else(|| ctx.content_rect());
@@ -207,8 +207,7 @@ impl Ui {
/// [`Ui::advance_cursor_after_rect`].
pub fn new_child(&mut self, ui_builder: UiBuilder) -> Self {
let UiBuilder {
id_salt,
global_scope,
id_source,
ui_stack_info,
layer_id,
max_rect,
@@ -224,7 +223,6 @@ impl Ui {
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 mut layout = layout.unwrap_or_else(|| *self.layout());
let enabled = self.enabled && !disabled && !invisible;
@@ -248,13 +246,15 @@ impl Ui {
}
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);
@@ -880,11 +880,8 @@ impl Ui {
/// # [`Id`] creation
impl Ui {
/// 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
where
IdSource: Hash,
{
self.id.with(&id_salt)
pub fn make_persistent_id(&self, id_salt: impl AsIdSalt) -> Id {
self.id.with(id_salt)
}
/// 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)`
pub fn auto_id_with<IdSource>(&self, id_salt: IdSource) -> Id
where
IdSource: Hash,
{
pub fn auto_id_with(&self, id_salt: impl AsIdSalt) -> Id {
Id::new(self.next_auto_id_salt).with(id_salt)
}
@@ -2168,7 +2162,7 @@ impl Ui {
/// ```
pub fn push_id<R>(
&mut self,
id_salt: impl Hash,
id_salt: impl AsIdSalt,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
self.scope_dyn(UiBuilder::new().id_salt(id_salt), Box::new(add_contents))
@@ -2233,7 +2227,7 @@ impl Ui {
#[inline]
pub fn indent<R>(
&mut self,
id_salt: impl Hash,
id_salt: impl AsIdSalt,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
self.indent_dyn(id_salt, Box::new(add_contents))
@@ -2241,7 +2235,7 @@ impl Ui {
fn indent_dyn<'c, R>(
&mut self,
id_salt: impl Hash,
id_salt: impl AsIdSalt,
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
) -> InnerResponse<R> {
assert!(

View File

@@ -1,10 +1,11 @@
use std::{hash::Hash, sync::Arc};
use std::sync::Arc;
#[expect(unused_imports)] // Used for doclinks
use crate::Ui;
use crate::widget_style::HasClasses;
use crate::{ClosableTag, widget_style::Classes};
use crate::{Id, LayerId, Layout, Rect, Sense, Style, UiStackInfo};
use crate::{
AsIdSalt, ClosableTag, Id, IdSalt, LayerId, Layout, Rect, Sense, Style, UiStackInfo,
widget_style::{Classes, HasClasses},
};
/// Build a [`Ui`] as the child of another [`Ui`].
///
@@ -14,8 +15,7 @@ use crate::{Id, LayerId, Layout, Rect, Sense, Style, UiStackInfo};
#[must_use]
#[derive(Clone, Default)]
pub struct UiBuilder {
pub id_salt: Option<Id>,
pub global_scope: bool,
pub id_source: Option<IdSource>,
pub ui_stack_info: UiStackInfo,
pub layer_id: Option<LayerId>,
pub max_rect: Option<Rect>,
@@ -29,6 +29,16 @@ pub struct UiBuilder {
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 {
#[inline]
pub fn new() -> Self {
@@ -41,8 +51,8 @@ impl UiBuilder {
/// You should give each [`Ui`] an `id_salt` that is unique
/// within the parent, or give it none at all.
#[inline]
pub fn id_salt(mut self, id_salt: impl Hash) -> Self {
self.id_salt = Some(Id::new(id_salt));
pub fn id_salt(mut self, id_salt: impl AsIdSalt) -> Self {
self.id_source = Some(IdSource::Child(IdSalt::new(id_salt)));
self
}
@@ -57,20 +67,7 @@ impl UiBuilder {
/// This is a shortcut for `.id_salt(my_id).global_scope(true)`.
#[inline]
pub fn id(mut self, id: Id) -> Self {
self.id_salt = Some(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.id_source = Some(IdSource::Explicit(id));
self
}

View File

@@ -73,7 +73,7 @@ use std::sync::Arc;
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);
#[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))
}
}

View File

@@ -4,10 +4,10 @@ use emath::{Rect, TSTransform};
use epaint::text::{Galley, LayoutJob, TextWrapMode, cursor::CCursor};
use crate::{
Align, Align2, AtomExt as _, AtomKind, AtomLayout, Atoms, Color32, Context, CursorIcon, Event,
EventFilter, FontSelection, Frame, Id, ImeEvent, IntoAtoms, IntoSizedResult, Key,
KeyboardShortcut, Margin, Modifiers, NumExt as _, Response, Sense, SizedAtomKind, TextBuffer,
TextStyle, Ui, Vec2, Widget, WidgetInfo, WidgetWithState, epaint,
Align, Align2, AsIdSalt, AtomExt as _, AtomKind, AtomLayout, Atoms, Color32, Context,
CursorIcon, Event, EventFilter, FontSelection, Frame, Id, IdSalt, ImeEvent, IntoAtoms,
IntoSizedResult, Key, KeyboardShortcut, Margin, Modifiers, NumExt as _, Response, Sense,
SizedAtomKind, TextBuffer, TextStyle, Ui, Vec2, Widget, WidgetInfo, WidgetWithState, epaint,
os::OperatingSystem,
output::OutputEvent,
response,
@@ -72,7 +72,7 @@ pub struct TextEdit<'t> {
suffix: Atoms<'static>,
hint_text: Atoms<'static>,
id: Option<Id>,
id_salt: Option<Id>,
id_salt: Option<IdSalt>,
font_selection: FontSelection,
text_color: Option<Color32>,
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)`.
#[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)
}
/// A source for the unique [`Id`], e.g. `.id_salt("second_text_edit_field")` or `.id_salt(loop_index)`.
#[inline]
pub fn id_salt(mut self, id_salt: impl std::hash::Hash) -> Self {
self.id_salt = Some(Id::new(id_salt));
pub fn id_salt(mut self, id_salt: impl AsIdSalt) -> Self {
self.id_salt = Some(IdSalt::new(id_salt));
self
}

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d14571fdd7602ea4924d04725008433e1bb53596d8a279f310ddc6c3231b6d32
size 114106
oid sha256:c9b497aa9b2b92843937c84a6ff8901f248501d5d4a89c2fb1237008a44fcad1
size 114038

View File

@@ -4,7 +4,7 @@
//! Takes all available height, so if you want something below the table, put it in a strip.
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},
};
@@ -246,7 +246,7 @@ impl Default for TableScrollOptions {
/// ```
pub struct TableBuilder<'a> {
ui: &'a mut Ui,
id_salt: Id,
id_salt: IdSalt,
columns: Vec<Column>,
striped: Option<bool>,
resizable: bool,
@@ -260,7 +260,7 @@ impl<'a> TableBuilder<'a> {
let cell_layout = *ui.layout();
Self {
ui,
id_salt: Id::new("__table_state"),
id_salt: IdSalt::new("__table_state"),
columns: Default::default(),
striped: None,
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`].
#[inline]
pub fn id_salt(mut self, id_salt: impl std::hash::Hash) -> Self {
self.id_salt = Id::new(id_salt);
pub fn id_salt(mut self, id_salt: impl AsIdSalt) -> Self {
self.id_salt = IdSalt::new(id_salt);
self
}