use ratatui::style::Style;
use ratatui::text::{Line, Span};
use std::borrow::Cow;
use unicode_width::UnicodeWidthStr;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BreadcrumbItem<'a> {
pub label: Line<'a>,
pub style: Option<Style>,
pub selected_style: Option<Style>,
pub has_dropdown: bool,
pub dropdown_symbol: Option<Cow<'a, str>>,
}
impl<'a> BreadcrumbItem<'a> {
pub fn new<T>(label: T) -> Self
where
T: Into<Line<'a>>,
{
Self {
label: label.into(),
style: None,
selected_style: None,
has_dropdown: false,
dropdown_symbol: None,
}
}
pub fn with_dropdown<T>(label: T) -> Self
where
T: Into<Line<'a>>,
{
Self {
label: label.into(),
style: None,
selected_style: None,
has_dropdown: true,
dropdown_symbol: None,
}
}
#[must_use]
pub fn style(mut self, style: impl Into<Style>) -> Self {
self.style = Some(style.into());
self
}
#[must_use]
pub fn selected_style(mut self, style: impl Into<Style>) -> Self {
self.selected_style = Some(style.into());
self
}
#[must_use]
pub fn dropdown(mut self, has_dropdown: bool) -> Self {
self.has_dropdown = has_dropdown;
self
}
#[must_use]
pub fn dropdown_symbol(mut self, symbol: impl Into<Cow<'a, str>>) -> Self {
self.dropdown_symbol = Some(symbol.into());
self
}
#[must_use]
pub fn label_width(&self) -> usize {
self.label.width()
}
#[must_use]
pub fn total_width(&self, default_dropdown_symbol: &str) -> usize {
let base_width = self.label_width();
if self.has_dropdown {
let symbol = self
.dropdown_symbol
.as_deref()
.unwrap_or(default_dropdown_symbol);
base_width + 1 + UnicodeWidthStr::width(symbol)
} else {
base_width
}
}
}
impl<'a> From<Line<'a>> for BreadcrumbItem<'a> {
fn from(line: Line<'a>) -> Self {
Self::new(line)
}
}
impl<'a> From<Span<'a>> for BreadcrumbItem<'a> {
fn from(span: Span<'a>) -> Self {
Self::new(Line::from(span))
}
}
impl<'a> From<&'a str> for BreadcrumbItem<'a> {
fn from(s: &'a str) -> Self {
Self::new(Line::from(s))
}
}
impl From<String> for BreadcrumbItem<'static> {
fn from(s: String) -> Self {
Self::new(Line::from(s))
}
}
impl<'a> From<(&'a str, Style)> for BreadcrumbItem<'a> {
fn from((s, style): (&'a str, Style)) -> Self {
Self::new(Line::styled(s, style)).style(style)
}
}
impl From<(String, Style)> for BreadcrumbItem<'static> {
fn from((s, style): (String, Style)) -> Self {
Self::new(Line::styled(s, style)).style(style)
}
}
impl<'a> From<(Span<'a>, Style)> for BreadcrumbItem<'a> {
fn from((span, style): (Span<'a>, Style)) -> Self {
Self::new(Line::from(span)).style(style)
}
}
impl<'a> From<(Line<'a>, Style)> for BreadcrumbItem<'a> {
fn from((line, style): (Line<'a>, Style)) -> Self {
Self::new(line).style(style)
}
}
#[cfg(test)]
mod tests {
use super::*;
use ratatui::style::Color;
#[test]
fn test_breadcrumb_item_creation() {
let item = BreadcrumbItem::new("Home");
assert_eq!(item.label_width(), 4);
assert_eq!(item.total_width("▾"), 4);
assert!(!item.has_dropdown);
let item_dropdown = BreadcrumbItem::with_dropdown("src");
assert_eq!(item_dropdown.label_width(), 3);
assert_eq!(item_dropdown.total_width("▾"), 5);
assert!(item_dropdown.has_dropdown);
}
#[test]
fn test_breadcrumb_item_custom_dropdown() {
let item = BreadcrumbItem::new("config")
.dropdown(true)
.dropdown_symbol("▼");
assert_eq!(item.label_width(), 6);
assert_eq!(item.total_width("▾"), 8);
}
#[test]
fn test_breadcrumb_item_from_conversions() {
let item_str: BreadcrumbItem = "test".into();
assert_eq!(item_str.label_width(), 4);
let item_string: BreadcrumbItem = String::from("workspace").into();
assert_eq!(item_string.label_width(), 9);
let item_styled: BreadcrumbItem = ("styled", Style::default().fg(Color::Yellow)).into();
assert_eq!(item_styled.label_width(), 6);
assert_eq!(item_styled.style, Some(Style::default().fg(Color::Yellow)));
let span = Span::raw("span_test");
let item_span: BreadcrumbItem = span.into();
assert_eq!(item_span.label_width(), 9);
}
}