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