1use crate::application_lifetimes::{IActivatableLifetime, IApplicationLifetime};
2use crate::templates::{DataTemplates, IDataTemplateHost};
3use crate::IGlobalDataTemplates;
4use ferroui_base::animation::IGlobalClock;
5use ferroui_base::controls::{
6 IResourceHost, IResourceNode, ResourceDictionary, ResourceHostRef, ResourceKey, ResourceValue,
7 ResourcesChangedEventArgs,
8};
9use ferroui_base::input::raw::IDragDropDevice;
10use ferroui_base::input::{
11 AccessKeyHandler, DragDropDevice, IAccessKeyHandler, IInputManager, IKeyboardNavigationHandler, InputManager,
12 KeyboardNavigationHandler,
13};
14use ferroui_base::media::MediaContext;
15use ferroui_base::platform::{IOptionalFeatureProvider, IPlatformSettings};
16use ferroui_base::reactive::{Disposable, IDisposable};
17use ferroui_base::styling::{
18 GlobalStylesHandler, IGlobalStyles, IStyle, IStyleHost, IThemeVariantHost, IThemeVariantRoot, StyleHostRef,
19 Styles, ThemeVariant,
20};
21use ferroui_base::threading::FerroSynchronizationContext;
22use ferroui_base::utilities::HandlerList;
23use ferroui_base::{
24 ferro_class, ferro_property, instantiate, BoxedValue, DirectProperty, FerroLocator, FerroObject, FerroObjectImpl,
25 FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, LocatorExtensions,
26 ObjectType, Ref, StyledElement, StyledProperty, Upcast, WeakRef,
27};
28use std::any::{Any, TypeId};
29use std::cell::{Cell, OnceCell, RefCell};
30use std::rc::{Rc, Weak};
31
32#[repr(C)]
66pub struct Application {
67 base: FerroObject,
68 host: Rc<ApplicationHost>,
69 application_lifetime: RefCell<Option<Rc<dyn IApplicationLifetime>>>,
70 setup_completed: Cell<bool>,
71 input_manager: RefCell<Option<Rc<InputManager>>>,
72 name: RefCell<Option<String>>,
73}
74
75ferro_class! {
76 Application: FerroObject, virtuals ApplicationImpl: FerroObjectImpl {
77 fn initialize(this);
79 fn register_services(this);
81 fn on_framework_initialization_completed(this);
84 }
85}
86ferroui_base::ferro_class_info!(Application {
87 new: Application::new,
88 interfaces: [
89 Rc<dyn IResourceHost> => |application: Ref<Application>| application.as_resource_host(),
90 Rc<dyn IStyleHost> => |application: Ref<Application>| application.as_style_host(),
91 Rc<dyn IGlobalStyles> => |application: Ref<Application>| application.as_global_styles(),
92 Rc<dyn IThemeVariantHost> => |application: Ref<Application>| application.as_theme_variant_host(),
93 ResourceHostRef => |application: Ref<Application>| ResourceHostRef::Other(application.as_resource_host()),
94 StyleHostRef => |application: Ref<Application>| StyleHostRef::Other(application.as_style_host()),
95 ],
96});
97
98pub trait NewApplication: ObjectType + Upcast<Application> {
101 fn new_application() -> Ref<Self>;
103}
104
105impl NewApplication for Application {
106 fn new_application() -> Ref<Self> {
107 Application::new()
108 }
109}
110
111impl FerroObjectImpl for Application {
112 fn constructed(this: &Self) {
113 Self::parent_constructed(this);
114 let _ = this.host.owner.set(this.to_ref().downgrade());
115 this.set_name(Some("FerroUI Application".to_string()));
116 }
117
118 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
119 Self::parent_on_property_changed(this, change);
120
121 if change.property() == Self::actual_theme_variant_property().as_property() {
122 for (_, handler) in this.host.actual_theme_variant_changed.snapshot().iter() {
123 handler();
124 }
125 }
126 }
127}
128
129impl ApplicationImpl for Application {
130 fn initialize(_this: &Self) {}
131
132 fn register_services(this: &Self) {
133 FerroSynchronizationContext::install_if_needed();
134 let input_manager = Rc::new(InputManager::new());
135 *this.input_manager.borrow_mut() = Some(input_manager.clone());
136
137 this.initialize_theme_variant();
138
139 let host = this.host.clone();
140 let locator = FerroLocator::current_mutable();
141 locator
142 .bind::<dyn IAccessKeyHandler>()
143 .to_func(|| Some(AccessKeyHandler::new() as Rc<dyn IAccessKeyHandler>))
144 .bind::<dyn IGlobalDataTemplates>()
145 .to_constant(host.clone())
146 .bind::<dyn IGlobalStyles>()
147 .to_constant(host.clone())
148 .bind::<dyn IThemeVariantHost>()
149 .to_constant(host)
150 .bind::<dyn IInputManager>()
151 .to_constant(input_manager.clone())
152 .bind::<dyn crate::IToolTipService>()
153 .to_constant(crate::ToolTipService::new(&(input_manager as Rc<dyn IInputManager>)))
154 .bind::<dyn IKeyboardNavigationHandler>()
155 .to_func(|| Some(KeyboardNavigationHandler::new() as Rc<dyn IKeyboardNavigationHandler>))
156 .bind::<dyn IDragDropDevice>()
157 .to_constant(DragDropDevice::instance() as Rc<dyn IDragDropDevice>);
158
159 {
162 use ferroui_base::input::platform::IPlatformDragSource;
163 use ferroui_base::LocatorExtensions;
164
165 if FerroLocator::current().get_service::<dyn IPlatformDragSource>().is_none() {
166 locator.bind::<dyn IPlatformDragSource>().to_func(|| {
167 Some(crate::platform::InProcessDragSource::new() as Rc<dyn IPlatformDragSource>)
168 });
169 }
170 }
171
172 locator.bind::<dyn IGlobalClock>().to_constant(MediaContext::instance().clock());
173
174 this.setup_completed.set(true);
175 }
176
177 fn on_framework_initialization_completed(_this: &Self) {}
178}
179
180ferroui_base::ferro_properties! { impl Application {
181 ferro_property!(
182 pub fn data_context_property() -> StyledProperty<Option<BoxedValue>> {
184 StyledElement::data_context_property().add_owner::<Application>()
185 }
186 );
187
188 ferro_property!(
189 pub fn actual_theme_variant_property() -> StyledProperty<Option<ThemeVariant>> {
192 ThemeVariant::actual_theme_variant_property().add_owner::<Application>()
193 }
194 );
195
196 ferro_property!(
197 pub fn requested_theme_variant_property() -> StyledProperty<Option<ThemeVariant>> {
200 ThemeVariant::requested_theme_variant_property().add_owner::<Application>()
201 }
202 );
203
204 ferro_property!(
205 pub fn name_property() -> DirectProperty<Application, Option<String>> {
207 FerroProperty::register_direct::<Application, _>("Name", |o| o.name(), Some(|o, v| o.set_name(v)), None)
208 }
209 );
210} }
211
212impl Application {
213 fn static_constructor() {
214 ThemeVariant::register_theme_variant_root_type(<Application as ferroui_base::StaticType>::TYPE);
216 <dyn ferroui_base::IDataContextProvider>::register_type(<Application as ferroui_base::StaticType>::TYPE);
218 }
219
220 pub fn construct() -> Self {
222 Self {
223 base: FerroObject::construct(),
224 host: ApplicationHost::new(),
225 application_lifetime: RefCell::new(None),
226 setup_completed: Cell::new(false),
227 input_manager: RefCell::new(None),
228 name: RefCell::new(None),
229 }
230 }
231
232 pub fn new() -> Ref<Self> {
234 instantiate(Self::construct())
235 }
236
237 pub fn data_context(&self) -> Option<BoxedValue> {
242 self.get_value(Self::data_context_property())
243 }
244
245 pub fn set_data_context(&self, value: Option<BoxedValue>) {
247 self.set_value(Self::data_context_property(), value)
248 }
249
250 pub fn requested_theme_variant(&self) -> Option<ThemeVariant> {
252 self.get_value(Self::requested_theme_variant_property())
253 }
254
255 pub fn set_requested_theme_variant(&self, value: Option<ThemeVariant>) {
257 self.set_value(Self::requested_theme_variant_property(), value)
258 }
259
260 pub fn actual_theme_variant(&self) -> Option<ThemeVariant> {
264 self.get_value(Self::actual_theme_variant_property())
265 }
266
267 pub fn is_theme_variant_root(&self) -> bool {
269 true
270 }
271
272 pub fn current() -> Option<Ref<Application>> {
276 FerroLocator::current().get_service::<Ref<Application>>().map(|application| (*application).clone())
277 }
278
279 pub fn bind_current(application: Ref<Application>) {
282 FerroLocator::current_mutable().bind_to_self(Rc::new(application));
283 }
284
285 pub fn data_templates(&self) -> DataTemplates {
287 self.host.data_templates()
288 }
289
290 pub fn is_data_templates_initialized(&self) -> bool {
292 self.host.data_templates.get().is_some()
293 }
294
295 pub fn input_manager(&self) -> Option<Rc<InputManager>> {
298 self.input_manager.borrow().clone()
299 }
300
301 pub fn resources(&self) -> Ref<ResourceDictionary> {
303 self.host.resources()
304 }
305
306 pub fn set_resources(&self, value: Ref<ResourceDictionary>) {
308 self.host.set_resources(value)
309 }
310
311 pub fn styles(&self) -> Ref<Styles> {
315 self.host.styles()
316 }
317
318 pub fn is_styles_initialized(&self) -> bool {
320 self.host.styles.get().is_some()
321 }
322
323 pub fn has_resources(&self) -> bool {
325 self.host.has_resources()
326 }
327
328 pub fn styling_parent(&self) -> Option<StyleHostRef> {
330 None
331 }
332
333 pub fn application_lifetime(&self) -> Option<Rc<dyn IApplicationLifetime>> {
336 self.application_lifetime.borrow().clone()
337 }
338
339 pub fn set_application_lifetime(&self, value: Option<Rc<dyn IApplicationLifetime>>) {
344 if self.setup_completed.get() {
345 panic!("It's not possible to change ApplicationLifetime after Application was initialized.");
346 }
347
348 *self.application_lifetime.borrow_mut() = value;
349 }
350
351 pub fn platform_settings(&self) -> Option<Rc<dyn IPlatformSettings>> {
358 self.try_get::<dyn IPlatformSettings>()
359 }
360
361 pub fn resources_changed(&self, handler: impl Fn(&ResourcesChangedEventArgs) + 'static) -> Rc<dyn IDisposable> {
364 let handler: Rc<dyn Fn(&ResourcesChangedEventArgs)> = Rc::new(handler);
365 self.host.subscribe(|host| &host.resources_changed, handler)
366 }
367
368 pub fn actual_theme_variant_changed(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
371 let handler: Rc<dyn Fn()> = Rc::new(handler);
372 self.host.subscribe(|host| &host.actual_theme_variant_changed, handler)
373 }
374
375 pub fn try_get_resource(&self, key: &ResourceKey, theme: Option<&ThemeVariant>) -> Option<ResourceValue> {
378 self.host.try_get_resource(key, theme)
379 }
380
381 pub fn name(&self) -> Option<String> {
384 self.name.borrow().clone()
385 }
386
387 pub fn set_name(&self, value: Option<String>) {
389 self.set_and_raise(Self::name_property(), &self.name, value);
390 }
391
392 pub fn try_get_feature(&self, feature_type: TypeId) -> Option<Rc<dyn Any>> {
401 self.host.try_get_feature(feature_type)
402 }
403
404 pub fn try_get<T: ?Sized + 'static>(&self) -> Option<Rc<T>> {
407 self.try_get_feature(TypeId::of::<T>()).and_then(|feature| feature.downcast_ref::<Rc<T>>().cloned())
408 }
409
410 pub fn initialize_theme_variant(&self) {
416 if let Some(platform_settings) = self.platform_settings() {
417 let weak = self.to_ref().downgrade();
418 platform_settings.color_values_changed(Rc::new(move |_| {
419 if let Some(this) = weak.upgrade() {
420 this.update_actual_theme_variant();
421 }
422 }));
423 }
424 self.update_actual_theme_variant();
425 }
426
427 fn update_actual_theme_variant(&self) {
428 ThemeVariant::update_actual_theme_variant(self);
429 }
430
431 pub fn as_global_styles(&self) -> Rc<dyn IGlobalStyles> {
435 self.host.clone()
436 }
437
438 pub fn as_style_host(&self) -> Rc<dyn IStyleHost> {
440 self.host.clone()
441 }
442
443 pub fn as_resource_host(&self) -> Rc<dyn IResourceHost> {
445 self.host.clone()
446 }
447
448 pub fn as_theme_variant_host(&self) -> Rc<dyn IThemeVariantHost> {
450 self.host.clone()
451 }
452
453 pub fn as_global_data_templates(&self) -> Rc<dyn IGlobalDataTemplates> {
455 self.host.clone()
456 }
457
458 pub fn as_optional_feature_provider(&self) -> Rc<dyn IOptionalFeatureProvider> {
460 self.host.clone()
461 }
462
463 pub fn as_theme_variant_root(&self) -> Rc<dyn IThemeVariantRoot> {
465 self.host.clone()
466 }
467
468 pub fn as_data_context_provider(&self) -> Rc<dyn ferroui_base::IDataContextProvider> {
471 self.host.clone()
472 }
473}
474
475struct ApplicationHost {
481 this: Weak<ApplicationHost>,
482 owner: OnceCell<WeakRef<Application>>,
483 data_templates: OnceCell<DataTemplates>,
485 styles: OnceCell<Ref<Styles>>,
486 resources: RefCell<Option<Ref<ResourceDictionary>>>,
487 styles_added: HandlerList<GlobalStylesHandler>,
488 styles_removed: HandlerList<GlobalStylesHandler>,
489 resources_changed: HandlerList<dyn Fn(&ResourcesChangedEventArgs)>,
490 actual_theme_variant_changed: HandlerList<dyn Fn()>,
491}
492
493impl ApplicationHost {
494 fn new() -> Rc<Self> {
495 Rc::new_cyclic(|this| Self {
496 this: this.clone(),
497 owner: OnceCell::new(),
498 data_templates: OnceCell::new(),
499 styles: OnceCell::new(),
500 resources: RefCell::new(None),
501 styles_added: HandlerList::new(),
502 styles_removed: HandlerList::new(),
503 resources_changed: HandlerList::new(),
504 actual_theme_variant_changed: HandlerList::new(),
505 })
506 }
507
508 fn host_ref(&self) -> ResourceHostRef {
509 let this: Rc<dyn IResourceHost> = self.this.upgrade().expect("the application host is alive");
510 ResourceHostRef::Other(this)
511 }
512
513 fn data_templates(&self) -> DataTemplates {
514 self.data_templates.get_or_init(DataTemplates::new).clone()
515 }
516
517 fn styles(&self) -> Ref<Styles> {
518 self.styles.get_or_init(|| Styles::with_owner(self.host_ref())).clone()
519 }
520
521 fn resources(&self) -> Ref<ResourceDictionary> {
522 if let Some(resources) = &*self.resources.borrow() {
523 return resources.clone();
524 }
525 let resources = ResourceDictionary::with_owner(self.host_ref());
526 *self.resources.borrow_mut() = Some(resources.clone());
527 resources
528 }
529
530 fn set_resources(&self, value: Ref<ResourceDictionary>) {
531 let this = self.host_ref();
532 let old = self.resources.borrow_mut().take();
533 if let Some(old) = old {
534 old.remove_owner(&this);
535 }
536 *self.resources.borrow_mut() = Some(value.clone());
537 value.add_owner(&this);
538 }
539
540 fn has_resources(&self) -> bool {
541 self.resources.borrow().as_ref().is_some_and(|r| r.has_resources())
542 || self.styles.get().is_some_and(|s| s.has_resources())
543 }
544
545 fn try_get_resource(&self, key: &ResourceKey, theme: Option<&ThemeVariant>) -> Option<ResourceValue> {
546 let resources = self.resources.borrow().clone();
547 if let Some(resources) = resources {
548 if let Some(value) = resources.try_get_resource(key, theme) {
549 return Some(value);
550 }
551 }
552 self.styles().try_get_resource(key, theme)
553 }
554
555 fn try_get_feature(&self, feature_type: TypeId) -> Option<Rc<dyn Any>> {
556 if feature_type == TypeId::of::<dyn IPlatformSettings>() {
557 return FerroLocator::current().get_service_untyped(TypeId::of::<dyn IPlatformSettings>());
558 }
559
560 if feature_type == TypeId::of::<dyn IActivatableLifetime>() {
561 return FerroLocator::current().get_service_untyped(TypeId::of::<dyn IActivatableLifetime>());
562 }
563
564 None
566 }
567
568 fn subscribe<F: ?Sized + 'static>(
569 &self,
570 select: fn(&ApplicationHost) -> &HandlerList<F>,
571 handler: Rc<F>,
572 ) -> Rc<dyn IDisposable> {
573 let token = select(self).add(handler);
574 let weak = self.this.clone();
575 Disposable::create(move || {
576 if let Some(this) = weak.upgrade() {
577 select(&this).remove(token);
578 }
579 })
580 }
581}
582
583impl IResourceNode for ApplicationHost {
584 fn has_resources(&self) -> bool {
585 ApplicationHost::has_resources(self)
586 }
587
588 fn try_get_resource(&self, key: &ResourceKey, theme: Option<&ThemeVariant>) -> Option<ResourceValue> {
589 ApplicationHost::try_get_resource(self, key, theme)
590 }
591}
592
593impl IResourceHost for ApplicationHost {
594 fn resources_changed(&self, handler: Rc<dyn Fn(&ResourcesChangedEventArgs)>) -> Rc<dyn IDisposable> {
595 self.subscribe(|host| &host.resources_changed, handler)
596 }
597
598 fn notify_hosted_resources_changed(&self, e: ResourcesChangedEventArgs) {
599 for (_, handler) in self.resources_changed.snapshot().iter() {
600 handler(&e);
601 }
602 }
603
604 fn as_style_host(&self) -> Option<&dyn IStyleHost> {
605 Some(self)
606 }
607
608 fn as_theme_variant_host(&self) -> Option<&dyn IThemeVariantHost> {
609 Some(self)
610 }
611}
612
613impl IStyleHost for ApplicationHost {
614 fn is_styles_initialized(&self) -> bool {
615 self.styles.get().is_some()
616 }
617
618 fn styles(&self) -> Ref<Styles> {
619 ApplicationHost::styles(self)
620 }
621
622 fn styling_parent(&self) -> Option<StyleHostRef> {
623 None
624 }
625
626 fn styles_added(&self, styles: &[Rc<dyn IStyle>]) {
627 for (_, handler) in self.styles_added.snapshot().iter() {
628 handler(styles);
629 }
630 }
631
632 fn styles_removed(&self, styles: &[Rc<dyn IStyle>]) {
633 for (_, handler) in self.styles_removed.snapshot().iter() {
634 handler(styles);
635 }
636 }
637}
638
639impl IGlobalStyles for ApplicationHost {
640 fn global_styles_added(&self, handler: Rc<GlobalStylesHandler>) -> Rc<dyn IDisposable> {
641 self.subscribe(|host| &host.styles_added, handler)
642 }
643
644 fn global_styles_removed(&self, handler: Rc<GlobalStylesHandler>) -> Rc<dyn IDisposable> {
645 self.subscribe(|host| &host.styles_removed, handler)
646 }
647}
648
649impl IThemeVariantHost for ApplicationHost {
650 fn actual_theme_variant(&self) -> Option<ThemeVariant> {
651 self.owner.get().and_then(WeakRef::upgrade).and_then(|application| application.actual_theme_variant())
652 }
653
654 fn actual_theme_variant_changed(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
655 self.subscribe(|host| &host.actual_theme_variant_changed, handler)
656 }
657}
658
659impl IThemeVariantRoot for ApplicationHost {
660 fn is_theme_variant_root(&self) -> bool {
661 true
662 }
663}
664
665impl ferroui_base::IDataContextProvider for ApplicationHost {
666 fn data_context(&self) -> Option<BoxedValue> {
667 self.owner.get().and_then(WeakRef::upgrade).and_then(|owner| owner.data_context())
668 }
669
670 fn set_data_context(&self, value: Option<BoxedValue>) {
671 if let Some(owner) = self.owner.get().and_then(WeakRef::upgrade) {
672 owner.set_data_context(value);
673 }
674 }
675}
676
677impl IDataTemplateHost for ApplicationHost {
678 fn data_templates(&self) -> DataTemplates {
679 ApplicationHost::data_templates(self)
680 }
681
682 fn is_data_templates_initialized(&self) -> bool {
683 self.data_templates.get().is_some()
684 }
685}
686
687impl IGlobalDataTemplates for ApplicationHost {}
688
689impl IOptionalFeatureProvider for ApplicationHost {
690 fn try_get_feature(&self, feature_type: TypeId) -> Option<Rc<dyn Any>> {
691 ApplicationHost::try_get_feature(self, feature_type)
692 }
693}