use cursive::{
align::HAlign,
event::{Event, EventResult},
theme::ColorStyle,
view::ViewWrapper,
Printer, Rect, Vec2, View,
};
use crate::config::{BorderStyle, CONFIG};
pub struct Panel<V> {
view: V,
title: String,
title_position: HAlign,
border: Border,
invalidated: bool,
}
pub struct Border {
upper_left: String,
upper_right: String,
lower_left: String,
lower_right: String,
horizontal: String,
vertical: String,
title_left: String,
title_right: String,
}
impl From<BorderStyle> for Border {
fn from(style: BorderStyle) -> Self {
match style {
BorderStyle::Light | BorderStyle::Default => Border {
upper_left: "\u{250C}".to_string(),
upper_right: "\u{2510}".to_string(),
lower_left: "\u{2514}".to_string(),
lower_right: "\u{2518}".to_string(),
horizontal: "\u{2500}".to_string(),
vertical: "\u{2502}".to_string(),
title_left: "\u{2524}".to_string(),
title_right: "\u{251C}".to_string(),
},
BorderStyle::Heavy => Border {
upper_left: "\u{2554}".to_string(),
upper_right: "\u{2557}".to_string(),
lower_left: "\u{255A}".to_string(),
lower_right: "\u{255D}".to_string(),
horizontal: "\u{2550}".to_string(),
vertical: "\u{2551}".to_string(),
title_left: "\u{2563}".to_string(),
title_right: "\u{2560}".to_string(),
},
BorderStyle::Round => Border {
upper_left: "\u{256D}".to_string(),
upper_right: "\u{256E}".to_string(),
lower_left: "\u{2570}".to_string(),
lower_right: "\u{256F}".to_string(),
horizontal: "\u{2500}".to_string(),
vertical: "\u{2502}".to_string(),
title_left: "\u{2524}".to_string(),
title_right: "\u{251C}".to_string(),
},
}
}
}
const TITLE_SPACING: usize = 3;
impl<V> Panel<V> {
pub fn new<B: Into<Border>>(view: V, border: B) -> Self {
Panel {
view,
title: String::new(),
title_position: HAlign::Center,
border: border.into(),
invalidated: true,
}
}
#[must_use]
pub fn title<S: Into<String>>(mut self, label: S) -> Self {
self.title = label.into();
self.invalidated = true;
self
}
fn draw_title(&self, printer: &Printer) {
if self.title.is_empty() {
return;
}
let available = match printer.size.x.checked_sub(2 * TITLE_SPACING) {
Some(available) => available,
None => return,
};
let len = std::cmp::min(self.title.chars().count(), available);
let x = TITLE_SPACING + self.title_position.get_offset(len, available);
printer
.offset((x, 0))
.cropped((len, 1))
.with_color(ColorStyle::title_primary(), |p| {
p.print((0, 0), &self.title)
});
printer.with_high_border(false, |printer| {
printer.print((x - 2, 0), &format!("{} ", self.border.title_left));
printer.print((x + len, 0), &format!(" {}", self.border.title_right));
});
}
fn draw_border(&self, printer: &Printer) {
let start: Vec2 = (0, 0).into();
let size: Vec2 = printer.size;
if size.x < 2 || size.y < 2 {
return;
}
let size = size.saturating_sub((1, 1));
printer.with_high_border(true, |s| {
s.print(start, &self.border.upper_left);
s.print(start + size.keep_y(), &self.border.lower_left);
s.print_hline(start + (1, 0), size.x - 1, &self.border.horizontal);
s.print_vline(start + (0, 1), size.y - 1, &self.border.vertical);
});
printer.with_low_border(true, |s| {
s.print(start + size.keep_x(), &self.border.upper_right);
s.print(start + size, &self.border.lower_right);
s.print_hline(
start + (1, 0) + size.keep_y(),
size.x - 1,
&self.border.horizontal,
);
s.print_vline(
start + (0, 1) + size.keep_x(),
size.y - 1,
&self.border.vertical,
);
});
}
inner_getters!(self.view: V);
}
impl<V: View> ViewWrapper for Panel<V> {
wrap_impl!(self.view: V);
fn wrap_on_event(&mut self, event: Event) -> EventResult {
self.view.on_event(event.relativized((1, 1)))
}
fn wrap_required_size(&mut self, req: Vec2) -> Vec2 {
let req = req.saturating_sub((2, 2));
let size = self.view.required_size(req).saturating_add((2, 2));
if self.title.is_empty() {
return size;
}
let title_width = self.title.chars().count() + 2 * TITLE_SPACING;
size.or_max((title_width, 0))
}
fn wrap_draw(&self, printer: &Printer) {
self.draw_border(printer);
self.draw_title(printer);
let printer = printer.offset((1, 1)).shrinked((1, 1));
self.view.draw(&printer);
}
fn wrap_layout(&mut self, size: Vec2) {
self.view.layout(size.saturating_sub((2, 2)));
}
fn wrap_important_area(&self, size: Vec2) -> Rect {
let inner_size = size.saturating_sub((2, 2));
self.view.important_area(inner_size) + (1, 1)
}
fn wrap_needs_relayout(&self) -> bool {
self.invalidated || self.view.needs_relayout()
}
}
pub trait WithPanel: View + Sized {
fn with_panel(self) -> Panel<Self> {
Panel::new(self, CONFIG.theme.border)
}
}
impl<T: View> WithPanel for T {}