x11_overlay/graphics/
renderer.rs

1#![allow(dead_code)]
2
3use anyhow::Result;
4use x11rb::connection::Connection;
5use x11rb::protocol::xproto::*;
6
7#[derive(Debug, Clone, Copy)]
8pub struct Rectangle {
9    pub x: i16,
10    pub y: i16,
11    pub width: u16,
12    pub height: u16,
13}
14
15impl Rectangle {
16    pub fn new(x: i16, y: i16, width: u16, height: u16) -> Self {
17        Self {
18            x,
19            y,
20            width,
21            height,
22        }
23    }
24
25    pub fn intersects(&self, other: &Rectangle) -> bool {
26        let self_right = self.x + self.width as i16;
27        let self_bottom = self.y + self.height as i16;
28        let other_right = other.x + other.width as i16;
29        let other_bottom = other.y + other.height as i16;
30
31        !(self.x >= other_right
32            || other.x >= self_right
33            || self.y >= other_bottom
34            || other.y >= self_bottom)
35    }
36
37    pub fn contains_point(&self, x: i16, y: i16) -> bool {
38        x >= self.x
39            && x < self.x + self.width as i16
40            && y >= self.y
41            && y < self.y + self.height as i16
42    }
43}
44
45#[derive(Debug, Clone, Copy)]
46pub struct Color {
47    pub argb: u32,
48}
49
50impl Color {
51    pub const fn new(a: u8, r: u8, g: u8, b: u8) -> Self {
52        Self {
53            argb: ((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32),
54        }
55    }
56
57    pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
58        Self::new(255, r, g, b)
59    }
60
61    pub const GREEN: Color = Color::rgb(0, 255, 0);
62    pub const RED: Color = Color::rgb(255, 0, 0);
63    pub const BLUE: Color = Color::rgb(0, 0, 255);
64    pub const WHITE: Color = Color::rgb(255, 255, 255);
65    #[allow(dead_code)]
66    pub const BLACK: Color = Color::rgb(0, 0, 0);
67}
68
69pub struct Renderer<'a> {
70    conn: &'a x11rb::xcb_ffi::XCBConnection,
71    window: Window,
72    gc: Option<Gcontext>,
73}
74
75impl<'a> Renderer<'a> {
76    pub fn new(conn: &'a x11rb::xcb_ffi::XCBConnection, window: Window) -> Self {
77        Self {
78            conn,
79            window,
80            gc: None,
81        }
82    }
83
84    fn ensure_gc(&mut self) -> Result<Gcontext> {
85        if let Some(gc) = self.gc {
86            Ok(gc)
87        } else {
88            let gc = self.conn.generate_id()?;
89            self.conn
90                .create_gc(gc, self.window, &CreateGCAux::default())?;
91            self.gc = Some(gc);
92            Ok(gc)
93        }
94    }
95
96    pub fn fill_rectangle(&mut self, rect: Rectangle, color: Color) -> Result<()> {
97        let gc = self.ensure_gc()?;
98
99        self.conn
100            .change_gc(gc, &ChangeGCAux::new().foreground(color.argb))?;
101
102        self.conn.poly_fill_rectangle(
103            self.window,
104            gc,
105            &[x11rb::protocol::xproto::Rectangle {
106                x: rect.x,
107                y: rect.y,
108                width: rect.width,
109                height: rect.height,
110            }],
111        )?;
112
113        Ok(())
114    }
115
116    #[allow(dead_code)]
117    pub fn clear_area(&self, rect: Rectangle) -> Result<()> {
118        self.conn
119            .clear_area(false, self.window, rect.x, rect.y, rect.width, rect.height)?;
120        Ok(())
121    }
122
123    pub fn flush(&self) -> Result<()> {
124        self.conn.flush()?;
125        Ok(())
126    }
127}
128
129impl<'a> Drop for Renderer<'a> {
130    fn drop(&mut self) {
131        if let Some(gc) = self.gc {
132            let _ = self.conn.free_gc(gc);
133        }
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn test_color_creation() {
143        let color = Color::new(255, 255, 0, 0);
144        assert_eq!(color.argb, 0xFF_FF_00_00);
145    }
146
147    #[test]
148    fn test_color_rgb() {
149        let red = Color::rgb(255, 0, 0);
150        assert_eq!(red.argb, 0xFF_FF_00_00);
151
152        let green = Color::rgb(0, 255, 0);
153        assert_eq!(green.argb, 0xFF_00_FF_00);
154
155        let blue = Color::rgb(0, 0, 255);
156        assert_eq!(blue.argb, 0xFF_00_00_FF);
157    }
158
159    #[test]
160    fn test_color_constants() {
161        assert_eq!(Color::RED.argb, 0xFF_FF_00_00);
162        assert_eq!(Color::GREEN.argb, 0xFF_00_FF_00);
163        assert_eq!(Color::BLUE.argb, 0xFF_00_00_FF);
164        assert_eq!(Color::WHITE.argb, 0xFF_FF_FF_FF);
165        assert_eq!(Color::BLACK.argb, 0xFF_00_00_00);
166    }
167
168    #[test]
169    fn test_color_alpha_channel() {
170        let transparent = Color::new(0, 255, 255, 255);
171        assert_eq!(transparent.argb, 0x00_FF_FF_FF);
172
173        let semi_transparent = Color::new(128, 255, 0, 0);
174        assert_eq!(semi_transparent.argb, 0x80_FF_00_00);
175    }
176
177    #[test]
178    fn test_rectangle_creation() {
179        let rect = Rectangle {
180            x: 10,
181            y: 20,
182            width: 100,
183            height: 200,
184        };
185
186        assert_eq!(rect.x, 10);
187        assert_eq!(rect.y, 20);
188        assert_eq!(rect.width, 100);
189        assert_eq!(rect.height, 200);
190    }
191
192    mod property_tests {
193        use super::*;
194        use proptest::prelude::*;
195
196        proptest! {
197            #[test]
198            fn test_color_components_roundtrip(a: u8, r: u8, g: u8, b: u8) {
199                let color = Color::new(a, r, g, b);
200
201                // Extract components back from ARGB
202                let extracted_a = ((color.argb >> 24) & 0xFF) as u8;
203                let extracted_r = ((color.argb >> 16) & 0xFF) as u8;
204                let extracted_g = ((color.argb >> 8) & 0xFF) as u8;
205                let extracted_b = (color.argb & 0xFF) as u8;
206
207                assert_eq!(extracted_a, a);
208                assert_eq!(extracted_r, r);
209                assert_eq!(extracted_g, g);
210                assert_eq!(extracted_b, b);
211            }
212
213            #[test]
214            fn test_rectangle_bounds(x: i16, y: i16, width: u16, height: u16) {
215                let rect = Rectangle { x, y, width, height };
216
217                // Test that all fields are preserved
218                assert_eq!(rect.x, x);
219                assert_eq!(rect.y, y);
220                assert_eq!(rect.width, width);
221                assert_eq!(rect.height, height);
222            }
223        }
224    }
225}