x11-overlay 0.1.0

A library for creating overlay interfaces on X11 systems using Cairo for rendering
Documentation
#![allow(dead_code)]

use anyhow::Result;
use x11rb::connection::Connection;
use x11rb::protocol::xproto::*;

#[derive(Debug, Clone, Copy)]
pub struct Rectangle {
    pub x: i16,
    pub y: i16,
    pub width: u16,
    pub height: u16,
}

impl Rectangle {
    pub fn new(x: i16, y: i16, width: u16, height: u16) -> Self {
        Self {
            x,
            y,
            width,
            height,
        }
    }

    pub fn intersects(&self, other: &Rectangle) -> bool {
        let self_right = self.x + self.width as i16;
        let self_bottom = self.y + self.height as i16;
        let other_right = other.x + other.width as i16;
        let other_bottom = other.y + other.height as i16;

        !(self.x >= other_right
            || other.x >= self_right
            || self.y >= other_bottom
            || other.y >= self_bottom)
    }

    pub fn contains_point(&self, x: i16, y: i16) -> bool {
        x >= self.x
            && x < self.x + self.width as i16
            && y >= self.y
            && y < self.y + self.height as i16
    }
}

#[derive(Debug, Clone, Copy)]
pub struct Color {
    pub argb: u32,
}

impl Color {
    pub const fn new(a: u8, r: u8, g: u8, b: u8) -> Self {
        Self {
            argb: ((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32),
        }
    }

    pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
        Self::new(255, r, g, b)
    }

    pub const GREEN: Color = Color::rgb(0, 255, 0);
    pub const RED: Color = Color::rgb(255, 0, 0);
    pub const BLUE: Color = Color::rgb(0, 0, 255);
    pub const WHITE: Color = Color::rgb(255, 255, 255);
    #[allow(dead_code)]
    pub const BLACK: Color = Color::rgb(0, 0, 0);
}

pub struct Renderer<'a> {
    conn: &'a x11rb::xcb_ffi::XCBConnection,
    window: Window,
    gc: Option<Gcontext>,
}

impl<'a> Renderer<'a> {
    pub fn new(conn: &'a x11rb::xcb_ffi::XCBConnection, window: Window) -> Self {
        Self {
            conn,
            window,
            gc: None,
        }
    }

    fn ensure_gc(&mut self) -> Result<Gcontext> {
        if let Some(gc) = self.gc {
            Ok(gc)
        } else {
            let gc = self.conn.generate_id()?;
            self.conn
                .create_gc(gc, self.window, &CreateGCAux::default())?;
            self.gc = Some(gc);
            Ok(gc)
        }
    }

    pub fn fill_rectangle(&mut self, rect: Rectangle, color: Color) -> Result<()> {
        let gc = self.ensure_gc()?;

        self.conn
            .change_gc(gc, &ChangeGCAux::new().foreground(color.argb))?;

        self.conn.poly_fill_rectangle(
            self.window,
            gc,
            &[x11rb::protocol::xproto::Rectangle {
                x: rect.x,
                y: rect.y,
                width: rect.width,
                height: rect.height,
            }],
        )?;

        Ok(())
    }

    #[allow(dead_code)]
    pub fn clear_area(&self, rect: Rectangle) -> Result<()> {
        self.conn
            .clear_area(false, self.window, rect.x, rect.y, rect.width, rect.height)?;
        Ok(())
    }

    pub fn flush(&self) -> Result<()> {
        self.conn.flush()?;
        Ok(())
    }
}

impl<'a> Drop for Renderer<'a> {
    fn drop(&mut self) {
        if let Some(gc) = self.gc {
            let _ = self.conn.free_gc(gc);
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_color_creation() {
        let color = Color::new(255, 255, 0, 0);
        assert_eq!(color.argb, 0xFF_FF_00_00);
    }

    #[test]
    fn test_color_rgb() {
        let red = Color::rgb(255, 0, 0);
        assert_eq!(red.argb, 0xFF_FF_00_00);

        let green = Color::rgb(0, 255, 0);
        assert_eq!(green.argb, 0xFF_00_FF_00);

        let blue = Color::rgb(0, 0, 255);
        assert_eq!(blue.argb, 0xFF_00_00_FF);
    }

    #[test]
    fn test_color_constants() {
        assert_eq!(Color::RED.argb, 0xFF_FF_00_00);
        assert_eq!(Color::GREEN.argb, 0xFF_00_FF_00);
        assert_eq!(Color::BLUE.argb, 0xFF_00_00_FF);
        assert_eq!(Color::WHITE.argb, 0xFF_FF_FF_FF);
        assert_eq!(Color::BLACK.argb, 0xFF_00_00_00);
    }

    #[test]
    fn test_color_alpha_channel() {
        let transparent = Color::new(0, 255, 255, 255);
        assert_eq!(transparent.argb, 0x00_FF_FF_FF);

        let semi_transparent = Color::new(128, 255, 0, 0);
        assert_eq!(semi_transparent.argb, 0x80_FF_00_00);
    }

    #[test]
    fn test_rectangle_creation() {
        let rect = Rectangle {
            x: 10,
            y: 20,
            width: 100,
            height: 200,
        };

        assert_eq!(rect.x, 10);
        assert_eq!(rect.y, 20);
        assert_eq!(rect.width, 100);
        assert_eq!(rect.height, 200);
    }

    mod property_tests {
        use super::*;
        use proptest::prelude::*;

        proptest! {
            #[test]
            fn test_color_components_roundtrip(a: u8, r: u8, g: u8, b: u8) {
                let color = Color::new(a, r, g, b);

                // Extract components back from ARGB
                let extracted_a = ((color.argb >> 24) & 0xFF) as u8;
                let extracted_r = ((color.argb >> 16) & 0xFF) as u8;
                let extracted_g = ((color.argb >> 8) & 0xFF) as u8;
                let extracted_b = (color.argb & 0xFF) as u8;

                assert_eq!(extracted_a, a);
                assert_eq!(extracted_r, r);
                assert_eq!(extracted_g, g);
                assert_eq!(extracted_b, b);
            }

            #[test]
            fn test_rectangle_bounds(x: i16, y: i16, width: u16, height: u16) {
                let rect = Rectangle { x, y, width, height };

                // Test that all fields are preserved
                assert_eq!(rect.x, x);
                assert_eq!(rect.y, y);
                assert_eq!(rect.width, width);
                assert_eq!(rect.height, height);
            }
        }
    }
}