cadmus_core/view/file_chooser/
file_entry.rs1use super::FileEntryData;
2use crate::color::{TEXT_NORMAL, WHITE};
3use crate::context::Context;
4use crate::device::CURRENT_DEVICE;
5use crate::font::{font_from_style, Fonts, NORMAL_STYLE};
6use crate::framebuffer::Framebuffer;
7use crate::geom::Rectangle;
8use crate::gesture::GestureEvent;
9use crate::view::label::Label;
10use crate::view::{Align, Bus, EntryId, Event, Hub, Id, RenderQueue, View, ID_FEEDER};
11use chrono::{DateTime, Local};
12
13pub struct FileEntry {
26 id: Id,
27 rect: Rectangle,
28 children: Vec<Box<dyn View>>,
29 data: FileEntryData,
30}
31
32impl FileEntry {
33 pub fn new(rect: Rectangle, data: FileEntryData, context: &mut Context) -> FileEntry {
52 let mut children: Vec<Box<dyn View>> = Vec::new();
53 let dpi = CURRENT_DEVICE.dpi;
54 let font = font_from_style(&mut context.fonts, &NORMAL_STYLE, dpi);
55 let padding = font.em() as i32;
56
57 let event = Some(Event::Select(EntryId::FileEntry(data.path.clone())));
58 let icon = if data.is_dir { "📁" } else { "📄" };
59 let size_text = data
60 .size
61 .map(Self::format_size)
62 .unwrap_or_else(|| "-".to_string());
63 let date_text = data
64 .modified
65 .map(Self::format_date)
66 .unwrap_or_else(|| "-".to_string());
67
68 let icon_plan = font.plan(icon, None, None);
69 let date_plan = font.plan(&date_text, None, None);
70 let size_plan = font.plan(&size_text, None, None);
71
72 let mut x = rect.min.x + padding;
73 let icon_width = icon_plan.width + padding;
74
75 let name_max_width = rect.width() as i32
76 - icon_width
77 - padding
78 - date_plan.width
79 - size_plan.width
80 - 4 * padding;
81
82 let name_plan = font.plan(&data.name, Some(name_max_width), None);
83
84 let icon_rect = rect![x, rect.min.y, x + icon_width, rect.max.y];
85 children.push(Box::new(
86 Label::new(icon_rect, icon.to_string(), Align::Left(0))
87 .scheme([WHITE, TEXT_NORMAL[1], TEXT_NORMAL[2]])
88 .event(event.clone()),
89 ));
90 x += icon_width;
91
92 let name_rect = rect![x, rect.min.y, x + name_plan.width + padding, rect.max.y];
93 children.push(Box::new(
94 Label::new(name_rect, data.name.clone(), Align::Left(0))
95 .scheme([WHITE, TEXT_NORMAL[1], TEXT_NORMAL[2]])
96 .event(event.clone()),
97 ));
98
99 let size_x = rect.max.x - date_plan.width - size_plan.width - 2 * padding;
100 let size_rect = rect![
101 size_x,
102 rect.min.y,
103 size_x + size_plan.width + padding,
104 rect.max.y
105 ];
106 children.push(Box::new(
107 Label::new(size_rect, size_text, Align::Left(0))
108 .scheme([WHITE, TEXT_NORMAL[1], TEXT_NORMAL[2]])
109 .event(event.clone()),
110 ));
111
112 let date_x = rect.max.x - date_plan.width - padding;
113 let date_rect = rect![date_x, rect.min.y, rect.max.x, rect.max.y];
114 children.push(Box::new(
115 Label::new(date_rect, date_text, Align::Left(0))
116 .scheme([WHITE, TEXT_NORMAL[1], TEXT_NORMAL[2]])
117 .event(event.clone()),
118 ));
119
120 FileEntry {
121 id: ID_FEEDER.next(),
122 rect,
123 children,
124 data,
125 }
126 }
127
128 fn format_size(size: u64) -> String {
129 const KB: u64 = 1024;
130 const MB: u64 = KB * 1024;
131 const GB: u64 = MB * 1024;
132
133 if size >= GB {
134 format!("{:.1} GB", size as f64 / GB as f64)
135 } else if size >= MB {
136 format!("{:.1} MB", size as f64 / MB as f64)
137 } else if size >= KB {
138 format!("{:.1} KB", size as f64 / KB as f64)
139 } else {
140 format!("{} B", size)
141 }
142 }
143
144 fn format_date(system_time: std::time::SystemTime) -> String {
145 let datetime: DateTime<Local> = system_time.into();
146 datetime.format("%b %d, %Y %H:%M").to_string()
147 }
148}
149
150impl View for FileEntry {
151 #[cfg_attr(feature = "otel", tracing::instrument(skip(self, _hub, bus, _rq, _context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
170 fn handle_event(
171 &mut self,
172 evt: &Event,
173 _hub: &Hub,
174 bus: &mut Bus,
175 _rq: &mut RenderQueue,
176 _context: &mut Context,
177 ) -> bool {
178 match evt {
179 Event::Gesture(GestureEvent::Tap(center)) if self.rect.includes(*center) => {
180 bus.push_back(Event::Select(EntryId::FileEntry(self.data.path.clone())));
181 true
182 }
183 _ => false,
184 }
185 }
186
187 #[cfg_attr(feature = "otel", tracing::instrument(skip(self, fb, _fonts), fields(rect = ?_rect)))]
188 fn render(&self, fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut Fonts) {
189 fb.draw_rectangle(&self.rect, WHITE);
190 }
191
192 fn rect(&self) -> &Rectangle {
193 &self.rect
194 }
195
196 fn rect_mut(&mut self) -> &mut Rectangle {
197 &mut self.rect
198 }
199
200 fn children(&self) -> &Vec<Box<dyn View>> {
201 &self.children
202 }
203
204 fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
205 &mut self.children
206 }
207
208 fn id(&self) -> Id {
209 self.id
210 }
211}