summaryrefslogtreecommitdiff
path: root/helix-tui/src/widgets/block.rs
diff options
context:
space:
mode:
Diffstat (limited to 'helix-tui/src/widgets/block.rs')
-rw-r--r--helix-tui/src/widgets/block.rs511
1 files changed, 511 insertions, 0 deletions
diff --git a/helix-tui/src/widgets/block.rs b/helix-tui/src/widgets/block.rs
new file mode 100644
index 00000000..2569c17d
--- /dev/null
+++ b/helix-tui/src/widgets/block.rs
@@ -0,0 +1,511 @@
+use crate::{
+ buffer::Buffer,
+ layout::Rect,
+ style::Style,
+ symbols::line,
+ text::{Span, Spans},
+ widgets::{Borders, Widget},
+};
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum BorderType {
+ Plain,
+ Rounded,
+ Double,
+ Thick,
+}
+
+impl BorderType {
+ pub fn line_symbols(border_type: BorderType) -> line::Set {
+ match border_type {
+ BorderType::Plain => line::NORMAL,
+ BorderType::Rounded => line::ROUNDED,
+ BorderType::Double => line::DOUBLE,
+ BorderType::Thick => line::THICK,
+ }
+ }
+}
+
+/// Base widget to be used with all upper level ones. It may be used to display a box border around
+/// the widget and/or add a title.
+///
+/// # Examples
+///
+/// ```
+/// # use helix_tui::widgets::{Block, BorderType, Borders};
+/// # use helix_tui::style::{Style, Color};
+/// Block::default()
+/// .title("Block")
+/// .borders(Borders::LEFT | Borders::RIGHT)
+/// .border_style(Style::default().fg(Color::White))
+/// .border_type(BorderType::Rounded)
+/// .style(Style::default().bg(Color::Black));
+/// ```
+#[derive(Debug, Clone, PartialEq)]
+pub struct Block<'a> {
+ /// Optional title place on the upper left of the block
+ title: Option<Spans<'a>>,
+ /// Visible borders
+ borders: Borders,
+ /// Border style
+ border_style: Style,
+ /// Type of the border. The default is plain lines but one can choose to have rounded corners
+ /// or doubled lines instead.
+ border_type: BorderType,
+ /// Widget style
+ style: Style,
+}
+
+impl<'a> Default for Block<'a> {
+ fn default() -> Block<'a> {
+ Block {
+ title: None,
+ borders: Borders::NONE,
+ border_style: Default::default(),
+ border_type: BorderType::Plain,
+ style: Default::default(),
+ }
+ }
+}
+
+impl<'a> Block<'a> {
+ pub fn title<T>(mut self, title: T) -> Block<'a>
+ where
+ T: Into<Spans<'a>>,
+ {
+ self.title = Some(title.into());
+ self
+ }
+
+ #[deprecated(
+ since = "0.10.0",
+ note = "You should use styling capabilities of `text::Spans` given as argument of the `title` method to apply styling to the title."
+ )]
+ pub fn title_style(mut self, style: Style) -> Block<'a> {
+ if let Some(t) = self.title {
+ let title = String::from(t);
+ self.title = Some(Spans::from(Span::styled(title, style)));
+ }
+ self
+ }
+
+ pub fn border_style(mut self, style: Style) -> Block<'a> {
+ self.border_style = style;
+ self
+ }
+
+ pub fn style(mut self, style: Style) -> Block<'a> {
+ self.style = style;
+ self
+ }
+
+ pub fn borders(mut self, flag: Borders) -> Block<'a> {
+ self.borders = flag;
+ self
+ }
+
+ pub fn border_type(mut self, border_type: BorderType) -> Block<'a> {
+ self.border_type = border_type;
+ self
+ }
+
+ /// Compute the inner area of a block based on its border visibility rules.
+ pub fn inner(&self, area: Rect) -> Rect {
+ let mut inner = area;
+ 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.title.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
+ }
+}
+
+impl<'a> Widget for Block<'a> {
+ fn render(self, area: Rect, buf: &mut Buffer) {
+ if area.area() == 0 {
+ return;
+ }
+ buf.set_style(area, self.style);
+ let symbols = BorderType::line_symbols(self.border_type);
+
+ // Sides
+ if self.borders.intersects(Borders::LEFT) {
+ for y in area.top()..area.bottom() {
+ buf.get_mut(area.left(), y)
+ .set_symbol(symbols.vertical)
+ .set_style(self.border_style);
+ }
+ }
+ if self.borders.intersects(Borders::TOP) {
+ for x in area.left()..area.right() {
+ buf.get_mut(x, area.top())
+ .set_symbol(symbols.horizontal)
+ .set_style(self.border_style);
+ }
+ }
+ if self.borders.intersects(Borders::RIGHT) {
+ let x = area.right() - 1;
+ for y in area.top()..area.bottom() {
+ buf.get_mut(x, y)
+ .set_symbol(symbols.vertical)
+ .set_style(self.border_style);
+ }
+ }
+ if self.borders.intersects(Borders::BOTTOM) {
+ let y = area.bottom() - 1;
+ for x in area.left()..area.right() {
+ buf.get_mut(x, y)
+ .set_symbol(symbols.horizontal)
+ .set_style(self.border_style);
+ }
+ }
+
+ // Corners
+ if self.borders.contains(Borders::RIGHT | Borders::BOTTOM) {
+ buf.get_mut(area.right() - 1, area.bottom() - 1)
+ .set_symbol(symbols.bottom_right)
+ .set_style(self.border_style);
+ }
+ if self.borders.contains(Borders::RIGHT | Borders::TOP) {
+ buf.get_mut(area.right() - 1, area.top())
+ .set_symbol(symbols.top_right)
+ .set_style(self.border_style);
+ }
+ if self.borders.contains(Borders::LEFT | Borders::BOTTOM) {
+ buf.get_mut(area.left(), area.bottom() - 1)
+ .set_symbol(symbols.bottom_left)
+ .set_style(self.border_style);
+ }
+ if self.borders.contains(Borders::LEFT | Borders::TOP) {
+ buf.get_mut(area.left(), area.top())
+ .set_symbol(symbols.top_left)
+ .set_style(self.border_style);
+ }
+
+ if let Some(title) = self.title {
+ let lx = if self.borders.intersects(Borders::LEFT) {
+ 1
+ } else {
+ 0
+ };
+ let rx = if self.borders.intersects(Borders::RIGHT) {
+ 1
+ } else {
+ 0
+ };
+ let width = area.width.saturating_sub(lx).saturating_sub(rx);
+ buf.set_spans(area.left() + lx, area.top(), &title, width);
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::layout::Rect;
+
+ #[test]
+ fn inner_takes_into_account_the_borders() {
+ // No borders
+ assert_eq!(
+ Block::default().inner(Rect::default()),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0
+ },
+ "no borders, width=0, height=0"
+ );
+ assert_eq!(
+ Block::default().inner(Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 1
+ }),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 1
+ },
+ "no borders, width=1, height=1"
+ );
+
+ // Left border
+ assert_eq!(
+ Block::default().borders(Borders::LEFT).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 1
+ }),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 1
+ },
+ "left, width=0"
+ );
+ assert_eq!(
+ Block::default().borders(Borders::LEFT).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 1
+ }),
+ Rect {
+ x: 1,
+ y: 0,
+ width: 0,
+ height: 1
+ },
+ "left, width=1"
+ );
+ assert_eq!(
+ Block::default().borders(Borders::LEFT).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 2,
+ height: 1
+ }),
+ Rect {
+ x: 1,
+ y: 0,
+ width: 1,
+ height: 1
+ },
+ "left, width=2"
+ );
+
+ // Top border
+ assert_eq!(
+ Block::default().borders(Borders::TOP).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 0
+ }),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 0
+ },
+ "top, height=0"
+ );
+ assert_eq!(
+ Block::default().borders(Borders::TOP).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 1
+ }),
+ Rect {
+ x: 0,
+ y: 1,
+ width: 1,
+ height: 0
+ },
+ "top, height=1"
+ );
+ assert_eq!(
+ Block::default().borders(Borders::TOP).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 2
+ }),
+ Rect {
+ x: 0,
+ y: 1,
+ width: 1,
+ height: 1
+ },
+ "top, height=2"
+ );
+
+ // Right border
+ assert_eq!(
+ Block::default().borders(Borders::RIGHT).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 1
+ }),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 1
+ },
+ "right, width=0"
+ );
+ assert_eq!(
+ Block::default().borders(Borders::RIGHT).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 1
+ }),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 1
+ },
+ "right, width=1"
+ );
+ assert_eq!(
+ Block::default().borders(Borders::RIGHT).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 2,
+ height: 1
+ }),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 1
+ },
+ "right, width=2"
+ );
+
+ // Bottom border
+ assert_eq!(
+ Block::default().borders(Borders::BOTTOM).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 0
+ }),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 0
+ },
+ "bottom, height=0"
+ );
+ assert_eq!(
+ Block::default().borders(Borders::BOTTOM).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 1
+ }),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 0
+ },
+ "bottom, height=1"
+ );
+ assert_eq!(
+ Block::default().borders(Borders::BOTTOM).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 2
+ }),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 1
+ },
+ "bottom, height=2"
+ );
+
+ // All borders
+ assert_eq!(
+ Block::default()
+ .borders(Borders::ALL)
+ .inner(Rect::default()),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0
+ },
+ "all borders, width=0, height=0"
+ );
+ assert_eq!(
+ Block::default().borders(Borders::ALL).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 1
+ }),
+ Rect {
+ x: 1,
+ y: 1,
+ width: 0,
+ height: 0,
+ },
+ "all borders, width=1, height=1"
+ );
+ assert_eq!(
+ Block::default().borders(Borders::ALL).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 2,
+ height: 2,
+ }),
+ Rect {
+ x: 1,
+ y: 1,
+ width: 0,
+ height: 0,
+ },
+ "all borders, width=2, height=2"
+ );
+ assert_eq!(
+ Block::default().borders(Borders::ALL).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 3,
+ height: 3,
+ }),
+ Rect {
+ x: 1,
+ y: 1,
+ width: 1,
+ height: 1,
+ },
+ "all borders, width=3, height=3"
+ );
+ }
+
+ #[test]
+ fn inner_takes_into_account_the_title() {
+ assert_eq!(
+ Block::default().title("Test").inner(Rect {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 1,
+ }),
+ Rect {
+ x: 0,
+ y: 1,
+ width: 0,
+ height: 0,
+ },
+ );
+ }
+}