Skip to main content

launcher/widgets/
gallery.rs

1use eframe::egui;
2use std::collections::HashMap;
3use std::path::PathBuf;
4
5#[derive(serde::Serialize, serde::Deserialize)]
6#[serde(default)]
7pub struct GalleryState {
8    pub tile_size: f32,
9    #[serde(skip)]
10    pub selected_item: Option<PathBuf>,
11    #[serde(skip)]
12    pub textures: HashMap<String, egui::TextureHandle>,
13}
14
15impl Default for GalleryState {
16    fn default() -> Self {
17        Self {
18            tile_size: 205.0,
19            selected_item: None,
20            textures: HashMap::new(),
21        }
22    }
23}
24
25pub struct GalleryConfig<'a> {
26    pub id: &'a str,
27    pub title: &'a str,
28    pub info_text: Option<&'a str>,
29    pub open_folder_path: Option<&'a PathBuf>,
30}
31
32pub fn gallery_grid<T, F>(
33    ui: &mut egui::Ui,
34    config: GalleryConfig<'_>,
35    state: &mut GalleryState,
36    items: &[T],
37    header_extra: impl FnOnce(&mut egui::Ui),
38    mut draw_tile: F,
39) where
40    F: FnMut(&mut egui::Ui, &T, f32, &mut GalleryState),
41{
42    ui.horizontal(|ui| {
43        ui.heading(config.title);
44        if let Some(info) = config.info_text {
45            ui.label(egui::RichText::new("ℹ").size(16.0))
46                .on_hover_text(info);
47        }
48        if let Some(path) = config.open_folder_path
49            && ui
50                .button(egui::RichText::new("📂").size(16.0))
51                .on_hover_text("Open directory")
52                .clicked()
53            && let Err(e) = open::that(path)
54        {
55            eprintln!("Failed to open directory {}: {e}", path.display());
56        }
57        ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
58            ui.add_space(8.0);
59            ui.add(egui::Slider::new(&mut state.tile_size, 192.0..=512.0).show_value(false));
60            ui.label("Tile size:");
61            header_extra(ui);
62        });
63    });
64
65    ui.add_space(10.0);
66
67    egui::ScrollArea::vertical()
68        .auto_shrink([false, false])
69        .show(ui, |ui| {
70            ui.add_space(10.0);
71
72            // Responsive grid layout
73            let tile_spacing = 16.0;
74            let available_width = ui.available_width();
75
76            let column_width = state.tile_size + tile_spacing;
77            let num_columns = ((available_width / column_width).floor()) as usize;
78            let num_columns = num_columns.max(1);
79
80            egui::Grid::new(config.id)
81                .spacing(egui::vec2(tile_spacing, tile_spacing))
82                .min_col_width(state.tile_size)
83                .show(ui, |ui| {
84                    for (i, item) in items.iter().enumerate() {
85                        if i > 0 && i % num_columns == 0 {
86                            ui.end_row();
87                        }
88                        draw_tile(ui, item, state.tile_size, state);
89                    }
90                });
91        });
92}