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 cornerxincreases to the rightyincreases 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:
- rotate:
rotate_cw,rotate_ccw,rotate_180 - flip:
flip_h,flip_v - combine:
combine_h,combine_v(join two layouts into a larger one)
§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 assertSerpentine 3×2 rotated to 2×3:
Before: After:
LED0 LED3 LED4 LED1 LED0
LED1 LED2 LED5 LED2 LED3
LED5 LED4Implementations§
Source§impl<const N: usize, const W: usize, const H: usize> LedLayout<N, W, H>
impl<const N: usize, const W: usize, const H: usize> LedLayout<N, W, H>
Sourcepub const fn index_to_xy(&self) -> &[(u16, u16); N]
pub const fn index_to_xy(&self) -> &[(u16, u16); N]
Return the array mapping LED wiring order to (x, y) coordinates.
Sourcepub const fn equals(&self, other: &Self) -> bool
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 equalLINEAR: LED0 LED1 LED2 LED3
ROTATED: LED3 LED2 LED1 LED0Sourcepub const fn new(map: [(u16, u16); N]) -> Self
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 LED4Sourcepub const fn linear_h() -> Self
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 LED5Sourcepub const fn linear_v() -> Self
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
LED5Sourcepub const fn serpentine_column_major() -> Self
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 LED5Sourcepub const fn serpentine_row_major() -> Self
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 LED3Sourcepub const fn rotate_cw(self) -> LedLayout<N, H, W>
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 LED4Sourcepub const fn flip_h(self) -> Self
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 LED1Sourcepub const fn rotate_180(self) -> Self
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 LED0Sourcepub const fn rotate_ccw(self) -> LedLayout<N, H, W>
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 LED1Sourcepub const fn flip_v(self) -> Self
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 LED4Sourcepub 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>
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 11Sourcepub 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>
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 11Trait Implementations§
impl<const N: usize, const W: usize, const H: usize> Copy for LedLayout<N, W, H>
impl<const N: usize, const W: usize, const H: usize> Eq for LedLayout<N, W, H>
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> 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> CheckedAs for T
impl<T> CheckedAs for T
Source§fn checked_as<Dst>(self) -> Option<Dst>where
T: CheckedCast<Dst>,
fn checked_as<Dst>(self) -> Option<Dst>where
T: CheckedCast<Dst>,
Source§impl<Src, Dst> CheckedCastFrom<Src> for Dstwhere
Src: CheckedCast<Dst>,
impl<Src, Dst> CheckedCastFrom<Src> for Dstwhere
Src: CheckedCast<Dst>,
Source§fn checked_cast_from(src: Src) -> Option<Dst>
fn checked_cast_from(src: Src) -> Option<Dst>
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
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