mirror of
https://github.com/emilk/egui.git
synced 2026-06-26 22:53:14 -04:00
IdSalt (WIP)
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
use std::hash::Hash;
|
||||
|
||||
use crate::{
|
||||
Context, Id, InnerResponse, NumExt as _, Rect, Response, Sense, Stroke, TextStyle,
|
||||
TextWrapMode, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, WidgetInfo, WidgetText, WidgetType,
|
||||
@@ -410,7 +408,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 = Id::new_salt(text.text());
|
||||
Self {
|
||||
text,
|
||||
default_open: false,
|
||||
@@ -446,8 +444,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 Into<crate::IdSalt>) -> Self {
|
||||
self.id_salt = id_salt.into().id();
|
||||
self
|
||||
}
|
||||
|
||||
@@ -455,8 +453,8 @@ impl CollapsingHeader {
|
||||
/// This is useful if the title label is dynamic or not unique.
|
||||
#[deprecated = "Renamed id_salt"]
|
||||
#[inline]
|
||||
pub fn id_source(mut self, id_salt: impl Hash) -> Self {
|
||||
self.id_salt = Id::new(id_salt);
|
||||
pub fn id_source(mut self, id_salt: impl Into<crate::IdSalt>) -> Self {
|
||||
self.id_salt = id_salt.into().id();
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
@@ -49,9 +49,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 Into<crate::IdSalt>, label: impl Into<WidgetText>) -> Self {
|
||||
Self {
|
||||
id_salt: Id::new(id_salt),
|
||||
id_salt: id_salt.into().id(),
|
||||
label: Some(label.into()),
|
||||
selected_text: Default::default(),
|
||||
width: None,
|
||||
@@ -67,7 +67,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: Id::new_salt(label.text()),
|
||||
label: Some(label),
|
||||
selected_text: Default::default(),
|
||||
width: None,
|
||||
@@ -80,9 +80,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 Into<crate::IdSalt>) -> Self {
|
||||
Self {
|
||||
id_salt: Id::new(id_salt),
|
||||
id_salt: id_salt.into().id(),
|
||||
label: Default::default(),
|
||||
selected_text: Default::default(),
|
||||
width: None,
|
||||
@@ -96,7 +96,7 @@ impl ComboBox {
|
||||
|
||||
/// Without label.
|
||||
#[deprecated = "Renamed from_id_salt"]
|
||||
pub fn from_id_source(id_salt: impl std::hash::Hash) -> Self {
|
||||
pub fn from_id_source(id_salt: impl Into<crate::IdSalt>) -> Self {
|
||||
Self::from_id_salt(id_salt)
|
||||
}
|
||||
|
||||
|
||||
@@ -72,14 +72,14 @@ impl Resize {
|
||||
/// A source for the unique [`Id`], e.g. `.id_source("second_resize_area")` or `.id_source(loop_index)`.
|
||||
#[inline]
|
||||
#[deprecated = "Renamed id_salt"]
|
||||
pub fn id_source(self, id_salt: impl std::hash::Hash) -> Self {
|
||||
pub fn id_source(self, id_salt: impl Into<crate::IdSalt>) -> Self {
|
||||
self.id_salt(id_salt)
|
||||
}
|
||||
|
||||
/// 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 Into<crate::IdSalt>) -> Self {
|
||||
self.id_salt = Some(id_salt.into().id());
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
@@ -426,14 +426,14 @@ impl ScrollArea {
|
||||
/// A source for the unique [`Id`], e.g. `.id_source("second_scroll_area")` or `.id_source(loop_index)`.
|
||||
#[inline]
|
||||
#[deprecated = "Renamed id_salt"]
|
||||
pub fn id_source(self, id_salt: impl std::hash::Hash) -> Self {
|
||||
pub fn id_source(self, id_salt: impl Into<crate::IdSalt>) -> Self {
|
||||
self.id_salt(id_salt)
|
||||
}
|
||||
|
||||
/// 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 Into<crate::IdSalt>) -> Self {
|
||||
self.id_salt = Some(id_salt.into().id());
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
@@ -984,7 +984,7 @@ fn do_resize_interaction(
|
||||
}
|
||||
};
|
||||
|
||||
let id = Id::new(layer_id).with("edge_drag");
|
||||
let id = layer_id.id.with("edge_drag");
|
||||
|
||||
let style = ctx.global_style();
|
||||
|
||||
|
||||
@@ -1194,7 +1194,7 @@ impl RawInput {
|
||||
for (id, viewport) in ordered_viewports {
|
||||
ui.group(|ui| {
|
||||
ui.label(format!("Viewport {id:?}"));
|
||||
ui.push_id(id, |ui| {
|
||||
ui.push_id(id.0, |ui| {
|
||||
viewport.ui(ui);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 Into<crate::IdSalt>) -> Self {
|
||||
Self {
|
||||
id_salt: Id::new(id_salt),
|
||||
id_salt: id_salt.into().id(),
|
||||
num_columns: None,
|
||||
min_col_width: None,
|
||||
min_row_height: None,
|
||||
|
||||
@@ -54,6 +54,19 @@ 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 {
|
||||
debug_assert!(
|
||||
std::any::type_name_of_val(&source) != std::any::type_name::<Self>(),
|
||||
"Don't pass an `Id` to `Id::new()`: use `.with()` to create child `Id`s"
|
||||
);
|
||||
Self::from_hash(ahash::RandomState::with_seeds(1, 2, 3, 4).hash_one(source))
|
||||
}
|
||||
|
||||
/// Like [`Self::new`], but for use as an id salt.
|
||||
///
|
||||
/// Unlike [`Self::new`], this does not reject [`Id`] input,
|
||||
/// because using an [`Id`] as a salt (to be mixed into a parent via [`.with()`](Self::with))
|
||||
/// is a valid use case.
|
||||
pub(crate) fn new_salt(source: impl std::hash::Hash) -> Self {
|
||||
Self::from_hash(ahash::RandomState::with_seeds(1, 2, 3, 4).hash_one(source))
|
||||
}
|
||||
|
||||
@@ -130,6 +143,91 @@ fn id_size() {
|
||||
assert_eq!(std::mem::size_of::<Option<Id>>(), 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Don't pass an `Id` to `Id::new()`")]
|
||||
fn test_id_new_rejects_id() {
|
||||
let _ = Id::new(Id::NULL);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// A value to be used as an [`Id`] salt.
|
||||
///
|
||||
/// This is used by builder methods like [`crate::UiBuilder::id_salt`], [`crate::Grid::new`], etc.
|
||||
/// It can be created from common hashable types (`&str`, `String`, integers)
|
||||
/// as well as from an existing [`Id`].
|
||||
///
|
||||
/// When created from an [`Id`], the value is stored directly without re-hashing.
|
||||
/// When created from other types, the value is hashed into an [`Id`].
|
||||
///
|
||||
/// ## Example
|
||||
/// ```
|
||||
/// use egui::{Id, IdSalt};
|
||||
///
|
||||
/// // From a string:
|
||||
/// let salt: IdSalt = "my_widget".into();
|
||||
///
|
||||
/// // From an existing Id (no re-hash):
|
||||
/// let id = Id::new("parent");
|
||||
/// let salt: IdSalt = id.into();
|
||||
/// ```
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct IdSalt(Id);
|
||||
|
||||
impl IdSalt {
|
||||
/// Create an [`IdSalt`] by hashing some source.
|
||||
///
|
||||
/// Use this for types that don't have a `From` impl
|
||||
/// (e.g. tuples, custom types).
|
||||
#[inline]
|
||||
pub fn new(source: impl std::hash::Hash) -> Self {
|
||||
Self(Id::new_salt(source))
|
||||
}
|
||||
|
||||
/// Get the inner [`Id`].
|
||||
#[inline]
|
||||
pub fn id(self) -> Id {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for IdSalt {
|
||||
/// Store an [`Id`] directly as a salt, without re-hashing.
|
||||
#[inline]
|
||||
fn from(id: Id) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for IdSalt {
|
||||
#[inline]
|
||||
fn from(s: &str) -> Self {
|
||||
Self::new(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for IdSalt {
|
||||
#[inline]
|
||||
fn from(s: String) -> Self {
|
||||
Self::new(s)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_id_salt_from_int {
|
||||
($($t:ty),*) => {
|
||||
$(
|
||||
impl From<$t> for IdSalt {
|
||||
#[inline]
|
||||
fn from(v: $t) -> Self {
|
||||
Self::new(v)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_id_salt_from_int!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, bool);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// `IdSet` is a `HashSet<Id>` optimized by knowing that [`Id`] has good entropy, and doesn't need more hashing.
|
||||
|
||||
@@ -478,7 +478,7 @@ pub use self::{
|
||||
drag_and_drop::DragAndDrop,
|
||||
epaint::text::TextWrapMode,
|
||||
grid::Grid,
|
||||
id::{Id, IdMap},
|
||||
id::{Id, IdMap, IdSalt},
|
||||
input_state::{InputOptions, InputState, MultiTouchInfo, PointerState, SurrenderFocusOn},
|
||||
layers::{LayerId, Order},
|
||||
layout::*,
|
||||
|
||||
@@ -226,7 +226,7 @@ impl Ui {
|
||||
&mut self,
|
||||
max_rect: Rect,
|
||||
layout: Layout,
|
||||
id_salt: impl Hash,
|
||||
id_salt: impl Into<IdSalt>,
|
||||
ui_stack_info: Option<UiStackInfo>,
|
||||
) -> Self {
|
||||
self.new_child(
|
||||
@@ -2365,7 +2365,7 @@ impl Ui {
|
||||
/// ```
|
||||
pub fn push_id<R>(
|
||||
&mut self,
|
||||
id_salt: impl Hash,
|
||||
id_salt: impl Into<IdSalt>,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> InnerResponse<R> {
|
||||
self.scope_dyn(UiBuilder::new().id_salt(id_salt), Box::new(add_contents))
|
||||
@@ -2465,15 +2465,15 @@ impl Ui {
|
||||
#[inline]
|
||||
pub fn indent<R>(
|
||||
&mut self,
|
||||
id_salt: impl Hash,
|
||||
id_salt: impl Into<IdSalt>,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> InnerResponse<R> {
|
||||
self.indent_dyn(id_salt, Box::new(add_contents))
|
||||
self.indent_dyn(id_salt.into(), Box::new(add_contents))
|
||||
}
|
||||
|
||||
fn indent_dyn<'c, R>(
|
||||
&mut self,
|
||||
id_salt: impl Hash,
|
||||
id_salt: IdSalt,
|
||||
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||
) -> InnerResponse<R> {
|
||||
assert!(
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::{hash::Hash, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::ClosableTag;
|
||||
#[expect(unused_imports)] // Used for doclinks
|
||||
use crate::Ui;
|
||||
use crate::{Id, LayerId, Layout, Rect, Sense, Style, UiStackInfo};
|
||||
use crate::{Id, IdSalt, LayerId, Layout, Rect, Sense, Style, UiStackInfo};
|
||||
|
||||
/// Build a [`Ui`] as the child of another [`Ui`].
|
||||
///
|
||||
@@ -39,8 +39,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 Into<IdSalt>) -> Self {
|
||||
self.id_salt = Some(id_salt.into().id());
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
@@ -168,14 +168,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 Into<crate::IdSalt>) -> 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 Into<crate::IdSalt>) -> Self {
|
||||
self.id_salt = Some(id_salt.into().id());
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ fn viewport_content(ui: &mut egui::Ui, open: &mut bool) {
|
||||
for (id, viewport) in ordered_viewports {
|
||||
ui.group(|ui| {
|
||||
ui.label(format!("viewport {id:?}"));
|
||||
ui.push_id(id, |ui| {
|
||||
ui.push_id(id.0, |ui| {
|
||||
viewport.ui(ui);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -275,7 +275,7 @@ impl<'a> TableBuilder<'a> {
|
||||
/// This is required if you have multiple tables in the same [`Ui`].
|
||||
#[inline]
|
||||
#[deprecated = "Renamed id_salt"]
|
||||
pub fn id_source(self, id_salt: impl std::hash::Hash) -> Self {
|
||||
pub fn id_source(self, id_salt: impl Into<egui::IdSalt>) -> Self {
|
||||
self.id_salt(id_salt)
|
||||
}
|
||||
|
||||
@@ -283,8 +283,8 @@ impl<'a> TableBuilder<'a> {
|
||||
///
|
||||
/// 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 Into<egui::IdSalt>) -> Self {
|
||||
self.id_salt = id_salt.into().id();
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user