ferroui_controls/notifications/
notification.rs1use super::{INotification, NotificationType};
2use ferroui_base::data::core::{Maybe, Value, ValueTypes};
3use ferroui_base::data::model::{Event, INotifyPropertyChanged, Model};
4use ferroui_base::{ferro_model, BoxedValue};
5use std::cell::{Cell, RefCell};
6use std::rc::Rc;
7use std::time::Duration;
8
9pub trait NotificationOverrides {
15 fn on_property_changed(&self, notification: &Notification, property_name: Option<&str>) {
18 notification.base_on_property_changed(property_name)
19 }
20}
21
22pub struct Notification {
30 overrides: Option<Rc<dyn NotificationOverrides>>,
31 title: RefCell<Option<String>>,
32 message: RefCell<Option<String>>,
33 type_: Cell<NotificationType>,
34 expiration: Cell<Duration>,
35 on_click: RefCell<Option<Rc<dyn Fn()>>>,
36 on_close: RefCell<Option<Rc<dyn Fn()>>>,
37 property_changed: Event<str>,
38}
39
40impl Notification {
41 const DEFAULT_EXPIRATION: Duration = Duration::from_secs(5);
43
44 pub fn new(
55 title: Option<String>,
56 message: Option<String>,
57 type_: NotificationType,
58 expiration: Option<Duration>,
59 on_click: Option<Rc<dyn Fn()>>,
60 on_close: Option<Rc<dyn Fn()>>,
61 ) -> Rc<Self> {
62 Self::create(title, message, type_, expiration, on_click, on_close, None)
63 }
64
65 pub fn empty() -> Rc<Self> {
68 Self::new(None, None, NotificationType::Information, None, None, None)
69 }
70
71 pub fn with_overrides(
74 title: Option<String>,
75 message: Option<String>,
76 type_: NotificationType,
77 expiration: Option<Duration>,
78 on_click: Option<Rc<dyn Fn()>>,
79 on_close: Option<Rc<dyn Fn()>>,
80 overrides: Rc<dyn NotificationOverrides>,
81 ) -> Rc<Self> {
82 Self::create(title, message, type_, expiration, on_click, on_close, Some(overrides))
83 }
84
85 fn create(
86 title: Option<String>,
87 message: Option<String>,
88 type_: NotificationType,
89 expiration: Option<Duration>,
90 on_click: Option<Rc<dyn Fn()>>,
91 on_close: Option<Rc<dyn Fn()>>,
92 overrides: Option<Rc<dyn NotificationOverrides>>,
93 ) -> Rc<Self> {
94 Self::ensure_registered();
95
96 let notification = Rc::new(Self {
97 overrides,
98 title: RefCell::new(None),
99 message: RefCell::new(None),
100 type_: Cell::new(type_),
101 expiration: Cell::new(expiration.unwrap_or(Self::DEFAULT_EXPIRATION)),
102 on_click: RefCell::new(on_click),
103 on_close: RefCell::new(on_close),
104 property_changed: Event::new(),
105 });
106 notification.set_title(title);
109 notification.set_message(message);
110 notification
111 }
112
113 fn ensure_registered() {
116 ferroui_base::ferro_thread_local! {
117 static REGISTERED: Cell<bool> = const { Cell::new(false) };
118 }
119
120 if REGISTERED.replace(true) {
121 return;
122 }
123
124 <Self as Model>::register();
125 ValueTypes::register_boxed_cast::<Notification, Rc<dyn INotification>>(|object| {
126 let any: Rc<dyn std::any::Any> = object.clone();
127 any.downcast::<Notification>().ok().map(|notification| notification as Rc<dyn INotification>)
128 });
129 }
130
131 pub fn title(&self) -> Option<String> {
133 self.title.borrow().clone()
134 }
135
136 pub fn set_title(&self, value: Option<String>) {
137 if *self.title.borrow() != value {
138 *self.title.borrow_mut() = value;
139 self.on_property_changed(Some("Title"));
140 }
141 }
142
143 pub fn with_title(self: Rc<Self>, value: Option<String>) -> Rc<Self> {
145 self.set_title(value);
146 self
147 }
148
149 pub fn message(&self) -> Option<String> {
151 self.message.borrow().clone()
152 }
153
154 pub fn set_message(&self, value: Option<String>) {
155 if *self.message.borrow() != value {
156 *self.message.borrow_mut() = value;
157 self.on_property_changed(Some("Message"));
158 }
159 }
160
161 pub fn with_message(self: Rc<Self>, value: Option<String>) -> Rc<Self> {
163 self.set_message(value);
164 self
165 }
166
167 pub fn type_(&self) -> NotificationType {
169 self.type_.get()
170 }
171
172 pub fn set_type(&self, value: NotificationType) {
173 self.type_.set(value)
174 }
175
176 pub fn with_type(self: Rc<Self>, value: NotificationType) -> Rc<Self> {
178 self.set_type(value);
179 self
180 }
181
182 pub fn expiration(&self) -> Duration {
186 self.expiration.get()
187 }
188
189 pub fn set_expiration(&self, value: Duration) {
190 self.expiration.set(value)
191 }
192
193 pub fn with_expiration(self: Rc<Self>, value: Duration) -> Rc<Self> {
195 self.set_expiration(value);
196 self
197 }
198
199 pub fn on_click(&self) -> Option<Rc<dyn Fn()>> {
201 self.on_click.borrow().clone()
202 }
203
204 pub fn set_on_click(&self, value: Option<Rc<dyn Fn()>>) {
205 *self.on_click.borrow_mut() = value;
206 }
207
208 pub fn with_on_click(self: Rc<Self>, value: Option<Rc<dyn Fn()>>) -> Rc<Self> {
210 self.set_on_click(value);
211 self
212 }
213
214 pub fn on_close(&self) -> Option<Rc<dyn Fn()>> {
216 self.on_close.borrow().clone()
217 }
218
219 pub fn set_on_close(&self, value: Option<Rc<dyn Fn()>>) {
220 *self.on_close.borrow_mut() = value;
221 }
222
223 pub fn with_on_close(self: Rc<Self>, value: Option<Rc<dyn Fn()>>) -> Rc<Self> {
225 self.set_on_close(value);
226 self
227 }
228
229 pub fn on_property_changed(&self, property_name: Option<&str>) {
232 match &self.overrides {
233 Some(overrides) => overrides.on_property_changed(self, property_name),
234 None => self.base_on_property_changed(property_name),
235 }
236 }
237
238 pub fn base_on_property_changed(&self, property_name: Option<&str>) {
242 self.property_changed.raise(property_name.unwrap_or(""));
244 }
245}
246
247impl INotification for Notification {
248 fn title(&self) -> Option<String> {
249 Notification::title(self)
250 }
251
252 fn message(&self) -> Option<String> {
253 Notification::message(self)
254 }
255
256 fn type_(&self) -> NotificationType {
257 Notification::type_(self)
258 }
259
260 fn expiration(&self) -> Duration {
261 Notification::expiration(self)
262 }
263
264 fn on_click(&self) -> Option<Rc<dyn Fn()>> {
265 Notification::on_click(self)
266 }
267
268 fn on_close(&self) -> Option<Rc<dyn Fn()>> {
269 Notification::on_close(self)
270 }
271
272 fn to_boxed(self: Rc<Self>) -> BoxedValue {
273 self
274 }
275}
276
277impl INotifyPropertyChanged for Notification {
278 fn property_changed(&self) -> &Event<str> {
279 &self.property_changed
280 }
281}
282
283ferro_model!(Notification, |b| b
285 .notify_property_changed()
286 .property::<Maybe<String>>("Title", |n| n.title(), |n, v| n.set_title(v))
287 .property::<Maybe<String>>("Message", |n| n.message(), |n, v| n.set_message(v))
288 .property::<Value<NotificationType>>("Type", |n| n.type_(), |n, v| n.set_type(v))
289 .property::<Value<Duration>>("Expiration", |n| n.expiration(), |n, v| n.set_expiration(v)));