Skip to main content

retroglyph_core/surface/
geometry.rs

1use crate::color::Style;
2use crate::color::Tint;
3use crate::grid::{Grid, Rect};
4
5use super::styled::StyledSurface;
6use super::{Layer, Surface};
7
8impl<'a> Surface<'a> {
9    /// The region this surface represents, e.g. for a widget to lay itself out in.
10    ///
11    /// Unlike [`clip_rect`](Self::clip_rect), this is never narrowed by [`clip`](Self::clip): it
12    /// only changes when [`scope`](Self::scope) sets a new one. A widget that reads its own area
13    /// off the surface after being clipped (e.g. while partially offscreen) sees the region it
14    /// was given, not the visible sliver of it, so it can still center itself correctly and let
15    /// the clip take care of what actually lands.
16    ///
17    /// Every drawing method on this surface ([`put`](Self::put), [`print`](Self::print),
18    /// [`fill_rect`](Self::fill_rect), ...) takes coordinates local to this surface, where
19    /// `(0, 0)` is this area's own top-left corner, not the underlying grid's. `area()` itself is
20    /// absolute grid space, so `surface.put((surface.area().left(), ...), ...)` only lands
21    /// correctly for a surface whose area happens to start at the grid origin; anywhere else it
22    /// silently misses. A widget that wants to place itself relative to its own bounds (e.g. a
23    /// label in a corner) should reach for `area().at_origin()`, or just [`width`](Self::width)/
24    /// [`height`](Self::height) directly, and never for `area()`'s own [`left`](Rect::left)/
25    /// [`top`](Rect::top).
26    ///
27    /// # Examples
28    ///
29    /// ```
30    /// use retroglyph_core::grid::{Grid, Rect};
31    /// use retroglyph_core::surface::Surface;
32    ///
33    /// let mut grid = Grid::new(10, 10);
34    /// let mut surface = Surface::new(&mut grid, Rect::new(0, 0, 10, 10), 0);
35    /// let mut scoped = surface.scope(Rect::new(3, 3, 4, 4));
36    ///
37    /// assert_eq!(scoped.area(), Rect::new(3, 3, 4, 4));
38    /// assert_eq!(scoped.area().at_origin(), Rect::new(0, 0, 4, 4));
39    /// ```
40    #[must_use]
41    pub const fn area(&self) -> Rect {
42        self.area
43    }
44
45    /// The visible subset of [`area`](Self::area). Every write this surface accepts is
46    /// bounds-checked against this rect, not `area`.
47    #[must_use]
48    pub const fn clip_rect(&self) -> Rect {
49        self.clip
50    }
51
52    /// The width of this surface's area, in columns.
53    #[must_use]
54    pub const fn width(&self) -> u16 {
55        self.area.width()
56    }
57
58    /// The height of this surface's area, in rows.
59    #[must_use]
60    pub const fn height(&self) -> u16 {
61        self.area.height()
62    }
63
64    /// The grid layer this surface writes to.
65    #[must_use]
66    pub const fn layer(&self) -> u8 {
67        self.layer
68    }
69
70    /// A new surface over the same grid, area, and clip, but writing to `layer` instead.
71    #[must_use]
72    pub const fn on_layer(&mut self, layer: u8) -> Surface<'_> {
73        Surface {
74            grid: self.grid,
75            area: self.area,
76            clip: self.clip,
77            layer,
78            tint: self.tint,
79            origin_offset: self.origin_offset,
80        }
81    }
82
83    /// Equivalent to [`self.on_layer(tier.as_u8())`](crate::surface::Surface::on_layer), for switching to one of
84    /// the workspace's named [`Layer`](crate::surface::Layer) tiers instead of a raw layer id. See [`Layer`](crate::surface::Layer)'s docs for
85    /// when to reach for this over a numeric [`Surface::on_layer`](crate::surface::Surface::on_layer) call.
86    #[must_use]
87    pub const fn on_tier(&mut self, tier: Layer) -> Surface<'_> {
88        self.on_layer(tier.as_u8())
89    }
90
91    /// The tint every sprite drawn through this surface is recoloured by.
92    #[must_use]
93    pub const fn tint(&self) -> Tint {
94        self.tint
95    }
96
97    /// The offset [`translate`](Self::translate) has accumulated on this surface, `(0, 0)` if it
98    /// has never been called.
99    ///
100    /// Every coordinate a caller passes to a coordinate-taking method has this subtracted from it
101    /// before the usual bounds check (see [`translate`](Self::translate)'s doc), so a callee
102    /// handed a `&mut Surface` can use this to tell whether it is in a translated coordinate
103    /// space, compose a further offset relative to the current one without over- or
104    /// undershooting, or convert a local coordinate it read back off the surface into the
105    /// caller's own coordinate space by adding this back in.
106    #[must_use]
107    pub const fn origin(&self) -> (i32, i32) {
108        self.origin_offset
109    }
110
111    /// A new surface over the same grid, area, and layer, recolouring every sprite it draws by
112    /// `tint`.
113    ///
114    /// Substituted rather than combined: unlike [`clip`](Self::clip), which can only narrow,
115    /// a tint replaces whatever the parent surface carried. Two tints do not compose into a
116    /// third meaningful one, and silently multiplying an inherited shadow into a caller's damage
117    /// flash would be harder to predict than replacing it.
118    ///
119    /// Applies to sprites only. A cell backend has no sprite to recolour and draws the cell's
120    /// glyph in its own [`Style`](crate::color::Style), tinted or not, so this is invisible there. See [`Tint`](crate::color::Tint).
121    ///
122    /// This tint composes with the sheet's own colour treatment; see
123    /// `retroglyph_window::tileset::SheetColor` and `retroglyph_window::sprite_cache::SpriteTint`
124    /// for the two-stage resolution (retroglyph-core has no dependency on retroglyph-window, so
125    /// these are plain names, not intra-doc links).
126    ///
127    /// For a multi-cell span the tint lands on the anchor cell, which is where a pixel backend
128    /// draws the sprite from. [`blit`](Self::blit) has no such anchor (`grid` is arbitrary
129    /// composed content, not one sprite) and does not apply this tint at all; see its own doc.
130    ///
131    /// # Examples
132    ///
133    /// ```
134    /// # fn main() {
135    /// # fn run() -> Option<()> {
136    /// use retroglyph_core::color::{Style, Tint};
137    /// use retroglyph_core::grid::{Grid, Rect};
138    /// use retroglyph_core::surface::Surface;
139    ///
140    /// let mut grid = Grid::new(8, 4);
141    /// let mut surface = Surface::new(&mut grid, Rect::new(0, 0, 8, 4), 0);
142    ///
143    /// // One grass sprite, drawn twice: once as itself, once dimmed into shadow.
144    /// let grass = '\u{E000}';
145    /// surface.put_span_uniform((0, 0), (2, 1), grass, ' ', Style::default())?;
146    /// surface
147    ///     .with_tint(Tint::multiply(128, 128, 128))
148    ///     .put_span_uniform((2, 0), (2, 1), grass, ' ', Style::default())?;
149    ///
150    /// assert_eq!(grid.tint(0, 0, 0), Tint::None);
151    /// assert_eq!(grid.tint(0, 2, 0), Tint::multiply(128, 128, 128));
152    /// # Some(())
153    /// # }
154    /// # run().unwrap();
155    /// # }
156    /// ```
157    #[must_use]
158    pub const fn with_tint(&mut self, tint: Tint) -> Surface<'_> {
159        Surface {
160            grid: self.grid,
161            area: self.area,
162            clip: self.clip,
163            layer: self.layer,
164            tint,
165            origin_offset: self.origin_offset,
166        }
167    }
168
169    /// A new surface over the same grid, layer, and [`area`](Self::area), whose
170    /// [`clip_rect`](Self::clip_rect) is narrowed to `rect` intersected with this surface's own
171    /// clip. What this surface *represents* is unchanged; only what is visible shrinks.
172    ///
173    /// `rect` is in absolute grid coordinates (it intersects [`clip_rect`](Self::clip_rect),
174    /// itself absolute), not local to this surface's own [`area`](Self::area) the way
175    /// [`fill_rect`](Self::fill_rect), [`clear_region`](Self::clear_region), and
176    /// [`print_aligned`](Self::print_aligned)'s own `rect` are. Coordinates are otherwise
177    /// unchanged: the sub-surface addresses the same space this one does, so a
178    /// sub-rect computed against [`Surface::area`](crate::surface::Surface::area) (e.g. by a [`layout`](crate::layout) split)
179    /// can be passed straight in. Because the clip is intersected rather than substituted,
180    /// narrowing is monotonic: handing a surface down a layout tree can only ever tighten what a
181    /// callee is able to draw into, never widen it.
182    ///
183    /// Clipping is also how the clip-sensitive calls are told what they are drawing into:
184    ///
185    /// - [`print`](Self::print) wraps overflow onto the next row. Clipped to a one-row bar, the
186    ///   wrapped remainder falls outside the clip and is dropped, which is what a single-line
187    ///   bar wants.
188    /// - [`put_span`](Self::put_span) and [`put_span_uniform`](Self::put_span_uniform) refuse a
189    ///   footprint that leaves the clip. Clipped to a content rect, "fits" stops meaning "fits
190    ///   the screen" and starts meaning "does not reserve cells in the status bar below".
191    ///
192    /// A sub-surface that should instead *represent* `rect` (e.g. a widget's own region, laid out
193    /// and centered against `rect` rather than the parent's wider area) wants
194    /// [`scope`](Self::scope), not this.
195    ///
196    /// # Examples
197    ///
198    /// ```
199    /// use retroglyph_core::color::Style;
200    /// use retroglyph_core::grid::{Grid, Pos, Rect};
201    /// use retroglyph_core::surface::Surface;
202    ///
203    /// let mut grid = Grid::new(6, 2);
204    /// let mut screen = Surface::new(&mut grid, Rect::new(0, 0, 6, 2), 0);
205    ///
206    /// // A title too long for the one-row bar at the top: the remainder wraps out of the
207    /// // clip instead of onto the map below.
208    /// screen
209    ///     .clip(Rect::new(0, 0, 6, 1))
210    ///     .print((0, 0), "retroglyph", Style::default());
211    ///
212    /// assert_eq!(grid[Pos::new(0, 0)].glyph(), 'r');
213    /// assert_eq!(grid[Pos::new(0, 1)].glyph(), ' ');
214    /// ```
215    #[must_use]
216    pub fn clip(&mut self, rect: Rect) -> Surface<'_> {
217        Surface {
218            area: self.area,
219            clip: self.clip.intersect(rect),
220            grid: self.grid,
221            layer: self.layer,
222            tint: self.tint,
223            origin_offset: self.origin_offset,
224        }
225    }
226
227    /// A new surface over the same grid and layer, that *represents* `rect`: its
228    /// [`area`](Self::area) becomes `rect`, and its [`clip_rect`](Self::clip_rect) is narrowed to
229    /// `rect` intersected with this surface's own clip.
230    ///
231    /// This is the primitive a widget's own region is built from: a sub-widget laid out against
232    /// `rect` should center, align, and measure itself against `rect` (via [`area`](Self::area)),
233    /// while still being unable to draw outside whatever was already visible in the parent. A
234    /// clip alone cannot do this, because [`clip`](Self::clip) leaves `area` untouched; `scope`
235    /// is what a caller reaches for when handing a sub-rect down to something that is going to
236    /// read that rect back off the surface.
237    ///
238    /// Like [`clip`](Self::clip), the clip narrows monotonically: a `rect` that reaches outside
239    /// this surface's own clip only ever tightens what the returned surface can draw into, never
240    /// widens it, even though `area` itself becomes exactly `rect`.
241    ///
242    /// # Examples
243    ///
244    /// ```
245    /// use retroglyph_core::grid::{Grid, Rect};
246    /// use retroglyph_core::surface::Surface;
247    ///
248    /// let mut grid = Grid::new(8, 4);
249    /// let mut surface = Surface::new(&mut grid, Rect::new(0, 0, 8, 4), 0);
250    ///
251    /// let mut clipped = surface.clip(Rect::new(0, 0, 4, 4));
252    /// // `scope` widens `area` to a rect the parent's clip does not fully cover...
253    /// let scoped = clipped.scope(Rect::new(2, 0, 4, 4));
254    /// assert_eq!(scoped.area(), Rect::new(2, 0, 4, 4));
255    /// // ...but the visible region still cannot exceed the parent's own clip.
256    /// assert_eq!(scoped.clip_rect(), Rect::new(2, 0, 2, 4));
257    /// ```
258    #[must_use]
259    pub fn scope(&mut self, rect: Rect) -> Surface<'_> {
260        Surface {
261            area: rect,
262            clip: self.clip.intersect(rect),
263            grid: self.grid,
264            layer: self.layer,
265            tint: self.tint,
266            origin_offset: self.origin_offset,
267        }
268    }
269
270    /// A view whose `(0, 0)` sits at `origin` relative to this surface's own coordinate space, so
271    /// a caller can draw in a shifted (e.g. world/camera) coordinate space and let the surface do
272    /// the clipping, rather than subtracting `origin` from every coordinate by hand.
273    ///
274    /// Every coordinate-taking method on the returned surface ([`put`](Self::put),
275    /// [`put_signed`](Self::put_signed), [`print`](Self::print), [`print_line`](Self::print_line),
276    /// [`fill_rect`](Self::fill_rect), [`put_offset`](Self::put_offset),
277    /// [`put_span`](Self::put_span), [`put_span_uniform`](Self::put_span_uniform), and
278    /// [`clear_region`](Self::clear_region)) subtracts `origin` (composed with any outstanding
279    /// translate) from the coordinate it is given before applying its usual bounds check. Only
280    /// [`clear`](Self::clear), which takes no coordinate and always clears this surface's whole
281    /// area, is unaffected.
282    ///
283    /// This does not touch [`area`](Self::area) or [`clip_rect`](Self::clip_rect), so both, along
284    /// with [`width`](Self::width) and [`height`](Self::height), keep reporting the same thing
285    /// before and after translating: only the coordinate a caller must pass to land a write
286    /// shifts, never what the surface itself covers or what is visible in it. This composes with
287    /// [`scope`](Self::scope) the same order it is called in: `scope(...).translate(...)` first
288    /// narrows the area and clip, then shifts the coordinate space that still-narrowed area is
289    /// addressed in, so a coordinate that goes negative after the shift can land inside the
290    /// pre-narrowed area.
291    ///
292    /// The offset accumulates with saturating arithmetic: chaining enough `translate` calls in one
293    /// direction clamps `origin` at `i32::MIN`/`i32::MAX` instead of wrapping or panicking. A
294    /// coordinate that only lands after an offset larger than that was never addressable in this
295    /// surface's `u16` grid space to begin with.
296    ///
297    /// # Examples
298    ///
299    /// ```
300    /// use retroglyph_core::color::Style;
301    /// use retroglyph_core::grid::{Grid, Pos, Rect};
302    /// use retroglyph_core::surface::Surface;
303    ///
304    /// let mut grid = Grid::new(10, 10);
305    /// let mut surface = Surface::new(&mut grid, Rect::new(0, 0, 10, 10), 0);
306    ///
307    /// // Narrow to a 4x4 viewport, then shift its coordinate space by (-5, -5): translating
308    /// // does not move or resize the viewport itself.
309    /// let mut scoped = surface.scope(Rect::new(5, 5, 4, 4));
310    /// let mut view = scoped.translate((-5, -5));
311    /// assert_eq!(view.area(), Rect::new(5, 5, 4, 4));
312    ///
313    /// // (-5, -5) minus the translate offset (-5, -5) is (0, 0): the viewport's own local
314    /// // origin, which lands at the viewport's top-left grid cell (5, 5).
315    /// view.put_signed((-5, -5), 'X', Style::default());
316    ///
317    /// assert_eq!(grid[Pos::new(5, 5)].glyph(), 'X');
318    /// ```
319    #[must_use]
320    pub const fn translate(&mut self, origin: (i32, i32)) -> Surface<'_> {
321        Surface {
322            grid: self.grid,
323            area: self.area,
324            clip: self.clip,
325            layer: self.layer,
326            tint: self.tint,
327            origin_offset: (
328                self.origin_offset.0.saturating_add(origin.0),
329                self.origin_offset.1.saturating_add(origin.1),
330            ),
331        }
332    }
333
334    /// [`clip`](Self::clip) to `area`, then [`translate`](Self::translate) by `origin`, in one
335    /// call -- except that unlike plain [`clip`](Self::clip), the returned surface's
336    /// [`area`](Self::area) is `area` intersected with this surface's own area, not `area`
337    /// verbatim.
338    ///
339    /// Chaining `clip(...).translate(...)` directly works when the result is used right where
340    /// it's produced (both `clip` and `translate` return a `Surface<'_>` borrowing the previous
341    /// step for exactly that call), but a helper that hands the composed view back to its own
342    /// caller (for example a scrolling-camera widget's own `surface` method) needs the two
343    /// narrowings applied against a single `&mut self` borrow instead, so the returned surface
344    /// can outlive the call. This does that.
345    ///
346    /// This intersects `area` with this surface's own area rather than replacing it the way
347    /// [`scope`](Self::scope) does, so [`area`](Self::area)/[`width`](Self::width)/
348    /// [`height`](Self::height) on the result can report something smaller than the `area`
349    /// argument. A scrolling-camera widget's `surface` method relies on exactly this: when the
350    /// world is smaller than the viewport, it hands in a viewport-sized `area` and depends on the
351    /// intersection to shrink it back down to the world's own size. A caller that wants `area`
352    /// to become exactly its argument, even reaching outside the parent's current area, should
353    /// use [`scope`](Self::scope) followed by [`translate`](Self::translate) instead.
354    ///
355    /// The offset accumulates with saturating arithmetic, as in [`translate`](Self::translate).
356    ///
357    /// # Examples
358    ///
359    /// ```
360    /// use retroglyph_core::color::Style;
361    /// use retroglyph_core::grid::{Grid, Pos, Rect};
362    /// use retroglyph_core::surface::Surface;
363    ///
364    /// let mut grid = Grid::new(10, 10);
365    /// let mut surface = Surface::new(&mut grid, Rect::new(0, 0, 10, 10), 0);
366    ///
367    /// let mut view = surface.clip_translate(Rect::new(5, 5, 4, 4), (-5, -5));
368    /// assert_eq!(view.area(), Rect::new(5, 5, 4, 4));
369    ///
370    /// view.put_signed((-5, -5), 'X', Style::default());
371    /// assert_eq!(grid[Pos::new(5, 5)].glyph(), 'X');
372    /// ```
373    #[must_use]
374    pub fn clip_translate(&mut self, area: Rect, origin: (i32, i32)) -> Surface<'_> {
375        let area = self.area.intersect(area);
376        Surface {
377            area,
378            clip: self.clip.intersect(area),
379            grid: self.grid,
380            layer: self.layer,
381            tint: self.tint,
382            origin_offset: (
383                self.origin_offset.0.saturating_add(origin.0),
384                self.origin_offset.1.saturating_add(origin.1),
385            ),
386        }
387    }
388
389    /// A styled view over this surface: same area and layer, but every draw call uses `style`
390    /// without needing to pass it each time. Handy for a run of same-styled writes (e.g. filling
391    /// in a wall glyph over many cells) without repeating the [`Style`](crate::color::Style) at every call site.
392    pub const fn with_style(&mut self, style: Style) -> StyledSurface<'_, 'a> {
393        StyledSurface {
394            surface: self,
395            style,
396        }
397    }
398
399    /// Borrows the underlying [`Grid`](crate::grid::Grid) directly, with no clipping.
400    ///
401    /// Escape hatch for multi-layer or whole-grid operations (e.g. [`Grid::blit`](crate::grid::Grid::blit)) that don't fit
402    /// this surface's clipped, single-layer model. Drawing into a sub-rect is not one of those:
403    /// [`clip`](Self::clip) and [`scope`](Self::scope) narrow a surface without handing out the
404    /// unclipped grid to do it.
405    pub const fn grid_mut(&mut self) -> &mut Grid {
406        self.grid
407    }
408
409    /// Read-only counterpart of [`grid_mut`](Self::grid_mut).
410    #[must_use]
411    pub const fn grid(&self) -> &Grid {
412        self.grid
413    }
414}