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 an area and clipped to a clip rect.
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 relative to this surface’s own area origin, not to the underlying grid.
Surface::put/Surface::print/… take coordinates in that same local space, where
(0, 0) is area’s top-left corner, and silently drop any write that falls outside
Surface::clip_rect, matching the rest of the workspace’s clip-on-draw policy for
out-of-bounds drawing.
area and clip_rect answer two different questions. area is the region this surface
represents: what a widget lays itself out in, and what width/
height report. clip_rect is the subset of area that is actually
visible: what every write is bounds-checked against. The two start out equal (see
Surface::new) and diverge once Surface::clip or Surface::scope is used.
Surface::clip narrows what is visible without changing what this surface represents:
clip_rect is intersected with the given rect, area is untouched. Surface::scope does
both: area becomes the given rect and clip_rect is intersected with it, which is what a
widget’s own sub-surface needs when it should be laid out against a new rect but still bounded
by whatever was already visible. Both narrow monotonically: neither can widen clip_rect
beyond what the parent surface already allowed.
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/Surface::on_tier
rather than being restricted to the layer it was constructed with.
Implementations§
Source§impl Surface<'_>
impl Surface<'_>
Sourcepub fn put(&mut self, pos: impl Into<Pos>, ch: char, style: Style)
pub fn put(&mut self, pos: impl Into<Pos>, ch: char, style: Style)
Place ch at pos in style. A no-op if pos is outside this surface’s clip.
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::color::Style;
use retroglyph_core::grid::{Grid, Pos, Rect};
use retroglyph_core::surface::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 clip: 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). A coordinate that stays non-negative but exceeds u16::MAX after
this surface’s translate offset is subtracted is dropped the same way: it addresses a cell
this surface’s u16 grid space cannot name.
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::color::Style;
use retroglyph_core::grid::{Grid, Pos, Rect};
use retroglyph_core::surface::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 fill_rect(&mut self, rect: Rect, ch: char, style: Style)
pub fn fill_rect(&mut self, rect: Rect, ch: char, style: Style)
Fill rect (clipped to this surface’s own clip) with ch in style.
rect is local to this surface’s own area: (0, 0) is area’s own
top-left, not the grid’s, the same convention clear_region and
print_aligned use for their own rect (not absolute grid
coordinates, the convention clip/scope use).
§Examples
use retroglyph_core::color::Style;
use retroglyph_core::grid::{Grid, Pos, Rect};
use retroglyph_core::surface::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 clip 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 blit(&mut self, grid: &Grid, x: u16, y: u16)
pub fn blit(&mut self, grid: &Grid, x: u16, y: u16)
Stamps grid’s layer 0 onto this surface’s own layer, with its top-left cell at (x, y)
(local to this surface’s area, matching put’s convention), clipped to this
surface’s clip.
Always reads grid’s layer 0, regardless of which layer this surface itself is currently
writing to: grid is typically a standalone buffer composed elsewhere (e.g.
BoxStyle::render‘s output, or retroglyph-ui’ join_h/join_v), and per their own
docs those only ever populate layer 0. Reading this surface’s own layer off grid instead
(what Grid::blit’s single layer parameter would do if called directly) finds nothing
there whenever this surface isn’t on layer 0, and the copy silently does nothing.
Unlike a single-cell put, a write that starts outside this surface’s clip is
not necessarily dropped whole: the part of grid that does land inside the clip is copied,
matching fill_rect’s per-cell clipping rather than
put_span’s all-or-nothing footprint check, since grid is arbitrary
composed content rather than one indivisible sprite.
Unlike put and the rest of this surface’s single-sprite writes, this does
not apply with_tint’s tint: a tint lands on one sprite’s anchor cell,
and grid is arbitrary composed content with no single anchor to land it on, the same
reason Grid::blit_cross_layer (this method’s own cross-layer copy, internal to Grid)
carries no tint either. A tinted surface’s blit copies grid through unchanged.
§Examples
use retroglyph_core::color::Style;
use retroglyph_core::grid::{Grid, Rect};
use retroglyph_core::surface::{Layer, Surface};
use retroglyph_core::tile::Tile;
let mut src = Grid::new(2, 2);
src.put_tile(0, (0, 0), Tile::new('x', Style::default()));
let mut dst = Grid::new(4, 4);
let mut surface = Surface::new(&mut dst, Rect::new(0, 0, 4, 4), Layer::World.as_u8());
// `surface` is on the overlay tier; `src` only ever has layer 0, but `blit` reads that
// layer regardless, so the copy still lands (unlike `Grid::blit(surface.layer(), ...)`).
surface.on_tier(Layer::Overlay).blit(&src, 1, 1);
assert_eq!(dst.tile(Layer::Overlay.as_u8(), (1, 1)).map(Tile::glyph), Some('x'));Sourcepub fn clear(&mut self)
pub fn clear(&mut self)
Clears this surface’s own area, intersected with its clip (on its own layer), back to
Tile::default.
Sourcepub fn clear_region(&mut self, rect: Rect)
pub fn clear_region(&mut self, rect: Rect)
Clears rect (clipped to this surface’s own clip, on its own layer) back to
Tile::default.
rect is local to this surface’s own area, the same convention
fill_rect and print_aligned use for their
own rect (not absolute grid coordinates, the convention
clip/scope use).
§Examples
use retroglyph_core::color::Style;
use retroglyph_core::grid::{Grid, Pos, Rect};
use retroglyph_core::surface::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 clip; 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(), '#');Source§impl Surface<'_>
impl Surface<'_>
Sourcepub fn put_span<S: AsRef<str>>(
&mut self,
pos: impl Into<Pos>,
rows: &[S],
style: Style,
) -> Option<()>
pub fn put_span<S: AsRef<str>>( &mut self, pos: impl Into<Pos>, 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 clip (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>,
size: impl Into<Size>,
anchor: char,
fill: char,
style: Style,
) -> Option<()>
pub fn put_span_uniform( &mut self, pos: impl Into<Pos>, 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 clip at pos.
§Examples
use retroglyph_core::color::Style;
use retroglyph_core::grid::{Grid, Rect};
use retroglyph_core::surface::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>,
offset: impl Into<Offset>,
ch: char,
style: Style,
)
pub fn put_offset( &mut self, pos: impl Into<Pos>, 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 clip.
§Examples
use retroglyph_core::color::Style;
use retroglyph_core::grid::{Grid, Offset, Pos, Rect};
use retroglyph_core::surface::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 clip: silently dropped, matching `put`.
surface.put_offset((10, 10), Offset::default(), 'X', Style::default());
assert_eq!(grid[Pos::new(1, 1)].glyph(), 'X');Source§impl Surface<'_>
impl Surface<'_>
Sourcepub fn print(&mut self, pos: impl Into<Pos>, text: &str, style: Style)
pub fn print(&mut self, pos: impl Into<Pos>, 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 clip wraps to the next row at the original column; cells outside the clip
(either axis) are dropped. 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::color::Style;
use retroglyph_core::terminal::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>, line: &Line)
pub fn print_line(&mut self, pos: impl Into<Pos>, 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 clip.
§Examples
use retroglyph_core::backend::Headless;
use retroglyph_core::text::{Line, Span};
use retroglyph_core::terminal::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,
text: &str,
align: HAlign,
style: Style,
)
pub fn print_aligned( &mut self, rect: Rect, text: &str, align: HAlign, style: Style, )
print, horizontally aligned within rect (clipped to this surface’s own
clip) and measured in display columns (via unicode_width), not bytes.
rect is local to this surface’s own area, the same convention as
fill_rect and clear_region (not absolute grid
coordinates, the convention clip/scope use for their own
rect): (0, 0) is area’s own top-left, so a widget’s own area().at_origin() can be
passed straight in.
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).
Not gated behind the egc feature: unlike TextLayout, this needs nothing from it, so
it’s reachable from any crate that only measures with unicode-width, including
retroglyph-ui without opting into egc.
§Examples
use retroglyph_core::backend::Headless;
use retroglyph_core::layout::HAlign;
use retroglyph_core::color::Style;
use retroglyph_core::grid::Rect;
use retroglyph_core::terminal::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§impl<'a> Surface<'a>
impl<'a> Surface<'a>
Sourcepub const fn area(&self) -> Rect
pub const fn area(&self) -> Rect
The region this surface represents, e.g. for a widget to lay itself out in.
Unlike clip_rect, this is never narrowed by clip: it
only changes when scope sets a new one. A widget that reads its own area
off the surface after being clipped (e.g. while partially offscreen) sees the region it
was given, not the visible sliver of it, so it can still center itself correctly and let
the clip take care of what actually lands.
Every drawing method on this surface (put, print,
fill_rect, …) takes coordinates local to this surface, where
(0, 0) is this area’s own top-left corner, not the underlying grid’s. area() itself is
absolute grid space, so surface.put((surface.area().left(), ...), ...) only lands
correctly for a surface whose area happens to start at the grid origin; anywhere else it
silently misses. A widget that wants to place itself relative to its own bounds (e.g. a
label in a corner) should reach for area().at_origin(), or just width/
height directly, and never for area()’s own left/
top.
§Examples
use retroglyph_core::grid::{Grid, Rect};
use retroglyph_core::surface::Surface;
let mut grid = Grid::new(10, 10);
let mut surface = Surface::new(&mut grid, Rect::new(0, 0, 10, 10), 0);
let mut scoped = surface.scope(Rect::new(3, 3, 4, 4));
assert_eq!(scoped.area(), Rect::new(3, 3, 4, 4));
assert_eq!(scoped.area().at_origin(), Rect::new(0, 0, 4, 4));Sourcepub const fn clip_rect(&self) -> Rect
pub const fn clip_rect(&self) -> Rect
The visible subset of area. Every write this surface accepts is
bounds-checked against this rect, not area.
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, area, and clip, but writing to layer instead.
Sourcepub const fn on_tier(&mut self, tier: Layer) -> Surface<'_>
pub const fn on_tier(&mut self, tier: Layer) -> Surface<'_>
Equivalent to self.on_layer(tier.as_u8()), for switching to one of
the workspace’s named Layer tiers instead of a raw layer id. See Layer’s docs for
when to reach for this over a numeric Surface::on_layer call.
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 origin(&self) -> (i32, i32)
pub const fn origin(&self) -> (i32, i32)
The offset translate has accumulated on this surface, (0, 0) if it
has never been called.
Every coordinate a caller passes to a coordinate-taking method has this subtracted from it
before the usual bounds check (see translate’s doc), so a callee
handed a &mut Surface can use this to tell whether it is in a translated coordinate
space, compose a further offset relative to the current one without over- or
undershooting, or convert a local coordinate it read back off the surface into the
caller’s own coordinate space by adding this back in.
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. blit has no such anchor (grid is arbitrary
composed content, not one sprite) and does not apply this tint at all; see its own doc.
§Examples
use retroglyph_core::color::{Style, Tint};
use retroglyph_core::grid::{Grid, Rect};
use retroglyph_core::surface::Surface;
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, rect: Rect) -> Surface<'_>
pub fn clip(&mut self, rect: Rect) -> Surface<'_>
A new surface over the same grid, layer, and area, whose
clip_rect is narrowed to rect intersected with this surface’s own
clip. What this surface represents is unchanged; only what is visible shrinks.
rect is in absolute grid coordinates (it intersects clip_rect,
itself absolute), not local to this surface’s own area the way
fill_rect, clear_region, and
print_aligned’s own rect are. Coordinates are otherwise
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 the clip 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 draw into, never widen it.
Clipping is also how the clip-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 clip and is dropped, which is what a single-line bar wants.put_spanandput_span_uniformrefuse a footprint that leaves the clip. Clipped to a content rect, “fits” stops meaning “fits the screen” and starts meaning “does not reserve cells in the status bar below”.
A sub-surface that should instead represent rect (e.g. a widget’s own region, laid out
and centered against rect rather than the parent’s wider area) wants
scope, not this.
§Examples
use retroglyph_core::color::Style;
use retroglyph_core::grid::{Grid, Pos, Rect};
use retroglyph_core::surface::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 fn scope(&mut self, rect: Rect) -> Surface<'_>
pub fn scope(&mut self, rect: Rect) -> Surface<'_>
A new surface over the same grid and layer, that represents rect: its
area becomes rect, and its clip_rect is narrowed to
rect intersected with this surface’s own clip.
This is the primitive a widget’s own region is built from: a sub-widget laid out against
rect should center, align, and measure itself against rect (via area),
while still being unable to draw outside whatever was already visible in the parent. A
clip alone cannot do this, because clip leaves area untouched; scope
is what a caller reaches for when handing a sub-rect down to something that is going to
read that rect back off the surface.
Like clip, the clip narrows monotonically: a rect that reaches outside
this surface’s own clip only ever tightens what the returned surface can draw into, never
widens it, even though area itself becomes exactly rect.
§Examples
use retroglyph_core::grid::{Grid, Rect};
use retroglyph_core::surface::Surface;
let mut grid = Grid::new(8, 4);
let mut surface = Surface::new(&mut grid, Rect::new(0, 0, 8, 4), 0);
let mut clipped = surface.clip(Rect::new(0, 0, 4, 4));
// `scope` widens `area` to a rect the parent's clip does not fully cover...
let scoped = clipped.scope(Rect::new(2, 0, 4, 4));
assert_eq!(scoped.area(), Rect::new(2, 0, 4, 4));
// ...but the visible region still cannot exceed the parent's own clip.
assert_eq!(scoped.clip_rect(), Rect::new(2, 0, 2, 4));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 or clip_rect, so both, along
with 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 or what is visible in it. This composes with
scope the same order it is called in: scope(...).translate(...) first
narrows the area and clip, 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.
The offset accumulates with saturating arithmetic: chaining enough translate calls in one
direction clamps origin at i32::MIN/i32::MAX instead of wrapping or panicking. A
coordinate that only lands after an offset larger than that was never addressable in this
surface’s u16 grid space to begin with.
§Examples
use retroglyph_core::color::Style;
use retroglyph_core::grid::{Grid, Pos, Rect};
use retroglyph_core::surface::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 scoped = surface.scope(Rect::new(5, 5, 4, 4));
let mut view = scoped.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 fn clip_translate(&mut self, area: Rect, origin: (i32, i32)) -> Surface<'_>
pub fn clip_translate(&mut self, area: Rect, origin: (i32, i32)) -> Surface<'_>
clip to area, then translate by origin, in one
call – except that unlike plain clip, the returned surface’s
area is area intersected with this surface’s own area, not area
verbatim.
Chaining clip(...).translate(...) directly works when the result is used right where
it’s produced (both clip and translate return a Surface<'_> borrowing the previous
step for exactly that call), but a helper that hands the composed view back to its own
caller (for example a scrolling-camera widget’s own surface method) needs the two
narrowings applied against a single &mut self borrow instead, so the returned surface
can outlive the call. This does that.
This intersects area with this surface’s own area rather than replacing it the way
scope does, so area/width/
height on the result can report something smaller than the area
argument. A scrolling-camera widget’s surface method relies on exactly this: when the
world is smaller than the viewport, it hands in a viewport-sized area and depends on the
intersection to shrink it back down to the world’s own size. A caller that wants area
to become exactly its argument, even reaching outside the parent’s current area, should
use scope followed by translate instead.
The offset accumulates with saturating arithmetic, as in translate.
§Examples
use retroglyph_core::color::Style;
use retroglyph_core::grid::{Grid, Pos, Rect};
use retroglyph_core::surface::Surface;
let mut grid = Grid::new(10, 10);
let mut surface = Surface::new(&mut grid, Rect::new(0, 0, 10, 10), 0);
let mut view = surface.clip_translate(Rect::new(5, 5, 4, 4), (-5, -5));
assert_eq!(view.area(), Rect::new(5, 5, 4, 4));
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 and scope narrow a surface without handing out the
unclipped grid to do it.