agui_widgets/plugins/
provider.rs

1use std::{
2    any::TypeId,
3    collections::{HashMap, HashSet},
4    sync::Arc,
5};
6
7use agui_core::{
8    context::{Notify, Value, WidgetContext},
9    event::WidgetEvent,
10    plugin::WidgetPlugin,
11    widget::WidgetId,
12};
13use parking_lot::Mutex;
14
15#[derive(Default)]
16pub struct Provider {
17    providers: Arc<Mutex<HashMap<TypeId, HashSet<WidgetId>>>>,
18    widgets: Arc<Mutex<HashMap<WidgetId, HashSet<TypeId>>>>,
19}
20
21impl WidgetPlugin for Provider {
22    fn on_update(&self, _ctx: &WidgetContext) {}
23
24    fn on_layout(&self, _ctx: &WidgetContext) {}
25
26    fn on_events(&self, _ctx: &WidgetContext, events: &[WidgetEvent]) {
27        for event in events {
28            if let WidgetEvent::Destroyed { widget_id, .. } = event {
29                if let Some(providing) = self.widgets.lock().remove(widget_id) {
30                    let mut providers = self.providers.lock();
31
32                    for type_id in providing {
33                        let widgets = providers.get_mut(&type_id).expect("provider map broken");
34
35                        widgets.remove(widget_id);
36                    }
37                }
38            }
39        }
40    }
41}
42
43pub trait ProviderExt<'ui> {
44    fn provide(&self, ctx: &WidgetContext);
45}
46
47impl<'ui, V> ProviderExt<'ui> for Notify<V>
48where
49    V: Value,
50{
51    /// Makes some local widget state available to any child widget.
52    fn provide(&self, ctx: &WidgetContext) {
53        let plugin = ctx.get_plugin_or::<Provider, _>(Provider::default);
54
55        let mut providers = plugin.providers.lock();
56
57        let type_id = TypeId::of::<V>();
58
59        providers
60            .entry(type_id)
61            .or_insert_with(HashSet::default)
62            .insert(ctx.get_self());
63
64        let mut widgets = plugin.widgets.lock();
65
66        let widget_id = ctx.get_self();
67
68        widgets
69            .entry(widget_id)
70            .or_insert_with(HashSet::new)
71            .insert(type_id);
72    }
73}
74
75pub trait ConsumerExt<'ui> {
76    fn consume<V>(&self) -> Option<Notify<V>>
77    where
78        V: Value;
79}
80
81impl<'ui> ConsumerExt<'ui> for WidgetContext<'ui> {
82    /// Makes some local widget state available to any child widget.
83    fn consume<V>(&self) -> Option<Notify<V>>
84    where
85        V: Value,
86    {
87        if let Some(plugin) = self.get_plugin::<Provider>() {
88            let providers = plugin.providers.lock();
89
90            if let Some(providers) = providers.get(&TypeId::of::<V>()) {
91                for parent_id in self.get_tree().iter_parents(self.get_self()) {
92                    if providers.contains(&parent_id) {
93                        return Some(
94                            self.get_state_for(parent_id, || panic!("provider state broken")),
95                        );
96                    }
97                }
98            }
99        }
100
101        // Fall back to global state
102        if let Some(state) = self.try_use_global::<V>() {
103            return Some(state);
104        }
105
106        None
107    }
108}