Skip to main content

spritebox/
screen.rs

1//! Fixed-position UI compositions, rendered independently of the atlas.
2use crate::{Color, Document, Error, Image, Limits, Position, Result, Sprite};
3
4/// A sprite chain placed on a screen.
5#[derive(Clone, Debug, Eq, PartialEq)]
6pub struct ScreenItem {
7    pub(crate) sprite: Sprite,
8    pub(crate) x: i32,
9    pub(crate) y: i32,
10    pub(crate) position: Position,
11}
12impl ScreenItem {
13    pub fn sprite(&self) -> &Sprite {
14        &self.sprite
15    }
16    pub fn position(&self) -> (i32, i32) {
17        (self.x, self.y)
18    }
19}
20
21/// A screen with a finite pixel canvas and ordered sprite placements.
22#[derive(Clone, Debug, Eq, PartialEq)]
23pub struct Screen {
24    pub(crate) name: String,
25    pub(crate) width: u32,
26    pub(crate) height: u32,
27    pub(crate) background: Option<(String, char)>,
28    pub(crate) grid: Option<u32>,
29    pub(crate) items: Vec<ScreenItem>,
30    pub(crate) position: Position,
31}
32impl Screen {
33    pub fn name(&self) -> &str {
34        &self.name
35    }
36    pub fn size(&self) -> (u32, u32) {
37        (self.width, self.height)
38    }
39    pub fn items(&self) -> &[ScreenItem] {
40        &self.items
41    }
42    pub fn grid(&self) -> Option<u32> {
43        self.grid
44    }
45    pub(crate) fn canvas(&self, document: &Document, limits: &Limits) -> Result<Image> {
46        let mut image = Image::new_with(self.width, self.height, limits)?;
47        let color = self
48            .background
49            .as_ref()
50            .and_then(|(p, k)| document.palette(p)?.color(*k))
51            .unwrap_or(Color::TRANSPARENT);
52        for y in 0..self.height {
53            for x in 0..self.width {
54                image.set_pixel(x, y, color);
55            }
56        }
57        Ok(image)
58    }
59    pub(crate) fn render(&self, document: &Document, limits: &Limits) -> Result<Image> {
60        let mut image = self.canvas(document, limits)?;
61        for item in &self.items {
62            image.over_from(&item.sprite.render(), item.x as i64, item.y as i64);
63        }
64        Ok(image)
65    }
66}
67impl Document {
68    pub fn screens(&self) -> &[Screen] {
69        &self.screens
70    }
71    /// Render a named screen within the default limits.
72    pub fn screen(&self, name: &str) -> Option<Image> {
73        self.screen_with(name, &Limits::default()).ok().flatten()
74    }
75    /// Render a screen with explicit limits. Unknown names return `Ok(None)`.
76    pub fn screen_with(&self, name: &str, limits: &Limits) -> Result<Option<Image>> {
77        self.screens
78            .iter()
79            .find(|s| s.name == name)
80            .map(|s| s.render(self, limits))
81            .transpose()
82    }
83}
84impl Image {
85    /// Draw source-over at signed coordinates, clipping to this image.
86    pub fn over_from(&mut self, source: &Image, x: i64, y: i64) {
87        let (left, top, right, bottom) = placement_bounds(
88            self.width(),
89            self.height(),
90            source.width(),
91            source.height(),
92            x,
93            y,
94        );
95        for ty in top..bottom {
96            for tx in left..right {
97                let color = source.pixel((tx - x) as u32, (ty - y) as u32).unwrap();
98                let below = self.pixel(tx as u32, ty as u32).unwrap();
99                self.set_pixel(tx as u32, ty as u32, color.over(below));
100            }
101        }
102    }
103    /// Nearest-neighbour integer scaling within explicit image limits.
104    pub fn scaled_with(&self, scale: u32, limits: &Limits) -> Result<Image> {
105        if !(1..=64).contains(&scale) {
106            return Err(Error::invalid("Scale must be 1..=64"));
107        }
108        let w = self
109            .width()
110            .checked_mul(scale)
111            .ok_or_else(|| Error::limit("Scaled width overflow"))?;
112        let h = self
113            .height()
114            .checked_mul(scale)
115            .ok_or_else(|| Error::limit("Scaled height overflow"))?;
116        let mut image = Image::new_with(w, h, limits)?;
117        for y in 0..h {
118            for x in 0..w {
119                image.set_pixel(x, y, self.pixel(x / scale, y / scale).unwrap());
120            }
121        }
122        Ok(image)
123    }
124}
125
126pub(crate) fn placement_bounds(
127    width: u32,
128    height: u32,
129    source_width: u32,
130    source_height: u32,
131    x: i64,
132    y: i64,
133) -> (i64, i64, i64, i64) {
134    (
135        x.clamp(0, width as i64),
136        y.clamp(0, height as i64),
137        x.saturating_add(source_width as i64).clamp(0, width as i64),
138        y.saturating_add(source_height as i64)
139            .clamp(0, height as i64),
140    )
141}