1#![allow(dead_code)]
8
9use alloc::{string::String, vec, vec::Vec};
10
11#[derive(Debug, Clone)]
17pub struct DesktopFile {
18 pub name: String,
20 pub exec_command: String,
22 pub icon: String,
24 pub categories: Vec<String>,
26 pub mime_types: Vec<String>,
28}
29
30impl DesktopFile {
31 pub fn parse(content: &str) -> Option<Self> {
33 let mut name = String::new();
34 let mut exec_command = String::new();
35 let mut icon = String::new();
36 let mut categories = Vec::new();
37 let mut mime_types = Vec::new();
38
39 for line in content.lines() {
40 let line = line.trim();
41 if line.is_empty() || line.starts_with('#') || line.starts_with('[') {
42 continue;
43 }
44
45 if let Some(val) = line.strip_prefix("Name=") {
46 name = String::from(val);
47 } else if let Some(val) = line.strip_prefix("Exec=") {
48 exec_command = String::from(val);
49 } else if let Some(val) = line.strip_prefix("Icon=") {
50 icon = String::from(val);
51 } else if let Some(val) = line.strip_prefix("Categories=") {
52 categories = val
53 .split(';')
54 .filter(|s| !s.is_empty())
55 .map(String::from)
56 .collect();
57 } else if let Some(val) = line.strip_prefix("MimeType=") {
58 mime_types = val
59 .split(';')
60 .filter(|s| !s.is_empty())
61 .map(String::from)
62 .collect();
63 }
64 }
65
66 if name.is_empty() {
67 return None;
68 }
69
70 Some(Self {
71 name,
72 exec_command,
73 icon,
74 categories,
75 mime_types,
76 })
77 }
78}
79
80pub const ICON_SIZE: u32 = 16;
86
87#[derive(Debug, Clone)]
89pub struct DesktopIcon {
90 pub name: String,
92 pub icon_data: Vec<u32>,
94 pub x: i32,
96 pub y: i32,
98 pub selected: bool,
100 pub desktop_file_path: String,
102 pub desktop_file: Option<DesktopFile>,
104}
105
106impl DesktopIcon {
107 pub fn new(name: &str, x: i32, y: i32) -> Self {
109 Self {
110 name: String::from(name),
111 icon_data: vec![0xFF4488CC; (ICON_SIZE * ICON_SIZE) as usize],
112 x,
113 y,
114 selected: false,
115 desktop_file_path: String::new(),
116 desktop_file: None,
117 }
118 }
119
120 pub fn set_icon_data(&mut self, data: &[u32]) {
122 let expected = (ICON_SIZE * ICON_SIZE) as usize;
123 if data.len() == expected {
124 self.icon_data.clear();
125 self.icon_data.extend_from_slice(data);
126 }
127 }
128
129 pub fn total_height(&self) -> u32 {
131 ICON_SIZE + 4 + 16
133 }
134
135 pub fn hit_test(&self, px: i32, py: i32) -> bool {
137 let w = ICON_SIZE as i32;
138 let h = self.total_height() as i32;
139 px >= self.x && px < self.x + w && py >= self.y && py < self.y + h
140 }
141}
142
143#[derive(Debug)]
149pub struct IconGrid {
150 pub icons: Vec<DesktopIcon>,
152 pub grid_spacing_x: u32,
154 pub grid_spacing_y: u32,
156 pub cell_size: u32,
158 desktop_width: u32,
160 desktop_height: u32,
162}
163
164impl IconGrid {
165 pub fn new(desktop_width: u32, desktop_height: u32) -> Self {
167 Self {
168 icons: Vec::new(),
169 grid_spacing_x: 80,
170 grid_spacing_y: 80,
171 cell_size: 64,
172 desktop_width,
173 desktop_height,
174 }
175 }
176
177 pub fn add_icon(&mut self, icon: DesktopIcon) {
179 self.icons.push(icon);
180 }
181
182 pub fn remove_icon(&mut self, index: usize) -> Option<DesktopIcon> {
184 if index < self.icons.len() {
185 Some(self.icons.remove(index))
186 } else {
187 None
188 }
189 }
190
191 pub fn arrange(&mut self) {
193 let margin_x: i32 = 20;
194 let margin_y: i32 = 40;
195 let cols = if self.grid_spacing_x == 0 {
196 1
197 } else {
198 ((self.desktop_width as i32 - margin_x * 2) / self.grid_spacing_x as i32).max(1)
199 };
200
201 for (i, icon) in self.icons.iter_mut().enumerate() {
202 let col = (i as i32) % cols;
203 let row = (i as i32) / cols;
204 icon.x = margin_x + col * self.grid_spacing_x as i32;
205 icon.y = margin_y + row * self.grid_spacing_y as i32;
206 }
207 }
208
209 pub fn snap_to_grid(&self, x: i32, y: i32) -> (i32, i32) {
211 let gx = self.grid_spacing_x as i32;
212 let gy = self.grid_spacing_y as i32;
213 if gx == 0 || gy == 0 {
214 return (x, y);
215 }
216 let sx = ((x + gx / 2) / gx) * gx;
217 let sy = ((y + gy / 2) / gy) * gy;
218 (sx.max(0), sy.max(0))
219 }
220
221 pub fn render_icon(icon: &DesktopIcon, buf: &mut [u32], buf_width: u32, buf_height: u32) {
225 let bw = buf_width as i32;
226 let bh = buf_height as i32;
227 let iw = ICON_SIZE as i32;
228
229 for row in 0..ICON_SIZE as i32 {
231 let dy = icon.y + row;
232 if dy < 0 || dy >= bh {
233 continue;
234 }
235 for col in 0..iw {
236 let dx = icon.x + col;
237 if dx < 0 || dx >= bw {
238 continue;
239 }
240 let src = icon.icon_data[(row * iw + col) as usize];
241 buf[(dy * bw + dx) as usize] = src;
242 }
243 }
244
245 if icon.selected {
247 let color = 0xFF44AAFF; let x0 = icon.x - 1;
249 let y0 = icon.y - 1;
250 let x1 = icon.x + iw;
251 let y1 = icon.y + ICON_SIZE as i32;
252 for dx in x0..=x1 {
253 if dx >= 0 && dx < bw {
254 if y0 >= 0 && y0 < bh {
255 buf[(y0 * bw + dx) as usize] = color;
256 }
257 if y1 >= 0 && y1 < bh {
258 buf[(y1 * bw + dx) as usize] = color;
259 }
260 }
261 }
262 for dy in y0..=y1 {
263 if dy >= 0 && dy < bh {
264 if x0 >= 0 && x0 < bw {
265 buf[(dy * bw + x0) as usize] = color;
266 }
267 if x1 >= 0 && x1 < bw {
268 buf[(dy * bw + x1) as usize] = color;
269 }
270 }
271 }
272 }
273 }
274
275 pub fn handle_click(&mut self, px: i32, py: i32) -> Option<usize> {
279 let mut clicked = None;
280
281 for (i, icon) in self.icons.iter_mut().enumerate() {
282 if icon.hit_test(px, py) {
283 icon.selected = true;
284 clicked = Some(i);
285 } else {
286 icon.selected = false;
287 }
288 }
289
290 clicked
291 }
292
293 pub fn handle_double_click(&mut self, px: i32, py: i32) -> Option<String> {
297 for icon in &self.icons {
298 if icon.hit_test(px, py) {
299 if let Some(ref df) = icon.desktop_file {
300 if !df.exec_command.is_empty() {
301 return Some(df.exec_command.clone());
302 }
303 }
304 }
305 }
306 None
307 }
308
309 pub fn handle_drag(&mut self, px: i32, py: i32) {
311 let (sx, sy) = self.snap_to_grid(px, py);
312 for icon in &mut self.icons {
313 if icon.selected {
314 icon.x = sx;
315 icon.y = sy;
316 break;
317 }
318 }
319 }
320
321 pub fn deselect_all(&mut self) {
323 for icon in &mut self.icons {
324 icon.selected = false;
325 }
326 }
327
328 pub fn icon_count(&self) -> usize {
330 self.icons.len()
331 }
332}
333
334#[cfg(test)]
339mod tests {
340 use super::*;
341
342 #[test]
343 fn test_desktop_file_parse() {
344 let content = concat!(
345 "[Desktop Entry]\n",
346 "Name=Terminal\n",
347 "Exec=vterm\n",
348 "Icon=terminal\n",
349 "Categories=System;Utility;\n",
350 "MimeType=text/plain;\n",
351 );
352 let df = DesktopFile::parse(content).unwrap();
353 assert_eq!(df.name, "Terminal");
354 assert_eq!(df.exec_command, "vterm");
355 assert_eq!(df.categories.len(), 2);
356 assert_eq!(df.mime_types.len(), 1);
357 }
358
359 #[test]
360 fn test_desktop_file_missing_name() {
361 let content = "Exec=foo\n";
362 assert!(DesktopFile::parse(content).is_none());
363 }
364
365 #[test]
366 fn test_icon_hit_test() {
367 let icon = DesktopIcon::new("Test", 100, 200);
368 assert!(icon.hit_test(108, 210));
369 assert!(!icon.hit_test(50, 50));
370 }
371
372 #[test]
373 fn test_icon_grid_arrange() {
374 let mut grid = IconGrid::new(800, 600);
375 for i in 0..5 {
376 grid.add_icon(DesktopIcon::new(&alloc::format!("Icon{}", i), 0, 0));
377 }
378 grid.arrange();
379 assert!(grid.icons[0].x >= 20);
381 assert!(grid.icons[0].y >= 40);
382 }
383
384 #[test]
385 fn test_icon_grid_click() {
386 let mut grid = IconGrid::new(800, 600);
387 grid.add_icon(DesktopIcon::new("A", 100, 100));
388 grid.add_icon(DesktopIcon::new("B", 200, 100));
389 let clicked = grid.handle_click(108, 110);
390 assert_eq!(clicked, Some(0));
391 assert!(grid.icons[0].selected);
392 assert!(!grid.icons[1].selected);
393 }
394
395 #[test]
396 fn test_snap_to_grid() {
397 let grid = IconGrid::new(800, 600);
398 let (sx, sy) = grid.snap_to_grid(45, 95);
399 assert_eq!(sx, 80);
400 assert_eq!(sy, 80);
401 }
402
403 #[test]
404 fn test_icon_drag() {
405 let mut grid = IconGrid::new(800, 600);
406 grid.add_icon(DesktopIcon::new("A", 0, 0));
407 grid.icons[0].selected = true;
408 grid.handle_drag(90, 90);
409 assert_eq!(grid.icons[0].x, 80);
411 assert_eq!(grid.icons[0].y, 80);
412 }
413
414 #[test]
415 fn test_remove_icon() {
416 let mut grid = IconGrid::new(800, 600);
417 grid.add_icon(DesktopIcon::new("A", 0, 0));
418 grid.add_icon(DesktopIcon::new("B", 80, 0));
419 let removed = grid.remove_icon(0);
420 assert!(removed.is_some());
421 assert_eq!(grid.icon_count(), 1);
422 }
423
424 #[test]
425 fn test_deselect_all() {
426 let mut grid = IconGrid::new(800, 600);
427 grid.add_icon(DesktopIcon::new("A", 0, 0));
428 grid.icons[0].selected = true;
429 grid.deselect_all();
430 assert!(!grid.icons[0].selected);
431 }
432
433 #[test]
434 fn test_render_icon_no_panic() {
435 let icon = DesktopIcon::new("Test", 0, 0);
436 let mut buf = vec![0u32; 64 * 64];
437 IconGrid::render_icon(&icon, &mut buf, 64, 64);
438 }
440}