1use crate::automation::peers::{AutomationPeer, NoneAutomationPeer};
2use crate::primitives::{AdornerLayer, FlyoutBase};
3use crate::templates::{DataTemplates, IDataTemplateHost, ITemplateOf};
4use crate::Panel;
5use crate::ContextMenu;
6use ferroui_base::data::core::ValueTypes;
7use ferroui_base::reactive::IDisposable;
8use crate::{RequestBringIntoViewEventArgs, SizeChangedEventArgs};
9use ferroui_base::input::{
10 ContextRequestedEventArgs, FocusChangedEventArgs, InputElement, InputElementImpl, InputElementImplExt, KeyEventArgs,
11 MouseButton, NavigationMethod,
12 PointerReleasedEventArgs,
13};
14use ferroui_base::interactivity::{
15 Interactive, InteractiveImpl, RoutedEvent, RoutedEventArgs, RoutedEventHandlerToken, RoutingStrategies,
16};
17use ferroui_base::layout::LayoutableImpl;
18use ferroui_base::styling::{ISetterValue, Setter};
19use ferroui_base::threading::{Dispatcher, DispatcherPriority};
20use ferroui_base::{
21 ferro_class, ferro_impl_classes, ferro_property, ferro_routed_event, instantiate, AnyValue, BoxedValue,
22 FerroObjectImpl, FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, IntoRef, Nullable, Rect,
23 Ref, Size, StyledElement, StyledElementImpl, StyledProperty, Visual, VisualImpl, VisualImplExt, VisualTreeAttachmentEventArgs,
24};
25use std::cell::{Cell, OnceCell, RefCell};
26use std::collections::{HashSet, VecDeque};
27use std::rc::Rc;
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30enum LoadState {
31 Unloaded,
32 Loaded,
33 LoadPending,
34}
35
36#[derive(Default)]
45struct LoadedQueues {
46 is_loaded_processing: bool,
47 queue: Vec<Ref<Control>>,
49 queued: HashSet<Ref<Control>>,
51 processing_queue: VecDeque<Ref<Control>>,
52}
53
54impl LoadedQueues {
55 fn add(&mut self, control: Ref<Control>) -> bool {
56 if self.queued.insert(control.clone()) {
57 self.queue.push(control);
58 true
59 } else {
60 false
61 }
62 }
63
64 fn remove(&mut self, control: &Ref<Control>) {
65 if self.queued.remove(control) {
66 self.queue.retain(|c| c != control);
67 }
68 }
69}
70
71ferroui_base::ferro_thread_local! {
72 static LOADED: RefCell<LoadedQueues> = RefCell::new(LoadedQueues::default());
73}
74
75#[repr(C)]
83pub struct Control {
84 base: InputElement,
85 load_state: Cell<LoadState>,
86 data_templates: OnceCell<DataTemplates>,
87 focus_adorner: RefCell<Option<Ref<Control>>>,
88 context_flyout_handlers: Cell<Option<(RoutedEventHandlerToken, RoutedEventHandlerToken)>>,
89 context_menu_handlers: Cell<Option<ContextMenuHandlers>>,
90 automation_peer: RefCell<Option<Ref<AutomationPeer>>>,
91}
92
93pub(crate) type ContextMenuHandlers =
97 (RoutedEventHandlerToken, RoutedEventHandlerToken, Rc<dyn IDisposable>, Rc<dyn IDisposable>);
98
99ferro_class! {
100 Control: InputElement, virtuals ControlImpl: InputElementImpl {
101 fn get_template_focus_target(this) -> Option<Ref<Control>>;
103 fn on_loaded(this, e: &RoutedEventArgs);
105 fn on_unloaded(this, e: &RoutedEventArgs);
107 fn on_size_changed(this, e: &SizeChangedEventArgs);
109 fn on_create_automation_peer(this) -> Ref<AutomationPeer>;
112 fn effective_context_flyout(this, for_request: bool) -> Option<Ref<FlyoutBase>>;
118 fn context_request_generation(this) -> Option<u64>;
123 }
124}
125ferroui_base::ferro_class_info!(Control { new: Control::new });
126
127ferro_impl_classes!(Control: StyledElementImpl, LayoutableImpl, InteractiveImpl);
128
129ferroui_base::ferro_overrides! { impl FerroObjectImpl for Control {
130 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
131 Self::parent_on_property_changed(this, change);
132
133 if change.property() == Visual::bounds_property().as_property() {
134 let (old_value, new_value) = change.get_old_and_new_value::<Rect>();
135
136 if new_value.size() != old_value.size() {
141 let size_changed_event_args = SizeChangedEventArgs::new(
142 Self::size_changed_event(),
143 this.to_ref(),
144 Size::new(old_value.width, old_value.height),
145 Size::new(new_value.width, new_value.height),
146 );
147
148 this.on_size_changed(&size_changed_event_args);
149 }
150 }
151 }
152
153 fn update_data_validation(
154 this: &Self,
155 property: &'static ferroui_base::FerroProperty,
156 state: ferroui_base::data::BindingValueType,
157 error: Option<&ferroui_base::data::BindingError>,
158 ) {
159 crate::DataValidationErrors::set_error(this, error);
160 Self::parent_update_data_validation(this, property, state, error);
161 }
162} }
163
164impl VisualImpl for Control {
165 fn on_attached_to_visual_tree_core(this: &Self, e: &VisualTreeAttachmentEventArgs) {
166 Self::parent_on_attached_to_visual_tree_core(this, e);
167
168 this.initialize_if_needed();
169
170 this.schedule_on_loaded_core();
171 }
172
173 fn on_detached_from_visual_tree_core(this: &Self, e: &VisualTreeAttachmentEventArgs) {
174 Self::parent_on_detached_from_visual_tree_core(this, e);
175
176 this.on_unloaded_core();
177 }
178
179 fn ensure_initialized_for_visual_brush(this: &Self) {
180 this.ensure_initialized();
181 }
182}
183
184impl InputElementImpl for Control {
185 fn on_got_focus(this: &Self, e: &FocusChangedEventArgs) {
186 Self::parent_on_got_focus(this, e);
187
188 if this.is_focused()
189 && (e.navigation_method == NavigationMethod::Tab || e.navigation_method == NavigationMethod::Directional)
190 {
191 if let Some(adorner_layer) = AdornerLayer::get_adorner_layer(this) {
192 if this.focus_adorner.borrow().is_none() {
193 let template = if this.is_set(Self::focus_adorner_property().as_property()) {
194 this.get_value(Self::focus_adorner_property())
195 } else {
196 adorner_layer.default_focus_adorner()
197 };
198
199 if let Some(template) = template {
200 *this.focus_adorner.borrow_mut() = template.build_typed();
201 }
202 }
203
204 let focus_adorner = this.focus_adorner.borrow().clone();
205 if let (Some(focus_adorner), Some(target)) = (focus_adorner, this.get_template_focus_target()) {
206 AdornerLayer::set_adorned_element(&focus_adorner, target);
207 adorner_layer.children().add(focus_adorner);
208 }
209 }
210 }
211
212 if this.is_focused() {
214 let target = if this.focus_adorner.borrow().is_some() { this.get_template_focus_target() } else { None };
215 crate::media::on_control_got_focus(
216 target.as_ref().map(|target| {
217 let visual: &ferroui_base::Visual = target;
218 visual
219 }),
220 e,
221 );
222 }
223 }
224
225 fn on_lost_focus(this: &Self, e: &FocusChangedEventArgs) {
226 Self::parent_on_lost_focus(this, e);
227
228 let target = this.get_template_focus_target();
229 crate::media::on_control_lost_focus(
230 this,
231 target.as_ref().map(|target| {
232 let visual: &ferroui_base::Visual = target;
233 visual
234 }),
235 );
236
237 let focus_adorner = this.focus_adorner.borrow().clone();
238 if let Some(focus_adorner) = focus_adorner {
239 if let Some(adorner_layer) = focus_adorner.parent().and_then(|parent| parent.cast::<Panel>()) {
240 adorner_layer.children().remove(focus_adorner);
241 *this.focus_adorner.borrow_mut() = None;
242 }
243 }
244 }
245
246 fn on_pointer_released(this: &Self, e: &PointerReleasedEventArgs) {
247 Self::parent_on_pointer_released(this, e);
248
249 if e.is_source(this) && !e.handled() && e.initial_press_mouse_button() == MouseButton::Right {
250 let args = ContextRequestedEventArgs::from_pointer_event_args(e);
251 this.raise_event(&args);
252 e.set_handled(args.handled());
253 }
254 }
255
256 fn on_key_up(this: &Self, e: &KeyEventArgs) {
257 Self::parent_on_key_up(this, e);
258
259 if e.is_source(this) && !e.handled() {
260 let Some(settings) = this.get_platform_settings() else { return };
261 let configuration = settings.hotkey_configuration();
262 let keymap = &configuration.open_context_menu;
263
264 let matches = keymap.iter().any(|key| key.matches(Some(e)));
265
266 if matches {
267 let args = ContextRequestedEventArgs::new();
268 this.raise_event(&args);
269 e.set_handled(args.handled());
270 }
271 }
272 }
273}
274
275impl ControlImpl for Control {
276 fn get_template_focus_target(this: &Self) -> Option<Ref<Control>> {
277 Some(this.to_ref())
278 }
279
280 fn on_loaded(this: &Self, e: &RoutedEventArgs) {
281 this.raise_event(e);
282 }
283
284 fn on_unloaded(this: &Self, e: &RoutedEventArgs) {
285 this.raise_event(e);
286 }
287
288 fn on_create_automation_peer(this: &Self) -> Ref<AutomationPeer> {
289 NoneAutomationPeer::new(this).upcast()
290 }
291
292 fn on_size_changed(this: &Self, e: &SizeChangedEventArgs) {
293 this.raise_event(e);
294 }
295
296 fn effective_context_flyout(this: &Self, _for_request: bool) -> Option<Ref<FlyoutBase>> {
297 this.context_flyout()
298 }
299
300 fn context_request_generation(_this: &Self) -> Option<u64> {
301 None
302 }
303}
304
305ferroui_base::ferro_properties! { impl Control {
306 ferro_property!(
307 pub fn focus_adorner_property() -> StyledProperty<Option<Rc<dyn ITemplateOf<Option<Ref<Control>>>>>> {
309 FerroProperty::register::<Control, _>("FocusAdorner", None)
310 }
311 );
312
313 ferro_property!(
314 pub fn tag_property() -> StyledProperty<Option<BoxedValue>> {
316 FerroProperty::register::<Control, _>("Tag", None)
317 }
318 );
319
320 ferro_property!(
321 pub fn context_menu_property() -> StyledProperty<Option<Ref<ContextMenu>>> {
323 FerroProperty::register::<Control, _>("ContextMenu", None)
324 }
325 );
326
327 ferro_property!(
328 pub fn context_flyout_property() -> StyledProperty<Option<Ref<FlyoutBase>>> {
330 FerroProperty::register::<Control, _>("ContextFlyout", None)
331 }
332 );
333} }
334
335impl Control {
336 ferro_routed_event!(
337 pub fn request_bring_into_view_event() -> RoutedEvent<RequestBringIntoViewEventArgs> {
339 RoutedEvent::register::<Control, _>("RequestBringIntoView", RoutingStrategies::BUBBLE)
340 }
341 );
342
343 ferro_routed_event!(
344 pub fn loaded_event() -> RoutedEvent<RoutedEventArgs> {
346 RoutedEvent::register::<Control, _>("Loaded", RoutingStrategies::DIRECT)
347 }
348 );
349
350 ferro_routed_event!(
351 pub fn unloaded_event() -> RoutedEvent<RoutedEventArgs> {
353 RoutedEvent::register::<Control, _>("Unloaded", RoutingStrategies::DIRECT)
354 }
355 );
356
357 ferro_routed_event!(
358 pub fn size_changed_event() -> RoutedEvent<SizeChangedEventArgs> {
360 RoutedEvent::register::<Control, _>("SizeChanged", RoutingStrategies::DIRECT)
361 }
362 );
363
364 pub fn construct() -> Self {
366 Self { base: InputElement::construct(), load_state: Cell::new(LoadState::Unloaded), data_templates: OnceCell::new(), focus_adorner: RefCell::new(None), context_flyout_handlers: Cell::new(None), context_menu_handlers: Cell::new(None), automation_peer: RefCell::new(None) }
367 }
368
369 pub fn new() -> Ref<Self> {
370 instantiate(Self::construct())
371 }
372
373 pub(crate) fn get_automation_peer(&self) -> Option<Ref<AutomationPeer>> {
375 self.verify_access();
376 self.automation_peer.borrow().clone()
377 }
378
379 pub(crate) fn get_or_create_automation_peer(&self) -> Ref<AutomationPeer> {
382 self.verify_access();
383
384 if let Some(peer) = self.automation_peer.borrow().clone() {
385 return peer;
386 }
387
388 let peer = self.on_create_automation_peer();
389 *self.automation_peer.borrow_mut() = Some(peer.clone());
390 peer
391 }
392
393 pub fn focus_adorner(&self) -> Option<Rc<dyn ITemplateOf<Option<Ref<Control>>>>> {
395 self.get_value(Self::focus_adorner_property())
396 }
397
398 pub fn set_focus_adorner(&self, value: Option<Rc<dyn ITemplateOf<Option<Ref<Control>>>>>) {
399 self.set_value(Self::focus_adorner_property(), value)
400 }
401
402 pub fn data_templates(&self) -> DataTemplates {
407 self.data_templates.get_or_init(DataTemplates::new).clone()
408 }
409
410 pub fn is_data_templates_initialized(&self) -> bool {
412 self.data_templates.get().is_some()
413 }
414
415 pub fn context_menu(&self) -> Option<Ref<ContextMenu>> {
417 self.get_value(Self::context_menu_property())
418 }
419
420 pub fn set_context_menu(&self, value: impl Into<Nullable<ContextMenu>>) {
421 self.set_value(Self::context_menu_property(), value.into().0)
422 }
423
424 pub(crate) fn context_menu_handlers(&self) -> &Cell<Option<ContextMenuHandlers>> {
426 &self.context_menu_handlers
427 }
428
429 pub fn context_flyout(&self) -> Option<Ref<FlyoutBase>> {
431 self.get_value(Self::context_flyout_property())
432 }
433
434 pub fn set_context_flyout(&self, value: impl Into<Nullable<FlyoutBase>>) {
435 self.set_value(Self::context_flyout_property(), value.into().0)
436 }
437
438 pub(crate) fn context_flyout_handlers(&self) -> &Cell<Option<(RoutedEventHandlerToken, RoutedEventHandlerToken)>> {
441 &self.context_flyout_handlers
442 }
443
444 pub fn is_loaded(&self) -> bool {
449 self.load_state.get() == LoadState::Loaded
450 }
451
452 pub fn tag(&self) -> Option<BoxedValue> {
454 self.get_value(Self::tag_property())
455 }
456
457 pub fn set_tag(&self, value: Option<BoxedValue>) {
458 self.set_value(Self::tag_property(), value)
459 }
460
461 pub fn loaded(&self, handler: impl Fn(&Interactive, &RoutedEventArgs) + 'static) -> RoutedEventHandlerToken {
471 self.add_handler(Self::loaded_event(), handler)
472 }
473
474 pub fn unloaded(&self, handler: impl Fn(&Interactive, &RoutedEventArgs) + 'static) -> RoutedEventHandlerToken {
480 self.add_handler(Self::unloaded_event(), handler)
481 }
482
483 pub fn size_changed(
485 &self,
486 handler: impl Fn(&Interactive, &SizeChangedEventArgs) + 'static,
487 ) -> RoutedEventHandlerToken {
488 self.add_handler(Self::size_changed_event(), handler)
489 }
490
491 pub fn boxed(control: impl IntoRef<Control>) -> BoxedValue {
494 Rc::new(control.into_ref())
495 }
496
497 pub fn from_boxed(value: &BoxedValue) -> Option<Ref<Control>> {
507 let value: &dyn AnyValue = &**value;
508 if let Some(control) = value.downcast_ref::<Ref<Control>>() {
509 return Some(control.clone());
510 }
511 ValueTypes::as_object(value)?.cast::<Control>()
512 }
513
514 pub fn logical_from_boxed(value: &BoxedValue) -> Option<Ref<StyledElement>> {
518 let value: &dyn AnyValue = &**value;
519 if let Some(control) = value.downcast_ref::<Ref<Control>>() {
520 return Some(control.clone().upcast());
521 }
522 if let Some(element) = value.downcast_ref::<Ref<StyledElement>>() {
523 return Some(element.clone());
524 }
525 ValueTypes::as_object(value)?.cast::<StyledElement>()
526 }
527
528 pub fn ensure_initialized(&self) {
531 if self.visual_root().is_none() {
532 if !self.is_initialized() {
533 for i in self.get_self_and_visual_descendants() {
534 if let Some(c) = i.downcast_ref::<Control>() {
535 if !c.is_initialized() {
536 c.begin_init();
537 c.end_init();
538 }
539 }
540 }
541 }
542
543 if !self.is_arrange_valid() {
544 self.measure(Size::INFINITY);
545 self.arrange(Rect::from_size(self.desired_size()));
546 }
547 }
548 }
549
550 fn loaded_processing_action() {
551 LOADED.with(|loaded| {
556 let mut loaded = loaded.borrow_mut();
557 let queue = std::mem::take(&mut loaded.queue);
558 loaded.queued.clear();
559 loaded.processing_queue.extend(queue);
560 });
561
562 struct Finally;
568
569 impl Drop for Finally {
570 fn drop(&mut self) {
571 let restart = LOADED.with(|loaded| {
572 let mut loaded = loaded.borrow_mut();
573 let remaining = std::mem::take(&mut loaded.processing_queue);
574 for control in remaining {
575 loaded.add(control);
576 }
577 loaded.is_loaded_processing = false;
578
579 if !loaded.queue.is_empty() {
582 loaded.is_loaded_processing = true;
583 true
584 } else {
585 false
586 }
587 });
588
589 if restart {
590 Dispatcher::ui_thread().post_local(Control::loaded_processing_action, DispatcherPriority::LOADED);
591 }
592 }
593 }
594
595 let _finally = Finally;
596
597 loop {
598 let next = LOADED.with(|loaded| loaded.borrow_mut().processing_queue.pop_front());
599 match next {
600 Some(control) => control.on_loaded_core(),
601 None => break,
602 }
603 }
604 }
605
606 pub(crate) fn schedule_on_loaded_core(&self) {
609 if self.load_state.get() == LoadState::Unloaded {
610 let post = LOADED.with(|loaded| {
611 let mut loaded = loaded.borrow_mut();
612 let is_added = loaded.add(self.to_ref());
613 if is_added && !loaded.is_loaded_processing {
614 loaded.is_loaded_processing = true;
615 true
616 } else {
617 false
618 }
619 });
620 self.load_state.set(LoadState::LoadPending);
621
622 if post {
623 Dispatcher::ui_thread().post_local(Self::loaded_processing_action, DispatcherPriority::LOADED);
624 }
625 }
626 }
627
628 pub(crate) fn on_loaded_core(&self) {
631 if self.load_state.get() == LoadState::LoadPending && self.is_attached_to_logical_tree() {
632 self.load_state.set(LoadState::Loaded);
633
634 self.on_loaded(&RoutedEventArgs::with_event_and_source(Self::loaded_event(), self.to_ref()));
635 } else {
636 self.load_state.set(LoadState::Unloaded);
638 }
639 }
640
641 pub(crate) fn on_unloaded_core(&self) {
644 match self.load_state.get() {
645 LoadState::Loaded => {
646 self.load_state.set(LoadState::Unloaded);
647
648 self.on_unloaded(&RoutedEventArgs::with_event_and_source(Self::unloaded_event(), self.to_ref()));
649 }
650 LoadState::LoadPending => {
651 let this = self.to_ref();
655 LOADED.with(|loaded| loaded.borrow_mut().remove(&this));
656
657 self.load_state.set(LoadState::Unloaded);
658 }
659 LoadState::Unloaded => {}
660 }
661 }
662
663 #[doc(hidden)]
666 pub fn reset_loaded_queue_for_unit_tests() {
667 LOADED.with(|loaded| {
668 let mut loaded = loaded.borrow_mut();
669 loaded.queue.clear();
670 loaded.queued.clear();
671 loaded.processing_queue.clear();
672 loaded.is_loaded_processing = false;
673 });
674 }
675}
676
677impl IDataTemplateHost for Control {
678 fn data_templates(&self) -> DataTemplates {
679 Control::data_templates(self)
680 }
681
682 fn is_data_templates_initialized(&self) -> bool {
683 Control::is_data_templates_initialized(self)
684 }
685}
686
687impl ISetterValue for Control {
688 fn initialize(&self, setter: &Setter) {
689 if let Some(context_menu) = self.downcast_ref::<ContextMenu>() {
691 return context_menu.initialize(setter);
692 }
693
694 if setter.property() == Some(Self::context_flyout_property().as_property()) {
695 return; }
697
698 panic!("Cannot use a control as a Setter value. Wrap the control in a <Template>.");
699 }
700}