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