Skip to main content

Surface

Struct Surface 

Source
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>

Source

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.

Source

pub const fn area(&self) -> Rect<u16>

The area this surface clips writes to.

Source

pub const fn width(&self) -> u16

The width of this surface’s area, in columns.

Source

pub const fn height(&self) -> u16

The height of this surface’s area, in rows.

Source

pub const fn layer(&self) -> u8

The grid layer this surface writes to.

Source

pub const fn on_layer(&mut self, layer: u8) -> Surface<'_>

A new surface over the same grid and area, but writing to layer instead.

Source

pub const fn tint(&self) -> Tint

The tint every sprite drawn through this surface is recoloured by.

Source

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));
Source

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:

  • print wraps 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_span and put_span_uniform refuse 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(), ' ');
Source

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');
Source

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.

Source

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.

Source

pub const fn grid(&self) -> &Grid

Read-only counterpart of grid_mut.

Source

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(' '));
Source

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);
Source

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');
Source

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(), ' ');
Source

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",
);
Source

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");
Source

pub fn print_aligned( &mut self, rect: Rect<u16>, text: &str, align: HAlign, style: Style, )

Available on crate feature 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");
Source

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(), ' ');
Source

pub fn put_span<S>( &mut self, pos: impl Into<Pos<u16>>, rows: &[S], style: Style, ) -> Option<()>
where S: AsRef<str>,

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.

Source

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())?;
Source

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');
Source

pub fn clear(&mut self)

Clears this surface’s entire area (on its own layer) back to Tile::default.

Source

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(), '#');

Auto Trait Implementations§

§

impl<'a> !UnwindSafe for Surface<'a>

§

impl<'a> Freeze for Surface<'a>

§

impl<'a> RefUnwindSafe for Surface<'a>

§

impl<'a> Send for Surface<'a>

§

impl<'a> Sync for Surface<'a>

§

impl<'a> Unpin for Surface<'a>

§

impl<'a> UnsafeUnpin for Surface<'a>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.