mirror of
https://github.com/storytold/deckcraft.git
synced 2026-11-09 03:42:11 +00:00
UI: follow Grayscale view mode in slide thumbnails
In PowerPoint, turning on Grayscale view mode turns both the active slide canvas and the slide thumbnail pane to grayscale, while Print retains the original full color for output. Previously, DeckCraft only passed `app.ui.grayscale` to the main canvas texture renderer (`Textures::slide`), while thumbnail rendering (`Textures::thumb`) hardcoded `false` and lacked `grayscale` in its cache key. As a result, when Grayscale mode was enabled, the canvas rendered in grayscale but the thumbnail pane remained in full color. - Update `Textures::thumb` to accept `grayscale: bool` and include it in the cache key (`extra: grayscale as u64`). - Pass `app.ui.grayscale` to `Textures::thumb` in the thumbnail pane (`panels/thumbs.rs`), Slide Sorter & Notes Page (`sorter.rs`), and Notes Master view (`canvas.rs`). - Keep Print preview (`backstage/print.rs`) in full color (`false`) to match the actual exported PDF print output. - Add regression test `to_color_image_turns_colors_to_grayscale_when_flag_is_set`.
This commit is contained in:
1 parent
6431460e80
commit
b06d60b8ed
5 files changed
+33
-8
No files matched your search
@@ -207,7 +207,8 @@ fn preview(app: &mut SlideApp, ui: &mut Ui, bs: &mut Backstage, doc: &deckcraft_
|
||||
let mut budget = 4u32;
|
||||
let mut draw_slide = |ui: &mut Ui, i: usize, r: Rect| {
|
||||
let px = (r.width() * ui.ctx().pixels_per_point()).round().clamp(16.0, 1600.0) as u32;
|
||||
match app.textures.thumb(ui.ctx(), doc, i, px, &mut budget) {
|
||||
// Print preview matches the actual exported PDF output (always full color).
|
||||
match app.textures.thumb(ui.ctx(), doc, i, px, false, &mut budget) {
|
||||
Some(tex) => {
|
||||
ui.painter().image(tex.id(), r, Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)), Color32::WHITE);
|
||||
}
|
||||
|
||||
@@ -299,7 +299,7 @@ pub fn show(app: &mut SlideApp, ui: &mut Ui) {
|
||||
vec2(w as f32 * scale, h as f32 * scale),
|
||||
);
|
||||
let mut budget = 2;
|
||||
if let Some(tex) = app.textures.thumb(ui.ctx(), &doc, idx, (r.width() * ppp).round().max(1.0) as u32, &mut budget) {
|
||||
if let Some(tex) = app.textures.thumb(ui.ctx(), &doc, idx, (r.width() * ppp).round().max(1.0) as u32, app.ui.grayscale, &mut budget) {
|
||||
painter.image(tex.id(), r, Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)), Color32::WHITE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ fn list(app: &mut SlideApp, ui: &mut Ui) {
|
||||
// Only rows in view (plus a little margin) ask for a thumbnail: with every row asking, a
|
||||
// deck bigger than the thumbnail cache evicts its own thumbnails and never settles (#969).
|
||||
let near = ui.clip_rect().expand2(vec2(0.0, 2.0 * row.height())).intersects(row);
|
||||
let tex = if near { app.textures.thumb(ui.ctx(), &doc, i, wpx, &mut budget) } else { None };
|
||||
let tex = if near { app.textures.thumb(ui.ctx(), &doc, i, wpx, app.ui.grayscale, &mut budget) } else { None };
|
||||
match tex {
|
||||
Some(tex) => {
|
||||
p.image(
|
||||
|
||||
@@ -209,7 +209,8 @@ fn place_slides(app: &mut SlideApp, ui: &mut Ui, g: &Grid<'_>, budget: &mut u32,
|
||||
// Only cells in view (plus a little margin) ask for a thumbnail, so a deck bigger
|
||||
// than the thumbnail cache does not evict its own thumbnails forever (#969).
|
||||
let near = ui.clip_rect().expand2(vec2(0.0, 2.0 * cell.height())).intersects(cell);
|
||||
let tex = if near { app.textures.thumb(ui.ctx(), g.doc, i, (img.width() * ppp).round() as u32, budget) } else { None };
|
||||
let tex =
|
||||
if near { app.textures.thumb(ui.ctx(), g.doc, i, (img.width() * ppp).round() as u32, app.ui.grayscale, budget) } else { None };
|
||||
match tex {
|
||||
Some(tex) => {
|
||||
p.image(tex.id(), img, Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)), Color32::WHITE);
|
||||
@@ -391,7 +392,7 @@ pub fn notes_page(app: &mut SlideApp, ui: &mut Ui) {
|
||||
}
|
||||
let ppp = ui.ctx().pixels_per_point();
|
||||
let mut budget = 2;
|
||||
if let Some(tex) = app.textures.thumb(ui.ctx(), &doc, idx, (srect.width() * ppp).round() as u32, &mut budget) {
|
||||
if let Some(tex) = app.textures.thumb(ui.ctx(), &doc, idx, (srect.width() * ppp).round() as u32, app.ui.grayscale, &mut budget) {
|
||||
ui.painter().image(tex.id(), srect, Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)), Color32::WHITE);
|
||||
}
|
||||
ui.painter().rect_stroke(srect, CornerRadius::ZERO, Stroke::new(1.0, Color32::GRAY), egui::StrokeKind::Outside);
|
||||
|
||||
@@ -363,7 +363,16 @@ impl Textures {
|
||||
|
||||
/// A thumbnail texture; renders at most `budget` new thumbnails per frame (returns None when
|
||||
/// it is over budget, so the caller draws a placeholder and asks for another frame).
|
||||
pub fn thumb(&mut self, ctx: &egui::Context, p: &Presentation, index: usize, width_px: u32, budget: &mut u32) -> Option<TextureHandle> {
|
||||
/// When `grayscale` is true, the thumbnail renders in black-and-white (matching Grayscale view).
|
||||
pub fn thumb(
|
||||
&mut self,
|
||||
ctx: &egui::Context,
|
||||
p: &Presentation,
|
||||
index: usize,
|
||||
width_px: u32,
|
||||
grayscale: bool,
|
||||
budget: &mut u32,
|
||||
) -> Option<TextureHandle> {
|
||||
let slide = p.slides.get(index)?.clone();
|
||||
let master = master_of(p, &slide);
|
||||
let h = (width_px as f64 * p.slide_size.height / p.slide_size.width.max(1.0)).round().max(1.0) as u32;
|
||||
@@ -372,7 +381,7 @@ impl Textures {
|
||||
master: master.as_ref().map(|m| Arc::as_ptr(m) as usize).unwrap_or(0),
|
||||
size: (width_px, h),
|
||||
edit: false,
|
||||
extra: 0,
|
||||
extra: grayscale as u64,
|
||||
want: Region::slide(width_px, h),
|
||||
editing_text: None,
|
||||
};
|
||||
@@ -393,7 +402,7 @@ impl Textures {
|
||||
let scale = tw as f64 / p.slide_size.width.max(1.0);
|
||||
let img = deckcraft_render::render_slide(p, index, &RenderOpts { scale, size: Some((tw, th)), threads: 0, ..Default::default() });
|
||||
self.thumb_renders += 1;
|
||||
let tex = upload_editor(ctx, format!("thumb-{index}"), to_color_image(&img, false));
|
||||
let tex = upload_editor(ctx, format!("thumb-{index}"), to_color_image(&img, grayscale));
|
||||
// Drop the old entry for this slide address; entries for slides that no longer exist age out.
|
||||
let region = key.want;
|
||||
self.thumbs.insert(key.slide, Entry { key, tex: tex.clone(), region, _slide: slide, _master: master, stamp: clock });
|
||||
@@ -433,6 +442,20 @@ mod tests {
|
||||
assert_eq!(device_pixel_rect(aligned, ppp), aligned);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_color_image_turns_colors_to_grayscale_when_flag_is_set() {
|
||||
let img = deckcraft_render::Image { width: 2, height: 1, pixels: vec![0, 0, 255, 255, 255, 0, 0, 255] };
|
||||
let color = super::to_color_image(&img, false);
|
||||
assert_eq!(color.pixels[0], egui::Color32::from_rgba_premultiplied(0, 0, 255, 255));
|
||||
assert_eq!(color.pixels[1], egui::Color32::from_rgba_premultiplied(255, 0, 0, 255));
|
||||
|
||||
let gray = super::to_color_image(&img, true);
|
||||
assert_eq!(gray.pixels[0].r(), gray.pixels[0].g());
|
||||
assert_eq!(gray.pixels[0].g(), gray.pixels[0].b());
|
||||
assert_eq!(gray.pixels[1].r(), gray.pixels[1].g());
|
||||
assert_eq!(gray.pixels[1].g(), gray.pixels[1].b());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_bad_scale_does_not_move_the_rect() {
|
||||
let r = Rect::from_min_size(pos2(3.0, 4.0), vec2(10.0, 12.0));
|
||||
|
||||
Reference in new issue
Block a user