Skip to main content

LedLayout

Struct LedLayout 

Source
pub struct LedLayout<const N: usize, const W: usize, const H: usize> { /* private fields */ }
Expand description

Compile-time description of panel geometry and wiring, including dimensions (with examples).

LedLayout defines how a rectangular (x, y) panel of LEDs maps to the linear wiring order of LEDs on a NeoPixel-style (WS2812) panel.

For examples of LedLayout in use, see the led2d module, Frame2d, and the example below.

What LedLayout does:

  • Lets you describe panel wiring once
  • Enables drawing text, graphics, and animations in (x, y) space
  • Hides LED strip order from rendering code

Coordinates use a screen-style convention:

  • (0, 0) is the top-left corner
  • x increases to the right
  • y increases downward

Most users should start with one of the constructors below and then apply transforms (rotate_cw, flip_h, combine_v, etc.) as needed.

§Constructing layouts

Prefer the built-in constructors when possible:

For unusual wiring, you can construct a layout directly with LedLayout::new by listing (x, y) for each LED in the order the strip is wired.

The example below shows both construction methods. Also, the documentation for every constructor and method includes illustrations of use.

§Transforming layouts

You can adapt a layout without rewriting it:

§Validation

Layouts are validated at compile time:

  • coordinates must be in-bounds
  • every (x, y) cell must appear exactly once

If you want the final mapping, use index_to_xy.

§Example

Rotate a serpentine-wired 3×2 panel into a 2×3 layout and verify the result at compile time:

use device_envoy::led2d::layout::LedLayout;

const ROTATED: LedLayout<6, 2, 3> = LedLayout::serpentine_column_major().rotate_cw();
const EXPECTED: LedLayout<6, 2, 3> =
    LedLayout::new([(1, 0), (0, 0), (0, 1), (1, 1), (1, 2), (0, 2)]);
const _: () = assert!(ROTATED.equals(&EXPECTED)); // Compile-time assert
Serpentine 3×2 rotated to 2×3:

  Before:              After:
    LED0  LED3  LED4     LED1  LED0
    LED1  LED2  LED5     LED2  LED3
                         LED5  LED4

Implementations§

Source§

impl<const N: usize, const W: usize, const H: usize> LedLayout<N, W, H>

Source

pub const fn index_to_xy(&self) -> &[(u16, u16); N]

Return the array mapping LED wiring order to (x, y) coordinates.

Source

pub const fn width(&self) -> usize

The width of the layout.

Source

pub const fn height(&self) -> usize

The height of the layout.

Source

pub const fn len(&self) -> usize

Total LEDs in this layout (width × height).

Source

pub const fn equals(&self, other: &Self) -> bool

Const equality helper for doctests/examples.

use device_envoy::led2d::layout::LedLayout;

const LINEAR: LedLayout<4, 4, 1> = LedLayout::linear_h();
const ROTATED: LedLayout<4, 4, 1> = LedLayout::linear_v().rotate_cw();

const _: () = assert!(LINEAR.equals(&LINEAR));   // assert equal
const _: () = assert!(!LINEAR.equals(&ROTATED)); // assert not equal
LINEAR:  LED0  LED1  LED2  LED3
ROTATED: LED3  LED2  LED1  LED0
Source

pub const fn new(map: [(u16, u16); N]) -> Self

Construct a LedLayout by explicitly specifying the wiring order.

Use this constructor when your panel wiring does not match one of the built-in patterns (linear, serpentine, etc.). You provide the (x, y) coordinate for each LED in strip order, and LedLayout derives the panel geometry from that mapping.

This constructor is const and is intended to be used in a const definition, so layout errors are caught at compile time, not at runtime.

use device_envoy::led2d::layout::LedLayout;

// 3×2 panel (landscape, W×H)
const MAP: LedLayout<6, 3, 2> =
    LedLayout::new([(0, 0), (1, 0), (2, 0), (2, 1), (1, 1), (0, 1)]);

// Rotate to portrait (CW)
const ROTATED: LedLayout<6, 2, 3> = MAP.rotate_cw();

// Expected: 2×3 panel (W×H)
const EXPECTED: LedLayout<6, 2, 3> =
    LedLayout::new([(1, 0), (1, 1), (1, 2), (0, 2), (0, 1), (0, 0)]);

const _: () = assert!(ROTATED.equals(&EXPECTED));
3×2 input (col,row by LED index):
  LED0  LED1  LED2
  LED5  LED4  LED3

After rotate to 2×3:
  LED1  LED0
  LED2  LED3
  LED5  LED4
Source

pub const fn linear_h() -> Self

Linear row-major mapping for a single-row strip (cols increase left-to-right).

use device_envoy::led2d::layout::LedLayout;

const LINEAR: LedLayout<6, 6, 1> = LedLayout::linear_h();
const EXPECTED: LedLayout<6, 6, 1> =
    LedLayout::new([(0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (5, 0)]);
const _: () = assert!(LINEAR.equals(&EXPECTED));
6×1 strip maps to single row:
  LED0  LED1  LED2  LED3  LED4  LED5
Source

pub const fn linear_v() -> Self

Linear column-major mapping for a single-column strip (rows increase top-to-bottom).

use device_envoy::led2d::layout::LedLayout;

const LINEAR: LedLayout<6, 1, 6> = LedLayout::linear_v();
const EXPECTED: LedLayout<6, 1, 6> =
    LedLayout::new([(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (0, 5)]);
const _: () = assert!(LINEAR.equals(&EXPECTED));
1×6 strip maps to single column:
  LED0
  LED1
  LED2
  LED3
  LED4
  LED5
Source

pub const fn serpentine_column_major() -> Self

Serpentine column-major mapping returned as a checked LedLayout.

use device_envoy::led2d::layout::LedLayout;

const MAP: LedLayout<6, 3, 2> = LedLayout::serpentine_column_major();
const EXPECTED: LedLayout<6, 3, 2> =
    LedLayout::new([(0, 0), (0, 1), (1, 1), (1, 0), (2, 0), (2, 1)]);
const _: () = assert!(MAP.equals(&EXPECTED));
Strip snakes down columns (3×2 example):
  LED0  LED3  LED4
  LED1  LED2  LED5
Source

pub const fn serpentine_row_major() -> Self

Serpentine row-major mapping (alternating left-to-right and right-to-left across rows).

use device_envoy::led2d::layout::LedLayout;

const MAP: LedLayout<6, 3, 2> = LedLayout::serpentine_row_major();
const EXPECTED: LedLayout<6, 3, 2> =
    LedLayout::new([(0, 0), (1, 0), (2, 0), (2, 1), (1, 1), (0, 1)]);
const _: () = assert!(MAP.equals(&EXPECTED));
Strip snakes across rows (3×2 example):
  LED0  LED1  LED2
  LED5  LED4  LED3
Source

pub const fn rotate_cw(self) -> LedLayout<N, H, W>

Rotate 90° clockwise (dims swap).

use device_envoy::led2d::layout::LedLayout;

const ROTATED: LedLayout<6, 2, 3> = LedLayout::serpentine_column_major().rotate_cw();
const EXPECTED: LedLayout<6, 2, 3> =
    LedLayout::new([(1, 0), (0, 0), (0, 1), (1, 1), (1, 2), (0, 2)]);
const _: () = assert!(ROTATED.equals(&EXPECTED));
Before (3×2 serpentine): After (2×3):
  LED0  LED3  LED4        LED1  LED0
  LED1  LED2  LED5        LED2  LED3
                          LED5  LED4
Source

pub const fn flip_h(self) -> Self

Flip horizontally (mirror columns).

use device_envoy::led2d::layout::LedLayout;

const FLIPPED: LedLayout<6, 3, 2> = LedLayout::serpentine_column_major().flip_h();
const EXPECTED: LedLayout<6, 3, 2> =
    LedLayout::new([(2, 0), (2, 1), (1, 1), (1, 0), (0, 0), (0, 1)]);
const _: () = assert!(FLIPPED.equals(&EXPECTED));
Before (serpentine): After:
  LED0  LED3  LED4      LED4  LED3  LED0
  LED1  LED2  LED5      LED5  LED2  LED1
Source

pub const fn rotate_180(self) -> Self

Rotate 180° derived from rotate_cw.

use device_envoy::led2d::layout::LedLayout;

const ROTATED: LedLayout<6, 3, 2> = LedLayout::serpentine_column_major().rotate_180();
const EXPECTED: LedLayout<6, 3, 2> =
    LedLayout::new([(2, 1), (2, 0), (1, 0), (1, 1), (0, 1), (0, 0)]);
const _: () = assert!(ROTATED.equals(&EXPECTED));
Before (3×2 serpentine): After 180°:
  LED0  LED3  LED4        LED5  LED2  LED1
  LED1  LED2  LED5        LED4  LED3  LED0
Source

pub const fn rotate_ccw(self) -> LedLayout<N, H, W>

Rotate 90° counter-clockwise derived from rotate_cw.

use device_envoy::led2d::layout::LedLayout;

const ROTATED: LedLayout<6, 2, 3> = LedLayout::serpentine_column_major().rotate_ccw();
const EXPECTED: LedLayout<6, 2, 3> =
    LedLayout::new([(0, 2), (1, 2), (1, 1), (0, 1), (0, 0), (1, 0)]);
const _: () = assert!(ROTATED.equals(&EXPECTED));
Before (3×2 serpentine): After (2×3):
  LED0  LED3  LED4        LED4  LED5
  LED1  LED2  LED5        LED3  LED2
                          LED0  LED1
Source

pub const fn flip_v(self) -> Self

Flip vertically derived from rotation + horizontal flip.

use device_envoy::led2d::layout::LedLayout;

const FLIPPED: LedLayout<6, 3, 2> = LedLayout::serpentine_column_major().flip_v();
const EXPECTED: LedLayout<6, 3, 2> =
    LedLayout::new([(0, 1), (0, 0), (1, 0), (1, 1), (2, 1), (2, 0)]);
const _: () = assert!(FLIPPED.equals(&EXPECTED));
Before (serpentine): After:
  LED0  LED3  LED4      LED1  LED2  LED5
  LED1  LED2  LED5      LED0  LED3  LED4
Source

pub const fn combine_h<const N2: usize, const OUT_N: usize, const W2: usize, const OUT_W: usize>( self, right: LedLayout<N2, W2, H>, ) -> LedLayout<OUT_N, OUT_W, H>

Concatenate horizontally with another mapping sharing the same rows.

use device_envoy::led2d::layout::LedLayout;

const LED_LAYOUT: LedLayout<6, 3, 2> = LedLayout::serpentine_column_major();
const COMBINED: LedLayout<12, 6, 2> = LED_LAYOUT.combine_h::<6, 12, 3, 6>(LED_LAYOUT);
const EXPECTED: LedLayout<12, 6, 2> = LedLayout::new([
    (0, 0), (0, 1), (1, 1), (1, 0), (2, 0), (2, 1), (3, 0), (3, 1), (4, 1),
    (4, 0), (5, 0), (5, 1),
]);
const _: () = assert!(COMBINED.equals(&EXPECTED));
Left serpentine (3×2):    Right serpentine (3×2):
  0  3  4                   6  9 10
  1  2  5                   7  8 11

Combined (6×2):
  0  3  4  6  9 10
  1  2  5  7  8 11
Source

pub const fn combine_v<const N2: usize, const OUT_N: usize, const H2: usize, const OUT_H: usize>( self, bottom: LedLayout<N2, W, H2>, ) -> LedLayout<OUT_N, W, OUT_H>

Concatenate vertically with another mapping sharing the same columns.

use device_envoy::led2d::layout::LedLayout;

const LED_LAYOUT: LedLayout<6, 3, 2> = LedLayout::serpentine_column_major();
const COMBINED: LedLayout<12, 3, 4> = LED_LAYOUT.combine_v::<6, 12, 2, 4>(LED_LAYOUT);
const EXPECTED: LedLayout<12, 3, 4> = LedLayout::new([
    (0, 0), (0, 1), (1, 1), (1, 0), (2, 0), (2, 1), (0, 2), (0, 3), (1, 3),
    (1, 2), (2, 2), (2, 3),
]);
const _: () = assert!(COMBINED.equals(&EXPECTED));
Top serpentine (3×2):    Bottom serpentine (3×2):
  0  3  4                   6  9 10
  1  2  5                   7  8 11

Combined (3×4):
  0  3  4
  1  2  5
  6  9 10
  7  8 11

Trait Implementations§

Source§

impl<const N: usize, const W: usize, const H: usize> Clone for LedLayout<N, W, H>

Source§

fn clone(&self) -> LedLayout<N, W, H>

Returns a duplicate of the value. Read more
1.0.0 · Source§

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

Performs copy-assignment from source. Read more
Source§

impl<const N: usize, const W: usize, const H: usize> Debug for LedLayout<N, W, H>

Source§

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

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

impl<const N: usize, const W: usize, const H: usize> PartialEq for LedLayout<N, W, H>

Source§

fn eq(&self, other: &LedLayout<N, W, H>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · 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<const N: usize, const W: usize, const H: usize> Copy for LedLayout<N, W, H>

Source§

impl<const N: usize, const W: usize, const H: usize> Eq for LedLayout<N, W, H>

Source§

impl<const N: usize, const W: usize, const H: usize> StructuralPartialEq for LedLayout<N, W, H>

Auto Trait Implementations§

§

impl<const N: usize, const W: usize, const H: usize> Freeze for LedLayout<N, W, H>

§

impl<const N: usize, const W: usize, const H: usize> RefUnwindSafe for LedLayout<N, W, H>

§

impl<const N: usize, const W: usize, const H: usize> Send for LedLayout<N, W, H>

§

impl<const N: usize, const W: usize, const H: usize> Sync for LedLayout<N, W, H>

§

impl<const N: usize, const W: usize, const H: usize> Unpin for LedLayout<N, W, H>

§

impl<const N: usize, const W: usize, const H: usize> UnwindSafe for LedLayout<N, W, H>

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> Az for T

Source§

fn az<Dst>(self) -> Dst
where T: Cast<Dst>,

Casts the value.
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<Src, Dst> CastFrom<Src> for Dst
where Src: Cast<Dst>,

Source§

fn cast_from(src: Src) -> Dst

Casts the value.
Source§

impl<T> CheckedAs for T

Source§

fn checked_as<Dst>(self) -> Option<Dst>
where T: CheckedCast<Dst>,

Casts the value.
Source§

impl<Src, Dst> CheckedCastFrom<Src> for Dst
where Src: CheckedCast<Dst>,

Source§

fn checked_cast_from(src: Src) -> Option<Dst>

Casts the value.
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> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<Src, Dst> LosslessTryInto<Dst> for Src
where Dst: LosslessTryFrom<Src>,

Source§

fn lossless_try_into(self) -> Option<Dst>

Performs the conversion.
Source§

impl<Src, Dst> LossyInto<Dst> for Src
where Dst: LossyFrom<Src>,

Source§

fn lossy_into(self) -> Dst

Performs the conversion.
Source§

impl<T> OverflowingAs for T

Source§

fn overflowing_as<Dst>(self) -> (Dst, bool)
where T: OverflowingCast<Dst>,

Casts the value.
Source§

impl<Src, Dst> OverflowingCastFrom<Src> for Dst
where Src: OverflowingCast<Dst>,

Source§

fn overflowing_cast_from(src: Src) -> (Dst, bool)

Casts the value.
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> SaturatingAs for T

Source§

fn saturating_as<Dst>(self) -> Dst
where T: SaturatingCast<Dst>,

Casts the value.
Source§

impl<Src, Dst> SaturatingCastFrom<Src> for Dst
where Src: SaturatingCast<Dst>,

Source§

fn saturating_cast_from(src: Src) -> Dst

Casts the value.
Source§

impl<T> StrictAs for T

Source§

fn strict_as<Dst>(self) -> Dst
where T: StrictCast<Dst>,

Casts the value.
Source§

impl<Src, Dst> StrictCastFrom<Src> for Dst
where Src: StrictCast<Dst>,

Source§

fn strict_cast_from(src: Src) -> Dst

Casts the value.
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.
Source§

impl<T> UnwrappedAs for T

Source§

fn unwrapped_as<Dst>(self) -> Dst
where T: UnwrappedCast<Dst>,

Casts the value.
Source§

impl<Src, Dst> UnwrappedCastFrom<Src> for Dst
where Src: UnwrappedCast<Dst>,

Source§

fn unwrapped_cast_from(src: Src) -> Dst

Casts the value.
Source§

impl<T> WrappingAs for T

Source§

fn wrapping_as<Dst>(self) -> Dst
where T: WrappingCast<Dst>,

Casts the value.
Source§

impl<Src, Dst> WrappingCastFrom<Src> for Dst
where Src: WrappingCast<Dst>,

Source§

fn wrapping_cast_from(src: Src) -> Dst

Casts the value.