Skip to main content

ferroui_controls/presenters/
table_view_cells_presenter.rs

1use super::table_view_layout_helper::TableViewLayoutHelper;
2use crate::{ControlImpl, Panel, PanelImpl, TableViewCell, TableViewRow};
3use ferroui_base::input::InputElementImpl;
4use ferroui_base::interactivity::InteractiveImpl;
5use ferroui_base::layout::{LayoutHelper, LayoutableImpl};
6use ferroui_base::{
7    ferro_class, ferro_class_info, ferro_impl_classes, instantiate, FerroObjectImpl, Ref, Size, StyledElement,
8    StyledElementImpl, VisualImpl, WeakRef,
9};
10use std::cell::RefCell;
11
12/// Lays out the cells of a [`TableViewRow`] according to the column
13/// definitions of the parent table view.
14///
15/// The cells are recycled alongside their owning row in a virtualizing stack
16/// panel, but are not virtualized in a single row (i.e. there is no column
17/// virtualization).
18#[repr(C)]
19pub struct TableViewCellsPresenter {
20    base: Panel,
21    /// The row the presenter belongs to: an ancestor, held weakly.
22    row: RefCell<Option<WeakRef<TableViewRow>>>,
23}
24
25ferro_class!(TableViewCellsPresenter: Panel);
26ferro_class_info!(TableViewCellsPresenter { new: TableViewCellsPresenter::new });
27ferro_impl_classes!(
28    TableViewCellsPresenter: FerroObjectImpl,
29    StyledElementImpl,
30    VisualImpl,
31    InteractiveImpl,
32    InputElementImpl,
33    ControlImpl,
34    PanelImpl
35);
36
37impl LayoutableImpl for TableViewCellsPresenter {
38    fn measure_override(this: &Self, available_size: Size) -> Size {
39        let Some(columns) = this.row().and_then(|row| row.columns()) else { return Size::default() };
40
41        // In a standard template, the column widths should have been
42        // computed by the headers' presenter. If for some reason they
43        // weren't, do it now.
44        if TableViewLayoutHelper::needs_actual_widths(&columns) {
45            TableViewLayoutHelper::update_actual_widths(
46                &columns,
47                available_size.width,
48                this.use_layout_rounding(),
49                LayoutHelper::get_layout_scale(this),
50            );
51        }
52
53        TableViewLayoutHelper::measure_row(&columns, &this.children(), available_size)
54    }
55
56    fn arrange_override(this: &Self, final_size: Size) -> Size {
57        let Some(columns) = this.row().and_then(|row| row.columns()) else { return final_size };
58
59        if TableViewLayoutHelper::needs_actual_widths(&columns) {
60            TableViewLayoutHelper::update_actual_widths(
61                &columns,
62                final_size.width,
63                this.use_layout_rounding(),
64                LayoutHelper::get_layout_scale(this),
65            );
66        }
67
68        TableViewLayoutHelper::arrange_row(&columns, &this.children(), final_size, 0.0)
69    }
70}
71
72impl TableViewCellsPresenter {
73    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
74    pub fn construct() -> Self {
75        Self { base: Panel::construct(), row: RefCell::new(None) }
76    }
77
78    pub fn new() -> Ref<Self> {
79        instantiate(Self::construct())
80    }
81
82    pub(crate) fn row(&self) -> Option<Ref<TableViewRow>> {
83        self.row.borrow().as_ref().and_then(WeakRef::upgrade)
84    }
85
86    pub(crate) fn set_row(&self, value: Option<&Ref<TableViewRow>>) {
87        *self.row.borrow_mut() = value.map(Ref::downgrade);
88    }
89
90    pub(crate) fn clear_cells(&self) {
91        for child in self.children().snapshot().iter() {
92            if let Some(cell) = child.downcast_ref::<TableViewCell>() {
93                cell.set_column(None);
94            }
95        }
96    }
97
98    pub(crate) fn remove_cells(&self) {
99        self.clear_cells();
100        self.children().clear();
101        if let Some(row) = self.row() {
102            StyledElement::logical_children(&row).clear();
103        }
104    }
105
106    pub(crate) fn rebuild_cells(&self) {
107        let row = self.row();
108        let Some((row, columns)) = row.and_then(|row| row.columns().map(|columns| (row, columns))) else {
109            self.remove_cells();
110            return;
111        };
112
113        let children = self.children();
114        let columns = columns.snapshot();
115
116        if columns.len() != children.count() {
117            self.remove_cells();
118
119            for column in columns.iter() {
120                let cell = TableViewCell::new();
121                cell.set_column(Some(column.clone()));
122                children.add(cell);
123            }
124
125            StyledElement::logical_children(&row)
126                .add_range(children.snapshot().iter().map(|child| child.clone().upcast::<StyledElement>()));
127        } else {
128            for (child, column) in children.snapshot().iter().zip(columns.iter()) {
129                Self::cell(child).set_column(Some(column.clone()));
130            }
131        }
132
133        self.invalidate_measure();
134    }
135
136    pub(crate) fn refresh_cell(&self, column_index: usize) {
137        Self::cell(&self.children().get(column_index)).refresh();
138    }
139
140    /// A child of the presenter as the cell it has to be.
141    fn cell(child: &Ref<crate::Control>) -> &TableViewCell {
142        child.downcast_ref::<TableViewCell>().unwrap_or_else(|| {
143            panic!(
144                "Unable to cast object of type '{}' to type '{}'.",
145                child.get_type().name(),
146                <TableViewCell as ferroui_base::StaticType>::TYPE.name()
147            )
148        })
149    }
150}