Skip to main content

ferroui_controls/
table_view_column_header.rs

1use crate::primitives::{TemplateAppliedEventArgs, TemplatedControlImpl, TemplatedControlImplExt, Thumb};
2use crate::utils::debug_display::{append_optional_boxed_value, build_base_debug_display};
3use crate::{ContentControl, ContentControlImpl, ControlImpl, GridLength, GridUnitType, TableViewColumn};
4use ferroui_base::input::{InputElementImpl, VectorEventArgs};
5use ferroui_base::interactivity::{InteractiveImpl, RoutedEventHandlerToken};
6use ferroui_base::layout::LayoutableImpl;
7use ferroui_base::utilities::math_utilities::max;
8use ferroui_base::{
9    ferro_class, ferro_class_info, ferro_impl_classes, ferro_properties, instantiate, DirectProperty, FerroObjectImpl,
10    FerroProperty, Ref, StyledElement, StyledElementImpl, Visual, VisualImpl,
11};
12use std::cell::RefCell;
13
14const PART_RESIZER: &str = "PART_Resizer";
15
16/// Represents the header of a [`TableViewColumn`].
17#[repr(C)]
18pub struct TableViewColumnHeader {
19    base: ContentControl,
20    column: RefCell<Option<Ref<TableViewColumn>>>,
21    /// The resizer of the template and the handler of its drags.
22    resizer: RefCell<Option<(Ref<Thumb>, RoutedEventHandlerToken)>>,
23}
24
25ferro_class!(TableViewColumnHeader: ContentControl);
26ferro_class_info!(TableViewColumnHeader {
27    new: TableViewColumnHeader::new,
28    markup: {
29        attributes: [TemplatePart("PART_Resizer", type(Ref<Thumb>))],
30    },
31});
32ferro_impl_classes!(
33    TableViewColumnHeader: FerroObjectImpl,
34    StyledElementImpl,
35    VisualImpl,
36    LayoutableImpl,
37    InteractiveImpl,
38    InputElementImpl,
39    ControlImpl,
40    ContentControlImpl
41);
42
43impl TemplatedControlImpl for TableViewColumnHeader {
44    fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
45        Self::parent_on_apply_template(this, e);
46
47        if let Some((resizer, token)) = this.resizer.take() {
48            resizer.remove_handler(Thumb::drag_delta_event(), token);
49        }
50
51        if let Some(resizer) = e.name_scope().find_as::<Thumb>(PART_RESIZER) {
52            let weak = this.to_ref().downgrade();
53            let token = resizer.drag_delta(move |_, e| {
54                if let Some(this) = weak.upgrade() {
55                    this.on_resizer_drag_delta(e);
56                }
57            });
58            *this.resizer.borrow_mut() = Some((resizer, token));
59        }
60    }
61}
62
63ferro_properties! {
64    impl TableViewColumnHeader {
65        /// Identifies the `Column` property.
66        pub fn column_property() -> DirectProperty<TableViewColumnHeader, Option<Ref<TableViewColumn>>> {
67            FerroProperty::register_direct::<TableViewColumnHeader, _>("Column", |o| o.column(), None, None)
68        }
69    }
70}
71
72impl TableViewColumnHeader {
73    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
74    pub fn construct() -> Self {
75        Self { base: ContentControl::construct(), column: RefCell::new(None), resizer: RefCell::new(None) }
76    }
77
78    pub fn new() -> Ref<Self> {
79        instantiate(Self::construct())
80    }
81
82    /// Gets the column associated with this header.
83    pub fn column(&self) -> Option<Ref<TableViewColumn>> {
84        self.column.borrow().clone()
85    }
86
87    pub(crate) fn set_column(&self, value: Option<Ref<TableViewColumn>>) {
88        let old_value = self.column();
89        if !self.set_and_raise(Self::column_property(), &self.column, value.clone()) {
90            return;
91        }
92
93        if old_value.is_some() {
94            self.clear_properties();
95        }
96
97        if let Some(value) = value {
98            self.set_properties(&value);
99        }
100    }
101
102    fn on_resizer_drag_delta(&self, e: &VectorEventArgs) {
103        let Some(column) = self.column().filter(|column| column.can_user_effectively_resize()) else { return };
104
105        let actual_width = column.actual_width();
106        if actual_width.is_nan() {
107            return;
108        }
109
110        let resizer = self.resizer.borrow().as_ref().map(|(resizer, _)| resizer.clone());
111        let min_width = resizer.map_or(0.0, |resizer| resizer.bounds().width);
112        let new_width = max(min_width, actual_width + e.vector.x);
113        column.set_width(GridLength::new(new_width, GridUnitType::Pixel));
114    }
115
116    /// Appends the text that describes the header in diagnostics: the name
117    /// of its class, its name and, with the content, its content and the
118    /// header of its column.
119    pub(crate) fn build_debug_display(&self, builder: &mut String, include_content: bool) {
120        build_base_debug_display(self, builder);
121
122        if include_content {
123            // DEBUG-DISPLAY-SEAM: the content is the part of the description
124            // the content control adds in the reference; it has no debug
125            // display of its own here.
126            append_optional_boxed_value(builder, "Content", self.content().as_ref(), include_content);
127
128            let header = self.column().and_then(|column| column.header());
129            append_optional_boxed_value(builder, "Column", header.as_ref(), include_content);
130        }
131    }
132
133    fn clear_properties(&self) {
134        self.clear_value(Visual::is_visible_property());
135        self.clear_value(StyledElement::theme_property());
136        self.clear_value(ContentControl::horizontal_content_alignment_property());
137        self.clear_value(ContentControl::content_template_property());
138        self.clear_value(ContentControl::content_property());
139    }
140
141    fn set_properties(&self, column: &TableViewColumn) {
142        self.set_value(Visual::is_visible_property(), column.is_visible());
143
144        match column.header_theme() {
145            Some(header_theme) => self.set_value(StyledElement::theme_property(), Some(header_theme)),
146            None => self.clear_value(StyledElement::theme_property()),
147        }
148
149        self.set_value(ContentControl::horizontal_content_alignment_property(), column.horizontal_content_alignment());
150
151        match column.header_template() {
152            Some(header_template) => self.set_value(ContentControl::content_template_property(), Some(header_template)),
153            None => self.clear_value(ContentControl::content_template_property()),
154        }
155
156        match column.header() {
157            Some(header) => self.set_value(ContentControl::content_property(), Some(header)),
158            None => self.clear_value(ContentControl::content_property()),
159        }
160    }
161
162    pub(crate) fn refresh(&self) {
163        if let Some(column) = self.column() {
164            self.set_properties(&column);
165        }
166    }
167}