Files
brahman/crates/modules/mirada/mirada-layout/src/geometry.rs
T
sergio b975dc7919 feat(mirada): mirada-layout — motor de teselado del compositor Wayland
Rect + split (reparto exacto de píxeles), 4 modos de layout
(MasterStack, Monocle, Grid, Columns) con tile(), y Workspace:
ventanas en orden de teselado, foco cíclico, reordenado y
resolución de geometría. Determinista, agnóstico de Wayland/smithay.

22 tests. #![forbid(unsafe_code)].

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 17:24:48 +00:00

104 lines
2.9 KiB
Rust

//! Geometría — el rectángulo en coordenadas de pantalla.
use serde::{Deserialize, Serialize};
/// Un rectángulo en píxeles de pantalla. El origen `(0,0)` es la
/// esquina superior-izquierda; `x` crece a la derecha, `y` hacia abajo.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Rect {
pub x: i32,
pub y: i32,
pub w: i32,
pub h: i32,
}
impl Rect {
pub fn new(x: i32, y: i32, w: i32, h: i32) -> Self {
Self { x, y, w, h }
}
/// Área en píxeles cuadrados.
pub fn area(&self) -> i64 {
self.w.max(0) as i64 * self.h.max(0) as i64
}
/// `true` si el rectángulo tiene ancho y alto positivos.
pub fn is_visible(&self) -> bool {
self.w > 0 && self.h > 0
}
/// Encoge el rectángulo `g` píxeles por cada lado. Si el margen se
/// come toda la dimensión, ésta queda en `0` (no negativa).
pub fn inset(&self, g: i32) -> Rect {
Rect {
x: self.x + g,
y: self.y + g,
w: (self.w - 2 * g).max(0),
h: (self.h - 2 * g).max(0),
}
}
/// `true` si `(px, py)` cae dentro del rectángulo.
pub fn contains(&self, px: i32, py: i32) -> bool {
px >= self.x && px < self.x + self.w && py >= self.y && py < self.y + self.h
}
}
/// Reparte `total` píxeles en `n` tramos contiguos sin perder ni un
/// píxel: las fronteras caen en `total · k / n`, así que la suma de los
/// tamaños es exactamente `total`. Devuelve `(offset, tamaño)` por tramo.
pub fn split(total: i32, n: usize) -> Vec<(i32, i32)> {
if n == 0 {
return Vec::new();
}
let total = total.max(0) as i64;
let n64 = n as i64;
(0..n)
.map(|k| {
let start = total * k as i64 / n64;
let end = total * (k as i64 + 1) / n64;
(start as i32, (end - start) as i32)
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn inset_shrinks_by_gap_on_every_side() {
let r = Rect::new(0, 0, 100, 80).inset(5);
assert_eq!(r, Rect::new(5, 5, 90, 70));
}
#[test]
fn inset_clamps_to_zero() {
let r = Rect::new(0, 0, 8, 8).inset(10);
assert_eq!((r.w, r.h), (0, 0));
assert!(!r.is_visible());
}
#[test]
fn split_loses_no_pixels() {
for n in 1..=13 {
let parts = split(1000, n);
assert_eq!(parts.len(), n);
assert_eq!(parts.iter().map(|(_, s)| *s).sum::<i32>(), 1000);
// Los tramos son contiguos.
for w in parts.windows(2) {
assert_eq!(w[0].0 + w[0].1, w[1].0);
}
}
}
#[test]
fn contains_checks_bounds() {
let r = Rect::new(10, 10, 20, 20);
assert!(r.contains(10, 10));
assert!(r.contains(29, 29));
assert!(!r.contains(30, 30));
assert!(!r.contains(9, 15));
}
}