ratatui_toolkit/widgets/file_system_tree/traits/
stateful_widget.rs

1use ratatui::{
2    buffer::Buffer,
3    layout::Rect,
4    style::{Color, Modifier, Style},
5    text::{Line, Span},
6    widgets::StatefulWidget,
7};
8
9use crate::primitives::tree_view::{matches_filter, TreeViewRef, TreeViewState};
10use crate::widgets::file_system_tree::{FileSystemEntry, FileSystemTree};
11
12use crate::widgets::file_system_tree::traits::get_ayu_dark_color::get_ayu_dark_color;
13use crate::widgets::file_system_tree::traits::get_custom_icon::get_custom_icon;
14use devicons::{icon_for_file, Theme as DevIconTheme};
15
16/// Filter function for FileSystemEntry nodes.
17///
18/// Returns true if the entry's name matches the filter (case-insensitive contains).
19fn entry_matches_filter(entry: &FileSystemEntry, filter: &Option<String>) -> bool {
20    matches_filter(&entry.name, filter)
21}
22
23/// Renders the filter input line at the bottom of the tree.
24fn render_filter_line(filter_text: Option<&str>, filter_mode: bool, area: Rect, buf: &mut Buffer) {
25    if area.height == 0 {
26        return;
27    }
28
29    let y = area.y + area.height - 1;
30
31    // Build the filter line
32    let filter_str = filter_text.unwrap_or("");
33    let cursor = if filter_mode { "_" } else { "" };
34
35    let line = Line::from(vec![
36        Span::styled(
37            "/ ",
38            Style::default()
39                .fg(Color::Yellow)
40                .add_modifier(Modifier::BOLD),
41        ),
42        Span::styled(filter_str, Style::default().fg(Color::White)),
43        Span::styled(
44            cursor,
45            Style::default()
46                .fg(Color::Yellow)
47                .add_modifier(Modifier::SLOW_BLINK),
48        ),
49    ]);
50
51    // Fill background for the filter line
52    let bg_style = Style::default().bg(Color::Rgb(15, 25, 40));
53    for x in area.x..(area.x + area.width) {
54        buf[(x, y)].set_style(bg_style);
55    }
56
57    buf.set_line(area.x, y, &line, area.width);
58}
59
60impl<'a> StatefulWidget for FileSystemTree<'a> {
61    type State = TreeViewState;
62
63    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
64        let config = self.config;
65        let block = self.block;
66        let filter_mode = state.filter_mode;
67        let has_filter = state.filter.as_ref().is_some_and(|f| !f.is_empty());
68        let show_filter_line = filter_mode || has_filter;
69
70        // Calculate tree area (leave room for filter line if needed)
71        let tree_area = if show_filter_line && area.height > 1 {
72            Rect {
73                height: area.height - 1,
74                ..area
75            }
76        } else {
77            area
78        };
79
80        let tree_view = TreeViewRef::new(&self.nodes)
81            .icons("", "")
82            .render_fn(move |entry, node_state| {
83                let (icon_glyph, icon_color) = if entry.is_dir {
84                    if node_state.is_expanded {
85                        ('\u{f07c}', Color::Rgb(31, 111, 136))
86                    } else {
87                        ('\u{f07b}', Color::Rgb(31, 111, 136))
88                    }
89                } else {
90                    let theme = if config.use_dark_theme {
91                        DevIconTheme::Dark
92                    } else {
93                        DevIconTheme::Light
94                    };
95                    let icon_char = if let Some((custom_icon, _)) = get_custom_icon(&entry.name) {
96                        custom_icon
97                    } else {
98                        let file_icon = icon_for_file(&entry.name, &Some(theme));
99                        file_icon.icon
100                    };
101                    let color = get_ayu_dark_color(&entry.name);
102                    (icon_char, color)
103                };
104
105                let style = ratatui::style::Style::default().fg(icon_color);
106
107                Line::from(vec![
108                    Span::styled(
109                        format!("{} ", icon_glyph),
110                        ratatui::style::Style::default().fg(icon_color),
111                    ),
112                    Span::styled(entry.name.clone(), style),
113                ])
114            })
115            .filter_fn(entry_matches_filter)
116            .highlight_style(Style::default().bg(Color::Rgb(15, 25, 40)));
117
118        let tree_view = if let Some(block) = block {
119            tree_view.block(block)
120        } else {
121            tree_view
122        };
123
124        tree_view.render(tree_area, buf, state);
125
126        // Render filter line if needed
127        if show_filter_line && area.height > 1 {
128            render_filter_line(state.filter.as_deref(), filter_mode, area, buf);
129        }
130    }
131}