Skip to main content

ferroui_controls/
table_view_column.rs

1use crate::i_headered::{register_headered, IHeadered};
2use crate::primitives::HeaderedContentControl;
3use crate::templates::IDataTemplate;
4use crate::utils::debug_display::{append_optional_boxed_value, build_base_debug_display};
5use crate::{AssignedBinding, ContentControl, GridLength, GridUnitType, TableView};
6use ferroui_base::layout::HorizontalAlignment;
7use ferroui_base::styling::ControlTheme;
8use ferroui_base::{
9    ferro_class, ferro_class_info, ferro_impl_classes, ferro_properties, instantiate, BoxedValue, DirectProperty,
10    FerroObjectImpl, FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Nullable, Ref, StyledElement, StyledElementImpl,
11    StyledProperty, StyledPropertyMetadata, StyledPropertyOptions, Visual, WeakRef,
12};
13use std::cell::{Cell, RefCell};
14use std::rc::Rc;
15
16/// Defines a column in a [`TableView`].
17#[repr(C)]
18pub struct TableViewColumn {
19    base: StyledElement,
20    /// The table view the column belongs to. The column does not own it (it
21    /// is its logical parent), so the reference is weak.
22    table_view: RefCell<Option<WeakRef<TableView>>>,
23    actual_width: Cell<f64>,
24    can_user_effectively_resize: Cell<bool>,
25}
26
27ferro_class!(TableViewColumn: StyledElement);
28ferro_class_info!(TableViewColumn {
29    new: TableViewColumn::new,
30    markup: {
31        property_attributes: [
32            CellTemplate: [InheritDataTypeFromItems("ItemsSource", AncestorType = type(Ref<TableView>))],
33            Binding: [InheritDataTypeFromItems("ItemsSource", AncestorType = type(Ref<TableView>))],
34        ],
35    },
36});
37ferro_impl_classes!(TableViewColumn: StyledElementImpl);
38
39impl FerroObjectImpl for TableViewColumn {
40    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
41        Self::parent_on_property_changed(this, change);
42
43        let property = change.property();
44
45        if property == Self::width_property().as_property() || property == Self::is_visible_property().as_property()
46        {
47            if let Some(table_view) = this.table_view() {
48                table_view.on_columns_size_changed();
49            }
50        }
51
52        if property == Self::can_user_resize_property().as_property() {
53            this.update_can_user_effectively_resize();
54        } else {
55            if Self::is_header_property(property) {
56                if let Some(table_view) = this.table_view() {
57                    table_view.refresh_column_headers(&this.to_ref());
58                }
59            }
60            if Self::is_cell_property(property) {
61                if let Some(table_view) = this.table_view() {
62                    table_view.refresh_column_cells(&this.to_ref());
63                }
64            }
65        }
66    }
67}
68
69ferro_properties! {
70    impl TableViewColumn {
71        /// Defines the `IsVisible` property.
72        pub fn is_visible_property() -> StyledProperty<bool> {
73            Visual::is_visible_property().add_owner::<TableViewColumn>()
74        }
75
76        /// Defines the `HeaderTheme` property.
77        pub fn header_theme_property() -> StyledProperty<Option<Ref<ControlTheme>>> {
78            FerroProperty::register::<TableViewColumn, _>("HeaderTheme", None)
79        }
80
81        /// Defines the `HeaderTemplate` property.
82        pub fn header_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
83            HeaderedContentControl::header_template_property().add_owner::<TableViewColumn>()
84        }
85
86        /// Defines the `Header` property.
87        pub fn header_property() -> StyledProperty<Option<BoxedValue>> {
88            HeaderedContentControl::header_property().add_owner::<TableViewColumn>()
89        }
90
91        /// Defines the `Width` property.
92        pub fn width_property() -> StyledProperty<GridLength> {
93            FerroProperty::register::<TableViewColumn, _>("Width", GridLength::new(1.0, GridUnitType::Star))
94        }
95
96        /// Defines the `CellTheme` property.
97        pub fn cell_theme_property() -> StyledProperty<Option<Ref<ControlTheme>>> {
98            FerroProperty::register::<TableViewColumn, _>("CellTheme", None)
99        }
100
101        /// Defines the `CellTemplate` property.
102        pub fn cell_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
103            FerroProperty::register::<TableViewColumn, _>("CellTemplate", None)
104        }
105
106        /// Defines the `Binding` property.
107        pub fn binding_property() -> StyledProperty<Option<AssignedBinding>> {
108            FerroProperty::register_with::<TableViewColumn, _>(
109                "Binding",
110                StyledPropertyOptions::new(None).assign_binding(true),
111            )
112        }
113
114        /// Defines the `CanUserResize` property.
115        pub fn can_user_resize_property() -> StyledProperty<Option<bool>> {
116            FerroProperty::register::<TableViewColumn, _>("CanUserResize", None)
117        }
118
119        /// Defines the `HorizontalContentAlignment` property.
120        pub fn horizontal_content_alignment_property() -> StyledProperty<HorizontalAlignment> {
121            ContentControl::horizontal_content_alignment_property()
122                .add_owner_with::<TableViewColumn>(StyledPropertyMetadata::new(Some(HorizontalAlignment::Left)))
123        }
124
125        /// Defines the `TableView` property.
126        ///
127        /// The column does not own its table view: the value is held weakly
128        /// and read as a handle.
129        pub fn table_view_property() -> DirectProperty<TableViewColumn, Option<Ref<TableView>>> {
130            FerroProperty::register_direct::<TableViewColumn, _>("TableView", |o| o.table_view(), None, None)
131        }
132
133        /// Defines the `ActualWidth` property.
134        pub fn actual_width_property() -> DirectProperty<TableViewColumn, f64> {
135            FerroProperty::register_direct::<TableViewColumn, _>("ActualWidth", |o| o.actual_width(), None, 0.0)
136        }
137
138        /// Defines the `CanUserEffectivelyResize` property.
139        pub fn can_user_effectively_resize_property() -> DirectProperty<TableViewColumn, bool> {
140            FerroProperty::register_direct::<TableViewColumn, _>(
141                "CanUserEffectivelyResize",
142                |o| o.can_user_effectively_resize(),
143                None,
144                false,
145            )
146        }
147    }
148}
149
150impl TableViewColumn {
151    fn static_constructor() {
152        register_headered::<TableViewColumn>();
153    }
154
155    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
156    pub fn construct() -> Self {
157        Self {
158            base: StyledElement::construct(),
159            table_view: RefCell::new(None),
160            actual_width: Cell::new(f64::NAN),
161            can_user_effectively_resize: Cell::new(false),
162        }
163    }
164
165    pub fn new() -> Ref<Self> {
166        instantiate(Self::construct())
167    }
168
169    /// Gets or sets whether the column is visible. The default is true.
170    pub fn is_visible(&self) -> bool {
171        self.get_value(Self::is_visible_property())
172    }
173
174    pub fn set_is_visible(&self, value: bool) {
175        self.set_value(Self::is_visible_property(), value)
176    }
177
178    /// Gets or sets the theme to apply to the header. It must target
179    /// [`TableViewColumnHeader`](crate::TableViewColumnHeader).
180    pub fn header_theme(&self) -> Option<Ref<ControlTheme>> {
181        self.get_value(Self::header_theme_property())
182    }
183
184    pub fn set_header_theme(&self, value: impl Into<Nullable<ControlTheme>>) {
185        self.set_value(Self::header_theme_property(), value.into().0)
186    }
187
188    /// Gets or sets the data template used to display the header.
189    pub fn header_template(&self) -> Option<Rc<dyn IDataTemplate>> {
190        self.get_value(Self::header_template_property())
191    }
192
193    pub fn set_header_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
194        self.set_value(Self::header_template_property(), value)
195    }
196
197    /// Gets or sets the column header content.
198    pub fn header(&self) -> Option<BoxedValue> {
199        self.get_value(Self::header_property())
200    }
201
202    pub fn set_header(&self, value: Option<BoxedValue>) {
203        self.set_value(Self::header_property(), value)
204    }
205
206    /// Gets or sets the column width. Supports pixel, star (*) and auto
207    /// sizing.
208    pub fn width(&self) -> GridLength {
209        self.get_value(Self::width_property())
210    }
211
212    pub fn set_width(&self, value: GridLength) {
213        self.set_value(Self::width_property(), value)
214    }
215
216    /// Gets or sets the theme to apply to the cells. It must target
217    /// [`TableViewCell`](crate::TableViewCell).
218    pub fn cell_theme(&self) -> Option<Ref<ControlTheme>> {
219        self.get_value(Self::cell_theme_property())
220    }
221
222    pub fn set_cell_theme(&self, value: impl Into<Nullable<ControlTheme>>) {
223        self.set_value(Self::cell_theme_property(), value.into().0)
224    }
225
226    /// Gets or sets the data template used to display cell content. When
227    /// set, the whole row data item is passed as the template's data
228    /// context.
229    ///
230    /// This property takes priority over [`binding`](Self::binding).
231    pub fn cell_template(&self) -> Option<Rc<dyn IDataTemplate>> {
232        self.get_value(Self::cell_template_property())
233    }
234
235    pub fn set_cell_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
236        self.set_value(Self::cell_template_property(), value)
237    }
238
239    /// Gets or sets a binding that retrieves the cell value from the row
240    /// data item. Use this for simple property display, such as a binding to
241    /// the `Name` property of the items.
242    pub fn binding(&self) -> Option<AssignedBinding> {
243        self.get_value(Self::binding_property())
244    }
245
246    pub fn set_binding(&self, value: Option<AssignedBinding>) {
247        self.set_value(Self::binding_property(), value)
248    }
249
250    /// Gets or sets whether the column can be resized. Set to `None` to use
251    /// the value of [`TableView::can_user_resize_columns`]. The default is
252    /// `None`.
253    pub fn can_user_resize(&self) -> Option<bool> {
254        self.get_value(Self::can_user_resize_property())
255    }
256
257    pub fn set_can_user_resize(&self, value: Option<bool>) {
258        self.set_value(Self::can_user_resize_property(), value)
259    }
260
261    /// Gets or sets the horizontal alignment of the content within a cell.
262    pub fn horizontal_content_alignment(&self) -> HorizontalAlignment {
263        self.get_value(Self::horizontal_content_alignment_property())
264    }
265
266    pub fn set_horizontal_content_alignment(&self, value: HorizontalAlignment) {
267        self.set_value(Self::horizontal_content_alignment_property(), value)
268    }
269
270    /// Gets the [`TableView`] associated with this column.
271    pub fn table_view(&self) -> Option<Ref<TableView>> {
272        self.table_view.borrow().as_ref().and_then(WeakRef::upgrade)
273    }
274
275    pub(crate) fn set_table_view(&self, value: Option<Ref<TableView>>) {
276        let old_value = self.table_view();
277        if old_value == value {
278            return;
279        }
280
281        *self.table_view.borrow_mut() = value.as_ref().map(Ref::downgrade);
282        self.raise_direct_property_changed(Self::table_view_property(), &old_value, &value);
283        self.update_can_user_effectively_resize();
284    }
285
286    /// Gets the actual width of the column, in device independent pixels.
287    /// If the column hasn't yet been measured, returns NaN.
288    pub fn actual_width(&self) -> f64 {
289        self.actual_width.get()
290    }
291
292    pub(crate) fn set_actual_width(&self, value: f64) {
293        // A NaN equals a NaN for the change detection of the reference.
294        if value.is_nan() && self.actual_width.get().is_nan() {
295            return;
296        }
297
298        self.set_and_raise_cell(Self::actual_width_property(), &self.actual_width, value);
299    }
300
301    /// Gets whether the column can be effectively resized. The value of
302    /// this property depends on both [`can_user_resize`](Self::can_user_resize)
303    /// and [`TableView::can_user_resize_columns`].
304    pub fn can_user_effectively_resize(&self) -> bool {
305        self.can_user_effectively_resize.get()
306    }
307
308    pub(crate) fn set_can_user_effectively_resize(&self, value: bool) {
309        self.set_and_raise_cell(
310            Self::can_user_effectively_resize_property(),
311            &self.can_user_effectively_resize,
312            value,
313        );
314    }
315
316    fn is_cell_property(property: &'static FerroProperty) -> bool {
317        property == Self::cell_theme_property().as_property()
318            || property == Self::cell_template_property().as_property()
319            || property == Self::binding_property().as_property()
320            || property == Self::horizontal_content_alignment_property().as_property()
321            || property == Self::is_visible_property().as_property()
322    }
323
324    fn is_header_property(property: &'static FerroProperty) -> bool {
325        property == Self::header_theme_property().as_property()
326            || property == Self::header_template_property().as_property()
327            || property == Self::header_property().as_property()
328            || property == Self::horizontal_content_alignment_property().as_property()
329            || property == Self::is_visible_property().as_property()
330    }
331
332    pub(crate) fn update_can_user_effectively_resize(&self) {
333        let value = self
334            .can_user_resize()
335            .or_else(|| self.table_view().map(|table_view| table_view.can_user_resize_columns()))
336            .unwrap_or(true);
337        self.set_can_user_effectively_resize(value);
338    }
339
340    /// The text that describes the column in diagnostics.
341    pub(crate) fn debug_display(&self) -> String {
342        let mut builder = String::new();
343        self.build_debug_display(&mut builder, true);
344        builder
345    }
346
347    /// Appends the text that describes the column in diagnostics: the name
348    /// of its class, its name and, with the content, its header.
349    pub(crate) fn build_debug_display(&self, builder: &mut String, include_content: bool) {
350        build_base_debug_display(self, builder);
351
352        if include_content {
353            append_optional_boxed_value(builder, "Header", self.header().as_ref(), include_content);
354        }
355    }
356}
357
358impl IHeadered for TableViewColumn {
359    fn header(&self) -> Option<BoxedValue> {
360        TableViewColumn::header(self)
361    }
362
363    fn set_header(&self, value: Option<BoxedValue>) {
364        TableViewColumn::set_header(self, value)
365    }
366}