1#[allow(dead_code)]
8#[derive(Clone, PartialEq, Debug)]
9pub enum SplitLayout {
10 Single,
11 Horizontal,
12 Vertical,
13 Quad,
14}
15
16#[allow(dead_code)]
18#[derive(Clone, Debug)]
19pub struct ViewportPane {
20 pub x: f32,
21 pub y: f32,
22 pub width: f32,
23 pub height: f32,
24 pub label: String,
25}
26
27#[allow(dead_code)]
29pub struct SplitViewConfig {
30 pub layout: SplitLayout,
31 pub viewport_width: f32,
32 pub viewport_height: f32,
33 pub split_ratio: f32,
34 pub active_pane: usize,
35 pub maximized_pane: Option<usize>,
36 pub gap: f32,
37}
38
39#[allow(dead_code)]
41pub type PaneRect = [f32; 4];
42
43#[allow(dead_code)]
44pub fn default_split_view_config(width: f32, height: f32) -> SplitViewConfig {
45 SplitViewConfig {
46 layout: SplitLayout::Single,
47 viewport_width: width,
48 viewport_height: height,
49 split_ratio: 0.5,
50 active_pane: 0,
51 maximized_pane: None,
52 gap: 2.0,
53 }
54}
55
56#[allow(dead_code)]
57pub fn new_split_view(width: f32, height: f32, layout: SplitLayout) -> SplitViewConfig {
58 SplitViewConfig {
59 layout,
60 viewport_width: width,
61 viewport_height: height,
62 split_ratio: 0.5,
63 active_pane: 0,
64 maximized_pane: None,
65 gap: 2.0,
66 }
67}
68
69#[allow(dead_code)]
70pub fn set_layout(config: &mut SplitViewConfig, layout: SplitLayout) {
71 config.layout = layout;
72 config.active_pane = 0;
73 config.maximized_pane = None;
74}
75
76#[allow(dead_code)]
77pub fn pane_count(config: &SplitViewConfig) -> usize {
78 match config.layout {
79 SplitLayout::Single => 1,
80 SplitLayout::Horizontal | SplitLayout::Vertical => 2,
81 SplitLayout::Quad => 4,
82 }
83}
84
85#[allow(dead_code)]
87pub fn pane_rect(config: &SplitViewConfig, index: usize) -> PaneRect {
88 if let Some(max_idx) = config.maximized_pane {
90 if index == max_idx {
91 return [0.0, 0.0, config.viewport_width, config.viewport_height];
92 } else {
93 return [0.0, 0.0, 0.0, 0.0];
94 }
95 }
96
97 let w = config.viewport_width;
98 let h = config.viewport_height;
99 let r = config.split_ratio;
100 let g = config.gap;
101 let half_gap = g * 0.5;
102
103 match config.layout {
104 SplitLayout::Single => [0.0, 0.0, w, h],
105 SplitLayout::Horizontal => {
106 let split_x = w * r;
107 match index {
108 0 => [0.0, 0.0, split_x - half_gap, h],
109 1 => [split_x + half_gap, 0.0, w - split_x - half_gap, h],
110 _ => [0.0, 0.0, 0.0, 0.0],
111 }
112 }
113 SplitLayout::Vertical => {
114 let split_y = h * r;
115 match index {
116 0 => [0.0, 0.0, w, split_y - half_gap],
117 1 => [0.0, split_y + half_gap, w, h - split_y - half_gap],
118 _ => [0.0, 0.0, 0.0, 0.0],
119 }
120 }
121 SplitLayout::Quad => {
122 let split_x = w * r;
123 let split_y = h * r;
124 match index {
125 0 => [0.0, 0.0, split_x - half_gap, split_y - half_gap],
126 1 => [
127 split_x + half_gap,
128 0.0,
129 w - split_x - half_gap,
130 split_y - half_gap,
131 ],
132 2 => [
133 0.0,
134 split_y + half_gap,
135 split_x - half_gap,
136 h - split_y - half_gap,
137 ],
138 3 => [
139 split_x + half_gap,
140 split_y + half_gap,
141 w - split_x - half_gap,
142 h - split_y - half_gap,
143 ],
144 _ => [0.0, 0.0, 0.0, 0.0],
145 }
146 }
147 }
148}
149
150#[allow(dead_code)]
151pub fn active_pane(config: &SplitViewConfig) -> usize {
152 config.active_pane
153}
154
155#[allow(dead_code)]
156pub fn set_active_pane(config: &mut SplitViewConfig, index: usize) {
157 if index < pane_count(config) {
158 config.active_pane = index;
159 }
160}
161
162#[allow(dead_code)]
164pub fn resize_split(config: &mut SplitViewConfig, ratio: f32) {
165 config.split_ratio = ratio.clamp(0.1, 0.9);
166}
167
168#[allow(dead_code)]
169pub fn split_ratio(config: &SplitViewConfig) -> f32 {
170 config.split_ratio
171}
172
173#[allow(dead_code)]
175pub fn pane_at_position(config: &SplitViewConfig, x: f32, y: f32) -> Option<usize> {
176 let count = pane_count(config);
177 for i in 0..count {
178 let rect = pane_rect(config, i);
179 let rx = rect[0];
180 let ry = rect[1];
181 let rw = rect[2];
182 let rh = rect[3];
183 if x >= rx && x < rx + rw && y >= ry && y < ry + rh {
184 return Some(i);
185 }
186 }
187 None
188}
189
190#[allow(dead_code)]
192pub fn layout_name(config: &SplitViewConfig) -> &'static str {
193 match config.layout {
194 SplitLayout::Single => "Single",
195 SplitLayout::Horizontal => "Horizontal",
196 SplitLayout::Vertical => "Vertical",
197 SplitLayout::Quad => "Quad",
198 }
199}
200
201#[allow(dead_code)]
203pub fn split_view_to_json(config: &SplitViewConfig) -> String {
204 let layout_str = match config.layout {
205 SplitLayout::Single => "single",
206 SplitLayout::Horizontal => "horizontal",
207 SplitLayout::Vertical => "vertical",
208 SplitLayout::Quad => "quad",
209 };
210 let max_str = match config.maximized_pane {
211 Some(idx) => format!("{}", idx),
212 None => "null".to_string(),
213 };
214 format!(
215 "{{\"layout\":\"{}\",\"viewport_width\":{},\"viewport_height\":{},\"split_ratio\":{},\"active_pane\":{},\"maximized_pane\":{},\"gap\":{}}}",
216 layout_str, config.viewport_width, config.viewport_height,
217 config.split_ratio, config.active_pane, max_str, config.gap
218 )
219}
220
221#[allow(dead_code)]
223pub fn toggle_maximize_pane(config: &mut SplitViewConfig, index: usize) {
224 if config.maximized_pane == Some(index) {
225 config.maximized_pane = None;
226 } else if index < pane_count(config) {
227 config.maximized_pane = Some(index);
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234
235 #[test]
236 fn test_default_config() {
237 let cfg = default_split_view_config(800.0, 600.0);
238 assert_eq!(cfg.layout, SplitLayout::Single);
239 assert!((cfg.viewport_width - 800.0).abs() < f32::EPSILON);
240 assert!((cfg.viewport_height - 600.0).abs() < f32::EPSILON);
241 assert!((cfg.split_ratio - 0.5).abs() < f32::EPSILON);
242 }
243
244 #[test]
245 fn test_new_split_view() {
246 let cfg = new_split_view(1024.0, 768.0, SplitLayout::Horizontal);
247 assert_eq!(cfg.layout, SplitLayout::Horizontal);
248 assert_eq!(cfg.active_pane, 0);
249 }
250
251 #[test]
252 fn test_set_layout() {
253 let mut cfg = default_split_view_config(800.0, 600.0);
254 set_layout(&mut cfg, SplitLayout::Quad);
255 assert_eq!(cfg.layout, SplitLayout::Quad);
256 assert_eq!(cfg.active_pane, 0);
257 }
258
259 #[test]
260 fn test_pane_count_single() {
261 let cfg = default_split_view_config(800.0, 600.0);
262 assert_eq!(pane_count(&cfg), 1);
263 }
264
265 #[test]
266 fn test_pane_count_horizontal() {
267 let cfg = new_split_view(800.0, 600.0, SplitLayout::Horizontal);
268 assert_eq!(pane_count(&cfg), 2);
269 }
270
271 #[test]
272 fn test_pane_count_quad() {
273 let cfg = new_split_view(800.0, 600.0, SplitLayout::Quad);
274 assert_eq!(pane_count(&cfg), 4);
275 }
276
277 #[test]
278 fn test_pane_rect_single() {
279 let cfg = default_split_view_config(800.0, 600.0);
280 let rect = pane_rect(&cfg, 0);
281 assert!((rect[0] - 0.0).abs() < f32::EPSILON);
282 assert!((rect[1] - 0.0).abs() < f32::EPSILON);
283 assert!((rect[2] - 800.0).abs() < f32::EPSILON);
284 assert!((rect[3] - 600.0).abs() < f32::EPSILON);
285 }
286
287 #[test]
288 fn test_pane_rect_horizontal() {
289 let cfg = new_split_view(800.0, 600.0, SplitLayout::Horizontal);
290 let r0 = pane_rect(&cfg, 0);
291 let r1 = pane_rect(&cfg, 1);
292 assert!(r0[2] > 0.0);
293 assert!(r1[2] > 0.0);
294 assert!((r0[2] + r1[2] + cfg.gap - 800.0).abs() < 1.0);
296 }
297
298 #[test]
299 fn test_active_pane() {
300 let cfg = new_split_view(800.0, 600.0, SplitLayout::Horizontal);
301 assert_eq!(active_pane(&cfg), 0);
302 }
303
304 #[test]
305 fn test_set_active_pane() {
306 let mut cfg = new_split_view(800.0, 600.0, SplitLayout::Horizontal);
307 set_active_pane(&mut cfg, 1);
308 assert_eq!(active_pane(&cfg), 1);
309 }
310
311 #[test]
312 fn test_set_active_pane_out_of_range() {
313 let mut cfg = default_split_view_config(800.0, 600.0);
314 set_active_pane(&mut cfg, 5);
315 assert_eq!(active_pane(&cfg), 0); }
317
318 #[test]
319 fn test_resize_split() {
320 let mut cfg = new_split_view(800.0, 600.0, SplitLayout::Horizontal);
321 resize_split(&mut cfg, 0.3);
322 assert!((split_ratio(&cfg) - 0.3).abs() < f32::EPSILON);
323 }
324
325 #[test]
326 fn test_resize_split_clamped() {
327 let mut cfg = new_split_view(800.0, 600.0, SplitLayout::Horizontal);
328 resize_split(&mut cfg, 0.0);
329 assert!((split_ratio(&cfg) - 0.1).abs() < f32::EPSILON);
330 resize_split(&mut cfg, 1.0);
331 assert!((split_ratio(&cfg) - 0.9).abs() < f32::EPSILON);
332 }
333
334 #[test]
335 fn test_pane_at_position_single() {
336 let cfg = default_split_view_config(800.0, 600.0);
337 assert_eq!(pane_at_position(&cfg, 400.0, 300.0), Some(0));
338 }
339
340 #[test]
341 fn test_pane_at_position_horizontal() {
342 let cfg = new_split_view(800.0, 600.0, SplitLayout::Horizontal);
343 assert_eq!(pane_at_position(&cfg, 100.0, 300.0), Some(0));
345 assert_eq!(pane_at_position(&cfg, 600.0, 300.0), Some(1));
347 }
348
349 #[test]
350 fn test_layout_name() {
351 let cfg = new_split_view(800.0, 600.0, SplitLayout::Vertical);
352 assert_eq!(layout_name(&cfg), "Vertical");
353 }
354
355 #[test]
356 fn test_split_view_to_json() {
357 let cfg = default_split_view_config(800.0, 600.0);
358 let json = split_view_to_json(&cfg);
359 assert!(json.contains("\"layout\":\"single\""));
360 assert!(json.contains("\"maximized_pane\":null"));
361 }
362
363 #[test]
364 fn test_toggle_maximize_pane() {
365 let mut cfg = new_split_view(800.0, 600.0, SplitLayout::Quad);
366 toggle_maximize_pane(&mut cfg, 2);
367 assert_eq!(cfg.maximized_pane, Some(2));
368 let rect = pane_rect(&cfg, 2);
370 assert!((rect[2] - 800.0).abs() < f32::EPSILON);
371 let rect0 = pane_rect(&cfg, 0);
373 assert!((rect0[2] - 0.0).abs() < f32::EPSILON);
374 toggle_maximize_pane(&mut cfg, 2);
376 assert_eq!(cfg.maximized_pane, None);
377 }
378
379 #[test]
380 fn test_pane_at_position_out_of_bounds() {
381 let cfg = default_split_view_config(800.0, 600.0);
382 assert_eq!(pane_at_position(&cfg, -10.0, -10.0), None);
383 }
384}