idiom_tui 1.0.1

Bundel of tui widgest and layout componenets (split from idiom editor)
Documentation
use crate::{
    backend::Backend,
    layout::{DoublePaddedRectIter, IterLines, LineBuilder, Rect},
};

#[derive(PartialEq, Debug)]
pub struct State<B: Backend> {
    pub at_line: usize,
    pub selected: usize,
    pub highlight: <B as Backend>::Style,
}

impl<B: Backend> Clone for State<B> {
    fn clone(&self) -> Self {
        Self {
            at_line: self.at_line,
            selected: self.selected,
            highlight: self.highlight.clone(),
        }
    }
}

impl<B: Backend> Default for State<B> {
    fn default() -> Self {
        Self::new()
    }
}

#[allow(dead_code)]
impl<B: Backend> State<B> {
    pub fn new() -> Self {
        let highlight = B::reversed_style();
        Self {
            at_line: 0,
            selected: 0,
            highlight,
        }
    }

    pub fn with_highlight(highlight: <B as Backend>::Style) -> Self {
        Self {
            at_line: 0,
            selected: 0,
            highlight,
        }
    }

    #[inline]
    pub fn reset(&mut self) {
        self.at_line = 0;
        self.selected = 0;
    }

    pub fn select(&mut self, idx: usize, option_len: usize) {
        if option_len > idx {
            self.selected = idx;
        }
    }

    pub fn next(&mut self, option_len: usize) {
        self.selected += 1;
        if self.selected >= option_len {
            self.selected = 0;
        };
    }

    pub fn prev(&mut self, option_len: usize) {
        if self.selected > 0 {
            self.selected -= 1;
        } else if option_len > 0 {
            self.selected = option_len - 1;
        };
    }

    #[inline]
    pub fn update_at_line(&mut self, limit: usize) {
        if self.at_line > self.selected {
            self.at_line = self.selected;
        } else if self.selected - self.at_line >= limit {
            self.at_line = self.selected - limit + 1;
        };
    }

    #[inline]
    pub fn render_list_complex<T>(
        &mut self,
        options: &[T],
        callbacks: &[fn(&T, builder: LineBuilder<B>)],
        rect: Rect,
        backend: &mut B,
    ) {
        let limit = rect.height as usize / callbacks.len();
        self.update_at_line(limit);
        let mut lines = rect.into_iter();
        for (idx, option) in options.iter().enumerate().skip(self.at_line) {
            if idx == self.selected {
                backend.set_style(self.highlight.clone());
                for callback in callbacks {
                    match lines.next() {
                        Some(line) => {
                            (callback)(option, line.unsafe_builder(backend));
                        }
                        None => break,
                    };
                }
                backend.reset_style();
                continue;
            };
            for callback in callbacks {
                match lines.next() {
                    Some(line) => {
                        (callback)(option, line.unsafe_builder(backend));
                    }
                    None => break,
                };
            }
        }
        backend.reset_style();
        for line in lines {
            line.render_empty(backend);
        }
    }

    #[inline]
    pub fn render_list_styled<'a>(
        &mut self,
        options: impl Iterator<Item = (&'a str, <B as Backend>::Style)>,
        rect: &Rect,
        backend: &mut B,
    ) {
        self.update_at_line(rect.height as usize);
        let mut lines = rect.into_iter();
        for (idx, (text, mut style)) in options.enumerate().skip(self.at_line) {
            let Some(line) = lines.next() else { break };
            if idx == self.selected {
                style = B::merge_style(style, self.highlight.clone());
            }
            line.render_styled(text, style, backend);
        }
        lines.clear_to_end(backend);
    }

    pub fn render_list<'a>(
        &mut self,
        options: impl Iterator<Item = &'a str>,
        rect: Rect,
        backend: &mut B,
    ) {
        self.update_at_line(rect.height as usize);
        let mut lines = rect.into_iter();
        for (idx, text) in options.enumerate().skip(self.at_line) {
            let Some(line) = lines.next() else { break };
            match idx == self.selected {
                true => line.render_styled(text, self.highlight.clone(), backend),
                false => line.render(text, backend),
            }
        }
        lines.clear_to_end(backend);
    }

    pub fn render_list_padded<'a>(
        &mut self,
        options: impl Iterator<Item = &'a str>,
        mut lines: DoublePaddedRectIter,
        backend: &mut B,
    ) {
        self.update_at_line(lines.len());
        for (idx, text) in options.enumerate().skip(self.at_line) {
            let Some(line) = lines.next_padded(backend) else {
                break;
            };
            match idx == self.selected {
                true => line.render_styled(text, self.highlight.clone(), backend),
                false => line.render(text, backend),
            };
        }
        lines.clear_to_end(backend);
    }
}