pub struct Camera { /* private fields */ }Expand description
A rectangular viewport onto a larger world, with world/screen conversions.
Implementations§
Source§impl Camera
impl Camera
Sourcepub fn visible_bounds(&self) -> Rect
pub fn visible_bounds(&self) -> Rect
The world rectangle currently visible, clamped to world bounds.
Never panics: the clamp against world uses
saturating_sub, so it cannot underflow even if origin is
somehow past world’s edge.
§Examples
use retroglyph_core::grid::{Pos, Rect, Size};
use retroglyph_ui::Camera;
// A 10x10 viewport near the bottom-right corner of a 12x12 world: the origin clamps
// to (2, 2), so the visible rect is narrower than the viewport rather than reading
// past the world edge.
let mut cam = Camera::new(Rect::new(0, 0, 10, 10), Size::new(12, 12));
cam.center_on(Pos::new(11, 11));
assert_eq!(cam.origin(), Pos::new(2, 2));
assert_eq!(cam.visible_bounds(), Rect::new(2, 2, 10, 10));
// A world smaller than the viewport: the visible rect is the whole world, not the
// full viewport size.
let small = Camera::new(Rect::new(0, 0, 20, 20), Size::new(5, 5));
assert_eq!(small.visible_bounds(), Rect::new(0, 0, 5, 5));Sourcepub const fn world_to_screen(&self, world: Pos) -> Option<Pos>
pub const fn world_to_screen(&self, world: Pos) -> Option<Pos>
Map a world position to its screen position, or None if it is outside
visible_bounds: the viewport, clamped to the world.
Sourcepub const fn world_to_offset(&self, world: Pos) -> (i32, i32)
pub const fn world_to_offset(&self, world: Pos) -> (i32, i32)
Map a world position to its screen position, without culling: the result may fall
outside the viewport (negative, or past its far edge) instead of coming back None.
world_to_screen is the right call when the only question is
“is this single cell visible” (a minimap dot, a cursor). It falls short for anything
wider than one cell (a hex, an iso diamond, a multi-cell sprite) where the anchor
can be off-viewport while part of the content is still visible. This is the signed
sibling for that case: it hands back the same math world_to_screen computes, minus the
culling, ready for Surface::put_signed to clip.
§Examples
use retroglyph_core::grid::{Pos, Rect, Size};
use retroglyph_ui::Camera;
let mut cam = Camera::new(Rect::new(0, 0, 10, 10), Size::new(100, 100));
cam.center_on(Pos::new(50, 50));
// Inside the viewport: matches `world_to_screen`.
assert_eq!(cam.world_to_offset(Pos::new(50, 50)), (5, 5));
// A multi-cell sprite's top-left anchor two cells left of the viewport: negative, not
// `None`, so a caller can still hand this to `Surface::put_signed` and let the visible
// half draw.
assert_eq!(cam.world_to_offset(Pos::new(43, 50)), (-2, 5));Sourcepub fn surface<'a>(&self, surface: &'a mut Surface<'_>) -> Surface<'a>
pub fn surface<'a>(&self, surface: &'a mut Surface<'_>) -> Surface<'a>
A view of surface in this camera’s world coordinate space, clipped to
visible_bounds: Surface::clip_translate to the visible
rect, by origin.
The returned surface’s put, put_signed, print, and the rest of Surface’s
coordinate-taking methods all take world coordinates directly, and anything that lands
outside visible_bounds (including a multi-cell draw anchored off-screen, or - for a
world smaller than the viewport - the dead margin past the world edge) is dropped by the
surface’s own bounds check, the same way world_to_offset composes with
Surface::put_signed by hand. This is that composition done once instead of at every
call site.
Clipping to visible_bounds rather than viewport directly matches
world_to_screen and screen_to_world:
a world smaller than the viewport (under plain set_viewport, not
set_viewport_fitted) shrinks the clip to the world’s size
instead of leaving the viewport’s dead margin drawable.
§Examples
use retroglyph_core::color::Style;
use retroglyph_core::grid::{Grid, Pos, Rect, Size};
use retroglyph_core::surface::Surface;
use retroglyph_ui::Camera;
let mut grid = Grid::new(20, 20);
let mut root = Surface::new(&mut grid, Rect::new(0, 0, 20, 20), 0);
let mut cam = Camera::new(Rect::new(5, 5, 10, 10), Size::new(100, 100));
cam.center_on(Pos::new(50, 50));
let mut world = cam.surface(&mut root);
// Drawn in world coordinates: (50, 50) is the centered target, landing at the
// viewport's center cell (10, 10) in grid space.
world.put(Pos::new(50, 50), '@', Style::default());
// A world position outside the viewport is dropped, not a panic or a manual guard.
world.put(Pos::new(0, 0), 'X', Style::default());
assert_eq!(grid[Pos::new(10, 10)].glyph(), '@');A world smaller than the viewport: drawing into the dead margin past the world edge is dropped, not written past the world into unused grid cells.
use retroglyph_core::color::Style;
use retroglyph_core::grid::{Grid, Pos, Rect, Size};
use retroglyph_core::surface::Surface;
use retroglyph_ui::Camera;
let mut grid = Grid::new(20, 20);
let mut root = Surface::new(&mut grid, Rect::new(0, 0, 20, 20), 0);
// A 20x20 viewport over a 5x5 world: `visible_bounds` is only 5x5, not the full
// viewport, so the clip shrinks to match.
let cam = Camera::new(Rect::new(0, 0, 20, 20), Size::new(5, 5));
let mut world = cam.surface(&mut root);
world.put(Pos::new(0, 0), '@', Style::default());
// Inside the viewport but past the (smaller) world's edge: dropped.
world.put(Pos::new(10, 10), 'X', Style::default());
assert_eq!(grid[Pos::new(0, 0)].glyph(), '@');
assert_eq!(grid[Pos::new(10, 10)].glyph(), ' ');Sourcepub fn screen_to_world(&self, screen: Pos) -> Option<Pos>
pub fn screen_to_world(&self, screen: Pos) -> Option<Pos>
Map a screen position back to a world position, or None if it is
outside the viewport or beyond the world (useful for mouse picking).
§Examples
use retroglyph_core::grid::{Pos, Rect, Size};
use retroglyph_ui::Camera;
let mut cam = Camera::new(Rect::new(5, 5, 10, 10), Size::new(100, 100));
cam.center_on(Pos::new(50, 50));
// Inside the viewport: maps back to the world cell under it.
assert_eq!(cam.screen_to_world(Pos::new(5, 5)), Some(Pos::new(45, 45)));
// Off the viewport entirely (the viewport starts at x = 5): `None`, not a clamp.
assert_eq!(cam.screen_to_world(Pos::new(0, 0)), None);Sourcepub const fn screen_to_world_signed(&self, screen: Pos) -> (i32, i32)
pub const fn screen_to_world_signed(&self, screen: Pos) -> (i32, i32)
Map a screen position back to a world position, without culling: the result may fall
outside the viewport or outside [0, world), instead of coming back None.
screen_to_world is the right call when the only question is
“which world cell is under this screen position” (mouse picking, a single-cell cursor).
It falls short once a gesture can leave the viewport or the world mid-flight: a pointer
drag that overshoots the edge, or a rubber-band selection rect that extends past it, has
no Pos to report and no way to compute a world-space delta. This is the signed sibling
for that case: it hands back the same math screen_to_world computes, minus the culling.
§Examples
use retroglyph_core::grid::{Pos, Rect, Size};
use retroglyph_ui::Camera;
let mut cam = Camera::new(Rect::new(5, 5, 10, 10), Size::new(100, 100));
cam.center_on(Pos::new(50, 50));
// Inside the viewport: matches `screen_to_world`.
assert_eq!(cam.screen_to_world_signed(Pos::new(5, 5)), (45, 45));
// Off the viewport entirely (the viewport starts at x = 5): negative, not `None`, so a
// caller mid-drag can still compute a world-space delta.
assert_eq!(cam.screen_to_world_signed(Pos::new(0, 0)), (40, 40));Source§impl Camera
impl Camera
Sourcepub const fn new(viewport: Rect, world: Size) -> Self
pub const fn new(viewport: Rect, world: Size) -> Self
Create a camera drawing into viewport (screen cells) over a world of
world cells. The initial origin is (0, 0); call
center_on to follow a target.
Sourcepub fn set_viewport(&mut self, viewport: Rect)
pub fn set_viewport(&mut self, viewport: Rect)
Replace the viewport (for example after a terminal resize), keeping the world unchanged and re-clamping the origin so it stays in bounds.
Never panics: a viewport larger than world re-clamps the origin to (0, 0) via
saturating_sub rather than underflowing.
Sourcepub fn set_world(&mut self, world: Size)
pub fn set_world(&mut self, world: Size)
Replace the world dimensions (for example when a level changes), keeping the viewport unchanged and re-clamping the origin so it stays in bounds.
If the camera was last positioned with
set_viewport_fitted, this does not re-run that letterboxing
against the new world; call set_viewport_fitted again afterward if the new world may be
smaller than the viewport on either axis.
Never panics: the re-clamp uses the same saturating arithmetic as
set_viewport.
§Examples
use retroglyph_core::grid::{Pos, Rect, Size};
use retroglyph_ui::Camera;
let mut cam = Camera::new(Rect::new(0, 0, 10, 10), Size::new(100, 100));
cam.center_on(Pos::new(50, 50));
assert_eq!(cam.origin(), Pos::new(45, 45));
// Shrinking the world re-clamps the origin so it stays in bounds.
cam.set_world(Size::new(20, 20));
assert_eq!(cam.world(), Size::new(20, 20));
assert_eq!(cam.origin(), Pos::new(10, 10));Sourcepub fn set_viewport_fitted(&mut self, viewport: Rect)
pub fn set_viewport_fitted(&mut self, viewport: Rect)
Replace the viewport like set_viewport, but shrink it to the
world’s size on any axis where the world is smaller, and center the shrunk rect within
viewport rather than pinning it to the top-left.
A viewport at least as large as the world on both axes lands exactly on the world with no
slack, so origin is (0, 0) and viewport reports that centered
rect, not viewport itself; hit-testing via screen_to_world
therefore only recognizes screen positions actually over the world, not the letterboxed
margin. This is the fix for the pinned-to-the-corner behaviour
set_viewport has for a world smaller than the viewport: a fixed
board, a generated map of fixed dimensions, or a minimap drawn into a terminal whose size
the app does not control.
Odd leftover slack rounds down, the same way center_on rounds: any
extra cell of margin lands on the right or bottom, not the left or top.
Never panics: all arithmetic is saturating, so a viewport narrower than it is tall (or
vice versa) relative to world cannot underflow.
§Examples
use retroglyph_core::grid::{Pos, Rect, Size};
use retroglyph_ui::Camera;
// A 20x20 viewport at (2, 2) over a 5x5 world: the effective viewport shrinks to 5x5
// and centers within the given rect, instead of pinning to (2, 2).
let mut cam = Camera::new(Rect::new(0, 0, 1, 1), Size::new(5, 5));
cam.set_viewport_fitted(Rect::new(2, 2, 20, 20));
assert_eq!(cam.viewport(), Rect::new(9, 9, 5, 5));
assert_eq!(cam.origin(), Pos::new(0, 0));
// A viewport already no larger than the world on both axes behaves like `set_viewport`:
// no shrinking, no centering.
let mut cam = Camera::new(Rect::new(0, 0, 1, 1), Size::new(100, 100));
cam.set_viewport_fitted(Rect::new(0, 0, 10, 10));
assert_eq!(cam.viewport(), Rect::new(0, 0, 10, 10));Sourcepub fn center_on(&mut self, target: Pos)
pub fn center_on(&mut self, target: Pos)
Center the view on target (world coords), clamped to the world edges so
the viewport never scrolls past [0, world).
Never panics, even for a target outside [0, world): the offset and clamp are both
computed with saturating arithmetic.
Sourcepub fn set_origin(&mut self, origin: Pos)
pub fn set_origin(&mut self, origin: Pos)
Set the top-left world cell directly, clamped to the world edges so origin never
scrolls past [0, world), the same invariant center_on maintains.
This is the primitive center_on and scroll_by
both clamp through, and what a save/restore of camera state needs: origin
is otherwise read-only.
Never panics: the clamp is [Rect::clamp_within], which uses
saturating_sub internally, so it cannot underflow even for a
viewport larger than world.
§Examples
use retroglyph_core::grid::{Pos, Rect, Size};
use retroglyph_ui::Camera;
let mut cam = Camera::new(Rect::new(0, 0, 10, 10), Size::new(100, 100));
cam.set_origin(Pos::new(50, 50));
assert_eq!(cam.origin(), Pos::new(50, 50));
// Clamped to `world - viewport`, same as `center_on`.
cam.set_origin(Pos::new(200, 200));
assert_eq!(cam.origin(), Pos::new(90, 90));Sourcepub fn scroll_by(&mut self, dx: i32, dy: i32)
pub fn scroll_by(&mut self, dx: i32, dy: i32)
Scroll the view by a signed cell delta, clamped to the world edges like
set_origin.
This is the method a drag or a scroll wheel wants: unlike center_on,
which reinterprets its argument as a new target to center on, scroll_by moves origin
directly, so there is exactly one clamp between the input delta and the visible result.
A caller that instead clamps its own running “center” position to [0, world) and feeds
it through center_on every frame is clamping against a wider range than center_on’s
own [0, world - viewport], which leaves slack: dragging past an edge no longer moves
the origin, but the caller’s tracked position keeps moving, so dragging back “sticks”
until it works through that slack before the view responds again.
Never panics: the delta is applied in i32 and saturates at 0 or u16::MAX before the
world-edge clamp in set_origin runs, so neither a very large
negative nor positive dx/dy can overflow or underflow u16.
§Examples
use retroglyph_core::grid::{Pos, Rect, Size};
use retroglyph_ui::Camera;
let mut cam = Camera::new(Rect::new(0, 0, 10, 10), Size::new(100, 100));
cam.scroll_by(5, 3);
assert_eq!(cam.origin(), Pos::new(5, 3));
// Clamped at the world edge, same as `center_on`: no negative or past-`world` origin.
cam.scroll_by(-100, -100);
assert_eq!(cam.origin(), Pos::new(0, 0));Trait Implementations§
impl Copy for Camera
impl Eq for Camera
impl StructuralPartialEq for Camera
Auto Trait Implementations§
impl Freeze for Camera
impl RefUnwindSafe for Camera
impl Send for Camera
impl Sync for Camera
impl Unpin for Camera
impl UnsafeUnpin for Camera
impl UnwindSafe for Camera
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§impl<C> WithAlpha for Cwhere
C: Copy,
impl<C> WithAlpha for Cwhere
C: Copy,
§fn with_alpha_first<A>(self, alpha: A) -> AlphaFirst<A, Self>
fn with_alpha_first<A>(self, alpha: A) -> AlphaFirst<A, Self>
self with alpha, storing alpha before the color in memory
(see [AlphaFirst]).§fn with_alpha_last<A>(self, alpha: A) -> AlphaLast<A, Self>
fn with_alpha_last<A>(self, alpha: A) -> AlphaLast<A, Self>
self with alpha, storing alpha after the color in memory
(see [AlphaLast]).