1use std::collections::HashMap;
2
3use super::column_sizing_info::ColumnSizingInfoState;
4use super::{ColumnDef, ColumnId};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum ColumnSizingRegion {
8 All,
9 Left,
10 Center,
11 Right,
12}
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum ColumnResizeMode {
16 OnChange,
17 OnEnd,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum ColumnResizeDirection {
22 Ltr,
23 Rtl,
24}
25
26pub type ColumnSizingState = HashMap<ColumnId, f32>;
28
29pub fn column_size(state: &ColumnSizingState, column: &ColumnId) -> Option<f32> {
30 state.get(column).copied()
31}
32
33pub fn resolved_column_size<TData>(state: &ColumnSizingState, column: &ColumnDef<TData>) -> f32 {
34 let raw = state.get(&column.id).copied().unwrap_or(column.size);
35 raw.clamp(column.min_size, column.max_size)
36}
37
38pub fn column_can_resize<TData>(options: super::TableOptions, column: &ColumnDef<TData>) -> bool {
39 options.enable_column_resizing && column.enable_resizing
40}
41
42fn round_column_size(size: f32) -> f32 {
43 (size * 100.0).round() / 100.0
44}
45
46pub fn column_resize_preview_size(info: &ColumnSizingInfoState, column: &ColumnId) -> Option<f32> {
47 let delta_percentage = info.delta_percentage?;
48 let (_, start) = info
49 .column_sizing_start
50 .iter()
51 .find(|(id, _)| id.as_ref() == column.as_ref())?;
52 Some(round_column_size(
53 (*start + *start * delta_percentage).max(0.0),
54 ))
55}
56
57pub fn begin_column_resize(
58 info: &mut ColumnSizingInfoState,
59 resizing_column: ColumnId,
60 start_offset: f32,
61 start_size: f32,
62 column_sizing_start: Vec<(ColumnId, f32)>,
63) {
64 *info = ColumnSizingInfoState {
65 start_offset: Some(start_offset),
66 start_size: Some(start_size),
67 delta_offset: Some(0.0),
68 delta_percentage: Some(0.0),
69 is_resizing_column: Some(resizing_column),
70 column_sizing_start,
71 };
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75enum ResizeEvent {
76 Move,
77 End,
78}
79
80fn update_column_resize(
81 mode: ColumnResizeMode,
82 direction: ColumnResizeDirection,
83 sizing: &mut ColumnSizingState,
84 info: &mut ColumnSizingInfoState,
85 event: ResizeEvent,
86 client_x: Option<f32>,
87) {
88 let Some(client_x) = client_x else { return };
89
90 let Some(start_offset) = info.start_offset else {
91 return;
92 };
93 let Some(start_size) = info.start_size else {
94 return;
95 };
96 if start_size <= 0.0 {
97 return;
98 }
99
100 let direction_mul = match direction {
101 ColumnResizeDirection::Ltr => 1.0,
102 ColumnResizeDirection::Rtl => -1.0,
103 };
104
105 let delta_offset = (client_x - start_offset) * direction_mul;
106 let delta_percentage = (delta_offset / start_size).max(-0.999_999);
107
108 info.delta_offset = Some(delta_offset);
109 info.delta_percentage = Some(delta_percentage);
110
111 if !(mode == ColumnResizeMode::OnChange || event == ResizeEvent::End) {
112 return;
113 }
114
115 for (column_id, header_size) in &info.column_sizing_start {
116 let next = (header_size + header_size * delta_percentage).max(0.0);
117 sizing.insert(column_id.clone(), round_column_size(next));
118 }
119}
120
121pub fn drag_column_resize(
122 mode: ColumnResizeMode,
123 direction: ColumnResizeDirection,
124 sizing: &mut ColumnSizingState,
125 info: &mut ColumnSizingInfoState,
126 client_x: f32,
127) {
128 update_column_resize(
129 mode,
130 direction,
131 sizing,
132 info,
133 ResizeEvent::Move,
134 Some(client_x),
135 );
136}
137
138pub fn end_column_resize(
139 mode: ColumnResizeMode,
140 direction: ColumnResizeDirection,
141 sizing: &mut ColumnSizingState,
142 info: &mut ColumnSizingInfoState,
143 client_x: Option<f32>,
144) {
145 update_column_resize(mode, direction, sizing, info, ResizeEvent::End, client_x);
146 *info = ColumnSizingInfoState::default();
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152 use crate::table::TableOptions;
153
154 #[test]
155 fn column_size_reads_from_map() {
156 let mut state = ColumnSizingState::default();
157 state.insert(ColumnId::from("a"), 123.0);
158
159 assert_eq!(column_size(&state, &ColumnId::from("a")), Some(123.0));
160 assert_eq!(column_size(&state, &ColumnId::from("b")), None);
161 }
162
163 #[test]
164 fn resolved_column_size_falls_back_to_column_default_and_clamps() {
165 #[derive(Debug)]
166 struct Item;
167
168 let col = ColumnDef::<Item>::new("a")
169 .size(100.0)
170 .min_size(60.0)
171 .max_size(80.0);
172
173 let state = ColumnSizingState::default();
174 assert_eq!(resolved_column_size(&state, &col), 80.0);
175
176 let mut state = ColumnSizingState::default();
177 state.insert(ColumnId::from("a"), 10.0);
178 assert_eq!(resolved_column_size(&state, &col), 60.0);
179 }
180
181 #[test]
182 fn column_can_resize_respects_table_and_column_flags() {
183 #[derive(Debug)]
184 struct Item;
185
186 let col = ColumnDef::<Item>::new("a").enable_resizing(false);
187 assert!(!column_can_resize(TableOptions::default(), &col));
188
189 let mut options = TableOptions::default();
190 options.enable_column_resizing = false;
191 let col = ColumnDef::<Item>::new("a").enable_resizing(true);
192 assert!(!column_can_resize(options, &col));
193 }
194
195 #[test]
196 fn column_resize_on_change_updates_sizing_on_move_and_resets_on_end() {
197 let mut sizing = ColumnSizingState::default();
198 let mut info = ColumnSizingInfoState::default();
199
200 begin_column_resize(
201 &mut info,
202 ColumnId::from("a"),
203 10.0,
204 100.0,
205 vec![(ColumnId::from("a"), 100.0)],
206 );
207
208 drag_column_resize(
209 ColumnResizeMode::OnChange,
210 ColumnResizeDirection::Ltr,
211 &mut sizing,
212 &mut info,
213 60.0,
214 );
215
216 assert_eq!(sizing.get(&ColumnId::from("a")).copied(), Some(150.0));
217 assert_eq!(info.delta_offset, Some(50.0));
218 assert_eq!(info.delta_percentage, Some(0.5));
219 assert_eq!(
220 info.is_resizing_column.as_ref().map(|s| s.as_ref()),
221 Some("a")
222 );
223
224 end_column_resize(
225 ColumnResizeMode::OnChange,
226 ColumnResizeDirection::Ltr,
227 &mut sizing,
228 &mut info,
229 Some(60.0),
230 );
231
232 assert_eq!(sizing.get(&ColumnId::from("a")).copied(), Some(150.0));
233 assert!(info.is_resizing_column.is_none());
234 assert!(info.start_offset.is_none());
235 assert!(info.start_size.is_none());
236 assert!(info.delta_offset.is_none());
237 assert!(info.delta_percentage.is_none());
238 assert!(info.column_sizing_start.is_empty());
239 }
240
241 #[test]
242 fn column_resize_on_end_only_writes_sizing_at_end() {
243 let mut sizing = ColumnSizingState::default();
244 let mut info = ColumnSizingInfoState::default();
245
246 begin_column_resize(
247 &mut info,
248 ColumnId::from("a"),
249 0.0,
250 100.0,
251 vec![(ColumnId::from("a"), 100.0)],
252 );
253
254 drag_column_resize(
255 ColumnResizeMode::OnEnd,
256 ColumnResizeDirection::Ltr,
257 &mut sizing,
258 &mut info,
259 50.0,
260 );
261
262 assert!(sizing.is_empty());
263 assert_eq!(info.delta_offset, Some(50.0));
264 assert_eq!(info.delta_percentage, Some(0.5));
265
266 end_column_resize(
267 ColumnResizeMode::OnEnd,
268 ColumnResizeDirection::Ltr,
269 &mut sizing,
270 &mut info,
271 Some(50.0),
272 );
273
274 assert_eq!(sizing.get(&ColumnId::from("a")).copied(), Some(150.0));
275 assert!(info.is_resizing_column.is_none());
276 }
277
278 #[test]
279 fn begin_column_resize_uses_resizing_column_size_for_start_size_when_present() {
280 let mut info = ColumnSizingInfoState::default();
281
282 begin_column_resize(
283 &mut info,
284 ColumnId::from("ab"),
285 0.0,
286 150.0,
287 vec![
288 (ColumnId::from("a"), 100.0),
289 (ColumnId::from("b"), 50.0),
290 (ColumnId::from("ab"), 150.0),
291 ],
292 );
293
294 assert_eq!(info.start_size, Some(150.0));
295 }
296
297 #[test]
298 fn column_resize_rtl_flips_delta_direction() {
299 let mut sizing = ColumnSizingState::default();
300 let mut info = ColumnSizingInfoState::default();
301
302 begin_column_resize(
303 &mut info,
304 ColumnId::from("a"),
305 0.0,
306 100.0,
307 vec![(ColumnId::from("a"), 100.0)],
308 );
309
310 drag_column_resize(
311 ColumnResizeMode::OnChange,
312 ColumnResizeDirection::Rtl,
313 &mut sizing,
314 &mut info,
315 50.0,
316 );
317
318 assert_eq!(sizing.get(&ColumnId::from("a")).copied(), Some(50.0));
319 assert_eq!(info.delta_offset, Some(-50.0));
320 assert_eq!(info.delta_percentage, Some(-0.5));
321 }
322
323 #[test]
324 fn column_resize_preview_reads_from_info_without_touching_sizing() {
325 let mut sizing = ColumnSizingState::default();
326 let mut info = ColumnSizingInfoState::default();
327
328 begin_column_resize(
329 &mut info,
330 ColumnId::from("a"),
331 0.0,
332 100.0,
333 vec![(ColumnId::from("a"), 100.0)],
334 );
335
336 drag_column_resize(
337 ColumnResizeMode::OnEnd,
338 ColumnResizeDirection::Ltr,
339 &mut sizing,
340 &mut info,
341 50.0,
342 );
343
344 assert!(sizing.is_empty());
345 assert_eq!(
346 column_resize_preview_size(&info, &ColumnId::from("a")),
347 Some(150.0)
348 );
349 }
350}