iced-yoda/core/src/id.rs
Ashley Wulber 2dd6137459
clippy
2026-01-21 14:01:37 -05:00

170 lines
4.9 KiB
Rust

//! Widget and Window IDs.
use std::borrow;
use std::num::NonZeroU128;
use std::sync::atomic::{self, AtomicU64};
static NEXT_ID: AtomicU64 = AtomicU64::new(1);
static NEXT_WINDOW_ID: AtomicU64 = AtomicU64::new(1);
/// The identifier of a generic widget.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Id(pub Internal);
impl Id {
/// Creates a custom [`Id`].
pub fn new(id: impl Into<borrow::Cow<'static, str>>) -> Self {
Self(Internal::Custom(Self::next(), id.into()))
}
/// resets the id counter
pub fn reset() {
NEXT_ID.store(1, atomic::Ordering::Relaxed);
}
fn next() -> u64 {
NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)
}
/// Creates a unique [`Id`].
///
/// This function produces a different [`Id`] every time it is called.
pub fn unique() -> Self {
let id = Self::next();
Self(Internal::Unique(id))
}
}
impl From<&'static str> for Id {
fn from(value: &'static str) -> Self {
Id(Internal::Custom(Id::next(), borrow::Cow::Borrowed(value)))
}
}
impl<'a> From<String> for Id {
fn from(value: String) -> Self {
Id(Internal::Custom(
Id::next(),
borrow::Cow::Owned(value.to_string()),
))
}
}
// Not meant to be used directly
impl From<u64> for Id {
fn from(value: u64) -> Self {
Self(Internal::Unique(value))
}
}
// Not meant to be used directly
impl From<Id> for NonZeroU128 {
fn from(id: Id) -> NonZeroU128 {
match &id.0 {
Internal::Unique(id) => NonZeroU128::try_from(*id as u128).unwrap(),
Internal::Custom(id, _) => {
NonZeroU128::try_from(*id as u128).unwrap()
}
// this is a set id, which is not a valid id and will not ever be converted to a NonZeroU128
// so we panic
Internal::Set(_) => {
panic!("Cannot convert a set id to a NonZeroU128")
}
}
}
}
impl std::fmt::Display for Id {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.0 {
Internal::Unique(_) => write!(f, "Undefined"),
Internal::Custom(_, id) => write!(f, "{}", id.to_string()),
Internal::Set(_) => write!(f, "Set"),
}
}
}
// XXX WIndow IDs are made unique by adding u64::MAX to them
/// get window node id that won't conflict with other node ids for the duration of the program
pub fn window_node_id() -> NonZeroU128 {
std::num::NonZeroU128::try_from(
u64::MAX as u128
+ NEXT_WINDOW_ID.fetch_add(1, atomic::Ordering::Relaxed) as u128,
)
.unwrap()
}
// TODO refactor to make panic impossible?
#[derive(Debug, Clone, Eq)]
/// Internal representation of an [`Id`].
pub enum Internal {
/// a unique id
Unique(u64),
/// a custom id, which is equal to any [`Id`] with a matching number or string
Custom(u64, borrow::Cow<'static, str>),
/// XXX Do not use this as an id for an accessibility node, it will panic!
/// XXX Only meant to be used for widgets that have multiple accessibility nodes, each with a
/// unique or custom id
/// an Id Set, which is equal to any [`Id`] with a matching number or string
Set(Vec<Self>),
}
impl PartialEq for Internal {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Unique(l0), Self::Unique(r0)) => l0 == r0,
(Self::Custom(_, l1), Self::Custom(_, r1)) => l1 == r1,
(Self::Set(l0), Self::Set(r0)) => l0 == r0,
_ => false,
}
}
}
/// Similar to PartialEq, but only intended for use when comparing Ids
pub trait IdEq {
/// Compare two Ids for equality based on their number or name
fn eq(&self, other: &Self) -> bool;
}
impl IdEq for Internal {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Unique(l0), Self::Unique(r0)) => l0 == r0,
(Self::Custom(l0, l1), Self::Custom(r0, r1)) => {
l0 == r0 || l1 == r1
}
// allow custom ids to be equal to unique ids
(Self::Unique(l0), Self::Custom(r0, _))
| (Self::Custom(l0, _), Self::Unique(r0)) => l0 == r0,
(Self::Set(l0), Self::Set(r0)) => l0 == r0,
// allow set ids to just be equal to any of their members
(Self::Set(l0), r) | (r, Self::Set(l0)) => {
l0.iter().any(|l| l == r)
}
}
}
}
impl std::hash::Hash for Internal {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
Self::Unique(id) => id.hash(state),
Self::Custom(name, _) => name.hash(state),
Self::Set(ids) => ids.hash(state),
}
}
}
#[cfg(test)]
mod tests {
use super::Id;
#[test]
fn unique_generates_different_ids() {
let a = Id::unique();
let b = Id::unique();
assert_ne!(a, b);
}
}