armas_basic/layout/
table.rs1use crate::Theme;
6use egui;
7
8const CELL_PADDING: f32 = 8.0; const HEADER_HEIGHT: f32 = 40.0; const CELL_SPACING: f32 = 0.0;
12
13fn get_theme(ui: &egui::Ui) -> Theme {
15 ui.ctx().data(|d| {
16 d.get_temp::<Theme>(egui::Id::new("armas_theme"))
17 .unwrap_or_else(Theme::dark)
18 })
19}
20
21fn draw_full_width_border(ui: &mut egui::Ui, _theme: &Theme, num_columns: usize) {
23 for _ in 0..num_columns {
24 ui.add(egui::Separator::default().horizontal().spacing(0.0));
25 }
26 ui.end_row();
27}
28
29pub fn table<R>(ui: &mut egui::Ui, content: impl FnOnce(&mut TableRows) -> R) -> R {
46 let theme = get_theme(ui);
47
48 egui::Grid::new(ui.id().with("table"))
49 .spacing([CELL_SPACING, CELL_SPACING])
50 .min_col_width(0.0)
51 .show(ui, |ui| {
52 let mut table_state = TableState {
53 theme,
54 num_columns: 0,
55 };
56
57 let mut rows = TableRows {
58 ui,
59 state: &mut table_state,
60 };
61
62 content(&mut rows)
63 })
64 .inner
65}
66
67struct TableState {
69 theme: Theme,
70 num_columns: usize,
71}
72
73pub struct TableRows<'a> {
75 ui: &'a mut egui::Ui,
76 state: &'a mut TableState,
77}
78
79pub struct TableCells<'a> {
81 ui: &'a mut egui::Ui,
82 theme: &'a Theme,
83 is_header: bool,
84 cell_index: usize,
85}
86
87pub fn header_row<R>(rows: &mut TableRows, content: impl FnOnce(&mut TableCells) -> R) -> R {
89 let result = render_row(rows, true, content);
90
91 rows.ui.end_row();
92
93 draw_full_width_border(rows.ui, &rows.state.theme, rows.state.num_columns);
95
96 result
97}
98
99pub fn row<R>(rows: &mut TableRows, content: impl FnOnce(&mut TableCells) -> R) -> R {
101 let result = render_row(rows, false, content);
102
103 rows.ui.end_row();
104
105 draw_full_width_border(rows.ui, &rows.state.theme, rows.state.num_columns);
107
108 result
109}
110
111fn render_row<R>(
113 rows: &mut TableRows,
114 is_header: bool,
115 content: impl FnOnce(&mut TableCells) -> R,
116) -> R {
117 let mut cells = TableCells {
118 ui: rows.ui,
119 theme: &rows.state.theme,
120 is_header,
121 cell_index: 0,
122 };
123
124 let result = content(&mut cells);
125
126 if rows.state.num_columns == 0 {
128 rows.state.num_columns = cells.cell_index;
129 }
130
131 result
132}
133
134pub fn cell(cells: &mut TableCells, text: impl Into<String>) {
136 render_cell(cells, |ui, theme, is_header| {
137 let text = text.into();
138 let label = create_label(&text, theme, is_header);
139 ui.add(label);
140 });
141}
142
143pub fn cell_ui<R>(cells: &mut TableCells, content: impl FnOnce(&mut egui::Ui) -> R) -> R {
145 render_cell(cells, |ui, _theme, _is_header| content(ui))
146}
147
148fn render_cell<R>(
150 cells: &mut TableCells,
151 content: impl FnOnce(&mut egui::Ui, &Theme, bool) -> R,
152) -> R {
153 let frame = egui::Frame::new().inner_margin(egui::Margin::same(CELL_PADDING as i8));
154
155 let result = frame
156 .show(cells.ui, |ui| {
157 let min_height = if cells.is_header {
159 HEADER_HEIGHT - CELL_PADDING * 2.0
160 } else {
161 0.0
162 };
163
164 if min_height > 0.0 {
165 ui.set_min_height(min_height);
166 }
167
168 ui.horizontal_centered(|ui| content(ui, cells.theme, cells.is_header))
170 .inner
171 })
172 .inner;
173
174 cells.cell_index += 1;
175 result
176}
177
178fn create_label(text: &str, theme: &Theme, is_header: bool) -> egui::Label {
180 if is_header {
181 egui::Label::new(egui::RichText::new(text).strong().color(theme.foreground()))
182 } else {
183 egui::Label::new(egui::RichText::new(text).color(theme.muted_foreground()))
184 }
185}