use crate::Theme;
use egui::Ui;
#[cfg(feature = "extras")]
use egui_extras::{Column, TableBuilder};
pub struct Table<'a> {
headers: &'a [&'a str],
rows: Vec<Vec<String>>,
striped: bool,
}
impl<'a> Table<'a> {
pub fn new(headers: &'a [&'a str]) -> Self {
Self {
headers,
rows: Vec::new(),
striped: true,
}
}
pub fn row(mut self, cells: Vec<String>) -> Self {
self.rows.push(cells);
self
}
pub fn rows(mut self, rows: Vec<Vec<String>>) -> Self {
self.rows = rows;
self
}
pub fn striped(mut self, striped: bool) -> Self {
self.striped = striped;
self
}
pub fn show(self, ui: &mut Ui) {
let theme = Theme::current(ui.ctx());
egui::Frame::new()
.stroke(egui::Stroke::new(theme.border_width, theme.border))
.corner_radius(theme.radius_md)
.show(ui, |ui| {
egui::Grid::new(ui.next_auto_id())
.num_columns(self.headers.len())
.striped(false)
.show(ui, |ui| {
for header in self.headers {
egui::Frame::new()
.fill(theme.bg_secondary)
.inner_margin(egui::Margin::symmetric(12, 8))
.show(ui, |ui| {
ui.label(egui::RichText::new(*header).strong());
});
}
ui.end_row();
for (i, row) in self.rows.iter().enumerate() {
let bg = if self.striped && i % 2 == 1 {
Some(theme.bg_tertiary)
} else {
None
};
for cell in row {
let mut frame =
egui::Frame::new().inner_margin(egui::Margin::symmetric(12, 8));
if let Some(bg_color) = bg {
frame = frame.fill(bg_color);
}
frame.show(ui, |ui| {
ui.label(cell);
});
}
ui.end_row();
}
});
});
}
}
#[cfg(feature = "extras")]
pub struct DataTable<'a, T> {
data: &'a [T],
columns: Vec<DataColumn<'a, T>>,
striped: bool,
resizable: bool,
row_height: Option<f32>,
selected: Option<usize>,
}
#[cfg(feature = "extras")]
struct DataColumn<'a, T> {
header: &'a str,
width: DataColumnWidth,
accessor: Box<dyn Fn(&T) -> String + 'a>,
}
#[cfg(feature = "extras")]
#[derive(Clone, Copy, Default)]
pub enum DataColumnWidth {
#[default]
Auto,
Fixed(f32),
Initial(f32),
Remainder,
}
#[cfg(feature = "extras")]
impl<'a, T> DataTable<'a, T> {
pub fn new(data: &'a [T]) -> Self {
Self {
data,
columns: Vec::new(),
striped: true,
resizable: true,
row_height: None,
selected: None,
}
}
pub fn column(mut self, header: &'a str, accessor: impl Fn(&T) -> String + 'a) -> Self {
self.columns.push(DataColumn {
header,
width: DataColumnWidth::Auto,
accessor: Box::new(accessor),
});
self
}
pub fn column_with_width(
mut self,
header: &'a str,
width: DataColumnWidth,
accessor: impl Fn(&T) -> String + 'a,
) -> Self {
self.columns.push(DataColumn {
header,
width,
accessor: Box::new(accessor),
});
self
}
pub fn striped(mut self, striped: bool) -> Self {
self.striped = striped;
self
}
pub fn resizable(mut self, resizable: bool) -> Self {
self.resizable = resizable;
self
}
pub fn row_height(mut self, height: f32) -> Self {
self.row_height = Some(height);
self
}
pub fn selected(mut self, index: Option<usize>) -> Self {
self.selected = index;
self
}
pub fn show(self, ui: &mut Ui) -> Option<usize> {
let theme = Theme::current(ui.ctx());
let row_height = self
.row_height
.unwrap_or(theme.spacing_lg + theme.spacing_sm);
let mut clicked_row: Option<usize> = None;
let mut builder = TableBuilder::new(ui)
.striped(self.striped)
.resizable(self.resizable)
.cell_layout(egui::Layout::left_to_right(egui::Align::Center));
for col in &self.columns {
let column = match col.width {
DataColumnWidth::Auto => Column::auto(),
DataColumnWidth::Fixed(w) => Column::exact(w),
DataColumnWidth::Initial(w) => Column::initial(w).resizable(self.resizable),
DataColumnWidth::Remainder => Column::remainder(),
};
builder = builder.column(column);
}
builder
.header(row_height, |mut header| {
for col in &self.columns {
header.col(|ui| {
ui.strong(col.header);
});
}
})
.body(|mut body| {
for (idx, item) in self.data.iter().enumerate() {
let is_selected = self.selected == Some(idx);
body.row(row_height, |mut row| {
for col in &self.columns {
row.col(|ui| {
if is_selected {
ui.painter().rect_filled(
ui.available_rect_before_wrap(),
0.0,
theme.primary.gamma_multiply(0.3),
);
}
let text = (col.accessor)(item);
let response = ui.label(&text);
if response.clicked() {
clicked_row = Some(idx);
}
});
}
});
}
});
clicked_row
}
}
#[cfg(not(feature = "extras"))]
pub struct DataTable<'a, T> {
data: &'a [T],
columns: Vec<DataColumnSimple<'a, T>>,
striped: bool,
}
#[cfg(not(feature = "extras"))]
struct DataColumnSimple<'a, T> {
header: &'a str,
accessor: Box<dyn Fn(&T) -> String + 'a>,
}
#[cfg(not(feature = "extras"))]
impl<'a, T> DataTable<'a, T> {
pub fn new(data: &'a [T]) -> Self {
Self {
data,
columns: Vec::new(),
striped: true,
}
}
pub fn column(mut self, header: &'a str, accessor: impl Fn(&T) -> String + 'a) -> Self {
self.columns.push(DataColumnSimple {
header,
accessor: Box::new(accessor),
});
self
}
pub fn striped(mut self, striped: bool) -> Self {
self.striped = striped;
self
}
pub fn show(self, ui: &mut Ui) {
let headers: Vec<&str> = self.columns.iter().map(|c| c.header).collect();
let rows: Vec<Vec<String>> = self
.data
.iter()
.map(|item| {
self.columns
.iter()
.map(|col| (col.accessor)(item))
.collect()
})
.collect();
Table::new(&headers)
.rows(rows)
.striped(self.striped)
.show(ui);
}
}