kimun_notes/components/
rich_row.rs1use ratatui::style::{Modifier, Style};
18use ratatui::text::{Line, Span, Text};
19use ratatui::widgets::ListItem;
20
21use crate::settings::themes::Theme;
22
23#[derive(Default)]
24pub struct RichRow {
25 glyph: String,
26 glyph_style: Option<Style>,
27 title: String,
28 title_style: Option<Style>,
29 meta: Option<String>,
31 secondary: Option<(String, Option<Style>)>,
33 filename: Option<String>,
35}
36
37impl RichRow {
38 pub fn new(glyph: impl Into<String>, title: impl Into<String>) -> Self {
39 Self {
40 glyph: glyph.into(),
41 title: title.into(),
42 ..Self::default()
43 }
44 }
45
46 pub fn glyph_style(mut self, style: Style) -> Self {
47 self.glyph_style = Some(style);
48 self
49 }
50
51 pub fn title_style(mut self, style: Style) -> Self {
52 self.title_style = Some(style);
53 self
54 }
55
56 pub fn meta(mut self, meta: impl Into<String>) -> Self {
57 self.meta = Some(meta.into());
58 self
59 }
60
61 pub fn secondary(mut self, text: impl Into<String>, style: Option<Style>) -> Self {
62 self.secondary = Some((text.into(), style));
63 self
64 }
65
66 pub fn filename(mut self, filename: impl Into<String>) -> Self {
67 self.filename = Some(filename.into());
68 self
69 }
70
71 pub fn height(&self) -> u16 {
73 1 + u16::from(self.secondary.is_some()) + u16::from(self.filename.is_some())
74 }
75
76 pub fn into_list_item(self, theme: &Theme) -> ListItem<'static> {
77 let fg = Style::default().fg(theme.fg.to_ratatui());
78 let gray = Style::default().fg(theme.gray.to_ratatui());
79 let secondary_default = Style::default()
80 .fg(theme.fg_secondary.to_ratatui())
81 .add_modifier(Modifier::ITALIC);
82
83 let mut main = vec![
84 Span::styled(format!("{} ", self.glyph), self.glyph_style.unwrap_or(fg)),
85 Span::styled(self.title, self.title_style.unwrap_or(fg)),
86 ];
87 if let Some(meta) = self.meta {
88 main.push(Span::styled(format!(" {meta}"), gray));
89 }
90
91 let mut lines = vec![Line::from(main)];
92 if let Some((text, style)) = self.secondary {
93 lines.push(Line::from(Span::styled(
94 format!(" {text}"),
95 style.unwrap_or(secondary_default),
96 )));
97 }
98 if let Some(filename) = self.filename {
99 lines.push(Line::from(Span::styled(
100 format!(" {filename}"),
101 secondary_default,
102 )));
103 }
104 ListItem::new(Text::from(lines))
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn height_counts_optional_lines() {
114 let theme = Theme::default();
115 let row = RichRow::new("X", "title");
116 assert_eq!(row.height(), 1);
117 let row = RichRow::new("X", "title").filename("a.md");
118 assert_eq!(row.height(), 2);
119 let row = RichRow::new("X", "title")
120 .secondary("sub", None)
121 .filename("a.md");
122 assert_eq!(row.height(), 3);
123 let _ = RichRow::new("X", "t").meta("42").into_list_item(&theme);
125 }
126}