sbom_tools/tui/widgets/
change_badge.rs1use crate::tui::theme::colors;
4use ratatui::{prelude::*, widgets::Widget};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum ChangeType {
9 Added,
10 Removed,
11 Modified,
12 Unchanged,
13}
14
15impl ChangeType {
16 pub fn from_label(s: &str) -> Self {
18 match s.to_lowercase().as_str() {
19 "added" | "new" | "introduced" => Self::Added,
20 "removed" | "deleted" | "resolved" => Self::Removed,
21 "modified" | "changed" | "updated" => Self::Modified,
22 _ => Self::Unchanged,
23 }
24 }
25
26 pub fn symbol(&self) -> &'static str {
28 match self {
29 Self::Added => "+",
30 Self::Removed => "-",
31 Self::Modified => "~",
32 Self::Unchanged => "=",
33 }
34 }
35
36 pub fn label(&self) -> &'static str {
38 match self {
39 Self::Added => "ADDED",
40 Self::Removed => "REMOVED",
41 Self::Modified => "MODIFIED",
42 Self::Unchanged => "UNCHANGED",
43 }
44 }
45
46 pub fn color(&self) -> Color {
48 let scheme = colors();
49 match self {
50 Self::Added => scheme.added,
51 Self::Removed => scheme.removed,
52 Self::Modified => scheme.modified,
53 Self::Unchanged => scheme.unchanged,
54 }
55 }
56
57 pub fn badge_fg(&self) -> Color {
59 colors().change_badge_fg()
60 }
61}
62
63#[derive(Debug, Clone)]
65pub struct ChangeTypeBadge {
66 change_type: ChangeType,
67 compact: bool,
68}
69
70impl ChangeTypeBadge {
71 pub fn new(change_type: ChangeType) -> Self {
73 Self {
74 change_type,
75 compact: false,
76 }
77 }
78
79 pub fn from_label(s: &str) -> Self {
81 Self::new(ChangeType::from_label(s))
82 }
83
84 pub fn compact(mut self) -> Self {
86 self.compact = true;
87 self
88 }
89
90 pub fn style(&self) -> Style {
92 Style::default()
93 .fg(self.change_type.badge_fg())
94 .bg(self.change_type.color())
95 .bold()
96 }
97
98 pub fn fg_color(&self) -> Color {
100 self.change_type.color()
101 }
102
103 pub fn to_span(&self) -> Span<'static> {
105 let text = if self.compact {
106 format!(" {} ", self.change_type.symbol())
107 } else {
108 format!(
109 " {} {} ",
110 self.change_type.symbol(),
111 self.change_type.label()
112 )
113 };
114
115 Span::styled(text, self.style())
116 }
117
118 pub fn change_type(&self) -> ChangeType {
120 self.change_type
121 }
122}
123
124impl Widget for ChangeTypeBadge {
125 fn render(self, area: Rect, buf: &mut Buffer) {
126 if area.width < 3 || area.height < 1 {
127 return;
128 }
129
130 let text = if self.compact {
131 format!(" {} ", self.change_type.symbol())
132 } else {
133 let full = format!(
134 " {} {} ",
135 self.change_type.symbol(),
136 self.change_type.label()
137 );
138 if area.width as usize >= full.len() {
139 full
140 } else {
141 format!(" {} ", self.change_type.symbol())
142 }
143 };
144
145 let style = self.style();
146 let x = area.x;
147 let y = area.y;
148
149 for (i, ch) in text.chars().enumerate() {
150 if i < area.width as usize {
151 if let Some(cell) = buf.cell_mut((x + i as u16, y)) {
152 cell.set_char(ch).set_style(style);
153 }
154 }
155 }
156 }
157}
158
159#[derive(Debug, Clone)]
161pub struct ChangeIndicator {
162 change_type: ChangeType,
163}
164
165impl ChangeIndicator {
166 pub fn new(change_type: ChangeType) -> Self {
167 Self { change_type }
168 }
169
170 pub fn from_label(s: &str) -> Self {
171 Self::new(ChangeType::from_label(s))
172 }
173
174 pub fn to_span(&self) -> Span<'static> {
176 Span::styled(
177 self.change_type.symbol().to_string(),
178 Style::default().fg(self.change_type.color()).bold(),
179 )
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn test_change_type_from_label() {
189 assert_eq!(ChangeType::from_label("added"), ChangeType::Added);
190 assert_eq!(ChangeType::from_label("NEW"), ChangeType::Added);
191 assert_eq!(ChangeType::from_label("removed"), ChangeType::Removed);
192 assert_eq!(ChangeType::from_label("modified"), ChangeType::Modified);
193 assert_eq!(ChangeType::from_label("unknown"), ChangeType::Unchanged);
194 }
195
196 #[test]
197 fn test_change_type_symbols() {
198 assert_eq!(ChangeType::Added.symbol(), "+");
199 assert_eq!(ChangeType::Removed.symbol(), "-");
200 assert_eq!(ChangeType::Modified.symbol(), "~");
201 assert_eq!(ChangeType::Unchanged.symbol(), "=");
202 }
203
204 #[test]
205 fn test_change_type_colors_use_theme() {
206 let scheme = colors();
207 assert_eq!(ChangeType::Added.color(), scheme.added);
208 assert_eq!(ChangeType::Removed.color(), scheme.removed);
209 assert_eq!(ChangeType::Modified.color(), scheme.modified);
210 }
211}