ferroui_base/input/
input_scope.rs1use super::{text_input::TextInputContentType, InputScopeNameValue};
5use crate::collections::FerroList;
6use crate::metadata::IAddChild;
7use crate::reactive::{Disposable, IDisposable};
8use crate::utilities::{FormatError, HandlerList};
9use crate::{
10 ferro_class, instantiate, FerroObject, FerroObjectImpl, FerroObjectImplExt, FerroProperty, Ref,
11 StyledProperty,
12};
13use std::cell::{Cell, RefCell};
14use std::collections::HashSet;
15use std::rc::Rc;
16
17#[repr(C)]
20pub struct InputScopeName {
21 base: FerroObject,
22}
23ferro_class!(InputScopeName: FerroObject);
24crate::ferro_impl_classes!(InputScopeName: FerroObjectImpl);
25crate::ferro_class_info!(InputScopeName {
26 new: InputScopeName::new,
27 markup: {
28 content: NameValue,
29 constructors: [(InputScopeNameValue) => InputScopeName::with_name_value],
30 },
31});
32crate::ferro_properties! { impl InputScopeName {
33 pub fn name_value_property() -> StyledProperty<InputScopeNameValue> {
34 FerroProperty::register::<InputScopeName, _>("NameValue", InputScopeNameValue::Default)
35 }
36} }
37
38impl InputScopeName {
39 pub fn construct() -> Self {
40 Self {
41 base: FerroObject::construct(),
42 }
43 }
44 pub fn new() -> Ref<Self> {
45 instantiate(Self::construct())
46 }
47 pub fn with_name_value(name_value: InputScopeNameValue) -> Ref<Self> {
48 let result = Self::new();
49 result.set_name_value(name_value);
50 result
51 }
52 pub fn parse(value: &str) -> Result<Ref<Self>, FormatError> {
53 InputScopeNameValue::parse(value).map(Self::with_name_value)
54 }
55 pub fn name_value(&self) -> InputScopeNameValue {
56 self.get_value(Self::name_value_property())
57 }
58 pub fn set_name_value(&self, value: InputScopeNameValue) {
59 self.set_value(Self::name_value_property(), value);
60 }
61}
62
63#[repr(C)]
66pub struct InputScope {
67 base: FerroObject,
68 names: FerroList<Ref<InputScopeName>>,
69 collection_subscription: Cell<Option<u64>>,
70 item_subscriptions: RefCell<Vec<Rc<dyn IDisposable>>>,
71 changed: Rc<HandlerList<dyn Fn()>>,
72}
73ferro_class!(InputScope: FerroObject);
74crate::ferro_class_info!(InputScope {
75 new: InputScope::new,
76 interfaces: [Rc<dyn IAddChild<Ref<InputScopeName>>> => InputScope::as_add_child],
77 markup: {
78 content: Names,
79 parse: InputScope::parse,
80 properties: [Names: FerroList<Ref<InputScopeName>> { get: InputScope::names }],
81 },
82});
83
84impl FerroObjectImpl for InputScope {
85 fn constructed(this: &Self) {
86 Self::parent_constructed(this);
87 let weak = this.to_ref().downgrade();
88 let token = this.names.add_collection_changed(Rc::new(move |_| {
89 if let Some(this) = weak.upgrade() {
90 this.refresh_item_subscriptions();
91 this.invalidate();
92 }
93 }));
94 this.collection_subscription.set(Some(token));
95 }
96}
97
98impl InputScope {
99 pub fn construct() -> Self {
100 Self {
101 base: FerroObject::construct(),
102 names: FerroList::new(),
103 collection_subscription: Cell::new(None),
104 item_subscriptions: RefCell::new(Vec::new()),
105 changed: Rc::new(HandlerList::new()),
106 }
107 }
108 pub fn new() -> Ref<Self> {
109 instantiate(Self::construct())
110 }
111 pub fn with_name_value(value: InputScopeNameValue) -> Ref<Self> {
112 let result = Self::new();
113 result.names.add(InputScopeName::with_name_value(value));
114 result
115 }
116 pub fn parse(value: &str) -> Result<Ref<Self>, FormatError> {
119 let legacy = [
120 ("Normal", InputScopeNameValue::Default),
121 ("Alpha", InputScopeNameValue::AlphanumericHalfWidth),
122 ("Email", InputScopeNameValue::EmailSmtpAddress),
123 ("Name", InputScopeNameValue::PersonalFullName),
124 ("Pin", InputScopeNameValue::NumericPin),
125 ("Social", InputScopeNameValue::Chat),
126 ]
127 .into_iter()
128 .find_map(|(name, scope)| name.eq_ignore_ascii_case(value.trim()).then_some(scope));
129 legacy
130 .map(Ok)
131 .unwrap_or_else(|| InputScopeNameValue::parse(value))
132 .map(Self::with_name_value)
133 }
134 pub fn names(&self) -> FerroList<Ref<InputScopeName>> {
135 self.names.clone()
136 }
137 pub fn snapshot(&self) -> Rc<[InputScopeNameValue]> {
139 self.names
140 .snapshot()
141 .iter()
142 .map(|name| name.name_value())
143 .collect()
144 }
145 pub fn changed(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
146 let token = self.changed.add(Rc::new(handler));
147 let weak = Rc::downgrade(&self.changed);
148 Disposable::create(move || {
149 if let Some(handlers) = weak.upgrade() {
150 handlers.remove(token);
151 }
152 })
153 }
154 pub fn as_add_child(this: Ref<Self>) -> Rc<dyn IAddChild<Ref<InputScopeName>>> {
155 Rc::new(InputScopeAddChild(this))
156 }
157 fn refresh_item_subscriptions(&self) {
158 let old = std::mem::take(&mut *self.item_subscriptions.borrow_mut());
159 for subscription in old {
160 subscription.dispose();
161 }
162 let mut subscriptions = Vec::new();
163 let mut seen = HashSet::new();
164 for name in self.names.snapshot().iter() {
165 if !seen.insert(name.clone()) {
166 continue;
167 }
168 let weak = self.to_ref().downgrade();
169 subscriptions.push(name.property_changed(move |change| {
170 if change.property() == InputScopeName::name_value_property().as_property() {
171 if let Some(this) = weak.upgrade() {
172 this.invalidate();
173 }
174 }
175 }));
176 }
177 *self.item_subscriptions.borrow_mut() = subscriptions;
178 }
179 fn invalidate(&self) {
180 for (_, handler) in self.changed.snapshot().iter() {
181 handler();
182 }
183 }
184}
185
186impl Drop for InputScope {
187 fn drop(&mut self) {
188 if let Some(token) = self.collection_subscription.take() {
189 self.names.remove_collection_changed(token);
190 }
191 for subscription in self.item_subscriptions.get_mut().drain(..) {
192 subscription.dispose();
193 }
194 }
195}
196
197impl From<TextInputContentType> for Option<Ref<InputScope>> {
200 fn from(value: TextInputContentType) -> Self {
201 Some(InputScope::with_name_value(value.into()))
202 }
203}
204impl From<InputScopeNameValue> for Option<Ref<InputScope>> {
205 fn from(value: InputScopeNameValue) -> Self {
206 Some(InputScope::with_name_value(value))
207 }
208}
209
210struct InputScopeAddChild(Ref<InputScope>);
211impl IAddChild<Ref<InputScopeName>> for InputScopeAddChild {
212 fn add_child(&self, value: Ref<InputScopeName>) {
213 self.0.names.add(value);
214 }
215 fn reference_id(&self) -> usize {
216 &*self.0 as *const InputScope as usize
217 }
218}