pub struct Surface<'a> { /* private fields */ }Expand description
The render target for every drawing call in the workspace: a mutable reference to a
Grid plus a fixed layer, scoped to one area.
A Surface is typically created once per frame, scoped to the whole drawing surface (e.g.
via Terminal::draw), and handed to every subsystem/widget in turn;
each caller’s own area: Rect (a sub-rect of the surface’s own area, e.g. one produced by a
layout split) is in the same coordinate space as Surface::area itself.
Surface::put/Surface::print/… take coordinates in that same space and silently clip
any write that falls outside Surface::area, matching the rest of the workspace’s
clip-on-draw policy for out-of-bounds drawing.
Surface::clip turns a sub-rect into a surface of its own, so a subsystem that should not
draw outside one is bounded by the type rather than trusted to respect an area handed to it
alongside a wider surface. The clip is intersected, never substituted, so narrowing only ever
tightens.
A caller that genuinely needs more than one layer at once (e.g. a modal dimming layer 0 while
drawing its own content on layer 1) switches layers with Surface::on_layer rather than
being restricted to the layer it was constructed with.
Implementations§
Source§impl<'a> Surface<'a>
impl<'a> Surface<'a>
Sourcepub const fn new(grid: &'a mut Grid, area: Rect<u16>, layer: u8) -> Surface<'a>
pub const fn new(grid: &'a mut Grid, area: Rect<u16>, layer: u8) -> Surface<'a>
A surface over grid, scoped to area on layer, tinting nothing.
Sourcepub const fn on_layer(&mut self, layer: u8) -> Surface<'_>
pub const fn on_layer(&mut self, layer: u8) -> Surface<'_>
A new surface over the same grid and area, but writing to layer instead.
Sourcepub const fn tint(&self) -> Tint
pub const fn tint(&self) -> Tint
The tint every sprite drawn through this surface is recoloured by.
Sourcepub const fn with_tint(&mut self, tint: Tint) -> Surface<'_>
pub const fn with_tint(&mut self, tint: Tint) -> Surface<'_>
A new surface over the same grid, area, and layer, recolouring every sprite it draws by
tint.
Substituted rather than combined: unlike clip, which can only narrow,
a tint replaces whatever the parent surface carried. Two tints do not compose into a
third meaningful one, and silently multiplying an inherited shadow into a caller’s damage
flash would be harder to predict than replacing it.
Applies to sprites only. A cell backend has no sprite to recolour and draws the cell’s
glyph in its own Style, tinted or not, so this is invisible there. See Tint.
This tint composes with the sheet’s own colour treatment; see
retroglyph_window::tileset::SheetColor and retroglyph_window::sprite_cache::SpriteTint
for the two-stage resolution (retroglyph-core has no dependency on retroglyph-window, so
these are plain names, not intra-doc links).
For a multi-cell span the tint lands on the anchor cell, which is where a pixel backend draws the sprite from.
§Examples
use retroglyph_core::{Grid, Rect, Style, Surface, Tint};
let mut grid = Grid::new(8, 4);
let mut surface = Surface::new(&mut grid, Rect::new(0, 0, 8, 4), 0);
// One grass sprite, drawn twice: once as itself, once dimmed into shadow.
let grass = '\u{E000}';
surface.put_span_uniform((0, 0), (2, 1), grass, ' ', Style::default())?;
surface
.with_tint(Tint::multiply(128, 128, 128))
.put_span_uniform((2, 0), (2, 1), grass, ' ', Style::default())?;
assert_eq!(grid.tint(0, 0, 0), Tint::None);
assert_eq!(grid.tint(0, 2, 0), Tint::multiply(128, 128, 128));Sourcepub fn clip(&mut self, area: Rect<u16>) -> Surface<'_>
pub fn clip(&mut self, area: Rect<u16>) -> Surface<'_>
A new surface over the same grid and layer, clipped to area intersected with this
surface’s own area.
Coordinates are unchanged: the sub-surface addresses the same space this one does, so a
sub-rect computed against Surface::area (e.g. by a layout split)
can be passed straight in. Because area is intersected rather than substituted,
narrowing is monotonic: handing a surface down a layout tree can only ever tighten what a
callee is able to touch.
Clipping is also how the area-sensitive calls are told what they are drawing into:
printwraps overflow onto the next row. Clipped to a one-row bar, the wrapped remainder falls outside the area and is dropped, which is what a single-line bar wants.put_spanandput_span_uniformrefuse a footprint that leaves the area. Clipped to a content rect, “fits” stops meaning “fits the screen” and starts meaning “does not reserve cells in the status bar below”.
§Examples
use retroglyph_core::{Grid, Pos, Rect, Style, Surface};
let mut grid = Grid::new(6, 2);
let mut screen = Surface::new(&mut grid, Rect::new(0, 0, 6, 2), 0);
// A title too long for the one-row bar at the top: the remainder wraps out of the
// clip instead of onto the map below.
screen
.clip(Rect::new(0, 0, 6, 1))
.print((0, 0), "retroglyph", Style::default());
assert_eq!(grid[Pos::new(0, 0)].glyph(), 'r');
assert_eq!(grid[Pos::new(0, 1)].glyph(), ' ');Sourcepub const fn translate(&mut self, origin: (i32, i32)) -> Surface<'_>
pub const fn translate(&mut self, origin: (i32, i32)) -> Surface<'_>
A view whose (0, 0) sits at origin relative to this surface’s own coordinate space, so
a caller can draw in a shifted (e.g. world/camera) coordinate space and let the surface do
the clipping, rather than subtracting origin from every coordinate by hand.
Every coordinate-taking method on the returned surface – put,
put_signed, print, print_line,
fill_rect, put_offset,
put_span, put_span_uniform, and
clear_region – subtracts origin (composed with any outstanding
translate) from the coordinate it is given before applying its usual bounds check. Only
clear, which takes no coordinate and always clears this surface’s whole
area, is unaffected.
This does not touch area, so area, width,
and height keep reporting the same thing before and after translating:
only the coordinate a caller must pass to land a write shifts, never what the surface
itself covers. This composes with clip the same order it is called in:
clip(...).translate(...) first narrows the area, then shifts the coordinate space that
still-narrowed area is addressed in, so a coordinate that goes negative after the shift can
land inside the pre-narrowed area.
§Examples
use retroglyph_core::{Grid, Pos, Rect, Style, Surface};
let mut grid = Grid::new(10, 10);
let mut surface = Surface::new(&mut grid, Rect::new(0, 0, 10, 10), 0);
// Narrow to a 4x4 viewport, then shift its coordinate space by (-5, -5): translating
// does not move or resize the viewport itself.
let mut clipped = surface.clip(Rect::new(5, 5, 4, 4));
let mut view = clipped.translate((-5, -5));
assert_eq!(view.area(), Rect::new(5, 5, 4, 4));
// (-5, -5) minus the translate offset (-5, -5) is (0, 0): the viewport's own local
// origin, which lands at the viewport's top-left grid cell (5, 5).
view.put_signed((-5, -5), 'X', Style::default());
assert_eq!(grid[Pos::new(5, 5)].glyph(), 'X');Sourcepub const fn with_style(&mut self, style: Style) -> StyledSurface<'_, 'a>
pub const fn with_style(&mut self, style: Style) -> StyledSurface<'_, 'a>
A styled view over this surface: same area and layer, but every draw call uses style
without needing to pass it each time. Handy for a run of same-styled writes (e.g. filling
in a wall glyph over many cells) without repeating the Style at every call site.
Sourcepub const fn grid_mut(&mut self) -> &mut Grid
pub const fn grid_mut(&mut self) -> &mut Grid
Borrows the underlying Grid directly, with no clipping.
Escape hatch for multi-layer or whole-grid operations (e.g. Grid::blit) that don’t fit
this surface’s clipped, single-layer model. Drawing into a sub-rect is not one of those:
clip narrows a surface without handing out the unclipped grid to do it.
Sourcepub fn tile(&self, pos: impl Into<Pos<u16>>) -> Option<&Tile>
pub fn tile(&self, pos: impl Into<Pos<u16>>) -> Option<&Tile>
The tile at pos on this surface’s layer, if any.
Respects this surface’s layer but not its area clip, mirroring grid_mut
in that sense: a caller wanting an area-clipped read should check
self.area().contains(...) first.
§Examples
use retroglyph_core::{Grid, Rect, Style, Surface};
let mut grid = Grid::new(4, 4);
let mut surface = Surface::new(&mut grid, Rect::new(0, 0, 4, 4), 0);
surface.put((1, 1), 'X', Style::default());
assert_eq!(surface.tile((1, 1)).map(|t| t.glyph()), Some('X'));
assert_eq!(surface.tile((0, 0)).map(|t| t.glyph()), Some(' '));Sourcepub fn background(&self, pos: impl Into<Pos<u16>>) -> Option<Color>
pub fn background(&self, pos: impl Into<Pos<u16>>) -> Option<Color>
The background colour at pos on this surface’s layer, or None if there’s no tile
there.
A read-only read of a cell’s own background lets a caller blend a new draw with what’s
already there (e.g. surface.background(pos).unwrap_or(default)) without the mutable
borrow grid_mut would otherwise force.
§Examples
use retroglyph_core::{Color, Grid, Rect, Style, Surface};
let mut grid = Grid::new(4, 4);
let mut surface = Surface::new(&mut grid, Rect::new(0, 0, 4, 4), 0);
surface.put((1, 1), 'X', Style::new().bg(Color::RED));
assert_eq!(surface.background((1, 1)), Some(Color::RED));
// Out of the grid entirely: no tile there to read a background from.
assert_eq!(surface.background((10, 10)), None);Sourcepub fn put(&mut self, pos: impl Into<Pos<u16>>, ch: char, style: Style)
pub fn put(&mut self, pos: impl Into<Pos<u16>>, ch: char, style: Style)
Place ch at pos in style. A no-op if pos is outside this surface’s area.
If a pixel backend resolves ch to a sprite, that sprite is composited from its own
pixels: style.fg does not tint it, and style.bg shows through only where
the sprite is transparent. See put_span.
§Examples
use retroglyph_core::{Grid, Pos, Rect, Style, Surface};
let mut grid = Grid::new(4, 4);
let mut surface = Surface::new(&mut grid, Rect::new(0, 0, 4, 4), 0);
surface.put((1, 1), 'X', Style::default());
// Outside the surface's area: silently dropped, not a panic.
surface.put((10, 10), 'X', Style::default());
assert_eq!(grid[Pos::new(1, 1)].glyph(), 'X');Sourcepub fn put_signed(&mut self, pos: (i32, i32), ch: char, style: Style)
pub fn put_signed(&mut self, pos: (i32, i32), ch: char, style: Style)
put, in coordinates relative to this surface’s own area origin, where a
negative coordinate is expressible and simply falls outside (a no-op, matching put’s
out-of-bounds behavior).
Scrolling/camera code (e.g. a viewport over a wider world) computes positions in a
coordinate space that can go negative relative to the viewport, which Pos (backed by
u16) cannot even express. put_signed takes that arithmetic directly, so a caller no
longer clip-tests by hand before calling put.
§Examples
use retroglyph_core::{Grid, Pos, Rect, Style, Surface};
let mut grid = Grid::new(4, 4);
let mut surface = Surface::new(&mut grid, Rect::new(0, 0, 4, 4), 0);
// Negative in either axis: outside this surface's area, silently dropped.
surface.put_signed((-1, 1), 'X', Style::default());
// Non-negative and within bounds: lands like `put`.
surface.put_signed((1, 1), 'X', Style::default());
assert_eq!(grid[Pos::new(1, 1)].glyph(), 'X');
assert_eq!(grid[Pos::new(0, 1)].glyph(), ' ');Sourcepub fn print(&mut self, pos: impl Into<Pos<u16>>, text: &str, style: Style)
pub fn print(&mut self, pos: impl Into<Pos<u16>>, text: &str, style: Style)
Print text starting at pos in style.
\n advances to the next row at the original column. Text that would extend beyond this
surface’s area wraps to the next row at the original column; cells outside the area
(either axis) are clipped. When the egc feature is enabled, text is split into
extended grapheme clusters (so combining marks and ZWJ sequences write as one cell each);
otherwise it is split by char.
§Examples
use retroglyph_core::backend::Headless;
use retroglyph_core::{Style, Terminal};
let mut term = Terminal::new(Headless::new(6, 3));
term.draw(|s| s.print((0, 0), "hello wrapped world", Style::default()))
.unwrap();
// Wraps back to column 0 every 6 cells; the surface is only 3 rows tall, so
// the remainder past row 2 is clipped rather than growing the grid.
assert_eq!(
term.backend().format_view(),
"hello·\nwrappe\nd·worl\n",
);Sourcepub fn print_line(&mut self, pos: impl Into<Pos<u16>>, line: &Line)
pub fn print_line(&mut self, pos: impl Into<Pos<u16>>, line: &Line)
Print line’s styled spans starting at pos, one row, each span in its own style.
Stops once a span would start past this surface’s area.
§Examples
use retroglyph_core::backend::Headless;
use retroglyph_core::text::{Line, Span};
use retroglyph_core::Terminal;
let mut term = Terminal::new(Headless::new(5, 2));
let line = Line::from(vec![Span::raw("hello"), Span::raw("world")]);
term.draw(|s| s.print_line((0, 0), &line)).unwrap();
// The first span exactly fills the one-row area. The second span would start at
// column 5, past the area, so it is skipped entirely rather than wrapped onto the
// next row the way `print` would wrap.
assert_eq!(term.backend().format_view(), "hello\n·····\n");Sourcepub fn print_aligned(
&mut self,
rect: Rect<u16>,
text: &str,
align: HAlign,
style: Style,
)
Available on crate feature egc only.
pub fn print_aligned( &mut self, rect: Rect<u16>, text: &str, align: HAlign, style: Style, )
egc only.print, horizontally aligned within rect (clipped to this surface’s own
area) and measured in display columns (via unicode_width), not bytes.
Wants a per-frame redrawn UI label (a status line, a centred title bar) that should not
allocate: unlike TextLayout, which only accepts a
Line (forcing an allocation to build one for every call), this
takes &str directly.
The starting column is computed with saturating arithmetic, so text wider than rect
does not panic or underflow: it simply left-aligns and lets print clip
the overflow, for every HAlign (matching how
HAlign::Center itself saturates in
TextLayout).
§Examples
use retroglyph_core::backend::Headless;
use retroglyph_core::layout::HAlign;
use retroglyph_core::{Rect, Style, Terminal};
let mut term = Terminal::new(Headless::new(6, 1));
term.draw(|s| {
s.print_aligned(Rect::new(0, 0, 6, 1), "hi", HAlign::Center, Style::default())
})
.unwrap();
// "hi" is 2 columns wide in a 6-column rect: (6 - 2) / 2 == 2 columns of left padding.
assert_eq!(term.backend().format_view(), "··hi··\n");Sourcepub fn fill_rect(&mut self, rect: Rect<u16>, ch: char, style: Style)
pub fn fill_rect(&mut self, rect: Rect<u16>, ch: char, style: Style)
Fill rect (clipped to this surface’s own area) with ch in style.
§Examples
use retroglyph_core::{Grid, Pos, Rect, Style, Surface};
let mut grid = Grid::new(4, 4);
let mut surface = Surface::new(&mut grid, Rect::new(0, 0, 4, 4), 0);
// `rect` extends well past the grid on both axes; only the cells inside the
// surface's own area are touched, the rest is silently clipped.
surface.fill_rect(Rect::new(2, 2, 10, 10), '#', Style::default());
assert_eq!(grid[Pos::new(3, 3)].glyph(), '#');
assert_eq!(grid[Pos::new(0, 0)].glyph(), ' ');Sourcepub fn put_span<S>(
&mut self,
pos: impl Into<Pos<u16>>,
rows: &[S],
style: Style,
) -> Option<()>
pub fn put_span<S>( &mut self, pos: impl Into<Pos<u16>>, rows: &[S], style: Style, ) -> Option<()>
Writes a multi-cell span at pos on this surface’s layer in style: one piece of
artwork occupying a block of cells rather than one, the Surface twin of
Grid::write_span.
rows holds one string per row of the footprint. Its first character is the anchor
glyph, which a pixel backend looks up in its sprite cache; the rest are the span’s text
fallback, printed by cell backends and skipped by pixel backends. Any AsRef<str> row
works, so a literal footprint (&["[==]", "|__|"]) and a computed one (&Vec<String>)
both pass without a borrowing pass over the rows; for the uniform case, see
put_span_uniform.
See Grid::write_span for the full write semantics, and Grid::span_owner to
hit-test the whole footprint.
§style applies to the text fallback, not to the sprite
A sprite is composited from its own pixels. style.fg does not tint it;
style.bg is still painted behind it, so it shows through wherever the sprite is
transparent. Recoloring a shared sprite per cell is therefore not possible: draw a
variant of the artwork instead, which is the usual tileset idiom.
style is not dead on such a cell, because the same span drawn by a cell backend
renders the text fallback in it. The consequence is that fg reads very differently
depending on the backend, and that a glyph missing from the sprite cache silently falls
back to a font glyph that is fg-colored, which looks a lot like a tint working.
§Returns
Some(()) once the whole span is written, or None having written nothing at all when
rows is empty or ragged, either axis exceeds 255 cells, or the footprint does not fit
entirely within this surface’s own area (not just the grid) at pos. The surface has
strictly more ways to refuse a span than Grid::write_span does, so a sprite that did
not draw is answered here rather than in the backend.
Sourcepub fn put_span_uniform(
&mut self,
pos: impl Into<Pos<u16>>,
size: impl Into<Size>,
anchor: char,
fill: char,
style: Style,
) -> Option<()>
pub fn put_span_uniform( &mut self, pos: impl Into<Pos<u16>>, size: impl Into<Size>, anchor: char, fill: char, style: Style, ) -> Option<()>
Writes a size multi-cell span at pos on this surface’s layer in style: anchor in
the anchor cell, fill in every other cell of the footprint, the Surface twin of
Grid::write_span_uniform.
The uniform case of put_span, and what a sheet-driven renderer usually
wants: one sprite, chosen at runtime, with the cells it covers blanked so nothing shows
through its transparent pixels. fill is the text fallback a cell backend prints for
those covered cells, so ' ' blanks them and a visible character keeps the footprint
legible in a terminal.
style reads exactly as it does for put_span: it applies to the text
fallback, never to the sprite.
§Returns
Some(()) once the whole span is written, or None having written nothing at all when
either axis of size is 0 or exceeds 255 cells, or the footprint does not fit entirely
within this surface’s own area at pos.
§Examples
use retroglyph_core::{Grid, Rect, Style, Surface};
let mut grid = Grid::new(8, 4);
let mut surface = Surface::new(&mut grid, Rect::new(0, 0, 8, 4), 0);
// A 16x16 sprite over a 2x1 block of 8x16 cells, anchored at a runtime glyph.
let anchor = '\u{E000}';
surface.put_span_uniform((1, 1), (2, 1), anchor, ' ', Style::default())?;Sourcepub fn put_offset(
&mut self,
pos: impl Into<Pos<u16>>,
offset: impl Into<Offset>,
ch: char,
style: Style,
)
pub fn put_offset( &mut self, pos: impl Into<Pos<u16>>, offset: impl Into<Offset>, ch: char, style: Style, )
Place ch at pos with a sub-cell pixel offset, in style.
Sub-cell offsets are visual only: they do not affect grid logic or hit-testing.
Backends that cannot represent pixel offsets (e.g. CrosstermBackend) ignore them. A
no-op if pos is outside this surface’s area.
§Examples
use retroglyph_core::{Grid, Offset, Pos, Rect, Style, Surface};
let mut grid = Grid::new(4, 4);
let mut surface = Surface::new(&mut grid, Rect::new(0, 0, 4, 4), 0);
// A large offset still lands the glyph in cell (1, 1): the offset is a pixel nudge
// for a pixel backend, never a coordinate shift.
surface.put_offset((1, 1), Offset::new(12, -12), 'X', Style::default());
// Outside the surface's area: silently dropped, matching `put`.
surface.put_offset((10, 10), Offset::default(), 'X', Style::default());
assert_eq!(grid[Pos::new(1, 1)].glyph(), 'X');Sourcepub fn clear(&mut self)
pub fn clear(&mut self)
Clears this surface’s entire area (on its own layer) back to Tile::default.
Sourcepub fn clear_region(&mut self, rect: Rect<u16>)
pub fn clear_region(&mut self, rect: Rect<u16>)
Clears rect (clipped to this surface’s own area, on its own layer) back to
Tile::default.
§Examples
use retroglyph_core::{Grid, Pos, Rect, Style, Surface};
let mut grid = Grid::new(4, 4);
let mut surface = Surface::new(&mut grid, Rect::new(0, 0, 4, 4), 0);
surface.fill_rect(Rect::new(0, 0, 4, 4), '#', Style::default());
// `rect` extends past the surface's own area; only the overlap is cleared.
surface.clear_region(Rect::new(2, 2, 10, 10));
assert_eq!(grid[Pos::new(2, 2)].glyph(), ' ');
assert_eq!(grid[Pos::new(1, 1)].glyph(), '#');