//! Places and sizes on the canvas. //! //! The canvas is a grid of terminal cells (at Normal zoom) with no edges, so //! coordinates can be negative. `x` grows to the right and `y` downward, as in //! the terminal. use std::ops::{Add, AddAssign, Neg, Sub}; /// A point on the canvas. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)] pub struct CanvasPos { pub x: i32, pub y: i32, } impl CanvasPos { /// The point at `x`, `y`. pub const fn new(x: i32, y: i32) -> CanvasPos { CanvasPos { x, y } } } /// A movement on the canvas: `dx` cells to the right, `dy` cells down. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)] pub struct CanvasOffset { pub dx: i32, pub dy: i32, } impl CanvasOffset { /// A movement of `dx` cells to the right and `dy` cells down. pub const fn new(dx: i32, dy: i32) -> CanvasOffset { CanvasOffset { dx, dy } } } impl Add for CanvasPos { type Output = CanvasPos; fn add(self, offset: CanvasOffset) -> CanvasPos { CanvasPos::new(self.x + offset.dx, self.y + offset.dy) } } impl AddAssign for CanvasPos { fn add_assign(&mut self, offset: CanvasOffset) { *self = *self + offset; } } impl Sub for CanvasPos { type Output = CanvasOffset; /// The movement that goes from `other` to `self`. fn sub(self, other: CanvasPos) -> CanvasOffset { CanvasOffset::new(self.x - other.x, self.y - other.y) } } impl Neg for CanvasOffset { type Output = CanvasOffset; fn neg(self) -> CanvasOffset { CanvasOffset::new(-self.dx, -self.dy) } } /// A width and a height on the canvas, in cells. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)] pub struct CanvasSize { pub width: u16, pub height: u16, } impl CanvasSize { /// A size of `width` by `height` cells. pub const fn new(width: u16, height: u16) -> CanvasSize { CanvasSize { width, height } } } /// A rectangle on the canvas: its top-left corner and its size. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)] pub struct CanvasRect { pub pos: CanvasPos, pub size: CanvasSize, } impl CanvasRect { /// The rectangle with its top-left corner at `pos`. pub const fn new(pos: CanvasPos, size: CanvasSize) -> CanvasRect { CanvasRect { pos, size } } /// The first column inside. pub fn left(self) -> i32 { self.pos.x } /// The first row inside. pub fn top(self) -> i32 { self.pos.y } /// The first column past the right edge. pub fn right(self) -> i32 { self.pos.x + i32::from(self.size.width) } /// The first row past the bottom edge. pub fn bottom(self) -> i32 { self.pos.y + i32::from(self.size.height) } /// The middle cell (rounded toward the top left). pub fn center(self) -> CanvasPos { CanvasPos::new( self.pos.x + i32::from(self.size.width) / 2, self.pos.y + i32::from(self.size.height) / 2, ) } /// Whether the two rectangles share at least one cell. pub fn overlaps(self, other: CanvasRect) -> bool { self.left() < other.right() && other.left() < self.right() && self.top() < other.bottom() && other.top() < self.bottom() } } /// The four directions, on screen and on the canvas. Up is toward smaller /// `y`. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Direction { Up, Right, Down, Left, } impl Direction { /// The direction pointing the other way. pub fn opposite(self) -> Direction { match self { Direction::Up => Direction::Down, Direction::Right => Direction::Left, Direction::Down => Direction::Up, Direction::Left => Direction::Right, } } }