tui_logger/widget/
smart.rs

1use crate::widget::logformatter::LogFormatter;
2use parking_lot::Mutex;
3use std::sync::Arc;
4use unicode_segmentation::UnicodeSegmentation;
5
6use log::LevelFilter;
7use ratatui::{
8    buffer::Buffer,
9    layout::{Constraint, Direction, Layout, Rect},
10    style::Style,
11    text::Line,
12    widgets::{Block, BorderType, Borders, Widget},
13};
14
15use crate::{TuiLoggerLevelOutput, TuiLoggerTargetWidget, TuiWidgetState, TUI_LOGGER};
16
17use super::{inner::TuiWidgetInnerState, standard::TuiLoggerWidget};
18
19/// The Smart Widget combines the TuiLoggerWidget and the TuiLoggerTargetWidget
20/// into a nice combo, where the TuiLoggerTargetWidget can be shown/hidden.
21///
22/// In the title the number of logging messages/s in the whole buffer is shown.
23pub struct TuiLoggerSmartWidget<'a> {
24    title_log: Line<'a>,
25    title_target: Line<'a>,
26    style: Option<Style>,
27    border_style: Style,
28    border_type: BorderType,
29    highlight_style: Option<Style>,
30    logformatter: Option<Box<dyn LogFormatter>>,
31    style_error: Option<Style>,
32    style_warn: Option<Style>,
33    style_debug: Option<Style>,
34    style_trace: Option<Style>,
35    style_info: Option<Style>,
36    style_show: Option<Style>,
37    style_hide: Option<Style>,
38    style_off: Option<Style>,
39    format_separator: Option<char>,
40    format_timestamp: Option<Option<String>>,
41    format_output_level: Option<Option<TuiLoggerLevelOutput>>,
42    format_output_target: Option<bool>,
43    format_output_file: Option<bool>,
44    format_output_line: Option<bool>,
45    state: Arc<Mutex<TuiWidgetInnerState>>,
46}
47impl<'a> Default for TuiLoggerSmartWidget<'a> {
48    fn default() -> Self {
49        //TUI_LOGGER.move_events();
50        TuiLoggerSmartWidget {
51            title_log: Line::from("Tui Log"),
52            title_target: Line::from("Tui Target Selector"),
53            style: None,
54            border_style: Style::default(),
55            border_type: BorderType::Plain,
56            highlight_style: None,
57            logformatter: None,
58            style_error: None,
59            style_warn: None,
60            style_debug: None,
61            style_trace: None,
62            style_info: None,
63            style_show: None,
64            style_hide: None,
65            style_off: None,
66            format_separator: None,
67            format_timestamp: None,
68            format_output_level: None,
69            format_output_target: None,
70            format_output_file: None,
71            format_output_line: None,
72            state: Arc::new(Mutex::new(TuiWidgetInnerState::new())),
73        }
74    }
75}
76impl<'a> TuiLoggerSmartWidget<'a> {
77    pub fn highlight_style(mut self, style: Style) -> Self {
78        self.highlight_style = Some(style);
79        self
80    }
81    pub fn border_style(mut self, style: Style) -> Self {
82        self.border_style = style;
83        self
84    }
85    pub fn border_type(mut self, border_type: BorderType) -> Self {
86        self.border_type = border_type;
87        self
88    }
89    pub fn style(mut self, style: Style) -> Self {
90        self.style = Some(style);
91        self
92    }
93    pub fn style_error(mut self, style: Style) -> Self {
94        self.style_error = Some(style);
95        self
96    }
97    pub fn style_warn(mut self, style: Style) -> Self {
98        self.style_warn = Some(style);
99        self
100    }
101    pub fn style_info(mut self, style: Style) -> Self {
102        self.style_info = Some(style);
103        self
104    }
105    pub fn style_trace(mut self, style: Style) -> Self {
106        self.style_trace = Some(style);
107        self
108    }
109    pub fn style_debug(mut self, style: Style) -> Self {
110        self.style_debug = Some(style);
111        self
112    }
113    pub fn style_off(mut self, style: Style) -> Self {
114        self.style_off = Some(style);
115        self
116    }
117    pub fn style_hide(mut self, style: Style) -> Self {
118        self.style_hide = Some(style);
119        self
120    }
121    pub fn style_show(mut self, style: Style) -> Self {
122        self.style_show = Some(style);
123        self
124    }
125    /// Separator character between field.
126    /// Default is ':'
127    pub fn output_separator(mut self, sep: char) -> Self {
128        self.format_separator = Some(sep);
129        self
130    }
131    /// The format string can be defined as described in
132    /// <https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html>
133    ///
134    /// If called with None, timestamp is not included in output.
135    ///
136    /// Default is %H:%M:%S
137    pub fn output_timestamp(mut self, fmt: Option<String>) -> Self {
138        self.format_timestamp = Some(fmt);
139        self
140    }
141    /// Possible values are
142    /// - TuiLoggerLevelOutput::Long        => DEBUG/TRACE/...
143    /// - TuiLoggerLevelOutput::Abbreviated => D/T/...
144    ///
145    /// If called with None, level is not included in output.
146    ///
147    /// Default is Long
148    pub fn output_level(mut self, level: Option<TuiLoggerLevelOutput>) -> Self {
149        self.format_output_level = Some(level);
150        self
151    }
152    /// Enables output of target field of event
153    ///
154    /// Default is true
155    pub fn output_target(mut self, enabled: bool) -> Self {
156        self.format_output_target = Some(enabled);
157        self
158    }
159    /// Enables output of file field of event
160    ///
161    /// Default is true
162    pub fn output_file(mut self, enabled: bool) -> Self {
163        self.format_output_file = Some(enabled);
164        self
165    }
166    /// Enables output of line field of event
167    ///
168    /// Default is true
169    pub fn output_line(mut self, enabled: bool) -> Self {
170        self.format_output_line = Some(enabled);
171        self
172    }
173    pub fn title_target<T>(mut self, title: T) -> Self
174    where
175        T: Into<Line<'a>>,
176    {
177        self.title_target = title.into();
178        self
179    }
180    pub fn title_log<T>(mut self, title: T) -> Self
181    where
182        T: Into<Line<'a>>,
183    {
184        self.title_log = title.into();
185        self
186    }
187    pub fn state(mut self, state: &TuiWidgetState) -> Self {
188        self.state = state.inner.clone();
189        self
190    }
191}
192impl<'a> Widget for TuiLoggerSmartWidget<'a> {
193    /// Nothing to draw for combo widget
194    fn render(self, area: Rect, buf: &mut Buffer) {
195        let entries_s = {
196            let mut tui_lock = TUI_LOGGER.inner.lock();
197            let first_timestamp = tui_lock
198                .events
199                .iter()
200                .next()
201                .map(|entry| entry.timestamp.timestamp_millis());
202            let last_timestamp = tui_lock
203                .events
204                .rev_iter()
205                .next()
206                .map(|entry| entry.timestamp.timestamp_millis());
207            if let Some(first) = first_timestamp {
208                if let Some(last) = last_timestamp {
209                    let dt = last - first;
210                    if dt > 0 {
211                        tui_lock.events.len() as f64 / (dt as f64) * 1000.0
212                    } else {
213                        0.0
214                    }
215                } else {
216                    0.0
217                }
218            } else {
219                0.0
220            }
221        };
222
223        let mut title_log = self.title_log.clone();
224        title_log
225            .spans
226            .push(format!(" [log={:.1}/s]", entries_s).into());
227
228        let hide_target = self.state.lock().hide_target;
229        if hide_target {
230            let tui_lw = TuiLoggerWidget::default()
231                .block(
232                    Block::default()
233                        .title(title_log)
234                        .border_style(self.border_style)
235                        .border_type(self.border_type)
236                        .borders(Borders::ALL),
237                )
238                .opt_style(self.style)
239                .opt_style_error(self.style_error)
240                .opt_style_warn(self.style_warn)
241                .opt_style_info(self.style_info)
242                .opt_style_debug(self.style_debug)
243                .opt_style_trace(self.style_trace)
244                .opt_output_separator(self.format_separator)
245                .opt_output_timestamp(self.format_timestamp)
246                .opt_output_level(self.format_output_level)
247                .opt_output_target(self.format_output_target)
248                .opt_output_file(self.format_output_file)
249                .opt_output_line(self.format_output_line)
250                .inner_state(self.state);
251            tui_lw.render(area, buf);
252        } else {
253            let mut width: usize = 0;
254            {
255                let hot_targets = &TUI_LOGGER.inner.lock().targets;
256                let mut state = self.state.lock();
257                let hide_off = state.hide_off;
258                {
259                    let targets = &mut state.config;
260                    targets.merge(hot_targets);
261                    for (t, levelfilter) in targets.iter() {
262                        if hide_off && levelfilter == &LevelFilter::Off {
263                            continue;
264                        }
265                        width = width.max(t.graphemes(true).count())
266                    }
267                }
268            }
269            let chunks = Layout::default()
270                .direction(Direction::Horizontal)
271                .constraints(vec![
272                    Constraint::Length(width as u16 + 6 + 2),
273                    Constraint::Min(10),
274                ])
275                .split(area);
276            let tui_ltw = TuiLoggerTargetWidget::default()
277                .block(
278                    Block::default()
279                        .title(self.title_target)
280                        .border_style(self.border_style)
281                        .border_type(self.border_type)
282                        .borders(Borders::ALL),
283                )
284                .opt_style(self.style)
285                .opt_highlight_style(self.highlight_style)
286                .opt_style_off(self.style_off)
287                .opt_style_hide(self.style_hide)
288                .opt_style_show(self.style_show)
289                .inner_state(self.state.clone());
290            tui_ltw.render(chunks[0], buf);
291            let tui_lw = TuiLoggerWidget::default()
292                .block(
293                    Block::default()
294                        .title(title_log)
295                        .border_style(self.border_style)
296                        .border_type(self.border_type)
297                        .borders(Borders::ALL),
298                )
299                .opt_formatter(self.logformatter)
300                .opt_style(self.style)
301                .opt_style_error(self.style_error)
302                .opt_style_warn(self.style_warn)
303                .opt_style_info(self.style_info)
304                .opt_style_debug(self.style_debug)
305                .opt_style_trace(self.style_trace)
306                .opt_output_separator(self.format_separator)
307                .opt_output_timestamp(self.format_timestamp)
308                .opt_output_level(self.format_output_level)
309                .opt_output_target(self.format_output_target)
310                .opt_output_file(self.format_output_file)
311                .opt_output_line(self.format_output_line)
312                .inner_state(self.state.clone());
313            tui_lw.render(chunks[1], buf);
314        }
315    }
316}