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 self.show_with_image_rect(ui, desired_size).0
43 }
44
45 #[allow(clippy::cast_precision_loss)]
48 pub fn show_with_image_rect(&self, ui: &mut Ui, desired_size: Vec2) -> (Response, egui::Rect) {
49 let Some(tex) = &self.texture else {
50 let (rect, response) = ui.allocate_exact_size(desired_size, egui::Sense::hover());
51 ui.painter().rect_filled(rect, 0.0, egui::Color32::from_gray(40));
52 return (response, rect);
53 };
54
55 let tex_aspect = self.width as f32 / self.height.max(1) as f32;
56 let box_aspect = desired_size.x / desired_size.y.max(1.0);
57
58 let display_size = if tex_aspect > box_aspect {
59 Vec2::new(desired_size.x, desired_size.x / tex_aspect)
60 } else {
61 Vec2::new(desired_size.y * tex_aspect, desired_size.y)
62 };
63
64 let (rect, response) = ui.allocate_exact_size(desired_size, egui::Sense::hover());
65
66 let offset = (desired_size - display_size) / 2.0;
67 let image_rect = egui::Rect::from_min_size(rect.min + offset, display_size);
68
69 ui.painter().rect_filled(rect, 0.0, egui::Color32::BLACK);
70
71 ui.painter().image(
72 tex.id(),
73 image_rect,
74 egui::Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0)),
75 egui::Color32::WHITE,
76 );
77
78 (response, image_rect)
79 }
80
81 #[allow(clippy::cast_precision_loss)]
84 pub fn show_interactive(&self, ui: &mut Ui, desired_size: Vec2) -> (Response, egui::Rect) {
85 self.show_with_sense(ui, desired_size, egui::Sense::click_and_drag())
86 }
87
88 #[allow(clippy::cast_precision_loss)]
90 pub fn show_with_sense(
91 &self,
92 ui: &mut Ui,
93 desired_size: Vec2,
94 sense: Sense,
95 ) -> (Response, egui::Rect) {
96 let Some(tex) = &self.texture else {
97 let (rect, response) = ui.allocate_exact_size(desired_size, sense);
98 ui.painter().rect_filled(rect, 0.0, egui::Color32::from_gray(40));
99 return (response, rect);
100 };
101
102 let tex_aspect = self.width as f32 / self.height.max(1) as f32;
103 let box_aspect = desired_size.x / desired_size.y.max(1.0);
104
105 let display_size = if tex_aspect > box_aspect {
106 Vec2::new(desired_size.x, desired_size.x / tex_aspect)
107 } else {
108 Vec2::new(desired_size.y * tex_aspect, desired_size.y)
109 };
110
111 let (rect, response) = ui.allocate_exact_size(desired_size, sense);
112
113 let offset = (desired_size - display_size) / 2.0;
114 let image_rect = egui::Rect::from_min_size(rect.min + offset, display_size);
115
116 ui.painter().rect_filled(rect, 0.0, egui::Color32::BLACK);
117
118 ui.painter().image(
119 tex.id(),
120 image_rect,
121 egui::Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0)),
122 egui::Color32::WHITE,
123 );
124
125 (response, image_rect)
126 }
127
128 pub fn has_texture(&self) -> bool {
129 self.texture.is_some()
130 }
131
132 #[allow(clippy::cast_precision_loss)]
135 pub fn show_zoomed(
136 &self,
137 ui: &mut Ui,
138 desired_size: Vec2,
139 center: (f32, f32),
140 factor: f32,
141 ) -> Response {
142 self.show_zoomed_with_image_rect(ui, desired_size, center, factor).0
143 }
144
145 #[allow(clippy::cast_precision_loss)]
147 pub fn show_zoomed_with_image_rect(
148 &self,
149 ui: &mut Ui,
150 desired_size: Vec2,
151 center: (f32, f32),
152 factor: f32,
153 ) -> (Response, egui::Rect) {
154 let Some(tex) = &self.texture else {
155 let (rect, response) = ui.allocate_exact_size(desired_size, egui::Sense::hover());
156 ui.painter().rect_filled(rect, 0.0, egui::Color32::from_gray(40));
157 return (response, rect);
158 };
159
160 let tex_aspect = self.width as f32 / self.height.max(1) as f32;
161 let box_aspect = desired_size.x / desired_size.y.max(1.0);
162
163 let display_size = if tex_aspect > box_aspect {
164 Vec2::new(desired_size.x, desired_size.x / tex_aspect)
165 } else {
166 Vec2::new(desired_size.y * tex_aspect, desired_size.y)
167 };
168
169 let (rect, response) = ui.allocate_exact_size(desired_size, egui::Sense::hover());
170
171 let offset = (desired_size - display_size) / 2.0;
172 let image_rect = egui::Rect::from_min_size(rect.min + offset, display_size);
173
174 ui.painter().rect_filled(rect, 0.0, egui::Color32::BLACK);
175
176 let half_u = 1.0 / (factor * 2.0);
178 let half_v = 1.0 / (factor * 2.0);
179 let u_center = center.0.clamp(half_u, 1.0 - half_u);
180 let v_center = center.1.clamp(half_v, 1.0 - half_v);
181 let uv = egui::Rect::from_min_max(
182 egui::pos2(u_center - half_u, v_center - half_v),
183 egui::pos2(u_center + half_u, v_center + half_v),
184 );
185
186 ui.painter().image(tex.id(), image_rect, uv, egui::Color32::WHITE);
187
188 (response, image_rect)
189 }
190}
191
192impl Default for SlideThumbnail {
193 fn default() -> Self {
194 Self::new()
195 }
196}