use iced::widget::{button, column, container, row, scrollable, text, text_input};
use iced::{Color, Element};
use crate::{header::HeaderSortState, selection::SelectionModel, RowSource};
pub fn render_iced<'a, Msg>(
source: &'a dyn RowSource,
viewport_rows: usize,
scroll_offset: usize,
sort_state: &HeaderSortState,
) -> Element<'a, Msg>
where
Msg: Clone + 'a,
{
const OVERSCAN: usize = 3;
let col_defs = source.column_defs();
let total = source.row_count();
let start = scroll_offset.saturating_sub(OVERSCAN).min(total);
let end = (scroll_offset + viewport_rows + OVERSCAN * 2).min(total);
let header_cells: Vec<Element<'a, Msg>> = col_defs
.iter()
.enumerate()
.map(|(col_idx, c)| {
let indicator = sort_state.indicator(col_idx);
let label = if indicator.is_empty() {
c.name.clone()
} else {
format!("{} {indicator}", c.name)
};
text(label).size(14).into()
})
.collect();
let header = row(header_cells).spacing(8);
let data_rows: Vec<Element<'a, Msg>> = (start..end)
.map(|i| {
let cells = source.row(i);
let cell_els: Vec<Element<'a, Msg>> = cells
.iter()
.map(|cell| text(cell.to_string()).size(13).into())
.collect();
row(cell_els).spacing(8).into()
})
.collect();
let body = scrollable(column(data_rows).spacing(2).padding(4));
column(vec![header.into(), body.into()])
.spacing(2)
.padding(4)
.into()
}
pub fn render_iced_with_filters<'a, Msg, F>(
source: &'a dyn RowSource,
viewport_rows: usize,
scroll_offset: usize,
sort_state: &HeaderSortState,
filter_values: &'a [String],
on_filter_change: F,
) -> Element<'a, Msg>
where
Msg: Clone + 'a,
F: Fn(usize, String) -> Msg + Clone + 'a,
{
const OVERSCAN: usize = 3;
let col_defs = source.column_defs();
let total = source.row_count();
let start = scroll_offset.saturating_sub(OVERSCAN).min(total);
let end = (scroll_offset + viewport_rows + OVERSCAN * 2).min(total);
let header_cells: Vec<Element<'a, Msg>> = col_defs
.iter()
.enumerate()
.map(|(col_idx, c)| {
let indicator = sort_state.indicator(col_idx);
let label = if indicator.is_empty() {
c.name.clone()
} else {
format!("{} {indicator}", c.name)
};
text(label).size(14).into()
})
.collect();
let header = row(header_cells).spacing(8);
let filter_row_cells: Vec<Element<'a, Msg>> = col_defs
.iter()
.enumerate()
.map(|(col_idx, _c)| {
let current = filter_values.get(col_idx).map(|s| s.as_str()).unwrap_or("");
let cb = on_filter_change.clone();
text_input("Filter…", current)
.on_input(move |new_text| cb(col_idx, new_text))
.size(12)
.into()
})
.collect();
let filter_row = row(filter_row_cells).spacing(8);
let data_rows: Vec<Element<'a, Msg>> = (start..end)
.map(|i| {
let cells = source.row(i);
let cell_els: Vec<Element<'a, Msg>> = cells
.iter()
.map(|cell| text(cell.to_string()).size(13).into())
.collect();
row(cell_els).spacing(8).into()
})
.collect();
let body = scrollable(column(data_rows).spacing(2).padding(4));
column(vec![header.into(), filter_row.into(), body.into()])
.spacing(2)
.padding(4)
.into()
}
pub fn render_iced_sortable<'a, Msg, F>(
source: &'a dyn RowSource,
viewport_rows: usize,
scroll_offset: usize,
sort_state: &HeaderSortState,
on_sort_toggle: F,
scrollable_id: Option<iced::widget::Id>,
) -> Element<'a, Msg>
where
Msg: Clone + 'a,
F: Fn(usize) -> Msg + Clone + 'a,
{
const OVERSCAN: usize = 3;
let col_defs = source.column_defs();
let total = source.row_count();
let start = scroll_offset.saturating_sub(OVERSCAN).min(total);
let end = (scroll_offset + viewport_rows + OVERSCAN * 2).min(total);
let header_cells: Vec<Element<'a, Msg>> = col_defs
.iter()
.enumerate()
.map(|(col_idx, c)| {
let indicator = sort_state.indicator(col_idx);
let label = if indicator.is_empty() {
c.name.clone()
} else {
format!("{} {indicator}", c.name)
};
let cb = on_sort_toggle.clone();
button(text(label).size(14)).on_press(cb(col_idx)).into()
})
.collect();
let header = row(header_cells).spacing(8);
let data_rows: Vec<Element<'a, Msg>> = (start..end)
.map(|i| {
let cells = source.row(i);
let cell_els: Vec<Element<'a, Msg>> = cells
.iter()
.map(|cell| text(cell.to_string()).size(13).into())
.collect();
row(cell_els).spacing(8).into()
})
.collect();
let body_col = column(data_rows).spacing(2).padding(4);
let body = if let Some(id) = scrollable_id {
scrollable(body_col).id(id)
} else {
scrollable(body_col)
};
column(vec![header.into(), body.into()])
.spacing(2)
.padding(4)
.into()
}
const SELECTION_BG: Color = Color {
r: 0.22,
g: 0.56,
b: 0.92,
a: 0.28,
};
#[allow(clippy::too_many_arguments)]
pub fn render_iced_with_selection<'a, Msg, F>(
source: &'a dyn RowSource,
viewport_rows: usize,
scroll_offset: usize,
sort_state: &HeaderSortState,
selection: &SelectionModel,
on_row_click: F,
scrollable_id: Option<iced::widget::Id>,
) -> Element<'a, Msg>
where
Msg: Clone + 'a,
F: Fn(usize) -> Msg + Clone + 'a,
{
const OVERSCAN: usize = 3;
let col_defs = source.column_defs();
let total = source.row_count();
let start = scroll_offset.saturating_sub(OVERSCAN).min(total);
let end = (scroll_offset + viewport_rows + OVERSCAN * 2).min(total);
let header_cells: Vec<Element<'a, Msg>> = col_defs
.iter()
.enumerate()
.map(|(col_idx, c)| {
let indicator = sort_state.indicator(col_idx);
let label = if indicator.is_empty() {
c.name.clone()
} else {
format!("{} {indicator}", c.name)
};
text(label).size(14).into()
})
.collect();
let header = row(header_cells).spacing(8);
let data_rows: Vec<Element<'a, Msg>> =
(start..end)
.map(|row_idx| {
let cells = source.row(row_idx);
let cell_els: Vec<Element<'a, Msg>> = cells
.iter()
.map(|cell| text(cell.to_string()).size(13).into())
.collect();
let row_widget = row(cell_els).spacing(8);
let cb = on_row_click.clone();
if selection.is_selected(row_idx) {
container(button(row_widget).on_press(cb(row_idx)).style(
move |_theme, _status| iced::widget::button::Style {
background: Some(iced::Background::Color(SELECTION_BG)),
..iced::widget::button::Style::default()
},
))
.into()
} else {
button(row_widget).on_press(cb(row_idx)).into()
}
})
.collect();
let body_col = column(data_rows).spacing(2).padding(4);
let body = if let Some(id) = scrollable_id {
scrollable(body_col).id(id)
} else {
scrollable(body_col)
};
column(vec![header.into(), body.into()])
.spacing(2)
.padding(4)
.into()
}