1use serde::{Deserialize, Serialize};
25use std::any::Any;
26use std::fmt;
27use std::sync::{Arc, RwLock};
28
29type SubscriberFn<T> = Box<dyn Fn(&T) + Send + Sync>;
31
32type Subscribers<T> = Arc<RwLock<Vec<SubscriberFn<T>>>>;
34
35#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
39pub struct PropertyPath {
40 segments: Vec<String>,
41}
42
43impl PropertyPath {
44 #[must_use]
46 pub fn new(path: &str) -> Self {
47 let segments = path
48 .split('.')
49 .filter(|s| !s.is_empty())
50 .map(String::from)
51 .collect();
52 Self { segments }
53 }
54
55 #[must_use]
57 pub const fn root() -> Self {
58 Self {
59 segments: Vec::new(),
60 }
61 }
62
63 #[must_use]
65 pub fn segments(&self) -> &[String] {
66 &self.segments
67 }
68
69 #[must_use]
71 pub fn is_root(&self) -> bool {
72 self.segments.is_empty()
73 }
74
75 #[must_use]
77 pub fn len(&self) -> usize {
78 self.segments.len()
79 }
80
81 #[must_use]
83 pub fn is_empty(&self) -> bool {
84 self.segments.is_empty()
85 }
86
87 #[must_use]
89 pub fn join(&self, segment: &str) -> Self {
90 let mut segments = self.segments.clone();
91 segments.push(segment.to_string());
92 Self { segments }
93 }
94
95 #[must_use]
97 pub fn parent(&self) -> Option<Self> {
98 if self.segments.is_empty() {
99 None
100 } else {
101 let mut segments = self.segments.clone();
102 segments.pop();
103 Some(Self { segments })
104 }
105 }
106
107 #[must_use]
109 pub fn leaf(&self) -> Option<&str> {
110 self.segments.last().map(String::as_str)
111 }
112
113 #[must_use]
115 pub fn to_string_path(&self) -> String {
116 self.segments.join(".")
117 }
118}
119
120impl fmt::Display for PropertyPath {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 write!(f, "{}", self.to_string_path())
123 }
124}
125
126impl From<&str> for PropertyPath {
127 fn from(s: &str) -> Self {
128 Self::new(s)
129 }
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
134pub enum BindingDirection {
135 #[default]
137 OneWay,
138 TwoWay,
140 OneTime,
142}
143
144#[derive(Debug, Clone)]
146pub struct BindingConfig {
147 pub source: PropertyPath,
149 pub target: String,
151 pub direction: BindingDirection,
153 pub transform: Option<String>,
155 pub fallback: Option<String>,
157}
158
159impl BindingConfig {
160 #[must_use]
162 pub fn one_way(source: impl Into<PropertyPath>, target: impl Into<String>) -> Self {
163 Self {
164 source: source.into(),
165 target: target.into(),
166 direction: BindingDirection::OneWay,
167 transform: None,
168 fallback: None,
169 }
170 }
171
172 #[must_use]
174 pub fn two_way(source: impl Into<PropertyPath>, target: impl Into<String>) -> Self {
175 Self {
176 source: source.into(),
177 target: target.into(),
178 direction: BindingDirection::TwoWay,
179 transform: None,
180 fallback: None,
181 }
182 }
183
184 #[must_use]
186 pub fn transform(mut self, name: impl Into<String>) -> Self {
187 self.transform = Some(name.into());
188 self
189 }
190
191 #[must_use]
193 pub fn fallback(mut self, value: impl Into<String>) -> Self {
194 self.fallback = Some(value.into());
195 self
196 }
197}
198
199pub trait Bindable: Any + Send + Sync {
201 fn bindings(&self) -> Vec<BindingConfig>;
203
204 fn set_bindings(&mut self, bindings: Vec<BindingConfig>);
206
207 fn apply_binding(&mut self, target: &str, value: &dyn Any) -> bool;
209
210 fn get_binding_value(&self, target: &str) -> Option<Box<dyn Any + Send>>;
212}
213
214pub struct ReactiveCell<T> {
216 value: Arc<RwLock<T>>,
217 subscribers: Subscribers<T>,
218}
219
220impl<T: Clone + Send + Sync + 'static> ReactiveCell<T> {
221 pub fn new(value: T) -> Self {
223 Self {
224 value: Arc::new(RwLock::new(value)),
225 subscribers: Arc::new(RwLock::new(Vec::new())),
226 }
227 }
228
229 pub fn get(&self) -> T {
231 self.value
232 .read()
233 .expect("ReactiveCell lock poisoned")
234 .clone()
235 }
236
237 pub fn set(&self, value: T) {
239 {
240 let mut guard = self.value.write().expect("ReactiveCell lock poisoned");
241 *guard = value;
242 }
243 self.notify();
244 }
245
246 pub fn update<F>(&self, f: F)
248 where
249 F: FnOnce(&mut T),
250 {
251 {
252 let mut guard = self.value.write().expect("ReactiveCell lock poisoned");
253 f(&mut guard);
254 }
255 self.notify();
256 }
257
258 pub fn subscribe<F>(&self, callback: F)
260 where
261 F: Fn(&T) + Send + Sync + 'static,
262 {
263 self.subscribers
264 .write()
265 .expect("ReactiveCell lock poisoned")
266 .push(Box::new(callback));
267 }
268
269 fn notify(&self) {
270 let value = self.value.read().expect("ReactiveCell lock poisoned");
271 let subscribers = self.subscribers.read().expect("ReactiveCell lock poisoned");
272 for sub in subscribers.iter() {
273 sub(&value);
274 }
275 }
276}
277
278impl<T: Clone + Send + Sync> Clone for ReactiveCell<T> {
279 fn clone(&self) -> Self {
280 Self {
281 value: self.value.clone(),
282 subscribers: Arc::new(RwLock::new(Vec::new())), }
284 }
285}
286
287impl<T: Clone + Send + Sync + Default + 'static> Default for ReactiveCell<T> {
288 fn default() -> Self {
289 Self::new(T::default())
290 }
291}
292
293impl<T: Clone + Send + Sync + fmt::Debug + 'static> fmt::Debug for ReactiveCell<T> {
294 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295 f.debug_struct("ReactiveCell")
296 .field(
297 "value",
298 &*self.value.read().expect("ReactiveCell lock poisoned"),
299 )
300 .finish_non_exhaustive()
301 }
302}
303
304pub struct Computed<T> {
306 #[allow(dead_code)]
307 compute: Box<dyn Fn() -> T + Send + Sync>,
308 cached: Arc<RwLock<Option<T>>>,
309 dirty: Arc<RwLock<bool>>,
310}
311
312impl<T: Clone + Send + Sync + 'static> Computed<T> {
313 pub fn new<F>(compute: F) -> Self
315 where
316 F: Fn() -> T + Send + Sync + 'static,
317 {
318 Self {
319 compute: Box::new(compute),
320 cached: Arc::new(RwLock::new(None)),
321 dirty: Arc::new(RwLock::new(true)),
322 }
323 }
324
325 pub fn get(&self) -> T {
327 let dirty = *self.dirty.read().expect("Computed lock poisoned");
328 if dirty {
329 let value = (self.compute)();
330 *self.cached.write().expect("Computed lock poisoned") = Some(value.clone());
331 *self.dirty.write().expect("Computed lock poisoned") = false;
332 value
333 } else {
334 self.cached
335 .read()
336 .expect("Computed lock poisoned")
337 .clone()
338 .expect("Computed cache should contain value when not dirty")
339 }
340 }
341
342 pub fn invalidate(&self) {
344 *self.dirty.write().expect("Computed lock poisoned") = true;
345 }
346}
347
348impl<T: Clone + Send + Sync + fmt::Debug + 'static> fmt::Debug for Computed<T> {
349 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350 f.debug_struct("Computed")
351 .field(
352 "cached",
353 &*self.cached.read().expect("Computed lock poisoned"),
354 )
355 .field(
356 "dirty",
357 &*self.dirty.read().expect("Computed lock poisoned"),
358 )
359 .finish_non_exhaustive()
360 }
361}
362
363#[derive(Debug, Clone, Serialize, Deserialize)]
365pub struct BindingExpression {
366 pub expression: String,
368 pub dependencies: Vec<PropertyPath>,
370}
371
372impl BindingExpression {
373 #[must_use]
375 pub fn new(expression: impl Into<String>) -> Self {
376 let expression = expression.into();
377 let dependencies = Self::parse_dependencies(&expression);
378 Self {
379 expression,
380 dependencies,
381 }
382 }
383
384 #[must_use]
386 pub fn property(path: impl Into<PropertyPath>) -> Self {
387 let path: PropertyPath = path.into();
388 let expression = format!("{{{{ {} }}}}", path.to_string_path());
389 Self {
390 expression,
391 dependencies: vec![path],
392 }
393 }
394
395 #[must_use]
397 pub fn is_simple_property(&self) -> bool {
398 self.dependencies.len() == 1
399 && self.expression.trim().starts_with("{{")
400 && self.expression.trim().ends_with("}}")
401 }
402
403 #[must_use]
405 pub fn as_property(&self) -> Option<&PropertyPath> {
406 if self.is_simple_property() {
407 self.dependencies.first()
408 } else {
409 None
410 }
411 }
412
413 fn parse_dependencies(expression: &str) -> Vec<PropertyPath> {
414 let mut deps = Vec::new();
415 let mut in_binding = false;
416 let mut current = String::new();
417
418 let chars: Vec<char> = expression.chars().collect();
419 let mut i = 0;
420
421 while i < chars.len() {
422 if i + 1 < chars.len() && chars[i] == '{' && chars[i + 1] == '{' {
423 in_binding = true;
424 i += 2;
425 continue;
426 }
427
428 if i + 1 < chars.len() && chars[i] == '}' && chars[i + 1] == '}' {
429 if !current.is_empty() {
430 let path_str = current.split('|').next().unwrap_or("").trim();
432 if !path_str.is_empty() && !path_str.contains(|c: char| c.is_whitespace()) {
433 deps.push(PropertyPath::new(path_str));
434 }
435 current.clear();
436 }
437 in_binding = false;
438 i += 2;
439 continue;
440 }
441
442 if in_binding {
443 current.push(chars[i]);
444 }
445
446 i += 1;
447 }
448
449 deps
450 }
451}
452
453#[derive(Debug, Clone, Serialize, Deserialize)]
455pub struct EventBinding {
456 pub event: String,
458 pub action: ActionBinding,
460}
461
462#[derive(Debug, Clone, Serialize, Deserialize)]
464pub enum ActionBinding {
465 SetProperty {
467 path: PropertyPath,
469 value: String,
471 },
472 ToggleProperty {
474 path: PropertyPath,
476 },
477 IncrementProperty {
479 path: PropertyPath,
481 amount: Option<f64>,
483 },
484 Navigate {
486 route: String,
488 },
489 Dispatch {
491 message: String,
493 payload: Option<String>,
495 },
496 Batch {
498 actions: Vec<Self>,
500 },
501}
502
503impl EventBinding {
504 #[must_use]
506 pub fn new(event: impl Into<String>, action: ActionBinding) -> Self {
507 Self {
508 event: event.into(),
509 action,
510 }
511 }
512
513 #[must_use]
515 pub fn on_click(action: ActionBinding) -> Self {
516 Self::new("click", action)
517 }
518
519 #[must_use]
521 pub fn on_change(action: ActionBinding) -> Self {
522 Self::new("change", action)
523 }
524}
525
526impl ActionBinding {
527 #[must_use]
529 pub fn set(path: impl Into<PropertyPath>, value: impl Into<String>) -> Self {
530 Self::SetProperty {
531 path: path.into(),
532 value: value.into(),
533 }
534 }
535
536 #[must_use]
538 pub fn toggle(path: impl Into<PropertyPath>) -> Self {
539 Self::ToggleProperty { path: path.into() }
540 }
541
542 #[must_use]
544 pub fn increment(path: impl Into<PropertyPath>) -> Self {
545 Self::IncrementProperty {
546 path: path.into(),
547 amount: None,
548 }
549 }
550
551 #[must_use]
553 pub fn increment_by(path: impl Into<PropertyPath>, amount: f64) -> Self {
554 Self::IncrementProperty {
555 path: path.into(),
556 amount: Some(amount),
557 }
558 }
559
560 #[must_use]
562 pub fn navigate(route: impl Into<String>) -> Self {
563 Self::Navigate {
564 route: route.into(),
565 }
566 }
567
568 #[must_use]
570 pub fn dispatch(message: impl Into<String>) -> Self {
571 Self::Dispatch {
572 message: message.into(),
573 payload: None,
574 }
575 }
576
577 #[must_use]
579 pub fn dispatch_with(message: impl Into<String>, payload: impl Into<String>) -> Self {
580 Self::Dispatch {
581 message: message.into(),
582 payload: Some(payload.into()),
583 }
584 }
585
586 #[must_use]
588 pub fn batch(actions: impl IntoIterator<Item = Self>) -> Self {
589 Self::Batch {
590 actions: actions.into_iter().collect(),
591 }
592 }
593}
594
595#[derive(Debug, Default)]
607pub struct BindingManager {
608 bindings: Vec<ActiveBinding>,
610 debounce_ms: Option<u32>,
612 pending_updates: Vec<PendingUpdate>,
614}
615
616#[derive(Debug, Clone)]
618pub struct ActiveBinding {
619 pub id: BindingId,
621 pub widget_id: String,
623 pub config: BindingConfig,
625 pub current_value: Option<String>,
627 pub active: bool,
629}
630
631#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
633pub struct BindingId(pub u64);
634
635#[derive(Debug, Clone)]
637pub struct PendingUpdate {
638 pub source: UpdateSource,
640 pub path: PropertyPath,
642 pub value: String,
644 pub timestamp: u64,
646}
647
648#[derive(Debug, Clone, Copy, PartialEq, Eq)]
650pub enum UpdateSource {
651 State,
653 Widget,
655}
656
657impl BindingManager {
658 #[must_use]
660 pub fn new() -> Self {
661 Self::default()
662 }
663
664 #[must_use]
666 pub fn with_debounce(mut self, ms: u32) -> Self {
667 self.debounce_ms = Some(ms);
668 self
669 }
670
671 pub fn register(&mut self, widget_id: impl Into<String>, config: BindingConfig) -> BindingId {
673 let id = BindingId(self.bindings.len() as u64);
674 self.bindings.push(ActiveBinding {
675 id,
676 widget_id: widget_id.into(),
677 config,
678 current_value: None,
679 active: true,
680 });
681 id
682 }
683
684 pub fn unregister(&mut self, id: BindingId) {
686 if let Some(binding) = self.bindings.iter_mut().find(|b| b.id == id) {
687 binding.active = false;
688 }
689 }
690
691 #[must_use]
693 pub fn bindings_for_widget(&self, widget_id: &str) -> Vec<&ActiveBinding> {
694 self.bindings
695 .iter()
696 .filter(|b| b.active && b.widget_id == widget_id)
697 .collect()
698 }
699
700 #[must_use]
702 pub fn bindings_for_path(&self, path: &PropertyPath) -> Vec<&ActiveBinding> {
703 self.bindings
704 .iter()
705 .filter(|b| b.active && &b.config.source == path)
706 .collect()
707 }
708
709 pub fn on_state_change(&mut self, path: &PropertyPath, value: &str) -> Vec<WidgetUpdate> {
711 let mut updates = Vec::new();
712
713 for binding in &mut self.bindings {
714 if !binding.active {
715 continue;
716 }
717
718 if &binding.config.source == path
720 || path
721 .to_string_path()
722 .starts_with(&binding.config.source.to_string_path())
723 {
724 binding.current_value = Some(value.to_string());
725
726 updates.push(WidgetUpdate {
727 widget_id: binding.widget_id.clone(),
728 property: binding.config.target.clone(),
729 value: value.to_string(),
730 });
731 }
732 }
733
734 updates
735 }
736
737 pub fn on_widget_change(
739 &mut self,
740 widget_id: &str,
741 property: &str,
742 value: &str,
743 ) -> Vec<StateUpdate> {
744 let mut updates = Vec::new();
745
746 for binding in &self.bindings {
747 if !binding.active {
748 continue;
749 }
750
751 if binding.config.direction != BindingDirection::TwoWay {
753 continue;
754 }
755
756 if binding.widget_id == widget_id && binding.config.target == property {
757 updates.push(StateUpdate {
758 path: binding.config.source.clone(),
759 value: value.to_string(),
760 });
761 }
762 }
763
764 updates
765 }
766
767 pub fn queue_update(&mut self, source: UpdateSource, path: PropertyPath, value: String) {
769 self.pending_updates.push(PendingUpdate {
770 source,
771 path,
772 value,
773 timestamp: 0, });
775 }
776
777 pub fn flush(&mut self) -> (Vec<WidgetUpdate>, Vec<StateUpdate>) {
779 let mut widget_updates = Vec::new();
780 let mut state_updates = Vec::new();
781
782 let updates: Vec<PendingUpdate> = self.pending_updates.drain(..).collect();
784
785 for update in updates {
786 match update.source {
787 UpdateSource::State => {
788 widget_updates.extend(self.on_state_change(&update.path, &update.value));
789 }
790 UpdateSource::Widget => {
791 state_updates.push(StateUpdate {
793 path: update.path,
794 value: update.value,
795 });
796 }
797 }
798 }
799
800 (widget_updates, state_updates)
801 }
802
803 #[must_use]
805 pub fn active_count(&self) -> usize {
806 self.bindings.iter().filter(|b| b.active).count()
807 }
808
809 pub fn clear(&mut self) {
811 self.bindings.clear();
812 self.pending_updates.clear();
813 }
814}
815
816#[derive(Debug, Clone)]
818pub struct WidgetUpdate {
819 pub widget_id: String,
821 pub property: String,
823 pub value: String,
825}
826
827#[derive(Debug, Clone)]
829pub struct StateUpdate {
830 pub path: PropertyPath,
832 pub value: String,
834}
835
836pub trait ValueConverter: Send + Sync {
842 fn convert(&self, value: &str) -> Result<String, ConversionError>;
844
845 fn convert_back(&self, value: &str) -> Result<String, ConversionError>;
847}
848
849#[derive(Debug, Clone)]
851pub struct ConversionError {
852 pub message: String,
854}
855
856impl fmt::Display for ConversionError {
857 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
858 write!(f, "conversion error: {}", self.message)
859 }
860}
861
862impl std::error::Error for ConversionError {}
863
864#[derive(Debug, Default)]
866pub struct IdentityConverter;
867
868impl ValueConverter for IdentityConverter {
869 fn convert(&self, value: &str) -> Result<String, ConversionError> {
870 Ok(value.to_string())
871 }
872
873 fn convert_back(&self, value: &str) -> Result<String, ConversionError> {
874 Ok(value.to_string())
875 }
876}
877
878#[derive(Debug, Default)]
880pub struct BoolToStringConverter {
881 pub true_string: String,
883 pub false_string: String,
885}
886
887impl BoolToStringConverter {
888 #[must_use]
890 pub fn new() -> Self {
891 Self {
892 true_string: "true".to_string(),
893 false_string: "false".to_string(),
894 }
895 }
896
897 #[must_use]
899 pub fn with_strings(true_str: impl Into<String>, false_str: impl Into<String>) -> Self {
900 Self {
901 true_string: true_str.into(),
902 false_string: false_str.into(),
903 }
904 }
905}
906
907impl ValueConverter for BoolToStringConverter {
908 fn convert(&self, value: &str) -> Result<String, ConversionError> {
909 match value {
910 "true" | "1" | "yes" => Ok(self.true_string.clone()),
911 "false" | "0" | "no" => Ok(self.false_string.clone()),
912 _ => Err(ConversionError {
913 message: format!("cannot convert '{value}' to bool"),
914 }),
915 }
916 }
917
918 fn convert_back(&self, value: &str) -> Result<String, ConversionError> {
919 if value == self.true_string {
920 Ok("true".to_string())
921 } else if value == self.false_string {
922 Ok("false".to_string())
923 } else {
924 Err(ConversionError {
925 message: format!("cannot convert '{value}' back to bool"),
926 })
927 }
928 }
929}
930
931#[derive(Debug, Default)]
933pub struct NumberFormatConverter {
934 pub decimals: usize,
936 pub prefix: String,
938 pub suffix: String,
940}
941
942impl NumberFormatConverter {
943 #[must_use]
945 pub fn new() -> Self {
946 Self::default()
947 }
948
949 #[must_use]
951 pub fn decimals(mut self, places: usize) -> Self {
952 self.decimals = places;
953 self
954 }
955
956 #[must_use]
958 pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
959 self.prefix = prefix.into();
960 self
961 }
962
963 #[must_use]
965 pub fn suffix(mut self, suffix: impl Into<String>) -> Self {
966 self.suffix = suffix.into();
967 self
968 }
969}
970
971impl ValueConverter for NumberFormatConverter {
972 fn convert(&self, value: &str) -> Result<String, ConversionError> {
973 let num: f64 = value.parse().map_err(|_| ConversionError {
974 message: format!("cannot parse '{value}' as number"),
975 })?;
976
977 let formatted = format!("{:.prec$}", num, prec = self.decimals);
978 Ok(format!("{}{}{}", self.prefix, formatted, self.suffix))
979 }
980
981 fn convert_back(&self, value: &str) -> Result<String, ConversionError> {
982 let stripped = value
984 .strip_prefix(&self.prefix)
985 .unwrap_or(value)
986 .strip_suffix(&self.suffix)
987 .unwrap_or(value)
988 .trim();
989
990 let _: f64 = stripped.parse().map_err(|_| ConversionError {
992 message: format!("cannot parse '{stripped}' as number"),
993 })?;
994
995 Ok(stripped.to_string())
996 }
997}
998
999#[cfg(test)]
1000#[allow(clippy::unwrap_used, clippy::disallowed_methods)]
1001#[path = "binding_tests.rs"]
1002mod tests;