bubbletea_widgets/list/
defaultitem.rs1use super::{Item, ItemDelegate, Model};
38use bubbletea_rs::{Cmd, Msg};
39use lipgloss::{self, style::Style, Color};
40
41#[derive(Debug, Clone)]
43pub struct DefaultItemStyles {
44 pub normal_title: Style,
46 pub normal_desc: Style,
48 pub selected_title: Style,
50 pub selected_desc: Style,
52 pub dimmed_title: Style,
54 pub dimmed_desc: Style,
56 pub filter_match: Style,
58}
59
60impl Default for DefaultItemStyles {
61 fn default() -> Self {
62 let normal_title = Style::new()
63 .foreground(Color::from("#dddddd"))
64 .padding(0, 0, 0, 2);
65 let normal_desc = normal_title.clone().foreground(Color::from("#777777"));
66 let selected_title = Style::new()
67 .border_style(lipgloss::normal_border())
68 .border_left(true)
69 .border_left_foreground(Color::from("#AD58B4"))
70 .foreground(Color::from("#EE6FF8"))
71 .padding(0, 0, 0, 1);
72 let selected_desc = selected_title.clone().foreground(Color::from("#AD58B4"));
73 let dimmed_title = Style::new()
74 .foreground(Color::from("#777777"))
75 .padding(0, 0, 0, 2);
76 let dimmed_desc = dimmed_title.clone().foreground(Color::from("#4D4D4D"));
77 let filter_match = Style::new().underline(true);
78 Self {
79 normal_title,
80 normal_desc,
81 selected_title,
82 selected_desc,
83 dimmed_title,
84 dimmed_desc,
85 filter_match,
86 }
87 }
88}
89
90#[derive(Debug, Clone)]
92pub struct DefaultItem {
93 pub title: String,
95 pub desc: String,
97}
98
99impl DefaultItem {
100 pub fn new(title: &str, desc: &str) -> Self {
102 Self {
103 title: title.to_string(),
104 desc: desc.to_string(),
105 }
106 }
107}
108
109impl std::fmt::Display for DefaultItem {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 write!(f, "{}", self.title)
112 }
113}
114
115impl Item for DefaultItem {
116 fn filter_value(&self) -> String {
117 self.title.clone()
118 }
119}
120
121#[derive(Debug, Clone)]
123pub struct DefaultDelegate {
124 pub show_description: bool,
126 pub styles: DefaultItemStyles,
128 height: usize,
129 spacing: usize,
130}
131
132impl Default for DefaultDelegate {
133 fn default() -> Self {
134 Self {
135 show_description: true,
136 styles: Default::default(),
137 height: 2,
138 spacing: 1,
139 }
140 }
141}
142impl DefaultDelegate {
143 pub fn new() -> Self {
145 Self::default()
146 }
147}
148
149impl<I: Item + 'static> ItemDelegate<I> for DefaultDelegate {
150 fn render(&self, m: &Model<I>, index: usize, item: &I) -> String {
151 let title = item.to_string();
152 let desc = if let Some(di) = (item as &dyn std::any::Any).downcast_ref::<DefaultItem>() {
153 di.desc.clone()
154 } else {
155 String::new()
156 };
157
158 if m.width == 0 {
159 return String::new();
160 }
161
162 let s = &self.styles;
163 let is_selected = index == m.cursor;
164 let empty_filter =
165 m.filter_state == super::FilterState::Filtering && m.filter_input.value().is_empty();
166 let is_filtered = matches!(
167 m.filter_state,
168 super::FilterState::Filtering | super::FilterState::FilterApplied
169 );
170
171 let mut title_out = title.clone();
172 let mut desc_out = desc.clone();
173
174 if empty_filter {
175 title_out = s.dimmed_title.clone().render(&title_out);
176 desc_out = s.dimmed_desc.clone().render(&desc_out);
177 } else if is_selected && m.filter_state != super::FilterState::Filtering {
178 if is_filtered { }
180 title_out = s.selected_title.clone().render(&title_out);
181 desc_out = s.selected_desc.clone().render(&desc_out);
182 } else {
183 if is_filtered { }
184 title_out = s.normal_title.clone().render(&title_out);
185 desc_out = s.normal_desc.clone().render(&desc_out);
186 }
187
188 if self.show_description && !desc_out.is_empty() {
189 format!("{}\n{}", title_out, desc_out)
190 } else {
191 title_out
192 }
193 }
194 fn height(&self) -> usize {
195 if self.show_description {
196 self.height
197 } else {
198 1
199 }
200 }
201 fn spacing(&self) -> usize {
202 self.spacing
203 }
204 fn update(&self, _msg: &Msg, _m: &mut Model<I>) -> Option<Cmd> {
205 None
206 }
207}