imx/
layout.rs

1use image::{Rgb, RgbImage};
2use std::path::Path;
3use crate::numeric::{i32_to_u32, u32_to_i32};
4
5/// Represents a rectangular region in the layout
6#[derive(Debug, Clone, Copy)]
7pub struct LayoutRect {
8    pub x: i32,
9    pub y: i32,
10    pub width: u32,
11    pub height: u32,
12}
13
14/// Represents different types of layout elements
15#[derive(Debug)]
16pub enum LayoutElement {
17    Image {
18        rect: LayoutRect,
19        path: String,
20    },
21    RowLabel {
22        rect: LayoutRect,
23        text: String,
24    },
25    ColumnLabel {
26        rect: LayoutRect,
27        text: String,
28    },
29    Padding {
30        rect: LayoutRect,
31        description: String,
32    },
33}
34
35/// Represents the complete layout of the plot
36#[derive(Debug)]
37pub struct Layout {
38    pub elements: Vec<LayoutElement>,
39    pub total_width: u32,
40    pub total_height: u32,
41}
42
43impl Layout {
44    /// Creates a new empty layout
45    #[must_use] pub fn new(width: u32, height: u32) -> Self {
46        Self {
47            elements: Vec::new(),
48            total_width: width,
49            total_height: height,
50        }
51    }
52
53    /// Adds an element to the layout
54    pub fn add_element(&mut self, element: LayoutElement) {
55        self.elements.push(element);
56    }
57
58    /// Renders the layout as a debug visualization
59    #[must_use] pub fn render_debug(&self) -> RgbImage {
60        let mut canvas = RgbImage::new(self.total_width, self.total_height);
61        
62        // Fill with white background
63        for pixel in canvas.pixels_mut() {
64            *pixel = Rgb([255, 255, 255]);
65        }
66
67        // Define colors for different element types
68        let image_color = Rgb([200, 200, 255]); // Light blue
69        let row_label_color = Rgb([255, 200, 200]); // Light red
70        let col_label_color = Rgb([200, 255, 200]); // Light green
71        let padding_color = Rgb([240, 240, 240]); // Light gray
72
73        // Draw each element
74        for element in &self.elements {
75            let color = match element {
76                LayoutElement::Image { path, .. } => {
77                    (image_color, format!("Image: {}", Path::new(path).file_name().unwrap_or_default().to_string_lossy()))
78                }
79                LayoutElement::RowLabel { text, .. } => {
80                    (row_label_color, format!("Row: {text}"))
81                }
82                LayoutElement::ColumnLabel { text, .. } => {
83                    (col_label_color, format!("Col: {text}"))
84                }
85                LayoutElement::Padding { description, .. } => {
86                    (padding_color, format!("Pad: {description}"))
87                }
88            }.0;
89
90            let rect = match element {
91                LayoutElement::Image { rect, .. } |
92                LayoutElement::RowLabel { rect, .. } |
93                LayoutElement::ColumnLabel { rect, .. } |
94                LayoutElement::Padding { rect, .. } => rect,
95            };
96
97            // Draw filled rectangle
98            let y_end = rect.y.saturating_add(u32_to_i32(rect.height));
99            let x_end = rect.x.saturating_add(u32_to_i32(rect.width));
100            for y in rect.y.max(0)..y_end {
101                for x in rect.x.max(0)..x_end {
102                    if x >= 0 && y >= 0 && 
103                       x < u32_to_i32(canvas.width()) && 
104                       y < u32_to_i32(canvas.height()) {
105                        canvas.put_pixel(i32_to_u32(x), i32_to_u32(y), color);
106                    }
107                }
108            }
109
110            // Draw border
111            let border_color = Rgb([100, 100, 100]);
112            let x_end = rect.x.saturating_add(u32_to_i32(rect.width));
113            for x in rect.x.max(0)..x_end {
114                if x >= 0 && x < u32_to_i32(canvas.width()) {
115                    if rect.y >= 0 && rect.y < u32_to_i32(canvas.height()) {
116                        canvas.put_pixel(i32_to_u32(x), i32_to_u32(rect.y), border_color);
117                    }
118                    let bottom_y = rect.y.saturating_add(u32_to_i32(rect.height) - 1);
119                    if bottom_y >= 0 && bottom_y < u32_to_i32(canvas.height()) {
120                        canvas.put_pixel(i32_to_u32(x), i32_to_u32(bottom_y), border_color);
121                    }
122                }
123            }
124
125            let y_end = rect.y.saturating_add(u32_to_i32(rect.height));
126            for y in rect.y.max(0)..y_end {
127                if y >= 0 && y < u32_to_i32(canvas.height()) {
128                    if rect.x >= 0 && rect.x < u32_to_i32(canvas.width()) {
129                        canvas.put_pixel(i32_to_u32(rect.x), i32_to_u32(y), border_color);
130                    }
131                    let right_x = rect.x.saturating_add(u32_to_i32(rect.width) - 1);
132                    if right_x >= 0 && right_x < u32_to_i32(canvas.width()) {
133                        canvas.put_pixel(i32_to_u32(right_x), i32_to_u32(y), border_color);
134                    }
135                }
136            }
137        }
138
139        canvas
140    }
141}