egui_table_kit/layout/
columns.rs1use egui::Rangef;
4
5bitflags::bitflags! {
6 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
8 #[serde(transparent)]
9 pub struct ColumnFlags: u8 {
10 const RESIZABLE = 1 << 0;
12 const AUTO_SIZE_THIS_FRAME = 1 << 1;
14 const AUTO_FIT = 1 << 2;
16 const REMAINDER = 1 << 3;
18 }
19}
20
21#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize)]
22pub struct Column {
23 pub current: f32,
24 pub range: Rangef,
25 pub id: Option<egui::Id>,
26 pub flags: ColumnFlags,
27}
28
29impl Default for Column {
30 fn default() -> Self {
31 Self {
32 current: 100.0,
33 range: Rangef::new(4.0, f32::INFINITY),
34 id: None,
35 flags: ColumnFlags::RESIZABLE,
36 }
37 }
38}
39
40impl Column {
41 #[must_use]
43 #[inline]
44 pub fn new(current: f32) -> Self {
45 Self {
46 current,
47 ..Default::default()
48 }
49 }
50
51 #[must_use]
53 #[inline]
54 pub fn range(mut self, range: impl Into<Rangef>) -> Self {
55 self.range = range.into();
56 self
57 }
58
59 #[must_use]
61 #[inline]
62 pub const fn id(mut self, id: egui::Id) -> Self {
63 self.id = Some(id);
64 self
65 }
66
67 #[must_use]
69 #[inline]
70 pub fn resizable(mut self, resizable: bool) -> Self {
71 self.flags.set(ColumnFlags::RESIZABLE, resizable);
72 self
73 }
74
75 #[must_use]
77 #[inline]
78 pub fn auto_size_this_frame(mut self, auto_size_this_frame: bool) -> Self {
79 self.flags
80 .set(ColumnFlags::AUTO_SIZE_THIS_FRAME, auto_size_this_frame);
81 self
82 }
83
84 #[must_use]
86 #[inline]
87 pub fn auto_fit(mut self, auto_fit: bool) -> Self {
88 self.flags.set(ColumnFlags::AUTO_FIT, auto_fit);
89 self
90 }
91
92 #[must_use]
94 #[inline]
95 pub fn remainder(mut self, remainder: bool) -> Self {
96 self.flags.set(ColumnFlags::REMAINDER, remainder);
97 self
98 }
99
100 #[inline]
102 #[must_use]
103 pub const fn is_resizable(&self) -> bool {
104 self.flags.contains(ColumnFlags::RESIZABLE)
105 }
106
107 #[inline]
109 #[must_use]
110 pub const fn is_auto_size_this_frame(&self) -> bool {
111 self.flags.contains(ColumnFlags::AUTO_SIZE_THIS_FRAME)
112 }
113
114 #[inline]
116 #[must_use]
117 pub const fn is_auto_fit(&self) -> bool {
118 self.flags.contains(ColumnFlags::AUTO_FIT)
119 }
120
121 #[inline]
123 #[must_use]
124 pub const fn is_remainder(&self) -> bool {
125 self.flags.contains(ColumnFlags::REMAINDER)
126 }
127
128 #[must_use]
129 #[inline]
130 pub fn id_for(&self, col_idx: usize) -> egui::Id {
131 self.id.unwrap_or_else(|| egui::Id::new(col_idx))
132 }
133
134 pub fn auto_size(columns: &mut [Self], target_width: f32) {
136 if columns.is_empty() {
137 return;
138 }
139
140 let mut max_width = 0.0;
142 let mut current_width = 0.0;
143 for column in columns.iter_mut() {
144 column.current = column.range.clamp(column.current);
145 max_width += column.range.max;
146 current_width += column.current;
147 }
148
149 if current_width >= target_width {
152 return;
153 }
154
155 let sign = 1.0;
156
157 if max_width <= current_width {
158 return; }
160
161 let mut can_change: Vec<(f32, usize)> = columns
163 .iter()
164 .enumerate()
165 .filter_map(|(i, c)| {
166 if c.flags.contains(ColumnFlags::REMAINDER) && c.current < c.range.max {
167 let room = if c.range.max == f32::INFINITY {
168 10000.0 - c.current } else {
170 c.range.max - c.current
171 };
172 return Some((room, i));
173 }
174 None
175 })
176 .collect();
177
178 if can_change.is_empty() {
179 return; }
181
182 can_change.sort_by(|a, b| b.0.partial_cmp(&a.0).expect("NaN or Inf in column code"));
184 debug_assert!(
185 can_change[0].0 >= can_change.last().expect("Can't be empty").0,
186 "The sort is broken"
187 );
188
189 let mut remaining_abs = (target_width - current_width).abs();
190
191 while let Some((room_in_least, least_idx)) = can_change.pop() {
192 #[allow(clippy::cast_precision_loss)]
193 let evenly_distributed = remaining_abs / (can_change.len() as f32 + 1.0);
194
195 if evenly_distributed <= room_in_least {
196 columns[least_idx].current += sign * evenly_distributed;
197 for (_, i) in can_change {
198 columns[i].current += sign * evenly_distributed;
199 }
200 return;
201 }
202
203 columns[least_idx].current = columns[least_idx].range.max;
204 remaining_abs -= room_in_least;
205 }
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 fn col(c: i32, range: std::ops::RangeInclusive<i32>) -> Column {
214 #[allow(clippy::cast_precision_loss)]
215 Column::new(c as f32).range(Rangef::new(*range.start() as f32, *range.end() as f32))
216 }
217
218 fn widths(columns: &[Column]) -> Vec<f32> {
219 columns.iter().map(|c| c.current).collect()
220 }
221
222 #[test]
223 fn test_single_column() {
224 let mut columns = [col(0, 100..=200).remainder(true)];
225 Column::auto_size(&mut columns, 50.0);
226 assert_eq!(widths(&columns), [100.0]);
227 Column::auto_size(&mut columns, 132.0);
228 assert_eq!(widths(&columns), [132.0]);
229 Column::auto_size(&mut columns, 500.0);
230 assert_eq!(widths(&columns), [200.0]);
231 }
232
233 #[test]
234 fn test_many_columns() {
235 let mut columns = [
236 col(15, 10..=20).remainder(true),
237 col(25, 10..=100).remainder(true),
238 col(150, 100..=200).remainder(true),
239 ];
240
241 Column::auto_size(&mut columns, 190.0);
242 assert_eq!(
243 widths(&columns),
244 [15.0, 25.0, 150.0],
245 "They have no need to grow"
246 );
247
248 Column::auto_size(&mut columns, 207.0);
249 assert_eq!(
250 widths(&columns),
251 [20.0, 31.0, 156.0],
252 "They should grow equally"
253 );
254 }
255}