1use dais_document::page::RenderedPage;
6use egui::{Response, Sense, TextureHandle, Ui, Vec2};
7
8pub struct SlideThumbnail {
10 texture: Option<TextureHandle>,
11 page_index: usize,
12 width: u32,
13 height: u32,
14}
15
16impl SlideThumbnail {
17 pub fn new() -> Self {
18 Self { texture: None, page_index: usize::MAX, width: 0, height: 0 }
19 }
20
21 pub fn update(&mut self, ctx: &egui::Context, page: &RenderedPage, page_index: usize) {
23 if self.page_index == page_index && self.width == page.width && self.height == page.height {
24 return;
25 }
26
27 let color_image = egui::ColorImage::from_rgba_premultiplied(
28 [page.width as usize, page.height as usize],
29 &page.data,
30 );
31 let name = format!("slide_{page_index}_{}", page.width);
32 self.texture = Some(ctx.load_texture(name, color_image, egui::TextureOptions::LINEAR));
33 self.page_index = page_index;
34 self.width = page.width;
35 self.height = page.height;
36 }
37
38 #[allow(clippy::cast_precision_loss)]
41 pub fn show(&self, ui: &mut Ui, desired_size: Vec2) -> Response {
42 let Some(tex) = &self.texture else {
43 let (rect, response) = ui.allocate_exact_size(desired_size, egui::Sense::hover());
44 ui.painter().rect_filled(rect, 0.0, egui::Color32::from_gray(40));
45 return response;
46 };
47
48 let tex_aspect = self.width as f32 / self.height.max(1) as f32;
49 let box_aspect = desired_size.x / desired_size.y.max(1.0);
50
51 let display_size = if tex_aspect > box_aspect {
52 Vec2::new(desired_size.x, desired_size.x / tex_aspect)
53 } else {
54 Vec2::new(desired_size.y * tex_aspect, desired_size.y)
55 };
56
57 let (rect, response) = ui.allocate_exact_size(desired_size, egui::Sense::hover());
58
59 let offset = (desired_size - display_size) / 2.0;
60 let image_rect = egui::Rect::from_min_size(rect.min + offset, display_size);
61
62 ui.painter().rect_filled(rect, 0.0, egui::Color32::BLACK);
63
64 ui.painter().image(
65 tex.id(),
66 image_rect,
67 egui::Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0)),
68 egui::Color32::WHITE,
69 );
70
71 response
72 }
73
74 #[allow(clippy::cast_precision_loss)]
77 pub fn show_interactive(&self, ui: &mut Ui, desired_size: Vec2) -> (Response, egui::Rect) {
78 self.show_with_sense(ui, desired_size, egui::Sense::click_and_drag())
79 }
80
81 #[allow(clippy::cast_precision_loss)]
83 pub fn show_with_sense(
84 &self,
85 ui: &mut Ui,
86 desired_size: Vec2,
87 sense: Sense,
88 ) -> (Response, egui::Rect) {
89 let Some(tex) = &self.texture else {
90 let (rect, response) = ui.allocate_exact_size(desired_size, sense);
91 ui.painter().rect_filled(rect, 0.0, egui::Color32::from_gray(40));
92 return (response, rect);
93 };
94
95 let tex_aspect = self.width as f32 / self.height.max(1) as f32;
96 let box_aspect = desired_size.x / desired_size.y.max(1.0);
97
98 let display_size = if tex_aspect > box_aspect {
99 Vec2::new(desired_size.x, desired_size.x / tex_aspect)
100 } else {
101 Vec2::new(desired_size.y * tex_aspect, desired_size.y)
102 };
103
104 let (rect, response) = ui.allocate_exact_size(desired_size, sense);
105
106 let offset = (desired_size - display_size) / 2.0;
107 let image_rect = egui::Rect::from_min_size(rect.min + offset, display_size);
108
109 ui.painter().rect_filled(rect, 0.0, egui::Color32::BLACK);
110
111 ui.painter().image(
112 tex.id(),
113 image_rect,
114 egui::Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0)),
115 egui::Color32::WHITE,
116 );
117
118 (response, image_rect)
119 }
120
121 pub fn has_texture(&self) -> bool {
122 self.texture.is_some()
123 }
124
125 #[allow(clippy::cast_precision_loss)]
128 pub fn show_zoomed(
129 &self,
130 ui: &mut Ui,
131 desired_size: Vec2,
132 center: (f32, f32),
133 factor: f32,
134 ) -> Response {
135 let Some(tex) = &self.texture else {
136 let (rect, response) = ui.allocate_exact_size(desired_size, egui::Sense::hover());
137 ui.painter().rect_filled(rect, 0.0, egui::Color32::from_gray(40));
138 return response;
139 };
140
141 let tex_aspect = self.width as f32 / self.height.max(1) as f32;
142 let box_aspect = desired_size.x / desired_size.y.max(1.0);
143
144 let display_size = if tex_aspect > box_aspect {
145 Vec2::new(desired_size.x, desired_size.x / tex_aspect)
146 } else {
147 Vec2::new(desired_size.y * tex_aspect, desired_size.y)
148 };
149
150 let (rect, response) = ui.allocate_exact_size(desired_size, egui::Sense::hover());
151
152 let offset = (desired_size - display_size) / 2.0;
153 let image_rect = egui::Rect::from_min_size(rect.min + offset, display_size);
154
155 ui.painter().rect_filled(rect, 0.0, egui::Color32::BLACK);
156
157 let half_u = 1.0 / (factor * 2.0);
159 let half_v = 1.0 / (factor * 2.0);
160 let u_center = center.0.clamp(half_u, 1.0 - half_u);
161 let v_center = center.1.clamp(half_v, 1.0 - half_v);
162 let uv = egui::Rect::from_min_max(
163 egui::pos2(u_center - half_u, v_center - half_v),
164 egui::pos2(u_center + half_u, v_center + half_v),
165 );
166
167 ui.painter().image(tex.id(), image_rect, uv, egui::Color32::WHITE);
168
169 response
170 }
171}
172
173impl Default for SlideThumbnail {
174 fn default() -> Self {
175 Self::new()
176 }
177}