Skip to main content

ferroui_controls/
i_command_source.rs

1//! The registry of the classes that implement `ICommandSource` (declared in
2//! the base crate's input module).
3//!
4//! A class cannot implement the trait itself (its methods would shadow the
5//! inherent ones of the class): it provides a small adapter handle that
6//! does, and makes it known with [`register_command_source`], normally from
7//! its class initialization. [`as_command_source`] then views an object of
8//! that class (or of a class derived from it) as a command source.
9
10use ferroui_base::input::ICommandSource;
11use ferroui_base::{FerroObject, ObjectType, Ref, TypeInfo};
12use std::cell::RefCell;
13use std::rc::Rc;
14
15type Adapter = Rc<dyn Fn(&FerroObject) -> Option<Rc<dyn ICommandSource>>>;
16
17ferroui_base::ferro_thread_local! {
18    static SOURCE_TYPES: RefCell<Vec<(&'static TypeInfo, Adapter)>> = const { RefCell::new(Vec::new()) };
19}
20
21/// Declares that class `T` (and the classes derived from it) implements
22/// [`ICommandSource`] through the handle created by `adapter`.
23pub fn register_command_source<T: ObjectType>(adapter: fn(Ref<T>) -> Rc<dyn ICommandSource>) {
24    SOURCE_TYPES.with(|types| {
25        let mut types = types.borrow_mut();
26        if !types.iter().any(|(type_, _)| *type_ == T::TYPE) {
27            let adapter: Adapter =
28                Rc::new(move |object| object.downcast_ref::<T>().map(|target| adapter(FerroObject::ref_of(target))));
29            types.push((T::TYPE, adapter));
30        }
31    });
32}
33
34/// The object viewed as a command source, if its class implements
35/// [`ICommandSource`].
36pub fn as_command_source(object: &FerroObject) -> Option<Rc<dyn ICommandSource>> {
37    let adapter = SOURCE_TYPES.with(|types| {
38        let types = types.borrow();
39        let mut current = Some(object.get_type());
40        while let Some(type_) = current {
41            if let Some((_, adapter)) = types.iter().find(|(candidate, _)| *candidate == type_) {
42                return Some(adapter.clone());
43            }
44            current = type_.base_type();
45        }
46        None
47    });
48    adapter.and_then(|adapter| adapter(object))
49}