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