Skip to main content

ferroui_diagnostics/diagnostics/view_models/
control_details_view_model.rs

1//! Port of `Diagnostics/ViewModels/ControlDetailsViewModel.cs`.
2//!
3//! The managed original shows the properties of the selected object in a DataGrid through a
4//! `DataGridCollectionView`, grouped by `PropertyViewModel.Group`, sorted by its
5//! `PropertyComparer` and filtered by the properties filter of the tree page. The port shows
6//! them in a TreeDataGrid: [`PropertiesView`] takes the place of the collection view. It holds
7//! the properties, groups, sorts and filters them as the collection view does, and presents the
8//! groups as the rows of a hierarchical source ([`PropertiesViewItem`]: a group or a property)
9//! with the properties below them (docs/porting/DEVIATIONS.md, Developer tools).
10
11use 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
42/// The names of the pinned properties, shared by the details of every object (`ISet<string>`).
43pub type PinnedProperties = Rc<RefCell<HashSet<String>>>;
44
45/// The object whose property changes the details follow: its handler token, and how to remove
46/// it.
47enum EntitySubscription {
48    /// `FerroObject.PropertyChanged`.
49    Object(Ref<FerroObject>, u64),
50    /// `INotifyPropertyChanged.PropertyChanged` of a model.
51    Model(BoxedValue, u64),
52}
53
54/// The notification interface of an untyped value: its model type metadata, else its markup
55/// metadata (`o as INotifyPropertyChanged`).
56fn 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
70/// The details of an object: its properties, the frames of its values with their setters, its
71/// pseudo-classes and its layout.
72pub 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            // `styledElement.Classes.AddListener(this)`: `IClassesChangedListener.Changed`.
146            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            // `GetCustomAttributes<PseudoClassesAttribute>(true)`: the pseudo-classes the class
155            // and its base classes declare, the class first.
156            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            // `OrderBy(s => s.Priority)`: a stable sort.
179            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    /// The object of the details, as an untyped value.
195    fn boxed_object(&self) -> BoxedValue {
196        Rc::new(self.ferro_object.clone())
197    }
198
199    /// `(_ferroObject as Control)?.Name ?? _ferroObject.ToString()`.
200    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    /// The tree page the details belong to (held weakly: the page holds the details).
212    pub fn tree_page(&self) -> Option<Rc<TreePageViewModel>> {
213        self.tree_page.upgrade()
214    }
215
216    /// The properties of the selected entity, grouped, sorted and filtered (the
217    /// `DataGridCollectionView` of the managed original).
218    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    /// Sets the selected property, and selects its row in the grid (the two-way binding of
275    /// `SelectedItem` of the managed original).
276    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    /// `RaiseAndSetIfChanged` of the base class, through this class's `RaisePropertyChanged`,
313    /// which runs the override of `OnPropertyChanged` first.
314    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    /// `RaisePropertyChanged`: the override of `OnPropertyChanged`, then the handlers.
324    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    /// The override of `OnPropertyChanged`: leaving the snapshot of the frames updates them.
330    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    /// The registered properties of an object: the ones of its class and the attached properties
372    /// registered for it; nothing for a value that is not an object.
373    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        // `Union`: distinct, in order.
380        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    /// The plain properties of an object, and with `show_implemented_interfaces` the ones of the
393    /// contracts its type implements.
394    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    /// The markup metadata of the type of an object and of its base types, the type first: the
413    /// counterpart of the reflection over a type and its base types of the managed original.
414    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    /// `GetClrProperties(o, o.GetType())`: the public properties of the type of an object, with
441    /// the type that declares each (a property a derived type redeclares once).
442    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    /// `o.GetType().GetInterfaces()`: the contracts the type of an object and its base types
456    /// implement, by their markup metadata.
457    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    /// `FerroObject.PropertyChanged` of the selected entity.
486    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    /// `INotifyPropertyChanged.PropertyChanged` of the selected entity.
504    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    /// `IClassesChangedListener.Changed`.
522    fn classes_changed(&self) {
523        if !self.snapshot_frames() {
524            self.post_update_styles();
525        }
526    }
527
528    /// `Dispatcher.UIThread.Post(UpdateStyles)`.
529    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    /// `FilterProperty`: whether the properties filter of the tree page shows a property.
588    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    /// The public properties of a type and of the contracts it implements
596    /// (`GetAllPublicProperties`).
597    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        // Strip out interface names.
641        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    /// Selects the property `property` of the object of the details, navigating back to the
736    /// object first.
737    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    /// Pins or unpins the property `parameter` (a property view model, or the row of one).
770    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    /// Shows the type columns of the grid as the main view asks: the type of the property and
792    /// of its value in one column, or in two (the bindings of `IsVisible` of the columns to
793    /// `MainViewModel.ShowDetailsPropertyType` of the managed original).
794    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
814/// Whether a type is a value type (`Type.IsValueType`): a primitive, a structure or an
815/// enumeration with metadata, or the nullable form of one; not text, a class or a contract.
816pub(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
849/// `PropertyComparer.GroupIndex`.
850fn 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
860/// `PropertyComparer.Compare` as a sort key: by the index of the group, then by the name
861/// (ordinal).
862fn property_sort_key(property: &PropertyViewModelRef) -> (i32, String) {
863    (group_index(&property.group()), property.name())
864}
865
866/// A row of the property grid: a group of properties, or a property of a group.
867pub 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
881/// The notifications of the property of a row (the row of a group raises its own).
882impl 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    /// States what the source and its columns observe of the rows: their property change
893    /// notifications. Once per thread.
894    fn register() {
895        if !ModelTypes::is_registered(TypeId::of::<PropertiesViewItem>()) {
896            ModelTypes::register::<PropertiesViewItem>(|builder| builder.notify_property_changed());
897        }
898    }
899
900    /// The row of the group `name` with the properties `children`.
901    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    /// The row of a property.
913    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    /// The row of an untyped value (a data context of a cell).
925    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    /// The name of the group, or of the property.
938    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    /// The group of the row: the group itself, or the group of the property.
947    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    /// The property of the row; `None` for a group.
956    pub fn property(&self) -> Option<PropertyViewModelRef> {
957        self.property.clone()
958    }
959
960    /// The property as a value of its class; `None` for a group.
961    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    /// The number of properties of a group (the item count of a group header).
970    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
1029/// The key of the data template of the cells of the name column, a resource of the view.
1030pub const PROPERTY_NAME_CELL_TEMPLATE: &str = "PropertyNameCell";
1031/// The key of the data template of the cells of the value column, a resource of the view.
1032pub const PROPERTY_VALUE_CELL_TEMPLATE: &str = "PropertyValueCell";
1033
1034/// A comparison of the rows of the grid for a sort by a column: the groups keep their order
1035/// (`PropertyComparer`), the properties of a group follow `compare` in the direction asked.
1036fn 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
1057/// The properties of an object for the property grid: the counterpart of the
1058/// `DataGridCollectionView` of the managed original, with its group description (by
1059/// `Group`), its sort description (`PropertyComparer`) and its filter, presented as a
1060/// hierarchical TreeDataGrid source whose root rows are the groups.
1061///
1062/// Deviation (DEVIATIONS.md, Developer tools): the property grid is a TreeDataGrid, not a DataGrid.
1063pub 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    /// The columns of the grid, as upstream's columns: the name (with the pin of a property, and
1125    /// the expander of a group), the value (an editor), the type, the assigned type and the type
1126    /// of the property (shown instead of the type), and the priority.
1127    fn create_columns(&self) {
1128        let columns = self.source.columns();
1129
1130        // `DataGridTemplateColumn Header="Property"`, under the expander of the groups.
1131        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            // Deviation (DEVIATIONS.md, Developer tools): as wide as the names, so that the
1141            // columns of types after it do not take their room.
1142            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        // `DataGridTemplateColumn Header="Value" Width="100"`: the editor of the value.
1169        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            // Deviation (DEVIATIONS.md, Developer tools): 200 px, wide enough for the editors of the
1177            // compact form (upstream: 100).
1178            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    /// A read-only text column of a value of the rows (`DataGridTextColumn IsReadOnly="True"`).
1216    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    /// Shows the type of a property and of its value in one column, or (with `show_property_type`)
1232    /// in two.
1233    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    /// The source of the grid.
1246    pub fn source(&self) -> Rc<HierarchicalTreeDataGridSource<PropertiesViewItem>> {
1247        self.source.clone()
1248    }
1249
1250    /// The source of the grid, as the grid takes it.
1251    pub fn source_ref(&self) -> TreeDataGridSourceRef {
1252        TreeDataGridSourceRef::of::<PropertiesViewItem, _>(self.source.clone())
1253    }
1254
1255    /// The rows of the groups.
1256    pub fn groups(&self) -> Rc<Vec<Rc<PropertiesViewItem>>> {
1257        self.items.borrow().clone()
1258    }
1259
1260    /// The properties the view shows, in the order it shows them (`foreach (object o in
1261    /// PropertiesView)`).
1262    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    /// Sets the handler of a change of the selected row (the selected property, `None` for a
1267    /// group or no row).
1268    pub fn set_selection_changed(&self, handler: Rc<dyn Fn(Option<PropertyViewModelRef>)>) {
1269        *self.selection_changed.borrow_mut() = Some(handler);
1270    }
1271
1272    /// Groups, sorts and filters the properties again (`Refresh()`), keeping the selected
1273    /// property selected while it is shown.
1274    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        // `PropertyComparer`, with the keys of each property read once (a stable sort, as
1280        // `sort_by`): the group and the name are new strings at every read.
1281        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    /// The index path of the row of a property.
1316    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    /// The index of the row of a property among the rows the grid shows; `-1` when it shows
1328    /// none.
1329    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    /// The selected property; `None` when no row or a group is selected.
1337    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    /// Selects the row of a property, or clears the selection.
1345    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    /// Selects the row of a property, expanding its group; `false` when the view does not show
1355    /// it (the selection is cleared then).
1356    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    /// Remembers the groups the user collapsed, for the next refresh.
1385    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
1460/// What the untyped value conversions must know about the view models of the control details:
1461/// they are reference types, held by bindings as the shared objects they are. Registered once
1462/// with `ValueTypes::register_global` from the `register_types()` of the crate.
1463pub 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
1479// The lists of the control details that items controls show: their markup metadata gives the
1480// compiler the type of their items (the data type of the item templates of the documents).
1481ferroui_controls::ferro_markup_list!(pub ValueFrameViewModelList: Rc<ValueFrameViewModel>);
1482ferroui_controls::ferro_markup_list!(pub PseudoClassViewModelList: Rc<PseudoClassViewModel>);