use crate::{
align::{horizontal, vertical},
align::{HorizontalAlignment, VerticalAlignment},
view_group::{EmptyViewGroup, ViewGroup},
View,
};
mod orientation;
mod secondary_alignment;
pub mod spacing;
use embedded_graphics::{
draw_target::DrawTarget,
prelude::{PixelColor, Point},
primitives::Rectangle,
Drawable,
};
pub use orientation::{Horizontal, Orientation, Vertical};
pub use secondary_alignment::SecondaryAlignment;
pub use spacing::{ElementSpacing, FixedMargin};
use spacing::Tight;
pub struct LinearLayout<LD, VG> {
position: Point,
direction: LD,
views: VG,
}
impl<LD, VG> LinearLayout<LD, VG> {
#[inline]
pub fn inner(&self) -> &VG {
&self.views
}
#[inline]
pub fn inner_mut(&mut self) -> &mut VG {
&mut self.views
}
}
impl<VG> LinearLayout<Horizontal<vertical::Bottom, Tight>, VG>
where
VG: ViewGroup,
{
#[inline]
#[must_use]
pub fn horizontal(views: VG) -> Self {
Self {
position: Point::new(0, 0),
direction: Horizontal::default(),
views,
}
}
}
impl<VG> LinearLayout<Vertical<horizontal::Left, Tight>, VG>
where
VG: ViewGroup,
{
#[inline]
#[must_use]
pub fn vertical(views: VG) -> Self {
Self {
position: Point::new(0, 0),
direction: Vertical::default(),
views,
}
}
}
impl<S, ELS, VG> LinearLayout<Horizontal<S, ELS>, VG>
where
S: SecondaryAlignment + VerticalAlignment,
ELS: ElementSpacing,
VG: ViewGroup,
{
#[inline]
pub fn with_alignment<Sec>(self, alignment: Sec) -> LinearLayout<Horizontal<Sec, ELS>, VG>
where
Sec: SecondaryAlignment + VerticalAlignment,
{
LinearLayout {
position: self.position,
direction: self.direction.with_secondary_alignment(alignment),
views: self.views,
}
}
#[inline]
pub fn with_spacing<ES>(self, spacing: ES) -> LinearLayout<Horizontal<S, ES>, VG>
where
ES: ElementSpacing,
{
LinearLayout {
position: self.position,
direction: self.direction.with_spacing(spacing),
views: self.views,
}
}
}
impl<S, ELS, VG> LinearLayout<Vertical<S, ELS>, VG>
where
S: SecondaryAlignment + HorizontalAlignment,
ELS: ElementSpacing,
VG: ViewGroup,
{
#[inline]
pub fn with_alignment<Sec>(self, alignment: Sec) -> LinearLayout<Vertical<Sec, ELS>, VG>
where
Sec: SecondaryAlignment + HorizontalAlignment,
{
LinearLayout {
position: self.position,
direction: self.direction.with_secondary_alignment(alignment),
views: self.views,
}
}
#[inline]
pub fn with_spacing<ES>(self, spacing: ES) -> LinearLayout<Vertical<S, ES>, VG>
where
ES: ElementSpacing,
{
LinearLayout {
position: self.position,
direction: self.direction.with_spacing(spacing),
views: self.views,
}
}
}
impl<LD, VG> Clone for LinearLayout<LD, VG>
where
LD: Orientation,
VG: ViewGroup + Clone,
{
fn clone(&self) -> Self {
Self {
position: self.position,
direction: self.direction,
views: self.views.clone(),
}
}
}
impl<LD, VG> LinearLayout<LD, VG>
where
LD: Orientation,
VG: ViewGroup,
{
#[inline]
#[must_use]
pub fn into_inner(self) -> VG {
self.views
}
#[inline]
#[must_use]
pub fn arrange(mut self) -> Self {
self.views
.translate_child(0, self.position - self.views.bounds_of(0).top_left);
LinearLayout {
position: Point::zero(),
direction: self.direction,
views: EmptyViewGroup,
}
.arrange_view_group(&mut self.views);
self
}
#[inline]
pub fn arrange_view_group(&self, view_group: &mut impl ViewGroup) {
let view_count = view_group.len();
let bounds = view_group.bounds_of(0);
let position = bounds.top_left;
let mut size = bounds.size();
for i in 1..view_count {
let current_el_size = view_group.bounds_of(i).size();
size = LD::Secondary::measure(size, current_el_size);
}
let mut bounds = Rectangle::new(position, size);
for i in 0..view_count {
let offset =
self.direction
.compute_offset(view_group.bounds_of(i), size, bounds, i, view_count);
view_group.translate_child(i, offset);
bounds = view_group.bounds_of(i);
}
}
}
impl<LD, VG> View for LinearLayout<LD, VG>
where
LD: Orientation,
VG: ViewGroup,
{
#[inline]
fn translate_impl(&mut self, by: Point) {
self.position += by;
View::translate_impl(&mut self.views, by);
}
#[inline]
fn bounds(&self) -> Rectangle {
let bounds = View::bounds(&self.views);
let top_left = bounds.top_left;
let correction = self.position - top_left;
bounds.translate(correction)
}
}
impl<LD, VG> ViewGroup for LinearLayout<LD, VG>
where
LD: Orientation,
VG: ViewGroup,
{
#[inline]
fn len(&self) -> usize {
self.views.len()
}
#[inline]
fn at(&self, idx: usize) -> &dyn View {
self.views.at(idx)
}
#[inline]
fn at_mut(&mut self, idx: usize) -> &mut dyn View {
self.views.at_mut(idx)
}
#[inline]
fn bounds_of(&self, idx: usize) -> Rectangle {
self.views.bounds_of(idx)
}
#[inline]
fn translate_child(&mut self, idx: usize, by: Point) {
self.views.translate_child(idx, by)
}
}
impl<C, LD, VG> Drawable for LinearLayout<LD, VG>
where
C: PixelColor,
LD: Orientation,
VG: ViewGroup + Drawable<Color = C>,
{
type Color = C;
type Output = ();
#[inline]
fn draw<D>(&self, display: &mut D) -> Result<(), D::Error>
where
D: DrawTarget<Color = C>,
{
self.views.draw(display)?;
Ok(())
}
}
#[cfg(test)]
mod test {
use crate::{
layout::linear::{
spacing::{DistributeFill, FixedMargin},
LinearLayout,
},
object_chain::Chain,
prelude::*,
};
use embedded_graphics::{
mock_display::MockDisplay,
pixelcolor::BinaryColor,
prelude::{Point, Primitive, Size},
primitives::{Circle, PrimitiveStyle, Rectangle},
Drawable,
};
#[allow(dead_code)]
fn compile_check() {
let style = PrimitiveStyle::with_fill(BinaryColor::On);
let rect = Rectangle::new(Point::zero(), Size::new(10, 20)).into_styled(style);
let circ = Circle::new(Point::zero(), 10).into_styled(style);
let _ = LinearLayout::horizontal(Chain::new(rect).append(circ));
}
#[test]
fn layout_size() {
let rect = Rectangle::new(Point::zero(), Size::new(10, 20));
let rect2 = Rectangle::new(Point::zero(), Size::new(10, 20));
let size = LinearLayout::horizontal(Chain::new(rect).append(rect2))
.arrange()
.size();
assert_eq!(Size::new(20, 20), size);
let rect = Rectangle::new(Point::zero(), Size::new(10, 20));
let rect2 = Rectangle::new(Point::zero(), Size::new(10, 20));
let size = LinearLayout::vertical(Chain::new(rect).append(rect2))
.arrange()
.size();
assert_eq!(Size::new(10, 40), size);
}
#[test]
fn layout_arrange_vertical() {
let mut disp: MockDisplay<BinaryColor> = MockDisplay::new();
let style = PrimitiveStyle::with_stroke(BinaryColor::On, 1);
let rect = Rectangle::new(Point::new(10, 30), Size::new(10, 5)).into_styled(style);
let rect2 = Rectangle::new(Point::new(-50, 10), Size::new(5, 10)).into_styled(style);
LinearLayout::vertical(Chain::new(rect).append(rect2))
.arrange()
.translate(Point::new(1, 2))
.draw(&mut disp)
.unwrap();
assert_eq!(
disp,
MockDisplay::from_pattern(&[
" ",
" ",
" ##########",
" # #",
" # #",
" # #",
" ##########",
" ##### ",
" # # ",
" # # ",
" # # ",
" # # ",
" # # ",
" # # ",
" # # ",
" # # ",
" ##### ",
])
);
}
#[test]
fn empty_rectangle_takes_up_no_vertical_space() {
let mut disp: MockDisplay<BinaryColor> = MockDisplay::new();
let style = PrimitiveStyle::with_stroke(BinaryColor::On, 1);
let rect = Rectangle::new(Point::new(10, 30), Size::new(10, 5)).into_styled(style);
let rect_empty = Rectangle::new(Point::new(-50, 10), Size::zero()).into_styled(style);
let rect2 = Rectangle::new(Point::new(-50, 10), Size::new(5, 10)).into_styled(style);
LinearLayout::vertical(Chain::new(rect).append(rect_empty).append(rect2))
.arrange()
.translate(Point::new(1, 2))
.draw(&mut disp)
.unwrap();
assert_eq!(
disp,
MockDisplay::from_pattern(&[
" ",
" ",
" ##########",
" # #",
" # #",
" # #",
" ##########",
" ##### ",
" # # ",
" # # ",
" # # ",
" # # ",
" # # ",
" # # ",
" # # ",
" # # ",
" ##### ",
])
);
}
#[test]
fn layout_arrange_vertical_secondary() {
let mut disp: MockDisplay<BinaryColor> = MockDisplay::new();
let style = PrimitiveStyle::with_stroke(BinaryColor::On, 1);
let rect = Rectangle::new(Point::new(10, 30), Size::new(10, 5)).into_styled(style);
let rect2 = Rectangle::new(Point::new(-50, 10), Size::new(5, 10)).into_styled(style);
LinearLayout::vertical(Chain::new(rect).append(rect2))
.with_alignment(horizontal::Right)
.arrange()
.translate(Point::new(1, 2))
.draw(&mut disp)
.unwrap();
assert_eq!(
disp,
MockDisplay::from_pattern(&[
" ",
" ",
" ##########",
" # #",
" # #",
" # #",
" ##########",
" #####",
" # #",
" # #",
" # #",
" # #",
" # #",
" # #",
" # #",
" # #",
" #####",
])
);
}
#[test]
fn layout_arrange_horizontal() {
let mut disp: MockDisplay<BinaryColor> = MockDisplay::new();
let style = PrimitiveStyle::with_stroke(BinaryColor::On, 1);
let rect = Rectangle::new(Point::new(10, 30), Size::new(10, 5)).into_styled(style);
let rect2 = Rectangle::new(Point::new(-50, 10), Size::new(5, 10)).into_styled(style);
LinearLayout::horizontal(Chain::new(rect).append(rect2))
.arrange()
.translate(Point::new(1, 2))
.draw(&mut disp)
.unwrap();
assert_eq!(
disp,
MockDisplay::from_pattern(&[
" ",
" ",
" #####",
" # #",
" # #",
" # #",
" # #",
" ########### #",
" # ## #",
" # ## #",
" # ## #",
" ###############",
])
);
}
#[test]
fn empty_rectangle_takes_up_no_horizontal_space() {
let mut disp: MockDisplay<BinaryColor> = MockDisplay::new();
let style = PrimitiveStyle::with_stroke(BinaryColor::On, 1);
let rect = Rectangle::new(Point::new(10, 30), Size::new(10, 5)).into_styled(style);
let rect_empty = Rectangle::new(Point::new(-50, 10), Size::zero()).into_styled(style);
let rect2 = Rectangle::new(Point::new(-50, 10), Size::new(5, 10)).into_styled(style);
LinearLayout::horizontal(Chain::new(rect).append(rect_empty).append(rect2))
.arrange()
.translate(Point::new(1, 2))
.draw(&mut disp)
.unwrap();
assert_eq!(
disp,
MockDisplay::from_pattern(&[
" ",
" ",
" #####",
" # #",
" # #",
" # #",
" # #",
" ########### #",
" # ## #",
" # ## #",
" # ## #",
" ###############",
])
);
}
#[test]
fn layout_arrange_horizontal_secondary() {
let mut disp: MockDisplay<BinaryColor> = MockDisplay::new();
let style = PrimitiveStyle::with_stroke(BinaryColor::On, 1);
let rect = Rectangle::new(Point::new(10, 30), Size::new(10, 5)).into_styled(style);
let rect2 = Rectangle::new(Point::new(-50, 10), Size::new(5, 10)).into_styled(style);
LinearLayout::horizontal(Chain::new(rect).append(rect2))
.with_alignment(vertical::Top)
.arrange()
.translate(Point::new(1, 2))
.draw(&mut disp)
.unwrap();
assert_eq!(
disp,
MockDisplay::from_pattern(&[
" ",
" ",
" ###############",
" # ## #",
" # ## #",
" # ## #",
" ########### #",
" # #",
" # #",
" # #",
" # #",
" #####",
])
);
}
#[test]
fn layout_spacing_size() {
let style = PrimitiveStyle::with_stroke(BinaryColor::On, 1);
let rect = Rectangle::new(Point::new(10, 30), Size::new(10, 5)).into_styled(style);
let rect2 = Rectangle::new(Point::new(-50, 10), Size::new(5, 10)).into_styled(style);
let size = LinearLayout::horizontal(Chain::new(rect).append(rect2))
.with_spacing(FixedMargin(2))
.with_alignment(vertical::Top)
.arrange()
.size();
assert_eq!(Size::new(17, 10), size);
let size = LinearLayout::vertical(Chain::new(rect).append(rect2))
.with_spacing(FixedMargin(2))
.arrange()
.size();
assert_eq!(Size::new(10, 17), size);
}
#[test]
fn layout_spacing() {
let mut disp: MockDisplay<BinaryColor> = MockDisplay::new();
let style = PrimitiveStyle::with_stroke(BinaryColor::On, 1);
let rect = Rectangle::new(Point::new(10, 30), Size::new(10, 5)).into_styled(style);
let rect2 = Rectangle::new(Point::new(-50, 10), Size::new(5, 10)).into_styled(style);
LinearLayout::horizontal(Chain::new(rect).append(rect2))
.with_spacing(FixedMargin(2))
.with_alignment(vertical::Top)
.arrange()
.translate(Point::new(1, 2))
.draw(&mut disp)
.unwrap();
assert_eq!(
disp,
MockDisplay::from_pattern(&[
" ",
" ",
" ########## #####",
" # # # #",
" # # # #",
" # # # #",
" ########## # #",
" # #",
" # #",
" # #",
" # #",
" #####",
])
);
}
#[test]
fn layout_spacing_distribute_overflow() {
let style = PrimitiveStyle::with_stroke(BinaryColor::On, 1);
let rect = Rectangle::new(Point::zero(), Size::new(5, 5)).into_styled(style);
let layout = LinearLayout::horizontal(Chain::new(rect).append(rect).append(rect))
.with_spacing(DistributeFill(11))
.with_alignment(vertical::TopToBottom)
.arrange();
assert_eq!(Size::new(11, 15), layout.size());
let mut disp: MockDisplay<BinaryColor> = MockDisplay::new();
layout.arrange().draw(&mut disp).unwrap();
assert_eq!(
disp,
MockDisplay::from_pattern(&[
"##### ",
"# # ",
"# # ",
"# # ",
"##### ",
" ##### ",
" # # ",
" # # ",
" # # ",
" ##### ",
" #####",
" # #",
" # #",
" # #",
" #####",
])
);
}
#[test]
fn layout_spacing_distribute_fill() {
let style = PrimitiveStyle::with_stroke(BinaryColor::On, 1);
let rect = Rectangle::new(Point::zero(), Size::new(2, 2)).into_styled(style);
let view_group =
LinearLayout::vertical(Chain::new(rect).append(rect).append(rect).append(rect))
.with_spacing(DistributeFill(18))
.arrange();
let mut disp: MockDisplay<BinaryColor> = MockDisplay::new();
view_group.draw(&mut disp).unwrap();
assert_eq!(
disp,
MockDisplay::from_pattern(&[
"## ",
"## ",
" ",
" ",
" ",
" ",
"## ",
"## ",
" ",
" ",
" ",
"## ",
"## ",
" ",
" ",
" ",
"## ",
"## "
])
);
}
#[test]
fn layout_size_independent_of_view_location() {
let rect = Rectangle::new(Point::zero(), Size::new(10, 20));
let rect2 = Rectangle::new(Point::zero(), Size::new(10, 20));
let size1 = LinearLayout::horizontal(Chain::new(rect).append(rect2))
.arrange()
.bounds()
.size();
let rect = rect.translate(Point::new(30, 50));
let size2 = LinearLayout::horizontal(Chain::new(rect).append(rect2))
.arrange()
.bounds()
.size();
assert_eq!(size1, size2);
}
}