ferroui_controls/
table_view_column_header.rs1use 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#[repr(C)]
18pub struct TableViewColumnHeader {
19 base: ContentControl,
20 column: RefCell<Option<Ref<TableViewColumn>>>,
21 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 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 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 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 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 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}