use crate::*;
#[derive(Clone, Debug)]
pub struct Frame<V> {
pub(crate) child: V,
pub(crate) min_width: Option<u16>,
pub(crate) min_height: Option<u16>,
pub(crate) max_width: Option<u16>,
pub(crate) max_height: Option<u16>,
pub(crate) alignment: Alignment,
}
impl<V: View> private::Sealed for Frame<V> {}
impl<V: View> View for Frame<V> {
fn size(&self, proposed: crate::Size) -> crate::Size {
fn calculate_dimension(
proposed: u16,
child: u16,
min: Option<u16>,
max: Option<u16>,
) -> u16 {
let max = max.unwrap_or(proposed);
if max == u16::MAX {
proposed
} else {
child.clamp(min.unwrap_or(0), max)
}
}
let child_size = self.child.size(proposed);
let width = calculate_dimension(
proposed.width,
child_size.width,
self.min_width,
self.max_width,
);
let height = calculate_dimension(
proposed.height,
child_size.height,
self.min_height,
self.max_height,
);
Size::new(width, height)
}
fn render(
&self,
id: &mut ViewId,
context: crate::Context,
state: &mut AppState,
buffer: &mut crate::Buffer,
) {
let child_size = self.child.size(context.rect.size);
let size = self.size(context.rect.size);
let offset_x = if self.max_width == Some(u16::MAX) {
match self.alignment.horizontal {
HorizontalAlignment::Left => 0,
HorizontalAlignment::Center => {
(size.width / 2).saturating_sub(child_size.width / 2)
}
HorizontalAlignment::Right => size.width.saturating_sub(child_size.width),
}
} else {
0
};
let offset_y = if self.max_height == Some(u16::MAX) {
match self.alignment.vertical {
VerticalAlignment::Top => 0,
VerticalAlignment::Center => {
(size.height / 2).saturating_sub(child_size.height / 2)
}
VerticalAlignment::Bottom => size.height.saturating_sub(child_size.height),
}
} else {
0
};
self.child.render(
id,
context.with_size(size).offset(offset_x, offset_y),
state,
buffer,
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::assert_rendered_view;
use pretty_assertions::assert_eq;
#[test]
fn test_frame_size_with_min_constraints() {
let frame = text("Hello").frame(Some(10), Some(5), None, None, Alignment::TOP_LEFT);
let proposed_size = Size::new(100, 100);
let size = frame.size(proposed_size);
assert_eq!(size, Size::new(10, 5));
}
#[test]
fn test_frame_size_with_max_constraints() {
let frame = text("Hello, world!").frame(None, None, Some(5), Some(1), Alignment::TOP_LEFT);
let proposed_size = Size::new(100, 100);
let size = frame.size(proposed_size);
assert_eq!(size.width, 5);
assert_eq!(size.height, 1);
}
#[test]
fn test_frame_size_within_constraints() {
let frame = text("Hello").frame(Some(3), Some(1), Some(10), Some(2), Alignment::TOP_LEFT);
let proposed_size = Size::max();
let size = frame.size(proposed_size);
assert_eq!(size, Size::new(5, 1));
}
#[test]
fn test_frame_size_with_no_constraints() {
let frame = text("Hello").frame(None, None, None, None, Alignment::TOP_LEFT);
let proposed_size = Size::new(20, 10);
let size = frame.size(proposed_size);
assert_eq!(size, Size::new(5, 1)); }
#[test]
fn test_frame_max_width_takes_up_entire_proposed_width() {
let frame = text("Hello").frame(None, None, Some(u16::MAX), None, Alignment::TOP_LEFT);
let proposed_size = Size::new(20, 2);
let size = frame.size(proposed_size);
assert_eq!(size, Size::new(20, 1));
}
#[test]
fn test_frame_max_height_takes_up_entire_proposed_height() {
let frame = text("Hello").frame(None, None, None, Some(u16::MAX), Alignment::TOP_LEFT);
let proposed_size = Size::new(20, 8);
let size = frame.size(proposed_size);
assert_eq!(size, Size::new(5, 8));
}
#[test]
fn test_render_frame_with_text() {
let frame = text("HELLO WORLD").frame(None, None, Some(5), None, Alignment::TOP_LEFT);
let expected_output = vec!["HELLO "];
assert_rendered_view(frame, expected_output, 11, 1);
}
#[test]
fn test_frame_max_width_with_alignment() {
let frame = text("HELLO!").frame(None, None, Some(u16::MAX), None, Alignment::CENTER);
let proposed_size = Size::new(20, 1);
let size = frame.size(proposed_size);
assert_eq!(size, Size::new(20, 1));
let expected_output = vec![" HELLO! "];
assert_rendered_view(frame, expected_output, 20, 1);
}
#[test]
fn test_frame_max_width_with_right_alignment() {
let frame = text("WOW")
.frame(None, None, Some(u16::MAX), None, Alignment::RIGHT)
.border();
let proposed_size = Size::new(10, 3);
let size = frame.size(proposed_size);
assert_eq!(size, Size::new(10, 3));
let expected_output = vec![
"┌────────┐", "│ WOW │", "└────────┘", ];
assert_rendered_view(frame, expected_output, 10, 3);
}
#[test]
fn test_frame_vertical_and_horizontal_alignment() {
let frame = text("WOW")
.frame(
None,
None,
Some(u16::MAX),
Some(u16::MAX),
Alignment::CENTER,
)
.border();
let proposed_size = Size::new(9, 5);
let size = frame.size(proposed_size);
assert_eq!(size, Size::new(9, 5));
let expected_output = vec![
"┌───────┐", "│ │", "│ WOW │", "│ │", "└───────┘", ];
assert_rendered_view(frame, expected_output, 9, 5);
}
#[test]
fn test_frame_vertical_and_horizontal_alignment_bottom_right() {
let view = text("WOW")
.frame(
None,
None,
Some(u16::MAX),
Some(u16::MAX),
Alignment::BOTTOM_RIGHT,
)
.border();
let proposed_size = Size::new(9, 5);
let size = view.size(proposed_size);
assert_eq!(size, Size::new(9, 5));
let expected_output = vec![
"┌───────┐", "│ │", "│ │", "│ WOW │", "└───────┘", ];
assert_rendered_view(view, expected_output, 9, 5);
}
}