ferroui_controls/notifications/
notification_card.rs1use super::{INotification, NotificationType};
2use crate::primitives::TemplatedControlImpl;
3use crate::{Button, ContentControl, ContentControlImpl, ControlImpl};
4use ferroui_base::input::InputElementImpl;
5use ferroui_base::interactivity::{
6 Interactive, InteractiveImpl, RoutedEvent, RoutedEventArgs, RoutedEventHandlerToken, RoutingStrategies,
7};
8use ferroui_base::layout::LayoutableImpl;
9use ferroui_base::{
10 ferro_class, ferro_class_info, ferro_impl_classes, ferro_properties, ferro_routed_event, instantiate,
11 AttachedProperty, BoxedValue, DirectProperty, FerroObjectImpl, FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Ref,
12 StyledElement, StyledElementImpl, StyledProperty, VisualImpl, WeakRef,
13};
14use std::cell::{Cell, RefCell};
15
16const PC_ERROR: &str = ":error";
17const PC_INFORMATION: &str = ":information";
18const PC_SUCCESS: &str = ":success";
19const PC_WARNING: &str = ":warning";
20
21ferroui_base::ferro_thread_local! {
22 static CLOSE_ON_CLICK_HANDLERS: RefCell<Vec<(WeakRef<Button>, RoutedEventHandlerToken)>> =
25 const { RefCell::new(Vec::new()) };
26}
27
28#[repr(C)]
30pub struct NotificationCard {
31 base: ContentControl,
32 is_closing: Cell<bool>,
33}
34
35ferro_class!(NotificationCard: ContentControl);
36
37ferro_class_info!(NotificationCard {
38 new: NotificationCard::new,
39 markup: {
40 attributes: [PseudoClasses(":error", ":information", ":success", ":warning")],
41 },
42});
43
44ferro_impl_classes!(
45 NotificationCard: StyledElementImpl,
46 VisualImpl,
47 LayoutableImpl,
48 InteractiveImpl,
49 InputElementImpl,
50 ControlImpl,
51 TemplatedControlImpl,
52 ContentControlImpl
53);
54
55impl FerroObjectImpl for NotificationCard {
56 fn constructed(this: &Self) {
57 Self::parent_constructed(this);
58 this.update_notification_type();
59 }
60
61 fn on_property_changed(this: &Self, e: &FerroPropertyChangedEventArgs<'_>) {
62 Self::parent_on_property_changed(this, e);
63
64 if e.property() == ContentControl::content_property().as_property() {
65 let content = e.get_new_value::<Option<BoxedValue>>();
66 if let Some(notification) = content.as_ref().and_then(<dyn INotification>::from_boxed) {
67 this.set_value(Self::notification_type_property(), notification.type_());
68 }
69 }
70
71 if e.property() == Self::notification_type_property().as_property() {
72 this.update_notification_type();
73 }
74
75 if e.property() == Self::is_closed_property().as_property() {
76 if !this.is_closing() && !this.is_closed() {
77 return;
78 }
79
80 this.raise_event(&RoutedEventArgs::with_event(Self::notification_closed_event()));
81 }
82 }
83}
84
85ferro_properties! {
86 impl NotificationCard {
87 pub fn is_closing_property() -> DirectProperty<NotificationCard, bool> {
89 FerroProperty::register_direct::<NotificationCard, _>("IsClosing", |o| o.is_closing(), None, false)
90 }
91
92 pub fn is_closed_property() -> StyledProperty<bool> {
94 FerroProperty::register::<NotificationCard, _>("IsClosed", false)
95 }
96
97 pub fn notification_type_property() -> StyledProperty<NotificationType> {
99 FerroProperty::register::<NotificationCard, _>("NotificationType", NotificationType::Information)
100 }
101
102 pub fn close_on_click_property() -> AttachedProperty<bool> {
104 FerroProperty::register_attached::<NotificationCard, Button, _>("CloseOnClick", false)
105 }
106 }
107}
108
109impl NotificationCard {
110 ferro_routed_event!(
111 pub fn notification_closed_event() -> RoutedEvent<RoutedEventArgs> {
113 RoutedEvent::register::<NotificationCard, _>("NotificationClosed", RoutingStrategies::BUBBLE)
114 }
115 );
116
117 fn static_constructor() {
118 Self::close_on_click_property()
119 .changed()
120 .add_class_handler::<Button>(Self::on_close_on_click_property_changed);
121 }
122
123 pub fn construct() -> Self {
125 Self { base: ContentControl::construct(), is_closing: Cell::new(false) }
126 }
127
128 pub fn new() -> Ref<Self> {
129 instantiate(Self::construct())
130 }
131
132 pub fn is_closing(&self) -> bool {
134 self.is_closing.get()
135 }
136
137 fn set_is_closing(&self, value: bool) {
138 self.set_and_raise_cell(Self::is_closing_property(), &self.is_closing, value);
139 }
140
141 pub fn is_closed(&self) -> bool {
143 self.get_value(Self::is_closed_property())
144 }
145
146 pub fn set_is_closed(&self, value: bool) {
147 self.set_value(Self::is_closed_property(), value)
148 }
149
150 pub fn notification_type(&self) -> NotificationType {
152 self.get_value(Self::notification_type_property())
153 }
154
155 pub fn set_notification_type(&self, value: NotificationType) {
156 self.set_value(Self::notification_type_property(), value)
157 }
158
159 pub fn notification_closed(
161 &self,
162 handler: impl Fn(&Interactive, &RoutedEventArgs) + 'static,
163 ) -> RoutedEventHandlerToken {
164 self.add_handler(Self::notification_closed_event(), handler)
165 }
166
167 pub fn get_close_on_click(obj: &Button) -> bool {
169 obj.get_value(Self::close_on_click_property())
170 }
171
172 pub fn set_close_on_click(obj: &Button, value: bool) {
175 obj.set_value(Self::close_on_click_property(), value)
176 }
177
178 fn on_close_on_click_property_changed(button: &Button, e: &FerroPropertyChangedEventArgs<'_>) {
179 let value = e.get_new_value::<bool>();
180 let button_ref = button.to_ref();
181
182 let existing = CLOSE_ON_CLICK_HANDLERS.with(|handlers| {
185 let mut handlers = handlers.borrow_mut();
186 let mut existing = None;
187 handlers.retain(|(candidate, token)| match candidate.upgrade() {
188 Some(candidate) if candidate.ptr_eq(&button_ref) => {
189 existing = Some(*token);
190 false
191 }
192 Some(_) => true,
193 None => false,
194 });
195 existing
196 });
197
198 if let Some(token) = existing {
199 button.remove_handler(Button::click_event(), token);
200 }
201
202 if value {
203 let token = button.add_handler(Button::click_event(), Self::button_click);
204 CLOSE_ON_CLICK_HANDLERS.with(|handlers| handlers.borrow_mut().push((button_ref.downgrade(), token)));
205 }
206 }
207
208 fn button_click(sender: &Interactive, _e: &RoutedEventArgs) {
210 let btn: &StyledElement = sender;
211 let mut ancestor = btn.parent();
212 while let Some(current) = ancestor {
213 if let Some(notification) = current.cast::<NotificationCard>() {
214 notification.close();
215 break;
216 }
217 ancestor = current.parent();
218 }
219 }
220
221 pub fn close(&self) {
223 if self.is_closing() {
224 return;
225 }
226
227 self.set_is_closing(true);
228 }
229
230 fn update_notification_type(&self) {
231 let notification_type = self.notification_type();
232 self.pseudo_classes().set(PC_ERROR, notification_type == NotificationType::Error);
233 self.pseudo_classes().set(PC_INFORMATION, notification_type == NotificationType::Information);
234 self.pseudo_classes().set(PC_SUCCESS, notification_type == NotificationType::Success);
235 self.pseudo_classes().set(PC_WARNING, notification_type == NotificationType::Warning);
236 }
237}