use crate::widgets::common::WindowView;
use crate::widgets::text::{Alignment, TextBlock};
use crate::widgets::{BorderChars, Widget};
use crate::{Color, ColorPair, Result, Window};
pub struct Panel {
width: u16,
height: u16,
header_text: String,
body_content: PanelContent,
header_style: BorderChars,
body_style: BorderChars,
header_color: Option<ColorPair>,
body_color: Option<ColorPair>,
header_border_color: Option<ColorPair>,
body_border_color: Option<ColorPair>,
padding: u16,
alignment: Alignment, auto_size: bool,
}
pub(crate) enum PanelContent {
Text(String),
Block(Box<TextBlock>),
}
impl Panel {
pub fn new(width: u16, height: u16) -> Self {
Self {
width,
height,
header_text: "".to_string(),
body_content: PanelContent::Text(String::new()),
header_style: BorderChars::single_line(),
body_style: BorderChars::single_line(),
header_color: None,
body_color: None,
header_border_color: None,
body_border_color: None,
padding: 1,
alignment: Alignment::Left,
auto_size: width == 0 || height == 0, }
}
pub fn auto_sized() -> Self {
Self::new(0, 0)
}
pub fn with_header(mut self, text: impl Into<String>) -> Self {
self.header_text = text.into();
if self.auto_size {
self.adjust_size();
}
self
}
pub fn with_body(mut self, text: impl Into<String>) -> Self {
self.body_content = PanelContent::Text(text.into());
if self.auto_size {
self.adjust_size();
}
self
}
pub fn with_body_block(mut self, text_block: TextBlock) -> Self {
self.body_content = PanelContent::Block(Box::new(text_block));
if self.auto_size {
self.adjust_size();
}
self
}
pub fn with_header_style(mut self, style: BorderChars) -> Self {
self.header_style = style;
self
}
pub fn with_body_style(mut self, style: BorderChars) -> Self {
self.body_style = style;
self
}
pub fn with_header_color(mut self, color: Option<ColorPair>) -> Self {
self.header_color = color;
self
}
pub fn with_body_color(mut self, color: Option<ColorPair>) -> Self {
self.body_color = color;
self
}
pub fn with_header_border_color(mut self, color: Color) -> Self {
self.header_border_color = Some(ColorPair::new(color, Color::Transparent));
self
}
pub fn with_body_border_color(mut self, color: Color) -> Self {
self.body_border_color = Some(ColorPair::new(color, Color::Transparent));
self
}
pub fn with_padding(mut self, padding: u16) -> Self {
self.padding = padding;
if self.auto_size {
self.adjust_size();
}
self
}
pub fn with_alignment(mut self, alignment: Alignment) -> Self {
self.alignment = alignment;
self
}
pub fn set_header(&mut self, text: impl Into<String>) {
self.header_text = text.into();
if self.auto_size {
self.adjust_size();
}
}
pub fn set_body(&mut self, text: impl Into<String>) {
self.body_content = PanelContent::Text(text.into());
if self.auto_size {
self.adjust_size();
}
}
pub fn with_auto_size(mut self, auto_size: bool) -> Self {
self.auto_size = auto_size;
if auto_size {
self.adjust_size();
}
self
}
fn adjust_size(&mut self) {
if !self.auto_size {
return;
}
let mut content_width = 0u16;
let mut content_height = 0u16;
if !self.header_text.is_empty() {
content_width = content_width.max(self.header_text.len() as u16);
content_height += 1; }
match &self.body_content {
PanelContent::Text(text) => {
if !text.is_empty() {
let lines: Vec<&str> = text.lines().collect();
let max_line_width =
lines.iter().map(|line| line.len()).max().unwrap_or(0) as u16;
content_width = content_width.max(max_line_width);
content_height += lines.len() as u16;
}
}
PanelContent::Block(text_block) => {
let (block_width, block_height) = text_block.get_size();
content_width = content_width.max(block_width);
content_height += block_height;
}
}
self.width = content_width + (self.padding * 2) + 2; self.height = content_height + (self.padding * 2) + 2;
if !self.header_text.is_empty() && content_height > 1 {
self.height += 1; }
}
fn get_inner_dimensions(&self) -> (u16, u16) {
let inner_width = self.width.saturating_sub(2 + (self.padding * 2));
let mut inner_height = self.height.saturating_sub(2 + (self.padding * 2));
if !self.header_text.is_empty() {
inner_height = inner_height.saturating_sub(2); }
(inner_width, inner_height)
}
}
impl Widget for Panel {
fn draw(&self, window: &mut dyn Window) -> Result<()> {
if self.width == 0 || self.height == 0 {
return Ok(()); }
let mut current_y = 0u16;
if !self.header_text.is_empty() {
self.draw_header_top_border(window, current_y)?;
current_y += 1;
self.draw_header_text(window, current_y)?;
current_y += 1;
self.draw_header_separator(window, current_y)?;
current_y += 1;
} else {
self.draw_body_top_border(window, current_y)?;
current_y += 1;
}
self.draw_body_content(window, current_y)?;
self.draw_bottom_border(window, self.height - 1)?;
Ok(())
}
fn get_size(&self) -> (u16, u16) {
(self.width, self.height)
}
fn get_position(&self) -> (u16, u16) {
(0, 0) }
}
impl Panel {
fn draw_header_top_border(&self, window: &mut dyn Window, y: u16) -> Result<()> {
let color = self.header_border_color;
self.draw_horizontal_line(window, y, &self.header_style, true, color)
}
fn draw_header_text(&self, window: &mut dyn Window, y: u16) -> Result<()> {
if let Some(color) = self.header_border_color {
window.write_str_colored(y, 0, &self.header_style.vertical.to_string(), color)?;
} else {
window.write_str(y, 0, &self.header_style.vertical.to_string())?;
}
let inner_width = self.width.saturating_sub(2);
let text_len = self.header_text.len() as u16;
let start_x = if text_len < inner_width {
1 + (inner_width - text_len) / 2
} else {
1
};
if let Some(color) = self.header_color {
window.write_str_colored(y, start_x, &self.header_text, color)?;
} else {
window.write_str(y, start_x, &self.header_text)?;
}
if let Some(color) = self.header_border_color {
window.write_str_colored(
y,
self.width - 1,
&self.header_style.vertical.to_string(),
color,
)?;
} else {
window.write_str(y, self.width - 1, &self.header_style.vertical.to_string())?;
}
Ok(())
}
fn draw_header_separator(&self, window: &mut dyn Window, y: u16) -> Result<()> {
let color = self.header_border_color;
if let Some(color) = color {
window.write_str_colored(y, 0, &self.header_style.intersect_left.to_string(), color)?;
} else {
window.write_str(y, 0, &self.header_style.intersect_left.to_string())?;
}
for x in 1..self.width - 1 {
if let Some(color) = color {
window.write_str_colored(y, x, &self.header_style.horizontal.to_string(), color)?;
} else {
window.write_str(y, x, &self.header_style.horizontal.to_string())?;
}
}
if let Some(color) = color {
window.write_str_colored(
y,
self.width - 1,
&self.header_style.intersect_right.to_string(),
color,
)?;
} else {
window.write_str(
y,
self.width - 1,
&self.header_style.intersect_right.to_string(),
)?;
}
Ok(())
}
fn draw_body_top_border(&self, window: &mut dyn Window, y: u16) -> Result<()> {
let color = self.body_border_color;
self.draw_horizontal_line(window, y, &self.body_style, true, color)
}
fn draw_body_content(&self, window: &mut dyn Window, start_y: u16) -> Result<()> {
let (inner_width, inner_height) = self.get_inner_dimensions();
for y_offset in 0..inner_height + (self.padding * 2) {
let y = start_y + y_offset;
if let Some(color) = self.body_border_color {
window.write_str_colored(y, 0, &self.body_style.vertical.to_string(), color)?;
} else {
window.write_str(y, 0, &self.body_style.vertical.to_string())?;
}
if let Some(color) = self.body_border_color {
window.write_str_colored(
y,
self.width - 1,
&self.body_style.vertical.to_string(),
color,
)?;
} else {
window.write_str(y, self.width - 1, &self.body_style.vertical.to_string())?;
}
}
let content_start_x = 1 + self.padding;
let content_start_y = start_y + self.padding;
let content_width = inner_width;
let content_height = inner_height;
if content_width > 0 && content_height > 0 {
let mut content_window = WindowView {
window,
x_offset: content_start_x,
y_offset: content_start_y,
width: content_width,
height: content_height,
};
match &self.body_content {
PanelContent::Text(text) => {
self.draw_text_content(&mut content_window, text)?;
}
PanelContent::Block(text_block) => {
text_block.draw(&mut content_window)?;
}
}
}
Ok(())
}
fn draw_text_content(&self, window: &mut dyn Window, text: &str) -> Result<()> {
let lines: Vec<&str> = text.lines().collect();
let (window_width, window_height) = window.get_size();
for (i, line) in lines.iter().enumerate() {
if i as u16 >= window_height {
break; }
let x_pos = match self.alignment {
Alignment::Left => 0,
Alignment::Center => {
let line_len = line.len() as u16;
if line_len < window_width {
(window_width - line_len) / 2
} else {
0
}
}
Alignment::Right => {
let line_len = line.len() as u16;
if line_len < window_width {
window_width - line_len
} else {
0
}
}
};
if let Some(color) = self.body_color {
window.write_str_colored(i as u16, x_pos, line, color)?;
} else {
window.write_str(i as u16, x_pos, line)?;
}
}
Ok(())
}
fn draw_bottom_border(&self, window: &mut dyn Window, y: u16) -> Result<()> {
let color = self.body_border_color;
self.draw_horizontal_line(window, y, &self.body_style, false, color)
}
fn draw_horizontal_line(
&self,
window: &mut dyn Window,
y: u16,
style: &BorderChars,
is_top: bool,
color: Option<ColorPair>,
) -> Result<()> {
let left_char = if is_top {
style.top_left
} else {
style.bottom_left
};
let right_char = if is_top {
style.top_right
} else {
style.bottom_right
};
if let Some(color) = color {
window.write_str_colored(y, 0, &left_char.to_string(), color)?;
} else {
window.write_str(y, 0, &left_char.to_string())?;
}
for x in 1..self.width - 1 {
if let Some(color) = color {
window.write_str_colored(y, x, &style.horizontal.to_string(), color)?;
} else {
window.write_str(y, x, &style.horizontal.to_string())?;
}
}
if let Some(color) = color {
window.write_str_colored(y, self.width - 1, &right_char.to_string(), color)?;
} else {
window.write_str(y, self.width - 1, &right_char.to_string())?;
}
Ok(())
}
}