Skip to main content

ferroui_controls/presenters/
table_view_column_headers_presenter.rs

1use super::table_view_layout_helper::TableViewLayoutHelper;
2use crate::{ControlImpl, Panel, PanelImpl, TableView, TableViewColumnHeader};
3use ferroui_base::input::{InputElement, InputElementImpl};
4use ferroui_base::interactivity::InteractiveImpl;
5use ferroui_base::layout::{LayoutHelper, LayoutableImpl};
6use ferroui_base::logical_tree::LogicalTreeAttachmentEventArgs;
7use ferroui_base::reactive::IDisposable;
8use ferroui_base::{
9    ferro_class, ferro_class_info, ferro_impl_classes, instantiate, FerroObjectImpl, Ref, Size, StyledElementImpl,
10    StyledElementImplExt, VisualImpl, WeakRef,
11};
12use std::cell::RefCell;
13use std::rc::Rc;
14
15/// Displays the column headers for a [`TableView`]. Computes column widths
16/// (pixel and star) and exposes them via the actual width of the columns so
17/// that the presenters of the cells can align cells with the headers.
18#[repr(C)]
19pub struct TableViewColumnHeadersPresenter {
20    base: Panel,
21    /// The table view the presenter belongs to: an ancestor, held weakly.
22    table_view: RefCell<Option<WeakRef<TableView>>>,
23    /// The scrollable whose property changes the presenter listens to, and
24    /// the subscription.
25    scroll_subscription: RefCell<Option<(WeakRef<InputElement>, Rc<dyn IDisposable>)>>,
26}
27
28ferro_class!(TableViewColumnHeadersPresenter: Panel);
29ferro_class_info!(TableViewColumnHeadersPresenter { new: TableViewColumnHeadersPresenter::new });
30ferro_impl_classes!(
31    TableViewColumnHeadersPresenter: FerroObjectImpl,
32    VisualImpl,
33    InteractiveImpl,
34    InputElementImpl,
35    ControlImpl,
36    PanelImpl
37);
38
39impl StyledElementImpl for TableViewColumnHeadersPresenter {
40    fn on_attached_to_logical_tree(this: &Self, e: &LogicalTreeAttachmentEventArgs) {
41        Self::parent_on_attached_to_logical_tree(this, e);
42
43        let mut table_view = None;
44        let mut current = this.parent();
45        while let Some(ancestor) = current {
46            if let Some(found) = ancestor.clone().cast::<TableView>() {
47                table_view = Some(found);
48                break;
49            }
50            current = ancestor.parent();
51        }
52
53        this.set_table_view(table_view.as_ref());
54
55        this.rebuild_headers();
56
57        if let Some(table_view) = table_view {
58            table_view.set_headers_presenter(Some(&this.to_ref()));
59
60            if let Some(scroll) = table_view.scroll() {
61                let weak = this.to_ref().downgrade();
62                let subscription = scroll.property_changed(move |e| {
63                    if e.property().name() == "Offset" {
64                        if let Some(this) = weak.upgrade() {
65                            this.invalidate_arrange();
66                        }
67                    }
68                });
69                *this.scroll_subscription.borrow_mut() = Some((scroll.downgrade(), subscription));
70            }
71        }
72    }
73
74    fn on_detached_from_logical_tree(this: &Self, e: &LogicalTreeAttachmentEventArgs) {
75        if let Some(table_view) = this.table_view() {
76            table_view.set_headers_presenter(None);
77
78            // As in the reference, only the scrollable the table view has
79            // now is unsubscribed from.
80            if let Some(scroll) = table_view.scroll() {
81                let subscribed = this
82                    .scroll_subscription
83                    .borrow()
84                    .as_ref()
85                    .is_some_and(|(element, _)| element.upgrade().is_some_and(|element| element == scroll));
86                if subscribed {
87                    if let Some((_, subscription)) = this.scroll_subscription.take() {
88                        subscription.dispose();
89                    }
90                }
91            }
92
93            this.set_table_view(None);
94            this.rebuild_headers();
95        }
96
97        Self::parent_on_detached_from_logical_tree(this, e);
98    }
99}
100
101impl LayoutableImpl for TableViewColumnHeadersPresenter {
102    fn measure_override(this: &Self, available_size: Size) -> Size {
103        let Some(table_view) = this.table_view() else { return Size::default() };
104
105        let columns = table_view.columns();
106
107        if TableViewLayoutHelper::update_actual_widths(
108            &columns,
109            available_size.width,
110            this.use_layout_rounding(),
111            LayoutHelper::get_layout_scale(this),
112        ) {
113            table_view.invalidate_cells_measure();
114        }
115
116        TableViewLayoutHelper::measure_row(&columns, &this.children(), available_size)
117    }
118
119    fn arrange_override(this: &Self, final_size: Size) -> Size {
120        let Some(table_view) = this.table_view() else { return final_size };
121
122        let columns = table_view.columns();
123
124        if TableViewLayoutHelper::update_actual_widths(
125            &columns,
126            final_size.width,
127            this.use_layout_rounding(),
128            LayoutHelper::get_layout_scale(this),
129        ) {
130            table_view.invalidate_cells_measure();
131        }
132
133        let offset = table_view
134            .scroll()
135            .and_then(|scroll| scroll.as_scrollable().map(|scrollable| scrollable.offset().x))
136            .unwrap_or(0.0);
137        TableViewLayoutHelper::arrange_row(&columns, &this.children(), final_size, -offset)
138    }
139}
140
141impl TableViewColumnHeadersPresenter {
142    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
143    pub fn construct() -> Self {
144        Self { base: Panel::construct(), table_view: RefCell::new(None), scroll_subscription: RefCell::new(None) }
145    }
146
147    pub fn new() -> Ref<Self> {
148        instantiate(Self::construct())
149    }
150
151    pub(crate) fn table_view(&self) -> Option<Ref<TableView>> {
152        self.table_view.borrow().as_ref().and_then(WeakRef::upgrade)
153    }
154
155    pub(crate) fn set_table_view(&self, value: Option<&Ref<TableView>>) {
156        *self.table_view.borrow_mut() = value.map(Ref::downgrade);
157    }
158
159    pub(crate) fn rebuild_headers(&self) {
160        let children = self.children();
161        children.clear();
162
163        let Some(table_view) = self.table_view() else { return };
164
165        for column in table_view.columns().snapshot().iter() {
166            let header = TableViewColumnHeader::new();
167            header.set_column(Some(column.clone()));
168            children.add(header);
169        }
170
171        self.invalidate_measure();
172    }
173
174    pub(crate) fn refresh_header(&self, column_index: usize) {
175        let child = self.children().get(column_index);
176        match child.downcast_ref::<TableViewColumnHeader>() {
177            Some(header) => header.refresh(),
178            None => panic!(
179                "Unable to cast object of type '{}' to type '{}'.",
180                child.get_type().name(),
181                <TableViewColumnHeader as ferroui_base::StaticType>::TYPE.name()
182            ),
183        }
184    }
185}