Skip to main content

waterui_core/foundation/
id.rs

1//! Identity, tagging, and mapping functionality for UI components.
2//!
3//! This module provides various utilities for:
4//! - Identifying and tagging UI elements with unique identifiers
5//! - Creating mappings between values and numeric IDs
6//! - Wrapping views with identifying information
7//! - Converting between different ID types
8//!
9//! The primary types in this module include:
10//! - `Identifiable`: A trait for types that can be uniquely identified
11//! - `TaggedView`: A view wrapper that includes an identifying tag
12//! - `Mapping`: A bidirectional mapping between values and numeric IDs
13//! - `UseId` and `SelfId`: Wrappers that implement different ID strategies
14
15use core::num::NonZeroI32;
16use core::num::TryFromIntError;
17use core::{hash::Hash, ops::Deref};
18
19use crate::{AnyView, View};
20
21/// A non-zero i32 value used for identification purposes throughout the crate.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
23pub struct Id(pub(crate) NonZeroI32);
24
25impl From<Id> for i32 {
26    fn from(val: Id) -> Self {
27        val.0.get()
28    }
29}
30
31impl From<NonZeroI32> for Id {
32    fn from(value: NonZeroI32) -> Self {
33        Self(value)
34    }
35}
36
37impl TryFrom<i32> for Id {
38    type Error = TryFromIntError;
39
40    fn try_from(value: i32) -> Result<Self, Self::Error> {
41        NonZeroI32::try_from(value).map(Id)
42    }
43}
44
45/// Defines an interface for types that can be uniquely identified.
46///
47/// Implementors of this trait can provide a specific ID type and a way to retrieve
48/// the ID from an instance.
49#[diagnostic::on_unimplemented(
50    message = "`{Self}` has no stable WaterUI identity",
51    label = "expected a type implementing `Identifiable`",
52    note = "Collections such as `ForEach` and `List` diff items by id, so each item needs `Identifiable`: derive it with `#[derive(Identifiable)]` and `#[id]` on the identifier field (`use waterui::Identifiable;` — the derive is not in the prelude), for a type you do not own, wrap the value with `.use_id(..)` / `.self_id()`."
53)]
54pub trait Identifiable {
55    /// The type of ID to use, which must implement Hash and Ord traits.
56    type Id: Hash + Ord + Clone;
57
58    /// Retrieves the unique identifier for this instance.
59    fn id(&self) -> Self::Id;
60}
61
62/// A wrapper that provides identity to a value through a function.
63///
64/// This allows attaching identity behavior to any type by providing a function
65/// to extract an ID from the wrapped value.
66#[derive(Debug)]
67pub struct UseId<T, F> {
68    /// The wrapped value
69    value: T,
70    /// Function to extract an ID from the value
71    f: F,
72}
73
74impl<T, F> UseId<T, F> {
75    /// Creates a new [`UseId`] instance wrapping the given value and function.
76    pub const fn new(value: T, f: F) -> Self {
77        Self { value, f }
78    }
79
80    /// Consumes the wrapper and returns the inner value.
81    pub fn into_inner(self) -> T {
82        self.value
83    }
84}
85
86impl<T, F> Deref for UseId<T, F> {
87    type Target = T;
88
89    fn deref(&self) -> &Self::Target {
90        &self.value
91    }
92}
93
94impl<T, F, Id> Identifiable for UseId<T, F>
95where
96    F: Fn(&T) -> Id,
97    Id: Ord + Hash + Clone,
98{
99    type Id = Id;
100
101    /// Applies the stored function to the wrapped value to generate an ID.
102    fn id(&self) -> Self::Id {
103        (self.f)(&self.value)
104    }
105}
106
107/// A wrapper that uses the value itself as its own identifier.
108///
109/// This is useful for types that are already suitable as identifiers.
110#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
111pub struct SelfId<T>(T);
112
113impl<T> SelfId<T> {
114    /// Creates a new [`SelfId`] instance wrapping the given value.
115    pub const fn new(value: T) -> Self {
116        Self(value)
117    }
118    /// Consumes the wrapper and returns the inner value.
119    pub fn into_inner(self) -> T {
120        self.0
121    }
122}
123
124impl<T: Hash + Ord + Clone> Identifiable for SelfId<T> {
125    type Id = T;
126
127    /// Returns a clone of the wrapped value as the identifier.
128    fn id(&self) -> Self::Id {
129        self.0.clone()
130    }
131}
132
133impl<T> Deref for SelfId<T> {
134    type Target = T;
135
136    fn deref(&self) -> &Self::Target {
137        &self.0
138    }
139}
140
141/// Extension trait that provides convenient methods for making types identifiable.
142pub trait IdentifiableExt: Sized {
143    /// Wraps the value in a [`UseId`] with the provided identification function.
144    fn use_id<F, Id>(self, f: F) -> UseId<Self, F>
145    where
146        F: Fn(&Self) -> Id,
147        Id: Ord + Hash,
148    {
149        UseId { value: self, f }
150    }
151
152    /// Wraps the value in a `SelfId`, making the value serve as its own identifier.
153    fn self_id(self) -> SelfId<Self> {
154        SelfId(self)
155    }
156}
157
158impl<T> IdentifiableExt for T {}
159
160/// A view that includes an identifying tag of type T.
161///
162/// This allows tracking and identification of views within a UI hierarchy.
163#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
164pub struct TaggedView<T, V> {
165    /// The tag used to identify this view
166    pub tag: T,
167    /// The actual view content
168    pub content: V,
169}
170
171impl<T, V: View> TaggedView<T, V> {
172    /// Creates a new tagged view with the specified tag and content.
173    pub const fn new(tag: T, content: V) -> Self {
174        Self { tag, content }
175    }
176
177    /// Transforms the tag of this view using the provided function.
178    pub fn map<F, T2>(self, f: F) -> TaggedView<T2, V>
179    where
180        F: Fn(T) -> T2,
181    {
182        TaggedView {
183            tag: f(self.tag),
184            content: self.content,
185        }
186    }
187
188    /// Converts the tag to an Id using the provided mapping.
189    pub fn mapping(self, mapping: &Mapping<T>) -> TaggedView<Id, V>
190    where
191        T: Ord + Clone,
192    {
193        self.map(move |v| mapping.register(v))
194    }
195
196    /// Erases the specific view type, converting it to [`AnyView`].
197    ///
198    /// This is useful for storing heterogeneous views in a collection.
199    pub fn erase(self) -> TaggedView<T, AnyView> {
200        TaggedView {
201            tag: self.tag,
202            content: AnyView::new(self.content),
203        }
204    }
205}
206
207use core::cell::RefCell;
208
209use alloc::{collections::btree_map::BTreeMap, rc::Rc};
210use nami::Binding;
211
212/// Internal implementation of the mapping functionality.
213///
214/// Handles the bidirectional mapping between values and IDs.
215#[derive(Debug)]
216struct MappingInner<T> {
217    /// Counter used to generate new IDs
218    counter: i32,
219    /// Maps from values to their assigned IDs
220    to_id: BTreeMap<T, Id>,
221    /// Maps from IDs back to their associated values
222    from_id: BTreeMap<Id, T>,
223}
224
225impl<T: Ord + Clone> MappingInner<T> {
226    /// Creates a new empty mapping with counter starting at 1.
227    pub const fn new() -> Self {
228        Self {
229            counter: 1,
230            to_id: BTreeMap::new(),
231            from_id: BTreeMap::new(),
232        }
233    }
234
235    /// Registers a new value in the mapping and returns its assigned ID.
236    pub fn register(&mut self, value: T) -> Id {
237        let id = Id(NonZeroI32::new(self.counter).expect("counter should not be zero"));
238        self.to_id.insert(value.clone(), id);
239        self.from_id.insert(id, value);
240        self.counter = self
241            .counter
242            .checked_add(1)
243            .expect("counter should not overflow");
244        id
245    }
246
247    /// Attempts to find the ID for a given value.
248    pub fn try_to_id(&self, value: &T) -> Option<Id> {
249        self.to_id.get(value).copied()
250    }
251
252    /// Retrieves the data associated with an ID.
253    pub fn to_data(&self, id: Id) -> Option<T> {
254        self.from_id.get(&id).cloned()
255    }
256
257    /// Gets the ID for a value, registering it if not already present.
258    #[allow(clippy::wrong_self_convention)]
259    pub fn to_id(&mut self, value: T) -> Id {
260        self.try_to_id(&value)
261            .unwrap_or_else(|| self.register(value))
262    }
263}
264
265/// A mapping between values and IDs.
266///
267/// This structure allows for bidirectional lookup between values and their
268/// assigned numeric IDs, with interior mutability for shared access.
269#[derive(Debug)]
270pub struct Mapping<T>(Rc<RefCell<MappingInner<T>>>);
271
272impl<T> Clone for Mapping<T> {
273    /// Creates a new reference to the same underlying mapping.
274    fn clone(&self) -> Self {
275        Self(self.0.clone())
276    }
277}
278
279impl<T: Ord + Clone> Default for Mapping<T> {
280    /// Creates a new empty mapping.
281    fn default() -> Self {
282        Self::new()
283    }
284}
285
286impl<T: Ord + Clone> Mapping<T> {
287    /// Creates a new empty mapping.
288    #[must_use]
289    pub fn new() -> Self {
290        Self(Rc::new(RefCell::new(MappingInner::new())))
291    }
292
293    /// Registers a new value in the mapping and returns its assigned ID.
294    pub fn register(&self, value: T) -> Id {
295        self.0.borrow_mut().register(value)
296    }
297
298    /// Attempts to find the ID for a given value.
299    pub fn try_to_id(&self, value: &T) -> Option<Id> {
300        self.0.borrow().try_to_id(value)
301    }
302
303    /// Gets the ID for a value, registering it if not already present.
304    pub fn to_id(&self, value: T) -> Id {
305        self.0.borrow_mut().to_id(value)
306    }
307
308    /// Retrieves the data associated with an ID.
309    #[must_use]
310    pub fn to_data(&self, id: Id) -> Option<T> {
311        self.0.borrow().to_data(id)
312    }
313
314    /// Creates a binding that maps between a value binding and an ID binding.
315    ///
316    /// This is useful for reactive UI systems where you need to work with IDs rather
317    /// than the actual values but still maintain synchronization.
318    ///
319    /// # Panics
320    ///
321    /// Panics if the provided `Id` does not correspond to any value in the mapping.
322    #[must_use]
323    pub fn binding(&self, source: &Binding<T>) -> Binding<Id>
324    where
325        T: 'static,
326    {
327        let mapping = self.clone();
328        let mapping2 = self.clone();
329        Binding::mapping(
330            source,
331            move |value| mapping.to_id(value),
332            move |binding, value| {
333                binding.set(
334                    mapping2
335                        .to_data(value)
336                        .expect("Invalid binding mapping : Data not found"),
337                );
338            },
339        )
340    }
341
342    /// Creates a binding that maps between an optional value binding and an optional ID binding.
343    ///
344    /// This is useful for selection-driven controls whose "no selection" state must survive
345    /// the type-erased boundary without collapsing to an arbitrary sentinel in Rust space.
346    ///
347    /// # Panics
348    ///
349    /// Panics if the provided `Id` does not correspond to any value in the mapping.
350    #[must_use]
351    pub fn optional_binding(&self, source: &Binding<Option<T>>) -> Binding<Option<Id>>
352    where
353        T: 'static,
354    {
355        let mapping = self.clone();
356        let mapping2 = self.clone();
357        Binding::mapping(
358            source,
359            move |value| value.map(|value| mapping.to_id(value)),
360            move |binding, value| {
361                binding.set(value.map(|value| {
362                    mapping2
363                        .to_data(value)
364                        .expect("Invalid optional binding mapping : Data not found")
365                }));
366            },
367        )
368    }
369}