lgui_core/events/
contract.rs1use std::{
2 borrow::Borrow,
3 fmt,
4 future::Future,
5 hash::{Hash, Hasher},
6 marker::PhantomData,
7 pin::Pin,
8 sync::Arc,
9};
10
11use crate::core::UiAsyncContext;
12
13const MAX_EVENT_KEY_BYTES: usize = 128;
14
15pub struct EventKey<T> {
20 name: EventKeyName,
21 _payload: PhantomData<fn(T)>,
22}
23
24enum EventKeyName {
25 Static(&'static str),
26 Shared(Arc<str>),
27}
28
29impl<T> EventKey<T> {
30 pub const fn new(name: &'static str) -> Self {
35 assert_valid_static_key(name);
36 Self {
37 name: EventKeyName::Static(name),
38 _payload: PhantomData,
39 }
40 }
41
42 pub fn dynamic(name: impl Into<Arc<str>>) -> Result<Self, InvalidEventKey> {
45 let name = name.into();
46 validate_key(&name)?;
47 Ok(Self {
48 name: EventKeyName::Shared(name),
49 _payload: PhantomData,
50 })
51 }
52
53 pub fn as_str(&self) -> &str {
54 match &self.name {
55 EventKeyName::Static(name) => name,
56 EventKeyName::Shared(name) => name,
57 }
58 }
59
60 pub(crate) fn shared_name(&self) -> Arc<str> {
61 match &self.name {
62 EventKeyName::Static(name) => Arc::from(*name),
63 EventKeyName::Shared(name) => Arc::clone(name),
64 }
65 }
66}
67
68impl<T> Clone for EventKey<T> {
69 fn clone(&self) -> Self {
70 Self {
71 name: self.name.clone(),
72 _payload: PhantomData,
73 }
74 }
75}
76
77impl Clone for EventKeyName {
78 fn clone(&self) -> Self {
79 match self {
80 Self::Static(name) => Self::Static(name),
81 Self::Shared(name) => Self::Shared(Arc::clone(name)),
82 }
83 }
84}
85
86impl<T> fmt::Debug for EventKey<T> {
87 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
88 formatter
89 .debug_tuple("EventKey")
90 .field(&self.as_str())
91 .finish()
92 }
93}
94
95impl<T> fmt::Display for EventKey<T> {
96 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
97 formatter.write_str(self.as_str())
98 }
99}
100
101impl<T> PartialEq for EventKey<T> {
102 fn eq(&self, other: &Self) -> bool {
103 self.as_str() == other.as_str()
104 }
105}
106
107impl<T> Eq for EventKey<T> {}
108
109impl<T> Hash for EventKey<T> {
110 fn hash<H: Hasher>(&self, state: &mut H) {
111 self.as_str().hash(state);
112 }
113}
114
115impl<T> Borrow<str> for EventKey<T> {
116 fn borrow(&self) -> &str {
117 self.as_str()
118 }
119}
120
121#[derive(Clone, Debug, PartialEq, Eq)]
122pub struct InvalidEventKey {
123 name: Arc<str>,
124}
125
126impl InvalidEventKey {
127 pub fn name(&self) -> &str {
128 &self.name
129 }
130}
131
132impl fmt::Display for InvalidEventKey {
133 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
134 write!(
135 formatter,
136 "invalid event key `{}`; expected 1..={MAX_EVENT_KEY_BYTES} bytes containing only lowercase ASCII letters, digits, `.`, `_`, `-`, or `:`",
137 self.name
138 )
139 }
140}
141
142impl std::error::Error for InvalidEventKey {}
143
144const fn assert_valid_static_key(name: &str) {
145 let bytes = name.as_bytes();
146 assert!(!bytes.is_empty(), "event key must not be empty");
147 assert!(
148 bytes.len() <= MAX_EVENT_KEY_BYTES,
149 "event key exceeds 128 bytes"
150 );
151 let mut index = 0;
152 while index < bytes.len() {
153 let byte = bytes[index];
154 assert!(
155 matches!(byte, b'a'..=b'z' | b'0'..=b'9' | b'.' | b'_' | b'-' | b':'),
156 "event key contains an invalid byte"
157 );
158 index += 1;
159 }
160}
161
162fn validate_key(name: &str) -> Result<(), InvalidEventKey> {
163 let valid = !name.is_empty()
164 && name.len() <= MAX_EVENT_KEY_BYTES
165 && name
166 .bytes()
167 .all(|byte| matches!(byte, b'a'..=b'z' | b'0'..=b'9' | b'.' | b'_' | b'-' | b':'));
168 if valid {
169 Ok(())
170 } else {
171 Err(InvalidEventKey {
172 name: Arc::from(name),
173 })
174 }
175}
176
177pub trait Event: Clone + Send + Sync + 'static {
182 const NAME: &'static str;
183}
184
185pub type EventFuture = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
186
187pub trait AsyncEventHandler<E>: Send + Sync + 'static
188where
189 E: Event,
190{
191 fn call(&self, context: UiAsyncContext, event: E) -> EventFuture;
192}
193
194impl<E, F, Fut> AsyncEventHandler<E> for F
195where
196 E: Event,
197 F: Fn(UiAsyncContext, E) -> Fut + Send + Sync + 'static,
198 Fut: Future<Output = ()> + Send + 'static,
199{
200 fn call(&self, context: UiAsyncContext, event: E) -> EventFuture {
201 Box::pin((self)(context, event))
202 }
203}