Skip to main content

ferroui_diagnostics/diagnostics/views/
control_details_view.rs

1//! Port of `Diagnostics/Views/ControlDetailsView.xaml.cs`: the class of the document
2//! `ControlDetailsView.xaml`.
3//!
4//! The property grid of the document is a TreeDataGrid, where the managed original has a
5//! DataGrid (docs/porting/DEVIATIONS.md, Developer tools).
6
7use crate::diagnostics::view_models::{ControlDetailsViewModel, SetterViewModelRef};
8use ferroui_base::data::core::ValueTypes;
9use ferroui_base::input::{InputElementImpl, PointerPressedEventArgs, TappedEventArgs};
10use ferroui_base::interactivity::{IRoutedEventArgs, InteractiveImpl};
11use ferroui_base::layout::LayoutableImpl;
12use ferroui_base::StyledElementImplExt;
13use ferroui_base::{
14    ferro_class, ferro_class_info, ferro_impl_classes, instantiate, BoxedValue, FerroObjectImpl, Ref,
15    StyledElementImpl, VisualImpl,
16};
17use ferroui_controls::primitives::TemplatedControlImpl;
18use ferroui_controls::{ContentControlImpl, Control, ControlImpl, UserControl};
19use ferroui_controls_treedatagrid::TreeDataGrid;
20use std::rc::Rc;
21
22/// The details of the selected object of a tree page: the property grid, the layout explorer,
23/// the value frames with their setters, and the pseudo-classes.
24#[repr(C)]
25pub struct ControlDetailsView {
26    base: UserControl,
27}
28
29ferro_class!(ControlDetailsView: UserControl);
30ferro_impl_classes!(
31    ControlDetailsView: FerroObjectImpl,
32    VisualImpl,
33    LayoutableImpl,
34    InteractiveImpl,
35    InputElementImpl,
36    ControlImpl,
37    TemplatedControlImpl,
38    ContentControlImpl
39);
40impl StyledElementImpl for ControlDetailsView {
41    fn on_data_context_changed(this: &Self) {
42        Self::parent_on_data_context_changed(this);
43
44        // Deviation (DEVIATIONS.md, Developer tools): the view is reused for the details of the next
45        // selected control (the view locator recycles it); its property grid starts at the top, as
46        // the new view of upstream does.
47        if let Some(scroll) = this.find_control::<TreeDataGrid>("DataGrid").and_then(|grid| grid.scroll()) {
48            scroll.set_offset(ferroui_base::Vector::default());
49        }
50    }
51}
52
53ferro_class_info!(ControlDetailsView {
54    new: ControlDetailsView::new,
55    markup: {
56        methods: [
57            fn PropertiesGrid_OnDoubleTapped(Option<BoxedValue>, Rc<dyn IRoutedEventArgs>) =>
58                |this: &Ref<ControlDetailsView>, sender: Option<BoxedValue>, e: Rc<dyn IRoutedEventArgs>| {
59                    if let Some(e) = e.downcast_ref::<TappedEventArgs>() {
60                        this.properties_grid_on_double_tapped(&sender, e)
61                    }
62                },
63            fn PropertyNamePressed(Option<BoxedValue>, Rc<dyn IRoutedEventArgs>) =>
64                |this: &Ref<ControlDetailsView>, sender: Option<BoxedValue>, e: Rc<dyn IRoutedEventArgs>| {
65                    if let Some(e) = e.downcast_ref::<PointerPressedEventArgs>() {
66                        this.property_name_pressed(&sender, e)
67                    }
68                },
69        ],
70    },
71});
72
73/// The details view model of an untyped value (`DataContext as ControlDetailsViewModel`).
74fn details_of(value: Option<&BoxedValue>) -> Option<Rc<ControlDetailsViewModel>> {
75    let value = value?;
76    if let Some(details) = value.downcast_ref::<Rc<ControlDetailsViewModel>>() {
77        return Some(details.clone());
78    }
79    let any: Rc<dyn std::any::Any> = value.clone();
80    any.downcast::<ControlDetailsViewModel>().ok()
81}
82
83impl ControlDetailsView {
84    /// The URI of the document of the class.
85    pub const DOCUMENT_URI: &'static str = "ferres://FerroUI.Diagnostics/diagnostics/views/ControlDetailsView.xaml";
86
87    pub fn construct() -> Self {
88        Self { base: UserControl::construct() }
89    }
90
91    /// # Panics
92    /// When the document fails to load or lacks the grid (an exception of the constructor in the
93    /// managed original).
94    pub fn new() -> Ref<Self> {
95        let this = instantiate(Self::construct());
96        Self::initialize_component(&this);
97
98        // `_dataGrid = this.GetControl<DataGrid>("DataGrid")`: the grid is looked up when it is
99        // used (`data_grid`), so that the view does not hold an element of its own tree.
100        let _ = this.data_grid();
101
102        this
103    }
104
105    fn initialize_component(this: &Ref<Self>) {
106        crate::register_types();
107        if let Err(error) = crate::compiled_diagnostics_views_control_details_view::populate(None, this) {
108            panic!("{}: {error}", Self::DOCUMENT_URI);
109        }
110    }
111
112    /// The property grid (`_dataGrid`).
113    fn data_grid(&self) -> Ref<TreeDataGrid> {
114        self.get_control::<TreeDataGrid>("DataGrid")
115    }
116
117    fn properties_grid_on_double_tapped(&self, sender: &Option<BoxedValue>, _e: &TappedEventArgs) {
118        let grid = sender
119            .as_ref()
120            .and_then(|sender| ValueTypes::as_object(&**sender))
121            .and_then(|sender| sender.cast::<TreeDataGrid>());
122        if let Some(grid) = grid {
123            if let Some(control_details) = details_of(grid.data_context().as_ref()) {
124                control_details.navigate_to_selected_property();
125            }
126        }
127    }
128
129    pub fn property_name_pressed(&self, sender: &Option<BoxedValue>, _e: &PointerPressedEventArgs) {
130        let Some(main_vm) = details_of(self.data_context().as_ref()) else {
131            return;
132        };
133
134        let control = sender
135            .as_ref()
136            .and_then(|sender| ValueTypes::as_object(&**sender))
137            .and_then(|sender| sender.cast::<Control>());
138        let setter_vm = control
139            .and_then(|control| control.data_context())
140            .and_then(|data_context| SetterViewModelRef::from_boxed(&data_context));
141
142        if let Some(setter_vm) = setter_vm {
143            main_vm.select_property(setter_vm.property());
144
145            if let Some(selected_property) = main_vm.selected_property() {
146                // `_dataGrid.ScrollIntoView(mainVm.SelectedProperty, null)`: the row of the
147                // property, brought into view by the presenter of the rows.
148                let row_index = main_vm
149                    .properties_view()
150                    .map(|properties_view| properties_view.row_index_of(&selected_property))
151                    .unwrap_or(-1);
152                if row_index >= 0 {
153                    if let Some(rows_presenter) = self.data_grid().rows_presenter() {
154                        rows_presenter.bring_into_view(row_index, None);
155                    }
156                }
157            }
158        }
159    }
160}