1use std::rc::Rc;
2
3use kano::{platform::Platform, Children};
4use ratatui::{
5 style::{Color, Modifier},
6 text::{Line, Span, Text},
7 widgets::{Block, Borders, Padding, Paragraph, Wrap},
8 Frame,
9};
10
11use crate::{
12 node::{NodeKind, NodeRef},
13 Tui,
14};
15
16#[derive(Clone)]
17pub struct Component<C> {
18 pub data: Rc<ComponentData>,
19 pub children: C,
20}
21
22impl<C: Children<Tui>> kano::Diff<Tui> for Component<C> {
23 type State = (Rc<ComponentData>, C::State);
24
25 fn init(self, cursor: &mut <Tui as Platform>::Cursor) -> Self::State {
26 cursor.set_component(self.data.clone());
27 let children_state = self.children.init(cursor);
28
29 (self.data, children_state)
30 }
31
32 fn diff(self, state: &mut Self::State, cursor: &mut <Tui as Platform>::Cursor) {
33 self.children.diff(&mut state.1, cursor);
34 }
35}
36
37impl<C: Children<Tui>> kano::View<Tui> for Component<C> {}
38
39#[derive(Clone, Debug)]
40pub struct ComponentData {
41 pub layout: Layout,
42 pub style: Style,
43}
44
45#[derive(Clone, Debug)]
46pub enum Layout {
47 Block,
48 Paragraph,
49 Inline,
50}
51
52#[derive(Clone, Default, Debug)]
53pub struct Style {
54 pub modifier: Option<Modifier>,
55 pub fg: Option<Color>,
56 pub bg: Option<Color>,
57 pub prefix: Option<(&'static str, Box<Style>)>,
58 pub postfix: Option<(&'static str, Box<Style>)>,
59}
60
61impl ComponentData {
62 pub fn render(&self, node: NodeRef, frame: &mut Frame, area: ratatui::prelude::Rect) {
63 let mut spans = vec![];
64 let mut lines = vec![];
65
66 collect_lines(node, &mut spans, &mut lines, Default::default());
67
68 if !spans.is_empty() {
69 lines.push(Line::from(spans));
70 }
71
72 frame.render_widget(
73 Paragraph::new(Text::from(lines))
74 .wrap(Wrap { trim: true })
75 .block(
76 Block::default()
77 .title("Kano TUI. Press q to quit.")
78 .borders(Borders::ALL)
79 .border_set(ratatui::symbols::border::DOUBLE)
80 .padding(Padding::uniform(1)),
81 ),
82 area,
83 );
84 }
85}
86
87pub fn all_children(node: NodeRef) -> Vec<NodeRef> {
88 let mut output = vec![];
89 let mut next_child = node.first_child();
90
91 while let Some(child) = next_child {
92 output.push(child.clone());
93 next_child = child.next_sibling();
94 }
95
96 output
97}
98
99pub fn text_children(node: NodeRef) -> String {
100 let mut buf = String::new();
101
102 let mut next_child = node.first_child();
103
104 while let Some(child) = next_child {
105 match &child.0.borrow().kind {
106 NodeKind::Text(text) => {
107 buf.push_str(&text);
108 }
109 _ => {}
110 }
111
112 next_child = child.next_sibling();
113 }
114
115 buf
116}
117
118fn collect_lines<'a>(
119 node: NodeRef,
120 spans: &mut Vec<Span<'a>>,
121 lines: &mut Vec<Line<'a>>,
122 tui_style: ratatui::style::Style,
123) {
124 match &node.0.borrow().kind {
125 NodeKind::Empty => {}
126 NodeKind::Text(text) => {
127 spans.push(Span::styled(text.clone(), tui_style));
128 }
129 NodeKind::Component(data) => {
130 match &data.layout {
131 Layout::Block | Layout::Paragraph => {
132 if !spans.is_empty() {
133 lines.push(Line::from(std::mem::take(spans)));
134 }
135 }
136 Layout::Inline => {}
137 }
138
139 if let Some((prefix, style)) = &data.style.prefix {
140 let mut tui_style = tui_style.clone();
141 apply_style(&mut tui_style, &style);
142 spans.push(Span::styled(*prefix, tui_style));
143 }
144
145 let mut sub_style = tui_style.clone();
146 apply_style(&mut sub_style, &data.style);
147
148 let mut next_child = node.first_child();
149
150 while let Some(child) = next_child {
151 collect_lines(child.clone(), spans, lines, sub_style);
152 next_child = child.next_sibling();
153 }
154
155 if let Some((postfix, style)) = &data.style.postfix {
156 let mut tui_style = tui_style.clone();
157 apply_style(&mut tui_style, &style);
158 spans.push(Span::styled(*postfix, tui_style));
159 }
160 }
161 }
162}
163
164fn apply_style(tui_style: &mut ratatui::style::Style, style: &Style) {
165 if let Some(modifier) = &style.modifier {
166 *tui_style = tui_style.add_modifier(*modifier);
167 }
168 if let Some(fg) = &style.fg {
169 *tui_style = tui_style.fg(*fg);
170 }
171 if let Some(bg) = &style.bg {
172 *tui_style = tui_style.bg(*bg);
173 }
174}