windows-api-utils 0.2.0

Windows API utilities for coordinate conversion, bit operations, and message parameter handling with feature gating
Documentation
//! Coordinate conversion utilities.
//!
//! This module provides types and traits for coordinate conversion
//! between client and screen coordinates.
//!
//! Requires the `coordinates` feature to be enabled.

use crate::{WindowsUtilsError, WindowsUtilsResult};
use core::fmt;

/// A point in 2D space.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Point {
    /// X coordinate
    pub x: i32,
    /// Y coordinate
    pub y: i32,
}

impl Point {
    /// Creates a new point.
    pub const fn new(x: i32, y: i32) -> Self {
        Self { x, y }
    }

    /// The origin point (0, 0).
    pub const ORIGIN: Self = Self::new(0, 0);

    /// Returns whether this point is valid (non-negative coordinates).
    pub fn is_valid(&self) -> bool {
        self.x >= 0 && self.y >= 0
    }

    /// Translates the point by the given offsets.
    pub fn translate(&self, dx: i32, dy: i32) -> Self {
        Self::new(self.x + dx, self.y + dy)
    }

    /// Calculates the distance to another point.
    pub fn distance_to(&self, other: Point) -> f64 {
        let dx = (other.x - self.x) as f64;
        let dy = (other.y - self.y) as f64;
        (dx * dx + dy * dy).sqrt()
    }
}

impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

/// A rectangle defined by its top-left corner and size.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Rect {
    /// Top-left corner
    pub origin: Point,
    /// Width
    pub width: i32,
    /// Height
    pub height: i32,
}

impl Rect {
    /// Creates a new rectangle.
    pub const fn new(x: i32, y: i32, width: i32, height: i32) -> Self {
        Self {
            origin: Point::new(x, y),
            width,
            height,
        }
    }

    /// Creates a rectangle from two points.
    pub fn from_points(top_left: Point, bottom_right: Point) -> Self {
        Self {
            origin: top_left,
            width: bottom_right.x - top_left.x,
            height: bottom_right.y - top_left.y,
        }
    }

    /// Returns the left coordinate.
    pub fn left(&self) -> i32 {
        self.origin.x
    }

    /// Returns the top coordinate.
    pub fn top(&self) -> i32 {
        self.origin.y
    }

    /// Returns the right coordinate.
    pub fn right(&self) -> i32 {
        self.origin.x + self.width
    }

    /// Returns the bottom coordinate.
    pub fn bottom(&self) -> i32 {
        self.origin.y + self.height
    }

    /// Returns the center point of the rectangle.
    pub fn center(&self) -> Point {
        Point::new(
            self.origin.x + self.width / 2,
            self.origin.y + self.height / 2,
        )
    }

    /// Returns whether the rectangle contains the given point.
    pub fn contains(&self, point: Point) -> bool {
        point.x >= self.left()
            && point.x <= self.right()
            && point.y >= self.top()
            && point.y <= self.bottom()
    }

    /// Returns the area of the rectangle.
    pub fn area(&self) -> i32 {
        self.width * self.height
    }

    /// Returns whether this rectangle intersects with another.
    pub fn intersects(&self, other: &Rect) -> bool {
        self.left() <= other.right()
            && self.right() >= other.left()
            && self.top() <= other.bottom()
            && self.bottom() >= other.top()
    }
}

/// Window style information for coordinate calculations.
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct WindowStyle {
    /// Whether the window has a border
    pub has_border: bool,
    /// Whether the window has a title bar
    pub has_title_bar: bool,
    /// Border width in pixels
    pub border_width: i32,
    /// Title bar height in pixels
    pub title_bar_height: i32,
}

impl Default for WindowStyle {
    fn default() -> Self {
        Self {
            has_border: true,
            has_title_bar: true,
            border_width: 8,
            title_bar_height: 30,
        }
    }
}

/// Represents a window for coordinate conversion.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Window {
    /// Window handle
    handle: isize,
    /// Window rectangle in screen coordinates
    window_rect: Rect,
    /// Window style information
    style: WindowStyle,
}

impl Window {
    /// Creates a new window.
    pub fn new(handle: isize, window_rect: Rect, style: WindowStyle) -> Self {
        Self {
            handle,
            window_rect,
            style,
        }
    }

    /// Calculates the client area rectangle.
    pub fn client_rect(&self) -> Rect {
        let mut client = self.window_rect;

        if self.style.has_title_bar {
            client.origin.y += self.style.title_bar_height;
            client.height -= self.style.title_bar_height;
        }

        if self.style.has_border {
            client.origin.x += self.style.border_width;
            client.origin.y += self.style.border_width;
            client.width -= self.style.border_width * 2;
            client.height -= self.style.border_width * 2;
        }

        // Ensure non-negative dimensions
        if client.width < 0 {
            client.width = 0;
        }
        if client.height < 0 {
            client.height = 0;
        }

        client
    }

    /// Returns the window handle.
    pub fn handle(&self) -> isize {
        self.handle
    }

    /// Returns the window rectangle.
    pub fn window_rect(&self) -> Rect {
        self.window_rect
    }

    /// Returns the window style.
    pub fn style(&self) -> WindowStyle {
        self.style
    }

    /// Moves the window to a new position.
    pub fn move_to(&mut self, new_position: Point) {
        self.window_rect.origin = new_position;
    }

    /// Resizes the window.
    pub fn resize(&mut self, new_width: i32, new_height: i32) {
        self.window_rect.width = new_width;
        self.window_rect.height = new_height;
    }

    /// Updates the window style.
    pub fn set_style(&mut self, style: WindowStyle) {
        self.style = style;
    }
}

/// Trait for coordinate transformation between client and screen spaces.
pub trait CoordinateTransformer {
    /// Converts client coordinates to screen coordinates.
    fn client_to_screen(&self, point: Point) -> WindowsUtilsResult<Point>;

    /// Converts screen coordinates to client coordinates.
    fn screen_to_client(&self, point: Point) -> WindowsUtilsResult<Point>;

    /// Returns the client area rectangle.
    fn client_rect(&self) -> Rect;

    /// Returns the window rectangle.
    fn window_rect(&self) -> Rect;

    /// Checks if a point is within the client area.
    fn is_point_in_client(&self, point: Point) -> bool;
}

impl CoordinateTransformer for Window {
    fn client_to_screen(&self, point: Point) -> WindowsUtilsResult<Point> {
        if !point.is_valid() {
            return Err(WindowsUtilsError::InvalidCoordinates {
                x: point.x,
                y: point.y,
            });
        }

        let client_rect = self.client_rect();

        // Client coordinates are relative to the client area
        // To convert to screen coordinates, add the client area's screen position
        Ok(Point::new(
            client_rect.left() + point.x,
            client_rect.top() + point.y,
        ))
    }

    fn screen_to_client(&self, point: Point) -> WindowsUtilsResult<Point> {
        if !point.is_valid() {
            return Err(WindowsUtilsError::InvalidCoordinates {
                x: point.x,
                y: point.y,
            });
        }

        let client_rect = self.client_rect();

        // Screen coordinates are absolute
        // To convert to client coordinates, subtract the client area's screen position
        let client_point = Point::new(point.x - client_rect.left(), point.y - client_rect.top());

        // Check if the resulting point is within the client area
        if client_point.x < 0
            || client_point.y < 0
            || client_point.x >= client_rect.width
            || client_point.y >= client_rect.height
        {
            return Err(WindowsUtilsError::OutOfBounds {
                x: point.x,
                y: point.y,
            });
        }

        Ok(client_point)
    }

    fn client_rect(&self) -> Rect {
        self.client_rect()
    }

    fn window_rect(&self) -> Rect {
        self.window_rect
    }

    fn is_point_in_client(&self, point: Point) -> bool {
        let client_rect = self.client_rect();
        point.x >= 0 && point.y >= 0 && point.x < client_rect.width && point.y < client_rect.height
    }
}