diff options
Diffstat (limited to 'src/tuine/component/base/block.rs')
-rw-r--r-- | src/tuine/component/base/block.rs | 421 |
1 files changed, 350 insertions, 71 deletions
diff --git a/src/tuine/component/base/block.rs b/src/tuine/component/base/block.rs index 99776ffe..743a39d7 100644 --- a/src/tuine/component/base/block.rs +++ b/src/tuine/component/base/block.rs @@ -12,6 +12,13 @@ pub struct StyleSheet { pub border: Style, } +struct BorderOffsets { + left: u16, + right: u16, + top: u16, + bottom: u16, +} + /// A [`Block`] is a widget that draws a border around a child [`Component`], as well as optional /// titles. pub struct Block<Message, Child> @@ -51,49 +58,30 @@ where self } - fn inner_rect(&self, original: Rect) -> Rect { - let mut inner = original; - - if self.borders.intersects(Borders::LEFT) { - inner.x = inner.x.saturating_add(1).min(inner.right()); - inner.width = inner.width.saturating_sub(1); - } - if self.borders.intersects(Borders::TOP) - || self.left_text.is_some() - || self.right_text.is_some() - { - inner.y = inner.y.saturating_add(1).min(inner.bottom()); - inner.height = inner.height.saturating_sub(1); - } - if self.borders.intersects(Borders::RIGHT) { - inner.width = inner.width.saturating_sub(1); - } - if self.borders.intersects(Borders::BOTTOM) { - inner.height = inner.height.saturating_sub(1); - } - inner + pub fn borders(mut self, borders: Borders) -> Self { + self.borders = borders; + self } - fn outer_size(&self, original: Size) -> Size { - let mut outer = original; - - if self.borders.intersects(Borders::LEFT) { - outer.width = outer.width.saturating_add(1); - } - if self.borders.intersects(Borders::TOP) - || self.left_text.is_some() - || self.right_text.is_some() - { - outer.height = outer.height.saturating_add(1); - } - if self.borders.intersects(Borders::RIGHT) { - outer.width = outer.width.saturating_add(1); - } - if self.borders.intersects(Borders::BOTTOM) { - outer.height = outer.height.saturating_add(1); + fn border_offsets(&self) -> BorderOffsets { + fn border_val(has_val: bool) -> u16 { + if has_val { + 1 + } else { + 0 + } } - outer + BorderOffsets { + left: border_val(self.borders.intersects(Borders::LEFT)), + right: border_val(self.borders.intersects(Borders::RIGHT)), + top: border_val( + self.borders.intersects(Borders::TOP) + || self.left_text.is_some() + || self.right_text.is_some(), + ), + bottom: border_val(self.borders.intersects(Borders::BOTTOM)), + } } } @@ -108,17 +96,17 @@ where B: Backend, { let rect = draw_ctx.global_rect(); - - frame.render_widget( - tui::widgets::Block::default() - .borders(self.borders) - .border_style(self.style_sheet.border), - rect, - ); - - if let Some(child) = &mut self.child { - if let Some(child_draw_ctx) = draw_ctx.children().next() { - child.draw(state_ctx, &child_draw_ctx, frame) + if rect.area() > 0 { + frame.render_widget( + tui::widgets::Block::default() + .borders(self.borders) + .border_style(self.style_sheet.border), + rect, + ); + if let Some(child) = &mut self.child { + if let Some(child_draw_ctx) = draw_ctx.children().next() { + child.draw(state_ctx, &child_draw_ctx, frame) + } } } } @@ -136,29 +124,45 @@ where Status::Ignored } - fn layout(&self, bounds: Bounds, node: &mut LayoutNode) -> crate::tuine::Size { + fn layout(&self, bounds: Bounds, node: &mut LayoutNode) -> Size { if let Some(child) = &self.child { - // Reduce bounds based on borders - let inner_rect = self.inner_rect(Rect::new(0, 0, bounds.max_width, bounds.max_height)); - let child_bounds = Bounds { - min_width: bounds.min_width, - min_height: bounds.min_height, - max_width: inner_rect.width, - max_height: inner_rect.height, - }; - - let mut child_node = LayoutNode::default(); - let child_size = child.layout(child_bounds, &mut child_node); - - child_node.rect = Rect::new( - inner_rect.x, - inner_rect.y, - child_size.width, - child_size.height, - ); - node.children = vec![child_node]; + let BorderOffsets { + left: left_offset, + right: right_offset, + top: top_offset, + bottom: bottom_offset, + } = self.border_offsets(); + + let vertical_offset = top_offset + bottom_offset; + let horizontal_offset = left_offset + right_offset; + + if bounds.max_height > vertical_offset && bounds.max_width > horizontal_offset { + let max_width = bounds.max_width - horizontal_offset; + let max_height = bounds.max_height - vertical_offset; + + let child_bounds = Bounds { + min_width: bounds.min_width, + min_height: bounds.min_height, + max_width, + max_height, + }; + let mut child_node = LayoutNode::default(); + let child_size = child.layout(child_bounds, &mut child_node); - self.outer_size(child_size) + child_node.rect = + Rect::new(left_offset, top_offset, child_size.width, child_size.height); + node.children = vec![child_node]; + + Size { + width: child_size.width + horizontal_offset, + height: child_size.height + vertical_offset, + } + } else { + Size { + width: 0, + height: 0, + } + } } else { Size { width: 0, @@ -167,3 +171,278 @@ where } } } + +#[cfg(test)] +mod tests { + use crate::tuine::Empty; + + use super::*; + + fn assert_border_offset(block: Block<(), Empty>, left: u16, right: u16, top: u16, bottom: u16) { + let offsets = block.border_offsets(); + assert_eq!(offsets.left, left, "left offset should be equal"); + assert_eq!(offsets.right, right, "right offset should be equal"); + assert_eq!(offsets.top, top, "top offset should be equal"); + assert_eq!(offsets.bottom, bottom, "bottom offset should be equal"); + } + + #[test] + fn empty_border_offset() { + let block: Block<(), Empty> = Block::with_child(Empty::default()).borders(Borders::empty()); + assert_border_offset(block, 0, 0, 0, 0); + } + + #[test] + fn all_border_offset() { + let block: Block<(), Empty> = Block::with_child(Empty::default()); + assert_border_offset(block, 1, 1, 1, 1); + } + + #[test] + fn horizontal_border_offset() { + let block: Block<(), Empty> = + Block::with_child(Empty::default()).borders(Borders::LEFT.union(Borders::RIGHT)); + assert_border_offset(block, 1, 1, 0, 0); + } + + #[test] + fn vertical_border_offset() { + let block: Block<(), Empty> = + Block::with_child(Empty::default()).borders(Borders::BOTTOM.union(Borders::TOP)); + assert_border_offset(block, 0, 0, 1, 1); + } + + #[test] + fn top_right() { + let block: Block<(), Empty> = + Block::with_child(Empty::default()).borders(Borders::RIGHT.union(Borders::TOP)); + assert_border_offset(block, 0, 1, 1, 0); + } + + #[test] + fn bottom_left() { + let block: Block<(), Empty> = + Block::with_child(Empty::default()).borders(Borders::BOTTOM.union(Borders::LEFT)); + assert_border_offset(block, 1, 0, 0, 1); + } + + #[test] + fn full_layout() { + let block: Block<(), Empty> = Block::with_child(Empty::default()); + let mut layout_node = LayoutNode::default(); + let bounds = Bounds { + min_width: 0, + min_height: 0, + max_width: 10, + max_height: 10, + }; + + assert_eq!( + block.layout(bounds, &mut layout_node), + Size { + width: 10, + height: 10, + }, + "the block should have dimensions (10, 10)." + ); + + assert_eq!( + layout_node.children[0].rect, + Rect { + x: 1, + y: 1, + width: 8, + height: 8 + }, + "the only child should have an offset of (1, 1), and dimensions (8, 8)" + ); + } + + #[test] + fn vertical_layout() { + let block: Block<(), Empty> = + Block::with_child(Empty::default()).borders(Borders::BOTTOM.union(Borders::TOP)); + let mut layout_node = LayoutNode::default(); + let bounds = Bounds { + min_width: 0, + min_height: 0, + max_width: 10, + max_height: 10, + }; + + assert_eq!( + block.layout(bounds, &mut layout_node), + Size { + width: 10, + height: 10, + }, + "the block should have dimensions (10, 10)." + ); + + assert_eq!( + layout_node.children[0].rect, + Rect { + x: 0, + y: 1, + width: 10, + height: 8 + }, + "the only child should have an offset of (0, 1), and dimensions (10, 8)" + ); + } + + #[test] + fn horizontal_layout() { + let block: Block<(), Empty> = + Block::with_child(Empty::default()).borders(Borders::LEFT.union(Borders::RIGHT)); + let mut layout_node = LayoutNode::default(); + let bounds = Bounds { + min_width: 0, + min_height: 0, + max_width: 10, + max_height: 10, + }; + + assert_eq!( + block.layout(bounds, &mut layout_node), + Size { + width: 10, + height: 10, + }, + "the block should have dimensions (10, 10)." + ); + + assert_eq!( + layout_node.children[0].rect, + Rect { + x: 1, + y: 0, + width: 8, + height: 10 + }, + "the only child should have an offset of (1, 0), and dimensions (8, 10)" + ); + } + + #[test] + fn irregular_layout_one() { + let block: Block<(), Empty> = + Block::with_child(Empty::default()).borders(Borders::LEFT.union(Borders::TOP)); + let mut layout_node = LayoutNode::default(); + let bounds = Bounds { + min_width: 0, + min_height: 0, + max_width: 10, + max_height: 10, + }; + + assert_eq!( + block.layout(bounds, &mut layout_node), + Size { + width: 10, + height: 10, + }, + "the block should have dimensions (10, 10)." + ); + + assert_eq!( + layout_node.children[0].rect, + Rect { + x: 1, + y: 1, + width: 9, + height: 9 + }, + "the only child should have an offset of (1, 1), and dimensions (9, 9)" + ); + } + + #[test] + fn irregular_layout_two() { + let block: Block<(), Empty> = + Block::with_child(Empty::default()).borders(Borders::BOTTOM.union(Borders::RIGHT)); + let mut layout_node = LayoutNode::default(); + let bounds = Bounds { + min_width: 0, + min_height: 0, + max_width: 10, + max_height: 10, + }; + + assert_eq!( + block.layout(bounds, &mut layout_node), + Size { + width: 10, + height: 10, + }, + "the block should have dimensions (10, 10)." + ); + + assert_eq!( + layout_node.children[0].rect, + Rect { + x: 0, + y: 0, + width: 9, + height: 9 + }, + "the only child should have an offset of (0, 0), and dimensions (9, 9)" + ); + } + + #[test] + fn irregular_layout_three() { + let block: Block<(), Empty> = + Block::with_child(Empty::default()).borders(Borders::RIGHT.union(Borders::TOP)); + let mut layout_node = LayoutNode::default(); + let bounds = Bounds { + min_width: 0, + min_height: 0, + max_width: 10, + max_height: 10, + }; + + assert_eq!( + block.layout(bounds, &mut layout_node), + Size { + width: 10, + height: 10, + }, + "the block should have dimensions (10, 10)." + ); + + assert_eq!( + layout_node.children[0].rect, + Rect { + x: 0, + y: 1, + width: 9, + height: 9 + }, + "the only child should have an offset of (0, 1), and dimensions (9, 9)" + ); + } + + #[test] + fn too_small_layout() { + let block: Block<(), Empty> = Block::with_child(Empty::default()); + let mut layout_node = LayoutNode::default(); + let bounds = Bounds { + min_width: 0, + min_height: 0, + max_width: 2, + max_height: 2, + }; + + assert_eq!( + block.layout(bounds, &mut layout_node), + Size { + width: 0, + height: 0, + }, + "the area should be 0" + ); + + assert_eq!(layout_node.children.len(), 0, "layout node should be empty"); + } +} |