ratatui_toolkit/widgets/file_system_tree/traits/
stateful_widget.rs1use 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
16fn entry_matches_filter(entry: &FileSystemEntry, filter: &Option<String>) -> bool {
20 matches_filter(&entry.name, filter)
21}
22
23fn 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 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 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 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 if show_filter_line && area.height > 1 {
128 render_filter_line(state.filter.as_deref(), filter_mode, area, buf);
129 }
130 }
131}