Skip to main content

Camera

Struct Camera 

Source
pub struct Camera { /* private fields */ }
Expand description

A rectangular viewport onto a larger world, with world/screen conversions.

Implementations§

Source§

impl Camera

Source

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

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.

Source

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

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

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

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

pub fn cells(&self) -> impl Iterator<Item = (Pos, Pos)>

Iterate the visible cells as (world, screen) position pairs, in row-major order. Only cells that exist in the world are yielded, so the caller can fill the rest of the viewport with a background.

Source§

impl Camera

Source

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.

Source

pub const fn viewport(&self) -> Rect

The screen rectangle the world is drawn into.

Source

pub const fn world(&self) -> Size

The world dimensions.

Source

pub const fn origin(&self) -> Pos

The world cell shown at the viewport’s top-left corner.

Source

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.

Source

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

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

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.

Source

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

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§

Source§

impl Clone for Camera

Source§

fn clone(&self) -> Camera

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Camera

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl PartialEq for Camera

Source§

fn eq(&self, other: &Camera) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Copy for Camera

Source§

impl Eq for Camera

Source§

impl StructuralPartialEq for Camera

Auto Trait Implementations§

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> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. 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> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
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.
§

impl<C> WithAlpha for C
where C: Copy,

§

fn with_alpha_first<A>(self, alpha: A) -> AlphaFirst<A, Self>

Wraps self with alpha, storing alpha before the color in memory (see [AlphaFirst]).
§

fn with_alpha_last<A>(self, alpha: A) -> AlphaLast<A, Self>

Wraps self with alpha, storing alpha after the color in memory (see [AlphaLast]).