Skip to main content

ferroui_base/controls/
name_scope.rs

1use super::{INameScope, NameScopeError, NameScopeRef};
2use crate::utilities::{SynchronousCompletionAsyncResult, SynchronousCompletionAsyncResultSource};
3use crate::{ferro_property, AttachedProperty, FerroObject, FerroProperty, Ref, StyledElement, WeakRef};
4use std::cell::{Cell, RefCell};
5use std::collections::HashMap;
6
7type FindResult = Option<Ref<FerroObject>>;
8
9/// An element of a name scope. Deviation (DEVIATIONS.md, Name scopes): the
10/// element the scope is attached to holds the scope, so the scope holds that
11/// one weakly: held as a reference, as
12/// the managed original holds every element for its collector, an element
13/// with a name of its own and its scope would keep each other alive. Every
14/// other element is held as a reference.
15enum Named {
16    Element(Ref<FerroObject>),
17    Owner(WeakRef<FerroObject>),
18}
19
20impl Named {
21    fn element(&self) -> Option<Ref<FerroObject>> {
22        match self {
23            Named::Element(element) => Some(element.clone()),
24            Named::Owner(owner) => owner.upgrade(),
25        }
26    }
27
28    fn is(&self, element: &Ref<FerroObject>) -> bool {
29        match self {
30            Named::Element(named) => named == element,
31            Named::Owner(owner) => owner.points_to(element),
32        }
33    }
34}
35
36/// Implements a name scope.
37#[derive(Default)]
38pub struct NameScope {
39    is_completed: Cell<bool>,
40    inner: RefCell<HashMap<String, Named>>,
41    // The elements the scope is attached to (`NameScope::set_name_scope`).
42    owners: RefCell<Vec<WeakRef<FerroObject>>>,
43    pending_searches: RefCell<HashMap<String, SynchronousCompletionAsyncResultSource<FindResult>>>,
44}
45
46crate::ferro_static_type!(NameScope);
47
48crate::ferro_properties! { impl NameScope {
49    ferro_property!(
50        /// Defines the NameScope attached property.
51        pub fn name_scope_property() -> AttachedProperty<Option<NameScopeRef>> {
52            FerroProperty::register_attached::<NameScope, StyledElement, _>("NameScope", None)
53        }
54    );
55} }
56
57impl NameScope {
58    pub fn new() -> Self {
59        Self::default()
60    }
61
62    /// Gets the value of the attached NameScope property on a styled element.
63    pub fn get_name_scope(styled: &StyledElement) -> Option<NameScopeRef> {
64        styled.get_value(Self::name_scope_property())
65    }
66
67    /// Sets the value of the attached NameScope property on a styled element.
68    ///
69    /// The scope is told which element holds it, and the scope the element
70    /// held before that it no longer does (see [`INameScope::attached_to`]).
71    pub fn set_name_scope(styled: &StyledElement, value: Option<NameScopeRef>) {
72        let owner = styled.to_ref().upcast::<FerroObject>();
73        let previous = Self::get_name_scope(styled);
74        if previous == value {
75            return;
76        }
77        if let Some(previous) = &previous {
78            previous.detached_from(&owner);
79        }
80        styled.set_value(Self::name_scope_property(), value.clone());
81        if let Some(scope) = &value {
82            scope.attached_to(&owner);
83        }
84    }
85
86    fn is_owner(&self, element: &Ref<FerroObject>) -> bool {
87        self.owners.borrow().iter().any(|owner| owner.points_to(element))
88    }
89}
90
91impl INameScope for NameScope {
92    fn try_register(&self, name: &str, element: Ref<FerroObject>) -> Result<(), NameScopeError> {
93        if self.is_completed.get() {
94            return Err(NameScopeError::Completed);
95        }
96
97        // A name whose element was dropped (the element the scope was
98        // attached to) stays taken, as it is while the element is alive.
99        let existing = self.inner.borrow().get(name).map(|existing| existing.is(&element));
100        match existing {
101            Some(is_element) => {
102                if !is_element {
103                    return Err(NameScopeError::DuplicateName(name.to_string()));
104                }
105            }
106            None => {
107                let named =
108                    if self.is_owner(&element) { Named::Owner(element.downgrade()) } else { Named::Element(element.clone()) };
109                self.inner.borrow_mut().insert(name.to_string(), named);
110                let pending = self.pending_searches.borrow_mut().remove(name);
111                if let Some(tcs) = pending {
112                    tcs.set_result(Some(element));
113                }
114            }
115        }
116        Ok(())
117    }
118
119    fn find_async(&self, name: &str) -> SynchronousCompletionAsyncResult<FindResult> {
120        let found = self.find(name);
121        if found.is_some() {
122            return SynchronousCompletionAsyncResult::new(found);
123        }
124        if self.is_completed.get() {
125            return SynchronousCompletionAsyncResult::new(None);
126        }
127        // Continuations intentionally run synchronously here.
128        self.pending_searches.borrow_mut().entry(name.to_string()).or_default().async_result()
129    }
130
131    fn find(&self, name: &str) -> FindResult {
132        self.inner.borrow().get(name).and_then(Named::element)
133    }
134
135    fn complete(&self) {
136        self.is_completed.set(true);
137        let pending = std::mem::take(&mut *self.pending_searches.borrow_mut());
138        for (_, tcs) in pending {
139            tcs.try_set_result(None);
140        }
141    }
142
143    fn is_completed(&self) -> bool {
144        self.is_completed.get()
145    }
146
147    fn attached_to(&self, owner: &Ref<FerroObject>) {
148        let mut owners = self.owners.borrow_mut();
149        owners.retain(|known| known.upgrade().is_some());
150        if !owners.iter().any(|known| known.points_to(owner)) {
151            owners.push(owner.downgrade());
152        }
153        drop(owners);
154        for named in self.inner.borrow_mut().values_mut() {
155            if matches!(named, Named::Element(element) if element == owner) {
156                *named = Named::Owner(owner.downgrade());
157            }
158        }
159    }
160
161    fn detached_from(&self, owner: &Ref<FerroObject>) {
162        self.owners.borrow_mut().retain(|known| known.upgrade().is_some() && !known.points_to(owner));
163        for named in self.inner.borrow_mut().values_mut() {
164            if matches!(named, Named::Owner(known) if known.points_to(owner)) {
165                *named = Named::Element(owner.clone());
166            }
167        }
168    }
169}