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