1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//!
//! Statusbar with multiple sections.
//!

use crate::_private::NonExhaustive;
use ratatui::buffer::Buffer;
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::style::Style;
use ratatui::text::Span;
#[cfg(feature = "unstable-widget-ref")]
use ratatui::widgets::StatefulWidgetRef;
use ratatui::widgets::{StatefulWidget, Widget};
use std::fmt::Debug;

/// Statusbar with multiple sections.
#[derive(Debug, Default, Clone)]
pub struct StatusLine {
    style: Vec<Style>,
    widths: Vec<Constraint>,
}

/// State & event handling.
#[derive(Debug, Clone)]
pub struct StatusLineState {
    /// Total area
    pub area: Rect,
    /// Areas for each section.
    pub areas: Vec<Rect>,
    /// Statustext for each section.
    pub status: Vec<String>,

    pub non_exhaustive: NonExhaustive,
}

impl StatusLine {
    /// New widget.
    pub fn new() -> Self {
        Self {
            style: Default::default(),
            widths: Default::default(),
        }
    }

    /// Layout for the sections.
    ///
    /// This layout determines the number of sections.
    /// If the styles or the status text vec differ, defaults are used.
    pub fn layout<It, Item>(mut self, widths: It) -> Self
    where
        It: IntoIterator<Item = Item>,
        Item: Into<Constraint>,
    {
        self.widths = widths.into_iter().map(|v| v.into()).collect();
        self
    }

    /// Styles for each section.
    pub fn styles(mut self, style: impl IntoIterator<Item = impl Into<Style>>) -> Self {
        self.style = style.into_iter().map(|v| v.into()).collect();
        self
    }
}

impl Default for StatusLineState {
    fn default() -> Self {
        Self {
            area: Default::default(),
            areas: Default::default(),
            status: Default::default(),
            non_exhaustive: NonExhaustive,
        }
    }
}

impl StatusLineState {
    pub fn new() -> Self {
        Self::default()
    }

    /// Clear all status text.
    pub fn clear_status(&mut self) {
        self.status.clear();
    }

    /// Set the specific status section.
    pub fn status<S: Into<String>>(&mut self, idx: usize, msg: S) {
        while self.status.len() <= idx {
            self.status.push("".to_string());
        }
        self.status[idx] = msg.into();
    }
}

#[cfg(feature = "unstable-widget-ref")]
impl StatefulWidgetRef for StatusLine {
    type State = StatusLineState;

    fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
        render_ref(self, area, buf, state);
    }
}

impl StatefulWidget for StatusLine {
    type State = StatusLineState;

    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
        render_ref(&self, area, buf, state);
    }
}

fn render_ref(widget: &StatusLine, area: Rect, buf: &mut Buffer, state: &mut StatusLineState) {
    state.area = area;

    let layout = Layout::horizontal(widget.widths.iter()).split(state.area);

    for (i, rect) in layout.iter().enumerate() {
        let style = widget.style.get(i).copied().unwrap_or_default();
        let txt = state.status.get(i).map(|v| v.as_str()).unwrap_or("");

        buf.set_style(*rect, style);
        Span::from(txt).render(*rect, buf);
    }
}