cursive-extras 0.13.4

Extra views for the Cursive TUI library as well some helper functions and macros
Documentation
use crate::SpannedStrExt;
use cursive_core::{
    Printer, Vec2,
    theme::{
        BaseColor,
        Color,
        ColorStyle,
        ColorType
    },
    utils::{
        markup::StyledString,
        lines::spans::{Row, LinesIterator}
    }
};
use unicode_width::UnicodeWidthStr;

mod button;
mod select_view;

pub use button::*;
pub use select_view::*;

// the content for the AdvancedButton title and AdvancedSelectView items
#[derive(Clone)]
enum ButtonContent {
    SingleRow(StyledString),
    MultiRow(Vec<Row>, StyledString)
}

impl ButtonContent {
    fn new<S: Into<StyledString>>(label: S) -> ButtonContent {
        Self::SingleRow(label.into())
    }

    fn get_content(&self) -> &StyledString {
        match self {
            Self::SingleRow(ref label) | Self::MultiRow(_, ref label) => label,
        }
    }

    fn get_content_mut(&mut self) -> &mut StyledString {
        match self {
            Self::SingleRow(ref mut label) | Self::MultiRow(_, ref mut label) => label,
        }
    }

    fn set_content<S: Into<StyledString>>(&mut self, new_label: S) {
        match self {
            Self::SingleRow(ref mut label) | Self::MultiRow(_, ref mut label) => *label = new_label.into()
        }
    }

    // resize the content to fit in the specified width
    fn fit_to_width(&mut self, width: usize) {
        if width == 0 { return; }
        let content = self.get_content().clone();

        if width < content.width() || content.source().contains('\n'){
            let rows: Vec<Row> = LinesIterator::new(content.as_spanned_str(), width).collect();
            *self = Self::MultiRow(rows, content);
        }
        else { *self = Self::SingleRow(content); }
    }

    // the size of the content
    fn size(&self, brackets: bool) -> Vec2 {
        match self {
            Self::SingleRow(ref label) => (label.width() + if brackets { 2 } else { 0 }, 1).into(),

            Self::MultiRow(ref rows, _) => {
                let width = rows
                    .iter()
                    .map(|row| row.width)
                    .max()
                    .unwrap_or(0);

                let height = rows.len();

                if let Some(first_width) = rows.iter().map(|row| row.width).next() {
                    if first_width >= width && brackets {
                        return (width + 1, height).into();
                    }
                }
                if let Some(last_width) = rows.iter().map(|row| row.width).last() {
                    if last_width >= width && brackets {
                        return (width + 1, height).into();
                    }
                }

                (width, height).into()
            }
        }
    }

    fn draw(&self, printer: &Printer, loc: Vec2, enabled: bool, focused: bool, brackets: bool) {
        if printer.size.x == 0 { return; }

        // the default style
        let style = if !enabled { ColorStyle::secondary() }
        else if focused { ColorStyle::highlight() }
        else { ColorStyle::primary() };

        printer.with_style(style, |printer| {
            match self {
                Self::SingleRow(ref label) => {
                    if brackets {
                        let len = label.width();
                        printer.print(loc, "<");
                        printer.print(loc + (len + 1, 0), ">");
                    }
                    let mut x = brackets as usize;
                    for span in label.spans() {
                        // style for the span
                        let new_style = if focused && enabled {
                            if matches!(span.attr.color.front, ColorType::InheritParent | ColorType::Palette(_)) && matches!(span.attr.color.front, ColorType::InheritParent | ColorType::Palette(_)) {
                                style
                            }

                            // instead of using the highlight color,
                            // highlight with the span's front color
                            else { ColorStyle::new(Color::Dark(BaseColor::Black), span.attr.color.front) }
                        }
                        else if enabled { span.attr.color }
                        else {
                            let back = span.attr.color.back;
                            match span.attr.color.front {
                                ColorType::Color(Color::Light(base)) => ColorStyle::new(Color::Dark(base), back),
                                c => ColorStyle::from(c)
                            }
                        };

                        printer.with_style(new_style, |printer| printer.print(loc + (x, 0), span.content));
                        x += span.content.width();
                    }
                }

                Self::MultiRow(ref rows, ref label) => {
                    for (y, row) in rows.iter().enumerate() {
                        printer.with_style(style, |printer| {
                            // the "background" of the button is spaces styled with the default style
                            printer.print_hline(loc + (0, y), self.size(brackets).x, " ");
                            if brackets {
                                if y == 0 {
                                    printer.print(loc, "<");
                                }
                                else if y == rows.len() - 1 {
                                    printer.print(loc + (row.width, y), ">");
                                }
                            }
                        });
                        let mut x = (brackets && y == 0) as usize;
                        // draw the spanned row
                        for span in row.resolve(label.as_spanned_str()) {
                            // style for the span
                            let new_style = if focused && enabled {
                                if matches!(span.attr.color.front, ColorType::InheritParent | ColorType::Palette(_)) && matches!(span.attr.color.front, ColorType::InheritParent | ColorType::Palette(_)) {
                                    style
                                }

                                // instead of using the highlight color,
                                // highlight with the span's front color
                                else { ColorStyle::new(Color::Dark(BaseColor::Black), span.attr.color.front) }
                            }
                            else if enabled { span.attr.color }
                            else {
                                let back = span.attr.color.back;
                                match span.attr.color.front {
                                    ColorType::Color(Color::Light(base)) => ColorStyle::new(Color::Dark(base), back),
                                    c => ColorStyle::from(c)
                                }
                            };

                            printer.with_style(new_style, |printer| printer.print(loc + (x, y), span.content));
                            x += span.content.width();
                        }
                    }
                }
            }
        });
    }
}