1use super::{
12 ClrPropertyViewModel, ControlLayoutViewModel, FerroPropertyViewModel, PropertyViewModelKey, PropertyViewModelRef,
13 PseudoClassViewModel, SetterViewModelRef, TreePageViewModel, ValueFrameViewModel, ViewModelBase,
14};
15use super::property_view_model::type_name;
16use ferroui_base::collections::FerroList;
17use ferroui_base::controls::ResourceKey;
18use ferroui_base::data::core::{ValueType, ValueTypes};
19use ferroui_base::data::model::{Event, INotifyPropertyChanged, ModelTypes};
20use ferroui_base::diagnostics::FerroObjectDiagnosticExtensions;
21use ferroui_base::input::platform::IClipboard;
22use ferroui_base::metadata::{MarkupProperty, MarkupType, MarkupTypeKind};
23use ferroui_base::threading::{Dispatcher, DispatcherPriority};
24use ferroui_base::{
25 ferro_markup_type, BoxedValue, FerroObject, FerroProperty, FerroPropertyChangedEventArgs, FerroPropertyRegistry, Ref,
26 StyledElement, TypeInfo, Visual,
27};
28use ferroui_controls::{Control, GridLength, GridUnitType, TopLevel};
29use ferroui_controls_treedatagrid::models::{
30 HierarchicalExpanderColumn, IViewColumn, TemplateColumn, TemplateColumnOptions, TextColumn, TextColumnOptions,
31};
32use ferroui_controls_treedatagrid::TreeDataGridSourceRef;
33use ferroui_treedatagrid_core::models::{ColumnRef, Comparison, IColumn};
34use ferroui_treedatagrid_core::selection::TreeSelectionModelSelectionChangedEventArgsOf;
35use ferroui_treedatagrid_core::{Accessor, HierarchicalTreeDataGridSource, IndexPath, Models};
36use std::any::TypeId;
37use std::cell::{Cell, RefCell};
38use std::cmp::Ordering;
39use std::collections::{HashMap, HashSet};
40use std::rc::{Rc, Weak};
41
42pub type PinnedProperties = Rc<RefCell<HashSet<String>>>;
44
45enum EntitySubscription {
48 Object(Ref<FerroObject>, u64),
50 Model(BoxedValue, u64),
52}
53
54fn notifier_of(value: &BoxedValue) -> Option<&dyn INotifyPropertyChanged> {
57 if let Some(notifier) = ModelTypes::find(&**value).and_then(|model| model.as_notify_property_changed(&**value)) {
58 return Some(notifier);
59 }
60 let id = value_type_id(value);
61 MarkupType::find_by_handle(id)
62 .and_then(|markup| markup.notify_property_changed)
63 .and_then(|as_notifier| as_notifier(&**value))
64}
65
66fn value_type_id(value: &BoxedValue) -> TypeId {
67 (**value).value_type_id()
68}
69
70pub struct ControlDetailsViewModel {
73 base: ViewModelBase,
74 weak_self: Weak<ControlDetailsViewModel>,
75 ferro_object: Ref<FerroObject>,
76 pinned_properties: PinnedProperties,
77 property_index: RefCell<Option<HashMap<PropertyViewModelKey, Vec<PropertyViewModelRef>>>>,
78 selected_property: RefCell<Option<PropertyViewModelRef>>,
79 properties_view: RefCell<Option<Rc<PropertiesView>>>,
80 snapshot_frames: RefCell<bool>,
81 show_inactive_frames: RefCell<bool>,
82 frames_status: RefCell<Option<String>>,
83 selected_entity: RefCell<Option<BoxedValue>>,
84 selected_entities_stack: RefCell<Vec<(String, BoxedValue)>>,
85 selected_entity_name: RefCell<Option<String>>,
86 selected_entity_type: RefCell<Option<String>>,
87 show_implemented_interfaces: Cell<bool>,
88 tree_page: Weak<TreePageViewModel>,
89 layout: Option<Rc<ControlLayoutViewModel>>,
90 applied_frames: FerroList<Rc<ValueFrameViewModel>>,
91 pseudo_classes: FerroList<Rc<PseudoClassViewModel>>,
92 entity_subscription: RefCell<Option<EntitySubscription>>,
93 classes_listener: Cell<Option<u64>>,
94 main_view_subscription: Cell<Option<u64>>,
95}
96
97impl PartialEq for ControlDetailsViewModel {
98 fn eq(&self, other: &Self) -> bool {
99 std::ptr::eq(self, other)
100 }
101}
102
103impl INotifyPropertyChanged for ControlDetailsViewModel {
104 fn property_changed(&self) -> &Event<str> {
105 self.base.property_changed()
106 }
107}
108
109impl ControlDetailsViewModel {
110 pub fn new(
111 tree_page: &Rc<TreePageViewModel>,
112 ferro_object: &FerroObject,
113 pinned_properties: PinnedProperties,
114 ) -> Rc<Self> {
115 let layout = ferro_object.downcast_ref::<Visual>().map(ControlLayoutViewModel::new);
116
117 let this = Rc::new_cyclic(|weak_self: &Weak<Self>| Self {
118 base: ViewModelBase::new(),
119 weak_self: weak_self.clone(),
120 ferro_object: ferro_object.to_ref(),
121 pinned_properties,
122 property_index: RefCell::new(None),
123 selected_property: RefCell::new(None),
124 properties_view: RefCell::new(None),
125 snapshot_frames: RefCell::new(false),
126 show_inactive_frames: RefCell::new(false),
127 frames_status: RefCell::new(None),
128 selected_entity: RefCell::new(None),
129 selected_entities_stack: RefCell::new(Vec::new()),
130 selected_entity_name: RefCell::new(None),
131 selected_entity_type: RefCell::new(None),
132 show_implemented_interfaces: Cell::new(false),
133 tree_page: Rc::downgrade(tree_page),
134 layout,
135 applied_frames: FerroList::new(),
136 pseudo_classes: FerroList::new(),
137 entity_subscription: RefCell::new(None),
138 classes_listener: Cell::new(None),
139 main_view_subscription: Cell::new(None),
140 });
141
142 this.navigate_to_property(this.boxed_object(), this.object_name());
143
144 if let Some(styled_element) = this.ferro_object.downcast_ref::<StyledElement>() {
145 let weak = this.weak_self.clone();
147 let token = styled_element.classes().add_listener(Rc::new(move || {
148 if let Some(this) = weak.upgrade() {
149 this.classes_changed();
150 }
151 }));
152 this.classes_listener.set(Some(token));
153
154 let mut type_ = Some(styled_element.get_type());
157 while let Some(current) = type_ {
158 if let Some(markup) = current.markup() {
159 for class_attribute in markup.attributes.iter().filter(|attribute| attribute.name == "PseudoClasses") {
160 for class_name in class_attribute.arguments {
161 if let ferroui_base::metadata::MarkupAttributeValue::Str(class_name) = class_name {
162 this.pseudo_classes.add(PseudoClassViewModel::new(class_name, styled_element));
163 }
164 }
165 }
166 }
167 type_ = current.base_type();
168 }
169
170 let style_diagnostics = styled_element.get_value_store_diagnostic();
171
172 let clipboard: Option<Rc<dyn IClipboard>> = this
173 .ferro_object
174 .downcast_ref::<Visual>()
175 .and_then(|visual| TopLevel::get_top_level(Some(visual)))
176 .and_then(|top_level| top_level.clipboard());
177
178 let mut applied_frames = style_diagnostics.applied_frames().to_vec();
180 applied_frames.sort_by_key(|frame| frame.priority());
181
182 for applied_style in applied_frames {
183 this.applied_frames.add(ValueFrameViewModel::new(styled_element, applied_style, clipboard.clone()));
184 }
185
186 this.update_styles();
187 }
188
189 this.subscribe_to_main_view();
190
191 this
192 }
193
194 fn boxed_object(&self) -> BoxedValue {
196 Rc::new(self.ferro_object.clone())
197 }
198
199 fn object_name(&self) -> Option<String> {
201 self.ferro_object
202 .downcast_ref::<Control>()
203 .and_then(|control| control.name())
204 .or_else(|| Some(ValueTypes::to_display_string(Some(&self.boxed_object()))))
205 }
206
207 pub fn can_navigate_to_parent_property(&self) -> bool {
208 !self.selected_entities_stack.borrow().is_empty()
209 }
210
211 pub fn tree_page(&self) -> Option<Rc<TreePageViewModel>> {
213 self.tree_page.upgrade()
214 }
215
216 pub fn properties_view(&self) -> Option<Rc<PropertiesView>> {
219 self.properties_view.borrow().clone()
220 }
221
222 fn set_properties_view(&self, value: Option<Rc<PropertiesView>>) {
223 let changed = match (&*self.properties_view.borrow(), &value) {
224 (Some(old), Some(new)) => !Rc::ptr_eq(old, new),
225 (None, None) => false,
226 _ => true,
227 };
228 if changed {
229 *self.properties_view.borrow_mut() = value;
230 self.raise_property_changed("PropertiesView");
231 }
232 }
233
234 pub fn applied_frames(&self) -> FerroList<Rc<ValueFrameViewModel>> {
235 self.applied_frames.clone()
236 }
237
238 pub fn pseudo_classes(&self) -> FerroList<Rc<PseudoClassViewModel>> {
239 self.pseudo_classes.clone()
240 }
241
242 pub fn selected_entity(&self) -> Option<BoxedValue> {
243 self.selected_entity.borrow().clone()
244 }
245
246 pub fn set_selected_entity(&self, value: Option<BoxedValue>) {
247 let changed = !ValueTypes::identity_equals(self.selected_entity.borrow().as_ref(), value.as_ref());
248 if changed {
249 *self.selected_entity.borrow_mut() = value;
250 self.raise_property_changed("SelectedEntity");
251 }
252 }
253
254 pub fn selected_entity_name(&self) -> Option<String> {
255 self.selected_entity_name.borrow().clone()
256 }
257
258 pub fn set_selected_entity_name(&self, value: Option<String>) {
259 self.raise_and_set_if_changed(&self.selected_entity_name, value, "SelectedEntityName");
260 }
261
262 pub fn selected_entity_type(&self) -> Option<String> {
263 self.selected_entity_type.borrow().clone()
264 }
265
266 pub fn set_selected_entity_type(&self, value: Option<String>) {
267 self.raise_and_set_if_changed(&self.selected_entity_type, value, "SelectedEntityType");
268 }
269
270 pub fn selected_property(&self) -> Option<PropertyViewModelRef> {
271 self.selected_property.borrow().clone()
272 }
273
274 pub fn set_selected_property(&self, value: Option<PropertyViewModelRef>) {
277 if self.raise_and_set_if_changed(&self.selected_property, value.clone(), "SelectedProperty") {
278 if let Some(view) = self.properties_view() {
279 view.select(value.as_ref());
280 }
281 }
282 }
283
284 pub fn snapshot_frames(&self) -> bool {
285 *self.snapshot_frames.borrow()
286 }
287
288 pub fn set_snapshot_frames(&self, value: bool) {
289 self.raise_and_set_if_changed(&self.snapshot_frames, value, "SnapshotFrames");
290 }
291
292 pub fn show_inactive_frames(&self) -> bool {
293 *self.show_inactive_frames.borrow()
294 }
295
296 pub fn set_show_inactive_frames(&self, value: bool) {
297 self.raise_and_set_if_changed(&self.show_inactive_frames, value, "ShowInactiveFrames");
298 }
299
300 pub fn frames_status(&self) -> Option<String> {
301 self.frames_status.borrow().clone()
302 }
303
304 pub fn set_frames_status(&self, value: Option<String>) {
305 self.raise_and_set_if_changed(&self.frames_status, value, "FramesStatus");
306 }
307
308 pub fn layout(&self) -> Option<Rc<ControlLayoutViewModel>> {
309 self.layout.clone()
310 }
311
312 fn raise_and_set_if_changed<T: PartialEq>(&self, field: &RefCell<T>, value: T, property_name: &str) -> bool {
315 if *field.borrow() == value {
316 return false;
317 }
318 *field.borrow_mut() = value;
319 self.raise_property_changed(property_name);
320 true
321 }
322
323 fn raise_property_changed(&self, property_name: &str) {
325 self.on_property_changed(property_name);
326 self.base.raise_property_changed(property_name);
327 }
328
329 fn on_property_changed(&self, property_name: &str) {
331 if property_name == "SnapshotFrames" && !self.snapshot_frames() {
332 self.update_styles();
333 }
334 }
335
336 pub fn update_style_filters(&self) {
337 let Some(tree_page) = self.tree_page() else {
338 return;
339 };
340 let setters_filter = tree_page.setters_filter();
341
342 for style in self.applied_frames.to_vec() {
343 let mut has_visible_setter = false;
344
345 for setter in style.setters() {
346 setter.set_is_visible(setters_filter.filter(&setter.name()));
347
348 has_visible_setter |= setter.is_visible();
349 }
350
351 style.set_is_visible(has_visible_setter);
352 }
353 }
354
355 pub fn dispose(&self) {
356 self.unsubscribe_entity();
357
358 if let (Some(styled_element), Some(token)) =
359 (self.ferro_object.downcast_ref::<StyledElement>(), self.classes_listener.take())
360 {
361 styled_element.classes().remove_listener(token);
362 }
363
364 if let (Some(main_view), Some(token)) =
365 (self.tree_page().and_then(|tree_page| tree_page.main_view()), self.main_view_subscription.take())
366 {
367 main_view.property_changed().remove(token);
368 }
369 }
370
371 fn get_ferro_properties(o: &BoxedValue) -> Vec<PropertyViewModelRef> {
374 let Some(ao) = ValueTypes::as_object(&**o) else {
375 return Vec::new();
376 };
377 let registry = FerroPropertyRegistry::instance();
378
379 let mut seen: HashSet<&'static FerroProperty> = HashSet::new();
381 let mut result = Vec::new();
382 let registered = registry.get_registered_for(&ao);
383 let attached = registry.get_registered_attached(ao.get_type());
384 for property in registered.iter().chain(attached.iter()).copied() {
385 if seen.insert(property) {
386 result.push(PropertyViewModelRef::Ferro(FerroPropertyViewModel::new(&ao, property)));
387 }
388 }
389 result
390 }
391
392 fn get_clr_properties(o: &BoxedValue, show_implemented_interfaces: bool) -> Vec<PropertyViewModelRef> {
395 let mut result = Vec::new();
396
397 for (property, declaring_type) in Self::get_type_properties(o) {
398 result.push(PropertyViewModelRef::Clr(ClrPropertyViewModel::new(o.clone(), property, declaring_type)));
399 }
400
401 if show_implemented_interfaces {
402 for i in Self::get_interfaces(o) {
403 for property in i.properties {
404 result.push(PropertyViewModelRef::Clr(ClrPropertyViewModel::new(o.clone(), property, i)));
405 }
406 }
407 }
408
409 result
410 }
411
412 fn get_type_chain(o: &BoxedValue) -> Vec<&'static MarkupType> {
415 let mut chain = Vec::new();
416
417 if let Some(object) = ValueTypes::as_object(&**o) {
418 let mut type_: Option<&'static TypeInfo> = Some(object.get_type());
419 while let Some(current) = type_ {
420 if let Some(markup) = current.markup() {
421 chain.push(markup);
422 }
423 type_ = current.base_type();
424 }
425 } else {
426 let id = value_type_id(o);
427 let mut markup = MarkupType::find_by_handle(id);
428 while let Some(current) = markup {
429 if chain.iter().any(|known: &&'static MarkupType| std::ptr::eq(*known, current)) {
430 break;
431 }
432 chain.push(current);
433 markup = current.base_type();
434 }
435 }
436
437 chain
438 }
439
440 fn get_type_properties(o: &BoxedValue) -> Vec<(&'static MarkupProperty, &'static MarkupType)> {
443 let mut seen: HashSet<&'static str> = HashSet::new();
444 let mut result = Vec::new();
445 for declaring_type in Self::get_type_chain(o) {
446 for property in declaring_type.properties {
447 if seen.insert(property.name) {
448 result.push((property, declaring_type));
449 }
450 }
451 }
452 result
453 }
454
455 fn get_interfaces(o: &BoxedValue) -> Vec<&'static MarkupType> {
458 let mut interfaces: Vec<&'static MarkupType> = Vec::new();
459 let mut add = |handle: ValueType| {
460 if let Some(markup) = MarkupType::find_by_handle(handle.id()) {
461 if markup.kind == MarkupTypeKind::Interface && !interfaces.iter().any(|known| std::ptr::eq(*known, markup)) {
462 interfaces.push(markup);
463 }
464 }
465 };
466
467 if let Some(object) = ValueTypes::as_object(&**o) {
468 let mut type_: Option<&'static TypeInfo> = Some(object.get_type());
469 while let Some(current) = type_ {
470 for interface in current.interfaces() {
471 add(interface());
472 }
473 type_ = current.base_type();
474 }
475 }
476 for markup in Self::get_type_chain(o) {
477 for interface in markup.interfaces {
478 add(interface());
479 }
480 }
481
482 interfaces
483 }
484
485 fn control_property_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
487 let properties = self
488 .property_index
489 .borrow()
490 .as_ref()
491 .and_then(|index| index.get(&PropertyViewModelKey::Property(e.property())).cloned());
492 if let Some(properties) = properties {
493 for property in properties {
494 property.update();
495 }
496 }
497
498 if let Some(layout) = &self.layout {
499 layout.control_property_changed(e);
500 }
501 }
502
503 fn model_property_changed(&self, property_name: &str) {
505 let properties = self
506 .property_index
507 .borrow()
508 .as_ref()
509 .and_then(|index| index.get(&PropertyViewModelKey::Name(property_name.to_string())).cloned());
510 if let Some(properties) = properties {
511 for property in properties {
512 property.update();
513 }
514 }
515
516 if !self.snapshot_frames() {
517 self.post_update_styles();
518 }
519 }
520
521 fn classes_changed(&self) {
523 if !self.snapshot_frames() {
524 self.post_update_styles();
525 }
526 }
527
528 fn post_update_styles(&self) {
530 let weak = self.weak_self.clone();
531 Dispatcher::ui_thread().post_local(
532 move || {
533 if let Some(this) = weak.upgrade() {
534 this.update_styles();
535 }
536 },
537 DispatcherPriority::DEFAULT,
538 );
539 }
540
541 fn update_styles(&self) {
542 let mut active_count = 0;
543 let applied_frames = self.applied_frames.to_vec();
544
545 for style in &applied_frames {
546 style.update();
547
548 if style.is_active() {
549 active_count += 1;
550 }
551 }
552
553 let mut property_buckets: HashMap<&'static FerroProperty, Vec<SetterViewModelRef>> = HashMap::new();
554
555 for style in applied_frames.iter().rev() {
556 if !style.is_active() {
557 continue;
558 }
559
560 for setter in style.setters() {
561 match property_buckets.get_mut(setter.property()) {
562 Some(setters) => {
563 for other_setter in setters.iter() {
564 other_setter.set_is_active(false);
565 }
566
567 setter.set_is_active(true);
568
569 setters.push(setter.clone());
570 }
571 None => {
572 setter.set_is_active(true);
573
574 property_buckets.insert(setter.property(), vec![setter.clone()]);
575 }
576 }
577 }
578 }
579
580 for pseudo_class in self.pseudo_classes.to_vec() {
581 pseudo_class.update();
582 }
583
584 self.set_frames_status(Some(format!("Value Frames ({}/{} active)", active_count, applied_frames.len())));
585 }
586
587 fn filter_property(&self, property: &PropertyViewModelRef) -> bool {
589 match self.tree_page() {
590 Some(tree_page) => tree_page.properties_filter().filter(&property.name()),
591 None => true,
592 }
593 }
594
595 fn get_all_public_properties(o: &BoxedValue) -> Vec<&'static MarkupProperty> {
598 let mut result: Vec<&'static MarkupProperty> =
599 Self::get_type_properties(o).into_iter().map(|(property, _)| property).collect();
600 for i in Self::get_interfaces(o) {
601 result.extend(i.properties.iter());
602 }
603 result
604 }
605
606 pub fn navigate_to_selected_property(&self) {
607 let selected_property = self.selected_property();
608 let selected_entity = self.selected_entity();
609 let selected_entity_name = self.selected_entity_name();
610 let (Some(selected_entity), Some(selected_property)) = (selected_entity, selected_property) else {
611 return;
612 };
613 let property_type = selected_property.property_type();
614 if property_type.is_string() || is_value_type(property_type) {
615 return;
616 }
617
618 let property: Option<BoxedValue> = match &selected_property {
619 PropertyViewModelRef::Ferro(ferro_property) => ValueTypes::as_object(&*selected_entity)
620 .filter(|object| object.is::<Control>())
621 .and_then(|control| ValueTypes::normalize(control.get_value_untyped(ferro_property.property()))),
622 PropertyViewModelRef::Clr(clr_property) => Self::get_all_public_properties(&selected_entity)
623 .into_iter()
624 .find(|pi| std::ptr::eq(*pi, clr_property.property()))
625 .and_then(|pi| pi.get)
626 .and_then(|get| get(&[Some(selected_entity.clone())]).ok().flatten())
627 .and_then(ValueTypes::normalize),
628 };
629
630 let Some(property) = property else {
631 return;
632 };
633
634 self.selected_entities_stack
635 .borrow_mut()
636 .push((selected_entity_name.clone().unwrap_or_default(), selected_entity));
637
638 let mut property_name = selected_property.name();
639
640 if let Some(p) = property_name.rfind('.') {
642 property_name = property_name[p + 1..].to_string();
643 }
644
645 self.navigate_to_property(
646 property,
647 Some(format!("{}.{}", selected_entity_name.unwrap_or_default(), property_name)),
648 );
649
650 self.raise_property_changed("CanNavigateToParentProperty");
651 }
652
653 pub fn navigate_to_parent_property(&self) {
654 let property = self.selected_entities_stack.borrow_mut().pop();
655 if let Some((name, entry)) = property {
656 self.navigate_to_property(entry, Some(name));
657
658 self.raise_property_changed("CanNavigateToParentProperty");
659 }
660 }
661
662 fn unsubscribe_entity(&self) {
663 let subscription = self.entity_subscription.borrow_mut().take();
664 match subscription {
665 Some(EntitySubscription::Object(object, token)) => {
666 object.remove_property_changed_handler(token);
667 }
668 Some(EntitySubscription::Model(model, token)) => {
669 if let Some(notifier) = notifier_of(&model) {
670 notifier.property_changed().remove(token);
671 }
672 }
673 None => {}
674 }
675 }
676
677 pub fn navigate_to_property(&self, o: BoxedValue, entity_name: Option<String>) {
678 self.unsubscribe_entity();
679
680 self.set_selected_entity(Some(o.clone()));
681 self.set_selected_entity_name(entity_name);
682 self.set_selected_entity_type(Some(ValueTypes::to_display_string(Some(&o))));
683
684 let mut properties = Self::get_ferro_properties(&o);
685 properties.extend(Self::get_clr_properties(&o, self.show_implemented_interfaces.get()));
686 for p in &properties {
687 let is_pinned = self.pinned_properties.borrow().contains(&p.full_name());
688 p.set_is_pinned(is_pinned);
689 }
690
691 let mut property_index: HashMap<PropertyViewModelKey, Vec<PropertyViewModelRef>> = HashMap::new();
692 for property in &properties {
693 property_index.entry(property.key()).or_default().push(property.clone());
694 }
695 *self.property_index.borrow_mut() = Some(property_index);
696
697 let weak = self.weak_self.clone();
698 let view = PropertiesView::new(
699 properties,
700 Rc::new(move |property: &PropertyViewModelRef| match weak.upgrade() {
701 Some(this) => this.filter_property(property),
702 None => true,
703 }),
704 );
705 let weak = self.weak_self.clone();
706 view.set_selection_changed(Rc::new(move |selected: Option<PropertyViewModelRef>| {
707 if let Some(this) = weak.upgrade() {
708 this.set_selected_property(selected);
709 }
710 }));
711 if let Some(main_view) = self.tree_page().and_then(|tree_page| tree_page.main_view()) {
712 view.set_property_type_columns(main_view.show_details_property_type());
713 }
714 self.set_properties_view(Some(view));
715
716 if let Some(object) = ValueTypes::as_object(&*o) {
717 let weak = self.weak_self.clone();
718 let token = object.add_property_changed_handler(Rc::new(move |e: &FerroPropertyChangedEventArgs<'_>| {
719 if let Some(this) = weak.upgrade() {
720 this.control_property_changed(e);
721 }
722 }));
723 *self.entity_subscription.borrow_mut() = Some(EntitySubscription::Object(object, token));
724 } else if let Some(notifier) = notifier_of(&o) {
725 let weak = self.weak_self.clone();
726 let token = notifier.property_changed().add(Rc::new(move |property_name: &str| {
727 if let Some(this) = weak.upgrade() {
728 this.model_property_changed(property_name);
729 }
730 }));
731 *self.entity_subscription.borrow_mut() = Some(EntitySubscription::Model(o.clone(), token));
732 }
733 }
734
735 pub fn select_property(&self, property: &'static FerroProperty) {
738 self.set_selected_property(None);
739
740 let is_object = self
741 .selected_entity()
742 .and_then(|entity| ValueTypes::as_object(&*entity))
743 .is_some_and(|entity| entity.ptr_eq(&self.ferro_object));
744 if !is_object {
745 self.navigate_to_property(self.boxed_object(), self.object_name());
746 }
747
748 let Some(properties_view) = self.properties_view() else {
749 return;
750 };
751
752 for o in properties_view.items() {
753 if let Some(property_vm) = o.as_ferro() {
754 if property_vm.property() == property {
755 self.set_selected_property(Some(o.clone()));
756
757 break;
758 }
759 }
760 }
761 }
762
763 pub fn update_properties_view(&self, show_implemented_interfaces: bool) {
764 self.show_implemented_interfaces.set(show_implemented_interfaces);
765 self.set_selected_property(None);
766 self.navigate_to_property(self.boxed_object(), self.object_name());
767 }
768
769 pub fn toggle_pinned_property(&self, parameter: Option<BoxedValue>) {
771 let model = parameter.as_ref().and_then(|parameter| {
772 PropertyViewModelRef::from_boxed(parameter)
773 .or_else(|| PropertiesViewItem::from_boxed(parameter).and_then(|item| item.property()))
774 });
775 if let Some(model) = model {
776 let fullname = model.full_name();
777 let contains = self.pinned_properties.borrow().contains(&fullname);
778 if contains {
779 self.pinned_properties.borrow_mut().remove(&fullname);
780 model.set_is_pinned(false);
781 } else {
782 self.pinned_properties.borrow_mut().insert(fullname);
783 model.set_is_pinned(true);
784 }
785 if let Some(properties_view) = self.properties_view() {
786 properties_view.refresh();
787 }
788 }
789 }
790
791 fn subscribe_to_main_view(&self) {
795 let Some(main_view) = self.tree_page().and_then(|tree_page| tree_page.main_view()) else {
796 return;
797 };
798 let weak = self.weak_self.clone();
799 let token = main_view.property_changed().add(Rc::new(move |property_name: &str| {
800 if property_name != "ShowDetailsPropertyType" {
801 return;
802 }
803 if let Some(this) = weak.upgrade() {
804 let show = this.tree_page().and_then(|tree_page| tree_page.main_view()).map(|main_view| main_view.show_details_property_type());
805 if let (Some(show), Some(view)) = (show, this.properties_view()) {
806 view.set_property_type_columns(show);
807 }
808 }
809 }));
810 self.main_view_subscription.set(Some(token));
811 }
812}
813
814pub(crate) fn is_value_type(type_: ValueType) -> bool {
817 if type_.is_string() || type_.is_object() {
818 return false;
819 }
820 if ValueTypes::class_of(type_).is_some() || ValueTypes::is_reference(type_.id()) {
821 return false;
822 }
823 if let Some(markup) = MarkupType::find_by_handle(type_.id()) {
824 return matches!(markup.kind, MarkupTypeKind::Struct | MarkupTypeKind::Enum);
825 }
826 if MarkupType::find_by_nullable_handle(type_.id()).is_some() {
827 return true;
828 }
829 if let Some(inner) = ValueTypes::nullable_inner(type_) {
830 return is_value_type(inner);
831 }
832 type_.is::<bool>()
833 || type_.is::<char>()
834 || type_.is::<i8>()
835 || type_.is::<i16>()
836 || type_.is::<i32>()
837 || type_.is::<i64>()
838 || type_.is::<u8>()
839 || type_.is::<u16>()
840 || type_.is::<u32>()
841 || type_.is::<u64>()
842 || type_.is::<f32>()
843 || type_.is::<f64>()
844 || type_.is::<Option<bool>>()
845 || type_.is::<Option<i32>>()
846 || type_.is::<Option<f64>>()
847}
848
849fn group_index(group: &str) -> i32 {
851 match group {
852 "Pinned" => -1,
853 "Properties" => 0,
854 "Attached Properties" => 1,
855 "CLR Properties" => 2,
856 _ => 3,
857 }
858}
859
860fn property_sort_key(property: &PropertyViewModelRef) -> (i32, String) {
863 (group_index(&property.group()), property.name())
864}
865
866pub struct PropertiesViewItem {
868 group: Option<String>,
869 property: Option<PropertyViewModelRef>,
870 children: Rc<Vec<Rc<PropertiesViewItem>>>,
871 is_expanded: Cell<bool>,
872 property_changed: Event<str>,
873}
874
875impl PartialEq for PropertiesViewItem {
876 fn eq(&self, other: &Self) -> bool {
877 std::ptr::eq(self, other)
878 }
879}
880
881impl INotifyPropertyChanged for PropertiesViewItem {
883 fn property_changed(&self) -> &Event<str> {
884 match &self.property {
885 Some(property) => property.property_changed(),
886 None => &self.property_changed,
887 }
888 }
889}
890
891impl PropertiesViewItem {
892 fn register() {
895 if !ModelTypes::is_registered(TypeId::of::<PropertiesViewItem>()) {
896 ModelTypes::register::<PropertiesViewItem>(|builder| builder.notify_property_changed());
897 }
898 }
899
900 pub fn group(name: String, children: Vec<Rc<PropertiesViewItem>>, is_expanded: bool) -> Rc<Self> {
902 Self::register();
903 Rc::new(Self {
904 group: Some(name),
905 property: None,
906 children: Rc::new(children),
907 is_expanded: Cell::new(is_expanded),
908 property_changed: Event::new(),
909 })
910 }
911
912 pub fn property_item(property: PropertyViewModelRef) -> Rc<Self> {
914 Self::register();
915 Rc::new(Self {
916 group: None,
917 property: Some(property),
918 children: Rc::new(Vec::new()),
919 is_expanded: Cell::new(false),
920 property_changed: Event::new(),
921 })
922 }
923
924 pub fn from_boxed(value: &BoxedValue) -> Option<Rc<Self>> {
926 if let Some(item) = value.downcast_ref::<Rc<PropertiesViewItem>>() {
927 return Some(item.clone());
928 }
929 let any: Rc<dyn std::any::Any> = value.clone();
930 any.downcast::<PropertiesViewItem>().ok()
931 }
932
933 pub fn is_group(&self) -> bool {
934 self.group.is_some()
935 }
936
937 pub fn name(&self) -> String {
939 match (&self.group, &self.property) {
940 (Some(group), _) => group.clone(),
941 (None, Some(property)) => property.name(),
942 (None, None) => String::new(),
943 }
944 }
945
946 pub fn group_name(&self) -> String {
948 match (&self.group, &self.property) {
949 (Some(group), _) => group.clone(),
950 (None, Some(property)) => property.group(),
951 (None, None) => String::new(),
952 }
953 }
954
955 pub fn property(&self) -> Option<PropertyViewModelRef> {
957 self.property.clone()
958 }
959
960 pub fn boxed_property(&self) -> Option<BoxedValue> {
962 self.property.as_ref().map(PropertyViewModelRef::boxed)
963 }
964
965 pub fn children(&self) -> Rc<Vec<Rc<PropertiesViewItem>>> {
966 self.children.clone()
967 }
968
969 pub fn item_count(&self) -> i32 {
971 self.children.len() as i32
972 }
973
974 pub fn is_pinned(&self) -> bool {
975 self.property.as_ref().is_some_and(|property| property.is_pinned())
976 }
977
978 pub fn set_is_pinned(&self, value: bool) {
979 if let Some(property) = &self.property {
980 property.set_is_pinned(value);
981 }
982 }
983
984 pub fn is_expanded(&self) -> bool {
985 self.is_expanded.get()
986 }
987
988 pub fn set_is_expanded(&self, value: bool) {
989 if self.is_expanded.replace(value) != value && self.property.is_none() {
990 self.property_changed.raise("IsExpanded");
991 }
992 }
993
994 pub fn type_(&self) -> String {
995 self.property.as_ref().map(PropertyViewModelRef::type_).unwrap_or_default()
996 }
997
998 pub fn assigned_type(&self) -> String {
999 self.property.as_ref().map(|property| type_name(property.assigned_type())).unwrap_or_default()
1000 }
1001
1002 pub fn property_type(&self) -> String {
1003 self.property.as_ref().map(|property| type_name(property.property_type())).unwrap_or_default()
1004 }
1005
1006 pub fn priority(&self) -> String {
1007 self.property.as_ref().map(PropertyViewModelRef::priority).unwrap_or_default()
1008 }
1009}
1010
1011ferro_markup_type!(class PropertiesViewItem {
1012 this: Rc<PropertiesViewItem>,
1013 handles: [PropertiesViewItem, Rc<PropertiesViewItem>, Option<Rc<PropertiesViewItem>>],
1014 properties: [
1015 Name: String { get: PropertiesViewItem::name },
1016 IsGroup: bool { get: PropertiesViewItem::is_group },
1017 ItemCount: i32 { get: PropertiesViewItem::item_count },
1018 Property: Option<BoxedValue> { get: PropertiesViewItem::boxed_property },
1019 IsPinned: bool { get: PropertiesViewItem::is_pinned, set: PropertiesViewItem::set_is_pinned },
1020 IsExpanded: bool { get: PropertiesViewItem::is_expanded, set: PropertiesViewItem::set_is_expanded },
1021 Type: String { get: PropertiesViewItem::type_ },
1022 AssignedType: String { get: PropertiesViewItem::assigned_type },
1023 PropertyType: String { get: PropertiesViewItem::property_type },
1024 Priority: String { get: PropertiesViewItem::priority },
1025 ],
1026 notify_property_changed: PropertiesViewItem,
1027});
1028
1029pub const PROPERTY_NAME_CELL_TEMPLATE: &str = "PropertyNameCell";
1031pub const PROPERTY_VALUE_CELL_TEMPLATE: &str = "PropertyValueCell";
1033
1034fn rows_comparison(
1037 ascending: bool,
1038 compare: fn(&PropertyViewModelRef, &PropertyViewModelRef) -> Ordering,
1039) -> Comparison<PropertiesViewItem> {
1040 Rc::new(move |x: &Rc<PropertiesViewItem>, y: &Rc<PropertiesViewItem>| match (&x.property, &y.property) {
1041 (Some(x), Some(y)) => {
1042 let ordering = compare(x, y);
1043 if ascending {
1044 ordering
1045 } else {
1046 ordering.reverse()
1047 }
1048 }
1049 _ => {
1050 let group_x = group_index(&x.group_name());
1051 let group_y = group_index(&y.group_name());
1052 group_x.cmp(&group_y).then_with(|| x.group_name().cmp(&y.group_name()))
1053 }
1054 })
1055}
1056
1057pub struct PropertiesView {
1064 weak_self: Weak<PropertiesView>,
1065 properties: Vec<PropertyViewModelRef>,
1066 filter: Rc<dyn Fn(&PropertyViewModelRef) -> bool>,
1067 items: RefCell<Rc<Vec<Rc<PropertiesViewItem>>>>,
1068 collapsed_groups: RefCell<HashSet<String>>,
1069 source: Rc<HierarchicalTreeDataGridSource<PropertiesViewItem>>,
1070 type_column: RefCell<Option<ColumnRef<PropertiesViewItem>>>,
1071 assigned_type_column: RefCell<Option<ColumnRef<PropertiesViewItem>>>,
1072 property_type_column: RefCell<Option<ColumnRef<PropertiesViewItem>>>,
1073 selection_changed: RefCell<Option<Rc<dyn Fn(Option<PropertyViewModelRef>)>>>,
1074 updating_selection: Cell<bool>,
1075}
1076
1077impl PartialEq for PropertiesView {
1078 fn eq(&self, other: &Self) -> bool {
1079 std::ptr::eq(self, other)
1080 }
1081}
1082
1083fn header(text: &str) -> Option<BoxedValue> {
1084 Some(Rc::new(text.to_string()) as BoxedValue)
1085}
1086
1087impl PropertiesView {
1088 pub fn new(properties: Vec<PropertyViewModelRef>, filter: Rc<dyn Fn(&PropertyViewModelRef) -> bool>) -> Rc<Self> {
1089 let source = HierarchicalTreeDataGridSource::new(
1090 Rc::new(Vec::<Rc<PropertiesViewItem>>::new()) as Models<PropertiesViewItem>
1091 );
1092
1093 let this = Rc::new_cyclic(|weak_self: &Weak<Self>| Self {
1094 weak_self: weak_self.clone(),
1095 properties,
1096 filter,
1097 items: RefCell::new(Rc::new(Vec::new())),
1098 collapsed_groups: RefCell::new(HashSet::new()),
1099 source,
1100 type_column: RefCell::new(None),
1101 assigned_type_column: RefCell::new(None),
1102 property_type_column: RefCell::new(None),
1103 selection_changed: RefCell::new(None),
1104 updating_selection: Cell::new(false),
1105 });
1106
1107 this.create_columns();
1108 this.refresh();
1109
1110 if let Some(row_selection) = this.source.row_selection() {
1111 let weak = this.weak_self.clone();
1112 row_selection.add_selection_changed(Rc::new(
1113 move |_: &TreeSelectionModelSelectionChangedEventArgsOf<PropertiesViewItem>| {
1114 if let Some(this) = weak.upgrade() {
1115 this.on_selection_changed();
1116 }
1117 },
1118 ));
1119 }
1120
1121 this
1122 }
1123
1124 fn create_columns(&self) {
1128 let columns = self.source.columns();
1129
1130 let name_options = TemplateColumnOptions::<PropertiesViewItem>::new();
1132 name_options.set_can_user_resize_column(Some(true));
1133 name_options.set_can_user_sort_column(Some(true));
1134 name_options.set_compare_ascending(Some(rows_comparison(true, |x, y| x.name().cmp(&y.name()))));
1135 name_options.set_compare_descending(Some(rows_comparison(false, |x, y| x.name().cmp(&y.name()))));
1136 let name_column = TemplateColumn::from_resource_keys(
1137 header("Property"),
1138 ResourceKey::from(PROPERTY_NAME_CELL_TEMPLATE),
1139 None,
1140 Some(GridLength::new(1.0, GridUnitType::Auto)),
1143 Some(Rc::new(name_options)),
1144 );
1145 let weak = self.weak_self.clone();
1146 columns.add(HierarchicalExpanderColumn::new(
1147 name_column as Rc<dyn IViewColumn<PropertiesViewItem>>,
1148 |x: &Rc<PropertiesViewItem>| {
1149 if x.is_group() {
1150 Some(x.children() as Models<PropertiesViewItem>)
1151 } else {
1152 None
1153 }
1154 },
1155 Some(Accessor::member("IsGroup", |x: &Rc<PropertiesViewItem>| x.is_group())),
1156 Some(
1157 Accessor::member("IsExpanded", |x: &Rc<PropertiesViewItem>| x.is_expanded()).with_setter(
1158 move |x: &Rc<PropertiesViewItem>, value: bool| {
1159 x.set_is_expanded(value);
1160 if let Some(this) = weak.upgrade() {
1161 this.group_expanded_changed(x, value);
1162 }
1163 },
1164 ),
1165 ),
1166 ) as ColumnRef<PropertiesViewItem>);
1167
1168 let value_options = TemplateColumnOptions::<PropertiesViewItem>::new();
1170 value_options.set_can_user_resize_column(Some(true));
1171 value_options.set_can_user_sort_column(Some(false));
1172 columns.add(TemplateColumn::from_resource_keys(
1173 header("Value"),
1174 ResourceKey::from(PROPERTY_VALUE_CELL_TEMPLATE),
1175 None,
1176 Some(GridLength::new(200.0, GridUnitType::Pixel)),
1179 Some(Rc::new(value_options)),
1180 ) as ColumnRef<PropertiesViewItem>);
1181
1182 let type_column =
1183 Self::text_column("Type", "Type", |x: &Rc<PropertiesViewItem>| x.type_(), |x, y| x.type_().cmp(&y.type_()));
1184 let assigned_type_column = Self::text_column(
1185 "Assigned Type",
1186 "AssignedType",
1187 |x: &Rc<PropertiesViewItem>| x.assigned_type(),
1188 |x, y| type_name(x.assigned_type()).cmp(&type_name(y.assigned_type())),
1189 );
1190 let property_type_column = Self::text_column(
1191 "Property Type",
1192 "PropertyType",
1193 |x: &Rc<PropertiesViewItem>| x.property_type(),
1194 |x, y| type_name(x.property_type()).cmp(&type_name(y.property_type())),
1195 );
1196 let priority_column = Self::text_column(
1197 "Priority",
1198 "Priority",
1199 |x: &Rc<PropertiesViewItem>| x.priority(),
1200 |x, y| x.priority().cmp(&y.priority()),
1201 );
1202
1203 columns.add(type_column.clone());
1204 columns.add(assigned_type_column.clone());
1205 columns.add(property_type_column.clone());
1206 columns.add(priority_column);
1207
1208 *self.type_column.borrow_mut() = Some(type_column);
1209 *self.assigned_type_column.borrow_mut() = Some(assigned_type_column);
1210 *self.property_type_column.borrow_mut() = Some(property_type_column);
1211
1212 self.set_property_type_columns(false);
1213 }
1214
1215 fn text_column(
1217 header_text: &str,
1218 member: &'static str,
1219 getter: impl Fn(&Rc<PropertiesViewItem>) -> String + 'static,
1220 compare: fn(&PropertyViewModelRef, &PropertyViewModelRef) -> Ordering,
1221 ) -> ColumnRef<PropertiesViewItem> {
1222 let options = TextColumnOptions::<PropertiesViewItem>::new();
1223 options.set_can_user_resize_column(Some(true));
1224 options.set_can_user_sort_column(Some(true));
1225 options.set_compare_ascending(Some(rows_comparison(true, compare)));
1226 options.set_compare_descending(Some(rows_comparison(false, compare)));
1227 TextColumn::new(header(header_text), Accessor::member(member, getter), None, Some(Rc::new(options)))
1228 as ColumnRef<PropertiesViewItem>
1229 }
1230
1231 pub fn set_property_type_columns(&self, show_property_type: bool) {
1234 if let Some(column) = self.type_column.borrow().as_ref() {
1235 IColumn::set_is_visible(&**column, !show_property_type);
1236 }
1237 if let Some(column) = self.assigned_type_column.borrow().as_ref() {
1238 IColumn::set_is_visible(&**column, show_property_type);
1239 }
1240 if let Some(column) = self.property_type_column.borrow().as_ref() {
1241 IColumn::set_is_visible(&**column, show_property_type);
1242 }
1243 }
1244
1245 pub fn source(&self) -> Rc<HierarchicalTreeDataGridSource<PropertiesViewItem>> {
1247 self.source.clone()
1248 }
1249
1250 pub fn source_ref(&self) -> TreeDataGridSourceRef {
1252 TreeDataGridSourceRef::of::<PropertiesViewItem, _>(self.source.clone())
1253 }
1254
1255 pub fn groups(&self) -> Rc<Vec<Rc<PropertiesViewItem>>> {
1257 self.items.borrow().clone()
1258 }
1259
1260 pub fn items(&self) -> Vec<PropertyViewModelRef> {
1263 self.groups().iter().flat_map(|group| group.children().iter().filter_map(|item| item.property()).collect::<Vec<_>>()).collect()
1264 }
1265
1266 pub fn set_selection_changed(&self, handler: Rc<dyn Fn(Option<PropertyViewModelRef>)>) {
1269 *self.selection_changed.borrow_mut() = Some(handler);
1270 }
1271
1272 pub fn refresh(&self) {
1275 let selected = self.selected();
1276
1277 let mut properties: Vec<PropertyViewModelRef> =
1278 self.properties.iter().filter(|property| (self.filter)(*property)).cloned().collect();
1279 properties.sort_by_cached_key(property_sort_key);
1282
1283 let mut groups: Vec<(String, Vec<Rc<PropertiesViewItem>>)> = Vec::new();
1284 for property in properties {
1285 let group = property.group();
1286 let item = PropertiesViewItem::property_item(property);
1287 match groups.iter_mut().find(|(name, _)| *name == group) {
1288 Some((_, items)) => items.push(item),
1289 None => groups.push((group, vec![item])),
1290 }
1291 }
1292
1293 let collapsed_groups = self.collapsed_groups.borrow().clone();
1294 let items: Rc<Vec<Rc<PropertiesViewItem>>> = Rc::new(
1295 groups
1296 .into_iter()
1297 .map(|(name, children)| {
1298 let is_expanded = !collapsed_groups.contains(&name);
1299 PropertiesViewItem::group(name, children, is_expanded)
1300 })
1301 .collect(),
1302 );
1303 *self.items.borrow_mut() = items.clone();
1304
1305 self.updating_selection.set(true);
1306 self.source.set_items(items as Models<PropertiesViewItem>);
1307 let reselected = selected.as_ref().is_some_and(|selected| self.select_row(Some(selected)));
1308 self.updating_selection.set(false);
1309
1310 if selected.is_some() && !reselected {
1311 self.on_selection_changed();
1312 }
1313 }
1314
1315 pub fn index_path_of(&self, property: &PropertyViewModelRef) -> Option<IndexPath> {
1317 for (group_index, group) in self.groups().iter().enumerate() {
1318 for (index, item) in group.children().iter().enumerate() {
1319 if item.property().as_ref() == Some(property) {
1320 return Some(IndexPath::new(group_index as i32).append(index as i32));
1321 }
1322 }
1323 }
1324 None
1325 }
1326
1327 pub fn row_index_of(&self, property: &PropertyViewModelRef) -> i32 {
1330 match self.index_path_of(property) {
1331 Some(index) => self.source.get_row_index(&index, 0),
1332 None => -1,
1333 }
1334 }
1335
1336 pub fn selected(&self) -> Option<PropertyViewModelRef> {
1338 self.source
1339 .row_selection()
1340 .and_then(|selection| selection.base().selected_item())
1341 .and_then(|item| item.property())
1342 }
1343
1344 pub fn select(&self, property: Option<&PropertyViewModelRef>) {
1346 if self.updating_selection.get() || self.selected().as_ref() == property {
1347 return;
1348 }
1349 self.updating_selection.set(true);
1350 self.select_row(property);
1351 self.updating_selection.set(false);
1352 }
1353
1354 fn select_row(&self, property: Option<&PropertyViewModelRef>) -> bool {
1357 let Some(selection) = self.source.row_selection() else {
1358 return false;
1359 };
1360 let index = property.and_then(|property| self.index_path_of(property));
1361 match index {
1362 Some(index) => {
1363 self.source.expand(&index.parent());
1364 selection.base().set_selected_index(index);
1365 true
1366 }
1367 None => {
1368 selection.base().clear();
1369 false
1370 }
1371 }
1372 }
1373
1374 fn on_selection_changed(&self) {
1375 if self.updating_selection.get() {
1376 return;
1377 }
1378 let handler = self.selection_changed.borrow().clone();
1379 if let Some(handler) = handler {
1380 handler(self.selected());
1381 }
1382 }
1383
1384 fn group_expanded_changed(&self, item: &PropertiesViewItem, is_expanded: bool) {
1386 if !item.is_group() {
1387 return;
1388 }
1389 let mut collapsed_groups = self.collapsed_groups.borrow_mut();
1390 if is_expanded {
1391 collapsed_groups.remove(&item.name());
1392 } else {
1393 collapsed_groups.insert(item.name());
1394 }
1395 }
1396}
1397
1398ferro_markup_type!(class PropertiesView {
1399 this: Rc<PropertiesView>,
1400 handles: [PropertiesView, Rc<PropertiesView>, Option<Rc<PropertiesView>>],
1401 properties: [
1402 Source: TreeDataGridSourceRef { get: PropertiesView::source_ref },
1403 ],
1404 methods: [
1405 fn Refresh() => PropertiesView::refresh,
1406 ],
1407});
1408
1409ferro_markup_type!(class ControlDetailsViewModel {
1410 this: Rc<ControlDetailsViewModel>,
1411 handles: [ControlDetailsViewModel, Rc<ControlDetailsViewModel>, Option<Rc<ControlDetailsViewModel>>],
1412 properties: [
1413 CanNavigateToParentProperty: bool { get: ControlDetailsViewModel::can_navigate_to_parent_property },
1414 TreePage: Option<Rc<TreePageViewModel>> { get: ControlDetailsViewModel::tree_page },
1415 PropertiesView: Option<Rc<PropertiesView>> { get: ControlDetailsViewModel::properties_view },
1416 AppliedFrames: FerroList<Rc<ValueFrameViewModel>> { get: ControlDetailsViewModel::applied_frames },
1417 PseudoClasses: FerroList<Rc<PseudoClassViewModel>> { get: ControlDetailsViewModel::pseudo_classes },
1418 SelectedEntity: Option<BoxedValue> {
1419 get: ControlDetailsViewModel::selected_entity,
1420 set: ControlDetailsViewModel::set_selected_entity
1421 },
1422 SelectedEntityName: Option<String> {
1423 get: ControlDetailsViewModel::selected_entity_name,
1424 set: ControlDetailsViewModel::set_selected_entity_name
1425 },
1426 SelectedEntityType: Option<String> {
1427 get: ControlDetailsViewModel::selected_entity_type,
1428 set: ControlDetailsViewModel::set_selected_entity_type
1429 },
1430 SelectedProperty: Option<BoxedValue> {
1431 get: |this: &Rc<ControlDetailsViewModel>| this.selected_property().map(|property| property.boxed()),
1432 set: |this: &Rc<ControlDetailsViewModel>, value: Option<BoxedValue>| {
1433 this.set_selected_property(value.as_ref().and_then(PropertyViewModelRef::from_boxed))
1434 }
1435 },
1436 SnapshotFrames: bool {
1437 get: ControlDetailsViewModel::snapshot_frames,
1438 set: ControlDetailsViewModel::set_snapshot_frames
1439 },
1440 ShowInactiveFrames: bool {
1441 get: ControlDetailsViewModel::show_inactive_frames,
1442 set: ControlDetailsViewModel::set_show_inactive_frames
1443 },
1444 FramesStatus: Option<String> {
1445 get: ControlDetailsViewModel::frames_status,
1446 set: ControlDetailsViewModel::set_frames_status
1447 },
1448 Layout: Option<Rc<ControlLayoutViewModel>> { get: ControlDetailsViewModel::layout },
1449 ],
1450 methods: [
1451 fn UpdateStyleFilters() => ControlDetailsViewModel::update_style_filters,
1452 fn NavigateToSelectedProperty() => ControlDetailsViewModel::navigate_to_selected_property,
1453 fn NavigateToParentProperty() => ControlDetailsViewModel::navigate_to_parent_property,
1454 fn TogglePinnedProperty(Option<BoxedValue>) => ControlDetailsViewModel::toggle_pinned_property,
1455 fn Dispose() => ControlDetailsViewModel::dispose,
1456 ],
1457 notify_property_changed: ControlDetailsViewModel,
1458});
1459
1460pub fn register_details_value_types() {
1464 use super::{BindingSetterViewModel, ResourceSetterViewModel, SetterViewModel};
1465
1466 ValueTypes::register_reference::<ControlDetailsViewModel>();
1467 ValueTypes::register_reference::<PropertiesView>();
1468 ValueTypes::register_reference::<PropertiesViewItem>();
1469 ValueTypes::register_reference::<ControlLayoutViewModel>();
1470 ValueTypes::register_reference::<ValueFrameViewModel>();
1471 ValueTypes::register_reference::<SetterViewModel>();
1472 ValueTypes::register_reference::<BindingSetterViewModel>();
1473 ValueTypes::register_reference::<ResourceSetterViewModel>();
1474 ValueTypes::register_reference::<PseudoClassViewModel>();
1475 ValueTypes::register_reference::<FerroPropertyViewModel>();
1476 ValueTypes::register_reference::<ClrPropertyViewModel>();
1477}
1478
1479ferroui_controls::ferro_markup_list!(pub ValueFrameViewModelList: Rc<ValueFrameViewModel>);
1482ferroui_controls::ferro_markup_list!(pub PseudoClassViewModelList: Rc<PseudoClassViewModel>);