Skip to main content

ansiq_runtime/
draw.rs

1use ansiq_core::{Alignment, ElementKind, Node, Rect, Style};
2use ansiq_render::FrameBuffer;
3
4use crate::draw_border::draw_border;
5use crate::draw_common::{
6    border_style, fill_surface, fill_surface_in_regions, intersects_any, single_title, title_style,
7};
8use crate::draw_cursor::find_cursor_position;
9use crate::draw_scrollbar::draw_scrollbar;
10use crate::draw_table::draw_table;
11use crate::draw_text::{draw_paragraph, draw_rich_text, draw_scroll_text, draw_text, text_content};
12use crate::draw_widgets::{
13    draw_bar_chart, draw_canvas, draw_chart, draw_gauge, draw_line_gauge, draw_list, draw_monthly,
14    draw_sparkline, draw_tabs,
15};
16
17pub fn draw_tree<Message>(tree: &Node<Message>, focused: Option<usize>, buffer: &mut FrameBuffer) {
18    buffer.fill_rect(
19        Rect::new(0, 0, buffer.width(), buffer.height()),
20        ' ',
21        tree.element.style,
22    );
23    draw_node(tree, focused, buffer);
24}
25
26pub fn draw_tree_in_regions<Message>(
27    tree: &Node<Message>,
28    focused: Option<usize>,
29    buffer: &mut FrameBuffer,
30    regions: &[Rect],
31) {
32    if regions.is_empty() {
33        return;
34    }
35
36    for region in regions {
37        if let Some(clip) = region.intersection(Rect::new(0, 0, buffer.width(), buffer.height())) {
38            buffer.fill_rect(clip, ' ', tree.element.style);
39        }
40    }
41
42    draw_node_in_regions(tree, focused, buffer, regions);
43}
44
45pub fn cursor_position<Message>(
46    tree: &Node<Message>,
47    focused: Option<usize>,
48) -> Option<(u16, u16)> {
49    let focused = focused?;
50    find_cursor_position(tree, focused)
51}
52
53fn draw_node<Message>(node: &Node<Message>, focused: Option<usize>, buffer: &mut FrameBuffer) {
54    draw_node_impl(node, focused, buffer, None);
55}
56
57fn draw_node_in_regions<Message>(
58    node: &Node<Message>,
59    focused: Option<usize>,
60    buffer: &mut FrameBuffer,
61    regions: &[Rect],
62) {
63    draw_node_impl(node, focused, buffer, Some(regions));
64}
65
66fn draw_node_impl<Message>(
67    node: &Node<Message>,
68    focused: Option<usize>,
69    buffer: &mut FrameBuffer,
70    regions: Option<&[Rect]>,
71) {
72    if regions.is_some_and(|regions| !intersects_any(node.rect, regions)) {
73        return;
74    }
75
76    match &node.element.kind {
77        ElementKind::Box(_) | ElementKind::Shell(_) => {
78            fill_node_surface(buffer, node.rect, node.element.style, regions);
79            draw_children(node, focused, buffer, regions);
80        }
81        ElementKind::Component(_) => {
82            draw_children(node, focused, buffer, regions);
83        }
84        ElementKind::Pane(props) => {
85            fill_node_surface(buffer, node.rect, node.element.style, regions);
86            let titles = single_title(props.title.as_deref());
87            draw_border(
88                buffer,
89                node.rect,
90                &titles,
91                Alignment::Left,
92                ansiq_core::TitlePosition::Top,
93                ansiq_core::Borders::ALL,
94                ansiq_core::BorderType::Rounded,
95                None,
96                border_style(node.element.style, Style::default(), false),
97                title_style(node.element.style, Style::default()),
98            );
99            draw_children(node, focused, buffer, regions);
100        }
101        ElementKind::Block(props) => {
102            fill_node_surface(buffer, node.rect, node.element.style, regions);
103            if !props.borders.is_empty() || !props.titles.is_empty() {
104                draw_border(
105                    buffer,
106                    node.rect,
107                    &props.titles,
108                    props.title_alignment,
109                    props.title_position,
110                    props.borders,
111                    props.border_type,
112                    props.border_set,
113                    border_style(node.element.style, props.border_style, false),
114                    title_style(node.element.style, props.title_style),
115                );
116            }
117            draw_children(node, focused, buffer, regions);
118        }
119        ElementKind::ScrollView(props) => {
120            fill_node_surface(buffer, node.rect, node.element.style, regions);
121            if let Some(child) = node.children.first() {
122                if let Some((content, style)) = text_content(child) {
123                    draw_scroll_text(
124                        buffer,
125                        node.rect,
126                        &content,
127                        style,
128                        props.follow_bottom,
129                        props.offset,
130                    );
131                    return;
132                }
133            }
134
135            draw_children(node, focused, buffer, regions);
136        }
137        ElementKind::StreamingText(props) => {
138            fill_node_surface(buffer, node.rect, node.element.style, regions);
139            draw_text(buffer, node.rect, &props.content, node.element.style);
140        }
141        ElementKind::Text(props) => {
142            fill_node_surface(buffer, node.rect, node.element.style, regions);
143            draw_text(buffer, node.rect, &props.content, node.element.style);
144        }
145        ElementKind::Paragraph(props) => {
146            fill_node_surface(buffer, node.rect, node.element.style, regions);
147            draw_paragraph(buffer, node.rect, props, node.element.style);
148        }
149        ElementKind::RichText(props) => {
150            fill_node_surface(buffer, node.rect, node.element.style, regions);
151            draw_rich_text(buffer, node.rect, &props.block);
152        }
153        ElementKind::List(props) => {
154            fill_node_surface(buffer, node.rect, node.element.style, regions);
155            draw_list(buffer, node.rect, props, node.element.style);
156        }
157        ElementKind::Tabs(props) => {
158            fill_node_surface(buffer, node.rect, node.element.style, regions);
159            draw_tabs(buffer, node.rect, props, node.element.style);
160        }
161        ElementKind::Gauge(props) => {
162            fill_node_surface(buffer, node.rect, node.element.style, regions);
163            draw_gauge(buffer, node.rect, props, node.element.style);
164        }
165        ElementKind::Clear(_) => {
166            clear_node_surface(buffer, node.rect, regions);
167        }
168        ElementKind::LineGauge(props) => {
169            fill_node_surface(buffer, node.rect, node.element.style, regions);
170            draw_line_gauge(buffer, node.rect, props, node.element.style);
171        }
172        ElementKind::Table(props) => {
173            fill_node_surface(buffer, node.rect, node.element.style, regions);
174            draw_table(buffer, node.rect, props, node.element.style);
175        }
176        ElementKind::Sparkline(props) => {
177            fill_node_surface(buffer, node.rect, node.element.style, regions);
178            draw_sparkline(buffer, node.rect, props, node.element.style);
179        }
180        ElementKind::BarChart(props) => {
181            fill_node_surface(buffer, node.rect, node.element.style, regions);
182            draw_bar_chart(buffer, node.rect, props, node.element.style);
183        }
184        ElementKind::Chart(props) => {
185            fill_node_surface(buffer, node.rect, node.element.style, regions);
186            draw_chart(buffer, node.rect, props, node.element.style);
187        }
188        ElementKind::Canvas(props) => {
189            fill_node_surface(buffer, node.rect, node.element.style, regions);
190            draw_canvas(buffer, node.rect, props, node.element.style);
191        }
192        ElementKind::Monthly(props) => {
193            fill_node_surface(buffer, node.rect, node.element.style, regions);
194            draw_monthly(buffer, node.rect, props, node.element.style);
195        }
196        ElementKind::Scrollbar(props) => {
197            fill_node_surface(buffer, node.rect, node.element.style, regions);
198            draw_scrollbar(buffer, node.rect, props, node.element.style);
199        }
200        ElementKind::StatusBar(props) => {
201            fill_node_surface(buffer, node.rect, node.element.style, regions);
202            draw_text(buffer, node.rect, &props.content, node.element.style);
203        }
204        ElementKind::Input(props) => {
205            fill_node_surface(buffer, node.rect, node.element.style, regions);
206            let titles = Vec::new();
207            draw_border(
208                buffer,
209                node.rect,
210                &titles,
211                Alignment::Left,
212                ansiq_core::TitlePosition::Top,
213                ansiq_core::Borders::ALL,
214                ansiq_core::BorderType::Rounded,
215                None,
216                border_style(
217                    node.element.style,
218                    Style::default(),
219                    focused == Some(node.id),
220                ),
221                node.element.style,
222            );
223            let inner = node.rect.shrink(1);
224            let content = if props.value.is_empty() {
225                &props.placeholder
226            } else {
227                &props.value
228            };
229            let content_style = if props.value.is_empty() {
230                node.element.style.fg(ansiq_core::Color::Grey)
231            } else {
232                node.element.style
233            };
234            draw_text(buffer, inner, content, content_style);
235        }
236    }
237}
238
239fn draw_children<Message>(
240    node: &Node<Message>,
241    focused: Option<usize>,
242    buffer: &mut FrameBuffer,
243    regions: Option<&[Rect]>,
244) {
245    for child in &node.children {
246        match regions {
247            Some(regions) => draw_node_in_regions(child, focused, buffer, regions),
248            None => draw_node(child, focused, buffer),
249        }
250    }
251}
252
253fn fill_node_surface(buffer: &mut FrameBuffer, rect: Rect, style: Style, regions: Option<&[Rect]>) {
254    match regions {
255        Some(regions) => fill_surface_in_regions(buffer, rect, style, regions),
256        None => fill_surface(buffer, rect, style),
257    }
258}
259
260fn clear_node_surface(buffer: &mut FrameBuffer, rect: Rect, regions: Option<&[Rect]>) {
261    match regions {
262        Some(regions) => {
263            for region in regions {
264                if let Some(clip) = rect.intersection(*region) {
265                    buffer.fill_rect(clip, ' ', Style::default());
266                }
267            }
268        }
269        None => buffer.fill_rect(rect, ' ', Style::default()),
270    }
271}