rust_pixel/ui/components/
present_list.rs1use crate::context::Context;
11use crate::render::Buffer;
12use crate::render::style::{Color, Style};
13use crate::util::Rect;
14use crate::ui::{
15 Widget, BaseWidget, WidgetId, WidgetState, UIEvent, UIResult,
16 next_widget_id,
17};
18use crate::impl_widget_base;
19
20#[derive(Debug, Clone)]
22pub struct PresentListItem {
23 pub text: String,
24 pub depth: u8,
25 pub ordered: bool,
26 pub index: usize,
27}
28
29impl PresentListItem {
30 pub fn new(text: &str) -> Self {
31 Self {
32 text: text.to_string(),
33 depth: 0,
34 ordered: false,
35 index: 1,
36 }
37 }
38
39 pub fn with_depth(mut self, depth: u8) -> Self {
40 self.depth = depth;
41 self
42 }
43
44 pub fn with_ordered(mut self, ordered: bool, index: usize) -> Self {
45 self.ordered = ordered;
46 self.index = index;
47 self
48 }
49}
50
51#[cfg(graphics_mode)]
54pub const DEFAULT_MARKERS: [&str; 3] = ["🟢", "🔵", "🟡"];
55#[cfg(not(graphics_mode))]
56pub const DEFAULT_MARKERS: [&str; 3] = ["◆", "●", "◇"];
57
58pub fn default_marker_style() -> Style {
60 if cfg!(graphics_mode) {
61 Style::default().fg(Color::Cyan).scale(0.5, 0.5)
62 } else {
63 Style::default().fg(Color::Cyan)
64 }
65}
66
67pub struct PresentList {
69 base: BaseWidget,
70 items: Vec<PresentListItem>,
71 prefix_style: Style,
72 text_style: Style,
73 marker_style: Style,
74 markers: [String; 3],
75}
76
77impl Default for PresentList {
78 fn default() -> Self {
79 Self::new()
80 }
81}
82
83impl PresentList {
84 pub fn new() -> Self {
85 Self {
86 base: BaseWidget::new(next_widget_id()),
87 items: Vec::new(),
88 prefix_style: Style::default().fg(Color::Cyan),
89 text_style: Style::default().fg(Color::White),
90 marker_style: default_marker_style(),
91 markers: DEFAULT_MARKERS.map(|s| s.to_string()),
92 }
93 }
94
95 pub fn with_items(mut self, items: Vec<PresentListItem>) -> Self {
96 self.items = items;
97 self
98 }
99
100 pub fn with_prefix_style(mut self, style: Style) -> Self {
101 self.prefix_style = style;
102 self
103 }
104
105 pub fn with_text_style(mut self, style: Style) -> Self {
106 self.text_style = style;
107 self
108 }
109
110 pub fn with_marker_style(mut self, style: Style) -> Self {
111 self.marker_style = style;
112 self
113 }
114
115 pub fn with_markers(mut self, markers: [String; 3]) -> Self {
116 self.markers = markers;
117 self
118 }
119
120 pub fn set_items(&mut self, items: Vec<PresentListItem>) {
121 self.items = items;
122 self.mark_dirty();
123 }
124
125 pub fn items(&self) -> &[PresentListItem] {
126 &self.items
127 }
128
129 fn render_item(&self, buf: &mut Buffer, x: u16, y: u16, item: &PresentListItem) {
130 let indent_width = item.depth as u16 * 2;
131 let indent = " ".repeat(item.depth as usize);
132
133 if item.ordered {
134 let bullet = format!("{}{}. ", indent, item.index);
135 let w = bullet.len() as u16;
136 buf.set_string(x, y, &bullet, self.prefix_style);
137 buf.set_string(x + w, y, &item.text, self.text_style);
138 } else {
139 let marker_idx = (item.depth as usize).min(self.markers.len() - 1);
140 let marker = &self.markers[marker_idx];
141 buf.set_string(x, y, &indent, self.prefix_style);
142 buf.set_string(x + indent_width, y, marker, self.marker_style);
143 let marker_offset: u16 = if cfg!(graphics_mode) { 3 } else { 2 };
146 buf.set_string(x + indent_width + marker_offset, y, &item.text, self.text_style);
147 }
148 }
149}
150
151impl Widget for PresentList {
152 impl_widget_base!(PresentList, base);
153
154 fn render(&self, buffer: &mut Buffer, _ctx: &Context) -> UIResult<()> {
155 if !self.state().visible {
156 return Ok(());
157 }
158 let bounds = self.bounds();
159 if bounds.width == 0 || bounds.height == 0 {
160 return Ok(());
161 }
162
163 let buffer_area = *buffer.area();
164 if bounds.y >= buffer_area.y + buffer_area.height
165 || bounds.x >= buffer_area.x + buffer_area.width
166 {
167 return Ok(());
168 }
169
170 for (i, item) in self.items.iter().enumerate() {
171 let y = bounds.y + i as u16;
172 if y >= bounds.y + bounds.height || y >= buffer_area.y + buffer_area.height {
173 break;
174 }
175 self.render_item(buffer, bounds.x, y, item);
176 }
177
178 Ok(())
179 }
180
181 fn handle_event(&mut self, _event: &UIEvent, _ctx: &mut Context) -> UIResult<bool> {
182 Ok(false)
183 }
184
185 fn preferred_size(&self, available: Rect) -> Rect {
186 let height = (self.items.len() as u16).min(available.height);
187 Rect::new(available.x, available.y, available.width, height)
188 }
189}